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

Compare commits

..

1 Commits

Author SHA1 Message Date
David Grudl
7a775aad4f fetchSingle returns FALSE 2018-05-17 13:27:45 +02:00
128 changed files with 2108 additions and 3219 deletions

6
.gitattributes vendored
View File

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

View File

@@ -6,12 +6,14 @@ about: "If you would like to support our efforts in maintaining this project
--------------^ Click "Preview" for a nicer view!
> https://nette.org/donate
Help support Dibi!
We develop Dibi for more than 14 years. In order to make your life more comfortable. Dibi cares about the safety of your sites. Dibi saves you time. Dibi earns you money. And is absolutely free.
To ensure future development and improving the documentation, we need your donation.
**[Please make a donation now](https://nette.org/make-donation?to=dibi)**.
[Please make a donation now](https://nette.org/donate).
Thank you!

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: ['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

60
.travis.yml Normal file
View File

@@ -0,0 +1,60 @@
language: php
php:
- 7.1
- 7.2
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:
- stage: Code Standard Checker
php: 7.1
install:
# Install Nette Code Checker
- travis_retry composer create-project nette/code-checker temp/code-checker ~2 --no-progress
# Install Nette Coding Standard
- travis_retry composer create-project nette/coding-standard temp/coding-standard --no-progress
script:
- php temp/code-checker/src/code-checker.php --short-arrays --strict-types
- php temp/coding-standard/ecs check src tests examples --config tests/coding-standard.neon
- 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:
- php: 7.2
- stage: Code Coverage
sudo: false
cache:
directories:
- $HOME/.composer/cache
notifications:
email: false

View File

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

View File

@@ -3,7 +3,7 @@
"description": "Dibi is Database Abstraction Library for PHP",
"keywords": ["database", "dbal", "mysql", "postgresql", "sqlite", "mssql", "sqlsrv", "oracle", "access", "pdo", "odbc"],
"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": [
{
"name": "David Grudl",
@@ -11,13 +11,11 @@
}
],
"require": {
"php": ">=8.0 <8.3"
"php": ">=7.1"
},
"require-dev": {
"tracy/tracy": "^2.8",
"nette/tester": "^2.4",
"nette/di": "^3.0",
"phpstan/phpstan": "^0.12"
"tracy/tracy": "~2.2",
"nette/tester": "~2.0"
},
"replace": {
"dg/dibi": "*"
@@ -25,13 +23,9 @@
"autoload": {
"classmap": ["src/"]
},
"scripts": {
"phpstan": "phpstan analyse",
"tester": "tester tests -s"
},
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
"dev-master": "4.0-dev"
}
}
}

View File

@@ -16,7 +16,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
echo '<p>Connecting to Sqlite: ';
try {
dibi::connect([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);
echo 'OK';
@@ -30,7 +30,7 @@ echo "</p>\n";
echo '<p>Connecting to Sqlite: ';
try {
$connection = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);
echo 'OK';

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -15,7 +15,7 @@ Tracy\Debugger::enable();
<?php
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);
@@ -31,7 +31,7 @@ $name = $cond1 ? 'K%' : null;
$dibi->test('
SELECT *
FROM customers
%if', isset($name), 'WHERE name LIKE ?', $name, '%end',
%if', isset($name), 'WHERE name LIKE ?', $name, '%end'
);
// -> SELECT * FROM customers WHERE name LIKE 'K%'
@@ -54,7 +54,7 @@ $dibi->test('
WHERE
%if', isset($name), 'name LIKE ?', $name, '
%if', $cond2, 'AND admin=1 %end
%else 1 LIMIT 10 %end',
%else 1 LIMIT 10 %end'
);
// -> SELECT * FROM customers WHERE LIMIT 10

View File

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

View File

@@ -19,7 +19,7 @@ date_default_timezone_set('Europe/Prague');
<?php
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -11,7 +11,7 @@ Tracy\Debugger::enable();
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -11,7 +11,7 @@ Tracy\Debugger::enable();
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -16,7 +16,7 @@ date_default_timezone_set('Europe/Prague');
// CHANGE TO REAL PARAMETERS!
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
'formatDate' => "'Y-m-d'",
'formatDateTime' => "'Y-m-d H-i-s'",
@@ -29,6 +29,6 @@ $dibi->test('
'id' => 123,
'date' => new DateTime('12.3.2007'),
'stamp' => new DateTime('23.1.2007 10:23'),
],
]
);
// -> INSERT INTO [mytable] ([id], [date], [stamp]) VALUES (123, '2007-03-12', '2007-01-23 10-23-00')

View File

@@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague');
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

View File

@@ -15,12 +15,11 @@ date_default_timezone_set('Europe/Prague');
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
// enable query logging to this file
'profiler' => [
'file' => 'log/log.sql',
'errorsOnly' => false,
],
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);
@@ -54,6 +54,6 @@ define('SUBST_ACTIVE', 7);
$dibi->test("
UPDATE :account:user
SET name='John Doe', status=:active:
WHERE id=", 7,
WHERE id=", 7
);
// -> UPDATE eshop_user SET name='John Doe', status=7 WHERE id= 7

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite',
'driver' => 'sqlite3',
'database' => 'data/sample.s3db',
]);

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

