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

Compare commits

..

11 Commits
v4.2.7 ... v4.0

Author SHA1 Message Date
David Grudl
fc5e7db484 SqlsrvDriver: workaround for "Driver's SQLSetConnectAttr failed on ODBC <=13" bug 2021-03-10 16:43:12 +01:00
David Grudl
44b420f70d Released version 4.0.3 2020-03-26 03:54:02 +01:00
David Grudl
4689101b88 SqliteResult: workaround for PHP bug 79414 2020-03-26 03:54:02 +01:00
David Grudl
db16b9e87a Result: does not drop the value if detection fails 2020-03-26 03:54:02 +01:00
Adam Klvač
803c9d539c MySqliDriver: coalesced password to an empty string (#360)
mysqli::real_connect() expects parameter 3 to be string, yields an error on NULL.
2020-03-26 03:54:02 +01:00
David Grudl
e137dfa28b Result::fetchAssoc() DateTime in key is converted to string [Closes #359] 2020-03-26 03:54:02 +01:00
Milan Pála
9651835f5b tests: added reconnect test (#352) 2020-03-26 03:54:02 +01:00
David Grudl
2cbebc02c4 Connection: translator is created/destructed in connect/disconnect [Closes #352][Closes #354] 2020-03-26 03:54:02 +01:00
David Grudl
4d647c2aed travis: fixed databases 2020-03-26 03:51:13 +01:00
David Grudl
5c5838aee4 travis: added PHP 7.4 2020-03-26 03:47:28 +01:00
David Grudl
7df72fd6cf fixes for PHP 7.4 2020-03-26 03:45:50 +01:00
94 changed files with 1033 additions and 1858 deletions

6
.gitattributes vendored
View File

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

1
.github/funding.yml vendored
View File

@@ -1 +0,0 @@
github: dg

View File

@@ -1,31 +0,0 @@
name: Coding Style
on: [push, pull_request]
jobs:
nette_cc:
name: Nette Code Checker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- 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@v3
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
coverage: none
- run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
- run: php temp/coding-standard/ecs check

View File

@@ -1,21 +0,0 @@
name: Static Analysis (only informative)
on:
push:
branches:
- master
jobs:
phpstan:
name: PHPStan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- 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

View File

@@ -1,121 +0,0 @@
name: Tests
on: [push, pull_request]
env:
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.10.0beta2, pdo_sqlsrv-5.10.0beta2
php-tools: "composer:v2, pecl"
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
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@v3
- 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@v3
with:
name: output
path: tests/**/output
- name: Save Code Coverage
if: ${{ matrix.php == '8.0' }}
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar
php php-coveralls.phar --verbose --config tests/.coveralls.yml

77
.travis.yml Normal file
View File

@@ -0,0 +1,77 @@
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
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
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)
install:
# Install PHPStan
- travis_retry composer create-project phpstan/phpstan-shim temp/phpstan --no-progress
- travis_retry composer install --no-progress --prefer-dist
script:
- php temp/phpstan/phpstan.phar analyse --autoload-file vendor/autoload.php --level 5 src
- stage: Code Coverage
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.2 # Install PHP 7.1
- 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.2.18-Win32-VC15-x64.zip --output php.zip - 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 7z x php.zip >nul - IF %PHP%==1 7z x php.zip >nul
- IF %PHP%==1 echo extension_dir=ext >> php.ini - IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- IF %PHP%==1 curl https://github.com/microsoft/msphpsql/releases/download/v5.8.0/Windows-7.2.zip -L --output sqlsrv.zip - IF %PHP%==1 appveyor DownloadFile https://files.nette.org/misc/php-sqlsrv.zip
- IF %PHP%==1 7z x sqlsrv.zip >nul - IF %PHP%==1 7z x php-sqlsrv.zip >nul
- IF %PHP%==1 copy Windows-7.2\x64\php_sqlsrv_72_ts.dll ext\php_sqlsrv_ts.dll - IF %PHP%==1 copy SQLSRV\php_sqlsrv_71_ts.dll ext\php_sqlsrv_71_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

@@ -3,7 +3,7 @@
"description": "Dibi is Database Abstraction Library for PHP", "description": "Dibi is Database Abstraction Library for PHP",
"keywords": ["database", "dbal", "mysql", "postgresql", "sqlite", "mssql", "sqlsrv", "oracle", "access", "pdo", "odbc"], "keywords": ["database", "dbal", "mysql", "postgresql", "sqlite", "mssql", "sqlsrv", "oracle", "access", "pdo", "odbc"],
"homepage": "https://dibiphp.com", "homepage": "https://dibiphp.com",
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], "license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [ "authors": [
{ {
"name": "David Grudl", "name": "David Grudl",
@@ -11,13 +11,11 @@
} }
], ],
"require": { "require": {
"php": ">=7.2" "php": ">=7.1"
}, },
"require-dev": { "require-dev": {
"tracy/tracy": "~2.2", "tracy/tracy": "~2.2",
"nette/tester": "~2.0", "nette/tester": "~2.0"
"nette/di": "^3.0",
"phpstan/phpstan": "^0.12"
}, },
"replace": { "replace": {
"dg/dibi": "*" "dg/dibi": "*"
@@ -25,13 +23,9 @@
"autoload": { "autoload": {
"classmap": ["src/"] "classmap": ["src/"]
}, },
"scripts": {
"phpstan": "phpstan analyse",
"tester": "tester tests -s"
},
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.2-dev" "dev-master": "4.0-dev"
} }
} }
} }

View File

@@ -20,7 +20,6 @@ $dibi = new Dibi\Connection([
// enable query logging to this file // enable query logging to this file
'profiler' => [ 'profiler' => [
'file' => 'log/log.sql', 'file' => 'log/log.sql',
'errorsOnly' => false,
], ],
]); ]);

View File

@@ -1,9 +0,0 @@
parameters:
level: 5
paths:
- src
ignoreErrors:
# The namespace is referenced, not the class.
- '#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)
[![Tests](https://github.com/dg/dibi/workflows/Tests/badge.svg?branch=master)](https://github.com/dg/dibi/actions) [![Build Status](https://travis-ci.org/dg/dibi.svg?branch=master)](https://travis-ci.org/dg/dibi)
[![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)
@@ -14,15 +14,7 @@ Introduction
Database access functions in PHP are not standardised. This library 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.
If you like Dibi, **[please make a donation now](https://nette.org/make-donation?to=dibi)**. Thank you!
Support Me
----------
Do you like Dibi? Are you looking forward to the new features?
[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
Thank you!
Installation Installation
@@ -34,7 +26,7 @@ Install Dibi via Composer:
composer require dibi/dibi composer require dibi/dibi
``` ```
The Dibi 4.2 requires PHP version 7.2 and supports PHP up to 8.2. The Dibi 4.0 requires PHP version 7.1 and supports PHP up to 7.4.
Usage Usage
@@ -50,11 +42,11 @@ The database connection is represented by the object `Dibi\Connection`:
```php ```php
$database = new Dibi\Connection([ $database = new Dibi\Connection([
'driver' => 'mysqli', 'driver' => 'mysqli',
'host' => 'localhost', 'host' => 'localhost',
'username' => 'root', 'username' => 'root',
'password' => '***', 'password' => '***',
'database' => 'table', 'database' => 'table',
]); ]);
$result = $database->query('SELECT * FROM users'); $result = $database->query('SELECT * FROM users');
@@ -64,12 +56,12 @@ Alternatively, you can use the `dibi` static register, which maintains a connect
```php ```php
dibi::connect([ dibi::connect([
'driver' => 'mysqli', 'driver' => 'mysqli',
'host' => 'localhost', 'host' => 'localhost',
'username' => 'root', 'username' => 'root',
'password' => '***', 'password' => '***',
'database' => 'test', 'database' => 'test',
'charset' => 'utf8', 'charset' => 'utf8',
]); ]);
$result = dibi::query('SELECT * FROM users'); $result = dibi::query('SELECT * FROM users');
@@ -83,8 +75,6 @@ In the event of a connection error, it throws `Dibi\Exception`.
We query the database queries by the method `query()` which returns `Dibi\Result`. Rows are objects `Dibi\Row`. We query the database queries by the method `query()` which returns `Dibi\Result`. Rows are objects `Dibi\Row`.
You can try all the examples [online at the playground](https://repl.it/@DavidGrudl/dibi-playground).
```php ```php
$result = $database->query('SELECT * FROM users'); $result = $database->query('SELECT * FROM users');
@@ -193,7 +183,7 @@ $result = $database->query('SELECT * FROM users WHERE id IN (%i)', $ids);
// SELECT * FROM users WHERE id IN (10, 20, 30) // SELECT * FROM users WHERE id IN (10, 20, 30)
``` ```
The modifier `%n` is used if the table or column name is a variable. (Beware, do not allow the user to manipulate the content of such a variable): The modifier '%n' is used if the table or column name is a variable. (Beware, do not allow the user to manipulate the content of such a variable):
```php ```php
$table = 'blog.users'; $table = 'blog.users';
@@ -209,7 +199,6 @@ Three special modifiers are available for LIKE:
| `%like~` | the expression starts with a string | `%like~` | the expression starts with a string
| `%~like` | the expression ends with a string | `%~like` | the expression ends with a string
| `%~like~` | the expression contains a string | `%~like~` | the expression contains a string
| `%like` | the expression matches a string
Search for names beginning with a string: Search for names beginning with a string:
@@ -237,8 +226,8 @@ Example:
```php ```php
$arr = [ $arr = [
'a' => 'hello', 'a' => 'hello',
'b' => true, 'b' => true,
]; ];
$database->query('INSERT INTO table %v', $arr); $database->query('INSERT INTO table %v', $arr);
@@ -510,7 +499,7 @@ $all = $result->fetchAssoc('customer_id|order_id');
// we will iterate like this: // we will iterate like this:
foreach ($all as $customerId => $orders) { foreach ($all as $customerId => $orders) {
foreach ($orders as $orderId => $order) { foreach ($orders as $orderId => $order) {
... ...
} }
} }
``` ```
@@ -542,7 +531,7 @@ $all = $result->fetchAssoc('name[]order_id');
// we get all the Arnolds in the results // we get all the Arnolds in the results
foreach ($all['Arnold Rimmer'] as $arnoldOrders) { foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
foreach ($arnoldOrders as $orderId => $order) { foreach ($arnoldOrders as $orderId => $order) {
... ...
} }
} }
``` ```
@@ -556,8 +545,8 @@ foreach ($all as $customerId => $orders) {
echo "Customer $customerId": echo "Customer $customerId":
foreach ($orders as $orderId => $order) { foreach ($orders as $orderId => $order) {
echo "ID number: $order->number"; echo "ID number: $order->number";
// customer name is in $order->name // customer name is in $order->name
} }
} }
``` ```
@@ -579,7 +568,7 @@ foreach ($all as $customerId => $row) {
echo "Customer $row->name": echo "Customer $row->name":
foreach ($row->order_id as $orderId => $order) { foreach ($row->order_id as $orderId => $order) {
echo "ID number: $order->number"; echo "ID number: $order->number";
} }
} }
``` ```

View File

@@ -22,14 +22,10 @@ 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;
} }
@@ -42,11 +38,7 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
$this->debugMode = $container->parameters['debugMode']; $this->debugMode = $container->parameters['debugMode'];
} }
if ($this->cliMode === null) { $useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode);
$this->cliMode = $container->parameters['consoleMode'];
}
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode && !$this->cliMode);
unset($config['profiler']); unset($config['profiler']);
@@ -55,7 +47,6 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
foreach ((array) $config['flags'] as $flag) { foreach ((array) $config['flags'] as $flag) {
$flags |= constant($flag); $flags |= constant($flag);
} }
$config['flags'] = $flags; $config['flags'] = $flags;
} }
@@ -69,7 +60,6 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']] [[Dibi\Bridges\Tracy\Panel::class, 'renderException']]
); );
} }
if ($useProfiler) { if ($useProfiler) {
$panel = $container->addDefinition($this->prefix('panel')) $panel = $container->addDefinition($this->prefix('panel'))
->setFactory(Dibi\Bridges\Tracy\Panel::class, [ ->setFactory(Dibi\Bridges\Tracy\Panel::class, [

View File

@@ -35,7 +35,7 @@ class Panel implements Tracy\IBarPanel
private $events = []; private $events = [];
public function __construct($explain = true, ?int $filter = null) public function __construct($explain = true, int $filter = null)
{ {
$this->filter = $filter ?: Event::QUERY; $this->filter = $filter ?: Event::QUERY;
$this->explain = $explain; $this->explain = $explain;
@@ -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([self::class, 'renderException']); Tracy\Debugger::getBlueScreen()->addPanel([__CLASS__, 'renderException']);
$connection->onEvent[] = [$this, 'logEvent']; $connection->onEvent[] = [$this, 'logEvent'];
} }
@@ -58,7 +58,6 @@ class Panel implements Tracy\IBarPanel
if (($event->type & $this->filter) === 0) { if (($event->type & $this->filter) === 0) {
return; return;
} }
$this->events[] = $event; $this->events[] = $event;
} }
@@ -89,10 +88,9 @@ class Panel implements Tracy\IBarPanel
foreach ($this->events as $event) { foreach ($this->events as $event) {
$totalTime += $event->time; $totalTime += $event->time;
} }
return '<span title="dibi"><svg viewBox="0 0 2048 2048" style="vertical-align: bottom; width:1.23em; height:1.55em"><path fill="' . ($count ? '#b079d6' : '#aaa') . '" d="M1024 896q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-1152q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-128q0-69 103-128t280-93.5 385-34.5z"/></svg><span class="tracy-label">' return '<span title="dibi"><svg viewBox="0 0 2048 2048" style="vertical-align: bottom; width:1.23em; height:1.55em"><path fill="' . ($count ? '#b079d6' : '#aaa') . '" d="M1024 896q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-1152q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-128q0-69 103-128t280-93.5 385-34.5z"/></svg><span class="tracy-label">'
. $count . "\u{a0}queries" . $count . ' queries'
. ($totalTime ? ' / ' . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms" : '') . ($totalTime ? ' / ' . number_format($totalTime * 1000, 1, '.', '') . 'ms' : '')
. '</span></span>'; . '</span></span>';
} }
@@ -107,15 +105,6 @@ class Panel implements Tracy\IBarPanel
} }
$totalTime = $s = null; $totalTime = $s = null;
$singleConnection = reset($this->events)->connection;
foreach ($this->events as $event) {
if ($event->connection !== $singleConnection) {
$singleConnection = null;
break;
}
}
foreach ($this->events as $event) { foreach ($this->events as $event) {
$totalTime += $event->time; $totalTime += $event->time;
$connection = $event->connection; $connection = $event->connection;
@@ -123,18 +112,15 @@ 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) $cmd = is_string($this->explain) ? $this->explain : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : '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) {
} }
[$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup; [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup;
} }
$s .= '<tr><td data-order="' . $event->time . '">' . number_format($event->time * 1000, 3, '.', "\u{202f}"); $s .= '<tr><td>' . number_format($event->time * 1000, 3, '.', '');
if ($explain) { if ($explain) {
static $counter; static $counter;
$counter++; $counter++;
@@ -145,37 +131,24 @@ class Panel implements Tracy\IBarPanel
if ($explain) { if ($explain) {
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>"; $s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
} }
if ($event->source) { if ($event->source) {
$s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]); //->class('tracy-DibiProfiler-source'); $s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]);//->class('tracy-DibiProfiler-source');
} }
$s .= "</td><td>{$event->count}</td>"; $s .= "</td><td>{$event->count}</td></tr>";
if (!$singleConnection) {
$s .= '<td>' . htmlspecialchars($this->getConnectionName($connection)) . '</td></tr>';
}
} }
return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important } return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important }
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important } #tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style> #tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
<h1>Queries:' . "\u{a0}" . count($this->events) <h1>Queries: ' . count($this->events)
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', ' . ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', '') . 'ms') . ', '
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1> . htmlspecialchars($connection->getConfig('driver') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? '@' . $connection->getConfig('host') : '')) . '</h1>
<div class="tracy-inner tracy-DibiProfiler"> <div class="tracy-inner tracy-DibiProfiler">
<table class="tracy-sortable"> <table>
<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></tr>' . $s . '
' . $s . '
</table> </table>
</div>'; </div>';
} }
private function getConnectionName(Dibi\Connection $connection): string
{
$driver = $connection->getConfig('driver');
return (is_object($driver) ? get_class($driver) : $driver)
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
}
} }

View File

@@ -28,9 +28,6 @@ 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;
@@ -40,36 +37,38 @@ 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
* - normalize => normalizes result fields (default: true) * - formatDateTime => date-time format (if empty, DateTime objects will be returned)
* - formatDateTime => date-time format
* empty for decoding as Dibi\DateTime (default)
* "..." formatted according to given format, see https://www.php.net/manual/en/datetime.format.php
* "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
* - errorsOnly (bool) => log only errors
* - substitutes (array) => map of driver specific substitutes (under development) * - substitutes (array) => map of driver specific substitutes (under development)
* - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established * - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
* @param array $config connection parameters
* @throws Exception * @throws Exception
*/ */
public function __construct(array $config, ?string $name = null) public function __construct($config, string $name = null)
{ {
if (is_string($config)) {
trigger_error(__METHOD__ . '() Configuration should be array.', E_USER_DEPRECATED);
parse_str($config, $config);
} elseif ($config instanceof Traversable) {
trigger_error(__METHOD__ . '() Configuration should be array.', E_USER_DEPRECATED);
$tmp = [];
foreach ($config as $key => $val) {
$tmp[$key] = $val instanceof Traversable ? iterator_to_array($val) : $val;
}
$config = $tmp;
} elseif (!is_array($config)) {
throw new \InvalidArgumentException('Configuration must be array.');
}
Helpers::alias($config, 'username', 'user'); Helpers::alias($config, 'username', 'user');
Helpers::alias($config, 'password', 'pass'); Helpers::alias($config, 'password', 'pass');
Helpers::alias($config, 'host', 'hostname'); Helpers::alias($config, 'host', 'hostname');
@@ -79,18 +78,10 @@ class Connection implements IConnection
$config['name'] = $name; $config['name'] = $name;
$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;
$errorsOnly = $config['profiler']['errorsOnly'] ?? false; $this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter), 'logEvent'];
$this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter, $errorsOnly), 'logEvent'];
} }
$this->substitutes = new HashMap(function (string $expr) { return ":$expr:"; }); $this->substitutes = new HashMap(function (string $expr) { return ":$expr:"; });
@@ -150,17 +141,16 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
if (isset($this->config['onConnect'])) { if (isset($this->config['onConnect'])) {
foreach ($this->config['onConnect'] as $sql) { foreach ($this->config['onConnect'] as $sql) {
$this->query($sql); $this->query($sql);
} }
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -192,7 +182,7 @@ class Connection implements IConnection
* @see self::__construct * @see self::__construct
* @return mixed * @return mixed
*/ */
final public function getConfig(?string $key = null, $default = null) final public function getConfig(string $key = null, $default = null)
{ {
return $key === null return $key === null
? $this->config ? $this->config
@@ -208,7 +198,6 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
return $this->driver; return $this->driver;
} }
@@ -220,7 +209,7 @@ class Connection implements IConnection
*/ */
final public function query(...$args): Result final public function query(...$args): Result
{ {
return $this->nativeQuery($this->translate(...$args)); return $this->nativeQuery($this->translateArgs($args));
} }
@@ -231,11 +220,7 @@ class Connection implements IConnection
*/ */
final public function translate(...$args): string final public function translate(...$args): string
{ {
if (!$this->driver) { return $this->translateArgs($args);
$this->connect();
}
return (clone $this->translator)->translate($args);
} }
@@ -246,7 +231,7 @@ class Connection implements IConnection
final public function test(...$args): bool final public function test(...$args): bool
{ {
try { try {
Helpers::dump($this->translate(...$args)); Helpers::dump($this->translateArgs($args));
return true; return true;
} catch (Exception $e) { } catch (Exception $e) {
@@ -255,7 +240,6 @@ class Connection implements IConnection
} else { } else {
echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>'); echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
} }
return false; return false;
} }
} }
@@ -268,7 +252,19 @@ class Connection implements IConnection
*/ */
final public function dataSource(...$args): DataSource final public function dataSource(...$args): DataSource
{ {
return new DataSource($this->translate(...$args), $this); return new DataSource($this->translateArgs($args), $this);
}
/**
* Generates SQL query.
*/
protected function translateArgs(array $args): string
{
if (!$this->driver) {
$this->connect();
}
return (clone $this->translator)->translate($args);
} }
@@ -291,7 +287,6 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
@@ -299,7 +294,6 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done($res)); $this->onEvent($event->done($res));
} }
return $res; return $res;
} }
@@ -313,59 +307,70 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$rows = $this->driver->getAffectedRows(); $rows = $this->driver->getAffectedRows();
if ($rows === null || $rows < 0) { if ($rows === null || $rows < 0) {
throw new Exception('Cannot retrieve number of affected rows.'); throw new Exception('Cannot retrieve number of affected rows.');
} }
return $rows; return $rows;
} }
/**
* @deprecated
*/
public function affectedRows(): int
{
trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
return $this->getAffectedRows();
}
/** /**
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query. * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
* @throws Exception * @throws Exception
*/ */
public function getInsertId(?string $sequence = null): int public function getInsertId(string $sequence = null): int
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$id = $this->driver->getInsertId($sequence); $id = $this->driver->getInsertId($sequence);
if ($id === null) { if ($id < 1) {
throw new Exception('Cannot retrieve last generated ID.'); throw new Exception('Cannot retrieve last generated ID.');
} }
return $id; return $id;
} }
/**
* @deprecated
*/
public function insertId(string $sequence = null): int
{
trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
return $this->getInsertId($sequence);
}
/** /**
* Begins a transaction (if supported). * Begins a transaction (if supported).
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null;
try { try {
$this->driver->begin($savepoint); $this->driver->begin($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -374,27 +379,22 @@ class Connection implements IConnection
/** /**
* Commits statements in a transaction. * Commits statements in a transaction.
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null;
try { try {
$this->driver->commit($savepoint); $this->driver->commit($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -403,69 +403,35 @@ class Connection implements IConnection
/** /**
* Rollback changes in a transaction. * Rollback changes in a transaction.
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null;
try { try {
$this->driver->rollback($savepoint); $this->driver->rollback($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
/**
* @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
{ {
return (new Result($resultDriver, $this->config['result']['normalize'] ?? true)) $res = new Result($resultDriver);
->setFormats($this->formats); return $res->setFormat(Type::DATE, $this->config['result']['formatDate'])
->setFormat(Type::DATETIME, $this->config['result']['formatDateTime']);
} }
@@ -498,7 +464,6 @@ class Connection implements IConnection
if ($args instanceof Traversable) { if ($args instanceof Traversable) {
$args = iterator_to_array($args); $args = iterator_to_array($args);
} }
return $this->command()->insert() return $this->command()->insert()
->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args); ->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args);
} }
@@ -602,7 +567,7 @@ class Connection implements IConnection
* @param callable $onProgress function (int $count, ?float $percent): void * @param callable $onProgress function (int $count, ?float $percent): void
* @return int count of sql commands * @return int count of sql commands
*/ */
public function loadFile(string $file, ?callable $onProgress = null): int public function loadFile(string $file, callable $onProgress = null): int
{ {
return Helpers::loadFromFile($this, $file, $onProgress); return Helpers::loadFromFile($this, $file, $onProgress);
} }
@@ -616,7 +581,6 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null); return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
} }
@@ -626,7 +590,7 @@ class Connection implements IConnection
*/ */
public function __wakeup() public function __wakeup()
{ {
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.'); throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
} }
@@ -635,7 +599,7 @@ class Connection implements IConnection
*/ */
public function __sleep() public function __sleep()
{ {
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.'); throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
} }

View File

@@ -53,9 +53,11 @@ class DataSource implements IDataSource
*/ */
public function __construct(string $sql, Connection $connection) public function __construct(string $sql, Connection $connection)
{ {
$this->sql = strpbrk($sql, " \t\r\n") === false if (strpbrk($sql, " \t\r\n") === false) {
? $connection->getDriver()->escapeIdentifier($sql) // table name $this->sql = $connection->getDriver()->escapeIdentifier($sql); // table name
: '(' . $sql . ') t'; // SQL command } else {
$this->sql = '(' . $sql . ') t'; // SQL command
}
$this->connection = $connection; $this->connection = $connection;
} }
@@ -65,14 +67,13 @@ class DataSource implements IDataSource
* @param string|array $col column name or array of column names * @param string|array $col column name or array of column names
* @param string $as column alias * @param string $as column alias
*/ */
public function select($col, ?string $as = null): self public function select($col, string $as = null): self
{ {
if (is_array($col)) { if (is_array($col)) {
$this->cols = $col; $this->cols = $col;
} else { } else {
$this->cols[$col] = $as; $this->cols[$col] = $as;
} }
$this->result = null; $this->result = null;
return $this; return $this;
} }
@@ -83,9 +84,12 @@ class DataSource implements IDataSource
*/ */
public function where($cond): self public function where($cond): self
{ {
$this->conds[] = is_array($cond) if (is_array($cond)) {
? $cond // TODO: not consistent with select and orderBy // TODO: not consistent with select and orderBy
: func_get_args(); $this->conds[] = $cond;
} else {
$this->conds[] = func_get_args();
}
$this->result = $this->count = null; $this->result = $this->count = null;
return $this; return $this;
} }
@@ -102,7 +106,6 @@ class DataSource implements IDataSource
} else { } else {
$this->sorting[$row] = $direction; $this->sorting[$row] = $direction;
} }
$this->result = null; $this->result = null;
return $this; return $this;
} }
@@ -111,7 +114,7 @@ class DataSource implements IDataSource
/** /**
* Limits number of rows. * Limits number of rows.
*/ */
public function applyLimit(int $limit, ?int $offset = null): self public function applyLimit(int $limit, int $offset = null): self
{ {
$this->limit = $limit; $this->limit = $limit;
$this->offset = $offset; $this->offset = $offset;
@@ -137,7 +140,6 @@ class DataSource implements IDataSource
if ($this->result === null) { if ($this->result === null) {
$this->result = $this->connection->nativeQuery($this->__toString()); $this->result = $this->connection->nativeQuery($this->__toString());
} }
return $this->result; return $this->result;
} }
@@ -188,7 +190,7 @@ class DataSource implements IDataSource
/** /**
* Fetches all records from table like $key => $value pairs. * Fetches all records from table like $key => $value pairs.
*/ */
public function fetchPairs(?string $key = null, ?string $value = null): array public function fetchPairs(string $key = null, string $value = null): array
{ {
return $this->getResult()->fetchPairs($key, $value); return $this->getResult()->fetchPairs($key, $value);
} }
@@ -230,18 +232,12 @@ class DataSource implements IDataSource
public function __toString(): string public function __toString(): string
{ {
try { try {
return $this->connection->translate( return $this->connection->translate('
"\nSELECT %n", SELECT %n', (empty($this->cols) ? '*' : $this->cols), '
(empty($this->cols) ? '*' : $this->cols), FROM %SQL', $this->sql, '
"\nFROM %SQL", %ex', $this->conds ? ['WHERE %and', $this->conds] : null, '
$this->sql, %ex', $this->sorting ? ['ORDER BY %by', $this->sorting] : null, '
"\n%ex", %ofs %lmt', $this->offset, $this->limit
$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);
@@ -265,7 +261,6 @@ class DataSource implements IDataSource
)->fetchSingle()) )->fetchSingle())
: $this->getTotalCount(); : $this->getTotalCount();
} }
return $this->count; return $this->count;
} }
@@ -280,7 +275,6 @@ class DataSource implements IDataSource
'SELECT COUNT(*) FROM ' . $this->sql 'SELECT COUNT(*) FROM ' . $this->sql
)->fetchSingle()); )->fetchSingle());
} }
return $this->totalCount; return $this->totalCount;
} }
} }

View File

@@ -20,7 +20,7 @@ class DateTime extends \DateTimeImmutable
/** /**
* @param string|int $time * @param string|int $time
*/ */
public function __construct($time = 'now', ?\DateTimeZone $timezone = null) public function __construct($time = 'now', \DateTimeZone $timezone = null)
{ {
$timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get()); $timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get());
if (is_numeric($time)) { if (is_numeric($time)) {
@@ -32,8 +32,77 @@ class DateTime extends \DateTimeImmutable
} }
/** @deprecated use modify() */
public function modifyClone(string $modify = ''): self
{
trigger_error(__METHOD__ . '() is deprecated, use modify()', E_USER_DEPRECATED);
$dolly = clone $this;
return $modify ? $dolly->modify($modify) : $dolly;
}
public function __toString(): string public function __toString(): string
{ {
return $this->format('Y-m-d H:i:s.u'); return $this->format('Y-m-d H:i:s.u');
} }
/********************* immutable usage detector ****************d*g**/
public function __destruct()
{
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
if (isset($trace[0]['file'], $trace[1]['function']) && $trace[0]['file'] === __FILE__ && $trace[1]['function'] !== '__construct') {
trigger_error(__CLASS__ . ' is immutable now, check how it is used in ' . $trace[1]['file'] . ':' . $trace[1]['line'], E_USER_WARNING);
}
}
public function add($interval)
{
return parent::add($interval);
}
public function modify($modify)
{
return parent::modify($modify);
}
public function setDate($year, $month, $day)
{
return parent::setDate($year, $month, $day);
}
public function setISODate($year, $week, $day = 1)
{
return parent::setISODate($year, $week, $day);
}
public function setTime($hour, $minute, $second = 0, $micro = 0)
{
return parent::setTime($hour, $minute, $second, $micro);
}
public function setTimestamp($unixtimestamp)
{
return parent::setTimestamp($unixtimestamp);
}
public function setTimezone($timezone)
{
return parent::setTimezone($timezone);
}
public function sub($interval)
{
return parent::sub($interval);
}
} }

View File

@@ -1,219 +0,0 @@
<?php
/**
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
/**
* The dummy driver for testing purposes.
*/
class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
{
use Dibi\Strict;
public function disconnect(): void
{
}
public function query(string $sql): ?Dibi\ResultDriver
{
return null;
}
public function getAffectedRows(): ?int
{
return null;
}
public function getInsertId(?string $sequence): ?int
{
return null;
}
public function begin(?string $savepoint = null): void
{
}
public function commit(?string $savepoint = null): void
{
}
public function rollback(?string $savepoint = null): void
{
}
public function getResource()
{
return null;
}
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
{
return $this;
}
/********************* SQL ****************d*g**/
/**
* Encodes data for use in a SQL statement.
*/
public function escapeText(string $value): string
{
return "'" . str_replace("'", "''", $value) . "'";
}
public function escapeBinary(string $value): string
{
return "N'" . str_replace("'", "''", $value) . "'";
}
public function escapeIdentifier(string $value): string
{
return '[' . strtr($value, '[]', ' ') . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null || $offset) {
$sql .= ' LIMIT ' . ($limit ?? '-1')
. ($offset ? ' OFFSET ' . $offset : '');
}
}
/********************* Result ****************d*g**/
public function getRowCount(): int
{
return 0;
}
public function fetch(bool $assoc): ?array
{
return null;
}
public function seek(int $row): bool
{
return false;
}
public function free(): void
{
}
public function getResultResource()
{
}
public function getResultColumns(): array
{
return [];
}
/**
* Decodes data from result set.
*/
public function unescapeBinary(string $value): string
{
return $value;
}
/********************* Reflector ****************d*g**/
public function getTables(): array
{
return [];
}
public function getColumns(string $table): array
{
return [];
}
public function getIndexes(string $table): array
{
return [];
}
public function getForeignKeys(string $table): array
{
return [];
}
}

View File

@@ -40,7 +40,9 @@ class FirebirdDriver implements Dibi\Driver
private $inTransaction = false; private $inTransaction = false;
/** @throws Dibi\NotSupportedException */ /**
* @throws Dibi\NotSupportedException
*/
public function __construct(array $config) public function __construct(array $config)
{ {
if (!extension_loaded('interbase')) { if (!extension_loaded('interbase')) {
@@ -62,9 +64,11 @@ class FirebirdDriver implements Dibi\Driver
'buffers' => 0, 'buffers' => 0,
]; ];
$this->connection = empty($config['persistent']) if (empty($config['persistent'])) {
? @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']) // intentionally @ $this->connection = @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
: @ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @ } else {
$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());
@@ -88,23 +92,21 @@ class FirebirdDriver implements Dibi\Driver
*/ */
public function query(string $sql): ?Dibi\ResultDriver public function query(string $sql): ?Dibi\ResultDriver
{ {
$resource = $this->inTransaction $resource = $this->inTransaction ? $this->transaction : $this->connection;
? $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);
} else { } else {
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode(), $sql); throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode(), $sql);
} }
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
return null; return null;
} }
@@ -131,12 +133,11 @@ class FirebirdDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
if ($savepoint !== null) { if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.'); throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
} }
$this->transaction = ibase_trans($this->getResource()); $this->transaction = ibase_trans($this->getResource());
$this->inTransaction = true; $this->inTransaction = true;
} }
@@ -146,7 +147,7 @@ class FirebirdDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
if ($savepoint !== null) { if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.'); throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
@@ -164,7 +165,7 @@ class FirebirdDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
if ($savepoint !== null) { if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.'); throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
@@ -246,31 +247,37 @@ class FirebirdDriver implements Dibi\Driver
} }
public function escapeDate(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format("'Y-m-d'"); return $value->format("'Y-m-d'");
} }
public function escapeDateTime(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'"; return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
} }
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/** /**
* Encodes string for use in a LIKE statement. * Encodes string for use in a LIKE statement.
*/ */
public function escapeLike(string $value, int $pos): string public function escapeLike(string $value, int $pos): string
{ {
$value = addcslashes($this->escapeText($value), '%_\\'); $value = addcslashes($this->escapeText($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'"; return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
} }

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[] = [
@@ -47,7 +47,6 @@ class FirebirdReflector implements Dibi\Reflector
'view' => $row[1] === 'TRUE', 'view' => $row[1] === 'TRUE',
]; ];
} }
return $tables; return $tables;
} }
@@ -85,8 +84,9 @@ 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'];
@@ -100,7 +100,6 @@ class FirebirdReflector implements Dibi\Reflector
'autoincrement' => false, 'autoincrement' => false,
]; ];
} }
return $columns; return $columns;
} }
@@ -122,8 +121,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'];
@@ -133,7 +132,6 @@ class FirebirdReflector implements Dibi\Reflector
$indexes[$key]['table'] = $table; $indexes[$key]['table'] = $table;
$indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME']; $indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME'];
} }
return $indexes; return $indexes;
} }
@@ -151,8 +149,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'];
@@ -162,7 +160,6 @@ class FirebirdReflector implements Dibi\Reflector
'table' => $table, 'table' => $table,
]; ];
} }
return $keys; return $keys;
} }
@@ -177,13 +174,12 @@ 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];
} }
return $indices; return $indices;
} }
@@ -200,13 +196,12 @@ 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];
} }
return $constraints; return $constraints;
} }
@@ -215,10 +210,9 @@ class FirebirdReflector implements Dibi\Reflector
* Returns metadata for all triggers in a table or database. * Returns metadata for all triggers in a table or database.
* (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table) * (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
*/ */
public function getTriggersMeta(?string $table = null): array public function getTriggersMeta(string $table = null): array
{ {
$res = $this->driver->query( $res = $this->driver->query("
"
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
@@ -254,7 +248,6 @@ class FirebirdReflector implements Dibi\Reflector
'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE', 'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE',
]; ];
} }
return $triggers; return $triggers;
} }
@@ -263,21 +256,18 @@ class FirebirdReflector implements Dibi\Reflector
* Returns list of triggers for given table. * Returns list of triggers for given table.
* (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table) * (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
*/ */
public function getTriggers(?string $table = null): array public function getTriggers(string $table = null): array
{ {
$q = 'SELECT TRIM(RDB$TRIGGER_NAME) $q = 'SELECT TRIM(RDB$TRIGGER_NAME)
FROM RDB$TRIGGERS FROM RDB$TRIGGERS
WHERE RDB$SYSTEM_FLAG = 0'; WHERE RDB$SYSTEM_FLAG = 0';
$q .= $table === null $q .= $table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table')";
? ';'
: " AND RDB\$RELATION_NAME = UPPER('$table')";
$res = $this->driver->query($q); $res = $this->driver->query($q);
$triggers = []; $triggers = [];
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$triggers[] = $row[0]; $triggers[] = $row[0];
} }
return $triggers; return $triggers;
} }
@@ -317,8 +307,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'];
@@ -329,7 +319,6 @@ class FirebirdReflector implements Dibi\Reflector
$procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']); $procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']);
$procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH']; $procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH'];
} }
return $procedures; return $procedures;
} }
@@ -341,13 +330,12 @@ 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];
} }
return $procedures; return $procedures;
} }
@@ -360,13 +348,12 @@ 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];
} }
return $generators; return $generators;
} }
@@ -379,13 +366,12 @@ 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];
} }
return $functions; return $functions;
} }
} }