@@ -1,8 +1,8 @@
[Dibi](https://dibiphp.com) - smart database layer for PHP [![Buy me a coffee](https://files.nette.org/images/coffee1s.png)](https://nette.org/make-donation?to=dibi)
[Dibi](https://dibiphp.com) - smart database layer for PHP [![Buy me a coffee](https://files.nette.org/images/coffee1s.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9XXL5ZJHAYQUN)
=========================================================
[![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)
[![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)
@@ -15,16 +15,6 @@ 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.
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
------------
@@ -34,7 +24,7 @@ Install Dibi via Composer:
composer require dibi/dibi
```
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.2.
The Dibi 4.0 requires PHP version 7.1 and supports PHP up to 7.2. Older Dibi 3.x requires PHP 5.4 and supports PHP up to 7.2.
Usage
@@ -50,11 +40,11 @@ The database connection is represented by the object `Dibi\Connection`:
```php
$database = new Dibi\Connection([
'driver' => 'mysqli',
'host' => 'localhost',
'username' => 'root',
'password' => '***',
'database' => 'table',
'driver' => 'mysqli',
'host' => 'localhost',
'username' => 'root',
'password' => '***',
'database' => 'table',
]);
$result = $database->query('SELECT * FROM users');
@@ -64,12 +54,12 @@ Alternatively, you can use the `dibi` static register, which maintains a connect
```php
dibi::connect([
'driver' => 'mysqli',
'host' => 'localhost',
'username' => 'root',
'password' => '***',
'database' => 'test',
'charset' => 'utf8',
'driver' => 'mysqli',
'host' => 'localhost',
'username' => 'root',
'password' => '***',
'database' => 'test',
'charset' => 'utf8',
]);
$result = dibi::query('SELECT * FROM users');
@@ -83,8 +73,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`.
You can try all the examples [online at the playground](https://repl.it/@DavidGrudl/dibi-playground).
```php
$result = $database->query('SELECT * FROM users');
@@ -120,7 +108,7 @@ $ids = [10, 20, 30];
$result = $database->query('SELECT * FROM users WHERE id IN (?)', $ids);
```
**WARNING: Never concatenate parameters to SQL. It would create a [SQL injection](https://en.wikipedia.org/wiki/SQL_injection)** vulnerability.
**WARNING, never concencate parameters to SQL, the vulnerability would arise [SQL injection](https://en.wikipedia.org/wiki/SQL_injection)**
```
$result = $database->query('SELECT * FROM users WHERE id = ' . $id); // BAD!!!
```
@@ -157,7 +145,7 @@ $name = $database->fetchSingle('SELECT name FROM users WHERE id = ?', $id);
### Modifiers
In addition to the `?` wildcard char, we can also use modifiers:
In addition to the `?` wild char, we can also use modifiers:
| modifier | description
|----------|-----
@@ -171,7 +159,6 @@ In addition to the `?` wildcard char, we can also use modifiers:
| %d | date (accepts DateTime, string or UNIX timestamp)
| %dt | datetime (accepts DateTime, string or UNIX timestamp)
| %n | identifier, ie the name of the table or column
| %N | identifier, treats period as a common character, ie alias or a database name (`%n AS %N` or `DROP DATABASE %N`)
| %SQL | SQL - directly inserts into SQL (the alternative is Dibi\Literal)
| %ex | SQL expression or array of expressions
| %lmt | special - adds LIMIT to the query
@@ -193,7 +180,7 @@ $result = $database->query('SELECT * FROM users WHERE id IN (%i)', $ids);
// 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
$table = 'blog.users';
@@ -209,7 +196,6 @@ Three special modifiers are available for LIKE:
| `%like~` | the expression starts with a string
| `%~like` | the expression ends with a string
| `%~like~` | the expression contains a string
| `%like` | the expression matches a string
Search for names beginning with a string:
@@ -237,8 +223,8 @@ Example:
```php
$arr = [
'a' => 'hello',
'b' => true,
'a' => 'hello',
'b' => true,
];
$database->query('INSERT INTO table %v', $arr);
@@ -510,7 +496,7 @@ $all = $result->fetchAssoc('customer_id|order_id');
// we will iterate like this:
foreach ($all as $customerId => $orders) {
foreach ($orders as $orderId => $order) {
...
...
}
}
```
@@ -542,7 +528,7 @@ $all = $result->fetchAssoc('name[]order_id');
// we get all the Arnolds in the results
foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
foreach ($arnoldOrders as $orderId => $order) {
...
...
}
}
```
@@ -556,8 +542,8 @@ foreach ($all as $customerId => $orders) {
echo "Customer $customerId":
foreach ($orders as $orderId => $order) {
echo "ID number: $order->number";
// customer name is in $order->name
echo "ID number: $order->number";
// customer name is in $order->name
}
}
```
@@ -579,7 +565,7 @@ foreach ($all as $customerId => $row) {
echo "Customer $row->name":
foreach ($row->order_id as $orderId => $order) {
echo "ID number: $order->number";
echo "ID number: $order->number";
}
}
```

View File

@@ -19,15 +19,13 @@ use Tracy;
*/
class DibiExtension22 extends Nette\DI\CompilerExtension
{
private ?bool $debugMode;
private ?bool $cliMode;
/** @var bool|null */
private $debugMode;
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
public function __construct(bool $debugMode = null)
{
$this->debugMode = $debugMode;
$this->cliMode = $cliMode;
}
@@ -40,11 +38,7 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
$this->debugMode = $container->parameters['debugMode'];
}
if ($this->cliMode === null) {
$this->cliMode = $container->parameters['consoleMode'];
}
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode && !$this->cliMode);
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode);
unset($config['profiler']);
@@ -53,24 +47,22 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
foreach ((array) $config['flags'] as $flag) {
$flags |= constant($flag);
}
$config['flags'] = $flags;
}
$connection = $container->addDefinition($this->prefix('connection'))
->setFactory(Dibi\Connection::class, [$config])
->setClass(Dibi\Connection::class, [$config])
->setAutowired($config['autowired'] ?? true);
if (class_exists(Tracy\Debugger::class)) {
$connection->addSetup(
[new Nette\DI\Statement('Tracy\Debugger::getBlueScreen'), 'addPanel'],
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']],
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']]
);
}
if ($useProfiler) {
$panel = $container->addDefinition($this->prefix('panel'))
->setFactory(Dibi\Bridges\Tracy\Panel::class, [
->setClass(Dibi\Bridges\Tracy\Panel::class, [
$config['explain'] ?? true,
isset($config['filter']) && $config['filter'] === false ? Dibi\Event::ALL : Dibi\Event::QUERY,
]);

View File

@@ -22,17 +22,20 @@ class Panel implements Tracy\IBarPanel
{
use Dibi\Strict;
/** maximum SQL length */
public static int $maxLength = 1000;
/** @var int maximum SQL length */
public static $maxLength = 1000;
public bool|string $explain;
/** @var bool|string explain queries? */
public $explain;
public int $filter;
/** @var int */
public $filter;
private array $events = [];
/** @var array */
private $events = [];
public function __construct(bool $explain = true, ?int $filter = null)
public function __construct($explain = true, int $filter = null)
{
$this->filter = $filter ?: Event::QUERY;
$this->explain = $explain;
@@ -42,7 +45,7 @@ class Panel implements Tracy\IBarPanel
public function register(Dibi\Connection $connection): void
{
Tracy\Debugger::getBar()->addPanel($this);
Tracy\Debugger::getBlueScreen()->addPanel([self::class, 'renderException']);
Tracy\Debugger::getBlueScreen()->addPanel([__CLASS__, 'renderException']);
$connection->onEvent[] = [$this, 'logEvent'];
}
@@ -55,7 +58,6 @@ class Panel implements Tracy\IBarPanel
if (($event->type & $this->filter) === 0) {
return;
}
$this->events[] = $event;
}
@@ -86,10 +88,9 @@ class Panel implements Tracy\IBarPanel
foreach ($this->events as $event) {
$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">'
. $count . "\u{a0}queries"
. ($totalTime ? ' / ' . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms" : '')
. $count . ' queries'
. ($totalTime ? ' / ' . number_format($totalTime * 1000, 1, '.', '') . 'ms' : '')
. '</span></span>';
}
@@ -104,15 +105,6 @@ class Panel implements Tracy\IBarPanel
}
$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) {
$totalTime += $event->time;
$connection = $event->connection;
@@ -120,18 +112,15 @@ class Panel implements Tracy\IBarPanel
if ($this->explain && $event->type === Event::SELECT) {
$backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime];
$connection->onEvent = null;
$cmd = is_string($this->explain)
? $this->explain
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
$cmd = is_string($this->explain) ? $this->explain : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
try {
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
} catch (Dibi\Exception $e) {
}
[$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) {
static $counter;
$counter++;
@@ -142,37 +131,24 @@ class Panel implements Tracy\IBarPanel
if ($explain) {
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
}
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>";
if (!$singleConnection) {
$s .= '<td>' . htmlspecialchars($this->getConnectionName($connection)) . '</td></tr>';
}
$s .= "</td><td>{$event->count}</td></tr>";
}
return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important }
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
<h1>Queries:' . "\u{a0}" . count($this->events)
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', '
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
<h1>Queries: ' . count($this->events)
. ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', '') . 'ms') . ', '
. htmlspecialchars($connection->getConfig('driver') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? '@' . $connection->getConfig('host') : '')) . '</h1>
<div class="tracy-inner tracy-DibiProfiler">
<table class="tracy-sortable">
<tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>
' . $s . '
<table>
<tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th></tr>' . $s . '
</table>
</div>';
}
private function getConnectionName(Dibi\Connection $connection): string
{
$driver = $connection->getConfig('driver');
return (is_object($driver) ? $driver::class : $driver)
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
}
}

View File

@@ -22,88 +22,74 @@ class Connection implements IConnection
{
use Strict;
/** function (Event $event); Occurs after query is executed */
public ?array $onEvent = [];
/** @var array of function (Event $event); Occurs after query is executed */
public $onEvent = [];
/** Current connection configuration */
private array $config;
/** @var array Current connection configuration */
private $config;
/** @var string[] resultset formats */
private array $formats;
/** @var Driver|null */
private $driver;
private ?Driver $driver = null;
/** @var Translator|null */
private $translator;
private ?Translator $translator = null;
/** @var array<string, callable(object): Expression> */
private array $translators = [];
private HashMap $substitutes;
private int $transactionDepth = 0;
/** @var HashMap Substitutes for identifiers */
private $substitutes;
/**
* Connection options: (see driver-specific options too)
* - lazy (bool) => if true, connection will be established only when required
* - result (array) => result set options
* - normalize => normalizes result fields (default: true)
* - 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
* - formatDateTime => date-time format (if empty, DateTime objects will be returned)
* - profiler (array)
* - run (bool) => enable profiler?
* - file => file to log
* - errorsOnly (bool) => log only errors
* - substitutes (array) => map of driver specific substitutes (under development)
* - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
* @param array $config connection parameters
* @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, 'password', 'pass');
Helpers::alias($config, 'host', 'hostname');
Helpers::alias($config, 'result|formatDate', 'resultDate');
Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
$config['driver'] ??= 'mysqli';
$config['driver'] = $config['driver'] ?? 'mysqli';
$config['name'] = $name;
$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
if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) {
$filter = $config['profiler']['filter'] ?? Event::QUERY;
$errorsOnly = $config['profiler']['errorsOnly'] ?? false;
$this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter, $errorsOnly), 'logEvent'];
$this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter), 'logEvent'];
}
$this->substitutes = new HashMap(fn(string $expr) => ":$expr:");
$this->substitutes = new HashMap(function (string $expr) { return ":$expr:"; });
if (!empty($config['substitutes'])) {
foreach ($config['substitutes'] as $key => $value) {
$this->substitutes->$key = $value;
}
}
if (isset($config['onConnect']) && !is_array($config['onConnect'])) {
throw new \InvalidArgumentException("Configuration option 'onConnect' must be array.");
}
if (empty($config['lazy'])) {
$this->connect();
}
@@ -126,14 +112,8 @@ class Connection implements IConnection
*/
final public function connect(): void
{
if ($this->config['driver'] instanceof Driver) {
$this->driver = $this->config['driver'];
$this->translator = new Translator($this);
return;
} elseif (is_subclass_of($this->config['driver'], Driver::class)) {
if (is_subclass_of($this->config['driver'], Driver::class)) {
$class = $this->config['driver'];
} else {
$class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($this->config['driver'])));
$class = "Dibi\\Drivers\\{$class}Driver";
@@ -145,22 +125,14 @@ class Connection implements IConnection
$event = $this->onEvent ? new Event($this, Event::CONNECT) : null;
try {
$this->driver = new $class($this->config);
$this->translator = new Translator($this);
if ($event) {
$this->onEvent($event->done());
}
if (isset($this->config['onConnect'])) {
foreach ($this->config['onConnect'] as $sql) {
$this->query($sql);
}
}
} catch (DriverException $e) {
if ($event) {
$this->onEvent($event->done($e));
}
throw $e;
}
}
@@ -173,7 +145,7 @@ class Connection implements IConnection
{
if ($this->driver) {
$this->driver->disconnect();
$this->driver = $this->translator = null;
$this->driver = null;
}
}
@@ -190,8 +162,9 @@ class Connection implements IConnection
/**
* Returns configuration variable. If no $key is passed, returns the entire array.
* @see self::__construct
* @return mixed
*/
final public function getConfig(?string $key = null, $default = null): mixed
final public function getConfig(string $key = null, $default = null)
{
return $key === null
? $this->config
@@ -207,51 +180,48 @@ class Connection implements IConnection
if (!$this->driver) {
$this->connect();
}
return $this->driver;
}
/**
* Generates (translates) and executes SQL query.
* @param mixed ...$args
* @throws Exception
*/
final public function query(mixed ...$args): Result
final public function query(...$args): Result
{
return $this->nativeQuery($this->translate(...$args));
return $this->nativeQuery($this->translateArgs($args));
}
/**
* Generates SQL query.
* @param mixed ...$args
* @throws Exception
*/
final public function translate(mixed ...$args): string
final public function translate(...$args): string
{
if (!$this->driver) {
$this->connect();
}
return (clone $this->translator)->translate($args);
return $this->translateArgs($args);
}
/**
* Generates and prints SQL query.
* @param mixed ...$args
*/
final public function test(mixed ...$args): bool
final public function test(...$args): bool
{
try {
Helpers::dump($this->translate(...$args));
Helpers::dump($this->translateArgs($args));
return true;
} catch (Exception $e) {
if ($e->getSql()) {
Helpers::dump($e->getSql());
} else {
echo $e::class . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
}
return false;
}
}
@@ -259,11 +229,28 @@ class Connection implements IConnection
/**
* Generates (translates) and returns SQL query as DataSource.
* @param mixed ...$args
* @throws Exception
*/
final public function dataSource(mixed ...$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();
}
if (!$this->translator) {
$this->translator = new Translator($this);
}
$translator = clone $this->translator;
return $translator->translate($args);
}
@@ -286,7 +273,6 @@ class Connection implements IConnection
if ($event) {
$this->onEvent($event->done($e));
}
throw $e;
}
@@ -294,7 +280,6 @@ class Connection implements IConnection
if ($event) {
$this->onEvent($event->done($res));
}
return $res;
}
@@ -308,59 +293,70 @@ class Connection implements IConnection
if (!$this->driver) {
$this->connect();
}
$rows = $this->driver->getAffectedRows();
if ($rows === null || $rows < 0) {
throw new Exception('Cannot retrieve number of affected 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.
* @throws Exception
*/
public function getInsertId(?string $sequence = null): int
public function getInsertId(string $sequence = null): int
{
if (!$this->driver) {
$this->connect();
}
$id = $this->driver->getInsertId($sequence);
if ($id === null) {
if ($id < 1) {
throw new Exception('Cannot retrieve last generated 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).
*/
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) {
$this->connect();
}
$event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null;
try {
$this->driver->begin($savepoint);
if ($event) {
$this->onEvent($event->done());
}
} catch (DriverException $e) {
if ($event) {
$this->onEvent($event->done($e));
}
throw $e;
}
}
@@ -369,27 +365,22 @@ class Connection implements IConnection
/**
* 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) {
$this->connect();
}
$event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null;
try {
$this->driver->commit($savepoint);
if ($event) {
$this->onEvent($event->done());
}
} catch (DriverException $e) {
if ($event) {
$this->onEvent($event->done($e));
}
throw $e;
}
}
@@ -398,66 +389,35 @@ class Connection implements IConnection
/**
* 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) {
$this->connect();
}
$event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null;
try {
$this->driver->rollback($savepoint);
if ($event) {
$this->onEvent($event->done());
}
} catch (DriverException $e) {
if ($event) {
$this->onEvent($event->done($e));
}
throw $e;
}
}
public function transaction(callable $callback): mixed
{
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.
*/
public function createResultSet(ResultDriver $resultDriver): Result
{
return (new Result($resultDriver, $this->config['result']['normalize'] ?? true))
->setFormats($this->formats);
$res = new Result($resultDriver);
return $res->setFormat(Type::DATE, $this->config['result']['formatDate'])
->setFormat(Type::DATETIME, $this->config['result']['formatDateTime']);
}
@@ -476,10 +436,7 @@ class Connection implements IConnection
}
/**
* @param string|string[] $table
*/
public function update($table, iterable $args): Fluent
public function update(string $table, iterable $args): Fluent
{
return $this->command()->update('%n', $table)->set($args);
}
@@ -490,7 +447,6 @@ class Connection implements IConnection
if ($args instanceof Traversable) {
$args = iterator_to_array($args);
}
return $this->command()->insert()
->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args);
}
@@ -519,28 +475,9 @@ class Connection implements IConnection
*/
public function substitute(string $value): string
{
return str_contains($value, ':')
? preg_replace_callback('#:([^:\s]*):#', fn(array $m) => $this->substitutes->{$m[1]}, $value)
: $value;
}
/********************* value objects translation ****************d*g**/
/** @param callable(object): Expression $translator */
public function addObjectTranslator(string $class, callable $translator): self
{
$this->translators[$class] = $translator;
uksort($this->translators, fn($a, $b) => class_exists($a, false) && is_subclass_of($a, $b) ? -1 : 1);
return $this;
}
/** @return array<string, callable(object): Expression> */
public function getObjectTranslators(): array
{
return $this->translators;
return strpos($value, ':') === false
? $value
: preg_replace_callback('#:([^:\s]*):#', function (array $m) { return $this->substitutes->{$m[1]}; }, $value);
}
@@ -549,9 +486,10 @@ class Connection implements IConnection
/**
* Executes SQL query and fetch result - shortcut for query() & fetch().
* @param mixed ...$args
* @throws Exception
*/
public function fetch(mixed ...$args): ?Row
public function fetch(...$args): ?Row
{
return $this->query($args)->fetch();
}
@@ -559,10 +497,11 @@ class Connection implements IConnection
/**
* Executes SQL query and fetch results - shortcut for query() & fetchAll().
* @param mixed ...$args
* @return Row[]|array[]
* @throws Exception
*/
public function fetchAll(mixed ...$args): array
public function fetchAll(...$args): array
{
return $this->query($args)->fetchAll();
}
@@ -570,9 +509,11 @@ class Connection implements IConnection
/**
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
* @param mixed ...$args
* @return mixed
* @throws Exception
*/
public function fetchSingle(mixed ...$args): mixed
public function fetchSingle(...$args)
{
return $this->query($args)->fetchSingle();
}
@@ -580,9 +521,10 @@ class Connection implements IConnection
/**
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
* @param mixed ...$args
* @throws Exception
*/
public function fetchPairs(mixed ...$args): array
public function fetchPairs(...$args): array
{
return $this->query($args)->fetchPairs();
}
@@ -608,7 +550,7 @@ class Connection implements IConnection
* @param callable $onProgress function (int $count, ?float $percent): void
* @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);
}
@@ -622,7 +564,6 @@ class Connection implements IConnection
if (!$this->driver) {
$this->connect();
}
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
}
@@ -632,7 +573,7 @@ class Connection implements IConnection
*/
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.');
}
@@ -641,7 +582,7 @@ class Connection implements IConnection
*/
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

@@ -17,25 +17,35 @@ class DataSource implements IDataSource
{
use Strict;
private Connection $connection;
/** @var Connection */
private $connection;
private string $sql;
/** @var string */
private $sql;
private ?Result $result = null;
/** @var Result|null */
private $result;
private ?int $count = null;
/** @var int|null */
private $count;
private ?int $totalCount = null;
/** @var int|null */
private $totalCount;
private array $cols = [];
/** @var array */
private $cols = [];
private array $sorting = [];
/** @var array */
private $sorting = [];
private array $conds = [];
/** @var array */
private $conds = [];
private ?int $offset = null;
/** @var int|null */
private $offset;
private ?int $limit = null;
/** @var int|null */
private $limit;
/**
@@ -43,9 +53,11 @@ class DataSource implements IDataSource
*/
public function __construct(string $sql, Connection $connection)
{
$this->sql = strpbrk($sql, " \t\r\n") === false
? $connection->getDriver()->escapeIdentifier($sql) // table name
: '(' . $sql . ') t'; // SQL command
if (strpbrk($sql, " \t\r\n") === false) {
$this->sql = $connection->getDriver()->escapeIdentifier($sql); // table name
} else {
$this->sql = '(' . $sql . ') t'; // SQL command
}
$this->connection = $connection;
}
@@ -55,14 +67,13 @@ class DataSource implements IDataSource
* @param string|array $col column name or array of column names
* @param string $as column alias
*/
public function select(string|array $col, ?string $as = null): static
public function select($col, string $as = null): self
{
if (is_array($col)) {
$this->cols = $col;
} else {
$this->cols[$col] = $as;
}
$this->result = null;
return $this;
}
@@ -71,11 +82,14 @@ class DataSource implements IDataSource
/**
* Adds conditions to query.
*/
public function where($cond): static
public function where($cond): self
{
$this->conds[] = is_array($cond)
? $cond // TODO: not consistent with select and orderBy
: func_get_args();
if (is_array($cond)) {
// TODO: not consistent with select and orderBy
$this->conds[] = $cond;
} else {
$this->conds[] = func_get_args();
}
$this->result = $this->count = null;
return $this;
}
@@ -85,14 +99,13 @@ class DataSource implements IDataSource
* Selects columns to order by.
* @param string|array $row column name or array of column names
*/
public function orderBy(string|array $row, string $direction = 'ASC'): static
public function orderBy($row, string $direction = 'ASC'): self
{
if (is_array($row)) {
$this->sorting = $row;
} else {
$this->sorting[$row] = $direction;
}
$this->result = null;
return $this;
}
@@ -101,7 +114,7 @@ class DataSource implements IDataSource
/**
* Limits number of rows.
*/
public function applyLimit(int $limit, ?int $offset = null): static
public function applyLimit(int $limit, int $offset = null): self
{
$this->limit = $limit;
$this->offset = $offset;
@@ -127,7 +140,6 @@ class DataSource implements IDataSource
if ($this->result === null) {
$this->result = $this->connection->nativeQuery($this->__toString());
}
return $this->result;
}
@@ -149,9 +161,9 @@ class DataSource implements IDataSource
/**
* Like fetch(), but returns only first field.
* @return mixed value on success, null if no next record
* @return mixed value on success, false if no next record
*/
public function fetchSingle(): mixed
public function fetchSingle()
{
return $this->getResult()->fetchSingle();
}
@@ -178,7 +190,7 @@ class DataSource implements IDataSource
/**
* 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);
}
@@ -219,19 +231,18 @@ class DataSource implements IDataSource
*/
public function __toString(): string
{
return $this->connection->translate(
"\nSELECT %n",
(empty($this->cols) ? '*' : $this->cols),
"\nFROM %SQL",
$this->sql,
"\n%ex",
$this->conds ? ['WHERE %and', $this->conds] : null,
"\n%ex",
$this->sorting ? ['ORDER BY %by', $this->sorting] : null,
"\n%ofs %lmt",
$this->offset,
$this->limit,
);
try {
return $this->connection->translate('
SELECT %n', (empty($this->cols) ? '*' : $this->cols), '
FROM %SQL', $this->sql, '
%ex', $this->conds ? ['WHERE %and', $this->conds] : null, '
%ex', $this->sorting ? ['ORDER BY %by', $this->sorting] : null, '
%ofs %lmt', $this->offset, $this->limit
);
} catch (\Throwable $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
return '';
}
}
@@ -246,11 +257,10 @@ class DataSource implements IDataSource
if ($this->count === null) {
$this->count = $this->conds || $this->offset || $this->limit
? Helpers::intVal($this->connection->nativeQuery(
'SELECT COUNT(*) FROM (' . $this->__toString() . ') t',
'SELECT COUNT(*) FROM (' . $this->__toString() . ') t'
)->fetchSingle())
: $this->getTotalCount();
}
return $this->count;
}
@@ -262,10 +272,9 @@ class DataSource implements IDataSource
{
if ($this->totalCount === null) {
$this->totalCount = Helpers::intVal($this->connection->nativeQuery(
'SELECT COUNT(*) FROM ' . $this->sql,
'SELECT COUNT(*) FROM ' . $this->sql
)->fetchSingle());
}
return $this->totalCount;
}
}

View File

@@ -17,7 +17,10 @@ class DateTime extends \DateTimeImmutable
{
use Strict;
public function __construct(string|int $time = 'now', ?\DateTimeZone $timezone = null)
/**
* @param string|int $time
*/
public function __construct($time = 'now', \DateTimeZone $timezone = null)
{
$timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get());
if (is_numeric($time)) {
@@ -29,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
{
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,220 +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(): mixed
{
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(): mixed
{
return null;
}
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

@@ -33,14 +33,17 @@ class FirebirdDriver implements Dibi\Driver
/** @var resource */
private $connection;
/** @var ?resource */
/** @var resource|null */
private $transaction;
private bool $inTransaction = false;
/** @var bool */
private $inTransaction = false;
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
/**
* @throws Dibi\NotSupportedException
*/
public function __construct(array &$config)
{
if (!extension_loaded('interbase')) {
throw new Dibi\NotSupportedException("PHP extension 'interbase' is not loaded.");
@@ -61,9 +64,11 @@ class FirebirdDriver implements Dibi\Driver
'buffers' => 0,
];
$this->connection = empty($config['persistent'])
? @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 @
if (empty($config['persistent'])) {
$this->connection = @ibase_connect($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)) {
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode());
@@ -87,23 +92,21 @@ class FirebirdDriver implements Dibi\Driver
*/
public function query(string $sql): ?Dibi\ResultDriver
{
$resource = $this->inTransaction
? $this->transaction
: $this->connection;
$resource = $this->inTransaction ? $this->transaction : $this->connection;
$res = ibase_query($resource, $sql);
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);
throw new Dibi\ProcedureException($match[3], $match[1], $match[2], $sql);
} else {
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode(), $sql);
}
} elseif (is_resource($res)) {
return $this->createResultDriver($res);
}
return null;
}
@@ -130,12 +133,11 @@ class FirebirdDriver implements Dibi\Driver
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(?string $savepoint = null): void
public function begin(string $savepoint = null): void
{
if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
}
$this->transaction = ibase_trans($this->getResource());
$this->inTransaction = true;
}
@@ -145,7 +147,7 @@ class FirebirdDriver implements Dibi\Driver
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
public function commit(string $savepoint = null): void
{
if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
@@ -163,7 +165,7 @@ class FirebirdDriver implements Dibi\Driver
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(?string $savepoint = null): void
public function rollback(string $savepoint = null): void
{
if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
@@ -190,7 +192,7 @@ class FirebirdDriver implements Dibi\Driver
* Returns the connection resource.
* @return resource|null
*/
public function getResource(): mixed
public function getResource()
{
return is_resource($this->connection) ? $this->connection : null;
}
@@ -245,31 +247,36 @@ 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'");
}
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) . "'";
}
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 = addcslashes($this->escapeText($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
throw new Dibi\NotImplementedException;
}

View File

@@ -19,7 +19,8 @@ class FirebirdReflector implements Dibi\Reflector
{
use Dibi\Strict;
private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver)
@@ -37,8 +38,8 @@ class FirebirdReflector implements Dibi\Reflector
SELECT TRIM(RDB\$RELATION_NAME),
CASE RDB\$VIEW_BLR WHEN NULL THEN 'TRUE' ELSE 'FALSE' END
FROM RDB\$RELATIONS
WHERE RDB\$SYSTEM_FLAG = 0;
");
WHERE RDB\$SYSTEM_FLAG = 0;"
);
$tables = [];
while ($row = $res->fetch(false)) {
$tables[] = [
@@ -46,7 +47,6 @@ class FirebirdReflector implements Dibi\Reflector
'view' => $row[1] === 'TRUE',
];
}
return $tables;
}
@@ -84,8 +84,9 @@ class FirebirdReflector implements Dibi\Reflector
FROM RDB\$RELATION_FIELDS r
LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
WHERE r.RDB\$RELATION_NAME = '$table'
ORDER BY r.RDB\$FIELD_POSITION;
");
ORDER BY r.RDB\$FIELD_POSITION;"
);
$columns = [];
while ($row = $res->fetch(true)) {
$key = $row['FIELD_NAME'];
@@ -99,7 +100,6 @@ class FirebirdReflector implements Dibi\Reflector
'autoincrement' => false,
];
}
return $columns;
}
@@ -121,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\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
ORDER BY s.RDB\$FIELD_POSITION
");
ORDER BY s.RDB\$FIELD_POSITION"
);
$indexes = [];
while ($row = $res->fetch(true)) {
$key = $row['INDEX_NAME'];
@@ -132,7 +132,6 @@ class FirebirdReflector implements Dibi\Reflector
$indexes[$key]['table'] = $table;
$indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME'];
}
return $indexes;
}
@@ -150,8 +149,8 @@ class FirebirdReflector implements Dibi\Reflector
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY'
ORDER BY s.RDB\$FIELD_POSITION
");
ORDER BY s.RDB\$FIELD_POSITION"
);
$keys = [];
while ($row = $res->fetch(true)) {
$key = $row['INDEX_NAME'];
@@ -161,7 +160,6 @@ class FirebirdReflector implements Dibi\Reflector
'table' => $table,
];
}
return $keys;
}
@@ -176,13 +174,12 @@ class FirebirdReflector implements Dibi\Reflector
FROM RDB\$INDICES
WHERE RDB\$RELATION_NAME = UPPER('$table')
AND RDB\$UNIQUE_FLAG IS NULL
AND RDB\$FOREIGN_KEY IS NULL;
");
AND RDB\$FOREIGN_KEY IS NULL;"
);
$indices = [];
while ($row = $res->fetch(false)) {
$indices[] = $row[0];
}
return $indices;
}
@@ -199,13 +196,12 @@ class FirebirdReflector implements Dibi\Reflector
AND (
RDB\$UNIQUE_FLAG IS NOT NULL
OR RDB\$FOREIGN_KEY IS NOT NULL
);
");
);"
);
$constraints = [];
while ($row = $res->fetch(false)) {
$constraints[] = $row[0];
}
return $constraints;
}
@@ -214,10 +210,9 @@ class FirebirdReflector implements Dibi\Reflector
* Returns metadata for all triggers in a table or database.
* (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,
TRIM(RDB\$RELATION_NAME) AS TABLE_NAME,
CASE RDB\$TRIGGER_TYPE
@@ -241,7 +236,7 @@ class FirebirdReflector implements Dibi\Reflector
END AS TRIGGER_ENABLED
FROM RDB\$TRIGGERS
WHERE RDB\$SYSTEM_FLAG = 0"
. ($table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table');"),
. ($table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table');")
);
$triggers = [];
while ($row = $res->fetch(true)) {
@@ -253,7 +248,6 @@ class FirebirdReflector implements Dibi\Reflector
'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE',
];
}
return $triggers;
}
@@ -262,21 +256,18 @@ class FirebirdReflector implements Dibi\Reflector
* Returns list of triggers for given 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)
FROM RDB$TRIGGERS
WHERE RDB$SYSTEM_FLAG = 0';
$q .= $table === null
? ';'
: " AND RDB\$RELATION_NAME = UPPER('$table')";
$q .= $table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table')";
$res = $this->driver->query($q);
$triggers = [];
while ($row = $res->fetch(false)) {
$triggers[] = $row[0];
}
return $triggers;
}
@@ -316,8 +307,8 @@ class FirebirdReflector implements Dibi\Reflector
p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER
FROM RDB\$PROCEDURE_PARAMETERS p
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 = [];
while ($row = $res->fetch(true)) {
$key = $row['PROCEDURE_NAME'];
@@ -328,7 +319,6 @@ class FirebirdReflector implements Dibi\Reflector
$procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']);
$procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH'];
}
return $procedures;
}
@@ -340,13 +330,12 @@ class FirebirdReflector implements Dibi\Reflector
{
$res = $this->driver->query('
SELECT TRIM(RDB$PROCEDURE_NAME)
FROM RDB$PROCEDURES;
');
FROM RDB$PROCEDURES;'
);
$procedures = [];
while ($row = $res->fetch(false)) {
$procedures[] = $row[0];
}
return $procedures;
}
@@ -359,13 +348,12 @@ class FirebirdReflector implements Dibi\Reflector
$res = $this->driver->query('
SELECT TRIM(RDB$GENERATOR_NAME)
FROM RDB$GENERATORS
WHERE RDB$SYSTEM_FLAG = 0;
');
WHERE RDB$SYSTEM_FLAG = 0;'
);
$generators = [];
while ($row = $res->fetch(false)) {
$generators[] = $row[0];
}
return $generators;
}
@@ -378,13 +366,12 @@ class FirebirdReflector implements Dibi\Reflector
$res = $this->driver->query('
SELECT TRIM(RDB$FUNCTION_NAME)
FROM RDB$FUNCTIONS
WHERE RDB$SYSTEM_FLAG = 0;
');
WHERE RDB$SYSTEM_FLAG = 0;'
);
$functions = [];
while ($row = $res->fetch(false)) {
$functions[] = $row[0];
}
return $functions;
}
}

View File

@@ -23,7 +23,8 @@ class FirebirdResult implements Dibi\ResultDriver
/** @var resource */
private $resultSet;
private bool $autoFree = true;
/** @var bool */
private $autoFree = true;
/**
@@ -61,12 +62,10 @@ class FirebirdResult implements Dibi\ResultDriver
*/
public function fetch(bool $assoc): ?array
{
$result = $assoc
? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT)
: @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
$result = $assoc ? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT) : @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
if (ibase_errcode()) {
if (ibase_errcode() === FirebirdDriver::ERROR_EXCEPTION_THROWN) {
if (ibase_errcode() == FirebirdDriver::ERROR_EXCEPTION_THROWN) {
preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);
@@ -102,7 +101,7 @@ class FirebirdResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|null
*/
public function getResultResource(): mixed
public function getResultResource()
{
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null;
@@ -125,7 +124,6 @@ class FirebirdResult implements Dibi\ResultDriver
'nativetype' => $row['type'],
];
}
return $columns;
}

View File

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

View File

@@ -40,14 +40,17 @@ class MySqliDriver implements Dibi\Driver
public const ERROR_DATA_TRUNCATED = 1265;
private \mysqli $connection;
/** @var \mysqli */
private $connection;
/** Is buffered (seekable and countable)? */
private bool $buffered = false;
/** @var bool Is buffered (seekable and countable)? */
private $buffered;
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
/**
* @throws Dibi\NotSupportedException
*/
public function __construct(array &$config)
{
if (!extension_loaded('mysqli')) {
throw new Dibi\NotSupportedException("PHP extension 'mysqli' is not loaded.");
@@ -71,7 +74,7 @@ class MySqliDriver implements Dibi\Driver
$host = ini_get('mysqli.default_host');
if ($host) {
$config['host'] = $host;
$config['port'] = (int) ini_get('mysqli.default_port');
$config['port'] = ini_get('mysqli.default_port');
} else {
$config['host'] = null;
$config['port'] = null;
@@ -87,15 +90,14 @@ class MySqliDriver implements Dibi\Driver
$this->connection->options($key, $value);
}
}
@$this->connection->real_connect( // intentionally @
(empty($config['persistent']) ? '' : 'p:') . $config['host'],
$config['username'],
$config['password'] ?? '',
$config['password'],
$config['database'] ?? '',
$config['port'] ?? 0,
$config['socket'],
$config['flags'] ?? 0,
$config['flags'] ?? 0
);
if ($this->connection->connect_errno) {
@@ -130,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.
* @throws Dibi\DriverException
@@ -153,12 +146,11 @@ class MySqliDriver implements Dibi\Driver
} elseif ($res instanceof \mysqli_result) {
return $this->createResultDriver($res);
}
return null;
}
public static function createException(string $message, int|string $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)) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
@@ -189,7 +181,6 @@ class MySqliDriver implements Dibi\Driver
foreach ($matches as $m) {
$res[$m[1]] = (int) $m[2];
}
return $res;
}
@@ -199,9 +190,7 @@ class MySqliDriver implements Dibi\Driver
*/
public function getAffectedRows(): ?int
{
return $this->connection->affected_rows === -1
? null
: $this->connection->affected_rows;
return $this->connection->affected_rows === -1 ? null : $this->connection->affected_rows;
}
@@ -210,7 +199,7 @@ class MySqliDriver implements Dibi\Driver
*/
public function getInsertId(?string $sequence): ?int
{
return $this->connection->insert_id ?: null;
return $this->connection->insert_id;
}
@@ -218,7 +207,7 @@ class MySqliDriver implements Dibi\Driver
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(?string $savepoint = null): void
public function begin(string $savepoint = null): void
{
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
}
@@ -228,7 +217,7 @@ class MySqliDriver implements Dibi\Driver
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
public function commit(string $savepoint = null): void
{
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
}
@@ -238,7 +227,7 @@ class MySqliDriver implements Dibi\Driver
* Rollback changes in a transaction.
* @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');
}
@@ -249,11 +238,7 @@ class MySqliDriver implements Dibi\Driver
*/
public function getResource(): ?\mysqli
{
try {
return @$this->connection->thread_id ? $this->connection : null;
} catch (\Throwable $e) {
return null;
}
return @$this->connection->thread_id ? $this->connection : null;
}
@@ -305,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'");
}
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'");
}
public function escapeDateInterval(\DateInterval $value): string
{
if ($value->y || $value->m || $value->d) {
throw new Dibi\NotSupportedException('Only time interval is supported.');
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format("'%r%H:%I:%S.%f'");
return $value->format("'Y-m-d H:i:s.u'");
}
@@ -333,7 +320,7 @@ class MySqliDriver implements Dibi\Driver
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
}
@@ -347,7 +334,7 @@ class MySqliDriver implements Dibi\Driver
} elseif ($limit !== null || $offset) {
// 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 : '');
}
}

View File

@@ -19,12 +19,14 @@ class MySqliResult implements Dibi\ResultDriver
{
use Dibi\Strict;
private \mysqli_result $resultSet;
/** @var \mysqli_result */
private $resultSet;
private bool $autoFree = true;
/** @var bool */
private $autoFree = true;
/** Is buffered (seekable and countable)? */
private bool $buffered;
/** @var bool Is buffered (seekable and countable)? */
private $buffered;
public function __construct(\mysqli_result $resultSet, bool $buffered)
@@ -53,7 +55,6 @@ class MySqliResult implements Dibi\ResultDriver
if (!$this->buffered) {
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
}
return $this->resultSet->num_rows;
}
@@ -79,7 +80,6 @@ class MySqliResult implements Dibi\ResultDriver
if (!$this->buffered) {
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
}
return $this->resultSet->data_seek($row);
}
@@ -107,7 +107,6 @@ class MySqliResult implements Dibi\ResultDriver
$types[$value] = substr($key, 12);
}
}
$types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT';
}
@@ -124,7 +123,6 @@ class MySqliResult implements Dibi\ResultDriver
'vendor' => $row,
];
}
return $columns;
}

View File

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

View File

@@ -21,7 +21,6 @@ use Dibi;
* - password (or pass)
* - persistent (bool) => try to find a persistent link?
* - resource (resource) => existing connection resource
* - microseconds (bool) => use microseconds in datetime format?
*/
class OdbcDriver implements Dibi\Driver
{
@@ -30,13 +29,14 @@ class OdbcDriver implements Dibi\Driver
/** @var resource */
private $connection;
private ?int $affectedRows;
private bool $microseconds = true;
/** @var int|null Affected rows */
private $affectedRows;
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
/**
* @throws Dibi\NotSupportedException
*/
public function __construct(array &$config)
{
if (!extension_loaded('odbc')) {
throw new Dibi\NotSupportedException("PHP extension 'odbc' is not loaded.");
@@ -52,18 +52,16 @@ class OdbcDriver implements Dibi\Driver
'dsn' => ini_get('odbc.default_db'),
];
$this->connection = empty($config['persistent'])
? @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? '') // intentionally @
: @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
if (empty($config['persistent'])) {
$this->connection = @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
} else {
$this->connection = @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
}
}
if (!is_resource($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg() . ' ' . odbc_error());
}
if (isset($config['microseconds'])) {
$this->microseconds = (bool) $config['microseconds'];
}
}
@@ -90,11 +88,8 @@ class OdbcDriver implements Dibi\Driver
} elseif (is_resource($res)) {
$this->affectedRows = Dibi\Helpers::false2Null(odbc_num_rows($res));
return odbc_num_fields($res)
? $this->createResultDriver($res)
: null;
return odbc_num_fields($res) ? $this->createResultDriver($res) : null;
}
return null;
}
@@ -121,9 +116,9 @@ class OdbcDriver implements Dibi\Driver
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(?string $savepoint = null): void
public function begin(string $savepoint = null): void
{
if (!odbc_autocommit($this->connection, false)) {
if (!odbc_autocommit($this->connection, 0/*false*/)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
}
}
@@ -133,13 +128,12 @@ class OdbcDriver implements Dibi\Driver
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
public function commit(string $savepoint = null): void
{
if (!odbc_commit($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
}
odbc_autocommit($this->connection, true);
odbc_autocommit($this->connection, 1/*true*/);
}
@@ -147,13 +141,12 @@ class OdbcDriver implements Dibi\Driver
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(?string $savepoint = null): void
public function rollback(string $savepoint = null): void
{
if (!odbc_rollback($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
}
odbc_autocommit($this->connection, true);
odbc_autocommit($this->connection, 1/*true*/);
}
@@ -170,7 +163,7 @@ class OdbcDriver implements Dibi\Driver
* Returns the connection resource.
* @return resource|null
*/
public function getResource(): mixed
public function getResource()
{
return is_resource($this->connection) ? $this->connection : null;
}
@@ -225,21 +218,27 @@ 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#');
}
public function escapeDateTime(\DateTimeInterface $value): string
/**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{
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;
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format('#m/d/Y H:i:s.u#');
}
@@ -249,7 +248,7 @@ class OdbcDriver implements Dibi\Driver
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
}

View File

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

View File

@@ -22,9 +22,11 @@ class OdbcResult implements Dibi\ResultDriver
/** @var resource */
private $resultSet;
private bool $autoFree = true;
/** @var bool */
private $autoFree = true;
private int $row = 0;
/** @var int Cursor */
private $row = 0;
/**
@@ -70,13 +72,11 @@ class OdbcResult implements Dibi\ResultDriver
if (!odbc_fetch_row($set, ++$this->row)) {
return null;
}
$count = odbc_num_fields($set);
$cols = [];
for ($i = 1; $i <= $count; $i++) {
$cols[] = odbc_result($set, $i);
}
return $cols;
}
}
@@ -116,7 +116,6 @@ class OdbcResult implements Dibi\ResultDriver
'nativetype' => odbc_field_type($this->resultSet, $i),
];
}
return $columns;
}
@@ -125,7 +124,7 @@ class OdbcResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|null
*/
public function getResultResource(): mixed
public function getResultResource()
{
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null;

View File

@@ -32,22 +32,30 @@ class OracleDriver implements Dibi\Driver
/** @var resource */
private $connection;
private bool $autocommit = true;
/** @var bool */
private $autocommit = true;
/** use native datetime format */
private bool $nativeDate;
/** @var bool use native datetime format */
private $nativeDate;
private ?int $affectedRows;
/** @var int|null Number of affected rows */
private $affectedRows;
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
/**
* @throws Dibi\NotSupportedException
*/
public function __construct(array &$config)
{
if (!extension_loaded('oci8')) {
throw new Dibi\NotSupportedException("PHP extension 'oci8' is not loaded.");
}
$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;
if (isset($config['resource'])) {
@@ -94,15 +102,12 @@ class OracleDriver implements Dibi\Driver
} elseif (is_resource($res)) {
$this->affectedRows = Dibi\Helpers::false2Null(oci_num_rows($res));
return oci_num_fields($res)
? $this->createResultDriver($res)
: null;
return oci_num_fields($res) ? $this->createResultDriver($res) : null;
}
} else {
$err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code'], $sql);
}
return null;
}
@@ -146,7 +151,7 @@ class OracleDriver implements Dibi\Driver
/**
* Begins a transaction (if supported).
*/
public function begin(?string $savepoint = null): void
public function begin(string $savepoint = null): void
{
$this->autocommit = false;
}
@@ -156,13 +161,12 @@ class OracleDriver implements Dibi\Driver
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
public function commit(string $savepoint = null): void
{
if (!oci_commit($this->connection)) {
$err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code']);
}
$this->autocommit = true;
}
@@ -171,13 +175,12 @@ class OracleDriver implements Dibi\Driver
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(?string $savepoint = null): void
public function rollback(string $savepoint = null): void
{
if (!oci_rollback($this->connection)) {
$err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code']);
}
$this->autocommit = true;
}
@@ -186,7 +189,7 @@ class OracleDriver implements Dibi\Driver
* Returns the connection resource.
* @return resource|null
*/
public function getResource(): mixed
public function getResource()
{
return is_resource($this->connection) ? $this->connection : null;
}
@@ -242,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
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
: $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
? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')"
: $value->format('U');
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
@@ -271,7 +280,7 @@ class OracleDriver implements Dibi\Driver
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
}

View File

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

View File

@@ -22,7 +22,8 @@ class OracleResult implements Dibi\ResultDriver
/** @var resource */
private $resultSet;
private bool $autoFree = true;
/** @var bool */
private $autoFree = true;
/**
@@ -95,11 +96,9 @@ class OracleResult implements Dibi\ResultDriver
'name' => oci_field_name($this->resultSet, $i),
'table' => null,
'fullname' => oci_field_name($this->resultSet, $i),
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null,
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
];
}
return $columns;
}
@@ -108,7 +107,7 @@ class OracleResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|null
*/
public function getResultResource(): mixed
public function getResultResource()
{
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null;

View File

@@ -29,17 +29,23 @@ class PdoDriver implements Dibi\Driver
{
use Dibi\Strict;
private ?PDO $connection;
/** @var PDO|null Connection resource */
private $connection;
private ?int $affectedRows;
/** @var int|null Affected rows */
private $affectedRows;
private string $driverName;
/** @var string */
private $driverName;
private string $serverVersion = '';
/** @var string */
private $serverVersion = '';
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
/**
* @throws Dibi\NotSupportedException
*/
public function __construct(array &$config)
{
if (!extension_loaded('pdo')) {
throw new Dibi\NotSupportedException("PHP extension 'pdo' is not loaded.");
@@ -52,19 +58,13 @@ class PdoDriver implements Dibi\Driver
if ($config['resource'] instanceof PDO) {
$this->connection = $config['resource'];
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 {
try {
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
} catch (\PDOException $e) {
if ($e->getMessage() === 'could not find driver') {
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
}
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
}
}
@@ -110,7 +110,7 @@ class PdoDriver implements Dibi\Driver
throw PostgreDriver::createException($message, $sqlState, $sql);
case 'sqlite':
throw SqliteDriver::createException($message, $code, $sql);
throw Sqlite3Driver::createException($message, $code, $sql);
default:
throw new Dibi\DriverException($message, $code, $sql);
@@ -132,7 +132,7 @@ class PdoDriver implements Dibi\Driver
*/
public function getInsertId(?string $sequence): ?int
{
return Helpers::intVal($this->connection->lastInsertId($sequence));
return Helpers::false2Null($this->connection->lastInsertId());
}
@@ -140,7 +140,7 @@ class PdoDriver implements Dibi\Driver
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(?string $savepoint = null): void
public function begin(string $savepoint = null): void
{
if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo();
@@ -153,7 +153,7 @@ class PdoDriver implements Dibi\Driver
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
public function commit(string $savepoint = null): void
{
if (!$this->connection->commit()) {
$err = $this->connection->errorInfo();
@@ -166,7 +166,7 @@ class PdoDriver implements Dibi\Driver
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(?string $savepoint = null): void
public function rollback(string $savepoint = null): void
{
if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo();
@@ -230,17 +230,21 @@ class PdoDriver implements Dibi\Driver
*/
public function escapeText(string $value): string
{
return $this->driverName === 'odbc'
? "'" . str_replace("'", "''", $value) . "'"
: $this->connection->quote($value, PDO::PARAM_STR);
if ($this->driverName === 'odbc') {
return "'" . str_replace("'", "''", $value) . "'";
} else {
return $this->connection->quote($value, PDO::PARAM_STR);
}
}
public function escapeBinary(string $value): string
{
return $this->driverName === 'odbc'
? "'" . str_replace("'", "''", $value) . "'"
: $this->connection->quote($value, PDO::PARAM_LOB);
if ($this->driverName === 'odbc') {
return "'" . str_replace("'", "''", $value) . "'";
} else {
return $this->connection->quote($value, PDO::PARAM_LOB);
}
}
@@ -281,14 +285,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'");
}
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) {
case 'odbc':
return $value->format('#m/d/Y H:i:s.u#');
@@ -302,12 +318,6 @@ class PdoDriver implements Dibi\Driver
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
@@ -316,29 +326,29 @@ class PdoDriver implements Dibi\Driver
switch ($this->driverName) {
case 'mysql':
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
case 'oci':
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
case 'pgsql':
$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 = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
case 'sqlite':
$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 'mssql':
case 'dblib':
case 'sqlsrv':
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
default:
throw new Dibi\NotImplementedException;
@@ -359,29 +369,25 @@ class PdoDriver implements Dibi\Driver
case 'mysql':
if ($limit !== null || $offset) {
// 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 : '');
}
break;
case 'pgsql':
if ($limit !== null) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}
break;
case 'sqlite':
if ($limit !== null || $offset) {
$sql .= ' LIMIT ' . ($limit ?? '-1')
$sql .= ' LIMIT ' . ($limit === null ? '-1' : $limit)
. ($offset ? ' OFFSET ' . $offset : '');
}
break;
case 'oci':
@@ -394,7 +400,6 @@ class PdoDriver implements Dibi\Driver
} elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
}
break;
case 'mssql':
@@ -407,10 +412,10 @@ class PdoDriver implements Dibi\Driver
} elseif ($offset) {
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
}
break;
}
// break omitted
case 'odbc':
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
@@ -420,6 +425,7 @@ class PdoDriver implements Dibi\Driver
break;
}
// break omitted
default:
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
}

View File

@@ -21,9 +21,11 @@ class PdoResult implements Dibi\ResultDriver
{
use Dibi\Strict;
private ?\PDOStatement $resultSet;
/** @var \PDOStatement|null */
private $resultSet;
private string $driverName;
/** @var string */
private $driverName;
public function __construct(\PDOStatement $resultSet, string $driverName)
@@ -83,8 +85,7 @@ class PdoResult implements Dibi\ResultDriver
if ($row === false) {
throw new Dibi\NotSupportedException('Driver does not support meta data.');
}
$row += [
$row = $row + [
'table' => null,
'native_type' => 'VAR_STRING',
];
@@ -98,7 +99,6 @@ class PdoResult implements Dibi\ResultDriver
'vendor' => $row,
];
}
return $columns;
}

View File

@@ -11,7 +11,6 @@ namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
use PgSql;
/**
@@ -24,20 +23,22 @@ use PgSql;
* - charset => character encoding to set (default is utf8)
* - persistent (bool) => try to find a persistent link?
* - resource (resource) => existing connection resource
* - connect_type (int) => see pg_connect()
*/
class PostgreDriver implements Dibi\Driver
{
use Dibi\Strict;
/** @var resource|PgSql\Connection */
/** @var resource */
private $connection;
private ?int $affectedRows;
/** @var int|null Affected rows */
private $affectedRows;
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
/**
* @throws Dibi\NotSupportedException
*/
public function __construct(array &$config)
{
if (!extension_loaded('pgsql')) {
throw new Dibi\NotSupportedException("PHP extension 'pgsql' is not loaded.");
@@ -64,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) {
$error = $message;
});
$this->connection = empty($config['persistent'])
? pg_connect($string, $connectType)
: pg_pconnect($string, $connectType);
if (empty($config['persistent'])) {
$this->connection = pg_connect($string, PGSQL_CONNECT_FORCE_NEW);
} else {
$this->connection = pg_pconnect($string, PGSQL_CONNECT_FORCE_NEW);
}
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.');
}
@@ -121,25 +122,24 @@ class PostgreDriver implements Dibi\Driver
if ($res === false) {
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));
if (pg_num_fields($res)) {
return $this->createResultDriver($res);
}
}
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)) {
$code = $m[1];
$message = substr($message, strlen($m[0]));
}
if ($code === '0A000' && str_contains($message, 'truncate')) {
if ($code === '0A000' && strpos($message, 'truncate') !== false) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} elseif ($code === '23502') {
@@ -171,9 +171,12 @@ class PostgreDriver implements Dibi\Driver
*/
public function getInsertId(?string $sequence): ?int
{
$res = $sequence === null
? $this->query('SELECT LASTVAL()') // PostgreSQL 8.1 is needed
: $this->query("SELECT CURRVAL('$sequence')");
if ($sequence === null) {
// PostgreSQL 8.1 is needed
$res = $this->query('SELECT LASTVAL()');
} else {
$res = $this->query("SELECT CURRVAL('$sequence')");
}
if (!$res) {
return null;
@@ -188,9 +191,9 @@ class PostgreDriver implements Dibi\Driver
* Begins a transaction (if supported).
* @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');
}
@@ -198,9 +201,9 @@ class PostgreDriver implements Dibi\Driver
* Commits statements in a transaction.
* @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');
}
@@ -208,9 +211,9 @@ class PostgreDriver implements Dibi\Driver
* Rollback changes in a transaction.
* @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');
}
@@ -227,11 +230,9 @@ class PostgreDriver implements Dibi\Driver
* Returns the connection resource.
* @return resource|null
*/
public function getResource(): mixed
public function getResource()
{
return is_resource($this->connection) || $this->connection instanceof PgSql\Connection
? $this->connection
: null;
return is_resource($this->connection) ? $this->connection : null;
}
@@ -262,20 +263,18 @@ class PostgreDriver implements Dibi\Driver
*/
public function escapeText(string $value): string
{
if (!$this->getResource()) {
if (!is_resource($this->connection)) {
throw new Dibi\Exception('Lost connection to server.');
}
return "'" . pg_escape_string($this->connection, $value) . "'";
}
public function escapeBinary(string $value): string
{
if (!$this->getResource()) {
if (!is_resource($this->connection)) {
throw new Dibi\Exception('Lost connection to server.');
}
return "'" . pg_escape_bytea($this->connection, $value) . "'";
}
@@ -293,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'");
}
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'");
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
@@ -319,7 +324,7 @@ class PostgreDriver implements Dibi\Driver
$bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
$value = pg_escape_string($this->connection, $value);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
}
@@ -331,11 +336,9 @@ class PostgreDriver implements Dibi\Driver
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
}
if ($limit !== null) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}

View File

@@ -19,13 +19,18 @@ class PostgreReflector implements Dibi\Reflector
{
use Dibi\Strict;
private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
private string $version;
/** @var string */
private $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->version = $version;
}
@@ -64,7 +69,6 @@ class PostgreReflector implements Dibi\Reflector
while ($row = $res->fetch(true)) {
$tables[] = $row;
}
return $tables;
}
@@ -101,7 +105,7 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable,
a.attnum AS ordinal_position,
pg_get_expr(adef.adbin, adef.adrelid) AS column_default
adef.adsrc AS column_default
FROM
pg_attribute a
JOIN pg_type ON a.atttypid = pg_type.oid
@@ -126,11 +130,10 @@ class PostgreReflector implements Dibi\Reflector
'size' => $size > 0 ? $size : null,
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
'default' => $row['column_default'],
'autoincrement' => (int) $row['ordinal_position'] === $primary && str_starts_with($row['column_default'] ?? '', 'nextval'),
'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'], 0, 7) === 'nextval',
'vendor' => $row,
];
}
return $columns;
}
@@ -173,14 +176,10 @@ class PostgreReflector implements Dibi\Reflector
$indexes[$row['relname']]['name'] = $row['relname'];
$indexes[$row['relname']]['unique'] = $row['indisunique'] === 't' || $row['indisunique'] === true;
$indexes[$row['relname']]['primary'] = $row['indisprimary'] === 't' || $row['indisprimary'] === true;
$indexes[$row['relname']]['columns'] = [];
foreach (explode(' ', $row['indkey']) as $index) {
if (isset($columns[$index])) {
$indexes[$row['relname']]['columns'][] = $columns[$index];
}
$indexes[$row['relname']]['columns'][] = $columns[$index];
}
}
return array_values($indexes);
}
@@ -249,10 +248,7 @@ class PostgreReflector implements Dibi\Reflector
$references[$row['name']] = array_combine($l, $f);
}
if (
isset($references[$row['name']][$row['lnum']])
&& $references[$row['name']][$row['lnum']] === $row['fnum']
) {
if (isset($references[$row['name']][$row['lnum']]) && $references[$row['name']][$row['lnum']] === $row['fnum']) {
$fKeys[$row['name']]['local'][] = $row['local'];
$fKeys[$row['name']]['foreign'][] = $row['foreign'];
}

View File

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

View File

@@ -9,10 +9,293 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
use SQLite3;
/**
* Alias for SqliteDriver driver.
* The driver for SQLite3 database.
*
* Driver options:
* - database (or file) => the filename of the SQLite3 database
* - formatDate => how to format date in SQL (@see date)
* - formatDateTime => how to format datetime in SQL (@see date)
* - resource (SQLite3) => existing connection resource
*/
class Sqlite3Driver extends SqliteDriver
class Sqlite3Driver implements Dibi\Driver
{
use Dibi\Strict;
/** @var SQLite3 */
private $connection;
/** @var string Date format */
private $fmtDate;
/** @var string Datetime format */
private $fmtDateTime;
/**
* @throws Dibi\NotSupportedException
*/
public function __construct(array &$config)
{
if (!extension_loaded('sqlite3')) {
throw new Dibi\NotSupportedException("PHP extension 'sqlite3' is not loaded.");
}
if (isset($config['dbcharset']) || isset($config['charset'])) {
throw new Dibi\NotSupportedException('Options dbcharset and charset are not longer supported.');
}
Helpers::alias($config, 'database', 'file');
$this->fmtDate = $config['formatDate'] ?? 'U';
$this->fmtDateTime = $config['formatDateTime'] ?? 'U';
if (isset($config['resource']) && $config['resource'] instanceof SQLite3) {
$this->connection = $config['resource'];
} else {
try {
$this->connection = new SQLite3($config['database']);
} catch (\Exception $e) {
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
}
}
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
$version = SQLite3::version();
if ($version['versionNumber'] >= '3006019') {
$this->query('PRAGMA foreign_keys = ON');
}
}
/**
* Disconnects from a database.
*/
public function disconnect(): void
{
$this->connection->close();
}
/**
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
{
$res = @$this->connection->query($sql); // intentionally @
if ($code = $this->connection->lastErrorCode()) {
throw static::createException($this->connection->lastErrorMsg(), $code, $sql);
} elseif ($res instanceof \SQLite3Result && $res->numColumns()) {
return $this->createResultDriver($res);
}
return null;
}
public static function createException(string $message, $code, string $sql): Dibi\DriverException
{
if ($code !== 19) {
return new Dibi\DriverException($message, $code, $sql);
} elseif (strpos($message, 'must be unique') !== false
|| strpos($message, 'is not unique') !== false
|| strpos($message, 'UNIQUE constraint failed') !== false
) {
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
} elseif (strpos($message, 'may not be null') !== false
|| strpos($message, 'NOT NULL constraint failed') !== false
) {
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
} elseif (strpos($message, 'foreign key constraint failed') !== false
|| strpos($message, 'FOREIGN KEY constraint failed') !== false
) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} else {
return new Dibi\ConstraintViolationException($message, $code, $sql);
}
}
/**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
*/
public function getAffectedRows(): ?int
{
return $this->connection->changes();
}
/**
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
*/
public function getInsertId(?string $sequence): ?int
{
return $this->connection->lastInsertRowID();
}
/**
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(string $savepoint = null): void
{
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
}
/**
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(string $savepoint = null): void
{
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
}
/**
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(string $savepoint = null): void
{
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
}
/**
* Returns the connection resource.
*/
public function getResource(): ?SQLite3
{
return $this->connection;
}
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
{
return new SqliteReflector($this);
}
/**
* Result set driver factory.
*/
public function createResultDriver(\SQLite3Result $result): Sqlite3Result
{
return new Sqlite3Result($result);
}
/********************* SQL ****************d*g**/
/**
* Encodes data for use in a SQL statement.
*/
public function escapeText(string $value): string
{
return "'" . $this->connection->escapeString($value) . "'";
}
public function escapeBinary(string $value): string
{
return "X'" . bin2hex($value) . "'";
}
public function escapeIdentifier(string $value): string
{
return '[' . strtr($value, '[]', ' ') . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
/**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDate($value): string
{
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format($this->fmtDate);
}
/**
* @param \DateTimeInterface|string|int $value
*/
public function escapeDateTime($value): string
{
if (!$value instanceof \DateTimeInterface) {
$value = new Dibi\DateTime($value);
}
return $value->format($this->fmtDateTime);
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes($this->connection->escapeString($value), '%_\\');
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
}
/**
* 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 === null ? '-1' : $limit)
. ($offset ? ' OFFSET ' . $offset : '');
}
}
/********************* user defined functions ****************d*g**/
/**
* Registers an user defined function for use in SQL statements.
*/
public function registerFunction(string $name, callable $callback, int $numArgs = -1): void
{
$this->connection->createFunction($name, $callback, $numArgs);
}
/**
* Registers an aggregating user defined function for use in SQL statements.
*/
public function registerAggregateFunction(string $name, callable $rowCallback, callable $agrCallback, int $numArgs = -1): void
{
$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
}
}

View File

@@ -9,10 +9,115 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
/**
* Alias for SqliteResult driver.
* The driver for SQLite3 result set.
*/
class Sqlite3Result extends SqliteResult
class Sqlite3Result implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var \SQLite3Result */
private $resultSet;
/** @var bool */
private $autoFree = true;
public function __construct(\SQLite3Result $resultSet)
{
$this->resultSet = $resultSet;
}
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
@$this->free();
}
}
/**
* Returns the number of rows in a result set.
* @throws Dibi\NotSupportedException
*/
public function getRowCount(): int
{
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
}
/**
* Fetches the row at current position and moves the internal cursor to the next position.
* @param bool $assoc true for associative array, false for numeric
*/
public function fetch(bool $assoc): ?array
{
return Helpers::false2Null($this->resultSet->fetchArray($assoc ? SQLITE3_ASSOC : SQLITE3_NUM));
}
/**
* Moves cursor position without fetching row.
* @throws Dibi\NotSupportedException
*/
public function seek(int $row): bool
{
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
}
/**
* Frees the resources allocated for this result set.
*/
public function free(): void
{
$this->resultSet->finalize();
}
/**
* Returns metadata for all columns in a result set.
*/
public function getResultColumns(): array
{
$count = $this->resultSet->numColumns();
$columns = [];
static $types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null'];
for ($i = 0; $i < $count; $i++) {
$columns[] = [
'name' => $this->resultSet->columnName($i),
'table' => null,
'fullname' => $this->resultSet->columnName($i),
'nativetype' => $types[$this->resultSet->columnType($i)],
];
}
return $columns;
}
/**
* Returns the result set resource.
*/
public function getResultResource(): \SQLite3Result
{
$this->autoFree = false;
return $this->resultSet;
}
/**
* Decodes data from result set.
*/
public function unescapeBinary(string $value): string
{
return $value;
}
}

View File

@@ -1,296 +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;
use Dibi\Helpers;
use SQLite3;
/**
* The driver for SQLite v3 database.
*
* Driver options:
* - database (or file) => the filename of the SQLite3 database
* - formatDate => how to format date in SQL (@see date)
* - formatDateTime => how to format datetime in SQL (@see date)
* - resource (SQLite3) => existing connection resource
*/
class SqliteDriver implements Dibi\Driver
{
use Dibi\Strict;
private SQLite3 $connection;
private string $fmtDate;
private string $fmtDateTime;
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
{
if (!extension_loaded('sqlite3')) {
throw new Dibi\NotSupportedException("PHP extension 'sqlite3' is not loaded.");
}
if (isset($config['dbcharset']) || isset($config['charset'])) {
throw new Dibi\NotSupportedException('Options dbcharset and charset are not longer supported.');
}
Helpers::alias($config, 'database', 'file');
$this->fmtDate = $config['formatDate'] ?? 'U';
$this->fmtDateTime = $config['formatDateTime'] ?? 'U';
if (isset($config['resource']) && $config['resource'] instanceof SQLite3) {
$this->connection = $config['resource'];
} else {
try {
$this->connection = new SQLite3($config['database']);
} catch (\Throwable $e) {
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
}
}
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
$version = SQLite3::version();
if ($version['versionNumber'] >= '3006019') {
$this->query('PRAGMA foreign_keys = ON');
}
}
/**
* Disconnects from a database.
*/
public function disconnect(): void
{
$this->connection->close();
}
/**
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
{
$res = @$this->connection->query($sql); // intentionally @
if ($code = $this->connection->lastErrorCode()) {
throw static::createException($this->connection->lastErrorMsg(), $code, $sql);
} elseif ($res instanceof \SQLite3Result && $res->numColumns()) {
return $this->createResultDriver($res);
}
return null;
}
public static function createException(string $message, $code, string $sql): Dibi\DriverException
{
if ($code !== 19) {
return new Dibi\DriverException($message, $code, $sql);
} elseif (str_contains($message, 'must be unique')
|| str_contains($message, 'is not unique')
|| str_contains($message, 'UNIQUE constraint failed')
) {
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
} elseif (str_contains($message, 'may not be null')
|| str_contains($message, 'NOT NULL constraint failed')
) {
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
} elseif (str_contains($message, 'foreign key constraint failed')
|| str_contains($message, 'FOREIGN KEY constraint failed')
) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} else {
return new Dibi\ConstraintViolationException($message, $code, $sql);
}
}
/**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
*/
public function getAffectedRows(): ?int
{
return $this->connection->changes();
}
/**
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
*/
public function getInsertId(?string $sequence): ?int
{
return $this->connection->lastInsertRowID() ?: null;
}
/**
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(?string $savepoint = null): void
{
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
}
/**
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
{
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
}
/**
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(?string $savepoint = null): void
{
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
}
/**
* Returns the connection resource.
*/
public function getResource(): ?SQLite3
{
return $this->connection;
}
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
{
return new SqliteReflector($this);
}
/**
* Result set driver factory.
*/
public function createResultDriver(\SQLite3Result $result): SqliteResult
{
return new SqliteResult($result);
}
/********************* SQL ****************d*g**/
/**
* Encodes data for use in a SQL statement.
*/
public function escapeText(string $value): string
{
return "'" . $this->connection->escapeString($value) . "'";
}
public function escapeBinary(string $value): string
{
return "X'" . bin2hex($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($this->fmtDate);
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format($this->fmtDateTime);
}
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 = addcslashes($this->connection->escapeString($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
}
/**
* 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 : '');
}
}
/********************* user defined functions ****************d*g**/
/**
* Registers an user defined function for use in SQL statements.
*/
public function registerFunction(string $name, callable $callback, int $numArgs = -1): void
{
$this->connection->createFunction($name, $callback, $numArgs);
}
/**
* Registers an aggregating user defined function for use in SQL statements.
*/
public function registerAggregateFunction(
string $name,
callable $rowCallback,
callable $agrCallback,
int $numArgs = -1,
): void
{
$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
}
}

View File

@@ -19,7 +19,8 @@ class SqliteReflector implements Dibi\Reflector
{
use Dibi\Strict;
private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver)
@@ -43,7 +44,6 @@ class SqliteReflector implements Dibi\Reflector
while ($row = $res->fetch(true)) {
$tables[] = $row;
}
return $tables;
}
@@ -64,13 +64,12 @@ class SqliteReflector implements Dibi\Reflector
'fullname' => "$table.$column",
'nativetype' => strtoupper($type[0]),
'size' => isset($type[1]) ? (int) $type[1] : null,
'nullable' => $row['notnull'] === 0,
'nullable' => $row['notnull'] == '0',
'default' => $row['dflt_value'],
'autoincrement' => $row['pk'] && $type[0] === 'INTEGER',
'vendor' => $row,
];
}
return $columns;
}
@@ -99,15 +98,13 @@ class SqliteReflector implements Dibi\Reflector
$column = $indexes[$index]['columns'][0];
$primary = false;
foreach ($columns as $info) {
if ($column === $info['name']) {
if ($column == $info['name']) {
$primary = $info['vendor']['pk'];
break;
}
}
$indexes[$index]['primary'] = (bool) $primary;
}
if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid
foreach ($columns as $column) {
if ($column['vendor']['pk']) {
@@ -145,7 +142,6 @@ class SqliteReflector implements Dibi\Reflector
$keys[$row['id']]['foreign'] = null;
}
}
return array_values($keys);
}
}

View File

@@ -1,122 +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;
use Dibi\Helpers;
/**
* The driver for SQLite result set.
*/
class SqliteResult implements Dibi\ResultDriver
{
use Dibi\Strict;
private \SQLite3Result $resultSet;
private bool $autoFree = true;
public function __construct(\SQLite3Result $resultSet)
{
$this->resultSet = $resultSet;
}
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
@$this->free();
}
}
/**
* Returns the number of rows in a result set.
* @throws Dibi\NotSupportedException
*/
public function getRowCount(): int
{
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
}
/**
* Fetches the row at current position and moves the internal cursor to the next position.
* @param bool $assoc true for associative array, false for numeric
*/
public function fetch(bool $assoc): ?array
{
return Helpers::false2Null($this->resultSet->fetchArray($assoc ? SQLITE3_ASSOC : SQLITE3_NUM));
}
/**
* Moves cursor position without fetching row.
* @throws Dibi\NotSupportedException
*/
public function seek(int $row): bool
{
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
}
/**
* Frees the resources allocated for this result set.
*/
public function free(): void
{
$this->resultSet->finalize();
}
/**
* Returns metadata for all columns in a result set.
*/
public function getResultColumns(): array
{
$count = $this->resultSet->numColumns();
$columns = [];
$types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null'];
for ($i = 0; $i < $count; $i++) {
$columns[] = [
'name' => $this->resultSet->columnName($i),
'table' => null,
'fullname' => $this->resultSet->columnName($i),
'nativetype' => $types[$this->resultSet->columnType($i)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414
];
}
return $columns;
}
/**
* Returns the result set resource.
*/
public function getResultResource(): \SQLite3Result
{
$this->autoFree = false;
return $this->resultSet;
}
/**
* Decodes data from result set.
*/
public function unescapeBinary(string $value): string
{
return $value;
}
}

View File

@@ -32,13 +32,17 @@ class SqlsrvDriver implements Dibi\Driver
/** @var resource */
private $connection;
private ?int $affectedRows;
/** @var int|null Affected rows */
private $affectedRows;
private string $version = '';
/** @var string */
private $version = '';
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
/**
* @throws Dibi\NotSupportedException
*/
public function __construct(array &$config)
{
if (!extension_loaded('sqlsrv')) {
throw new Dibi\NotSupportedException("PHP extension 'sqlsrv' is not loaded.");
@@ -51,28 +55,23 @@ class SqlsrvDriver implements Dibi\Driver
if (isset($config['resource'])) {
$this->connection = $config['resource'];
if (!is_resource($this->connection)) {
throw new \InvalidArgumentException("Configuration option 'resource' is not resource.");
}
} else {
$options = $config['options'];
// Default values
$options['CharacterSet'] ??= 'UTF-8';
$options['CharacterSet'] = $options['CharacterSet'] ?? 'UTF-8';
$options['PWD'] = (string) $options['PWD'];
$options['UID'] = (string) $options['UID'];
$options['Database'] = (string) $options['Database'];
sqlsrv_configure('WarningsReturnAsErrors', 0);
$this->connection = sqlsrv_connect($config['host'], $options);
if (!is_resource($this->connection)) {
$info = sqlsrv_errors(SQLSRV_ERR_ERRORS);
throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
}
sqlsrv_configure('WarningsReturnAsErrors', 1);
}
if (!is_resource($this->connection)) {
$info = sqlsrv_errors();
throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
}
$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
}
@@ -101,11 +100,8 @@ class SqlsrvDriver implements Dibi\Driver
} elseif (is_resource($res)) {
$this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
return sqlsrv_num_fields($res)
? $this->createResultDriver($res)
: null;
return sqlsrv_num_fields($res) ? $this->createResultDriver($res) : null;
}
return null;
}
@@ -129,7 +125,6 @@ class SqlsrvDriver implements Dibi\Driver
$row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
return Dibi\Helpers::intVal($row[0]);
}
return null;
}
@@ -138,7 +133,7 @@ class SqlsrvDriver implements Dibi\Driver
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(?string $savepoint = null): void
public function begin(string $savepoint = null): void
{
sqlsrv_begin_transaction($this->connection);
}
@@ -148,7 +143,7 @@ class SqlsrvDriver implements Dibi\Driver
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
public function commit(string $savepoint = null): void
{
sqlsrv_commit($this->connection);
}
@@ -158,7 +153,7 @@ class SqlsrvDriver implements Dibi\Driver
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(?string $savepoint = null): void
public function rollback(string $savepoint = null): void
{
sqlsrv_rollback($this->connection);
}
@@ -168,7 +163,7 @@ class SqlsrvDriver implements Dibi\Driver
* Returns the connection resource.
* @return resource|null
*/
public function getResource(): mixed
public function getResource()
{
return is_resource($this->connection) ? $this->connection : null;
}
@@ -201,13 +196,13 @@ class SqlsrvDriver implements Dibi\Driver
*/
public function escapeText(string $value): string
{
return "N'" . str_replace("'", "''", $value) . "'";
return "'" . str_replace("'", "''", $value) . "'";
}
public function escapeBinary(string $value): string
{
return '0x' . bin2hex($value);
return "'" . str_replace("'", "''", $value) . "'";
}
@@ -224,31 +219,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'");
}
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'") . ')';
}
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 ? "%'" : "'");
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
}
@@ -267,6 +268,7 @@ class SqlsrvDriver implements Dibi\Driver
} elseif ($limit !== null) {
$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
}
} elseif ($limit !== null) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);

View File

@@ -19,7 +19,8 @@ class SqlsrvReflector implements Dibi\Reflector
{
use Dibi\Strict;
private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver)
@@ -41,7 +42,6 @@ class SqlsrvReflector implements Dibi\Reflector
'view' => isset($row[1]) && $row[1] === 'VIEW',
];
}
return $tables;
}
@@ -91,7 +91,6 @@ class SqlsrvReflector implements Dibi\Reflector
'vendor' => $row,
];
}
return $columns;
}
@@ -101,7 +100,7 @@ class SqlsrvReflector implements Dibi\Reflector
*/
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 = [];
while ($row = $keyUsagesRes->fetch(true)) {
$keyUsages[$row['index_name']] = explode(',', $row['index_keys']);
@@ -115,7 +114,6 @@ class SqlsrvReflector implements Dibi\Reflector
$indexes[$row['name']]['primary'] = $row['is_primary_key'] === 1;
$indexes[$row['name']]['columns'] = $keyUsages[$row['name']] ?? [];
}
return array_values($indexes);
}

View File

@@ -22,7 +22,8 @@ class SqlsrvResult implements Dibi\ResultDriver
/** @var resource */
private $resultSet;
private bool $autoFree = true;
/** @var bool */
private $autoFree = true;
/**
@@ -60,7 +61,7 @@ class SqlsrvResult implements Dibi\ResultDriver
*/
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);
}
@@ -95,7 +96,6 @@ class SqlsrvResult implements Dibi\ResultDriver
'nativetype' => $fieldMetadata['Type'],
];
}
return $columns;
}
@@ -104,7 +104,7 @@ class SqlsrvResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|null
*/
public function getResultResource(): mixed
public function getResultResource()
{
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null;

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ namespace Dibi;
*
* @method Fluent select(...$field)
* @method Fluent distinct()
* @method Fluent from($table, ...$args = null)
* @method Fluent from($table, ...$args)
* @method Fluent where(...$cond)
* @method Fluent groupBy(...$field)
* @method Fluent having(...$cond)
@@ -29,17 +29,7 @@ namespace Dibi;
* @method Fluent outerJoin(...$table)
* @method Fluent as(...$field)
* @method Fluent on(...$cond)
* @method Fluent and(...$cond)
* @method Fluent or(...$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 desc()
*/
class Fluent implements IDataSource
{
@@ -47,7 +37,8 @@ class Fluent implements IDataSource
public const REMOVE = false;
public static array $masks = [
/** @var array */
public static $masks = [
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', ],
'UPDATE' => ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'],
@@ -55,8 +46,8 @@ class Fluent implements IDataSource
'DELETE' => ['DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'],
];
/** default modifiers for arrays */
public static array $modifiers = [
/** @var array default modifiers for arrays */
public static $modifiers = [
'SELECT' => '%n',
'FROM' => '%n',
'IN' => '%in',
@@ -68,8 +59,8 @@ class Fluent implements IDataSource
'GROUP BY' => '%by',
];
/** clauses separators */
public static array $separators = [
/** @var array clauses separators */
public static $separators = [
'SELECT' => ',',
'FROM' => ',',
'WHERE' => 'AND',
@@ -83,36 +74,42 @@ class Fluent implements IDataSource
'INTO' => false,
];
/** clauses */
public static array $clauseSwitches = [
/** @var array clauses */
public static $clauseSwitches = [
'JOIN' => 'FROM',
'INNER JOIN' => 'FROM',
'LEFT JOIN' => 'FROM',
'RIGHT JOIN' => 'FROM',
];
private Connection $connection;
/** @var Connection */
private $connection;
private array $setups = [];
/** @var array */
private $setups = [];
private ?string $command = null;
/** @var string|null */
private $command;
private array $clauses = [];
/** @var array */
private $clauses = [];
private array $flags = [];
/** @var array */
private $flags = [];
/** @var array|null */
private $cursor;
/** normalized clauses */
private static HashMap $normalizer;
/** @var HashMap normalized clauses */
private static $normalizer;
public function __construct(Connection $connection)
{
$this->connection = $connection;
if (!isset(self::$normalizer)) {
self::$normalizer = new HashMap([self::class, '_formatClause']);
if (self::$normalizer === null) {
self::$normalizer = new HashMap([__CLASS__, '_formatClause']);
}
}
@@ -120,7 +117,7 @@ class Fluent implements IDataSource
/**
* Appends new argument to the clause.
*/
public function __call(string $clause, array $args): static
public function __call(string $clause, array $args): self
{
$clause = self::$normalizer->$clause;
@@ -129,7 +126,6 @@ class Fluent implements IDataSource
if (isset(self::$masks[$clause])) {
$this->clauses = array_fill_keys(self::$masks[$clause], null);
}
$this->cursor = &$this->clauses[$clause];
$this->cursor = [];
$this->command = $clause;
@@ -159,6 +155,7 @@ class Fluent implements IDataSource
$this->cursor[] = $sep;
}
}
} else {
// append to currect flow
if ($args === [self::REMOVE]) {
@@ -196,7 +193,6 @@ class Fluent implements IDataSource
if ($arg instanceof self) {
$arg = new Literal("($arg)");
}
$this->cursor[] = $arg;
}
@@ -207,7 +203,7 @@ class Fluent implements IDataSource
/**
* Switch to a clause.
*/
public function clause(string $clause): static
public function clause(string $clause): self
{
$this->cursor = &$this->clauses[self::$normalizer->$clause];
if ($this->cursor === null) {
@@ -221,7 +217,7 @@ class Fluent implements IDataSource
/**
* Removes a clause.
*/
public function removeClause(string $clause): static
public function removeClause(string $clause): self
{
$this->clauses[self::$normalizer->$clause] = null;
return $this;
@@ -231,7 +227,7 @@ class Fluent implements IDataSource
/**
* Change a SQL flag.
*/
public function setFlag(string $flag, bool $value = true): static
public function setFlag(string $flag, bool $value = true): self
{
$flag = strtoupper($flag);
if ($value) {
@@ -239,7 +235,6 @@ class Fluent implements IDataSource
} else {
unset($this->flags[$flag]);
}
return $this;
}
@@ -271,7 +266,7 @@ class Fluent implements IDataSource
/**
* Adds Result setup.
*/
public function setupResult(string $method): static
public function setupResult(string $method): self
{
$this->setups[] = func_get_args();
return $this;
@@ -283,10 +278,10 @@ class Fluent implements IDataSource
/**
* Generates and executes SQL query.
* Returns result set or number of affected rows
* @return Result|int|null result set or number of affected rows
* @throws Exception
*/
public function execute(?string $return = null): Result|int|null
public function execute(string $return = null)
{
$res = $this->query($this->_export());
switch ($return) {
@@ -302,31 +297,36 @@ class Fluent implements IDataSource
/**
* Generates, executes SQL query and fetches the single row.
* @return Row|array|null
*/
public function fetch(): Row|array|null
public function fetch()
{
return $this->command === 'SELECT' && !$this->clauses['LIMIT']
? $this->query($this->_export(null, ['%lmt', 1]))->fetch()
: $this->query($this->_export())->fetch();
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
return $this->query($this->_export(null, ['%lmt', 1]))->fetch();
} else {
return $this->query($this->_export())->fetch();
}
}
/**
* Like fetch(), but returns only first field.
* Returns value on success, null if no next record
* @return mixed value on success, false if no next record
*/
public function fetchSingle(): mixed
public function fetchSingle()
{
return $this->command === 'SELECT' && !$this->clauses['LIMIT']
? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle()
: $this->query($this->_export())->fetchSingle();
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
return $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle();
} else {
return $this->query($this->_export())->fetchSingle();
}
}
/**
* 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();
}
@@ -345,7 +345,7 @@ class Fluent implements IDataSource
/**
* 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);
}
@@ -354,7 +354,7 @@ class Fluent implements IDataSource
/**
* 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();
}
@@ -363,7 +363,7 @@ class Fluent implements IDataSource
/**
* 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));
}
@@ -384,7 +384,6 @@ class Fluent implements IDataSource
$method = array_shift($setup);
$res->$method(...$setup);
}
return $res;
}
@@ -403,21 +402,27 @@ class Fluent implements IDataSource
*/
final public function __toString(): string
{
return $this->connection->translate($this->_export());
try {
return $this->connection->translate($this->_export());
} catch (\Throwable $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
return '';
}
}
/**
* 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) {
$data = $this->clauses;
if ($this->command === 'SELECT' && ($data['LIMIT'] || $data['OFFSET'])) {
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args);
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0], $data['OFFSET'][0]], $args);
unset($data['LIMIT'], $data['OFFSET']);
}
} else {
$clause = self::$normalizer->$clause;
if (array_key_exists($clause, $this->clauses)) {
@@ -433,7 +438,6 @@ class Fluent implements IDataSource
if ($clause === $this->command && $this->flags) {
$args[] = implode(' ', array_keys($this->flags));
}
foreach ($statement as $arg) {
$args[] = $arg;
}
@@ -454,7 +458,6 @@ class Fluent implements IDataSource
$s .= 'By';
trigger_error("Did you mean '$s'?", E_USER_NOTICE);
}
return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
}
@@ -466,7 +469,6 @@ class Fluent implements IDataSource
$this->clauses[$clause] = &$val;
unset($val);
}
$this->cursor = &$foo;
}
}

View File

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

View File

@@ -14,17 +14,19 @@ class Helpers
{
use Strict;
private static HashMap $types;
/** @var HashMap */
private static $types;
/**
* Prints out a syntax highlighted version of the SQL command or Result.
* @param string|Result $sql
*/
public static function dump(string|Result|null $sql = null, bool $return = false): ?string
public static function dump($sql = null, bool $return = false): ?string
{
ob_start();
if ($sql instanceof Result && PHP_SAPI === 'cli') {
$hasColors = (str_starts_with((string) getenv('TERM'), 'xterm'));
$hasColors = (substr((string) getenv('TERM'), 0, 5) === 'xterm');
$maxLen = 0;
foreach ($sql as $i => $row) {
if ($i === 0) {
@@ -39,7 +41,6 @@ class Helpers
$spaces = $maxLen - mb_strlen($col) + 2;
echo "$col" . str_repeat(' ', $spaces) . "$val\n";
}
echo "\n";
}
@@ -52,7 +53,6 @@ class Helpers
foreach ($row as $col => $foo) {
echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n";
}
echo "\t</tr>\n</thead>\n<tbody>\n";
}
@@ -60,7 +60,6 @@ class Helpers
foreach ($row as $col) {
echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n";
}
echo "\t</tr>\n";
}
@@ -73,8 +72,8 @@ class Helpers
$sql = \dibi::$sql;
}
$keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|FETCH\s+NEXT|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE|START\s+TRANSACTION|BEGIN|COMMIT|ROLLBACK(?:\s+TO\s+SAVEPOINT)?|(?:RELEASE\s+)?SAVEPOINT';
$keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE';
static $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|FETCH\s+NEXT|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE|START\s+TRANSACTION|BEGIN|COMMIT|ROLLBACK(?:\s+TO\s+SAVEPOINT)?|(?:RELEASE\s+)?SAVEPOINT';
static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE';
// insert new lines
$sql = " $sql ";
@@ -89,7 +88,7 @@ class Helpers
// syntax highlight
$highlighter = "#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is";
if (PHP_SAPI === 'cli') {
if (str_starts_with((string) getenv('TERM'), 'xterm')) {
if (substr((string) getenv('TERM'), 0, 5) === 'xterm') {
$sql = preg_replace_callback($highlighter, function (array $m) {
if (!empty($m[1])) { // comment
return "\033[1;30m" . $m[1] . "\033[0m";
@@ -105,7 +104,6 @@ class Helpers
}
}, $sql);
}
echo trim($sql) . "\n\n";
} else {
@@ -145,14 +143,13 @@ class Helpers
{
$best = null;
$min = (strlen($value) / 4 + 1) * 10 + .1;
$items = array_map('strval', $items);
foreach (array_unique($items) as $item) {
foreach (array_unique($items, SORT_REGULAR) as $item) {
$item = is_object($item) ? $item->getName() : $item;
if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
$min = $len;
$best = $item;
}
}
return $best;
}
@@ -160,7 +157,7 @@ class Helpers
/** @internal */
public static function escape(Driver $driver, $value, string $type): string
{
$types = [
static $types = [
Type::TEXT => 'text',
Type::BINARY => 'binary',
Type::BOOL => 'bool',
@@ -182,9 +179,8 @@ class Helpers
*/
public static function detectType(string $type): ?string
{
$patterns = [
static $patterns = [
'^_' => Type::TEXT, // PostgreSQL arrays
'RANGE$' => Type::TEXT, // PostgreSQL range types
'BYTEA|BLOB|BIN' => Type::BINARY,
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
@@ -201,18 +197,18 @@ class Helpers
return $val;
}
}
return null;
}
/** @internal */
/**
* @internal
*/
public static function getTypeCache(): HashMap
{
if (!isset(self::$types)) {
self::$types = new HashMap([self::class, 'detectType']);
if (self::$types === null) {
self::$types = new HashMap([__CLASS__, 'detectType']);
}
return self::$types;
}
@@ -236,9 +232,9 @@ class Helpers
/**
* Import SQL dump from file.
* Returns 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 @
@@ -257,7 +253,7 @@ class Helpers
if (strtoupper(substr($s, 0, 10)) === 'DELIMITER ') {
$delimiter = trim(substr($s, 10));
} elseif (str_ends_with($ts = rtrim($s), $delimiter)) {
} elseif (substr($ts = rtrim($s), -strlen($delimiter)) === $delimiter) {
$sql .= substr($ts, 0, -strlen($delimiter));
$driver->query($sql);
$sql = '';
@@ -265,6 +261,7 @@ class Helpers
if ($onProgress) {
$onProgress($count, isset($stat['size']) ? $size * 100 / $stat['size'] : null);
}
} else {
$sql .= $s;
}
@@ -277,21 +274,24 @@ class Helpers
$onProgress($count, isset($stat['size']) ? 100 : null);
}
}
fclose($handle);
return $count;
}
/** @internal */
public static function false2Null(mixed $val): mixed
/**
* @internal
*/
public static function false2Null($val)
{
return $val === false ? null : $val;
}
/** @internal */
public static function intVal(mixed $value): int
/**
* @internal
*/
public static function intVal($value): int
{
if (is_int($value)) {
return $value;
@@ -299,7 +299,6 @@ class Helpers
if (is_float($value * 1)) {
throw new Exception("Number $value is greater than integer.");
}
return (int) $value;
} else {
throw new Exception("Expected number, '$value' given.");

View File

@@ -17,7 +17,8 @@ class Literal
{
use Strict;
private string $value;
/** @var string */
private $value;
public function __construct($value)

View File

@@ -19,19 +19,17 @@ class FileLogger
{
use Dibi\Strict;
/** Name of the file where SQL errors should be logged */
public string $file;
/** @var string Name of the file where SQL errors should be logged */
public $file;
public int $filter;
private bool $errorsOnly;
/** @var int */
public $filter;
public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false)
public function __construct(string $file, int $filter = null)
{
$this->file = $file;
$this->filter = $filter ?: Dibi\Event::QUERY;
$this->errorsOnly = $errorsOnly;
}
@@ -40,43 +38,39 @@ class FileLogger
*/
public function logEvent(Dibi\Event $event): void
{
if (
(($event->type & $this->filter) === 0)
|| ($this->errorsOnly === true && !$event->result instanceof \Exception)
) {
if (($event->type & $this->filter) === 0) {
return;
}
$handle = fopen($this->file, 'a');
if (!$handle) {
return; // or throw exception?
}
flock($handle, LOCK_EX);
if ($event->result instanceof \Exception) {
$message = $event->result->getMessage();
if ($code = $event->result->getCode()) {
$message = "[$code] $message";
}
$this->writeToFile(
$event,
fwrite($handle,
"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 {
$this->writeToFile(
$event,
fwrite($handle,
'OK: ' . $event->sql
. ($event->count ? ";\n-- rows: " . $event->count : '')
. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
. "\n-- source: " . implode(':', $event->source),
. ($event->count ? ";\n-- rows: " . $event->count : '')
. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
. "\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"
);
}
}
private function writeToFile(Dibi\Event $event, string $message): void
{
$driver = $event->connection->getConfig('driver');
$message .=
"\n-- driver: " . (is_object($driver) ? $driver::class : $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);
fclose($handle);
}
}

View File

@@ -29,14 +29,14 @@ class Column
{
use Dibi\Strict;
/** when created by Result */
private ?Dibi\Reflector $reflector;
/** @var Dibi\Reflector|null when created by Result */
private $reflector;
/** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */
private array $info;
private $info;
public function __construct(?Dibi\Reflector $reflector, array $info)
public function __construct(Dibi\Reflector $reflector = null, array $info)
{
$this->reflector = $reflector;
$this->info = $info;
@@ -66,20 +66,17 @@ class Column
if (empty($this->info['table']) || !$this->reflector) {
throw new Dibi\Exception('Table is unknown or not available.');
}
return new Table($this->reflector, ['name' => $this->info['table']]);
}
public function getTableName(): ?string
{
return isset($this->info['table']) && $this->info['table'] != null // intentionally ==
? $this->info['table']
: null;
return isset($this->info['table']) && $this->info['table'] != null ? $this->info['table'] : null; // intentionally ==
}
public function getType(): ?string
public function getType(): string
{
return Dibi\Helpers::getTypeCache()->{$this->info['nativetype']};
}
@@ -109,13 +106,19 @@ class Column
}
public function getDefault(): mixed
/**
* @return mixed
*/
public function getDefault()
{
return $this->info['default'] ?? null;
}
public function getVendorInfo(string $key): mixed
/**
* @return mixed
*/
public function getVendorInfo(string $key)
{
return $this->info['vendor'][$key] ?? null;
}

View File

@@ -23,15 +23,17 @@ class Database
{
use Dibi\Strict;
private Dibi\Reflector $reflector;
/** @var Dibi\Reflector */
private $reflector;
private ?string $name;
/** @var string|null */
private $name;
/** @var Table[] */
private array $tables;
/** @var Table[]|null */
private $tables;
public function __construct(Dibi\Reflector $reflector, ?string $name = null)
public function __construct(Dibi\Reflector $reflector, string $name = null)
{
$this->reflector = $reflector;
$this->name = $name;
@@ -44,7 +46,9 @@ class Database
}
/** @return Table[] */
/**
* @return Table[]
*/
public function getTables(): array
{
$this->init();
@@ -52,7 +56,9 @@ class Database
}
/** @return string[] */
/**
* @return string[]
*/
public function getTableNames(): array
{
$this->init();
@@ -60,7 +66,6 @@ class Database
foreach ($this->tables as $table) {
$res[] = $table->getName();
}
return $res;
}
@@ -87,7 +92,7 @@ class Database
protected function init(): void
{
if (!isset($this->tables)) {
if ($this->tables === null) {
$this->tables = [];
foreach ($this->reflector->getTables() as $info) {
$this->tables[strtolower($info['name'])] = new Table($this->reflector, $info);

View File

@@ -22,10 +22,11 @@ class ForeignKey
{
use Dibi\Strict;
private string $name;
/** @var string */
private $name;
/** @var array of [local, foreign, onDelete, onUpdate] */
private array $references;
private $references;
public function __construct(string $name, array $references)

View File

@@ -25,7 +25,7 @@ class Index
use Dibi\Strict;
/** @var array (name, columns, [unique], [primary]) */
private array $info;
private $info;
public function __construct(array $info)

View File

@@ -22,13 +22,14 @@ class Result
{
use Dibi\Strict;
private Dibi\ResultDriver $driver;
/** @var Dibi\ResultDriver */
private $driver;
/** @var Column[]|null */
private ?array $columns;
private $columns;
/** @var Column[]|null */
private ?array $names;
/** @var string[]|null */
private $names;
public function __construct(Dibi\ResultDriver $driver)
@@ -37,7 +38,9 @@ class Result
}
/** @return Column[] */
/**
* @return Column[]
*/
public function getColumns(): array
{
$this->initColumns();
@@ -45,7 +48,9 @@ class Result
}
/** @return string[] */
/**
* @return string[]
*/
public function getColumnNames(bool $fullNames = false): array
{
$this->initColumns();
@@ -53,7 +58,6 @@ class Result
foreach ($this->columns as $column) {
$res[] = $fullNames ? $column->getFullName() : $column->getName();
}
return $res;
}
@@ -80,11 +84,9 @@ class Result
protected function initColumns(): void
{
if (!isset($this->columns)) {
if ($this->columns === null) {
$this->columns = [];
$reflector = $this->driver instanceof Dibi\Reflector
? $this->driver
: null;
$reflector = $this->driver instanceof Dibi\Reflector ? $this->driver : null;
foreach ($this->driver->getResultColumns() as $info) {
$this->columns[] = $this->names[strtolower($info['name'])] = new Column($reflector, $info);
}

View File

@@ -27,22 +27,26 @@ class Table
{
use Dibi\Strict;
private Dibi\Reflector $reflector;
/** @var Dibi\Reflector */
private $reflector;
private string $name;
/** @var string */
private $name;
private bool $view;
/** @var bool */
private $view;
/** @var Column[] */
private array $columns;
/** @var Column[]|null */
private $columns;
/** @var ForeignKey[] */
private array $foreignKeys;
/** @var ForeignKey[]|null */
private $foreignKeys;
/** @var Index[] */
private array $indexes;
/** @var Index[]|null */
private $indexes;
private ?Index $primaryKey;
/** @var Index|null */
private $primaryKey;
public function __construct(Dibi\Reflector $reflector, array $info)
@@ -65,7 +69,9 @@ class Table
}
/** @return Column[] */
/**
* @return Column[]
*/
public function getColumns(): array
{
$this->initColumns();
@@ -73,7 +79,9 @@ class Table
}
/** @return string[] */
/**
* @return string[]
*/
public function getColumnNames(): array
{
$this->initColumns();
@@ -81,7 +89,6 @@ class Table
foreach ($this->columns as $column) {
$res[] = $column->getName();
}
return $res;
}
@@ -106,7 +113,9 @@ class Table
}
/** @return ForeignKey[] */
/**
* @return ForeignKey[]
*/
public function getForeignKeys(): array
{
$this->initForeignKeys();
@@ -114,7 +123,9 @@ class Table
}
/** @return Index[] */
/**
* @return Index[]
*/
public function getIndexes(): array
{
$this->initIndexes();
@@ -131,7 +142,7 @@ class Table
protected function initColumns(): void
{
if (!isset($this->columns)) {
if ($this->columns === null) {
$this->columns = [];
foreach ($this->reflector->getColumns($this->name) as $info) {
$this->columns[strtolower($info['name'])] = new Column($this->reflector, $info);
@@ -142,14 +153,13 @@ class Table
protected function initIndexes(): void
{
if (!isset($this->indexes)) {
if ($this->indexes === null) {
$this->initColumns();
$this->indexes = [];
foreach ($this->reflector->getIndexes($this->name) as $info) {
foreach ($info['columns'] as $key => $name) {
$info['columns'][$key] = $this->columns[strtolower($name)];
}
$this->indexes[strtolower($info['name'])] = new Index($info);
if (!empty($info['primary'])) {
$this->primaryKey = $this->indexes[strtolower($info['name'])];

View File

@@ -19,31 +19,32 @@ class Result implements IDataSource
{
use Strict;
private ?ResultDriver $driver;
/** @var ResultDriver */
private $driver;
/** Translate table */
private array $types = [];
/** @var array Translate table */
private $types = [];
private ?Reflection\Result $meta;
/** @var Reflection\Result|null */
private $meta;
/** Already fetched? Used for allowance for first seek(0) */
private bool $fetched = false;
/** @var bool Already fetched? Used for allowance for first seek(0) */
private $fetched = false;
/** returned object class */
private ?string $rowClass = Row::class;
/** @var string|null returned object class */
private $rowClass = Row::class;
/** @var callable|null returned object factory */
private $rowFactory;
private array $formats = [];
/** @var array format */
private $formats = [];
public function __construct(ResultDriver $driver, bool $normalize = true)
public function __construct(ResultDriver $driver)
{
$this->driver = $driver;
if ($normalize) {
$this->detectTypes();
}
$this->detectTypes();
}
@@ -82,9 +83,7 @@ class Result implements IDataSource
*/
final public function seek(int $row): bool
{
return ($row !== 0 || $this->fetched)
? $this->getResultDriver()->seek($row)
: true;
return ($row !== 0 || $this->fetched) ? $this->getResultDriver()->seek($row) : true;
}
@@ -130,7 +129,7 @@ class Result implements IDataSource
/**
* Set fetched object class. This class should extend the Row class.
*/
public function setRowClass(?string $class): static
public function setRowClass(?string $class): self
{
$this->rowClass = $class;
return $this;
@@ -149,7 +148,7 @@ class Result implements IDataSource
/**
* Set a factory to create fetched object instances. These should extend the Row class.
*/
public function setRowFactory(callable $callback): static
public function setRowFactory(callable $callback): self
{
$this->rowFactory = $callback;
return $this;
@@ -159,14 +158,14 @@ class Result implements IDataSource
/**
* Fetches the row at current position, process optional type conversion.
* and moves the internal cursor to the next position
* @return Row|array|null
*/
final public function fetch(): mixed
final public function fetch()
{
$row = $this->getResultDriver()->fetch(true);
if ($row === null) {
return null;
}
$this->fetched = true;
$this->normalize($row);
if ($this->rowFactory) {
@@ -174,22 +173,20 @@ class Result implements IDataSource
} elseif ($this->rowClass) {
return new $this->rowClass($row);
}
return $row;
}
/**
* Like fetch(), but returns only first field.
* Returns value on success, null if no next record
* @return mixed value on success, false if no next record
*/
final public function fetchSingle(): mixed
final public function fetchSingle()
{
$row = $this->getResultDriver()->fetch(true);
if ($row === null) {
return null;
return false;
}
$this->fetched = true;
$this->normalize($row);
return reset($row);
@@ -200,9 +197,9 @@ class Result implements IDataSource
* Fetches all records from table.
* @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 ??= -1;
$limit = $limit === null ? -1 : $limit;
$this->seek($offset ?: 0);
$row = $this->fetch();
if (!$row) {
@@ -214,7 +211,6 @@ class Result implements IDataSource
if ($limit === 0) {
break;
}
$limit--;
$data[] = $row;
} while ($row = $this->fetch());
@@ -234,7 +230,7 @@ class Result implements IDataSource
*/
final public function fetchAssoc(string $assoc): array
{
if (str_contains($assoc, ',')) {
if (strpos($assoc, ',') !== false) {
return $this->oldFetchAssoc($assoc);
}
@@ -246,9 +242,6 @@ class Result implements IDataSource
$data = null;
$assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
if (!$assoc) {
throw new \InvalidArgumentException("Invalid descriptor '$assoc'.");
}
// check columns
foreach ($assoc as $as) {
@@ -287,8 +280,9 @@ class Result implements IDataSource
} else {
$x = &$x->{$assoc[$i + 1]};
}
} elseif ($as !== '|') { // associative-array node
$x = &$x[(string) $row->$as];
$x = &$x[$row->$as];
}
}
@@ -298,12 +292,13 @@ class Result implements IDataSource
} while ($row = $this->fetch());
unset($x);
/** @var mixed[] $data */
return $data;
}
/** @deprecated */
/**
* @deprecated
*/
private function oldFetchAssoc(string $assoc)
{
$this->seek(0);
@@ -344,6 +339,7 @@ class Result implements IDataSource
} else {
$x = &$x[$assoc[$i + 1]];
}
} elseif ($as === '@') { // "object" node
if ($x === null) {
$x = clone $row;
@@ -352,15 +348,18 @@ class Result implements IDataSource
} else {
$x = &$x->{$assoc[$i + 1]};
}
} else { // associative-array node
$x = &$x[(string) $row->$as];
$x = &$x[$row->$as];
}
}
if ($x === null) { // build leaf
$x = $leaf === '='
? $row->toArray()
: $row;
if ($leaf === '=') {
$x = $row->toArray();
} else {
$x = $row;
}
}
} while ($row = $this->fetch());
@@ -373,7 +372,7 @@ class Result implements IDataSource
* Fetches all records from table like $key => $value pairs.
* @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);
$row = $this->fetch();
@@ -395,7 +394,6 @@ class Result implements IDataSource
do {
$data[] = $row[$key];
} while ($row = $this->fetch());
return $data;
}
@@ -410,7 +408,6 @@ class Result implements IDataSource
do {
$data[] = $row[$value];
} while ($row = $this->fetch());
return $data;
}
@@ -454,14 +451,8 @@ class Result implements IDataSource
if (!isset($row[$key])) { // null
continue;
}
$value = $row[$key];
$format = $this->formats[$type] ?? null;
if ($type === null || $format === 'native') {
$row[$key] = $value;
} elseif ($type === Type::TEXT) {
if ($type === Type::TEXT) {
$row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) {
@@ -472,17 +463,12 @@ class Result implements IDataSource
} elseif ($type === Type::FLOAT) {
$value = ltrim((string) $value, '0');
$p = strpos($value, '.');
$e = strpos($value, 'e');
if ($p !== false && $e === false) {
if ($p !== false) {
$value = rtrim(rtrim($value, '0'), '.');
} elseif ($p !== false && $e !== false) {
$value = rtrim($value, '.');
}
if ($value === '' || $value[0] === '.') {
$value = '0' . $value;
}
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float
: $value;
@@ -491,31 +477,23 @@ class Result implements IDataSource
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ...
$value = new DateTime($value);
$row[$key] = $format ? $value->format($format) : $value;
$row[$key] = empty($this->formats[$type]) ? $value : $value->format($this->formats[$type]);
} else {
$row[$key] = null;
}
} elseif ($type === Type::TIME_INTERVAL) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$value->invert = (int) (bool) $m[1];
$row[$key] = $format ? $value->format($format) : $value;
$row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$row[$key]->invert = (int) (bool) $m[1];
} elseif ($type === Type::BINARY) {
$row[$key] = is_string($value)
? $this->getResultDriver()->unescapeBinary($value)
: $value;
$row[$key] = is_string($value) ? $this->getResultDriver()->unescapeBinary($value) : $value;
} elseif ($type === Type::JSON) {
if ($format === 'string') { // back compatibility with 'native'
$row[$key] = $value;
} else {
$row[$key] = json_decode($value, $format === 'array');
}
} else {
throw new \RuntimeException('Unexpected type ' . $type);
$row[$key] = json_decode($value, true);
}
}
}
@@ -523,9 +501,9 @@ class Result implements IDataSource
/**
* Define column type.
* @param string|null $type use constant Type::*
* @param string $type use constant Type::*
*/
final public function setType(string $column, ?string $type): static
final public function setType(string $column, string $type): self
{
$this->types[$column] = $type;
return $this;
@@ -535,41 +513,22 @@ class Result implements IDataSource
/**
* Returns column type.
*/
final public function getType(string $column): ?string
final public function getType(string $column): string
{
return $this->types[$column] ?? null;
}
/**
* Returns columns type.
* Sets date format.
*/
final public function getTypes(): array
{
return $this->types;
}
/**
* Sets type format.
*/
final public function setFormat(string $type, ?string $format): static
final public function setFormat(string $type, ?string $format): self
{
$this->formats[$type] = $format;
return $this;
}
/**
* Sets type formats.
*/
final public function setFormats(array $formats): static
{
$this->formats = $formats;
return $this;
}
/**
* Returns data format.
*/
@@ -587,15 +546,16 @@ class Result implements IDataSource
*/
public function getInfo(): Reflection\Result
{
if (!isset($this->meta)) {
if ($this->meta === null) {
$this->meta = new Reflection\Result($this->getResultDriver());
}
return $this->meta;
}
/** @return Reflection\Column[] */
/**
* @return Reflection\Column[]
*/
final public function getColumns(): array
{
return $this->getInfo()->getColumns();

View File

@@ -17,11 +17,14 @@ class ResultIterator implements \Iterator, \Countable
{
use Strict;
private Result $result;
/** @var Result */
private $result;
private mixed $row;
/** @var mixed */
private $row;
private int $pointer = 0;
/** @var int */
private $pointer = 0;
public function __construct(Result $result)
@@ -43,8 +46,9 @@ class ResultIterator implements \Iterator, \Countable
/**
* Returns the key of the current element.
* @return mixed
*/
public function key(): mixed
public function key()
{
return $this->pointer;
}
@@ -52,8 +56,9 @@ class ResultIterator implements \Iterator, \Countable
/**
* Returns the current element.
* @return mixed
*/
public function current(): mixed
public function current()
{
return $this->row;
}

View File

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

View File

@@ -20,7 +20,7 @@ use ReflectionProperty;
trait Strict
{
/** @var array [method => [type => callback]] */
private static array $extMethods;
private static $extMethods;
/**
@@ -29,12 +29,15 @@ trait Strict
*/
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 = array_map(fn($item) => $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");
}
@@ -45,12 +48,9 @@ trait Strict
*/
public static function __callStatic(string $name, array $args)
{
$rc = new ReflectionClass(static::class);
$items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), fn($m) => $m->isPublic());
$items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?"
: '.';
$rc = new ReflectionClass(get_called_class());
$items = array_intersect($rc->getMethods(ReflectionMethod::IS_PUBLIC), $rc->getMethods(ReflectionMethod::IS_STATIC));
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
}
@@ -67,13 +67,9 @@ trait Strict
$ret = $this->$m();
return $ret;
}
$rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic());
$items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?"
: '.';
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
}
@@ -85,11 +81,8 @@ trait Strict
public function __set(string $name, $value)
{
$rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic());
$items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?"
: '.';
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
}
@@ -106,7 +99,42 @@ trait Strict
*/
public function __unset(string $name)
{
$class = static::class;
$class = get_class($this);
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

@@ -17,28 +17,38 @@ final class Translator
{
use Strict;
private Connection $connection;
/** @var Connection */
private $connection;
private Driver $driver;
/** @var Driver */
private $driver;
private int $cursor = 0;
/** @var int */
private $cursor = 0;
private array $args;
/** @var array */
private $args;
/** @var string[] */
private array $errors;
private $errors;
private bool $comment = false;
/** @var bool */
private $comment = false;
private int $ifLevel = 0;
/** @var int */
private $ifLevel = 0;
private int $ifLevelStart = 0;
/** @var int */
private $ifLevelStart = 0;
private ?int $limit = null;
/** @var int|null */
private $limit;
private ?int $offset = null;
/** @var int|null */
private $offset;
private HashMap $identifiers;
/** @var HashMap */
private $identifiers;
public function __construct(Connection $connection)
@@ -59,7 +69,6 @@ final class Translator
while (count($args) === 1 && is_array($args[0])) { // implicit array expansion
$args = array_values($args[0]);
}
$this->args = $args;
$this->errors = [];
@@ -83,30 +92,28 @@ final class Translator
$sql[] = $arg;
} else {
$sql[] = substr($arg, 0, $toSkip)
// note: this can change $this->args & $this->cursor & ...
. preg_replace_callback(
<<<'XX'
/
(?=[`['":%?]) ## speed-up
(?:
`(.+?)`| ## 1) `identifier`
\[(.+?)\]| ## 2) [identifier]
(')((?:''|[^'])*)'| ## 3,4) string
(")((?:""|[^"])*)"| ## 5,6) "string"
('|")| ## 7) lone quote
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
%([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier
(\?) ## 11) placeholder
)/xs
XX,
/*
. preg_replace_callback('/
(?=[`[\'":%?]) ## speed-up
(?:
`(.+?)`| ## 1) `identifier`
\[(.+?)\]| ## 2) [identifier]
(\')((?:\'\'|[^\'])*)\'| ## 3,4) 'string'
(")((?:""|[^"])*)"| ## 5,6) "string"
(\'|")| ## 7) lone quote
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
%([a-zA-Z~][a-zA-Z0-9~]{0,5})|## 10) modifier
(\?) ## 11) placeholder
)/xs',
*/ // note: this can change $this->args & $this->cursor & ...
. preg_replace_callback('/(?=[`[\'":%?])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?)|%([a-zA-Z~][a-zA-Z0-9~]{0,5})|(\?))/s',
[$this, 'cb'],
substr($arg, $toSkip),
);
substr($arg, $toSkip)
);
if (preg_last_error()) {
throw new PcreException;
}
}
continue;
}
@@ -129,10 +136,8 @@ final class Translator
if ($lastArr === $cursor - 1) {
$sql[] = ',';
}
$sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
}
$lastArr = $cursor;
continue;
}
@@ -141,11 +146,12 @@ final class Translator
$sql[] = $this->formatValue($arg, null);
} // while
if ($comment) {
$sql[] = '*/';
}
$sql = trim(implode(' ', $sql), ' ');
$sql = implode(' ', $sql);
if ($this->errors) {
throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql);
@@ -162,8 +168,9 @@ final class Translator
/**
* Apply modifier to single value.
* @param mixed $value
*/
public function formatValue(mixed $value, ?string $modifier): string
public function formatValue($value, ?string $modifier): string
{
if ($this->comment) {
return '...';
@@ -198,21 +205,20 @@ final class Translator
$v = $this->formatValue($v, $pair[1]);
if ($pair[1] === 'l' || $pair[1] === 'in') {
$op = 'IN ';
} elseif (str_contains($pair[1], 'like')) {
} elseif (strpos($pair[1], 'like') !== false) {
$op = 'LIKE ';
} elseif ($v === 'NULL') {
$op = 'IS ';
} else {
$op = '= ';
}
$vx[] = $k . $op . $v;
}
} else {
$vx[] = $this->formatValue($v, 'ex');
}
}
return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')';
case 'n': // key, key, ... identifier names
@@ -224,7 +230,6 @@ final class Translator
$vx[] = $this->identifiers->{$pair[0]};
}
}
return implode(', ', $vx);
@@ -234,7 +239,6 @@ final class Translator
$vx[] = $this->identifiers->{$pair[0]} . '='
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
}
return implode(', ', $vx);
@@ -244,7 +248,6 @@ final class Translator
$pair = explode('%', (string) $k, 2); // split into identifier & modifier
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
}
return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')';
@@ -254,7 +257,6 @@ final class Translator
$kx[] = $this->identifiers->{$pair[0]};
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
}
return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ...
@@ -268,7 +270,7 @@ final class Translator
$proto = array_keys($v);
}
} else {
return $this->errors[] = '**Unexpected type ' . (is_object($v) ? $v::class : gettype($v)) . '**';
return $this->errors[] = '**Unexpected type ' . (is_object($v) ? get_class($v) : gettype($v)) . '**';
}
$pair = explode('%', $k, 2); // split into identifier & modifier
@@ -277,11 +279,9 @@ final class Translator
$vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null));
}
}
foreach ($vx as $k => $v) {
$vx[$k] = '(' . implode(', ', $v) . ')';
}
return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx);
case 'by': // key ASC, key DESC
@@ -289,13 +289,12 @@ final class Translator
if (is_array($v)) {
$vx[] = $this->formatValue($v, 'ex');
} 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;
} else {
$vx[] = $this->identifiers->$v;
}
}
return implode(', ', $vx);
case 'ex!':
@@ -309,28 +308,10 @@ final class Translator
foreach ($value as $v) {
$vx[] = $this->formatValue($v, $modifier);
}
return implode(', ', $vx);
}
}
if (is_object($value)
&& $modifier === null
&& !$value instanceof Literal
&& !$value instanceof Expression
) {
foreach ($this->connection->getObjectTranslators() as $class => $translator) {
if ($value instanceof $class) {
$value = $translator($value);
return $this->connection->translate(...$value->getValues());
}
}
}
// object-to-scalar procession
if ($value instanceof \BackedEnum && is_scalar($value->value)) {
$value = $value->value;
}
// with modifier procession
if ($modifier) {
@@ -341,45 +322,31 @@ final class Translator
} elseif ($value instanceof Expression && $modifier === 'ex') {
return $this->connection->translate(...$value->getValues());
} elseif (
$value instanceof \DateTimeInterface
&& ($modifier === 'd'
|| $modifier === 't'
|| $modifier === 'dt'
)
) {
} elseif ($value instanceof \DateTimeInterface && ($modifier === 'd' || $modifier === 't' || $modifier === 'dt')) {
// continue
} else {
$type = is_object($value) ? $value::class : gettype($value);
$type = is_object($value) ? get_class($value) : gettype($value);
return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
}
}
switch ($modifier) {
case 's': // string
return $value === null
? 'NULL'
: $this->driver->escapeText((string) $value);
return $value === null ? 'NULL' : $this->driver->escapeText((string) $value);
case 'bin':// binary
return $value === null
? 'NULL'
: $this->driver->escapeBinary($value);
return $value === null ? 'NULL' : $this->driver->escapeBinary($value);
case 'b': // boolean
return $value === null
? 'NULL'
: $this->driver->escapeBool((bool) $value);
return $value === null ? 'NULL' : $this->driver->escapeBool((bool) $value);
case 'sN': // string or null
case 'sn':
return $value === '' || $value === 0 || $value === null
? 'NULL'
: $this->driver->escapeText((string) $value);
return $value == '' ? 'NULL' : $this->driver->escapeText((string) $value); // notice two equal signs
case 'iN': // signed int or null
if ($value === '' || $value === 0 || $value === null) {
return 'NULL';
if ($value == '') {
$value = null;
}
// break omitted
case 'i': // signed int
@@ -414,14 +381,10 @@ final class Translator
case 'dt': // datetime
if ($value === null) {
return 'NULL';
} elseif (!$value instanceof \DateTimeInterface) {
$value = new DateTime($value);
} else {
return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value);
}
return $modifier === 'd'
? $this->driver->escapeDate($value)
: $this->driver->escapeDateTime($value);
// break omitted
case 'by':
case 'n': // composed identifier name
return $this->identifiers->$value;
@@ -436,43 +399,27 @@ final class Translator
$toSkip = strcspn($value, '`[\'":');
if (strlen($value) !== $toSkip) {
$value = substr($value, 0, $toSkip)
. preg_replace_callback(
<<<'XX'
/
(?=[`['":])
(?:
`(.+?)`|
\[(.+?)\]|
(')((?:''|[^'])*)'|
(")((?:""|[^"])*)"|
('|")|
:(\S*?:)([a-zA-Z0-9._]?)
)/sx
XX
,
[$this, 'cb'],
substr($value, $toSkip),
);
. preg_replace_callback(
'/(?=[`[\'":])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?))/s',
[$this, 'cb'],
substr($value, $toSkip)
);
if (preg_last_error()) {
throw new PcreException;
}
}
return $value;
case 'SQL': // preserve as real SQL (TODO: rename to %sql)
return (string) $value;
case 'like~': // LIKE string%
return $this->driver->escapeLike($value, 2);
case '~like': // LIKE %string
return $this->driver->escapeLike($value, 1);
case '~like~': // LIKE %string%
return $this->driver->escapeLike($value, 3);
case '~like': // LIKE %string
return $this->driver->escapeLike($value, -1);
case 'like': // LIKE string
case '~like~': // LIKE %string%
return $this->driver->escapeLike($value, 0);
case 'and':
@@ -488,6 +435,7 @@ final class Translator
}
}
// without modifier procession
if (is_string($value)) {
return $this->driver->escapeText($value);
@@ -507,9 +455,6 @@ final class Translator
} elseif ($value instanceof \DateTimeInterface) {
return $this->driver->escapeDateTime($value);
} elseif ($value instanceof \DateInterval) {
return $this->driver->escapeDateInterval($value);
} elseif ($value instanceof Literal) {
return (string) $value;
@@ -517,7 +462,7 @@ final class Translator
return $this->connection->translate(...$value->getValues());
} else {
$type = is_object($value) ? $value::class : gettype($value);
$type = is_object($value) ? get_class($value) : gettype($value);
return $this->errors[] = "**Unexpected $type**";
}
}
@@ -569,7 +514,6 @@ final class Translator
$this->comment = true;
return '/*';
}
return '';
} elseif ($mod === 'else') {
@@ -582,6 +526,7 @@ final class Translator
$this->comment = true;
return '/*';
}
} elseif ($mod === 'end') {
$this->ifLevel--;
if ($this->ifLevelStart === $this->ifLevel + 1) {
@@ -590,7 +535,6 @@ final class Translator
$this->comment = false;
return '*/';
}
return '';
} elseif ($mod === 'ex') { // array expansion
@@ -605,7 +549,6 @@ final class Translator
} else {
$this->limit = Helpers::intVal($arg);
}
return '';
} elseif ($mod === 'ofs') { // apply offset
@@ -616,7 +559,6 @@ final class Translator
} else {
$this->offset = Helpers::intVal($arg);
}
return '';
} else { // default processing
@@ -648,9 +590,7 @@ final class Translator
if ($matches[8]) { // SQL identifier substitution
$m = substr($matches[8], 0, -1);
$m = $this->connection->getSubstitutes()->$m;
return $matches[9] === ''
? $this->formatValue($m, null)
: $m . $matches[9]; // value or identifier
return $matches[9] == '' ? $this->formatValue($m, null) : $m . $matches[9]; // value or identifier
}
throw new \Exception('this should be never executed');
@@ -670,7 +610,6 @@ final class Translator
$v = $this->driver->escapeIdentifier($v);
}
}
return implode('.', $parts);
}
}