View File

@@ -62,12 +62,10 @@ class FirebirdResult implements Dibi\ResultDriver
*/ */
public function fetch(bool $assoc): ?array public function fetch(bool $assoc): ?array
{ {
$result = $assoc $result = $assoc ? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT) : @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
? @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]);
@@ -126,7 +124,6 @@ class FirebirdResult implements Dibi\ResultDriver
'nativetype' => $row['type'], 'nativetype' => $row['type'],
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -43,7 +43,6 @@ class MySqlReflector implements Dibi\Reflector
'view' => isset($row[1]) && $row[1] === 'VIEW', 'view' => isset($row[1]) && $row[1] === 'VIEW',
]; ];
} }
return $tables; return $tables;
} }
@@ -68,7 +67,6 @@ class MySqlReflector implements Dibi\Reflector
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -86,7 +84,6 @@ class MySqlReflector implements Dibi\Reflector
$indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY'; $indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY';
$indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name']; $indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name'];
} }
return array_values($indexes); return array_values($indexes);
} }
@@ -126,7 +123,6 @@ class MySqlReflector implements Dibi\Reflector
$foreignKeys[$keyName]['onDelete'] = $row['DELETE_RULE']; $foreignKeys[$keyName]['onDelete'] = $row['DELETE_RULE'];
$foreignKeys[$keyName]['onUpdate'] = $row['UPDATE_RULE']; $foreignKeys[$keyName]['onUpdate'] = $row['UPDATE_RULE'];
} }
return array_values($foreignKeys); return array_values($foreignKeys);
} }
} }

View File

@@ -47,7 +47,9 @@ class MySqliDriver implements Dibi\Driver
private $buffered; private $buffered;
/** @throws Dibi\NotSupportedException */ /**
* @throws Dibi\NotSupportedException
*/
public function __construct(array $config) public function __construct(array $config)
{ {
if (!extension_loaded('mysqli')) { if (!extension_loaded('mysqli')) {
@@ -72,7 +74,7 @@ class MySqliDriver implements Dibi\Driver
$host = ini_get('mysqli.default_host'); $host = ini_get('mysqli.default_host');
if ($host) { if ($host) {
$config['host'] = $host; $config['host'] = $host;
$config['port'] = (int) ini_get('mysqli.default_port'); $config['port'] = ini_get('mysqli.default_port');
} else { } else {
$config['host'] = null; $config['host'] = null;
$config['port'] = null; $config['port'] = null;
@@ -88,7 +90,6 @@ class MySqliDriver implements Dibi\Driver
$this->connection->options($key, $value); $this->connection->options($key, $value);
} }
} }
@$this->connection->real_connect( // intentionally @ @$this->connection->real_connect( // intentionally @
(empty($config['persistent']) ? '' : 'p:') . $config['host'], (empty($config['persistent']) ? '' : 'p:') . $config['host'],
$config['username'], $config['username'],
@@ -131,15 +132,6 @@ class MySqliDriver implements Dibi\Driver
} }
/**
* Pings a server connection, or tries to reconnect if the connection has gone down.
*/
public function ping(): bool
{
return $this->connection->ping();
}
/** /**
* Executes the SQL query. * Executes the SQL query.
* @throws Dibi\DriverException * @throws Dibi\DriverException
@@ -154,14 +146,10 @@ class MySqliDriver implements Dibi\Driver
} elseif ($res instanceof \mysqli_result) { } elseif ($res instanceof \mysqli_result) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
return null; return null;
} }
/**
* @param int|string $code
*/
public static function createException(string $message, $code, string $sql): Dibi\DriverException public static function createException(string $message, $code, string $sql): Dibi\DriverException
{ {
if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) { if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
@@ -193,7 +181,6 @@ class MySqliDriver implements Dibi\Driver
foreach ($matches as $m) { foreach ($matches as $m) {
$res[$m[1]] = (int) $m[2]; $res[$m[1]] = (int) $m[2];
} }
return $res; return $res;
} }
@@ -203,9 +190,7 @@ class MySqliDriver implements Dibi\Driver
*/ */
public function getAffectedRows(): ?int public function getAffectedRows(): ?int
{ {
return $this->connection->affected_rows === -1 return $this->connection->affected_rows === -1 ? null : $this->connection->affected_rows;
? null
: $this->connection->affected_rows;
} }
@@ -214,7 +199,7 @@ class MySqliDriver implements Dibi\Driver
*/ */
public function getInsertId(?string $sequence): ?int public function getInsertId(?string $sequence): ?int
{ {
return $this->connection->insert_id ?: null; return $this->connection->insert_id;
} }
@@ -222,7 +207,7 @@ class MySqliDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION'); $this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
} }
@@ -232,7 +217,7 @@ class MySqliDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT'); $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
} }
@@ -242,7 +227,7 @@ class MySqliDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK'); $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
} }
@@ -253,11 +238,7 @@ class MySqliDriver implements Dibi\Driver
*/ */
public function getResource(): ?\mysqli public function getResource(): ?\mysqli
{ {
try { return @$this->connection->thread_id ? $this->connection : null;
return @$this->connection->thread_id ? $this->connection : null;
} catch (\Throwable $e) {
return null;
}
} }
@@ -309,25 +290,27 @@ class MySqliDriver implements Dibi\Driver
} }
public function escapeDate(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format("'Y-m-d'"); return $value->format("'Y-m-d'");
} }
public function escapeDateTime(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{ {
return $value->format("'Y-m-d H:i:s.u'"); if (!$value instanceof \DateTimeInterface) {
} $value = new Dibi\DateTime($value);
public function escapeDateInterval(\DateInterval $value): string
{
if ($value->y || $value->m || $value->d) {
throw new Dibi\NotSupportedException('Only time interval is supported.');
} }
return $value->format("'Y-m-d H:i:s.u'");
return $value->format("'%r%H:%I:%S.%f'");
} }
@@ -337,7 +320,7 @@ class MySqliDriver implements Dibi\Driver
public function escapeLike(string $value, int $pos): string public function escapeLike(string $value, int $pos): string
{ {
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_"); $value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
} }
@@ -351,7 +334,7 @@ class MySqliDriver implements Dibi\Driver
} elseif ($limit !== null || $offset) { } elseif ($limit !== null || $offset) {
// see http://dev.mysql.com/doc/refman/5.0/en/select.html // see http://dev.mysql.com/doc/refman/5.0/en/select.html
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615') $sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : $limit)
. ($offset ? ' OFFSET ' . $offset : ''); . ($offset ? ' OFFSET ' . $offset : '');
} }
} }

View File

@@ -55,7 +55,6 @@ class MySqliResult implements Dibi\ResultDriver
if (!$this->buffered) { if (!$this->buffered) {
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.'); throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
} }
return $this->resultSet->num_rows; return $this->resultSet->num_rows;
} }
@@ -81,7 +80,6 @@ class MySqliResult implements Dibi\ResultDriver
if (!$this->buffered) { if (!$this->buffered) {
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.'); throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
} }
return $this->resultSet->data_seek($row); return $this->resultSet->data_seek($row);
} }
@@ -109,7 +107,6 @@ class MySqliResult implements Dibi\ResultDriver
$types[$value] = substr($key, 12); $types[$value] = substr($key, 12);
} }
} }
$types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT'; $types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT';
} }
@@ -126,7 +123,6 @@ class MySqliResult implements Dibi\ResultDriver
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -37,7 +37,9 @@ class OdbcDriver implements Dibi\Driver
private $microseconds = true; private $microseconds = true;
/** @throws Dibi\NotSupportedException */ /**
* @throws Dibi\NotSupportedException
*/
public function __construct(array $config) public function __construct(array $config)
{ {
if (!extension_loaded('odbc')) { if (!extension_loaded('odbc')) {
@@ -54,9 +56,11 @@ class OdbcDriver implements Dibi\Driver
'dsn' => ini_get('odbc.default_db'), 'dsn' => ini_get('odbc.default_db'),
]; ];
$this->connection = empty($config['persistent']) if (empty($config['persistent'])) {
? @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? '') // intentionally @ $this->connection = @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
: @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @ } else {
$this->connection = @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
}
} }
if (!is_resource($this->connection)) { if (!is_resource($this->connection)) {
@@ -92,11 +96,8 @@ 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) return odbc_num_fields($res) ? $this->createResultDriver($res) : null;
? $this->createResultDriver($res)
: null;
} }
return null; return null;
} }
@@ -123,9 +124,9 @@ class OdbcDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
if (!odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 0 : false)) { if (!odbc_autocommit($this->connection, 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));
} }
} }
@@ -135,13 +136,12 @@ class OdbcDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
if (!odbc_commit($this->connection)) { if (!odbc_commit($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
odbc_autocommit($this->connection, 1/*true*/);
odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 1 : true);
} }
@@ -149,13 +149,12 @@ class OdbcDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
if (!odbc_rollback($this->connection)) { if (!odbc_rollback($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
odbc_autocommit($this->connection, 1/*true*/);
odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 1 : true);
} }
@@ -227,31 +226,37 @@ class OdbcDriver implements Dibi\Driver
} }
public function escapeDate(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format('#m/d/Y#'); return $value->format('#m/d/Y#');
} }
public function escapeDateTime(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#'); return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#');
} }
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/** /**
* Encodes string for use in a LIKE statement. * Encodes string for use in a LIKE statement.
*/ */
public function escapeLike(string $value, int $pos): string public function escapeLike(string $value, int $pos): string
{ {
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
} }

View File

@@ -44,7 +44,6 @@ class OdbcReflector implements Dibi\Reflector
]; ];
} }
} }
odbc_free_result($res); odbc_free_result($res);
return $tables; return $tables;
} }
@@ -69,7 +68,6 @@ class OdbcReflector implements Dibi\Reflector
]; ];
} }
} }
odbc_free_result($res); odbc_free_result($res);
return $columns; return $columns;
} }

View File

@@ -72,13 +72,11 @@ class OdbcResult implements Dibi\ResultDriver
if (!odbc_fetch_row($set, ++$this->row)) { if (!odbc_fetch_row($set, ++$this->row)) {
return null; return null;
} }
$count = odbc_num_fields($set); $count = odbc_num_fields($set);
$cols = []; $cols = [];
for ($i = 1; $i <= $count; $i++) { for ($i = 1; $i <= $count; $i++) {
$cols[] = odbc_result($set, $i); $cols[] = odbc_result($set, $i);
} }
return $cols; return $cols;
} }
} }
@@ -118,7 +116,6 @@ class OdbcResult implements Dibi\ResultDriver
'nativetype' => odbc_field_type($this->resultSet, $i), 'nativetype' => odbc_field_type($this->resultSet, $i),
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -42,7 +42,9 @@ class OracleDriver implements Dibi\Driver
private $affectedRows; private $affectedRows;
/** @throws Dibi\NotSupportedException */ /**
* @throws Dibi\NotSupportedException
*/
public function __construct(array $config) public function __construct(array $config)
{ {
if (!extension_loaded('oci8')) { if (!extension_loaded('oci8')) {
@@ -50,6 +52,10 @@ class OracleDriver implements Dibi\Driver
} }
$foo = &$config['charset']; $foo = &$config['charset'];
if (isset($config['formatDate']) || isset($config['formatDateTime'])) {
trigger_error('OracleDriver: options formatDate and formatDateTime are deprecated.', E_USER_DEPRECATED);
}
$this->nativeDate = $config['nativeDate'] ?? true; $this->nativeDate = $config['nativeDate'] ?? true;
if (isset($config['resource'])) { if (isset($config['resource'])) {
@@ -96,15 +102,12 @@ 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) return oci_num_fields($res) ? $this->createResultDriver($res) : null;
? $this->createResultDriver($res)
: null;
} }
} else { } else {
$err = oci_error($this->connection); $err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code'], $sql); throw new Dibi\DriverException($err['message'], $err['code'], $sql);
} }
return null; return null;
} }
@@ -148,7 +151,7 @@ class OracleDriver implements Dibi\Driver
/** /**
* Begins a transaction (if supported). * Begins a transaction (if supported).
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
$this->autocommit = false; $this->autocommit = false;
} }
@@ -158,13 +161,12 @@ class OracleDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
if (!oci_commit($this->connection)) { if (!oci_commit($this->connection)) {
$err = oci_error($this->connection); $err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code']); throw new Dibi\DriverException($err['message'], $err['code']);
} }
$this->autocommit = true; $this->autocommit = true;
} }
@@ -173,13 +175,12 @@ class OracleDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
if (!oci_rollback($this->connection)) { if (!oci_rollback($this->connection)) {
$err = oci_error($this->connection); $err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code']); throw new Dibi\DriverException($err['message'], $err['code']);
} }
$this->autocommit = true; $this->autocommit = true;
} }
@@ -244,28 +245,34 @@ class OracleDriver implements Dibi\Driver
} }
public function escapeDate(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $this->nativeDate return $this->nativeDate
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')" ? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
: $value->format('U'); : $value->format('U');
} }
public function escapeDateTime(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $this->nativeDate return $this->nativeDate
? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')" ? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')"
: $value->format('U'); : $value->format('U');
} }
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/** /**
* Encodes string for use in a LIKE statement. * Encodes string for use in a LIKE statement.
*/ */
@@ -273,7 +280,7 @@ class OracleDriver implements Dibi\Driver
{ {
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_"); $value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value); $value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
} }

View File

@@ -44,7 +44,6 @@ class OracleReflector implements Dibi\Reflector
]; ];
} }
} }
return $tables; return $tables;
} }
@@ -67,7 +66,6 @@ class OracleReflector implements Dibi\Reflector
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -96,11 +96,9 @@ class OracleResult implements Dibi\ResultDriver
'name' => oci_field_name($this->resultSet, $i), 'name' => oci_field_name($this->resultSet, $i),
'table' => null, 'table' => null,
'fullname' => oci_field_name($this->resultSet, $i), 'fullname' => oci_field_name($this->resultSet, $i),
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null,
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type, 'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -42,7 +42,9 @@ class PdoDriver implements Dibi\Driver
private $serverVersion = ''; private $serverVersion = '';
/** @throws Dibi\NotSupportedException */ /**
* @throws Dibi\NotSupportedException
*/
public function __construct(array $config) public function __construct(array $config)
{ {
if (!extension_loaded('pdo')) { if (!extension_loaded('pdo')) {
@@ -56,23 +58,21 @@ 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.');
} }
throw new Dibi\DriverException($e->getMessage(), $e->getCode()); throw new Dibi\DriverException($e->getMessage(), $e->getCode());
} }
} }
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
} }
@@ -144,7 +144,7 @@ class PdoDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
if (!$this->connection->beginTransaction()) { if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
@@ -157,7 +157,7 @@ class PdoDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
if (!$this->connection->commit()) { if (!$this->connection->commit()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
@@ -170,7 +170,7 @@ class PdoDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
if (!$this->connection->rollBack()) { if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
@@ -234,17 +234,21 @@ class PdoDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
return $this->driverName === 'odbc' if ($this->driverName === 'odbc') {
? "'" . str_replace("'", "''", $value) . "'" return "'" . str_replace("'", "''", $value) . "'";
: $this->connection->quote($value, PDO::PARAM_STR); } else {
return $this->connection->quote($value, PDO::PARAM_STR);
}
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
return $this->driverName === 'odbc' if ($this->driverName === 'odbc') {
? "'" . str_replace("'", "''", $value) . "'" return "'" . str_replace("'", "''", $value) . "'";
: $this->connection->quote($value, PDO::PARAM_LOB); } else {
return $this->connection->quote($value, PDO::PARAM_LOB);
}
} }
@@ -285,14 +289,26 @@ class PdoDriver implements Dibi\Driver
} }
public function escapeDate(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'"); return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
} }
public function escapeDateTime(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
switch ($this->driverName) { switch ($this->driverName) {
case 'odbc': case 'odbc':
return $value->format('#m/d/Y H:i:s.u#'); return $value->format('#m/d/Y H:i:s.u#');
@@ -306,12 +322,6 @@ class PdoDriver implements Dibi\Driver
} }
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/** /**
* Encodes string for use in a LIKE statement. * Encodes string for use in a LIKE statement.
*/ */
@@ -320,29 +330,29 @@ class PdoDriver implements Dibi\Driver
switch ($this->driverName) { switch ($this->driverName) {
case 'mysql': case 'mysql':
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_"); $value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
case 'oci': case 'oci':
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_"); $value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value); $value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
case 'pgsql': case 'pgsql':
$bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off $bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1); $value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']); $value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
case 'sqlite': case 'sqlite':
$value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\'); $value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'"; return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
case 'odbc': case 'odbc':
case 'mssql': case 'mssql':
case 'dblib': case 'dblib':
case 'sqlsrv': case 'sqlsrv':
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
default: default:
throw new Dibi\NotImplementedException; throw new Dibi\NotImplementedException;
@@ -363,29 +373,25 @@ class PdoDriver implements Dibi\Driver
case 'mysql': case 'mysql':
if ($limit !== null || $offset) { if ($limit !== null || $offset) {
// see http://dev.mysql.com/doc/refman/5.0/en/select.html // see http://dev.mysql.com/doc/refman/5.0/en/select.html
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615') $sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : $limit)
. ($offset ? ' OFFSET ' . $offset : ''); . ($offset ? ' OFFSET ' . $offset : '');
} }
break; break;
case 'pgsql': case 'pgsql':
if ($limit !== null) { if ($limit !== null) {
$sql .= ' LIMIT ' . $limit; $sql .= ' LIMIT ' . $limit;
} }
if ($offset) { if ($offset) {
$sql .= ' OFFSET ' . $offset; $sql .= ' OFFSET ' . $offset;
} }
break; break;
case 'sqlite': case 'sqlite':
if ($limit !== null || $offset) { if ($limit !== null || $offset) {
$sql .= ' LIMIT ' . ($limit ?? '-1') $sql .= ' LIMIT ' . ($limit === null ? '-1' : $limit)
. ($offset ? ' OFFSET ' . $offset : ''); . ($offset ? ' OFFSET ' . $offset : '');
} }
break; break;
case 'oci': case 'oci':
@@ -398,7 +404,6 @@ class PdoDriver implements Dibi\Driver
} elseif ($limit !== null) { } elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit; $sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
} }
break; break;
case 'mssql': case 'mssql':
@@ -411,10 +416,10 @@ class PdoDriver implements Dibi\Driver
} elseif ($offset) { } elseif ($offset) {
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset); $sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
} }
break; break;
} }
// break omitted // break omitted
case 'odbc': case 'odbc':
if ($offset) { if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.'); throw new Dibi\NotSupportedException('Offset is not supported by this database.');
@@ -424,6 +429,7 @@ class PdoDriver implements Dibi\Driver
break; break;
} }
// break omitted // break omitted
default: default:
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.'); throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
} }

View File

@@ -85,8 +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',
]; ];
@@ -100,7 +99,6 @@ class PdoResult implements Dibi\ResultDriver
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -11,7 +11,6 @@ namespace Dibi\Drivers;
use Dibi; use Dibi;
use Dibi\Helpers; use Dibi\Helpers;
use PgSql;
/** /**
@@ -24,20 +23,21 @@ use PgSql;
* - charset => character encoding to set (default is utf8) * - charset => character encoding to set (default is utf8)
* - persistent (bool) => try to find a persistent link? * - persistent (bool) => try to find a persistent link?
* - resource (resource) => existing connection resource * - resource (resource) => existing connection resource
* - connect_type (int) => see pg_connect()
*/ */
class PostgreDriver implements Dibi\Driver class PostgreDriver implements Dibi\Driver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var resource|PgSql\Connection */ /** @var resource */
private $connection; private $connection;
/** @var int|null Affected rows */ /** @var int|null Affected rows */
private $affectedRows; private $affectedRows;
/** @throws Dibi\NotSupportedException */ /**
* @throws Dibi\NotSupportedException
*/
public function __construct(array $config) public function __construct(array $config)
{ {
if (!extension_loaded('pgsql')) { if (!extension_loaded('pgsql')) {
@@ -65,18 +65,18 @@ class PostgreDriver implements Dibi\Driver
} }
} }
$connectType = $config['connect_type'] ?? PGSQL_CONNECT_FORCE_NEW;
set_error_handler(function (int $severity, string $message) use (&$error) { set_error_handler(function (int $severity, string $message) use (&$error) {
$error = $message; $error = $message;
}); });
$this->connection = empty($config['persistent']) if (empty($config['persistent'])) {
? pg_connect($string, $connectType) $this->connection = pg_connect($string, PGSQL_CONNECT_FORCE_NEW);
: pg_pconnect($string, $connectType); } else {
$this->connection = pg_pconnect($string, PGSQL_CONNECT_FORCE_NEW);
}
restore_error_handler(); restore_error_handler();
} }
if (!is_resource($this->connection) && !$this->connection instanceof PgSql\Connection) { if (!is_resource($this->connection)) {
throw new Dibi\DriverException($error ?: 'Connecting error.'); throw new Dibi\DriverException($error ?: 'Connecting error.');
} }
@@ -122,18 +122,17 @@ 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) || $res instanceof PgSql\Result) { } elseif (is_resource($res)) {
$this->affectedRows = Helpers::false2Null(pg_affected_rows($res)); $this->affectedRows = Helpers::false2Null(pg_affected_rows($res));
if (pg_num_fields($res)) { if (pg_num_fields($res)) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
} }
return null; return null;
} }
public static function createException(string $message, $code = null, ?string $sql = null): Dibi\DriverException public static function createException(string $message, $code = null, string $sql = null): Dibi\DriverException
{ {
if ($code === null && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) { if ($code === null && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) {
$code = $m[1]; $code = $m[1];
@@ -172,9 +171,12 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function getInsertId(?string $sequence): ?int public function getInsertId(?string $sequence): ?int
{ {
$res = $sequence === null if ($sequence === null) {
? $this->query('SELECT LASTVAL()') // PostgreSQL 8.1 is needed // PostgreSQL 8.1 is needed
: $this->query("SELECT CURRVAL('$sequence')"); $res = $this->query('SELECT LASTVAL()');
} else {
$res = $this->query("SELECT CURRVAL('$sequence')");
}
if (!$res) { if (!$res) {
return null; return null;
@@ -189,9 +191,9 @@ class PostgreDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
$this->query($savepoint ? "SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'START TRANSACTION'); $this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
} }
@@ -199,9 +201,9 @@ class PostgreDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
$this->query($savepoint ? "RELEASE SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'COMMIT'); $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
} }
@@ -209,9 +211,9 @@ class PostgreDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'ROLLBACK'); $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
} }
@@ -230,9 +232,7 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function getResource() public function getResource()
{ {
return is_resource($this->connection) || $this->connection instanceof PgSql\Connection return is_resource($this->connection) ? $this->connection : null;
? $this->connection
: null;
} }
@@ -263,20 +263,18 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
if (!$this->getResource()) { if (!is_resource($this->connection)) {
throw new Dibi\Exception('Lost connection to server.'); throw new Dibi\Exception('Lost connection to server.');
} }
return "'" . pg_escape_string($this->connection, $value) . "'"; return "'" . pg_escape_string($this->connection, $value) . "'";
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
if (!$this->getResource()) { if (!is_resource($this->connection)) {
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) . "'";
} }
@@ -294,24 +292,30 @@ class PostgreDriver implements Dibi\Driver
} }
public function escapeDate(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format("'Y-m-d'"); return $value->format("'Y-m-d'");
} }
public function escapeDateTime(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format("'Y-m-d H:i:s.u'"); return $value->format("'Y-m-d H:i:s.u'");
} }
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/** /**
* Encodes string for use in a LIKE statement. * Encodes string for use in a LIKE statement.
*/ */
@@ -320,7 +324,7 @@ class PostgreDriver implements Dibi\Driver
$bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off $bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
$value = pg_escape_string($this->connection, $value); $value = pg_escape_string($this->connection, $value);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']); $value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
} }
@@ -332,11 +336,9 @@ class PostgreDriver implements Dibi\Driver
if ($limit < 0 || $offset < 0) { if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.'); throw new Dibi\NotSupportedException('Negative offset or limit.');
} }
if ($limit !== null) { if ($limit !== null) {
$sql .= ' LIMIT ' . $limit; $sql .= ' LIMIT ' . $limit;
} }
if ($offset) { if ($offset) {
$sql .= ' OFFSET ' . $offset; $sql .= ' OFFSET ' . $offset;
} }

View File

@@ -28,6 +28,9 @@ 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;
} }
@@ -66,7 +69,6 @@ class PostgreReflector implements Dibi\Reflector
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$tables[] = $row; $tables[] = $row;
} }
return $tables; return $tables;
} }
@@ -103,7 +105,7 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length, a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable, NOT a.attnotnull AS is_nullable,
a.attnum AS ordinal_position, a.attnum AS ordinal_position,
pg_get_expr(adef.adbin, adef.adrelid) AS column_default adef.adsrc 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
@@ -132,7 +134,6 @@ class PostgreReflector implements Dibi\Reflector
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -182,7 +183,6 @@ class PostgreReflector implements Dibi\Reflector
} }
} }
} }
return array_values($indexes); return array_values($indexes);
} }
@@ -251,10 +251,7 @@ class PostgreReflector implements Dibi\Reflector
$references[$row['name']] = array_combine($l, $f); $references[$row['name']] = array_combine($l, $f);
} }
if ( if (isset($references[$row['name']][$row['lnum']]) && $references[$row['name']][$row['lnum']] === $row['fnum']) {
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,7 +11,6 @@ namespace Dibi\Drivers;
use Dibi; use Dibi;
use Dibi\Helpers; use Dibi\Helpers;
use PgSql;
/** /**
@@ -21,7 +20,7 @@ class PostgreResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var resource|PgSql\Result */ /** @var resource */
private $resultSet; private $resultSet;
/** @var bool */ /** @var bool */
@@ -29,7 +28,7 @@ class PostgreResult implements Dibi\ResultDriver
/** /**
* @param resource|PgSql\Result $resultSet * @param resource $resultSet
*/ */
public function __construct($resultSet) public function __construct($resultSet)
{ {
@@ -98,26 +97,21 @@ 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['fullname'] = $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'];
? $row['table'] . '.' . $row['name']
: $row['name'];
$columns[] = $row; $columns[] = $row;
} }
return $columns; return $columns;
} }
/** /**
* Returns the result set resource. * Returns the result set resource.
* @return resource|PgSql\Result|null * @return resource|null
*/ */
public function getResultResource() public function getResultResource()
{ {
$this->autoFree = false; $this->autoFree = false;
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result return is_resource($this->resultSet) ? $this->resultSet : null;
? $this->resultSet
: null;
} }

View File

@@ -37,7 +37,9 @@ class SqliteDriver implements Dibi\Driver
private $fmtDateTime; private $fmtDateTime;
/** @throws Dibi\NotSupportedException */ /**
* @throws Dibi\NotSupportedException
*/
public function __construct(array $config) public function __construct(array $config)
{ {
if (!extension_loaded('sqlite3')) { if (!extension_loaded('sqlite3')) {
@@ -57,7 +59,7 @@ class SqliteDriver implements Dibi\Driver
} else { } else {
try { try {
$this->connection = new SQLite3($config['database']); $this->connection = new SQLite3($config['database']);
} catch (\Throwable $e) { } catch (\Exception $e) {
throw new Dibi\DriverException($e->getMessage(), $e->getCode()); throw new Dibi\DriverException($e->getMessage(), $e->getCode());
} }
} }
@@ -92,7 +94,6 @@ class SqliteDriver implements Dibi\Driver
} elseif ($res instanceof \SQLite3Result && $res->numColumns()) { } elseif ($res instanceof \SQLite3Result && $res->numColumns()) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
return null; return null;
} }
@@ -138,7 +139,7 @@ class SqliteDriver implements Dibi\Driver
*/ */
public function getInsertId(?string $sequence): ?int public function getInsertId(?string $sequence): ?int
{ {
return $this->connection->lastInsertRowID() ?: null; return $this->connection->lastInsertRowID();
} }
@@ -146,7 +147,7 @@ class SqliteDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN'); $this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
} }
@@ -156,7 +157,7 @@ class SqliteDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT'); $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
} }
@@ -166,7 +167,7 @@ class SqliteDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK'); $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
} }
@@ -229,31 +230,37 @@ class SqliteDriver implements Dibi\Driver
} }
public function escapeDate(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format($this->fmtDate); return $value->format($this->fmtDate);
} }
public function escapeDateTime(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format($this->fmtDateTime); return $value->format($this->fmtDateTime);
} }
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/** /**
* Encodes string for use in a LIKE statement. * Encodes string for use in a LIKE statement.
*/ */
public function escapeLike(string $value, int $pos): string public function escapeLike(string $value, int $pos): string
{ {
$value = addcslashes($this->connection->escapeString($value), '%_\\'); $value = addcslashes($this->connection->escapeString($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'"; return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
} }
@@ -266,7 +273,7 @@ class SqliteDriver implements Dibi\Driver
throw new Dibi\NotSupportedException('Negative offset or limit.'); throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null || $offset) { } elseif ($limit !== null || $offset) {
$sql .= ' LIMIT ' . ($limit ?? '-1') $sql .= ' LIMIT ' . ($limit === null ? '-1' : $limit)
. ($offset ? ' OFFSET ' . $offset : ''); . ($offset ? ' OFFSET ' . $offset : '');
} }
} }
@@ -287,12 +294,7 @@ 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( public function registerAggregateFunction(string $name, callable $rowCallback, callable $agrCallback, int $numArgs = -1): void
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

@@ -44,7 +44,6 @@ class SqliteReflector implements Dibi\Reflector
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$tables[] = $row; $tables[] = $row;
} }
return $tables; return $tables;
} }
@@ -65,13 +64,12 @@ 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,
]; ];
} }
return $columns; return $columns;
} }
@@ -100,15 +98,13 @@ 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;
} }
} }
$indexes[$index]['primary'] = (bool) $primary; $indexes[$index]['primary'] = (bool) $primary;
} }
if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid
foreach ($columns as $column) { foreach ($columns as $column) {
if ($column['vendor']['pk']) { if ($column['vendor']['pk']) {
@@ -146,7 +142,6 @@ class SqliteReflector implements Dibi\Reflector
$keys[$row['id']]['foreign'] = null; $keys[$row['id']]['foreign'] = null;
} }
} }
return array_values($keys); return array_values($keys);
} }
} }

View File

@@ -99,7 +99,6 @@ class SqliteResult implements Dibi\ResultDriver
'nativetype' => $types[$this->resultSet->columnType($i)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414 'nativetype' => $types[$this->resultSet->columnType($i)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -39,7 +39,9 @@ class SqlsrvDriver implements Dibi\Driver
private $version = ''; private $version = '';
/** @throws Dibi\NotSupportedException */ /**
* @throws Dibi\NotSupportedException
*/
public function __construct(array $config) public function __construct(array $config)
{ {
if (!extension_loaded('sqlsrv')) { if (!extension_loaded('sqlsrv')) {
@@ -53,9 +55,7 @@ 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'];
@@ -67,14 +67,13 @@ class SqlsrvDriver implements Dibi\Driver
sqlsrv_configure('WarningsReturnAsErrors', 0); 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); sqlsrv_configure('WarningsReturnAsErrors', 1);
} }
if (!is_resource($this->connection)) {
$info = sqlsrv_errors(SQLSRV_ERR_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'];
} }
@@ -103,11 +102,8 @@ 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) return sqlsrv_num_fields($res) ? $this->createResultDriver($res) : null;
? $this->createResultDriver($res)
: null;
} }
return null; return null;
} }
@@ -131,7 +127,6 @@ class SqlsrvDriver implements Dibi\Driver
$row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC); $row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
return Dibi\Helpers::intVal($row[0]); return Dibi\Helpers::intVal($row[0]);
} }
return null; return null;
} }
@@ -140,7 +135,7 @@ class SqlsrvDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(?string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
sqlsrv_begin_transaction($this->connection); sqlsrv_begin_transaction($this->connection);
} }
@@ -150,7 +145,7 @@ class SqlsrvDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(?string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
sqlsrv_commit($this->connection); sqlsrv_commit($this->connection);
} }
@@ -160,7 +155,7 @@ class SqlsrvDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(?string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
sqlsrv_rollback($this->connection); sqlsrv_rollback($this->connection);
} }
@@ -203,13 +198,13 @@ class SqlsrvDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
return "N'" . str_replace("'", "''", $value) . "'"; return "'" . str_replace("'", "''", $value) . "'";
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
return '0x' . bin2hex($value); return "'" . str_replace("'", "''", $value) . "'";
} }
@@ -226,31 +221,37 @@ class SqlsrvDriver implements Dibi\Driver
} }
public function escapeDate(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format("'Y-m-d'"); return $value->format("'Y-m-d'");
} }
public function escapeDateTime(\DateTimeInterface $value): string /**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{ {
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')'; return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
} }
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/** /**
* Encodes string for use in a LIKE statement. * Encodes string for use in a LIKE statement.
*/ */
public function escapeLike(string $value, int $pos): string public function escapeLike(string $value, int $pos): string
{ {
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
} }
@@ -269,6 +270,7 @@ class SqlsrvDriver implements Dibi\Driver
} elseif ($limit !== null) { } elseif ($limit !== null) {
$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql); $sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
} }
} elseif ($limit !== null) { } elseif ($limit !== null) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx // requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit); $sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);

View File

@@ -42,7 +42,6 @@ class SqlsrvReflector implements Dibi\Reflector
'view' => isset($row[1]) && $row[1] === 'VIEW', 'view' => isset($row[1]) && $row[1] === 'VIEW',
]; ];
} }
return $tables; return $tables;
} }
@@ -92,7 +91,6 @@ class SqlsrvReflector implements Dibi\Reflector
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -102,7 +100,7 @@ class SqlsrvReflector implements Dibi\Reflector
*/ */
public function getIndexes(string $table): array public function getIndexes(string $table): array
{ {
$keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = %s', $this->driver->escapeText($table))); $keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = N%s', $this->driver->escapeText($table)));
$keyUsages = []; $keyUsages = [];
while ($row = $keyUsagesRes->fetch(true)) { while ($row = $keyUsagesRes->fetch(true)) {
$keyUsages[$row['index_name']] = explode(',', $row['index_keys']); $keyUsages[$row['index_name']] = explode(',', $row['index_keys']);
@@ -116,7 +114,6 @@ class SqlsrvReflector implements Dibi\Reflector
$indexes[$row['name']]['primary'] = $row['is_primary_key'] === 1; $indexes[$row['name']]['primary'] = $row['is_primary_key'] === 1;
$indexes[$row['name']]['columns'] = $keyUsages[$row['name']] ?? []; $indexes[$row['name']]['columns'] = $keyUsages[$row['name']] ?? [];
} }
return array_values($indexes); return array_values($indexes);
} }

View File