View File

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

View File

@@ -11,29 +11,28 @@ declare(strict_types=1);
/**
* Static container class for Dibi connections.
*
* @method static void disconnect()
* @method static Dibi\Result query(...$args)
* @method static Dibi\Result nativeQuery(...$args)
* @method static bool test(...$args)
* @method static Dibi\DataSource dataSource(...$args)
* @method static Dibi\Row|null fetch(...$args)
* @method static array fetchAll(...$args)
* @method static mixed fetchSingle(...$args)
* @method static array fetchPairs(...$args)
* @method static int getAffectedRows()
* @method static int getInsertId(string $sequence = null)
* @method static void begin(string $savepoint = null)
* @method static void commit(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\Fluent command()
* @method static Dibi\Fluent select(...$args)
* @method static Dibi\Fluent update(string|string[] $table, array $args)
* @method static Dibi\Fluent insert(string $table, array $args)
* @method static Dibi\Fluent delete(string $table)
* @method static Dibi\HashMap getSubstitutes()
* @method static int loadFile(string $file)
* @method void disconnect()
* @method Dibi\Result query(...$args)
* @method Dibi\Result nativeQuery(...$args)
* @method bool test(...$args)
* @method Dibi\DataSource dataSource(...$args)
* @method Dibi\Row|null fetch(...$args)
* @method array fetchAll(...$args)
* @method mixed fetchSingle(...$args)
* @method array fetchPairs(...$args)
* @method int getAffectedRows()
* @method int getInsertId(string $sequence = null)
* @method void begin(string $savepoint = null)
* @method void commit(string $savepoint = null)
* @method void rollback(string $savepoint = null)
* @method Dibi\Reflection\Database getDatabaseInfo()
* @method Dibi\Fluent command()
* @method Dibi\Fluent select(...$args)
* @method Dibi\Fluent update(string $table, array $args)
* @method Dibi\Fluent insert(string $table, array $args)
* @method Dibi\Fluent delete(string $table)
* @method Dibi\HashMap getSubstitutes()
* @method int loadFile(string $file)
*/
class dibi
{
@@ -44,30 +43,31 @@ class dibi
IDENTIFIER = 'n';
/** version */
public const VERSION = '5.0-dev';
public const
VERSION = '4.0.0';
/** sorting order */
public const
ASC = 'ASC',
DESC = 'DESC';
/** Last SQL command @see dibi::query() */
public static ?string $sql = null;
/** @var string|null Last SQL command @see dibi::query() */
public static $sql;
/** Elapsed time for last query */
public static ?float $elapsedTime = null;
/** @var float|null Elapsed time for last query */
public static $elapsedTime;
/** Elapsed time for all queries */
public static float $totalTime = 0;
/** @var float Elapsed time for all queries */
public static $totalTime;
/** Number or queries */
public static int $numOfQueries = 0;
/** @var int Number or queries */
public static $numOfQueries = 0;
/** @var Dibi\Connection[] Connection registry storage for Dibi\Connection objects */
private static array $registry = [];
private static $registry = [];
/** Current connection */
private static Dibi\Connection $connection;
/** @var Dibi\Connection Current connection */
private static $connection;
/**
@@ -75,7 +75,7 @@ class dibi
*/
final public function __construct()
{
throw new LogicException('Cannot instantiate static class ' . static::class);
throw new LogicException('Cannot instantiate static class ' . get_class($this));
}
@@ -87,7 +87,7 @@ class dibi
* @param array $config connection parameters
* @throws Dibi\Exception
*/
public static function connect(array $config = [], string $name = '0'): Dibi\Connection
public static function connect($config = [], string $name = '0'): Dibi\Connection
{
return self::$connection = self::$registry[$name] = new Dibi\Connection($config, $name);
}
@@ -106,7 +106,7 @@ class dibi
* Retrieve active connection.
* @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 (self::$connection === null) {
@@ -145,14 +145,35 @@ 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**/
/**
* Prints out a syntax highlighted version of the SQL command or Result.
* @param string|Dibi\Result $sql
* @param bool $return return output instead of printing it?
*/
public static function dump(string|Dibi\Result|null $sql = null, bool $return = false): ?string
public static function dump($sql = null, bool $return = false): ?string
{
return Dibi\Helpers::dump($sql, $return);
}
@@ -161,9 +182,9 @@ class dibi
/**
* Strips microseconds part.
*/
public static function stripMicroseconds(DateTimeInterface $dt): DateTimeInterface
public static function stripMicroseconds(\DateTimeInterface $dt): \DateTimeInterface
{
$class = $dt::class;
$class = get_class($dt);
return new $class($dt->format('Y-m-d H:i:s'), $dt->getTimezone());
}
}

View File

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

View File

@@ -51,24 +51,25 @@ interface Driver
* Begins a transaction (if supported).
* @throws DriverException
*/
function begin(?string $savepoint = null): void;
function begin(string $savepoint = null): void;
/**
* Commits statements in a transaction.
* @throws DriverException
*/
function commit(?string $savepoint = null): void;
function commit(string $savepoint = null): void;
/**
* Rollback changes in a transaction.
* @throws DriverException
*/
function rollback(?string $savepoint = null): void;
function rollback(string $savepoint = null): void;
/**
* Returns the connection resource.
* @return mixed
*/
function getResource(): mixed;
function getResource();
/**
* Returns the connection reflector.
@@ -86,11 +87,15 @@ interface Driver
function escapeBool(bool $value): string;
function escapeDate(\DateTimeInterface $value): string;
/**
* @param \DateTimeInterface|string|int $value
*/
function escapeDate($value): string;
function escapeDateTime(\DateTimeInterface $value): string;
function escapeDateInterval(\DateInterval $value): string;
/**
* @param \DateTimeInterface|string|int $value
*/
function escapeDateTime($value): string;
/**
* Encodes string for use in a LIKE statement.
@@ -116,6 +121,7 @@ interface ResultDriver
/**
* Moves cursor position without fetching row.
* @return bool true on success, false if unable to seek to specified record
* @throws Exception
*/
function seek(int $row): bool;
@@ -140,8 +146,9 @@ interface ResultDriver
/**
* Returns the result set resource.
* @return mixed
*/
function getResultResource(): mixed;
function getResultResource();
/**
* Decodes data from result set.
@@ -221,20 +228,20 @@ interface IConnection
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
* @throws Exception
*/
function getInsertId(?string $sequence = null): int;
function getInsertId(string $sequence = null): int;
/**
* Begins a transaction (if supported).
*/
function begin(?string $savepoint = null): void;
function begin(string $savepoint = null): void;
/**
* Commits statements in a transaction.
*/
function commit(?string $savepoint = null): void;
function commit(string $savepoint = null): void;
/**
* 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
service_name: github-actions
service_name: travis-ci
coverage_clover: coverage.xml
json_path: coverage.json

View File

@@ -0,0 +1,7 @@
includes:
- ../temp/coding-standard/coding-standard-php71.neon
parameters:
skip:
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer:
- src/Dibi/HashMap.php # issue #260

View File

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

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

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

View File

@@ -0,0 +1,38 @@
[sqlite] ; default
driver = sqlite3
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';
test('', function () use ($config) {
test(function () use ($config) {
$conn = new Connection($config);
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]);
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);
Assert::true($conn->isConnected());
@@ -40,7 +40,7 @@ test('', function () use ($config) {
});
test('', function () use ($config) {
test(function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
@@ -50,47 +50,3 @@ test('', function () use ($config) {
$conn->disconnect();
Assert::false($conn->isConnected());
});
test('', function () use ($config) {
$conn = new Connection($config);
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
$conn->disconnect();
$conn->connect();
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
});
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) {
new Connection($config + ['onConnect' => '']);
}, InvalidArgumentException::class, "Configuration option 'onConnect' must be array.");
$e = Assert::exception(function () use ($config) {
new Connection($config + ['onConnect' => ['STOP']]);
}, Dibi\DriverException::class);
Assert::same('STOP', $e->getSql());
$e = Assert::exception(function () use ($config) {
new Connection($config + ['onConnect' => [['STOP %i', 123]]]);
}, Dibi\DriverException::class);
Assert::same('STOP 123', $e->getSql());
// lazy
$conn = new Connection($config + ['lazy' => true, 'onConnect' => ['STOP']]);
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT 1');
}, Dibi\DriverException::class);
Assert::same('STOP', $e->getSql());
});

View File

@@ -18,6 +18,9 @@ $conn->loadFile(__DIR__ . "/data/$config[system].sql");
// fetch a single value
$res = $conn->query('SELECT [title] FROM [products] ORDER BY [product_id]');
Assert::same('Chair', $res->fetchSingle());
Assert::same('Table', $res->fetchSingle());
Assert::same('Computer', $res->fetchSingle());
Assert::false($res->fetchSingle());
// fetch complete result set
@@ -33,13 +36,13 @@ Assert::equal([
$res = $conn->query('SELECT * FROM [products] ORDER BY product_id');
Assert::same(
[1 => 'Chair', 'Table', 'Computer'],
$res->fetchPairs('product_id', 'title'),
$res->fetchPairs('product_id', 'title')
);
$res = $conn->query('SELECT * FROM [products] ORDER BY product_id');
Assert::same(
[1 => 'Chair', 'Table', 'Computer'],
$res->fetchPairs(),
$res->fetchPairs()
);

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,10 +10,12 @@ require __DIR__ . '/bootstrap.php';
date_default_timezone_set('Europe/Prague');
Assert::same('1978-01-23 11:40:00.000000', (string) new DateTime(254_400_000));
Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp(254_400_000));
Assert::same(254_400_000, (new DateTime(254_400_000))->getTimestamp());
Assert::same('1978-01-23 11:40:00.000000', (string) new DateTime(254400000));
Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp(254400000));
Assert::same(254400000, (new DateTime(254400000))->getTimestamp());
Assert::same(is_int(2_544_000_000) ? 2_544_000_000 : '2544000000', (new DateTime(2_544_000_000))->getTimestamp()); // 64 bit
Assert::same('2050-08-13 11:40:00.000000', (string) new DateTime(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('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
declare(strict_types=1);
use Dibi\Fluent;

View File

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

View File

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

View File

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

View File

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

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