@@ -61,7 +61,7 @@ class SqlsrvResult implements Dibi\ResultDriver
*/ */
public function fetch(bool $assoc): ?array public function fetch(bool $assoc): ?array
{ {
return Dibi\Helpers::false2Null(sqlsrv_fetch_array($this->resultSet, $assoc ? SQLSRV_FETCH_ASSOC : SQLSRV_FETCH_NUMERIC)); return sqlsrv_fetch_array($this->resultSet, $assoc ? SQLSRV_FETCH_ASSOC : SQLSRV_FETCH_NUMERIC);
} }
@@ -96,7 +96,6 @@ class SqlsrvResult implements Dibi\ResultDriver
'nativetype' => $fieldMetadata['Type'], 'nativetype' => $fieldMetadata['Type'],
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -53,7 +53,7 @@ class Event
public $source; public $source;
public function __construct(Connection $connection, int $type, ?string $sql = null) public function __construct(Connection $connection, int $type, string $sql = null)
{ {
$this->connection = $connection; $this->connection = $connection;
$this->type = $type; $this->type = $type;
@@ -70,11 +70,7 @@ class Event
$dibiDir = dirname((new \ReflectionClass('dibi'))->getFileName()) . DIRECTORY_SEPARATOR; $dibiDir = dirname((new \ReflectionClass('dibi'))->getFileName()) . DIRECTORY_SEPARATOR;
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $row) { foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $row) {
if ( if (isset($row['file']) && is_file($row['file']) && strpos($row['file'], $dibiDir) !== 0) {
isset($row['file'])
&& preg_match('~\.(php.?|phtml)$~', $row['file'])
&& substr($row['file'], 0, strlen($dibiDir)) !== $dibiDir
) {
$this->source = [$row['file'], (int) $row['line']]; $this->source = [$row['file'], (int) $row['line']];
break; break;
} }

View File

@@ -32,12 +32,6 @@ namespace Dibi;
* @method Fluent and(...$cond) * @method Fluent and(...$cond)
* @method Fluent or(...$cond) * @method Fluent or(...$cond)
* @method Fluent using(...$cond) * @method Fluent using(...$cond)
* @method Fluent update(...$cond)
* @method Fluent insert(...$cond)
* @method Fluent delete(...$cond)
* @method Fluent into(...$cond)
* @method Fluent values(...$cond)
* @method Fluent set(...$args)
* @method Fluent asc() * @method Fluent asc()
* @method Fluent desc() * @method Fluent desc()
*/ */
@@ -119,7 +113,7 @@ class Fluent implements IDataSource
$this->connection = $connection; $this->connection = $connection;
if (self::$normalizer === null) { if (self::$normalizer === null) {
self::$normalizer = new HashMap([self::class, '_formatClause']); self::$normalizer = new HashMap([__CLASS__, '_formatClause']);
} }
} }
@@ -136,7 +130,6 @@ class Fluent implements IDataSource
if (isset(self::$masks[$clause])) { if (isset(self::$masks[$clause])) {
$this->clauses = array_fill_keys(self::$masks[$clause], null); $this->clauses = array_fill_keys(self::$masks[$clause], null);
} }
$this->cursor = &$this->clauses[$clause]; $this->cursor = &$this->clauses[$clause];
$this->cursor = []; $this->cursor = [];
$this->command = $clause; $this->command = $clause;
@@ -166,6 +159,7 @@ class Fluent implements IDataSource
$this->cursor[] = $sep; $this->cursor[] = $sep;
} }
} }
} else { } else {
// append to currect flow // append to currect flow
if ($args === [self::REMOVE]) { if ($args === [self::REMOVE]) {
@@ -203,7 +197,6 @@ class Fluent implements IDataSource
if ($arg instanceof self) { if ($arg instanceof self) {
$arg = new Literal("($arg)"); $arg = new Literal("($arg)");
} }
$this->cursor[] = $arg; $this->cursor[] = $arg;
} }
@@ -246,7 +239,6 @@ class Fluent implements IDataSource
} else { } else {
unset($this->flags[$flag]); unset($this->flags[$flag]);
} }
return $this; return $this;
} }
@@ -293,7 +285,7 @@ class Fluent implements IDataSource
* @return Result|int|null result set or number of affected rows * @return Result|int|null result set or number of affected rows
* @throws Exception * @throws Exception
*/ */
public function execute(?string $return = null) public function execute(string $return = null)
{ {
$res = $this->query($this->_export()); $res = $this->query($this->_export());
switch ($return) { switch ($return) {
@@ -313,9 +305,11 @@ class Fluent implements IDataSource
*/ */
public function fetch() public function fetch()
{ {
return $this->command === 'SELECT' && !$this->clauses['LIMIT'] if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
? $this->query($this->_export(null, ['%lmt', 1]))->fetch() return $this->query($this->_export(null, ['%lmt', 1]))->fetch();
: $this->query($this->_export())->fetch(); } else {
return $this->query($this->_export())->fetch();
}
} }
@@ -325,16 +319,18 @@ class Fluent implements IDataSource
*/ */
public function fetchSingle() public function fetchSingle()
{ {
return $this->command === 'SELECT' && !$this->clauses['LIMIT'] if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle() return $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle();
: $this->query($this->_export())->fetchSingle(); } else {
return $this->query($this->_export())->fetchSingle();
}
} }
/** /**
* Fetches all records from table. * Fetches all records from table.
*/ */
public function fetchAll(?int $offset = null, ?int $limit = null): array public function fetchAll(int $offset = null, int $limit = null): array
{ {
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll(); return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll();
} }
@@ -353,7 +349,7 @@ class Fluent implements IDataSource
/** /**
* Fetches all records from table like $key => $value pairs. * Fetches all records from table like $key => $value pairs.
*/ */
public function fetchPairs(?string $key = null, ?string $value = null): array public function fetchPairs(string $key = null, string $value = null): array
{ {
return $this->query($this->_export())->fetchPairs($key, $value); return $this->query($this->_export())->fetchPairs($key, $value);
} }
@@ -362,7 +358,7 @@ class Fluent implements IDataSource
/** /**
* Required by the IteratorAggregate interface. * Required by the IteratorAggregate interface.
*/ */
public function getIterator(?int $offset = null, ?int $limit = null): ResultIterator public function getIterator(int $offset = null, int $limit = null): ResultIterator
{ {
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator(); return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator();
} }
@@ -371,7 +367,7 @@ class Fluent implements IDataSource
/** /**
* Generates and prints SQL query or it's part. * Generates and prints SQL query or it's part.
*/ */
public function test(?string $clause = null): bool public function test(string $clause = null): bool
{ {
return $this->connection->test($this->_export($clause)); return $this->connection->test($this->_export($clause));
} }
@@ -392,7 +388,6 @@ class Fluent implements IDataSource
$method = array_shift($setup); $method = array_shift($setup);
$res->$method(...$setup); $res->$method(...$setup);
} }
return $res; return $res;
} }
@@ -423,7 +418,7 @@ class Fluent implements IDataSource
/** /**
* Generates parameters for Translator. * Generates parameters for Translator.
*/ */
protected function _export(?string $clause = null, array $args = []): array protected function _export(string $clause = null, array $args = []): array
{ {
if ($clause === null) { if ($clause === null) {
$data = $this->clauses; $data = $this->clauses;
@@ -431,6 +426,7 @@ class Fluent implements IDataSource
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args); $args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args);
unset($data['LIMIT'], $data['OFFSET']); unset($data['LIMIT'], $data['OFFSET']);
} }
} else { } else {
$clause = self::$normalizer->$clause; $clause = self::$normalizer->$clause;
if (array_key_exists($clause, $this->clauses)) { if (array_key_exists($clause, $this->clauses)) {
@@ -446,7 +442,6 @@ class Fluent implements IDataSource
if ($clause === $this->command && $this->flags) { if ($clause === $this->command && $this->flags) {
$args[] = implode(' ', array_keys($this->flags)); $args[] = implode(' ', array_keys($this->flags));
} }
foreach ($statement as $arg) { foreach ($statement as $arg) {
$args[] = $arg; $args[] = $arg;
} }
@@ -467,7 +462,6 @@ class Fluent implements IDataSource
$s .= 'By'; $s .= 'By';
trigger_error("Did you mean '$s'?", E_USER_NOTICE); trigger_error("Did you mean '$s'?", E_USER_NOTICE);
} }
return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s)); return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
} }
@@ -479,7 +473,6 @@ class Fluent implements IDataSource
$this->clauses[$clause] = &$val; $this->clauses[$clause] = &$val;
unset($val); unset($val);
} }
$this->cursor = &$foo; $this->cursor = &$foo;
} }
} }

View File

@@ -14,7 +14,6 @@ namespace Dibi;
* Lazy cached storage. * Lazy cached storage.
* @internal * @internal
*/ */
#[\AllowDynamicProperties]
abstract class HashMapBase abstract class HashMapBase
{ {
/** @var callable */ /** @var callable */
@@ -51,7 +50,6 @@ final class HashMap extends HashMapBase
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
} }
$this->$nm = $val; $this->$nm = $val;
} }
@@ -60,7 +58,7 @@ final class HashMap extends HashMapBase
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
return isset($this->$nm) && true ? $this->$nm : $this->$nm = $this->getCallback()(''); return isset($this->$nm) ? $this->$nm : $this->$nm = $this->getCallback()('');
} else { } else {
return $this->$nm = $this->getCallback()($nm); return $this->$nm = $this->getCallback()($nm);
} }

View File

@@ -41,7 +41,6 @@ class Helpers
$spaces = $maxLen - mb_strlen($col) + 2; $spaces = $maxLen - mb_strlen($col) + 2;
echo "$col" . str_repeat(' ', $spaces) . "$val\n"; echo "$col" . str_repeat(' ', $spaces) . "$val\n";
} }
echo "\n"; echo "\n";
} }
@@ -54,7 +53,6 @@ class Helpers
foreach ($row as $col => $foo) { foreach ($row as $col => $foo) {
echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n"; echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n";
} }
echo "\t</tr>\n</thead>\n<tbody>\n"; echo "\t</tr>\n</thead>\n<tbody>\n";
} }
@@ -62,7 +60,6 @@ class Helpers
foreach ($row as $col) { foreach ($row as $col) {
echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n"; echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n";
} }
echo "\t</tr>\n"; echo "\t</tr>\n";
} }
@@ -107,7 +104,6 @@ class Helpers
} }
}, $sql); }, $sql);
} }
echo trim($sql) . "\n\n"; echo trim($sql) . "\n\n";
} else { } else {
@@ -147,14 +143,13 @@ class Helpers
{ {
$best = null; $best = null;
$min = (strlen($value) / 4 + 1) * 10 + .1; $min = (strlen($value) / 4 + 1) * 10 + .1;
$items = array_map('strval', $items); foreach (array_unique($items, SORT_REGULAR) as $item) {
foreach (array_unique($items) as $item) { $item = is_object($item) ? $item->getName() : $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;
} }
} }
return $best; return $best;
} }
@@ -186,7 +181,6 @@ 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,
@@ -203,18 +197,18 @@ class Helpers
return $val; return $val;
} }
} }
return null; return null;
} }
/** @internal */ /**
* @internal
*/
public static function getTypeCache(): HashMap public static function getTypeCache(): HashMap
{ {
if (self::$types === null) { if (self::$types === null) {
self::$types = new HashMap([self::class, 'detectType']); self::$types = new HashMap([__CLASS__, 'detectType']);
} }
return self::$types; return self::$types;
} }
@@ -240,7 +234,7 @@ class Helpers
* Import SQL dump from file. * Import SQL dump from file.
* @return int count of sql commands * @return int count of sql commands
*/ */
public static function loadFromFile(Connection $connection, string $file, ?callable $onProgress = null): int public static function loadFromFile(Connection $connection, string $file, callable $onProgress = null): int
{ {
@set_time_limit(0); // intentionally @ @set_time_limit(0); // intentionally @
@@ -267,6 +261,7 @@ class Helpers
if ($onProgress) { if ($onProgress) {
$onProgress($count, isset($stat['size']) ? $size * 100 / $stat['size'] : null); $onProgress($count, isset($stat['size']) ? $size * 100 / $stat['size'] : null);
} }
} else { } else {
$sql .= $s; $sql .= $s;
} }
@@ -279,20 +274,23 @@ class Helpers
$onProgress($count, isset($stat['size']) ? 100 : null); $onProgress($count, isset($stat['size']) ? 100 : null);
} }
} }
fclose($handle); fclose($handle);
return $count; return $count;
} }
/** @internal */ /**
* @internal
*/
public static function false2Null($val) public static function false2Null($val)
{ {
return $val === false ? null : $val; return $val === false ? null : $val;
} }
/** @internal */ /**
* @internal
*/
public static function intVal($value): int public static function intVal($value): int
{ {
if (is_int($value)) { if (is_int($value)) {
@@ -301,7 +299,6 @@ class Helpers
if (is_float($value * 1)) { if (is_float($value * 1)) {
throw new Exception("Number $value is greater than integer."); throw new Exception("Number $value is greater than integer.");
} }
return (int) $value; return (int) $value;
} else { } else {
throw new Exception("Expected number, '$value' given."); throw new Exception("Expected number, '$value' given.");

View File

@@ -25,15 +25,11 @@ class FileLogger
/** @var int */ /** @var int */
public $filter; public $filter;
/** @var bool */
private $errorsOnly;
public function __construct(string $file, int $filter = null)
public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false)
{ {
$this->file = $file; $this->file = $file;
$this->filter = $filter ?: Dibi\Event::QUERY; $this->filter = $filter ?: Dibi\Event::QUERY;
$this->errorsOnly = $errorsOnly;
} }
@@ -42,43 +38,39 @@ class FileLogger
*/ */
public function logEvent(Dibi\Event $event): void public function logEvent(Dibi\Event $event): void
{ {
if ( if (($event->type & $this->filter) === 0) {
(($event->type & $this->filter) === 0)
|| ($this->errorsOnly === true && !$event->result instanceof \Exception)
) {
return; return;
} }
$handle = fopen($this->file, 'a');
if (!$handle) {
return; // or throw exception?
}
flock($handle, LOCK_EX);
if ($event->result instanceof \Exception) { if ($event->result instanceof \Exception) {
$message = $event->result->getMessage(); $message = $event->result->getMessage();
if ($code = $event->result->getCode()) { if ($code = $event->result->getCode()) {
$message = "[$code] $message"; $message = "[$code] $message";
} }
fwrite($handle,
$this->writeToFile(
$event,
"ERROR: $message" "ERROR: $message"
. "\n-- SQL: " . $event->sql . "\n-- SQL: " . $event->sql
. "\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name')
. ";\n-- " . date('Y-m-d H:i:s')
. "\n\n"
); );
} else { } else {
$this->writeToFile( fwrite($handle,
$event,
'OK: ' . $event->sql 'OK: ' . $event->sql
. ($event->count ? ";\n-- rows: " . $event->count : '') . ($event->count ? ";\n-- rows: " . $event->count : '')
. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000) . "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
. "\n-- source: " . implode(':', $event->source) . "\n-- source: " . implode(':', $event->source)
. "\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name')
. "\n-- " . date('Y-m-d H:i:s')
. "\n\n"
); );
} }
} fclose($handle);
private function writeToFile(Dibi\Event $event, string $message): void
{
$driver = $event->connection->getConfig('driver');
$message .=
"\n-- driver: " . (is_object($driver) ? get_class($driver) : $driver) . '/' . $event->connection->getConfig('name')
. "\n-- " . date('Y-m-d H:i:s')
. "\n\n";
file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX);
} }
} }

View File

@@ -36,7 +36,7 @@ class Column
private $info; private $info;
public function __construct(?Dibi\Reflector $reflector, array $info) public function __construct(Dibi\Reflector $reflector = null, array $info)
{ {
$this->reflector = $reflector; $this->reflector = $reflector;
$this->info = $info; $this->info = $info;
@@ -66,16 +66,13 @@ class Column
if (empty($this->info['table']) || !$this->reflector) { if (empty($this->info['table']) || !$this->reflector) {
throw new Dibi\Exception('Table is unknown or not available.'); throw new Dibi\Exception('Table is unknown or not available.');
} }
return new Table($this->reflector, ['name' => $this->info['table']]); return new Table($this->reflector, ['name' => $this->info['table']]);
} }
public function getTableName(): ?string public function getTableName(): ?string
{ {
return isset($this->info['table']) && $this->info['table'] != null // intentionally == return isset($this->info['table']) && $this->info['table'] != null ? $this->info['table'] : null; // intentionally ==
? $this->info['table']
: null;
} }
@@ -109,14 +106,18 @@ class Column
} }
/** @return mixed */ /**
* @return mixed
*/
public function getDefault() public function getDefault()
{ {
return $this->info['default'] ?? null; return $this->info['default'] ?? null;
} }
/** @return mixed */ /**
* @return mixed
*/
public function getVendorInfo(string $key) public function getVendorInfo(string $key)
{ {
return $this->info['vendor'][$key] ?? null; return $this->info['vendor'][$key] ?? null;

View File

@@ -33,7 +33,7 @@ class Database
private $tables; private $tables;
public function __construct(Dibi\Reflector $reflector, ?string $name = null) public function __construct(Dibi\Reflector $reflector, string $name = null)
{ {
$this->reflector = $reflector; $this->reflector = $reflector;
$this->name = $name; $this->name = $name;
@@ -46,7 +46,9 @@ class Database
} }
/** @return Table[] */ /**
* @return Table[]
*/
public function getTables(): array public function getTables(): array
{ {
$this->init(); $this->init();
@@ -54,7 +56,9 @@ class Database
} }
/** @return string[] */ /**
* @return string[]
*/
public function getTableNames(): array public function getTableNames(): array
{ {
$this->init(); $this->init();
@@ -62,7 +66,6 @@ class Database
foreach ($this->tables as $table) { foreach ($this->tables as $table) {
$res[] = $table->getName(); $res[] = $table->getName();
} }
return $res; return $res;
} }

View File

@@ -28,7 +28,7 @@ class Result
/** @var Column[]|null */ /** @var Column[]|null */
private $columns; private $columns;
/** @var Column[]|null */ /** @var string[]|null */
private $names; private $names;
@@ -38,7 +38,9 @@ class Result
} }
/** @return Column[] */ /**
* @return Column[]
*/
public function getColumns(): array public function getColumns(): array
{ {
$this->initColumns(); $this->initColumns();
@@ -46,7 +48,9 @@ class Result
} }
/** @return string[] */ /**
* @return string[]
*/
public function getColumnNames(bool $fullNames = false): array public function getColumnNames(bool $fullNames = false): array
{ {
$this->initColumns(); $this->initColumns();
@@ -54,7 +58,6 @@ class Result
foreach ($this->columns as $column) { foreach ($this->columns as $column) {
$res[] = $fullNames ? $column->getFullName() : $column->getName(); $res[] = $fullNames ? $column->getFullName() : $column->getName();
} }
return $res; return $res;
} }
@@ -83,9 +86,7 @@ class Result
{ {
if ($this->columns === null) { if ($this->columns === null) {
$this->columns = []; $this->columns = [];
$reflector = $this->driver instanceof Dibi\Reflector $reflector = $this->driver instanceof Dibi\Reflector ? $this->driver : null;
? $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

@@ -69,7 +69,9 @@ class Table
} }
/** @return Column[] */ /**
* @return Column[]
*/
public function getColumns(): array public function getColumns(): array
{ {
$this->initColumns(); $this->initColumns();
@@ -77,7 +79,9 @@ class Table
} }
/** @return string[] */ /**
* @return string[]
*/
public function getColumnNames(): array public function getColumnNames(): array
{ {
$this->initColumns(); $this->initColumns();
@@ -85,7 +89,6 @@ class Table
foreach ($this->columns as $column) { foreach ($this->columns as $column) {
$res[] = $column->getName(); $res[] = $column->getName();
} }
return $res; return $res;
} }
@@ -110,7 +113,9 @@ class Table
} }
/** @return ForeignKey[] */ /**
* @return ForeignKey[]
*/
public function getForeignKeys(): array public function getForeignKeys(): array
{ {
$this->initForeignKeys(); $this->initForeignKeys();
@@ -118,7 +123,9 @@ class Table
} }
/** @return Index[] */ /**
* @return Index[]
*/
public function getIndexes(): array public function getIndexes(): array
{ {
$this->initIndexes(); $this->initIndexes();
@@ -153,7 +160,6 @@ class Table
foreach ($info['columns'] as $key => $name) { foreach ($info['columns'] as $key => $name) {
$info['columns'][$key] = $this->columns[strtolower($name)]; $info['columns'][$key] = $this->columns[strtolower($name)];
} }
$this->indexes[strtolower($info['name'])] = new Index($info); $this->indexes[strtolower($info['name'])] = new Index($info);
if (!empty($info['primary'])) { if (!empty($info['primary'])) {
$this->primaryKey = $this->indexes[strtolower($info['name'])]; $this->primaryKey = $this->indexes[strtolower($info['name'])];

View File

@@ -19,7 +19,7 @@ class Result implements IDataSource
{ {
use Strict; use Strict;
/** @var ResultDriver|null */ /** @var ResultDriver */
private $driver; private $driver;
/** @var array Translate table */ /** @var array Translate table */
@@ -41,12 +41,10 @@ class Result implements IDataSource
private $formats = []; private $formats = [];
public function __construct(ResultDriver $driver, bool $normalize = true) public function __construct(ResultDriver $driver)
{ {
$this->driver = $driver; $this->driver = $driver;
if ($normalize) { $this->detectTypes();
$this->detectTypes();
}
} }
@@ -85,9 +83,7 @@ class Result implements IDataSource
*/ */
final public function seek(int $row): bool final public function seek(int $row): bool
{ {
return ($row !== 0 || $this->fetched) return ($row !== 0 || $this->fetched) ? $this->getResultDriver()->seek($row) : true;
? $this->getResultDriver()->seek($row)
: true;
} }
@@ -170,7 +166,6 @@ class Result implements IDataSource
if ($row === null) { if ($row === null) {
return null; return null;
} }
$this->fetched = true; $this->fetched = true;
$this->normalize($row); $this->normalize($row);
if ($this->rowFactory) { if ($this->rowFactory) {
@@ -178,7 +173,6 @@ class Result implements IDataSource
} elseif ($this->rowClass) { } elseif ($this->rowClass) {
return new $this->rowClass($row); return new $this->rowClass($row);
} }
return $row; return $row;
} }
@@ -193,7 +187,6 @@ class Result implements IDataSource
if ($row === null) { if ($row === null) {
return null; return null;
} }
$this->fetched = true; $this->fetched = true;
$this->normalize($row); $this->normalize($row);
return reset($row); return reset($row);
@@ -204,9 +197,9 @@ class Result implements IDataSource
* Fetches all records from table. * Fetches all records from table.
* @return Row[]|array[] * @return Row[]|array[]
*/ */
final public function fetchAll(?int $offset = null, ?int $limit = null): array final public function fetchAll(int $offset = null, int $limit = null): array
{ {
$limit = $limit ?? -1; $limit = $limit === null ? -1 : $limit;
$this->seek($offset ?: 0); $this->seek($offset ?: 0);
$row = $this->fetch(); $row = $this->fetch();
if (!$row) { if (!$row) {
@@ -218,7 +211,6 @@ class Result implements IDataSource
if ($limit === 0) { if ($limit === 0) {
break; break;
} }
$limit--; $limit--;
$data[] = $row; $data[] = $row;
} while ($row = $this->fetch()); } while ($row = $this->fetch());
@@ -250,9 +242,6 @@ class Result implements IDataSource
$data = null; $data = null;
$assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
if (!$assoc) {
throw new \InvalidArgumentException("Invalid descriptor '$assoc'.");
}
// check columns // check columns
foreach ($assoc as $as) { foreach ($assoc as $as) {
@@ -291,6 +280,7 @@ class Result implements IDataSource
} else { } else {
$x = &$x->{$assoc[$i + 1]}; $x = &$x->{$assoc[$i + 1]};
} }
} elseif ($as !== '|') { // associative-array node } elseif ($as !== '|') { // associative-array node
$x = &$x[(string) $row->$as]; $x = &$x[(string) $row->$as];
} }
@@ -302,12 +292,13 @@ class Result implements IDataSource
} while ($row = $this->fetch()); } while ($row = $this->fetch());
unset($x); unset($x);
/** @var mixed[] $data */
return $data; return $data;
} }
/** @deprecated */ /**
* @deprecated
*/
private function oldFetchAssoc(string $assoc) private function oldFetchAssoc(string $assoc)
{ {
$this->seek(0); $this->seek(0);
@@ -348,6 +339,7 @@ class Result implements IDataSource
} else { } else {
$x = &$x[$assoc[$i + 1]]; $x = &$x[$assoc[$i + 1]];
} }
} elseif ($as === '@') { // "object" node } elseif ($as === '@') { // "object" node
if ($x === null) { if ($x === null) {
$x = clone $row; $x = clone $row;
@@ -356,15 +348,18 @@ class Result implements IDataSource
} else { } else {
$x = &$x->{$assoc[$i + 1]}; $x = &$x->{$assoc[$i + 1]};
} }
} else { // associative-array node } else { // associative-array node
$x = &$x[(string) $row->$as]; $x = &$x[(string) $row->$as];
} }
} }
if ($x === null) { // build leaf if ($x === null) { // build leaf
$x = $leaf === '=' if ($leaf === '=') {
? $row->toArray() $x = $row->toArray();
: $row; } else {
$x = $row;
}
} }
} while ($row = $this->fetch()); } while ($row = $this->fetch());
@@ -377,7 +372,7 @@ class Result implements IDataSource
* Fetches all records from table like $key => $value pairs. * Fetches all records from table like $key => $value pairs.
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
final public function fetchPairs(?string $key = null, ?string $value = null): array final public function fetchPairs(string $key = null, string $value = null): array
{ {
$this->seek(0); $this->seek(0);
$row = $this->fetch(); $row = $this->fetch();
@@ -399,7 +394,6 @@ class Result implements IDataSource
do { do {
$data[] = $row[$key]; $data[] = $row[$key];
} while ($row = $this->fetch()); } while ($row = $this->fetch());
return $data; return $data;
} }
@@ -414,7 +408,6 @@ class Result implements IDataSource
do { do {
$data[] = $row[$value]; $data[] = $row[$value];
} while ($row = $this->fetch()); } while ($row = $this->fetch());
return $data; return $data;
} }
@@ -458,14 +451,9 @@ class Result implements IDataSource
if (!isset($row[$key])) { // null if (!isset($row[$key])) { // null
continue; continue;
} }
$value = $row[$key]; $value = $row[$key];
$format = $this->formats[$type] ?? null;
if ($type === null || $format === 'native') { if ($type === Type::TEXT) {
$row[$key] = $value;
} elseif ($type === Type::TEXT) {
$row[$key] = (string) $value; $row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) { } elseif ($type === Type::INTEGER) {
@@ -482,11 +470,9 @@ class Result implements IDataSource
} elseif ($p !== false && $e !== false) { } elseif ($p !== false && $e !== false) {
$value = rtrim($value, '.'); $value = rtrim($value, '.');
} }
if ($value === '' || $value[0] === '.') { if ($value === '' || $value[0] === '.') {
$value = '0' . $value; $value = '0' . $value;
} }
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value)) $row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float ? $float
: $value; : $value;
@@ -495,31 +481,26 @@ 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, 7) !== '0000-00') { // '', null, false, '0000-00-00', ... if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ...
$value = new DateTime($value); $value = new DateTime($value);
$row[$key] = $format ? $value->format($format) : $value; $row[$key] = empty($this->formats[$type]) ? $value : $value->format($this->formats[$type]);
} 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);
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S"); $row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$value->invert = (int) (bool) $m[1]; $row[$key]->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) $row[$key] = is_string($value) ? $this->getResultDriver()->unescapeBinary($value) : $value;
? $this->getResultDriver()->unescapeBinary($value)
: $value;
} elseif ($type === Type::JSON) { } elseif ($type === Type::JSON) {
if ($format === 'string') { // back compatibility with 'native' $row[$key] = json_decode($value, true);
$row[$key] = $value;
} else {
$row[$key] = json_decode($value, $format === 'array');
}
} else { } else {
throw new \RuntimeException('Unexpected type ' . $type); $row[$key] = $value;
} }
} }
} }
@@ -555,7 +536,7 @@ class Result implements IDataSource
/** /**
* Sets type format. * Sets date format.
*/ */
final public function setFormat(string $type, ?string $format): self final public function setFormat(string $type, ?string $format): self
{ {
@@ -564,16 +545,6 @@ 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.
*/ */
@@ -594,12 +565,13 @@ class Result implements IDataSource
if ($this->meta === null) { if ($this->meta === null) {
$this->meta = new Reflection\Result($this->getResultDriver()); $this->meta = new Reflection\Result($this->getResultDriver());
} }
return $this->meta; return $this->meta;
} }
/** @return Reflection\Column[] */ /**
* @return Reflection\Column[]
*/
final public function getColumns(): array final public function getColumns(): array
{ {
return $this->getInfo()->getColumns(); return $this->getInfo()->getColumns();

View File

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

View File

@@ -13,7 +13,6 @@ namespace Dibi;
/** /**
* Result set single row. * Result set single row.
*/ */
#[\AllowDynamicProperties]
class Row implements \ArrayAccess, \IteratorAggregate, \Countable class Row implements \ArrayAccess, \IteratorAggregate, \Countable
{ {
public function __construct(array $arr) public function __construct(array $arr)
@@ -34,17 +33,15 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
* Converts value to DateTime object. * Converts value to DateTime object.
* @return DateTime|string|null * @return DateTime|string|null
*/ */
public function asDateTime(string $key, ?string $format = null) public function asDateTime(string $key, string $format = null)
{ {
$time = $this[$key]; $time = $this[$key];
if (!$time instanceof DateTime) { if (!$time instanceof DateTime) {
if (!$time || substr((string) $time, 0, 7) === '0000-00') { // '', null, false, '0000-00-00', ... if (!$time || substr((string) $time, 0, 3) === '000') { // '', null, false, '0000-00-00', ...
return null; return null;
} }
$time = new DateTime($time); $time = new DateTime($time);
} }
return $format === null ? $time : $time->format($format); return $format === null ? $time : $time->format($format);
} }
@@ -56,47 +53,40 @@ 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(): int final public function count()
{ {
return count((array) $this); return count((array) $this);
} }
final public function getIterator(): \ArrayIterator final public function getIterator()
{ {
return new \ArrayIterator($this); return new \ArrayIterator($this);
} }
final public function offsetSet($nm, $val): void final public function offsetSet($nm, $val)
{ {
$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): bool final public function offsetExists($nm)
{ {
return isset($this->$nm); return isset($this->$nm);
} }
final public function offsetUnset($nm): void final public function offsetUnset($nm)
{ {
unset($this->$nm); unset($this->$nm);
} }

View File

@@ -29,12 +29,15 @@ trait Strict
*/ */
public function __call(string $name, array $args) public function __call(string $name, array $args)
{ {
$class = method_exists($this, $name) ? 'parent' : static::class; $class = get_class($this);
if ($cb = self::extensionMethod($class . '::' . $name)) { // back compatiblity
trigger_error("Extension methods such as $class::$name() are deprecated", E_USER_DEPRECATED);
array_unshift($args, $this);
return $cb(...$args);
}
$class = method_exists($this, $name) ? 'parent' : get_class($this);
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC); $items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
$items = array_map(function ($item) { return $item->getName(); }, $items); $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
$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");
} }
@@ -45,12 +48,9 @@ trait Strict
*/ */
public static function __callStatic(string $name, array $args) public static function __callStatic(string $name, array $args)
{ {
$rc = new ReflectionClass(static::class); $rc = new ReflectionClass(get_called_class());
$items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), function ($m) { return $m->isPublic(); }); $items = array_intersect($rc->getMethods(ReflectionMethod::IS_PUBLIC), $rc->getMethods(ReflectionMethod::IS_STATIC));
$items = array_map(function ($item) { return $item->getName(); }, $items); $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
$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");
} }
@@ -67,13 +67,9 @@ trait Strict
$ret = $this->$m(); $ret = $this->$m();
return $ret; return $ret;
} }
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }); $items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
$items = array_map(function ($item) { return $item->getName(); }, $items); $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
$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");
} }
@@ -85,11 +81,8 @@ trait Strict
public function __set(string $name, $value) public function __set(string $name, $value)
{ {
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }); $items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
$items = array_map(function ($item) { return $item->getName(); }, $items); $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
$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");
} }
@@ -106,7 +99,42 @@ trait Strict
*/ */
public function __unset(string $name) public function __unset(string $name)
{ {
$class = static::class; $class = get_class($this);
throw new \LogicException("Attempt to unset undeclared property $class::$$name."); throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
} }
/**
* @return mixed
* @deprecated
*/
public static function extensionMethod(string $name, callable $callback = null)
{
if (strpos($name, '::') === false) {
$class = get_called_class();
} else {
[$class, $name] = explode('::', $name);
$class = (new ReflectionClass($class))->getName();
}
$list = &self::$extMethods[strtolower($name)];
if ($callback === null) { // getter
$cache = &$list[''][$class];
if (isset($cache)) {
return $cache;
}
foreach ([$class] + class_parents($class) + class_implements($class) as $cl) {
if (isset($list[$cl])) {
return $cache = $list[$cl];
}
}
return $cache = false;
} else { // setter
trigger_error("Extension methods such as $class::$name() are deprecated", E_USER_DEPRECATED);
$list[$class] = $callback;
$list[''] = null;
}
}
} }

View File

@@ -69,7 +69,6 @@ final class Translator
while (count($args) === 1 && is_array($args[0])) { // implicit array expansion while (count($args) === 1 && is_array($args[0])) { // implicit array expansion
$args = array_values($args[0]); $args = array_values($args[0]);
} }
$this->args = $args; $this->args = $args;
$this->errors = []; $this->errors = [];
@@ -93,31 +92,28 @@ final class Translator
$sql[] = $arg; $sql[] = $arg;
} else { } else {
$sql[] = substr($arg, 0, $toSkip) $sql[] = substr($arg, 0, $toSkip)
// note: this can change $this->args & $this->cursor & ... /*
. preg_replace_callback( . preg_replace_callback('/
<<<'XX' (?=[`[\'":%?]) ## speed-up
/ (?:
(?=[`['":%?]) ## speed-up `(.+?)`| ## 1) `identifier`
(?: \[(.+?)\]| ## 2) [identifier]
`(.+?)`| ## 1) `identifier` (\')((?:\'\'|[^\'])*)\'| ## 3,4) 'string'
\[(.+?)\]| ## 2) [identifier] (")((?:""|[^"])*)"| ## 5,6) "string"
(')((?:''|[^'])*)'| ## 3,4) string (\'|")| ## 7) lone quote
(")((?:""|[^"])*)"| ## 5,6) "string" :(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
('|")| ## 7) lone quote %([a-zA-Z~][a-zA-Z0-9~]{0,5})|## 10) modifier
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution: (\?) ## 11) placeholder
%([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier )/xs',
(\?) ## 11) placeholder */ // note: this can change $this->args & $this->cursor & ...
)/xs . preg_replace_callback('/(?=[`[\'":%?])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?)|%([a-zA-Z~][a-zA-Z0-9~]{0,5})|(\?))/s',
XX
,
[$this, 'cb'], [$this, 'cb'],
substr($arg, $toSkip) substr($arg, $toSkip)
); );
if (preg_last_error()) { if (preg_last_error()) {
throw new PcreException; throw new PcreException;
} }
} }
continue; continue;
} }
@@ -140,10 +136,8 @@ XX
if ($lastArr === $cursor - 1) { if ($lastArr === $cursor - 1) {
$sql[] = ','; $sql[] = ',';
} }
$sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a'); $sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
} }
$lastArr = $cursor; $lastArr = $cursor;
continue; continue;
} }
@@ -152,11 +146,12 @@ XX
$sql[] = $this->formatValue($arg, null); $sql[] = $this->formatValue($arg, null);
} // while } // while
if ($comment) { if ($comment) {
$sql[] = '*/'; $sql[] = '*/';
} }
$sql = trim(implode(' ', $sql), ' '); $sql = implode(' ', $sql);
if ($this->errors) { if ($this->errors) {
throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql); throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql);
@@ -217,14 +212,13 @@ XX
} else { } else {
$op = '= '; $op = '= ';
} }
$vx[] = $k . $op . $v; $vx[] = $k . $op . $v;
} }
} else { } else {
$vx[] = $this->formatValue($v, 'ex'); $vx[] = $this->formatValue($v, 'ex');
} }
} }
return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')'; return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')';
case 'n': // key, key, ... identifier names case 'n': // key, key, ... identifier names
@@ -236,7 +230,6 @@ XX
$vx[] = $this->identifiers->{$pair[0]}; $vx[] = $this->identifiers->{$pair[0]};
} }
} }
return implode(', ', $vx); return implode(', ', $vx);
@@ -246,7 +239,6 @@ XX
$vx[] = $this->identifiers->{$pair[0]} . '=' $vx[] = $this->identifiers->{$pair[0]} . '='
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); . $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return implode(', ', $vx); return implode(', ', $vx);
@@ -256,7 +248,6 @@ XX
$pair = explode('%', (string) $k, 2); // split into identifier & modifier $pair = explode('%', (string) $k, 2); // split into identifier & modifier
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); $vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')'; return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')';
@@ -266,7 +257,6 @@ XX
$kx[] = $this->identifiers->{$pair[0]}; $kx[] = $this->identifiers->{$pair[0]};
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); $vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')'; return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ... case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ...
@@ -289,11 +279,9 @@ XX
$vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null)); $vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null));
} }
} }
foreach ($vx as $k => $v) { foreach ($vx as $k => $v) {
$vx[$k] = '(' . implode(', ', $v) . ')'; $vx[$k] = '(' . implode(', ', $v) . ')';
} }
return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx); return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx);
case 'by': // key ASC, key DESC case 'by': // key ASC, key DESC
@@ -301,13 +289,12 @@ 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;
} }
} }
return implode(', ', $vx); return implode(', ', $vx);
case 'ex!': case 'ex!':
@@ -321,15 +308,10 @@ XX
foreach ($value as $v) { foreach ($value as $v) {
$vx[] = $this->formatValue($v, $modifier); $vx[] = $this->formatValue($v, $modifier);
} }
return implode(', ', $vx); return implode(', ', $vx);
} }
} }
// object-to-scalar procession
if ($value instanceof \BackedEnum && is_scalar($value->value)) {
$value = $value->value;
}
// with modifier procession // with modifier procession
if ($modifier) { if ($modifier) {
@@ -340,13 +322,7 @@ 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 ( } elseif ($value instanceof \DateTimeInterface && ($modifier === 'd' || $modifier === 't' || $modifier === 'dt')) {
$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);
@@ -356,29 +332,21 @@ XX
switch ($modifier) { switch ($modifier) {
case 's': // string case 's': // string
return $value === null return $value === null ? 'NULL' : $this->driver->escapeText((string) $value);
? 'NULL'
: $this->driver->escapeText((string) $value);
case 'bin':// binary case 'bin':// binary
return $value === null return $value === null ? 'NULL' : $this->driver->escapeBinary($value);
? 'NULL'
: $this->driver->escapeBinary($value);
case 'b': // boolean case 'b': // boolean
return $value === null return $value === null ? 'NULL' : $this->driver->escapeBool((bool) $value);
? 'NULL'
: $this->driver->escapeBool((bool) $value);
case 'sN': // string or null case 'sN': // string or null
case 'sn': case 'sn':
return $value === '' || $value === 0 || $value === null return $value == '' ? 'NULL' : $this->driver->escapeText((string) $value); // notice two equal signs
? 'NULL'
: $this->driver->escapeText((string) $value);
case 'iN': // signed int or null case 'iN': // signed int or null
if ($value === '' || $value === 0 || $value === null) { if ($value == '') {
return 'NULL'; $value = null;
} }
// break omitted // break omitted
case 'i': // signed int case 'i': // signed int
@@ -413,14 +381,10 @@ XX
case 'dt': // datetime case 'dt': // datetime
if ($value === null) { if ($value === null) {
return 'NULL'; return 'NULL';
} elseif (!$value instanceof \DateTimeInterface) { } else {
$value = new DateTime($value); return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value);
} }
// break omitted
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
return $this->identifiers->$value; return $this->identifiers->$value;
@@ -435,43 +399,27 @@ 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( . preg_replace_callback(
<<<'XX' '/(?=[`[\'":])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?))/s',
/ [$this, 'cb'],
(?=[`['":]) substr($value, $toSkip)
(?: );
`(.+?)`|
\[(.+?)\]|
(')((?:''|[^'])*)'|
(")((?:""|[^"])*)"|
('|")|
:(\S*?:)([a-zA-Z0-9._]?)
)/sx
XX
,
[$this, 'cb'],
substr($value, $toSkip)
);
if (preg_last_error()) { if (preg_last_error()) {
throw new PcreException; throw new PcreException;
} }
} }
return $value; return $value;
case 'SQL': // preserve as real SQL (TODO: rename to %sql) case 'SQL': // preserve as real SQL (TODO: rename to %sql)
return (string) $value; return (string) $value;
case 'like~': // LIKE string% case 'like~': // LIKE string%
return $this->driver->escapeLike($value, 2);
case '~like': // LIKE %string
return $this->driver->escapeLike($value, 1); return $this->driver->escapeLike($value, 1);
case '~like~': // LIKE %string% case '~like': // LIKE %string
return $this->driver->escapeLike($value, 3); return $this->driver->escapeLike($value, -1);
case 'like': // LIKE string case '~like~': // LIKE %string%
return $this->driver->escapeLike($value, 0); return $this->driver->escapeLike($value, 0);
case 'and': case 'and':
@@ -487,6 +435,7 @@ XX
} }
} }
// without modifier procession // without modifier procession
if (is_string($value)) { if (is_string($value)) {
return $this->driver->escapeText($value); return $this->driver->escapeText($value);
@@ -506,9 +455,6 @@ XX
} elseif ($value instanceof \DateTimeInterface) { } elseif ($value instanceof \DateTimeInterface) {
return $this->driver->escapeDateTime($value); return $this->driver->escapeDateTime($value);
} elseif ($value instanceof \DateInterval) {
return $this->driver->escapeDateInterval($value);
} elseif ($value instanceof Literal) { } elseif ($value instanceof Literal) {
return (string) $value; return (string) $value;
@@ -568,7 +514,6 @@ XX
$this->comment = true; $this->comment = true;
return '/*'; return '/*';
} }
return ''; return '';
} elseif ($mod === 'else') { } elseif ($mod === 'else') {
@@ -581,6 +526,7 @@ XX
$this->comment = true; $this->comment = true;
return '/*'; return '/*';
} }
} elseif ($mod === 'end') { } elseif ($mod === 'end') {
$this->ifLevel--; $this->ifLevel--;
if ($this->ifLevelStart === $this->ifLevel + 1) { if ($this->ifLevelStart === $this->ifLevel + 1) {
@@ -589,7 +535,6 @@ XX
$this->comment = false; $this->comment = false;
return '*/'; return '*/';
} }
return ''; return '';
} elseif ($mod === 'ex') { // array expansion } elseif ($mod === 'ex') { // array expansion
@@ -604,7 +549,6 @@ XX
} else { } else {
$this->limit = Helpers::intVal($arg); $this->limit = Helpers::intVal($arg);
} }
return ''; return '';
} elseif ($mod === 'ofs') { // apply offset } elseif ($mod === 'ofs') { // apply offset
@@ -615,7 +559,6 @@ XX
} else { } else {
$this->offset = Helpers::intVal($arg); $this->offset = Helpers::intVal($arg);
} }
return ''; return '';
} else { // default processing } else { // default processing
@@ -647,9 +590,7 @@ 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] === '' return $matches[9] == '' ? $this->formatValue($m, null) : $m . $matches[9]; // value or identifier
? $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');
@@ -669,7 +610,6 @@ XX
$v = $this->driver->escapeIdentifier($v); $v = $this->driver->escapeIdentifier($v);
} }
} }
return implode('.', $parts); return implode('.', $parts);
} }
} }

View File

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

View File

@@ -25,7 +25,6 @@ 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 +43,8 @@ class dibi
IDENTIFIER = 'n'; IDENTIFIER = 'n';
/** version */ /** version */
public const VERSION = '4.2.6'; public const
VERSION = '4.0.3';
/** sorting order */ /** sorting order */
public const public const
@@ -75,7 +75,7 @@ class dibi
*/ */
final public function __construct() final public function __construct()
{ {
throw new LogicException('Cannot instantiate static class ' . static::class); throw new LogicException('Cannot instantiate static class ' . get_class($this));
} }
@@ -106,7 +106,7 @@ class dibi
* Retrieve active connection. * Retrieve active connection.
* @throws Dibi\Exception * @throws Dibi\Exception
*/ */
public static function getConnection(?string $name = null): Dibi\Connection public static function getConnection(string $name = null): Dibi\Connection
{ {
if ($name === null) { if ($name === null) {
if (self::$connection === null) { if (self::$connection === null) {
@@ -145,6 +145,26 @@ class dibi
} }
/**
* @deprecated
*/
public static function affectedRows(): int
{
trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
return self::getConnection()->getAffectedRows();
}
/**
* @deprecated
*/
public static function insertId(string $sequence = null): int
{
trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
return self::getConnection()->getInsertId($sequence);
}
/********************* misc tools ****************d*g**/ /********************* misc tools ****************d*g**/
@@ -162,7 +182,7 @@ class dibi
/** /**
* Strips microseconds part. * Strips microseconds part.
*/ */
public static function stripMicroseconds(DateTimeInterface $dt): DateTimeInterface public static function stripMicroseconds(\DateTimeInterface $dt): \DateTimeInterface
{ {
$class = get_class($dt); $class = get_class($dt);
return new $class($dt->format('Y-m-d H:i:s'), $dt->getTimezone()); return new $class($dt->format('Y-m-d H:i:s'), $dt->getTimezone());

View File

@@ -22,7 +22,7 @@ class Exception extends \Exception
/** /**
* @param int|string $code * @param int|string $code
*/ */
public function __construct(string $message = '', $code = 0, ?string $sql = null, ?\Throwable $previous = null) public function __construct(string $message = '', $code = 0, string $sql = null, \Throwable $previous = null)
{ {
parent::__construct($message, 0, $previous); parent::__construct($message, 0, $previous);
$this->code = $code; $this->code = $code;
@@ -93,7 +93,7 @@ class ProcedureException extends Exception
/** /**
* Construct the exception. * Construct the exception.
*/ */
public function __construct(string $message = '', int $code = 0, string $severity = '', ?string $sql = null) public function __construct(string $message = '', int $code = 0, string $severity = '', string $sql = null)
{ {
parent::__construct($message, $code, $sql); parent::__construct($message, $code, $sql);
$this->severity = $severity; $this->severity = $severity;

View File

@@ -51,19 +51,19 @@ interface Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws DriverException * @throws DriverException
*/ */
function begin(?string $savepoint = null): void; function begin(string $savepoint = null): void;
/** /**
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws DriverException * @throws DriverException
*/ */
function commit(?string $savepoint = null): void; function commit(string $savepoint = null): void;
/** /**
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws DriverException * @throws DriverException
*/ */
function rollback(?string $savepoint = null): void; function rollback(string $savepoint = null): void;
/** /**
* Returns the connection resource. * Returns the connection resource.
@@ -87,11 +87,15 @@ interface Driver
function escapeBool(bool $value): string; function escapeBool(bool $value): string;
function escapeDate(\DateTimeInterface $value): string; /**
* @param \DateTimeInterface|string|int $value
*/
function escapeDate($value): string;
function escapeDateTime(\DateTimeInterface $value): string; /**
* @param \DateTimeInterface|string|int $value
function escapeDateInterval(\DateInterval $value): string; */
function escapeDateTime($value): string;
/** /**
* Encodes string for use in a LIKE statement. * Encodes string for use in a LIKE statement.
@@ -224,20 +228,20 @@ interface IConnection
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query. * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
* @throws Exception * @throws Exception
*/ */
function getInsertId(?string $sequence = null): int; function getInsertId(string $sequence = null): int;
/** /**
* Begins a transaction (if supported). * Begins a transaction (if supported).
*/ */
function begin(?string $savepoint = null): void; function begin(string $savepoint = null): void;
/** /**
* Commits statements in a transaction. * Commits statements in a transaction.
*/ */
function commit(?string $savepoint = null): void; function commit(string $savepoint = null): void;
/** /**
* Rollback changes in a transaction. * Rollback changes in a transaction.
*/ */
function rollback(?string $savepoint = null): void; function rollback(string $savepoint = null): void;
} }

View File

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

View File

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

View File

@@ -1,89 +0,0 @@
[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

@@ -0,0 +1,38 @@
[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

@@ -12,7 +12,7 @@ use Tester\Assert;
require __DIR__ . '/bootstrap.php'; require __DIR__ . '/bootstrap.php';
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config); $conn = new Connection($config);
Assert::true($conn->isConnected()); Assert::true($conn->isConnected());
@@ -21,7 +21,7 @@ test('', function () use ($config) {
}); });
test('lazy', function () use ($config) { test(function () use ($config) { // lazy
$conn = new Connection($config + ['lazy' => true]); $conn = new Connection($config + ['lazy' => true]);
Assert::false($conn->isConnected()); Assert::false($conn->isConnected());
@@ -30,7 +30,7 @@ test('lazy', function () use ($config) {
}); });
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config); $conn = new Connection($config);
Assert::true($conn->isConnected()); Assert::true($conn->isConnected());
@@ -40,7 +40,7 @@ test('', function () use ($config) {
}); });
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config); $conn = new Connection($config);
Assert::true($conn->isConnected()); Assert::true($conn->isConnected());
@@ -52,7 +52,7 @@ test('', function () use ($config) {
}); });
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config); $conn = new Connection($config);
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle()); Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
@@ -63,16 +63,7 @@ test('', function () use ($config) {
}); });
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
$conn->__destruct();
Assert::false($conn->isConnected());
});
test('', function () use ($config) {
Assert::exception(function () use ($config) { Assert::exception(function () use ($config) {
new Connection($config + ['onConnect' => '']); new Connection($config + ['onConnect' => '']);
}, InvalidArgumentException::class, "Configuration option 'onConnect' must be array."); }, InvalidArgumentException::class, "Configuration option 'onConnect' must be array.");

View File

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

View File

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

@@ -1,5 +1,4 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Dibi\Row; use Dibi\Row;
@@ -78,7 +77,7 @@ SELECT [product_id]
FROM (SELECT * FROM products) t FROM (SELECT * FROM products) t
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1) WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
ORDER BY [product_id] ASC ORDER BY [product_id] ASC
) t"), dibi::$sql); ) t"), dibi::$sql);
Assert::same(1, $ds->toDataSource()->count()); Assert::same(1, $ds->toDataSource()->count());
@@ -94,7 +93,7 @@ SELECT [product_id]
FROM (SELECT * FROM products) t FROM (SELECT * FROM products) t
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1) WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
ORDER BY [product_id] ASC ORDER BY [product_id] ASC
"), "),
dibi::$sql dibi::$sql
); );
@@ -107,7 +106,7 @@ SELECT [product_id]
FROM (SELECT * FROM products) t FROM (SELECT * FROM products) t
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1) WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
ORDER BY [product_id] ASC ORDER BY [product_id] ASC
) t"), ) t"),
(string) $fluent (string) $fluent
); );

View File

@@ -14,6 +14,8 @@ 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -95,9 +95,9 @@ $fluent = $conn->select('*')
Assert::same( Assert::same(
reformat([ reformat([
'odbc' => 'SELECT TOP 1 * FROM (SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123) t', 'odbc' => 'SELECT TOP 1 * FROM ( SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123) t',
'sqlsrv' => 'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY', 'sqlsrv' => ' SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY',
'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 LIMIT 1', ' SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 LIMIT 1',
]), ]),
(string) $fluent (string) $fluent
); );
@@ -134,10 +134,7 @@ $fluent = $conn->select('*')
->where(['x' => 'a', 'b', 'c']); ->where(['x' => 'a', 'b', 'c']);
Assert::same( Assert::same(
reformat([ reformat('SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = \'a\') AND (b) AND (c)'),
'sqlsrv' => "SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = N'a') AND (b) AND (c)",
"SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = 'a') AND (b) AND (c)",
]),
(string) $fluent (string) $fluent
); );

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ Assert::same(
); );
if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) { if (!in_array($config['driver'], ['sqlite', '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', 'sqlite3', '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', 'sqlite3', 'pdo', 'sqlsrv'], true)) { if (!in_array($config['driver'], ['sqlite', 'pdo', 'sqlsrv'], true)) {
Assert::same('products', $columns[0]->getTableName()); Assert::same('products', $columns[0]->getTableName());
} }
Assert::null($columns[0]->getVendorInfo('xxx')); Assert::null($columns[0]->getVendorInfo('xxx'));

View File

@@ -1,5 +1,4 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Dibi\Type; use Dibi\Type;
@@ -25,18 +24,7 @@ class MockResult extends Dibi\Result
} }
test('', function () { 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 () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::BOOL); $result->setType('col', Type::BOOL);
@@ -58,7 +46,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TEXT); $result->setType('col', Type::TEXT);
@@ -74,7 +62,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::FLOAT); $result->setType('col', Type::FLOAT);
@@ -151,7 +139,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::INTEGER); $result->setType('col', Type::INTEGER);
@@ -159,14 +147,7 @@ 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]));
if (PHP_VERSION_ID < 80000) { Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning in PHP 7.1
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']));
@@ -184,7 +165,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DATETIME);
@@ -202,7 +183,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DATETIME);
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s'); $result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
@@ -221,7 +202,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATE); $result->setType('col', Type::DATE);
@@ -237,7 +218,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TIME); $result->setType('col', Type::TIME);

View File

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

View File

@@ -35,10 +35,6 @@ 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,9 +23,7 @@ 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', 'UpdAAB', 'aab', 'aaa'
'aab',
'aaa'
); );
$conn->query('INSERT INTO %n DEFAULT VALUES', 'aab'); $conn->query('INSERT INTO %n DEFAULT VALUES', 'aab');

View File

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

View File

@@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$translator = new Dibi\Translator($conn);
switch ($config['system']) {
case 'mysql':
Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null));
Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null));
Assert::exception(function () use ($translator) {
$translator->formatValue(new DateInterval('P2Y4DT6H8M'), null);
}, Dibi\NotSupportedException::class, 'Only time interval is supported.');
break;
default:
Assert::exception(function () use ($translator) {
$translator->formatValue(new DateInterval('PT10H20M30S'), null);
}, Dibi\Exception::class);
}

View File

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

View File

@@ -19,17 +19,12 @@ Assert::same(
SELECT * SELECT *
FROM [customers] FROM [customers]
/* WHERE ... LIKE ... */'), /* WHERE ... LIKE ... */'),
$conn->translate(
' $conn->translate('
SELECT * SELECT *
FROM [customers] FROM [customers]
%if', %if', isset($name), 'WHERE [name] LIKE %s', 'xxx', '%end'
isset($name), ));
'WHERE [name] LIKE %s',
'xxx',
'%end'
)
);
// if & else & end (last end is optional) // if & else & end (last end is optional)
@@ -37,14 +32,11 @@ Assert::same(
reformat(' reformat('
SELECT * SELECT *
FROM [customers] /* ... */'), FROM [customers] /* ... */'),
$conn->translate(
' $conn->translate('
SELECT * SELECT *
FROM %if', FROM %if', true, '[customers] %else [products]'
true, ));
'[customers] %else [products]'
)
);
// if & else & (optional) end // if & else & (optional) end
@@ -56,63 +48,43 @@ 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
Assert::match( Assert::match(
reformat([ reformat("
'sqlsrv' => "
SELECT *
FROM [customers]
WHERE
[name] LIKE N'xxx'
/* AND ...=1 */
/* 1 LIMIT 10 */",
"
SELECT * SELECT *
FROM [customers] FROM [customers]
WHERE WHERE
[name] LIKE 'xxx' [name] LIKE 'xxx'
/* AND ...=1 */ /* AND ...=1 */
/* 1 LIMIT 10 */", /* 1 LIMIT 10 */"),
]),
$conn->translate( $conn->translate('
'
SELECT * SELECT *
FROM [customers] FROM [customers]
WHERE WHERE
%if', %if', true, '[name] LIKE %s', 'xxx', '
true, %if', false, 'AND [admin]=1 %end
'[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', '%if', false,
false, '%lmt', 3,
'%lmt', '%ofs', 5,
3, '%end'
'%ofs', ));
5,
'%end'
)
);

View File

@@ -1,41 +0,0 @@
<?php
/**
* @phpVersion 8.1
* @dataProvider ../databases.ini
*/
declare(strict_types=1);
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$translator = new Dibi\Translator($conn);
enum EnumInt: int
{
case One = 1;
}
enum EnumString: string
{
case One = 'one';
}
enum PureEnum
{
case One;
}
Assert::equal('1', $translator->formatValue(EnumInt::One, null));
Assert::equal(match ($config['driver']) {
'sqlsrv' => "N'one'",
default => "'one'",
}, $translator->formatValue(EnumString::One, null));
Assert::equal('**Unexpected PureEnum**', $translator->formatValue(PureEnum::One, null));

View File

@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @dataProvider ../databases.ini !=sqlsrv * @dataProvider ../databases.ini
*/ */
declare(strict_types=1); declare(strict_types=1);
@@ -71,9 +71,3 @@ Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'baa', 'aa'));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'aab', 'aa')); Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'aab', 'aa'));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like~', 'bba', '%a')); Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like~', 'bba', '%a'));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'b%a', '%a')); Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'b%a', '%a'));
// matches
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like', 'a', 'a'));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like', 'a', 'aa'));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like', 'a', 'b'));

View File

@@ -16,10 +16,7 @@ $conn = new Dibi\Connection($config + ['formatDateTime' => "'Y-m-d H:i:s.u'", 'f
// Dibi detects INSERT or REPLACE command & booleans // Dibi detects INSERT or REPLACE command & booleans
Assert::same( Assert::same(
reformat([ reformat("REPLACE INTO [products] ([title], [price]) VALUES ('Drticka', 318)"),
'sqlsrv' => "REPLACE INTO [products] ([title], [price]) VALUES (N'Drticka', 318)",
"REPLACE INTO [products] ([title], [price]) VALUES ('Drticka', 318)",
]),
$conn->translate('REPLACE INTO [products]', [ $conn->translate('REPLACE INTO [products]', [
'title' => 'Drticka', 'title' => 'Drticka',
'price' => 318, 'price' => 318,
@@ -34,10 +31,7 @@ $array = [
'brand' => null, 'brand' => null,
]; ];
Assert::same( Assert::same(
reformat([ reformat('INSERT INTO [products] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'),
'sqlsrv' => "INSERT INTO [products] ([title], [price], [brand]) VALUES (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL)",
"INSERT INTO [products] ([title], [price], [brand]) VALUES ('Super Product', 12, NULL) , ('Super Product', 12, NULL) , ('Super Product', 12, NULL)",
]),
$conn->translate('INSERT INTO [products]', $array, $array, $array) $conn->translate('INSERT INTO [products]', $array, $array, $array)
); );
@@ -49,20 +43,14 @@ $array = [
['pole' => 'hodnota3', 'bit' => 1], ['pole' => 'hodnota3', 'bit' => 1],
]; ];
Assert::same( Assert::same(
reformat([ reformat('INSERT INTO [products] ([pole], [bit]) VALUES (\'hodnota1\', 1) , (\'hodnota2\', 1) , (\'hodnota3\', 1)'),
'sqlsrv' => "INSERT INTO [products] ([pole], [bit]) VALUES (N'hodnota1', 1) , (N'hodnota2', 1) , (N'hodnota3', 1)",
"INSERT INTO [products] ([pole], [bit]) VALUES ('hodnota1', 1) , ('hodnota2', 1) , ('hodnota3', 1)",
]),
$conn->translate('INSERT INTO [products] %ex', $array) $conn->translate('INSERT INTO [products] %ex', $array)
); );
// Dibi detects UPDATE command // Dibi detects UPDATE command
Assert::same( Assert::same(
reformat([ reformat("UPDATE [colors] SET [color]='blue', [order]=12 WHERE [id]=123"),
'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [order]=12 WHERE [id]=123",
"UPDATE [colors] SET [color]='blue', [order]=12 WHERE [id]=123",
]),
$conn->translate('UPDATE [colors] SET', [ $conn->translate('UPDATE [colors] SET', [
'color' => 'blue', 'color' => 'blue',
'order' => 12, 'order' => 12,
@@ -97,26 +85,17 @@ $e = Assert::exception(function () use ($conn) {
Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql()); Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql());
Assert::same( Assert::same(
reformat([ reformat('SELECT * FROM [table] WHERE id=10 AND name=\'ahoj\''),
'sqlsrv' => "SELECT * FROM [table] WHERE id=10 AND name=N'ahoj'",
"SELECT * FROM [table] WHERE id=10 AND name='ahoj'",
]),
$conn->translate('SELECT * FROM [table] WHERE id=%i AND name=%s', 10, 'ahoj') $conn->translate('SELECT * FROM [table] WHERE id=%i AND name=%s', 10, 'ahoj')
); );
Assert::same( Assert::same(
reformat([ reformat('TEST ([cond] > 2) OR ([cond2] = \'3\') OR (cond3 < RAND())'),
'sqlsrv' => "TEST ([cond] > 2) OR ([cond2] = N'3') OR (cond3 < RAND())",
"TEST ([cond] > 2) OR ([cond2] = '3') OR (cond3 < RAND())",
]),
$conn->translate('TEST %or', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()']) $conn->translate('TEST %or', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'])
); );
Assert::same( Assert::same(
reformat([ reformat('TEST ([cond] > 2) AND ([cond2] = \'3\') AND (cond3 < RAND())'),
'sqlsrv' => "TEST ([cond] > 2) AND ([cond2] = N'3') AND (cond3 < RAND())",
"TEST ([cond] > 2) AND ([cond2] = '3') AND (cond3 < RAND())",
]),
$conn->translate('TEST %and', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()']) $conn->translate('TEST %and', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'])
); );
@@ -135,10 +114,7 @@ $where['age'] = null;
$where['email'] = 'ahoj'; $where['email'] = 'ahoj';
$where['id%l'] = [10, 20, 30]; $where['id%l'] = [10, 20, 30];
Assert::same( Assert::same(
reformat([ reformat('SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = \'ahoj\') AND ([id] IN (10, 20, 30))'),
'sqlsrv' => "SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = N'ahoj') AND ([id] IN (10, 20, 30))",
"SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = 'ahoj') AND ([id] IN (10, 20, 30))",
]),
$conn->translate('SELECT * FROM [table] WHERE %and', $where) $conn->translate('SELECT * FROM [table] WHERE %and', $where)
); );
@@ -168,9 +144,9 @@ Assert::same(
// with limit = 2 // with limit = 2
Assert::same( Assert::same(
reformat([ reformat([
'odbc' => 'SELECT TOP 2 * FROM (SELECT * FROM [products]) t', 'odbc' => 'SELECT TOP 2 * FROM (SELECT * FROM [products] ) t',
'sqlsrv' => 'SELECT * FROM [products] OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY', 'sqlsrv' => 'SELECT * FROM [products] OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY',
'SELECT * FROM [products] LIMIT 2', 'SELECT * FROM [products] LIMIT 2',
]), ]),
$conn->translate('SELECT * FROM [products] %lmt', 2) $conn->translate('SELECT * FROM [products] %lmt', 2)
); );
@@ -184,7 +160,7 @@ if ($config['system'] === 'odbc') {
Assert::same( Assert::same(
reformat([ reformat([
'sqlsrv' => 'SELECT * FROM [products] OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY', 'sqlsrv' => 'SELECT * FROM [products] OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY',
'SELECT * FROM [products] LIMIT 2 OFFSET 1', 'SELECT * FROM [products] LIMIT 2 OFFSET 1',
]), ]),
$conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1) $conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1)
); );
@@ -192,10 +168,10 @@ if ($config['system'] === 'odbc') {
// with offset = 50 // with offset = 50
Assert::same( Assert::same(
reformat([ reformat([
'mysql' => 'SELECT * FROM `products` LIMIT 18446744073709551615 OFFSET 50', 'mysql' => 'SELECT * FROM `products` LIMIT 18446744073709551615 OFFSET 50',
'postgre' => 'SELECT * FROM "products" OFFSET 50', 'postgre' => 'SELECT * FROM "products" OFFSET 50',
'sqlsrv' => 'SELECT * FROM [products] OFFSET 50 ROWS', 'sqlsrv' => 'SELECT * FROM [products] OFFSET 50 ROWS',
'SELECT * FROM [products] LIMIT -1 OFFSET 50', 'SELECT * FROM [products] LIMIT -1 OFFSET 50',
]), ]),
$conn->translate('SELECT * FROM [products] %ofs', 50) $conn->translate('SELECT * FROM [products] %ofs', 50)
); );
@@ -261,7 +237,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,17 +255,11 @@ 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'
", ",
'sqlsrv' => "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
CONCAT(last_name, N', ', first_name) AS full_name
GROUP BY [user]
HAVING MAX(salary) > %i 123
INTO OUTFILE N'/tmp/result''.txt'
FIELDS TERMINATED BY N',' OPTIONALLY ENCLOSED BY N'\"'
LINES TERMINATED BY N'\\n'", "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
CONCAT(last_name, ', ', first_name) AS full_name CONCAT(last_name, ', ', first_name) AS full_name
GROUP BY [user] GROUP BY [user]
HAVING MAX(salary) > %i 123 HAVING MAX(salary) > %i 123
@@ -344,34 +314,13 @@ 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
OR `false`= 0 OR `false`= 0
OR `str_null`=NULL OR `str_null`=NULL
OR `str_not_null`='hello' OR `str_not_null`='hello'
LIMIT 10",
'sqlsrv' => "SELECT *
FROM [db].[table]
WHERE ([test].[a] LIKE '1995-03-01'
OR [b1] IN ( 1, 2, 3 )
OR [b2] IN (N'1', N'2', N'3' )
OR [b3] IN ( )
OR [b4] IN ( N'one', N'two', N'three' )
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [b6] IN (N'one', N'two', N'thr.ee')
OR [b7] IN (NULL)
OR [b8] IN (RAND() [col1] > [col2] )
OR [b9] IN (RAND(), [col1] > [col2] )
OR [b10] IN ( )
AND [c] = N'embedded '' string'
OR [d]=10
OR [e]=NULL
OR [true]= 1
OR [false]= 0
OR [str_null]=NULL
OR [str_not_null]=N'hello'
LIMIT 10", LIMIT 10",
'postgre' => 'SELECT * 'postgre' => 'SELECT *
FROM "db"."table" FROM "db"."table"
@@ -437,6 +386,7 @@ 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', '
@@ -462,10 +412,7 @@ LIMIT 10')
Assert::same( Assert::same(
reformat([ reformat('TEST [cond] > 2 [cond2] = \'3\' cond3 < RAND() 123'),
'sqlsrv' => "TEST [cond] > 2 [cond2] = N'3' cond3 < RAND() 123",
"TEST [cond] > 2 [cond2] = '3' cond3 < RAND() 123",
]),
$conn->translate('TEST %ex', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'], 123) $conn->translate('TEST %ex', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'], 123)
); );
@@ -483,19 +430,16 @@ Assert::same(
Assert::same( Assert::same(
reformat([ reformat('TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE \'string\')'),
'sqlsrv' => "TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE N'string')",
"TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE 'string')",
]),
$conn->translate('TEST %or', ['cond1%ex' => 3, 'cond2%ex' => 'RAND()', 'cond3%ex' => ['LIKE %s', 'string']]) $conn->translate('TEST %or', ['cond1%ex' => 3, 'cond2%ex' => 'RAND()', 'cond3%ex' => ['LIKE %s', 'string']])
); );
Assert::same( Assert::same(
reformat([ reformat([
'odbc' => 'SELECT TOP 10 * FROM (SELECT * FROM [test] WHERE [id] LIKE \'%d%t\') t', 'odbc' => 'SELECT TOP 10 * FROM (SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' ) t',
'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE N\'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', 'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10', 'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10',
]), ]),
$conn->translate("SELECT * FROM [test] WHERE %n LIKE '%d%t' %lmt", 'id', 10) $conn->translate("SELECT * FROM [test] WHERE %n LIKE '%d%t' %lmt", 'id', 10)
); );
@@ -511,32 +455,23 @@ Assert::same(
Assert::same( Assert::same(
reformat('SELECT FROM ...'), reformat('SELECT FROM ... '),
$conn->translate('SELECT FROM ... %lmt', null) $conn->translate('SELECT FROM ... %lmt', null)
); );
Assert::same( Assert::same(
reformat([ reformat('SELECT \'%i\''),
'sqlsrv' => "SELECT N'%i'",
"SELECT '%i'",
]),
$conn->translate("SELECT '%i'") $conn->translate("SELECT '%i'")
); );
Assert::same( Assert::same(
reformat([ reformat('SELECT \'%i\''),
'sqlsrv' => "SELECT N'%i'",
"SELECT '%i'",
]),
$conn->translate('SELECT "%i"') $conn->translate('SELECT "%i"')
); );
Assert::same( Assert::same(
reformat([ reformat('INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1(\'Test product\')) , (1, SHA1(\'Test product\'))'),
'sqlsrv' => "INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1(N'Test product')) , (1, SHA1(N'Test product'))",
"INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1('Test product')) , (1, SHA1('Test product'))",
]),
$conn->translate('INSERT INTO [products]', [ $conn->translate('INSERT INTO [products]', [
'product_id' => 1, 'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
@@ -547,10 +482,7 @@ Assert::same(
); );
Assert::same( Assert::same(
reformat([ reformat('UPDATE [products] [product_id]=1, [title]=SHA1(\'Test product\')'),
'sqlsrv' => "UPDATE [products] [product_id]=1, [title]=SHA1(N'Test product')",
"UPDATE [products] [product_id]=1, [title]=SHA1('Test product')",
]),
$conn->translate('UPDATE [products]', [ $conn->translate('UPDATE [products]', [
'product_id' => 1, 'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
@@ -558,10 +490,7 @@ Assert::same(
); );
Assert::same( Assert::same(
reformat([ reformat('UPDATE [products] [product_id]=1, [title]=SHA1(\'Test product\')'),
'sqlsrv' => "UPDATE [products] [product_id]=1, [title]=SHA1(N'Test product')",
"UPDATE [products] [product_id]=1, [title]=SHA1('Test product')",
]),
$conn->translate('UPDATE [products]', [ $conn->translate('UPDATE [products]', [
'product_id' => 1, 'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
@@ -569,10 +498,7 @@ Assert::same(
); );
Assert::same( Assert::same(
reformat([ reformat('SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1(\'Test product\')'),
'sqlsrv' => "SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1(N'Test product')",
"SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1('Test product')",
]),
$conn->translate('SELECT * FROM [products] WHERE', [ $conn->translate('SELECT * FROM [products] WHERE', [
'product_id' => 1, 'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
@@ -609,10 +535,7 @@ $array6 = [
]; ];
Assert::same( Assert::same(
reformat([ reformat('INSERT INTO test ([id], [text], [num]) VALUES (1, \'ahoj\', 1), (2, \'jak\', -1), (3, \'se\', 10), (4, SUM(5), 1)'),
'sqlsrv' => "INSERT INTO test ([id], [text], [num]) VALUES (1, N'ahoj', 1), (2, N'jak', -1), (3, N'se', 10), (4, SUM(5), 1)",
"INSERT INTO test ([id], [text], [num]) VALUES (1, 'ahoj', 1), (2, 'jak', -1), (3, 'se', 10), (4, SUM(5), 1)",
]),
$conn->translate('INSERT INTO test %m', $array6) $conn->translate('INSERT INTO test %m', $array6)
); );
@@ -666,10 +589,8 @@ Assert::same(
setlocale(LC_ALL, 'czech'); setlocale(LC_ALL, 'czech');
Assert::same( Assert::same(
reformat([ reformat("UPDATE [colors] SET [color]='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",
]),
$conn->translate('UPDATE [colors] SET', [ $conn->translate('UPDATE [colors] SET', [
'color' => 'blue', 'color' => 'blue',
'price' => -12.4, 'price' => -12.4,

View File

@@ -1,5 +1,4 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
// The Nette Tester command-line runner can be // The Nette Tester command-line runner can be
@@ -19,15 +18,11 @@ date_default_timezone_set('Europe/Prague');
// load connection // load connection
try { try {
$config = Tester\Environment::loadData(); $config = Tester\Environment::loadData();
} catch (Throwable $e) { } catch (Exception $e) {
$config = parse_ini_file(__DIR__ . '/../databases.ini', true); $config = parse_ini_file(__DIR__ . '/../databases.ini', true);
$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');
@@ -42,7 +37,7 @@ if ($config['system'] === 'odbc') {
} }
function test(string $title, Closure $function): void function test(Closure $function)
{ {
$function(); $function();
} }
@@ -51,7 +46,7 @@ function test(string $title, Closure $function): void
/** Replaces [] with driver-specific quotes */ /** Replaces [] with driver-specific quotes */
function reformat($s) function reformat($s)
{ {
$config = $GLOBALS['config']; global $config;
if (is_array($s)) { if (is_array($s)) {
if (isset($s[$config['system']])) { if (isset($s[$config['system']])) {
return $s[$config['system']]; return $s[$config['system']];
@@ -72,7 +67,7 @@ function reformat($s)
function num($n) function num($n)
{ {
$config = $GLOBALS['config']; global $config;
if (substr($config['dsn'] ?? '', 0, 5) === 'odbc:') { if (substr($config['dsn'] ?? '', 0, 5) === 'odbc:') {
$n = is_float($n) ? "$n.0" : (string) $n; $n = is_float($n) ? "$n.0" : (string) $n;
} }

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 '%a?%PRIMARY'", 1062); }, Dibi\UniqueConstraintViolationException::class, "%a?%Duplicate entry '1' for key '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"%a%violates not-null constraint%A?%', '23502'); }, Dibi\NotNullConstraintViolationException::class, '%a?%null value in column "title" 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_ts.dll extension=php_sqlsrv_71_ts.dll
extension=php_odbc.dll extension=php_odbc.dll