mirror of
https://github.com/dg/dibi.git
synced 2025-09-01 18:12:51 +02:00
Compare commits
145 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c8457ab58a | ||
|
cb0cf4ba2f | ||
|
8e7df8374b | ||
|
848ac76fed | ||
|
a0f2ca2fca | ||
|
cf14987b42 | ||
|
01c7ab63e3 | ||
|
520119740d | ||
|
7fa05f381b | ||
|
3a962de553 | ||
|
96e370b8fe | ||
|
ec0455ae00 | ||
|
b61737311e | ||
|
9d5d430d3d | ||
|
04bb5ede3d | ||
|
7d82ce2ff6 | ||
|
82150d120d | ||
|
0f045c0986 | ||
|
5646884899 | ||
|
af33a354d6 | ||
|
a0c86747dc | ||
|
d70e274244 | ||
|
e05eb01233 | ||
|
2ac618ffff | ||
|
1881fea0e5 | ||
|
cb82357cfb | ||
|
0a29fcb502 | ||
|
8270b7c1c3 | ||
|
73e16eb1a3 | ||
|
0b394a993d | ||
|
df3edee70b | ||
|
245da39a9f | ||
|
3df64fc3b3 | ||
|
95c3f72a17 | ||
|
d71caf0c75 | ||
|
3066fea2aa | ||
|
b00e556289 | ||
|
877dffd460 | ||
|
7049949b14 | ||
|
771e846a62 | ||
|
4e056c52dd | ||
|
40ad77cf5f | ||
|
d09b462eef | ||
|
4c796a0e0f | ||
|
304af5185d | ||
|
e046935137 | ||
|
1a77048225 | ||
|
ca74488636 | ||
|
07f994a0b5 | ||
|
5fa5acb724 | ||
|
98563d8165 | ||
|
c464960239 | ||
|
a9e90d0b22 | ||
|
decb30de1e | ||
|
f4e71e8855 | ||
|
39f59e0f08 | ||
|
0d2f643795 | ||
|
70d4246866 | ||
|
34a1665915 | ||
|
b27db4a9aa | ||
|
212dd1ae55 | ||
|
b9683f8a3c | ||
|
177a800bff | ||
|
fa6a1203a9 | ||
|
3f7171c7a4 | ||
|
e4b6e769ee | ||
|
24d0b069d8 | ||
|
34bc742245 | ||
|
f5fa2255ff | ||
|
7b02296f3e | ||
|
df4cddac1f | ||
|
cc37121390 | ||
|
a95b409231 | ||
|
3b057c2e35 | ||
|
f444b5d993 | ||
|
6e41c4223b | ||
|
0ee4628712 | ||
|
ab3677203c | ||
|
1bdf6e93d0 | ||
|
ed2a827419 | ||
|
e46be6cee6 | ||
|
0f69d5d32c | ||
|
e826e3a719 | ||
|
6eac117f5f | ||
|
2a2c814b0a | ||
|
dfab3d711c | ||
|
34e16031f7 | ||
|
73160e9418 | ||
|
f18056a066 | ||
|
0bd222b3f1 | ||
|
9f71f39470 | ||
|
0b0d805040 | ||
|
8c761eac5c | ||
|
2f857c28d6 | ||
|
efe1cbdc20 | ||
|
21dad1d846 | ||
|
7d55fd03b0 | ||
|
294787a26e | ||
|
9d4bef53d3 | ||
|
1db63d81e9 | ||
|
c7dee4d822 | ||
|
f2927a1b08 | ||
|
b5a66fdb26 | ||
|
c38f6991b0 | ||
|
faab306418 | ||
|
74ba6cfd34 | ||
|
f46b7f4d79 | ||
|
c1640c5e7b | ||
|
a2afac80f2 | ||
|
0535d57e6b | ||
|
369768a62a | ||
|
78d6603bb0 | ||
|
7f22279333 | ||
|
e66cb84cb5 | ||
|
5ab8afc704 | ||
|
219882a962 | ||
|
7e127f5914 | ||
|
79f841ec90 | ||
|
69eaa71fde | ||
|
f895493016 | ||
|
19172c801e | ||
|
2b5683d0f2 | ||
|
76593b7da4 | ||
|
dd2fd654be | ||
|
12cbbb3140 | ||
|
811974139e | ||
|
7a49609468 | ||
|
89987f0cee | ||
|
b7e467ecac | ||
|
fe0e7510af | ||
|
eaf2494d90 | ||
|
168971292d | ||
|
95c424a71d | ||
|
4b85f0a973 | ||
|
e7539102cb | ||
|
0ad2dd70bc | ||
|
4abe874ce9 | ||
|
15df96bb22 | ||
|
2870fb9b31 | ||
|
c8dfb1f863 | ||
|
9840c31995 | ||
|
25fda3f8f1 | ||
|
38128fbf9e | ||
|
73790f4321 | ||
|
3930dafe3f |
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -1,6 +1,10 @@
|
|||||||
.gitattributes export-ignore
|
.gitattributes export-ignore
|
||||||
.gitignore export-ignore
|
.gitignore export-ignore
|
||||||
.github export-ignore
|
.github export-ignore
|
||||||
.travis.yml export-ignore
|
|
||||||
appveyor.yml export-ignore
|
appveyor.yml export-ignore
|
||||||
|
ncs.* export-ignore
|
||||||
|
phpstan.neon export-ignore
|
||||||
tests/ export-ignore
|
tests/ export-ignore
|
||||||
|
|
||||||
|
*.sh eol=lf
|
||||||
|
*.php* diff=php linguist-language=PHP
|
||||||
|
4
.github/ISSUE_TEMPLATE/Support_us.md
vendored
4
.github/ISSUE_TEMPLATE/Support_us.md
vendored
@@ -6,14 +6,12 @@ about: "If you would like to support our efforts in maintaining this project
|
|||||||
|
|
||||||
--------------^ Click "Preview" for a nicer view!
|
--------------^ Click "Preview" for a nicer view!
|
||||||
|
|
||||||
> https://nette.org/donate
|
|
||||||
|
|
||||||
Help support Dibi!
|
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.
|
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.
|
To ensure future development and improving the documentation, we need your donation.
|
||||||
|
|
||||||
[Please make a donation now](https://nette.org/donate).
|
**[Please make a donation now](https://nette.org/make-donation?to=dibi)**.
|
||||||
|
|
||||||
Thank you!
|
Thank you!
|
||||||
|
1
.github/funding.yml
vendored
Normal file
1
.github/funding.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
github: dg
|
31
.github/workflows/coding-style.yml
vendored
Normal file
31
.github/workflows/coding-style.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Coding Style
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nette_cc:
|
||||||
|
name: Nette Code Checker
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@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
|
21
.github/workflows/static-analysis.yml
vendored
Normal file
21
.github/workflows/static-analysis.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Static Analysis (only informative)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
phpstan:
|
||||||
|
name: PHPStan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@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
|
121
.github/workflows/tests.yml
vendored
Normal file
121
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
env:
|
||||||
|
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.10.0beta2, pdo_sqlsrv-5.10.0beta2
|
||||||
|
php-tools: "composer:v2, pecl"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
|
||||||
|
|
||||||
|
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
60
.travis.yml
@@ -1,60 +0,0 @@
|
|||||||
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
|
|
10
appveyor.yml
10
appveyor.yml
@@ -15,17 +15,17 @@ init:
|
|||||||
- SET ANSICON=121x90 (121x90)
|
- SET ANSICON=121x90 (121x90)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# Install PHP 7.1
|
# Install PHP 7.2
|
||||||
- IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1)
|
- IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1)
|
||||||
- IF %PHP%==1 mkdir c:\php7
|
- IF %PHP%==1 mkdir c:\php7
|
||||||
- IF %PHP%==1 cd c:\php7
|
- IF %PHP%==1 cd c:\php7
|
||||||
- IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.1.5-Win32-VC14-x64.zip --output php.zip
|
- IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.2.18-Win32-VC15-x64.zip --output php.zip
|
||||||
- IF %PHP%==1 7z x php.zip >nul
|
- IF %PHP%==1 7z x php.zip >nul
|
||||||
- IF %PHP%==1 echo extension_dir=ext >> php.ini
|
- IF %PHP%==1 echo extension_dir=ext >> php.ini
|
||||||
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
|
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
|
||||||
- IF %PHP%==1 appveyor DownloadFile https://files.nette.org/misc/php-sqlsrv.zip
|
- IF %PHP%==1 curl https://github.com/microsoft/msphpsql/releases/download/v5.8.0/Windows-7.2.zip -L --output sqlsrv.zip
|
||||||
- IF %PHP%==1 7z x php-sqlsrv.zip >nul
|
- IF %PHP%==1 7z x sqlsrv.zip >nul
|
||||||
- IF %PHP%==1 copy SQLSRV\php_sqlsrv_71_ts.dll ext\php_sqlsrv_71_ts.dll
|
- IF %PHP%==1 copy Windows-7.2\x64\php_sqlsrv_72_ts.dll ext\php_sqlsrv_ts.dll
|
||||||
- IF %PHP%==1 del /Q *.zip
|
- IF %PHP%==1 del /Q *.zip
|
||||||
|
|
||||||
# Install Microsoft Access Database Engine x64
|
# Install Microsoft Access Database Engine x64
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"description": "Dibi is Database Abstraction Library for PHP",
|
"description": "Dibi is Database Abstraction Library for PHP",
|
||||||
"keywords": ["database", "dbal", "mysql", "postgresql", "sqlite", "mssql", "sqlsrv", "oracle", "access", "pdo", "odbc"],
|
"keywords": ["database", "dbal", "mysql", "postgresql", "sqlite", "mssql", "sqlsrv", "oracle", "access", "pdo", "odbc"],
|
||||||
"homepage": "https://dibiphp.com",
|
"homepage": "https://dibiphp.com",
|
||||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "David Grudl",
|
"name": "David Grudl",
|
||||||
@@ -11,11 +11,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1"
|
"php": ">=7.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"tracy/tracy": "~2.2",
|
"tracy/tracy": "~2.2",
|
||||||
"nette/tester": "~2.0"
|
"nette/tester": "~2.0",
|
||||||
|
"nette/di": "^3.0",
|
||||||
|
"phpstan/phpstan": "^0.12"
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
"dg/dibi": "*"
|
"dg/dibi": "*"
|
||||||
@@ -23,9 +25,13 @@
|
|||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": ["src/"]
|
"classmap": ["src/"]
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"phpstan": "phpstan analyse",
|
||||||
|
"tester": "tester tests -s"
|
||||||
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "4.0-dev"
|
"dev-master": "4.2-dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
|||||||
echo '<p>Connecting to Sqlite: ';
|
echo '<p>Connecting to Sqlite: ';
|
||||||
try {
|
try {
|
||||||
dibi::connect([
|
dibi::connect([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
echo 'OK';
|
echo 'OK';
|
||||||
@@ -30,7 +30,7 @@ echo "</p>\n";
|
|||||||
echo '<p>Connecting to Sqlite: ';
|
echo '<p>Connecting to Sqlite: ';
|
||||||
try {
|
try {
|
||||||
$connection = new Dibi\Connection([
|
$connection = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
echo 'OK';
|
echo 'OK';
|
||||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ Tracy\Debugger::enable();
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague');
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -19,7 +19,7 @@ date_default_timezone_set('Europe/Prague');
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ Tracy\Debugger::enable();
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ Tracy\Debugger::enable();
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ date_default_timezone_set('Europe/Prague');
|
|||||||
|
|
||||||
// CHANGE TO REAL PARAMETERS!
|
// CHANGE TO REAL PARAMETERS!
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
'formatDate' => "'Y-m-d'",
|
'formatDate' => "'Y-m-d'",
|
||||||
'formatDateTime' => "'Y-m-d H-i-s'",
|
'formatDateTime' => "'Y-m-d H-i-s'",
|
||||||
|
@@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague');
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -15,11 +15,12 @@ date_default_timezone_set('Europe/Prague');
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
// enable query logging to this file
|
// enable query logging to this file
|
||||||
'profiler' => [
|
'profiler' => [
|
||||||
'file' => 'log/log.sql',
|
'file' => 'log/log.sql',
|
||||||
|
'errorsOnly' => false,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
|||||||
|
|
||||||
|
|
||||||
$dibi = new Dibi\Connection([
|
$dibi = new Dibi\Connection([
|
||||||
'driver' => 'sqlite3',
|
'driver' => 'sqlite',
|
||||||
'database' => 'data/sample.s3db',
|
'database' => 'data/sample.s3db',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
9
phpstan.neon
Normal file
9
phpstan.neon
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
parameters:
|
||||||
|
level: 5
|
||||||
|
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
|
||||||
|
ignoreErrors:
|
||||||
|
# The namespace is referenced, not the class.
|
||||||
|
- '#Class dibi referenced with incorrect case: Dibi#'
|
64
readme.md
64
readme.md
@@ -1,8 +1,8 @@
|
|||||||
[Dibi](https://dibiphp.com) - smart database layer for PHP [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9XXL5ZJHAYQUN)
|
[Dibi](https://dibiphp.com) - smart database layer for PHP [](https://nette.org/make-donation?to=dibi)
|
||||||
=========================================================
|
=========================================================
|
||||||
|
|
||||||
[](https://packagist.org/packages/dibi/dibi)
|
[](https://packagist.org/packages/dibi/dibi)
|
||||||
[](https://travis-ci.org/dg/dibi)
|
[](https://github.com/dg/dibi/actions)
|
||||||
[](https://ci.appveyor.com/project/dg/dibi/branch/master)
|
[](https://ci.appveyor.com/project/dg/dibi/branch/master)
|
||||||
[](https://github.com/dg/dibi/releases)
|
[](https://github.com/dg/dibi/releases)
|
||||||
[](https://github.com/dg/dibi/blob/master/license.md)
|
[](https://github.com/dg/dibi/blob/master/license.md)
|
||||||
@@ -15,6 +15,16 @@ Database access functions in PHP are not standardised. This library
|
|||||||
hides the differences between them, and above all, it gives you a very handy interface.
|
hides the differences between them, and above all, it gives you a very handy interface.
|
||||||
|
|
||||||
|
|
||||||
|
Support Me
|
||||||
|
----------
|
||||||
|
|
||||||
|
Do you like Dibi? Are you looking forward to the new features?
|
||||||
|
|
||||||
|
[](https://github.com/sponsors/dg)
|
||||||
|
|
||||||
|
Thank you!
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@@ -24,7 +34,7 @@ Install Dibi via Composer:
|
|||||||
composer require dibi/dibi
|
composer require dibi/dibi
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
The Dibi 4.2 requires PHP version 7.2 and supports PHP up to 8.3.
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
@@ -40,11 +50,11 @@ The database connection is represented by the object `Dibi\Connection`:
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
$database = new Dibi\Connection([
|
$database = new Dibi\Connection([
|
||||||
'driver' => 'mysqli',
|
'driver' => 'mysqli',
|
||||||
'host' => 'localhost',
|
'host' => 'localhost',
|
||||||
'username' => 'root',
|
'username' => 'root',
|
||||||
'password' => '***',
|
'password' => '***',
|
||||||
'database' => 'table',
|
'database' => 'table',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$result = $database->query('SELECT * FROM users');
|
$result = $database->query('SELECT * FROM users');
|
||||||
@@ -54,12 +64,12 @@ Alternatively, you can use the `dibi` static register, which maintains a connect
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
dibi::connect([
|
dibi::connect([
|
||||||
'driver' => 'mysqli',
|
'driver' => 'mysqli',
|
||||||
'host' => 'localhost',
|
'host' => 'localhost',
|
||||||
'username' => 'root',
|
'username' => 'root',
|
||||||
'password' => '***',
|
'password' => '***',
|
||||||
'database' => 'test',
|
'database' => 'test',
|
||||||
'charset' => 'utf8',
|
'charset' => 'utf8',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$result = dibi::query('SELECT * FROM users');
|
$result = dibi::query('SELECT * FROM users');
|
||||||
@@ -73,6 +83,8 @@ In the event of a connection error, it throws `Dibi\Exception`.
|
|||||||
|
|
||||||
We query the database queries by the method `query()` which returns `Dibi\Result`. Rows are objects `Dibi\Row`.
|
We query the database queries by the method `query()` which returns `Dibi\Result`. Rows are objects `Dibi\Row`.
|
||||||
|
|
||||||
|
You can try all the examples [online at the playground](https://repl.it/@DavidGrudl/dibi-playground).
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$result = $database->query('SELECT * FROM users');
|
$result = $database->query('SELECT * FROM users');
|
||||||
|
|
||||||
@@ -108,7 +120,7 @@ $ids = [10, 20, 30];
|
|||||||
$result = $database->query('SELECT * FROM users WHERE id IN (?)', $ids);
|
$result = $database->query('SELECT * FROM users WHERE id IN (?)', $ids);
|
||||||
```
|
```
|
||||||
|
|
||||||
**WARNING, never concencate parameters to SQL, the vulnerability would arise [SQL injection](https://en.wikipedia.org/wiki/SQL_injection)**
|
**WARNING: Never concatenate parameters to SQL. It would create a [SQL injection](https://en.wikipedia.org/wiki/SQL_injection)** vulnerability.
|
||||||
```
|
```
|
||||||
$result = $database->query('SELECT * FROM users WHERE id = ' . $id); // BAD!!!
|
$result = $database->query('SELECT * FROM users WHERE id = ' . $id); // BAD!!!
|
||||||
```
|
```
|
||||||
@@ -145,7 +157,7 @@ $name = $database->fetchSingle('SELECT name FROM users WHERE id = ?', $id);
|
|||||||
|
|
||||||
### Modifiers
|
### Modifiers
|
||||||
|
|
||||||
In addition to the `?` wild char, we can also use modifiers:
|
In addition to the `?` wildcard char, we can also use modifiers:
|
||||||
|
|
||||||
| modifier | description
|
| modifier | description
|
||||||
|----------|-----
|
|----------|-----
|
||||||
@@ -159,6 +171,7 @@ In addition to the `?` wild char, we can also use modifiers:
|
|||||||
| %d | date (accepts DateTime, string or UNIX timestamp)
|
| %d | date (accepts DateTime, string or UNIX timestamp)
|
||||||
| %dt | datetime (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, 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)
|
| %SQL | SQL - directly inserts into SQL (the alternative is Dibi\Literal)
|
||||||
| %ex | SQL expression or array of expressions
|
| %ex | SQL expression or array of expressions
|
||||||
| %lmt | special - adds LIMIT to the query
|
| %lmt | special - adds LIMIT to the query
|
||||||
@@ -180,7 +193,7 @@ $result = $database->query('SELECT * FROM users WHERE id IN (%i)', $ids);
|
|||||||
// SELECT * FROM users WHERE id IN (10, 20, 30)
|
// SELECT * FROM users WHERE id IN (10, 20, 30)
|
||||||
```
|
```
|
||||||
|
|
||||||
The modifier '%n' is used if the table or column name is a variable. (Beware, do not allow the user to manipulate the content of such a variable):
|
The modifier `%n` is used if the table or column name is a variable. (Beware, do not allow the user to manipulate the content of such a variable):
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$table = 'blog.users';
|
$table = 'blog.users';
|
||||||
@@ -196,6 +209,7 @@ Three special modifiers are available for LIKE:
|
|||||||
| `%like~` | the expression starts with a string
|
| `%like~` | the expression starts with a string
|
||||||
| `%~like` | the expression ends with a string
|
| `%~like` | the expression ends with a string
|
||||||
| `%~like~` | the expression contains a string
|
| `%~like~` | the expression contains a string
|
||||||
|
| `%like` | the expression matches a string
|
||||||
|
|
||||||
Search for names beginning with a string:
|
Search for names beginning with a string:
|
||||||
|
|
||||||
@@ -223,8 +237,8 @@ Example:
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
$arr = [
|
$arr = [
|
||||||
'a' => 'hello',
|
'a' => 'hello',
|
||||||
'b' => true,
|
'b' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
$database->query('INSERT INTO table %v', $arr);
|
$database->query('INSERT INTO table %v', $arr);
|
||||||
@@ -327,7 +341,7 @@ $database->query('INSERT INTO users', [
|
|||||||
There are three methods for dealing with transactions:
|
There are three methods for dealing with transactions:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$database->beginTransaction();
|
$database->begin();
|
||||||
|
|
||||||
$database->commit();
|
$database->commit();
|
||||||
|
|
||||||
@@ -496,7 +510,7 @@ $all = $result->fetchAssoc('customer_id|order_id');
|
|||||||
// we will iterate like this:
|
// we will iterate like this:
|
||||||
foreach ($all as $customerId => $orders) {
|
foreach ($all as $customerId => $orders) {
|
||||||
foreach ($orders as $orderId => $order) {
|
foreach ($orders as $orderId => $order) {
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -528,7 +542,7 @@ $all = $result->fetchAssoc('name[]order_id');
|
|||||||
// we get all the Arnolds in the results
|
// we get all the Arnolds in the results
|
||||||
foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
|
foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
|
||||||
foreach ($arnoldOrders as $orderId => $order) {
|
foreach ($arnoldOrders as $orderId => $order) {
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -542,8 +556,8 @@ foreach ($all as $customerId => $orders) {
|
|||||||
echo "Customer $customerId":
|
echo "Customer $customerId":
|
||||||
|
|
||||||
foreach ($orders as $orderId => $order) {
|
foreach ($orders as $orderId => $order) {
|
||||||
echo "ID number: $order->number";
|
echo "ID number: $order->number";
|
||||||
// customer name is in $order->name
|
// customer name is in $order->name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -565,7 +579,7 @@ foreach ($all as $customerId => $row) {
|
|||||||
echo "Customer $row->name":
|
echo "Customer $row->name":
|
||||||
|
|
||||||
foreach ($row->order_id as $orderId => $order) {
|
foreach ($row->order_id as $orderId => $order) {
|
||||||
echo "ID number: $order->number";
|
echo "ID number: $order->number";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@@ -22,10 +22,14 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
|
|||||||
/** @var bool|null */
|
/** @var bool|null */
|
||||||
private $debugMode;
|
private $debugMode;
|
||||||
|
|
||||||
|
/** @var bool|null */
|
||||||
|
private $cliMode;
|
||||||
|
|
||||||
public function __construct(bool $debugMode = null)
|
|
||||||
|
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
|
||||||
{
|
{
|
||||||
$this->debugMode = $debugMode;
|
$this->debugMode = $debugMode;
|
||||||
|
$this->cliMode = $cliMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +42,11 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
|
|||||||
$this->debugMode = $container->parameters['debugMode'];
|
$this->debugMode = $container->parameters['debugMode'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode);
|
if ($this->cliMode === null) {
|
||||||
|
$this->cliMode = $container->parameters['consoleMode'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode && !$this->cliMode);
|
||||||
|
|
||||||
unset($config['profiler']);
|
unset($config['profiler']);
|
||||||
|
|
||||||
@@ -47,11 +55,12 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
|
|||||||
foreach ((array) $config['flags'] as $flag) {
|
foreach ((array) $config['flags'] as $flag) {
|
||||||
$flags |= constant($flag);
|
$flags |= constant($flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
$config['flags'] = $flags;
|
$config['flags'] = $flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
$connection = $container->addDefinition($this->prefix('connection'))
|
$connection = $container->addDefinition($this->prefix('connection'))
|
||||||
->setClass(Dibi\Connection::class, [$config])
|
->setFactory(Dibi\Connection::class, [$config])
|
||||||
->setAutowired($config['autowired'] ?? true);
|
->setAutowired($config['autowired'] ?? true);
|
||||||
|
|
||||||
if (class_exists(Tracy\Debugger::class)) {
|
if (class_exists(Tracy\Debugger::class)) {
|
||||||
@@ -60,9 +69,10 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
|
|||||||
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']]
|
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($useProfiler) {
|
if ($useProfiler) {
|
||||||
$panel = $container->addDefinition($this->prefix('panel'))
|
$panel = $container->addDefinition($this->prefix('panel'))
|
||||||
->setClass(Dibi\Bridges\Tracy\Panel::class, [
|
->setFactory(Dibi\Bridges\Tracy\Panel::class, [
|
||||||
$config['explain'] ?? true,
|
$config['explain'] ?? true,
|
||||||
isset($config['filter']) && $config['filter'] === false ? Dibi\Event::ALL : Dibi\Event::QUERY,
|
isset($config['filter']) && $config['filter'] === false ? Dibi\Event::ALL : Dibi\Event::QUERY,
|
||||||
]);
|
]);
|
||||||
|
@@ -35,7 +35,7 @@ class Panel implements Tracy\IBarPanel
|
|||||||
private $events = [];
|
private $events = [];
|
||||||
|
|
||||||
|
|
||||||
public function __construct($explain = true, int $filter = null)
|
public function __construct($explain = true, ?int $filter = null)
|
||||||
{
|
{
|
||||||
$this->filter = $filter ?: Event::QUERY;
|
$this->filter = $filter ?: Event::QUERY;
|
||||||
$this->explain = $explain;
|
$this->explain = $explain;
|
||||||
@@ -45,7 +45,7 @@ class Panel implements Tracy\IBarPanel
|
|||||||
public function register(Dibi\Connection $connection): void
|
public function register(Dibi\Connection $connection): void
|
||||||
{
|
{
|
||||||
Tracy\Debugger::getBar()->addPanel($this);
|
Tracy\Debugger::getBar()->addPanel($this);
|
||||||
Tracy\Debugger::getBlueScreen()->addPanel([__CLASS__, 'renderException']);
|
Tracy\Debugger::getBlueScreen()->addPanel([self::class, 'renderException']);
|
||||||
$connection->onEvent[] = [$this, 'logEvent'];
|
$connection->onEvent[] = [$this, 'logEvent'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +58,7 @@ class Panel implements Tracy\IBarPanel
|
|||||||
if (($event->type & $this->filter) === 0) {
|
if (($event->type & $this->filter) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->events[] = $event;
|
$this->events[] = $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,9 +89,10 @@ class Panel implements Tracy\IBarPanel
|
|||||||
foreach ($this->events as $event) {
|
foreach ($this->events as $event) {
|
||||||
$totalTime += $event->time;
|
$totalTime += $event->time;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<span title="dibi"><svg viewBox="0 0 2048 2048" style="vertical-align: bottom; width:1.23em; height:1.55em"><path fill="' . ($count ? '#b079d6' : '#aaa') . '" d="M1024 896q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-1152q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-128q0-69 103-128t280-93.5 385-34.5z"/></svg><span class="tracy-label">'
|
return '<span title="dibi"><svg viewBox="0 0 2048 2048" style="vertical-align: bottom; width:1.23em; height:1.55em"><path fill="' . ($count ? '#b079d6' : '#aaa') . '" d="M1024 896q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-1152q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-128q0-69 103-128t280-93.5 385-34.5z"/></svg><span class="tracy-label">'
|
||||||
. $count . ' queries'
|
. $count . "\u{a0}queries"
|
||||||
. ($totalTime ? ' / ' . number_format($totalTime * 1000, 1, '.', ' ') . ' ms' : '')
|
. ($totalTime ? ' / ' . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms" : '')
|
||||||
. '</span></span>';
|
. '</span></span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +107,15 @@ class Panel implements Tracy\IBarPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
$totalTime = $s = null;
|
$totalTime = $s = null;
|
||||||
|
|
||||||
|
$singleConnection = reset($this->events)->connection;
|
||||||
|
foreach ($this->events as $event) {
|
||||||
|
if ($event->connection !== $singleConnection) {
|
||||||
|
$singleConnection = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->events as $event) {
|
foreach ($this->events as $event) {
|
||||||
$totalTime += $event->time;
|
$totalTime += $event->time;
|
||||||
$connection = $event->connection;
|
$connection = $event->connection;
|
||||||
@@ -112,15 +123,18 @@ class Panel implements Tracy\IBarPanel
|
|||||||
if ($this->explain && $event->type === Event::SELECT) {
|
if ($this->explain && $event->type === Event::SELECT) {
|
||||||
$backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime];
|
$backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime];
|
||||||
$connection->onEvent = null;
|
$connection->onEvent = null;
|
||||||
$cmd = is_string($this->explain) ? $this->explain : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
|
$cmd = is_string($this->explain)
|
||||||
|
? $this->explain
|
||||||
|
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
|
||||||
try {
|
try {
|
||||||
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
|
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
|
||||||
} catch (Dibi\Exception $e) {
|
} catch (Dibi\Exception $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
[$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup;
|
[$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup;
|
||||||
}
|
}
|
||||||
|
|
||||||
$s .= '<tr><td>' . number_format($event->time * 1000, 3, '.', ' ');
|
$s .= '<tr><td data-order="' . $event->time . '">' . number_format($event->time * 1000, 3, '.', "\u{202f}");
|
||||||
if ($explain) {
|
if ($explain) {
|
||||||
static $counter;
|
static $counter;
|
||||||
$counter++;
|
$counter++;
|
||||||
@@ -131,24 +145,37 @@ class Panel implements Tracy\IBarPanel
|
|||||||
if ($explain) {
|
if ($explain) {
|
||||||
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
|
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($event->source) {
|
if ($event->source) {
|
||||||
$s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]);//->class('tracy-DibiProfiler-source');
|
$s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]); //->class('tracy-DibiProfiler-source');
|
||||||
}
|
}
|
||||||
|
|
||||||
$s .= "</td><td>{$event->count}</td></tr>";
|
$s .= "</td><td>{$event->count}</td>";
|
||||||
|
if (!$singleConnection) {
|
||||||
|
$s .= '<td>' . htmlspecialchars($this->getConnectionName($connection)) . '</td></tr>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important }
|
return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important }
|
||||||
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
|
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
|
||||||
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
|
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
|
||||||
<h1>Queries: ' . count($this->events)
|
<h1>Queries:' . "\u{a0}" . count($this->events)
|
||||||
. ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', ' ') . ' ms') . ', '
|
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', '
|
||||||
. htmlspecialchars($connection->getConfig('driver') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
|
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
|
||||||
. ($connection->getConfig('host') ? ' @ ' . $connection->getConfig('host') : '')) . '</h1>
|
|
||||||
<div class="tracy-inner tracy-DibiProfiler">
|
<div class="tracy-inner tracy-DibiProfiler">
|
||||||
<table>
|
<table class="tracy-sortable">
|
||||||
<tr><th>Time ms</th><th>SQL Statement</th><th>Rows</th></tr>' . $s . '
|
<tr><th>Time ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>
|
||||||
|
' . $s . '
|
||||||
</table>
|
</table>
|
||||||
</div>';
|
</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getConnectionName(Dibi\Connection $connection): string
|
||||||
|
{
|
||||||
|
$driver = $connection->getConfig('driver');
|
||||||
|
return (is_object($driver) ? get_class($driver) : $driver)
|
||||||
|
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
|
||||||
|
. ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,9 @@ class Connection implements IConnection
|
|||||||
/** @var array Current connection configuration */
|
/** @var array Current connection configuration */
|
||||||
private $config;
|
private $config;
|
||||||
|
|
||||||
|
/** @var string[] resultset formats */
|
||||||
|
private $formats;
|
||||||
|
|
||||||
/** @var Driver|null */
|
/** @var Driver|null */
|
||||||
private $driver;
|
private $driver;
|
||||||
|
|
||||||
@@ -37,37 +40,36 @@ class Connection implements IConnection
|
|||||||
/** @var HashMap Substitutes for identifiers */
|
/** @var HashMap Substitutes for identifiers */
|
||||||
private $substitutes;
|
private $substitutes;
|
||||||
|
|
||||||
|
private $transactionDepth = 0;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection options: (see driver-specific options too)
|
* Connection options: (see driver-specific options too)
|
||||||
* - lazy (bool) => if true, connection will be established only when required
|
* - lazy (bool) => if true, connection will be established only when required
|
||||||
* - result (array) => result set options
|
* - result (array) => result set options
|
||||||
* - formatDateTime => date-time format (if empty, DateTime objects will be returned)
|
* - normalize => normalizes result fields (default: true)
|
||||||
|
* - formatDateTime => date-time format
|
||||||
|
* empty for decoding as Dibi\DateTime (default)
|
||||||
|
* "..." formatted according to given format, see https://www.php.net/manual/en/datetime.format.php
|
||||||
|
* "native" for leaving value as is
|
||||||
|
* - formatTimeInterval => time-interval format
|
||||||
|
* empty for decoding as DateInterval (default)
|
||||||
|
* "..." formatted according to given format, see https://www.php.net/manual/en/dateinterval.format.php
|
||||||
|
* "native" for leaving value as is
|
||||||
|
* - formatJson => json format
|
||||||
|
* "array" for decoding json as an array (default)
|
||||||
|
* "object" for decoding json as \stdClass
|
||||||
|
* "native" for leaving value as is
|
||||||
* - profiler (array)
|
* - profiler (array)
|
||||||
* - run (bool) => enable profiler?
|
* - run (bool) => enable profiler?
|
||||||
* - file => file to log
|
* - file => file to log
|
||||||
|
* - errorsOnly (bool) => log only errors
|
||||||
* - substitutes (array) => map of driver specific substitutes (under development)
|
* - substitutes (array) => map of driver specific substitutes (under development)
|
||||||
* @param array $config connection parameters
|
* - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function __construct($config, string $name = null)
|
public function __construct(array $config, ?string $name = null)
|
||||||
{
|
{
|
||||||
if (is_string($config)) {
|
|
||||||
trigger_error(__METHOD__ . '() Configuration should be array.', E_USER_DEPRECATED);
|
|
||||||
parse_str($config, $config);
|
|
||||||
|
|
||||||
} elseif ($config instanceof Traversable) {
|
|
||||||
trigger_error(__METHOD__ . '() Configuration should be array.', E_USER_DEPRECATED);
|
|
||||||
$tmp = [];
|
|
||||||
foreach ($config as $key => $val) {
|
|
||||||
$tmp[$key] = $val instanceof Traversable ? iterator_to_array($val) : $val;
|
|
||||||
}
|
|
||||||
$config = $tmp;
|
|
||||||
|
|
||||||
} elseif (!is_array($config)) {
|
|
||||||
throw new \InvalidArgumentException('Configuration must be array.');
|
|
||||||
}
|
|
||||||
|
|
||||||
Helpers::alias($config, 'username', 'user');
|
Helpers::alias($config, 'username', 'user');
|
||||||
Helpers::alias($config, 'password', 'pass');
|
Helpers::alias($config, 'password', 'pass');
|
||||||
Helpers::alias($config, 'host', 'hostname');
|
Helpers::alias($config, 'host', 'hostname');
|
||||||
@@ -77,10 +79,18 @@ class Connection implements IConnection
|
|||||||
$config['name'] = $name;
|
$config['name'] = $name;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
|
||||||
|
$this->formats = [
|
||||||
|
Type::DATE => $this->config['result']['formatDate'],
|
||||||
|
Type::DATETIME => $this->config['result']['formatDateTime'],
|
||||||
|
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
|
||||||
|
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
// profiler
|
// profiler
|
||||||
if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) {
|
if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) {
|
||||||
$filter = $config['profiler']['filter'] ?? Event::QUERY;
|
$filter = $config['profiler']['filter'] ?? Event::QUERY;
|
||||||
$this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter), 'logEvent'];
|
$errorsOnly = $config['profiler']['errorsOnly'] ?? false;
|
||||||
|
$this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter, $errorsOnly), 'logEvent'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->substitutes = new HashMap(function (string $expr) { return ":$expr:"; });
|
$this->substitutes = new HashMap(function (string $expr) { return ":$expr:"; });
|
||||||
@@ -90,6 +100,10 @@ class Connection implements IConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($config['onConnect']) && !is_array($config['onConnect'])) {
|
||||||
|
throw new \InvalidArgumentException("Configuration option 'onConnect' must be array.");
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($config['lazy'])) {
|
if (empty($config['lazy'])) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
@@ -112,8 +126,14 @@ class Connection implements IConnection
|
|||||||
*/
|
*/
|
||||||
final public function connect(): void
|
final public function connect(): void
|
||||||
{
|
{
|
||||||
if (is_subclass_of($this->config['driver'], Driver::class)) {
|
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)) {
|
||||||
$class = $this->config['driver'];
|
$class = $this->config['driver'];
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($this->config['driver'])));
|
$class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($this->config['driver'])));
|
||||||
$class = "Dibi\\Drivers\\{$class}Driver";
|
$class = "Dibi\\Drivers\\{$class}Driver";
|
||||||
@@ -125,14 +145,22 @@ class Connection implements IConnection
|
|||||||
$event = $this->onEvent ? new Event($this, Event::CONNECT) : null;
|
$event = $this->onEvent ? new Event($this, Event::CONNECT) : null;
|
||||||
try {
|
try {
|
||||||
$this->driver = new $class($this->config);
|
$this->driver = new $class($this->config);
|
||||||
|
$this->translator = new Translator($this);
|
||||||
|
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done());
|
$this->onEvent($event->done());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($this->config['onConnect'])) {
|
||||||
|
foreach ($this->config['onConnect'] as $sql) {
|
||||||
|
$this->query($sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (DriverException $e) {
|
} catch (DriverException $e) {
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done($e));
|
$this->onEvent($event->done($e));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +173,7 @@ class Connection implements IConnection
|
|||||||
{
|
{
|
||||||
if ($this->driver) {
|
if ($this->driver) {
|
||||||
$this->driver->disconnect();
|
$this->driver->disconnect();
|
||||||
$this->driver = null;
|
$this->driver = $this->translator = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +192,7 @@ class Connection implements IConnection
|
|||||||
* @see self::__construct
|
* @see self::__construct
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
final public function getConfig(string $key = null, $default = null)
|
final public function getConfig(?string $key = null, $default = null)
|
||||||
{
|
{
|
||||||
return $key === null
|
return $key === null
|
||||||
? $this->config
|
? $this->config
|
||||||
@@ -180,6 +208,7 @@ class Connection implements IConnection
|
|||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->driver;
|
return $this->driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +220,7 @@ class Connection implements IConnection
|
|||||||
*/
|
*/
|
||||||
final public function query(...$args): Result
|
final public function query(...$args): Result
|
||||||
{
|
{
|
||||||
return $this->nativeQuery($this->translateArgs($args));
|
return $this->nativeQuery($this->translate(...$args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -202,7 +231,11 @@ class Connection implements IConnection
|
|||||||
*/
|
*/
|
||||||
final public function translate(...$args): string
|
final public function translate(...$args): string
|
||||||
{
|
{
|
||||||
return $this->translateArgs($args);
|
if (!$this->driver) {
|
||||||
|
$this->connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (clone $this->translator)->translate($args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -213,7 +246,7 @@ class Connection implements IConnection
|
|||||||
final public function test(...$args): bool
|
final public function test(...$args): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Helpers::dump($this->translateArgs($args));
|
Helpers::dump($this->translate(...$args));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@@ -222,6 +255,7 @@ class Connection implements IConnection
|
|||||||
} else {
|
} else {
|
||||||
echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
|
echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,23 +268,7 @@ class Connection implements IConnection
|
|||||||
*/
|
*/
|
||||||
final public function dataSource(...$args): DataSource
|
final public function dataSource(...$args): DataSource
|
||||||
{
|
{
|
||||||
return new DataSource($this->translateArgs($args), $this);
|
return new DataSource($this->translate(...$args), $this);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates SQL query.
|
|
||||||
*/
|
|
||||||
protected function translateArgs(array $args): string
|
|
||||||
{
|
|
||||||
if (!$this->driver) {
|
|
||||||
$this->connect();
|
|
||||||
}
|
|
||||||
if (!$this->translator) {
|
|
||||||
$this->translator = new Translator($this);
|
|
||||||
}
|
|
||||||
$translator = clone $this->translator;
|
|
||||||
return $translator->translate($args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -273,6 +291,7 @@ class Connection implements IConnection
|
|||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done($e));
|
$this->onEvent($event->done($e));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +299,7 @@ class Connection implements IConnection
|
|||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done($res));
|
$this->onEvent($event->done($res));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,70 +313,59 @@ class Connection implements IConnection
|
|||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = $this->driver->getAffectedRows();
|
$rows = $this->driver->getAffectedRows();
|
||||||
if ($rows === null || $rows < 0) {
|
if ($rows === null || $rows < 0) {
|
||||||
throw new Exception('Cannot retrieve number of affected rows.');
|
throw new Exception('Cannot retrieve number of affected rows.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public function affectedRows(): int
|
|
||||||
{
|
|
||||||
trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
|
|
||||||
return $this->getAffectedRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function getInsertId(string $sequence = null): int
|
public function getInsertId(?string $sequence = null): int
|
||||||
{
|
{
|
||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = $this->driver->getInsertId($sequence);
|
$id = $this->driver->getInsertId($sequence);
|
||||||
if ($id < 1) {
|
if ($id === null) {
|
||||||
throw new Exception('Cannot retrieve last generated ID.');
|
throw new Exception('Cannot retrieve last generated ID.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $id;
|
return $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public function insertId(string $sequence = null): int
|
|
||||||
{
|
|
||||||
trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
|
|
||||||
return $this->getInsertId($sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
*/
|
*/
|
||||||
public function begin(string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
|
if ($this->transactionDepth !== 0) {
|
||||||
|
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null;
|
$event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null;
|
||||||
try {
|
try {
|
||||||
$this->driver->begin($savepoint);
|
$this->driver->begin($savepoint);
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done());
|
$this->onEvent($event->done());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (DriverException $e) {
|
} catch (DriverException $e) {
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done($e));
|
$this->onEvent($event->done($e));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,22 +374,27 @@ class Connection implements IConnection
|
|||||||
/**
|
/**
|
||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
*/
|
*/
|
||||||
public function commit(string $savepoint = null): void
|
public function commit(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
|
if ($this->transactionDepth !== 0) {
|
||||||
|
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null;
|
$event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null;
|
||||||
try {
|
try {
|
||||||
$this->driver->commit($savepoint);
|
$this->driver->commit($savepoint);
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done());
|
$this->onEvent($event->done());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (DriverException $e) {
|
} catch (DriverException $e) {
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done($e));
|
$this->onEvent($event->done($e));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,35 +403,69 @@ class Connection implements IConnection
|
|||||||
/**
|
/**
|
||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
*/
|
*/
|
||||||
public function rollback(string $savepoint = null): void
|
public function rollback(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
|
if ($this->transactionDepth !== 0) {
|
||||||
|
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null;
|
$event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null;
|
||||||
try {
|
try {
|
||||||
$this->driver->rollback($savepoint);
|
$this->driver->rollback($savepoint);
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done());
|
$this->onEvent($event->done());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (DriverException $e) {
|
} catch (DriverException $e) {
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$this->onEvent($event->done($e));
|
$this->onEvent($event->done($e));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function transaction(callable $callback)
|
||||||
|
{
|
||||||
|
if ($this->transactionDepth === 0) {
|
||||||
|
$this->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->transactionDepth++;
|
||||||
|
try {
|
||||||
|
$res = $callback($this);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->transactionDepth--;
|
||||||
|
if ($this->transactionDepth === 0) {
|
||||||
|
$this->rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->transactionDepth--;
|
||||||
|
if ($this->transactionDepth === 0) {
|
||||||
|
$this->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Result set factory.
|
* Result set factory.
|
||||||
*/
|
*/
|
||||||
public function createResultSet(ResultDriver $resultDriver): Result
|
public function createResultSet(ResultDriver $resultDriver): Result
|
||||||
{
|
{
|
||||||
$res = new Result($resultDriver);
|
return (new Result($resultDriver, $this->config['result']['normalize'] ?? true))
|
||||||
return $res->setFormat(Type::DATE, $this->config['result']['formatDate'])
|
->setFormats($this->formats);
|
||||||
->setFormat(Type::DATETIME, $this->config['result']['formatDateTime']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -436,7 +484,10 @@ class Connection implements IConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function update(string $table, iterable $args): Fluent
|
/**
|
||||||
|
* @param string|string[] $table
|
||||||
|
*/
|
||||||
|
public function update($table, iterable $args): Fluent
|
||||||
{
|
{
|
||||||
return $this->command()->update('%n', $table)->set($args);
|
return $this->command()->update('%n', $table)->set($args);
|
||||||
}
|
}
|
||||||
@@ -447,6 +498,7 @@ class Connection implements IConnection
|
|||||||
if ($args instanceof Traversable) {
|
if ($args instanceof Traversable) {
|
||||||
$args = iterator_to_array($args);
|
$args = iterator_to_array($args);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->command()->insert()
|
return $this->command()->insert()
|
||||||
->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args);
|
->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args);
|
||||||
}
|
}
|
||||||
@@ -550,7 +602,7 @@ class Connection implements IConnection
|
|||||||
* @param callable $onProgress function (int $count, ?float $percent): void
|
* @param callable $onProgress function (int $count, ?float $percent): void
|
||||||
* @return int count of sql commands
|
* @return int count of sql commands
|
||||||
*/
|
*/
|
||||||
public function loadFile(string $file, callable $onProgress = null): int
|
public function loadFile(string $file, ?callable $onProgress = null): int
|
||||||
{
|
{
|
||||||
return Helpers::loadFromFile($this, $file, $onProgress);
|
return Helpers::loadFromFile($this, $file, $onProgress);
|
||||||
}
|
}
|
||||||
@@ -564,6 +616,7 @@ class Connection implements IConnection
|
|||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
|
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,7 +626,7 @@ class Connection implements IConnection
|
|||||||
*/
|
*/
|
||||||
public function __wakeup()
|
public function __wakeup()
|
||||||
{
|
{
|
||||||
throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
|
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -582,7 +635,7 @@ class Connection implements IConnection
|
|||||||
*/
|
*/
|
||||||
public function __sleep()
|
public function __sleep()
|
||||||
{
|
{
|
||||||
throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
|
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -53,11 +53,9 @@ class DataSource implements IDataSource
|
|||||||
*/
|
*/
|
||||||
public function __construct(string $sql, Connection $connection)
|
public function __construct(string $sql, Connection $connection)
|
||||||
{
|
{
|
||||||
if (strpbrk($sql, " \t\r\n") === false) {
|
$this->sql = strpbrk($sql, " \t\r\n") === false
|
||||||
$this->sql = $connection->getDriver()->escapeIdentifier($sql); // table name
|
? $connection->getDriver()->escapeIdentifier($sql) // table name
|
||||||
} else {
|
: '(' . $sql . ') t'; // SQL command
|
||||||
$this->sql = '(' . $sql . ') t'; // SQL command
|
|
||||||
}
|
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,13 +65,14 @@ class DataSource implements IDataSource
|
|||||||
* @param string|array $col column name or array of column names
|
* @param string|array $col column name or array of column names
|
||||||
* @param string $as column alias
|
* @param string $as column alias
|
||||||
*/
|
*/
|
||||||
public function select($col, string $as = null): self
|
public function select($col, ?string $as = null): self
|
||||||
{
|
{
|
||||||
if (is_array($col)) {
|
if (is_array($col)) {
|
||||||
$this->cols = $col;
|
$this->cols = $col;
|
||||||
} else {
|
} else {
|
||||||
$this->cols[$col] = $as;
|
$this->cols[$col] = $as;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->result = null;
|
$this->result = null;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -84,12 +83,9 @@ class DataSource implements IDataSource
|
|||||||
*/
|
*/
|
||||||
public function where($cond): self
|
public function where($cond): self
|
||||||
{
|
{
|
||||||
if (is_array($cond)) {
|
$this->conds[] = is_array($cond)
|
||||||
// TODO: not consistent with select and orderBy
|
? $cond // TODO: not consistent with select and orderBy
|
||||||
$this->conds[] = $cond;
|
: func_get_args();
|
||||||
} else {
|
|
||||||
$this->conds[] = func_get_args();
|
|
||||||
}
|
|
||||||
$this->result = $this->count = null;
|
$this->result = $this->count = null;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -106,6 +102,7 @@ class DataSource implements IDataSource
|
|||||||
} else {
|
} else {
|
||||||
$this->sorting[$row] = $direction;
|
$this->sorting[$row] = $direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->result = null;
|
$this->result = null;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -114,7 +111,7 @@ class DataSource implements IDataSource
|
|||||||
/**
|
/**
|
||||||
* Limits number of rows.
|
* Limits number of rows.
|
||||||
*/
|
*/
|
||||||
public function applyLimit(int $limit, int $offset = null): self
|
public function applyLimit(int $limit, ?int $offset = null): self
|
||||||
{
|
{
|
||||||
$this->limit = $limit;
|
$this->limit = $limit;
|
||||||
$this->offset = $offset;
|
$this->offset = $offset;
|
||||||
@@ -140,6 +137,7 @@ class DataSource implements IDataSource
|
|||||||
if ($this->result === null) {
|
if ($this->result === null) {
|
||||||
$this->result = $this->connection->nativeQuery($this->__toString());
|
$this->result = $this->connection->nativeQuery($this->__toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->result;
|
return $this->result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +188,7 @@ class DataSource implements IDataSource
|
|||||||
/**
|
/**
|
||||||
* Fetches all records from table like $key => $value pairs.
|
* Fetches all records from table like $key => $value pairs.
|
||||||
*/
|
*/
|
||||||
public function fetchPairs(string $key = null, string $value = null): array
|
public function fetchPairs(?string $key = null, ?string $value = null): array
|
||||||
{
|
{
|
||||||
return $this->getResult()->fetchPairs($key, $value);
|
return $this->getResult()->fetchPairs($key, $value);
|
||||||
}
|
}
|
||||||
@@ -232,12 +230,18 @@ class DataSource implements IDataSource
|
|||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return $this->connection->translate('
|
return $this->connection->translate(
|
||||||
SELECT %n', (empty($this->cols) ? '*' : $this->cols), '
|
"\nSELECT %n",
|
||||||
FROM %SQL', $this->sql, '
|
(empty($this->cols) ? '*' : $this->cols),
|
||||||
%ex', $this->conds ? ['WHERE %and', $this->conds] : null, '
|
"\nFROM %SQL",
|
||||||
%ex', $this->sorting ? ['ORDER BY %by', $this->sorting] : null, '
|
$this->sql,
|
||||||
%ofs %lmt', $this->offset, $this->limit
|
"\n%ex",
|
||||||
|
$this->conds ? ['WHERE %and', $this->conds] : null,
|
||||||
|
"\n%ex",
|
||||||
|
$this->sorting ? ['ORDER BY %by', $this->sorting] : null,
|
||||||
|
"\n%ofs %lmt",
|
||||||
|
$this->offset,
|
||||||
|
$this->limit
|
||||||
);
|
);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
trigger_error($e->getMessage(), E_USER_ERROR);
|
trigger_error($e->getMessage(), E_USER_ERROR);
|
||||||
@@ -261,6 +265,7 @@ FROM %SQL', $this->sql, '
|
|||||||
)->fetchSingle())
|
)->fetchSingle())
|
||||||
: $this->getTotalCount();
|
: $this->getTotalCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->count;
|
return $this->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,6 +280,7 @@ FROM %SQL', $this->sql, '
|
|||||||
'SELECT COUNT(*) FROM ' . $this->sql
|
'SELECT COUNT(*) FROM ' . $this->sql
|
||||||
)->fetchSingle());
|
)->fetchSingle());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->totalCount;
|
return $this->totalCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ class DateTime extends \DateTimeImmutable
|
|||||||
/**
|
/**
|
||||||
* @param string|int $time
|
* @param string|int $time
|
||||||
*/
|
*/
|
||||||
public function __construct($time = 'now', \DateTimeZone $timezone = null)
|
public function __construct($time = 'now', ?\DateTimeZone $timezone = null)
|
||||||
{
|
{
|
||||||
$timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get());
|
$timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get());
|
||||||
if (is_numeric($time)) {
|
if (is_numeric($time)) {
|
||||||
@@ -32,77 +32,8 @@ class DateTime extends \DateTimeImmutable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** @deprecated use modify() */
|
|
||||||
public function modifyClone(string $modify = ''): self
|
|
||||||
{
|
|
||||||
trigger_error(__METHOD__ . '() is deprecated, use modify()', E_USER_DEPRECATED);
|
|
||||||
$dolly = clone $this;
|
|
||||||
return $modify ? $dolly->modify($modify) : $dolly;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return $this->format('Y-m-d H:i:s.u');
|
return $this->format('Y-m-d H:i:s.u');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/********************* immutable usage detector ****************d*g**/
|
|
||||||
|
|
||||||
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
|
||||||
if (isset($trace[0]['file'], $trace[1]['function']) && $trace[0]['file'] === __FILE__ && $trace[1]['function'] !== '__construct') {
|
|
||||||
trigger_error(__CLASS__ . ' is immutable now, check how it is used in ' . $trace[1]['file'] . ':' . $trace[1]['line'], E_USER_WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function add($interval)
|
|
||||||
{
|
|
||||||
return parent::add($interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function modify($modify)
|
|
||||||
{
|
|
||||||
return parent::modify($modify);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function setDate($year, $month, $day)
|
|
||||||
{
|
|
||||||
return parent::setDate($year, $month, $day);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function setISODate($year, $week, $day = 1)
|
|
||||||
{
|
|
||||||
return parent::setISODate($year, $week, $day);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function setTime($hour, $minute, $second = 0, $micro = 0)
|
|
||||||
{
|
|
||||||
return parent::setTime($hour, $minute, $second, $micro);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function setTimestamp($unixtimestamp)
|
|
||||||
{
|
|
||||||
return parent::setTimestamp($unixtimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function setTimezone($timezone)
|
|
||||||
{
|
|
||||||
return parent::setTimezone($timezone);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function sub($interval)
|
|
||||||
{
|
|
||||||
return parent::sub($interval);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
219
src/Dibi/Drivers/DummyDriver.php
Normal file
219
src/Dibi/Drivers/DummyDriver.php
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
|
||||||
|
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
|
use Dibi;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dummy driver for testing purposes.
|
||||||
|
*/
|
||||||
|
class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
|
||||||
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
|
public function disconnect(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function query(string $sql): ?Dibi\ResultDriver
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getAffectedRows(): ?int
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getInsertId(?string $sequence): ?int
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function begin(?string $savepoint = null): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function commit(?string $savepoint = null): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function rollback(?string $savepoint = null): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getResource()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the connection reflector.
|
||||||
|
*/
|
||||||
|
public function getReflector(): Dibi\Reflector
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/********************* SQL ****************d*g**/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes data for use in a SQL statement.
|
||||||
|
*/
|
||||||
|
public function escapeText(string $value): string
|
||||||
|
{
|
||||||
|
return "'" . str_replace("'", "''", $value) . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeBinary(string $value): string
|
||||||
|
{
|
||||||
|
return "N'" . str_replace("'", "''", $value) . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeIdentifier(string $value): string
|
||||||
|
{
|
||||||
|
return '[' . strtr($value, '[]', ' ') . ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeBool(bool $value): string
|
||||||
|
{
|
||||||
|
return $value ? '1' : '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDate(\DateTimeInterface $value): string
|
||||||
|
{
|
||||||
|
return $value->format("'Y-m-d'");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
|
{
|
||||||
|
return $value->format("'Y-m-d H:i:s.u'");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDateInterval(\DateInterval $value): string
|
||||||
|
{
|
||||||
|
throw new Dibi\NotImplementedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes string for use in a LIKE statement.
|
||||||
|
*/
|
||||||
|
public function escapeLike(string $value, int $pos): string
|
||||||
|
{
|
||||||
|
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||||
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects LIMIT/OFFSET to the SQL query.
|
||||||
|
*/
|
||||||
|
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||||
|
{
|
||||||
|
if ($limit < 0 || $offset < 0) {
|
||||||
|
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||||
|
|
||||||
|
} elseif ($limit !== null || $offset) {
|
||||||
|
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||||
|
. ($offset ? ' OFFSET ' . $offset : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/********************* Result ****************d*g**/
|
||||||
|
|
||||||
|
|
||||||
|
public function getRowCount(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function fetch(bool $assoc): ?array
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function seek(int $row): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function free(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getResultResource()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getResultColumns(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes data from result set.
|
||||||
|
*/
|
||||||
|
public function unescapeBinary(string $value): string
|
||||||
|
{
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/********************* Reflector ****************d*g**/
|
||||||
|
|
||||||
|
|
||||||
|
public function getTables(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getColumns(string $table): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getIndexes(string $table): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getForeignKeys(string $table): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@@ -40,10 +40,8 @@ class FirebirdDriver implements Dibi\Driver
|
|||||||
private $inTransaction = false;
|
private $inTransaction = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @throws Dibi\NotSupportedException */
|
||||||
* @throws Dibi\NotSupportedException
|
public function __construct(array $config)
|
||||||
*/
|
|
||||||
public function __construct(array &$config)
|
|
||||||
{
|
{
|
||||||
if (!extension_loaded('interbase')) {
|
if (!extension_loaded('interbase')) {
|
||||||
throw new Dibi\NotSupportedException("PHP extension 'interbase' is not loaded.");
|
throw new Dibi\NotSupportedException("PHP extension 'interbase' is not loaded.");
|
||||||
@@ -64,11 +62,9 @@ class FirebirdDriver implements Dibi\Driver
|
|||||||
'buffers' => 0,
|
'buffers' => 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (empty($config['persistent'])) {
|
$this->connection = empty($config['persistent'])
|
||||||
$this->connection = @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
|
? @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']) // intentionally @
|
||||||
} else {
|
: @ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
|
||||||
$this->connection = @ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_resource($this->connection)) {
|
if (!is_resource($this->connection)) {
|
||||||
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode());
|
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode());
|
||||||
@@ -92,21 +88,23 @@ class FirebirdDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function query(string $sql): ?Dibi\ResultDriver
|
public function query(string $sql): ?Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
$resource = $this->inTransaction ? $this->transaction : $this->connection;
|
$resource = $this->inTransaction
|
||||||
|
? $this->transaction
|
||||||
|
: $this->connection;
|
||||||
$res = ibase_query($resource, $sql);
|
$res = ibase_query($resource, $sql);
|
||||||
|
|
||||||
if ($res === false) {
|
if ($res === false) {
|
||||||
if (ibase_errcode() == self::ERROR_EXCEPTION_THROWN) {
|
if (ibase_errcode() === self::ERROR_EXCEPTION_THROWN) {
|
||||||
preg_match('/exception (\d+) (\w+) (.*)/i', ibase_errmsg(), $match);
|
preg_match('/exception (\d+) (\w+) (.*)/i', ibase_errmsg(), $match);
|
||||||
throw new Dibi\ProcedureException($match[3], $match[1], $match[2], $sql);
|
throw new Dibi\ProcedureException($match[3], $match[1], $match[2], $sql);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode(), $sql);
|
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode(), $sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
} elseif (is_resource($res)) {
|
} elseif (is_resource($res)) {
|
||||||
return $this->createResultDriver($res);
|
return $this->createResultDriver($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +131,12 @@ class FirebirdDriver implements Dibi\Driver
|
|||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function begin(string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if ($savepoint !== null) {
|
if ($savepoint !== null) {
|
||||||
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
|
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->transaction = ibase_trans($this->getResource());
|
$this->transaction = ibase_trans($this->getResource());
|
||||||
$this->inTransaction = true;
|
$this->inTransaction = true;
|
||||||
}
|
}
|
||||||
@@ -147,7 +146,7 @@ class FirebirdDriver implements Dibi\Driver
|
|||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function commit(string $savepoint = null): void
|
public function commit(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if ($savepoint !== null) {
|
if ($savepoint !== null) {
|
||||||
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
|
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
|
||||||
@@ -165,7 +164,7 @@ class FirebirdDriver implements Dibi\Driver
|
|||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function rollback(string $savepoint = null): void
|
public function rollback(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if ($savepoint !== null) {
|
if ($savepoint !== null) {
|
||||||
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
|
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
|
||||||
@@ -247,36 +246,31 @@ class FirebirdDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDate(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDate($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $value->format("'Y-m-d'");
|
return $value->format("'Y-m-d'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDateTime($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
|
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDateInterval(\DateInterval $value): string
|
||||||
|
{
|
||||||
|
throw new Dibi\NotImplementedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes string for use in a LIKE statement.
|
* Encodes string for use in a LIKE statement.
|
||||||
*/
|
*/
|
||||||
public function escapeLike(string $value, int $pos): string
|
public function escapeLike(string $value, int $pos): string
|
||||||
{
|
{
|
||||||
throw new Dibi\NotImplementedException;
|
$value = addcslashes($this->escapeText($value), '%_\\');
|
||||||
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -38,8 +38,8 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
SELECT TRIM(RDB\$RELATION_NAME),
|
SELECT TRIM(RDB\$RELATION_NAME),
|
||||||
CASE RDB\$VIEW_BLR WHEN NULL THEN 'TRUE' ELSE 'FALSE' END
|
CASE RDB\$VIEW_BLR WHEN NULL THEN 'TRUE' ELSE 'FALSE' END
|
||||||
FROM RDB\$RELATIONS
|
FROM RDB\$RELATIONS
|
||||||
WHERE RDB\$SYSTEM_FLAG = 0;"
|
WHERE RDB\$SYSTEM_FLAG = 0;
|
||||||
);
|
");
|
||||||
$tables = [];
|
$tables = [];
|
||||||
while ($row = $res->fetch(false)) {
|
while ($row = $res->fetch(false)) {
|
||||||
$tables[] = [
|
$tables[] = [
|
||||||
@@ -47,6 +47,7 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
'view' => $row[1] === 'TRUE',
|
'view' => $row[1] === 'TRUE',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tables;
|
return $tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +85,8 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
FROM RDB\$RELATION_FIELDS r
|
FROM RDB\$RELATION_FIELDS r
|
||||||
LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
|
LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
|
||||||
WHERE r.RDB\$RELATION_NAME = '$table'
|
WHERE r.RDB\$RELATION_NAME = '$table'
|
||||||
ORDER BY r.RDB\$FIELD_POSITION;"
|
ORDER BY r.RDB\$FIELD_POSITION;
|
||||||
|
");
|
||||||
);
|
|
||||||
$columns = [];
|
$columns = [];
|
||||||
while ($row = $res->fetch(true)) {
|
while ($row = $res->fetch(true)) {
|
||||||
$key = $row['FIELD_NAME'];
|
$key = $row['FIELD_NAME'];
|
||||||
@@ -100,6 +100,7 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
'autoincrement' => false,
|
'autoincrement' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +122,8 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
||||||
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
||||||
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
|
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
|
||||||
ORDER BY s.RDB\$FIELD_POSITION"
|
ORDER BY s.RDB\$FIELD_POSITION
|
||||||
);
|
");
|
||||||
$indexes = [];
|
$indexes = [];
|
||||||
while ($row = $res->fetch(true)) {
|
while ($row = $res->fetch(true)) {
|
||||||
$key = $row['INDEX_NAME'];
|
$key = $row['INDEX_NAME'];
|
||||||
@@ -132,6 +133,7 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
$indexes[$key]['table'] = $table;
|
$indexes[$key]['table'] = $table;
|
||||||
$indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME'];
|
$indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $indexes;
|
return $indexes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +151,8 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
||||||
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
|
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
|
||||||
AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY'
|
AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY'
|
||||||
ORDER BY s.RDB\$FIELD_POSITION"
|
ORDER BY s.RDB\$FIELD_POSITION
|
||||||
);
|
");
|
||||||
$keys = [];
|
$keys = [];
|
||||||
while ($row = $res->fetch(true)) {
|
while ($row = $res->fetch(true)) {
|
||||||
$key = $row['INDEX_NAME'];
|
$key = $row['INDEX_NAME'];
|
||||||
@@ -160,6 +162,7 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
'table' => $table,
|
'table' => $table,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $keys;
|
return $keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,12 +177,13 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
FROM RDB\$INDICES
|
FROM RDB\$INDICES
|
||||||
WHERE RDB\$RELATION_NAME = UPPER('$table')
|
WHERE RDB\$RELATION_NAME = UPPER('$table')
|
||||||
AND RDB\$UNIQUE_FLAG IS NULL
|
AND RDB\$UNIQUE_FLAG IS NULL
|
||||||
AND RDB\$FOREIGN_KEY IS NULL;"
|
AND RDB\$FOREIGN_KEY IS NULL;
|
||||||
);
|
");
|
||||||
$indices = [];
|
$indices = [];
|
||||||
while ($row = $res->fetch(false)) {
|
while ($row = $res->fetch(false)) {
|
||||||
$indices[] = $row[0];
|
$indices[] = $row[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $indices;
|
return $indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,12 +200,13 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
AND (
|
AND (
|
||||||
RDB\$UNIQUE_FLAG IS NOT NULL
|
RDB\$UNIQUE_FLAG IS NOT NULL
|
||||||
OR RDB\$FOREIGN_KEY IS NOT NULL
|
OR RDB\$FOREIGN_KEY IS NOT NULL
|
||||||
);"
|
);
|
||||||
);
|
");
|
||||||
$constraints = [];
|
$constraints = [];
|
||||||
while ($row = $res->fetch(false)) {
|
while ($row = $res->fetch(false)) {
|
||||||
$constraints[] = $row[0];
|
$constraints[] = $row[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $constraints;
|
return $constraints;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,9 +215,10 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
* Returns metadata for all triggers in a table or database.
|
* Returns metadata for all triggers in a table or database.
|
||||||
* (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
|
* (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
|
||||||
*/
|
*/
|
||||||
public function getTriggersMeta(string $table = null): array
|
public function getTriggersMeta(?string $table = null): array
|
||||||
{
|
{
|
||||||
$res = $this->driver->query("
|
$res = $this->driver->query(
|
||||||
|
"
|
||||||
SELECT TRIM(RDB\$TRIGGER_NAME) AS TRIGGER_NAME,
|
SELECT TRIM(RDB\$TRIGGER_NAME) AS TRIGGER_NAME,
|
||||||
TRIM(RDB\$RELATION_NAME) AS TABLE_NAME,
|
TRIM(RDB\$RELATION_NAME) AS TABLE_NAME,
|
||||||
CASE RDB\$TRIGGER_TYPE
|
CASE RDB\$TRIGGER_TYPE
|
||||||
@@ -248,6 +254,7 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE',
|
'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $triggers;
|
return $triggers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,18 +263,21 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
* Returns list of triggers for given table.
|
* Returns list of triggers for given table.
|
||||||
* (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
|
* (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
|
||||||
*/
|
*/
|
||||||
public function getTriggers(string $table = null): array
|
public function getTriggers(?string $table = null): array
|
||||||
{
|
{
|
||||||
$q = 'SELECT TRIM(RDB$TRIGGER_NAME)
|
$q = 'SELECT TRIM(RDB$TRIGGER_NAME)
|
||||||
FROM RDB$TRIGGERS
|
FROM RDB$TRIGGERS
|
||||||
WHERE RDB$SYSTEM_FLAG = 0';
|
WHERE RDB$SYSTEM_FLAG = 0';
|
||||||
$q .= $table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table')";
|
$q .= $table === null
|
||||||
|
? ';'
|
||||||
|
: " AND RDB\$RELATION_NAME = UPPER('$table')";
|
||||||
|
|
||||||
$res = $this->driver->query($q);
|
$res = $this->driver->query($q);
|
||||||
$triggers = [];
|
$triggers = [];
|
||||||
while ($row = $res->fetch(false)) {
|
while ($row = $res->fetch(false)) {
|
||||||
$triggers[] = $row[0];
|
$triggers[] = $row[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $triggers;
|
return $triggers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,8 +317,8 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER
|
p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER
|
||||||
FROM RDB\$PROCEDURE_PARAMETERS p
|
FROM RDB\$PROCEDURE_PARAMETERS p
|
||||||
LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE
|
LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE
|
||||||
ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;"
|
ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;
|
||||||
);
|
");
|
||||||
$procedures = [];
|
$procedures = [];
|
||||||
while ($row = $res->fetch(true)) {
|
while ($row = $res->fetch(true)) {
|
||||||
$key = $row['PROCEDURE_NAME'];
|
$key = $row['PROCEDURE_NAME'];
|
||||||
@@ -319,6 +329,7 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
$procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']);
|
$procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']);
|
||||||
$procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH'];
|
$procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $procedures;
|
return $procedures;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,12 +341,13 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
{
|
{
|
||||||
$res = $this->driver->query('
|
$res = $this->driver->query('
|
||||||
SELECT TRIM(RDB$PROCEDURE_NAME)
|
SELECT TRIM(RDB$PROCEDURE_NAME)
|
||||||
FROM RDB$PROCEDURES;'
|
FROM RDB$PROCEDURES;
|
||||||
);
|
');
|
||||||
$procedures = [];
|
$procedures = [];
|
||||||
while ($row = $res->fetch(false)) {
|
while ($row = $res->fetch(false)) {
|
||||||
$procedures[] = $row[0];
|
$procedures[] = $row[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $procedures;
|
return $procedures;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,12 +360,13 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
$res = $this->driver->query('
|
$res = $this->driver->query('
|
||||||
SELECT TRIM(RDB$GENERATOR_NAME)
|
SELECT TRIM(RDB$GENERATOR_NAME)
|
||||||
FROM RDB$GENERATORS
|
FROM RDB$GENERATORS
|
||||||
WHERE RDB$SYSTEM_FLAG = 0;'
|
WHERE RDB$SYSTEM_FLAG = 0;
|
||||||
);
|
');
|
||||||
$generators = [];
|
$generators = [];
|
||||||
while ($row = $res->fetch(false)) {
|
while ($row = $res->fetch(false)) {
|
||||||
$generators[] = $row[0];
|
$generators[] = $row[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $generators;
|
return $generators;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,12 +379,13 @@ class FirebirdReflector implements Dibi\Reflector
|
|||||||
$res = $this->driver->query('
|
$res = $this->driver->query('
|
||||||
SELECT TRIM(RDB$FUNCTION_NAME)
|
SELECT TRIM(RDB$FUNCTION_NAME)
|
||||||
FROM RDB$FUNCTIONS
|
FROM RDB$FUNCTIONS
|
||||||
WHERE RDB$SYSTEM_FLAG = 0;'
|
WHERE RDB$SYSTEM_FLAG = 0;
|
||||||
);
|
');
|
||||||
$functions = [];
|
$functions = [];
|
||||||
while ($row = $res->fetch(false)) {
|
while ($row = $res->fetch(false)) {
|
||||||
$functions[] = $row[0];
|
$functions[] = $row[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $functions;
|
return $functions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,9 +23,6 @@ class FirebirdResult implements Dibi\ResultDriver
|
|||||||
/** @var resource */
|
/** @var resource */
|
||||||
private $resultSet;
|
private $resultSet;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $autoFree = true;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $resultSet
|
* @param resource $resultSet
|
||||||
@@ -36,17 +33,6 @@ class FirebirdResult implements Dibi\ResultDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically frees the resources allocated for this result set.
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if ($this->autoFree && $this->getResultResource()) {
|
|
||||||
$this->free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of rows in a result set.
|
* Returns the number of rows in a result set.
|
||||||
*/
|
*/
|
||||||
@@ -62,10 +48,12 @@ class FirebirdResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function fetch(bool $assoc): ?array
|
public function fetch(bool $assoc): ?array
|
||||||
{
|
{
|
||||||
$result = $assoc ? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT) : @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
|
$result = $assoc
|
||||||
|
? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT)
|
||||||
|
: @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
|
||||||
|
|
||||||
if (ibase_errcode()) {
|
if (ibase_errcode()) {
|
||||||
if (ibase_errcode() == FirebirdDriver::ERROR_EXCEPTION_THROWN) {
|
if (ibase_errcode() === FirebirdDriver::ERROR_EXCEPTION_THROWN) {
|
||||||
preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
|
preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
|
||||||
throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);
|
throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);
|
||||||
|
|
||||||
@@ -103,7 +91,6 @@ class FirebirdResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource()
|
public function getResultResource()
|
||||||
{
|
{
|
||||||
$this->autoFree = false;
|
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
return is_resource($this->resultSet) ? $this->resultSet : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +111,7 @@ class FirebirdResult implements Dibi\ResultDriver
|
|||||||
'nativetype' => $row['type'],
|
'nativetype' => $row['type'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -43,6 +43,7 @@ class MySqlReflector implements Dibi\Reflector
|
|||||||
'view' => isset($row[1]) && $row[1] === 'VIEW',
|
'view' => isset($row[1]) && $row[1] === 'VIEW',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tables;
|
return $tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ class MySqlReflector implements Dibi\Reflector
|
|||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +86,7 @@ class MySqlReflector implements Dibi\Reflector
|
|||||||
$indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY';
|
$indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY';
|
||||||
$indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name'];
|
$indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values($indexes);
|
return array_values($indexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +126,7 @@ class MySqlReflector implements Dibi\Reflector
|
|||||||
$foreignKeys[$keyName]['onDelete'] = $row['DELETE_RULE'];
|
$foreignKeys[$keyName]['onDelete'] = $row['DELETE_RULE'];
|
||||||
$foreignKeys[$keyName]['onUpdate'] = $row['UPDATE_RULE'];
|
$foreignKeys[$keyName]['onUpdate'] = $row['UPDATE_RULE'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values($foreignKeys);
|
return array_values($foreignKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -47,10 +47,8 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
private $buffered;
|
private $buffered;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @throws Dibi\NotSupportedException */
|
||||||
* @throws Dibi\NotSupportedException
|
public function __construct(array $config)
|
||||||
*/
|
|
||||||
public function __construct(array &$config)
|
|
||||||
{
|
{
|
||||||
if (!extension_loaded('mysqli')) {
|
if (!extension_loaded('mysqli')) {
|
||||||
throw new Dibi\NotSupportedException("PHP extension 'mysqli' is not loaded.");
|
throw new Dibi\NotSupportedException("PHP extension 'mysqli' is not loaded.");
|
||||||
@@ -74,7 +72,7 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
$host = ini_get('mysqli.default_host');
|
$host = ini_get('mysqli.default_host');
|
||||||
if ($host) {
|
if ($host) {
|
||||||
$config['host'] = $host;
|
$config['host'] = $host;
|
||||||
$config['port'] = ini_get('mysqli.default_port');
|
$config['port'] = (int) ini_get('mysqli.default_port');
|
||||||
} else {
|
} else {
|
||||||
$config['host'] = null;
|
$config['host'] = null;
|
||||||
$config['port'] = null;
|
$config['port'] = null;
|
||||||
@@ -90,10 +88,11 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
$this->connection->options($key, $value);
|
$this->connection->options($key, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@$this->connection->real_connect( // intentionally @
|
@$this->connection->real_connect( // intentionally @
|
||||||
(empty($config['persistent']) ? '' : 'p:') . $config['host'],
|
(empty($config['persistent']) ? '' : 'p:') . $config['host'],
|
||||||
$config['username'],
|
$config['username'],
|
||||||
$config['password'],
|
$config['password'] ?? '',
|
||||||
$config['database'] ?? '',
|
$config['database'] ?? '',
|
||||||
$config['port'] ?? 0,
|
$config['port'] ?? 0,
|
||||||
$config['socket'],
|
$config['socket'],
|
||||||
@@ -132,6 +131,15 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pings a server connection, or tries to reconnect if the connection has gone down.
|
||||||
|
*/
|
||||||
|
public function ping(): bool
|
||||||
|
{
|
||||||
|
return $this->connection->ping();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the SQL query.
|
* Executes the SQL query.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
@@ -146,10 +154,14 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
} elseif ($res instanceof \mysqli_result) {
|
} elseif ($res instanceof \mysqli_result) {
|
||||||
return $this->createResultDriver($res);
|
return $this->createResultDriver($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int|string $code
|
||||||
|
*/
|
||||||
public static function createException(string $message, $code, string $sql): Dibi\DriverException
|
public static function createException(string $message, $code, string $sql): Dibi\DriverException
|
||||||
{
|
{
|
||||||
if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
|
if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
|
||||||
@@ -181,6 +193,7 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
foreach ($matches as $m) {
|
foreach ($matches as $m) {
|
||||||
$res[$m[1]] = (int) $m[2];
|
$res[$m[1]] = (int) $m[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +203,9 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function getAffectedRows(): ?int
|
public function getAffectedRows(): ?int
|
||||||
{
|
{
|
||||||
return $this->connection->affected_rows === -1 ? null : $this->connection->affected_rows;
|
return $this->connection->affected_rows === -1
|
||||||
|
? null
|
||||||
|
: $this->connection->affected_rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -199,7 +214,7 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function getInsertId(?string $sequence): ?int
|
public function getInsertId(?string $sequence): ?int
|
||||||
{
|
{
|
||||||
return $this->connection->insert_id;
|
return $this->connection->insert_id ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -207,7 +222,7 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function begin(string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
|
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
|
||||||
}
|
}
|
||||||
@@ -217,7 +232,7 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function commit(string $savepoint = null): void
|
public function commit(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
|
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
|
||||||
}
|
}
|
||||||
@@ -227,7 +242,7 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function rollback(string $savepoint = null): void
|
public function rollback(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
|
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
|
||||||
}
|
}
|
||||||
@@ -238,7 +253,11 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function getResource(): ?\mysqli
|
public function getResource(): ?\mysqli
|
||||||
{
|
{
|
||||||
return @$this->connection->thread_id ? $this->connection : null;
|
try {
|
||||||
|
return @$this->connection->thread_id ? $this->connection : null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -290,37 +309,35 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDate(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDate($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $value->format("'Y-m-d'");
|
return $value->format("'Y-m-d'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDateTime($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $value->format("'Y-m-d H:i:s.u'");
|
return $value->format("'Y-m-d H:i:s.u'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDateInterval(\DateInterval $value): string
|
||||||
|
{
|
||||||
|
if ($value->y || $value->m || $value->d) {
|
||||||
|
throw new Dibi\NotSupportedException('Only time interval is supported.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value->format("'%r%H:%I:%S.%f'");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes string for use in a LIKE statement.
|
* Encodes string for use in a LIKE statement.
|
||||||
*/
|
*/
|
||||||
public function escapeLike(string $value, int $pos): string
|
public function escapeLike(string $value, int $pos): string
|
||||||
{
|
{
|
||||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -334,7 +351,7 @@ class MySqliDriver implements Dibi\Driver
|
|||||||
|
|
||||||
} elseif ($limit !== null || $offset) {
|
} elseif ($limit !== null || $offset) {
|
||||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||||
$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : $limit)
|
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||||
. ($offset ? ' OFFSET ' . $offset : '');
|
. ($offset ? ' OFFSET ' . $offset : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,9 +22,6 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
/** @var \mysqli_result */
|
/** @var \mysqli_result */
|
||||||
private $resultSet;
|
private $resultSet;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $autoFree = true;
|
|
||||||
|
|
||||||
/** @var bool Is buffered (seekable and countable)? */
|
/** @var bool Is buffered (seekable and countable)? */
|
||||||
private $buffered;
|
private $buffered;
|
||||||
|
|
||||||
@@ -36,17 +33,6 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically frees the resources allocated for this result set.
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if ($this->autoFree && $this->getResultResource()) {
|
|
||||||
@$this->free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of rows in a result set.
|
* Returns the number of rows in a result set.
|
||||||
*/
|
*/
|
||||||
@@ -55,6 +41,7 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
if (!$this->buffered) {
|
if (!$this->buffered) {
|
||||||
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
|
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->resultSet->num_rows;
|
return $this->resultSet->num_rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +67,7 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
if (!$this->buffered) {
|
if (!$this->buffered) {
|
||||||
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
|
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->resultSet->data_seek($row);
|
return $this->resultSet->data_seek($row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +95,7 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
$types[$value] = substr($key, 12);
|
$types[$value] = substr($key, 12);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT';
|
$types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +112,7 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +122,6 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource(): \mysqli_result
|
public function getResultResource(): \mysqli_result
|
||||||
{
|
{
|
||||||
$this->autoFree = false;
|
|
||||||
return $this->resultSet;
|
return $this->resultSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,6 +21,7 @@ use Dibi;
|
|||||||
* - password (or pass)
|
* - password (or pass)
|
||||||
* - persistent (bool) => try to find a persistent link?
|
* - persistent (bool) => try to find a persistent link?
|
||||||
* - resource (resource) => existing connection resource
|
* - resource (resource) => existing connection resource
|
||||||
|
* - microseconds (bool) => use microseconds in datetime format?
|
||||||
*/
|
*/
|
||||||
class OdbcDriver implements Dibi\Driver
|
class OdbcDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
@@ -32,11 +33,12 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
/** @var int|null Affected rows */
|
/** @var int|null Affected rows */
|
||||||
private $affectedRows;
|
private $affectedRows;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $microseconds = true;
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Dibi\NotSupportedException
|
/** @throws Dibi\NotSupportedException */
|
||||||
*/
|
public function __construct(array $config)
|
||||||
public function __construct(array &$config)
|
|
||||||
{
|
{
|
||||||
if (!extension_loaded('odbc')) {
|
if (!extension_loaded('odbc')) {
|
||||||
throw new Dibi\NotSupportedException("PHP extension 'odbc' is not loaded.");
|
throw new Dibi\NotSupportedException("PHP extension 'odbc' is not loaded.");
|
||||||
@@ -52,16 +54,18 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
'dsn' => ini_get('odbc.default_db'),
|
'dsn' => ini_get('odbc.default_db'),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (empty($config['persistent'])) {
|
$this->connection = empty($config['persistent'])
|
||||||
$this->connection = @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
|
? @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? '') // intentionally @
|
||||||
} else {
|
: @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
|
||||||
$this->connection = @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_resource($this->connection)) {
|
if (!is_resource($this->connection)) {
|
||||||
throw new Dibi\DriverException(odbc_errormsg() . ' ' . odbc_error());
|
throw new Dibi\DriverException(odbc_errormsg() . ' ' . odbc_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($config['microseconds'])) {
|
||||||
|
$this->microseconds = (bool) $config['microseconds'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -88,8 +92,11 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
|
|
||||||
} elseif (is_resource($res)) {
|
} elseif (is_resource($res)) {
|
||||||
$this->affectedRows = Dibi\Helpers::false2Null(odbc_num_rows($res));
|
$this->affectedRows = Dibi\Helpers::false2Null(odbc_num_rows($res));
|
||||||
return odbc_num_fields($res) ? $this->createResultDriver($res) : null;
|
return odbc_num_fields($res)
|
||||||
|
? $this->createResultDriver($res)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,9 +123,9 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function begin(string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!odbc_autocommit($this->connection, 0/*false*/)) {
|
if (!odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 0 : false)) {
|
||||||
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
|
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,12 +135,13 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function commit(string $savepoint = null): void
|
public function commit(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!odbc_commit($this->connection)) {
|
if (!odbc_commit($this->connection)) {
|
||||||
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
|
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
|
||||||
}
|
}
|
||||||
odbc_autocommit($this->connection, 1/*true*/);
|
|
||||||
|
odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 1 : true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -141,12 +149,13 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function rollback(string $savepoint = null): void
|
public function rollback(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!odbc_rollback($this->connection)) {
|
if (!odbc_rollback($this->connection)) {
|
||||||
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
|
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
|
||||||
}
|
}
|
||||||
odbc_autocommit($this->connection, 1/*true*/);
|
|
||||||
|
odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 1 : true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -218,27 +227,21 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDate(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDate($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $value->format('#m/d/Y#');
|
return $value->format('#m/d/Y#');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDateTime($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#');
|
||||||
$value = new Dibi\DateTime($value);
|
}
|
||||||
}
|
|
||||||
return $value->format('#m/d/Y H:i:s.u#');
|
|
||||||
|
public function escapeDateInterval(\DateInterval $value): string
|
||||||
|
{
|
||||||
|
throw new Dibi\NotImplementedException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -248,7 +251,7 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
public function escapeLike(string $value, int $pos): string
|
public function escapeLike(string $value, int $pos): string
|
||||||
{
|
{
|
||||||
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -44,6 +44,7 @@ class OdbcReflector implements Dibi\Reflector
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
odbc_free_result($res);
|
odbc_free_result($res);
|
||||||
return $tables;
|
return $tables;
|
||||||
}
|
}
|
||||||
@@ -68,6 +69,7 @@ class OdbcReflector implements Dibi\Reflector
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
odbc_free_result($res);
|
odbc_free_result($res);
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
@@ -22,9 +22,6 @@ class OdbcResult implements Dibi\ResultDriver
|
|||||||
/** @var resource */
|
/** @var resource */
|
||||||
private $resultSet;
|
private $resultSet;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $autoFree = true;
|
|
||||||
|
|
||||||
/** @var int Cursor */
|
/** @var int Cursor */
|
||||||
private $row = 0;
|
private $row = 0;
|
||||||
|
|
||||||
@@ -38,17 +35,6 @@ class OdbcResult implements Dibi\ResultDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically frees the resources allocated for this result set.
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if ($this->autoFree && $this->getResultResource()) {
|
|
||||||
$this->free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of rows in a result set.
|
* Returns the number of rows in a result set.
|
||||||
*/
|
*/
|
||||||
@@ -72,11 +58,13 @@ class OdbcResult implements Dibi\ResultDriver
|
|||||||
if (!odbc_fetch_row($set, ++$this->row)) {
|
if (!odbc_fetch_row($set, ++$this->row)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$count = odbc_num_fields($set);
|
$count = odbc_num_fields($set);
|
||||||
$cols = [];
|
$cols = [];
|
||||||
for ($i = 1; $i <= $count; $i++) {
|
for ($i = 1; $i <= $count; $i++) {
|
||||||
$cols[] = odbc_result($set, $i);
|
$cols[] = odbc_result($set, $i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $cols;
|
return $cols;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +104,7 @@ class OdbcResult implements Dibi\ResultDriver
|
|||||||
'nativetype' => odbc_field_type($this->resultSet, $i),
|
'nativetype' => odbc_field_type($this->resultSet, $i),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +115,6 @@ class OdbcResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource()
|
public function getResultResource()
|
||||||
{
|
{
|
||||||
$this->autoFree = false;
|
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
return is_resource($this->resultSet) ? $this->resultSet : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,20 +42,14 @@ class OracleDriver implements Dibi\Driver
|
|||||||
private $affectedRows;
|
private $affectedRows;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @throws Dibi\NotSupportedException */
|
||||||
* @throws Dibi\NotSupportedException
|
public function __construct(array $config)
|
||||||
*/
|
|
||||||
public function __construct(array &$config)
|
|
||||||
{
|
{
|
||||||
if (!extension_loaded('oci8')) {
|
if (!extension_loaded('oci8')) {
|
||||||
throw new Dibi\NotSupportedException("PHP extension 'oci8' is not loaded.");
|
throw new Dibi\NotSupportedException("PHP extension 'oci8' is not loaded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$foo = &$config['charset'];
|
$foo = &$config['charset'];
|
||||||
|
|
||||||
if (isset($config['formatDate']) || isset($config['formatDateTime'])) {
|
|
||||||
trigger_error('OracleDriver: options formatDate and formatDateTime are deprecated.', E_USER_DEPRECATED);
|
|
||||||
}
|
|
||||||
$this->nativeDate = $config['nativeDate'] ?? true;
|
$this->nativeDate = $config['nativeDate'] ?? true;
|
||||||
|
|
||||||
if (isset($config['resource'])) {
|
if (isset($config['resource'])) {
|
||||||
@@ -102,12 +96,15 @@ class OracleDriver implements Dibi\Driver
|
|||||||
|
|
||||||
} elseif (is_resource($res)) {
|
} elseif (is_resource($res)) {
|
||||||
$this->affectedRows = Dibi\Helpers::false2Null(oci_num_rows($res));
|
$this->affectedRows = Dibi\Helpers::false2Null(oci_num_rows($res));
|
||||||
return oci_num_fields($res) ? $this->createResultDriver($res) : null;
|
return oci_num_fields($res)
|
||||||
|
? $this->createResultDriver($res)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$err = oci_error($this->connection);
|
$err = oci_error($this->connection);
|
||||||
throw new Dibi\DriverException($err['message'], $err['code'], $sql);
|
throw new Dibi\DriverException($err['message'], $err['code'], $sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +148,7 @@ class OracleDriver implements Dibi\Driver
|
|||||||
/**
|
/**
|
||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
*/
|
*/
|
||||||
public function begin(string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
$this->autocommit = false;
|
$this->autocommit = false;
|
||||||
}
|
}
|
||||||
@@ -161,12 +158,13 @@ class OracleDriver implements Dibi\Driver
|
|||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function commit(string $savepoint = null): void
|
public function commit(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!oci_commit($this->connection)) {
|
if (!oci_commit($this->connection)) {
|
||||||
$err = oci_error($this->connection);
|
$err = oci_error($this->connection);
|
||||||
throw new Dibi\DriverException($err['message'], $err['code']);
|
throw new Dibi\DriverException($err['message'], $err['code']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->autocommit = true;
|
$this->autocommit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,12 +173,13 @@ class OracleDriver implements Dibi\Driver
|
|||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function rollback(string $savepoint = null): void
|
public function rollback(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!oci_rollback($this->connection)) {
|
if (!oci_rollback($this->connection)) {
|
||||||
$err = oci_error($this->connection);
|
$err = oci_error($this->connection);
|
||||||
throw new Dibi\DriverException($err['message'], $err['code']);
|
throw new Dibi\DriverException($err['message'], $err['code']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->autocommit = true;
|
$this->autocommit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,34 +244,28 @@ class OracleDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDate(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDate($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $this->nativeDate
|
return $this->nativeDate
|
||||||
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
|
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
|
||||||
: $value->format('U');
|
: $value->format('U');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDateTime($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $this->nativeDate
|
return $this->nativeDate
|
||||||
? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')"
|
? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')"
|
||||||
: $value->format('U');
|
: $value->format('U');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDateInterval(\DateInterval $value): string
|
||||||
|
{
|
||||||
|
throw new Dibi\NotImplementedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes string for use in a LIKE statement.
|
* Encodes string for use in a LIKE statement.
|
||||||
*/
|
*/
|
||||||
@@ -280,7 +273,7 @@ class OracleDriver implements Dibi\Driver
|
|||||||
{
|
{
|
||||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
|
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
|
||||||
$value = str_replace("'", "''", $value);
|
$value = str_replace("'", "''", $value);
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -44,6 +44,7 @@ class OracleReflector implements Dibi\Reflector
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tables;
|
return $tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ class OracleReflector implements Dibi\Reflector
|
|||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,9 +22,6 @@ class OracleResult implements Dibi\ResultDriver
|
|||||||
/** @var resource */
|
/** @var resource */
|
||||||
private $resultSet;
|
private $resultSet;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $autoFree = true;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $resultSet
|
* @param resource $resultSet
|
||||||
@@ -35,17 +32,6 @@ class OracleResult implements Dibi\ResultDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically frees the resources allocated for this result set.
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if ($this->autoFree && $this->getResultResource()) {
|
|
||||||
$this->free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of rows in a result set.
|
* Returns the number of rows in a result set.
|
||||||
*/
|
*/
|
||||||
@@ -96,9 +82,11 @@ class OracleResult implements Dibi\ResultDriver
|
|||||||
'name' => oci_field_name($this->resultSet, $i),
|
'name' => oci_field_name($this->resultSet, $i),
|
||||||
'table' => null,
|
'table' => null,
|
||||||
'fullname' => oci_field_name($this->resultSet, $i),
|
'fullname' => oci_field_name($this->resultSet, $i),
|
||||||
|
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null,
|
||||||
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
|
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +97,6 @@ class OracleResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource()
|
public function getResultResource()
|
||||||
{
|
{
|
||||||
$this->autoFree = false;
|
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
return is_resource($this->resultSet) ? $this->resultSet : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,10 +42,8 @@ class PdoDriver implements Dibi\Driver
|
|||||||
private $serverVersion = '';
|
private $serverVersion = '';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @throws Dibi\NotSupportedException */
|
||||||
* @throws Dibi\NotSupportedException
|
public function __construct(array $config)
|
||||||
*/
|
|
||||||
public function __construct(array &$config)
|
|
||||||
{
|
{
|
||||||
if (!extension_loaded('pdo')) {
|
if (!extension_loaded('pdo')) {
|
||||||
throw new Dibi\NotSupportedException("PHP extension 'pdo' is not loaded.");
|
throw new Dibi\NotSupportedException("PHP extension 'pdo' is not loaded.");
|
||||||
@@ -58,13 +56,19 @@ class PdoDriver implements Dibi\Driver
|
|||||||
if ($config['resource'] instanceof PDO) {
|
if ($config['resource'] instanceof PDO) {
|
||||||
$this->connection = $config['resource'];
|
$this->connection = $config['resource'];
|
||||||
unset($config['resource'], $config['pdo']);
|
unset($config['resource'], $config['pdo']);
|
||||||
|
|
||||||
|
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
|
||||||
|
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
|
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
|
||||||
|
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
if ($e->getMessage() === 'could not find driver') {
|
if ($e->getMessage() === 'could not find driver') {
|
||||||
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
|
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
|
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +114,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
throw PostgreDriver::createException($message, $sqlState, $sql);
|
throw PostgreDriver::createException($message, $sqlState, $sql);
|
||||||
|
|
||||||
case 'sqlite':
|
case 'sqlite':
|
||||||
throw Sqlite3Driver::createException($message, $code, $sql);
|
throw SqliteDriver::createException($message, $code, $sql);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Dibi\DriverException($message, $code, $sql);
|
throw new Dibi\DriverException($message, $code, $sql);
|
||||||
@@ -132,7 +136,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function getInsertId(?string $sequence): ?int
|
public function getInsertId(?string $sequence): ?int
|
||||||
{
|
{
|
||||||
return Helpers::false2Null($this->connection->lastInsertId());
|
return Helpers::intVal($this->connection->lastInsertId($sequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -140,7 +144,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function begin(string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!$this->connection->beginTransaction()) {
|
if (!$this->connection->beginTransaction()) {
|
||||||
$err = $this->connection->errorInfo();
|
$err = $this->connection->errorInfo();
|
||||||
@@ -153,7 +157,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function commit(string $savepoint = null): void
|
public function commit(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!$this->connection->commit()) {
|
if (!$this->connection->commit()) {
|
||||||
$err = $this->connection->errorInfo();
|
$err = $this->connection->errorInfo();
|
||||||
@@ -166,7 +170,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function rollback(string $savepoint = null): void
|
public function rollback(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!$this->connection->rollBack()) {
|
if (!$this->connection->rollBack()) {
|
||||||
$err = $this->connection->errorInfo();
|
$err = $this->connection->errorInfo();
|
||||||
@@ -230,21 +234,17 @@ class PdoDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function escapeText(string $value): string
|
public function escapeText(string $value): string
|
||||||
{
|
{
|
||||||
if ($this->driverName === 'odbc') {
|
return $this->driverName === 'odbc'
|
||||||
return "'" . str_replace("'", "''", $value) . "'";
|
? "'" . str_replace("'", "''", $value) . "'"
|
||||||
} else {
|
: $this->connection->quote($value, PDO::PARAM_STR);
|
||||||
return $this->connection->quote($value, PDO::PARAM_STR);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function escapeBinary(string $value): string
|
public function escapeBinary(string $value): string
|
||||||
{
|
{
|
||||||
if ($this->driverName === 'odbc') {
|
return $this->driverName === 'odbc'
|
||||||
return "'" . str_replace("'", "''", $value) . "'";
|
? "'" . str_replace("'", "''", $value) . "'"
|
||||||
} else {
|
: $this->connection->quote($value, PDO::PARAM_LOB);
|
||||||
return $this->connection->quote($value, PDO::PARAM_LOB);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -285,26 +285,14 @@ class PdoDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDate(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDate($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
|
return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDateTime($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
switch ($this->driverName) {
|
switch ($this->driverName) {
|
||||||
case 'odbc':
|
case 'odbc':
|
||||||
return $value->format('#m/d/Y H:i:s.u#');
|
return $value->format('#m/d/Y H:i:s.u#');
|
||||||
@@ -318,6 +306,12 @@ class PdoDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDateInterval(\DateInterval $value): string
|
||||||
|
{
|
||||||
|
throw new Dibi\NotImplementedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes string for use in a LIKE statement.
|
* Encodes string for use in a LIKE statement.
|
||||||
*/
|
*/
|
||||||
@@ -326,29 +320,29 @@ class PdoDriver implements Dibi\Driver
|
|||||||
switch ($this->driverName) {
|
switch ($this->driverName) {
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
|
|
||||||
case 'oci':
|
case 'oci':
|
||||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
|
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
|
||||||
$value = str_replace("'", "''", $value);
|
$value = str_replace("'", "''", $value);
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
|
|
||||||
case 'pgsql':
|
case 'pgsql':
|
||||||
$bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
|
$bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
|
||||||
$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
|
$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
|
||||||
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
|
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
|
|
||||||
case 'sqlite':
|
case 'sqlite':
|
||||||
$value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\');
|
$value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\');
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||||
|
|
||||||
case 'odbc':
|
case 'odbc':
|
||||||
case 'mssql':
|
case 'mssql':
|
||||||
case 'dblib':
|
case 'dblib':
|
||||||
case 'sqlsrv':
|
case 'sqlsrv':
|
||||||
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Dibi\NotImplementedException;
|
throw new Dibi\NotImplementedException;
|
||||||
@@ -369,25 +363,29 @@ class PdoDriver implements Dibi\Driver
|
|||||||
case 'mysql':
|
case 'mysql':
|
||||||
if ($limit !== null || $offset) {
|
if ($limit !== null || $offset) {
|
||||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||||
$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : $limit)
|
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||||
. ($offset ? ' OFFSET ' . $offset : '');
|
. ($offset ? ' OFFSET ' . $offset : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'pgsql':
|
case 'pgsql':
|
||||||
if ($limit !== null) {
|
if ($limit !== null) {
|
||||||
$sql .= ' LIMIT ' . $limit;
|
$sql .= ' LIMIT ' . $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($offset) {
|
if ($offset) {
|
||||||
$sql .= ' OFFSET ' . $offset;
|
$sql .= ' OFFSET ' . $offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'sqlite':
|
case 'sqlite':
|
||||||
if ($limit !== null || $offset) {
|
if ($limit !== null || $offset) {
|
||||||
$sql .= ' LIMIT ' . ($limit === null ? '-1' : $limit)
|
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||||
. ($offset ? ' OFFSET ' . $offset : '');
|
. ($offset ? ' OFFSET ' . $offset : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'oci':
|
case 'oci':
|
||||||
@@ -400,6 +398,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
} elseif ($limit !== null) {
|
} elseif ($limit !== null) {
|
||||||
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
|
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'mssql':
|
case 'mssql':
|
||||||
@@ -412,10 +411,10 @@ class PdoDriver implements Dibi\Driver
|
|||||||
} elseif ($offset) {
|
} elseif ($offset) {
|
||||||
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
|
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// break omitted
|
// break omitted
|
||||||
|
|
||||||
case 'odbc':
|
case 'odbc':
|
||||||
if ($offset) {
|
if ($offset) {
|
||||||
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
|
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
|
||||||
@@ -425,7 +424,6 @@ class PdoDriver implements Dibi\Driver
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// break omitted
|
// break omitted
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
|
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
|
||||||
}
|
}
|
||||||
|
@@ -85,7 +85,8 @@ class PdoResult implements Dibi\ResultDriver
|
|||||||
if ($row === false) {
|
if ($row === false) {
|
||||||
throw new Dibi\NotSupportedException('Driver does not support meta data.');
|
throw new Dibi\NotSupportedException('Driver does not support meta data.');
|
||||||
}
|
}
|
||||||
$row = $row + [
|
|
||||||
|
$row += [
|
||||||
'table' => null,
|
'table' => null,
|
||||||
'native_type' => 'VAR_STRING',
|
'native_type' => 'VAR_STRING',
|
||||||
];
|
];
|
||||||
@@ -99,6 +100,7 @@ class PdoResult implements Dibi\ResultDriver
|
|||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
|
|||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
|
use PgSql;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,22 +24,21 @@ use Dibi\Helpers;
|
|||||||
* - charset => character encoding to set (default is utf8)
|
* - charset => character encoding to set (default is utf8)
|
||||||
* - persistent (bool) => try to find a persistent link?
|
* - persistent (bool) => try to find a persistent link?
|
||||||
* - resource (resource) => existing connection resource
|
* - resource (resource) => existing connection resource
|
||||||
|
* - connect_type (int) => see pg_connect()
|
||||||
*/
|
*/
|
||||||
class PostgreDriver implements Dibi\Driver
|
class PostgreDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
use Dibi\Strict;
|
use Dibi\Strict;
|
||||||
|
|
||||||
/** @var resource */
|
/** @var resource|PgSql\Connection */
|
||||||
private $connection;
|
private $connection;
|
||||||
|
|
||||||
/** @var int|null Affected rows */
|
/** @var int|null Affected rows */
|
||||||
private $affectedRows;
|
private $affectedRows;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @throws Dibi\NotSupportedException */
|
||||||
* @throws Dibi\NotSupportedException
|
public function __construct(array $config)
|
||||||
*/
|
|
||||||
public function __construct(array &$config)
|
|
||||||
{
|
{
|
||||||
if (!extension_loaded('pgsql')) {
|
if (!extension_loaded('pgsql')) {
|
||||||
throw new Dibi\NotSupportedException("PHP extension 'pgsql' is not loaded.");
|
throw new Dibi\NotSupportedException("PHP extension 'pgsql' is not loaded.");
|
||||||
@@ -65,18 +65,18 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$connectType = $config['connect_type'] ?? PGSQL_CONNECT_FORCE_NEW;
|
||||||
|
|
||||||
set_error_handler(function (int $severity, string $message) use (&$error) {
|
set_error_handler(function (int $severity, string $message) use (&$error) {
|
||||||
$error = $message;
|
$error = $message;
|
||||||
});
|
});
|
||||||
if (empty($config['persistent'])) {
|
$this->connection = empty($config['persistent'])
|
||||||
$this->connection = pg_connect($string, PGSQL_CONNECT_FORCE_NEW);
|
? pg_connect($string, $connectType)
|
||||||
} else {
|
: pg_pconnect($string, $connectType);
|
||||||
$this->connection = pg_pconnect($string, PGSQL_CONNECT_FORCE_NEW);
|
|
||||||
}
|
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_resource($this->connection)) {
|
if (!is_resource($this->connection) && !$this->connection instanceof PgSql\Connection) {
|
||||||
throw new Dibi\DriverException($error ?: 'Connecting error.');
|
throw new Dibi\DriverException($error ?: 'Connecting error.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,17 +122,18 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
if ($res === false) {
|
if ($res === false) {
|
||||||
throw static::createException(pg_last_error($this->connection), null, $sql);
|
throw static::createException(pg_last_error($this->connection), null, $sql);
|
||||||
|
|
||||||
} elseif (is_resource($res)) {
|
} elseif (is_resource($res) || $res instanceof PgSql\Result) {
|
||||||
$this->affectedRows = Helpers::false2Null(pg_affected_rows($res));
|
$this->affectedRows = Helpers::false2Null(pg_affected_rows($res));
|
||||||
if (pg_num_fields($res)) {
|
if (pg_num_fields($res)) {
|
||||||
return $this->createResultDriver($res);
|
return $this->createResultDriver($res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function createException(string $message, $code = null, string $sql = null): Dibi\DriverException
|
public static function createException(string $message, $code = null, ?string $sql = null): Dibi\DriverException
|
||||||
{
|
{
|
||||||
if ($code === null && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) {
|
if ($code === null && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) {
|
||||||
$code = $m[1];
|
$code = $m[1];
|
||||||
@@ -171,12 +172,9 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function getInsertId(?string $sequence): ?int
|
public function getInsertId(?string $sequence): ?int
|
||||||
{
|
{
|
||||||
if ($sequence === null) {
|
$res = $sequence === null
|
||||||
// PostgreSQL 8.1 is needed
|
? $this->query('SELECT LASTVAL()') // PostgreSQL 8.1 is needed
|
||||||
$res = $this->query('SELECT LASTVAL()');
|
: $this->query("SELECT CURRVAL('$sequence')");
|
||||||
} else {
|
|
||||||
$res = $this->query("SELECT CURRVAL('$sequence')");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$res) {
|
if (!$res) {
|
||||||
return null;
|
return null;
|
||||||
@@ -191,9 +189,9 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function begin(string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
|
$this->query($savepoint ? "SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'START TRANSACTION');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -201,9 +199,9 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function commit(string $savepoint = null): void
|
public function commit(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
|
$this->query($savepoint ? "RELEASE SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'COMMIT');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -211,9 +209,9 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function rollback(string $savepoint = null): void
|
public function rollback(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
|
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'ROLLBACK');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -232,7 +230,9 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function getResource()
|
public function getResource()
|
||||||
{
|
{
|
||||||
return is_resource($this->connection) ? $this->connection : null;
|
return is_resource($this->connection) || $this->connection instanceof PgSql\Connection
|
||||||
|
? $this->connection
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -263,18 +263,20 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function escapeText(string $value): string
|
public function escapeText(string $value): string
|
||||||
{
|
{
|
||||||
if (!is_resource($this->connection)) {
|
if (!$this->getResource()) {
|
||||||
throw new Dibi\Exception('Lost connection to server.');
|
throw new Dibi\Exception('Lost connection to server.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return "'" . pg_escape_string($this->connection, $value) . "'";
|
return "'" . pg_escape_string($this->connection, $value) . "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function escapeBinary(string $value): string
|
public function escapeBinary(string $value): string
|
||||||
{
|
{
|
||||||
if (!is_resource($this->connection)) {
|
if (!$this->getResource()) {
|
||||||
throw new Dibi\Exception('Lost connection to server.');
|
throw new Dibi\Exception('Lost connection to server.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return "'" . pg_escape_bytea($this->connection, $value) . "'";
|
return "'" . pg_escape_bytea($this->connection, $value) . "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,30 +294,24 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDate(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDate($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $value->format("'Y-m-d'");
|
return $value->format("'Y-m-d'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDateTime($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $value->format("'Y-m-d H:i:s.u'");
|
return $value->format("'Y-m-d H:i:s.u'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDateInterval(\DateInterval $value): string
|
||||||
|
{
|
||||||
|
throw new Dibi\NotImplementedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes string for use in a LIKE statement.
|
* Encodes string for use in a LIKE statement.
|
||||||
*/
|
*/
|
||||||
@@ -324,7 +320,7 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
$bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
|
$bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
|
||||||
$value = pg_escape_string($this->connection, $value);
|
$value = pg_escape_string($this->connection, $value);
|
||||||
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
|
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -336,9 +332,11 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
if ($limit < 0 || $offset < 0) {
|
if ($limit < 0 || $offset < 0) {
|
||||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($limit !== null) {
|
if ($limit !== null) {
|
||||||
$sql .= ' LIMIT ' . $limit;
|
$sql .= ' LIMIT ' . $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($offset) {
|
if ($offset) {
|
||||||
$sql .= ' OFFSET ' . $offset;
|
$sql .= ' OFFSET ' . $offset;
|
||||||
}
|
}
|
||||||
|
@@ -28,9 +28,6 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
|
|
||||||
public function __construct(Dibi\Driver $driver, string $version)
|
public function __construct(Dibi\Driver $driver, string $version)
|
||||||
{
|
{
|
||||||
if ($version < 7.4) {
|
|
||||||
throw new Dibi\DriverException('Reflection requires PostgreSQL 7.4 and newer.');
|
|
||||||
}
|
|
||||||
$this->driver = $driver;
|
$this->driver = $driver;
|
||||||
$this->version = $version;
|
$this->version = $version;
|
||||||
}
|
}
|
||||||
@@ -69,6 +66,7 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
while ($row = $res->fetch(true)) {
|
while ($row = $res->fetch(true)) {
|
||||||
$tables[] = $row;
|
$tables[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tables;
|
return $tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +103,7 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
a.atttypmod-4 AS character_maximum_length,
|
a.atttypmod-4 AS character_maximum_length,
|
||||||
NOT a.attnotnull AS is_nullable,
|
NOT a.attnotnull AS is_nullable,
|
||||||
a.attnum AS ordinal_position,
|
a.attnum AS ordinal_position,
|
||||||
adef.adsrc AS column_default
|
pg_get_expr(adef.adbin, adef.adrelid) AS column_default
|
||||||
FROM
|
FROM
|
||||||
pg_attribute a
|
pg_attribute a
|
||||||
JOIN pg_type ON a.atttypid = pg_type.oid
|
JOIN pg_type ON a.atttypid = pg_type.oid
|
||||||
@@ -130,10 +128,11 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
'size' => $size > 0 ? $size : null,
|
'size' => $size > 0 ? $size : null,
|
||||||
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
|
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
|
||||||
'default' => $row['column_default'],
|
'default' => $row['column_default'],
|
||||||
'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'], 0, 7) === 'nextval',
|
'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'] ?? '', 0, 7) === 'nextval',
|
||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,10 +175,14 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
$indexes[$row['relname']]['name'] = $row['relname'];
|
$indexes[$row['relname']]['name'] = $row['relname'];
|
||||||
$indexes[$row['relname']]['unique'] = $row['indisunique'] === 't' || $row['indisunique'] === true;
|
$indexes[$row['relname']]['unique'] = $row['indisunique'] === 't' || $row['indisunique'] === true;
|
||||||
$indexes[$row['relname']]['primary'] = $row['indisprimary'] === 't' || $row['indisprimary'] === true;
|
$indexes[$row['relname']]['primary'] = $row['indisprimary'] === 't' || $row['indisprimary'] === true;
|
||||||
|
$indexes[$row['relname']]['columns'] = [];
|
||||||
foreach (explode(' ', $row['indkey']) as $index) {
|
foreach (explode(' ', $row['indkey']) as $index) {
|
||||||
$indexes[$row['relname']]['columns'][] = $columns[$index];
|
if (isset($columns[$index])) {
|
||||||
|
$indexes[$row['relname']]['columns'][] = $columns[$index];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values($indexes);
|
return array_values($indexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +251,10 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
$references[$row['name']] = array_combine($l, $f);
|
$references[$row['name']] = array_combine($l, $f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($references[$row['name']][$row['lnum']]) && $references[$row['name']][$row['lnum']] === $row['fnum']) {
|
if (
|
||||||
|
isset($references[$row['name']][$row['lnum']])
|
||||||
|
&& $references[$row['name']][$row['lnum']] === $row['fnum']
|
||||||
|
) {
|
||||||
$fKeys[$row['name']]['local'][] = $row['local'];
|
$fKeys[$row['name']]['local'][] = $row['local'];
|
||||||
$fKeys[$row['name']]['foreign'][] = $row['foreign'];
|
$fKeys[$row['name']]['foreign'][] = $row['foreign'];
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
|
|||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
|
use PgSql;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,15 +21,12 @@ class PostgreResult implements Dibi\ResultDriver
|
|||||||
{
|
{
|
||||||
use Dibi\Strict;
|
use Dibi\Strict;
|
||||||
|
|
||||||
/** @var resource */
|
/** @var resource|PgSql\Result */
|
||||||
private $resultSet;
|
private $resultSet;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $autoFree = true;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $resultSet
|
* @param resource|PgSql\Result $resultSet
|
||||||
*/
|
*/
|
||||||
public function __construct($resultSet)
|
public function __construct($resultSet)
|
||||||
{
|
{
|
||||||
@@ -36,17 +34,6 @@ class PostgreResult implements Dibi\ResultDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically frees the resources allocated for this result set.
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if ($this->autoFree && $this->getResultResource()) {
|
|
||||||
$this->free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of rows in a result set.
|
* Returns the number of rows in a result set.
|
||||||
*/
|
*/
|
||||||
@@ -97,21 +84,25 @@ class PostgreResult implements Dibi\ResultDriver
|
|||||||
'table' => pg_field_table($this->resultSet, $i),
|
'table' => pg_field_table($this->resultSet, $i),
|
||||||
'nativetype' => pg_field_type($this->resultSet, $i),
|
'nativetype' => pg_field_type($this->resultSet, $i),
|
||||||
];
|
];
|
||||||
$row['fullname'] = $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'];
|
$row['fullname'] = $row['table']
|
||||||
|
? $row['table'] . '.' . $row['name']
|
||||||
|
: $row['name'];
|
||||||
$columns[] = $row;
|
$columns[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the result set resource.
|
* Returns the result set resource.
|
||||||
* @return resource|null
|
* @return resource|PgSql\Result|null
|
||||||
*/
|
*/
|
||||||
public function getResultResource()
|
public function getResultResource()
|
||||||
{
|
{
|
||||||
$this->autoFree = false;
|
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
? $this->resultSet
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -9,293 +9,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
|
||||||
use Dibi\Helpers;
|
|
||||||
use SQLite3;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The driver for SQLite3 database.
|
* Alias for SqliteDriver driver.
|
||||||
*
|
|
||||||
* 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 implements Dibi\Driver
|
class Sqlite3Driver extends SqliteDriver
|
||||||
{
|
{
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -9,115 +9,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
|
||||||
use Dibi\Helpers;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The driver for SQLite3 result set.
|
* Alias for SqliteResult driver.
|
||||||
*/
|
*/
|
||||||
class Sqlite3Result implements Dibi\ResultDriver
|
class Sqlite3Result extends SqliteResult
|
||||||
{
|
{
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
299
src/Dibi/Drivers/SqliteDriver.php
Normal file
299
src/Dibi/Drivers/SqliteDriver.php
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/** @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 (\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 (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() ?: 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -44,6 +44,7 @@ class SqliteReflector implements Dibi\Reflector
|
|||||||
while ($row = $res->fetch(true)) {
|
while ($row = $res->fetch(true)) {
|
||||||
$tables[] = $row;
|
$tables[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tables;
|
return $tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,12 +65,13 @@ class SqliteReflector implements Dibi\Reflector
|
|||||||
'fullname' => "$table.$column",
|
'fullname' => "$table.$column",
|
||||||
'nativetype' => strtoupper($type[0]),
|
'nativetype' => strtoupper($type[0]),
|
||||||
'size' => isset($type[1]) ? (int) $type[1] : null,
|
'size' => isset($type[1]) ? (int) $type[1] : null,
|
||||||
'nullable' => $row['notnull'] == '0',
|
'nullable' => $row['notnull'] === 0,
|
||||||
'default' => $row['dflt_value'],
|
'default' => $row['dflt_value'],
|
||||||
'autoincrement' => $row['pk'] && $type[0] === 'INTEGER',
|
'autoincrement' => $row['pk'] && $type[0] === 'INTEGER',
|
||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,13 +100,15 @@ class SqliteReflector implements Dibi\Reflector
|
|||||||
$column = $indexes[$index]['columns'][0];
|
$column = $indexes[$index]['columns'][0];
|
||||||
$primary = false;
|
$primary = false;
|
||||||
foreach ($columns as $info) {
|
foreach ($columns as $info) {
|
||||||
if ($column == $info['name']) {
|
if ($column === $info['name']) {
|
||||||
$primary = $info['vendor']['pk'];
|
$primary = $info['vendor']['pk'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$indexes[$index]['primary'] = (bool) $primary;
|
$indexes[$index]['primary'] = (bool) $primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid
|
if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid
|
||||||
foreach ($columns as $column) {
|
foreach ($columns as $column) {
|
||||||
if ($column['vendor']['pk']) {
|
if ($column['vendor']['pk']) {
|
||||||
@@ -142,6 +146,7 @@ class SqliteReflector implements Dibi\Reflector
|
|||||||
$keys[$row['id']]['foreign'] = null;
|
$keys[$row['id']]['foreign'] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values($keys);
|
return array_values($keys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
109
src/Dibi/Drivers/SqliteResult.php
Normal file
109
src/Dibi/Drivers/SqliteResult.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/** @var \SQLite3Result */
|
||||||
|
private $resultSet;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(\SQLite3Result $resultSet)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result set resource.
|
||||||
|
*/
|
||||||
|
public function getResultResource(): \SQLite3Result
|
||||||
|
{
|
||||||
|
return $this->resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes data from result set.
|
||||||
|
*/
|
||||||
|
public function unescapeBinary(string $value): string
|
||||||
|
{
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
@@ -39,10 +39,8 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
private $version = '';
|
private $version = '';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @throws Dibi\NotSupportedException */
|
||||||
* @throws Dibi\NotSupportedException
|
public function __construct(array $config)
|
||||||
*/
|
|
||||||
public function __construct(array &$config)
|
|
||||||
{
|
{
|
||||||
if (!extension_loaded('sqlsrv')) {
|
if (!extension_loaded('sqlsrv')) {
|
||||||
throw new Dibi\NotSupportedException("PHP extension 'sqlsrv' is not loaded.");
|
throw new Dibi\NotSupportedException("PHP extension 'sqlsrv' is not loaded.");
|
||||||
@@ -55,7 +53,9 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
|
|
||||||
if (isset($config['resource'])) {
|
if (isset($config['resource'])) {
|
||||||
$this->connection = $config['resource'];
|
$this->connection = $config['resource'];
|
||||||
|
if (!is_resource($this->connection)) {
|
||||||
|
throw new \InvalidArgumentException("Configuration option 'resource' is not resource.");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$options = $config['options'];
|
$options = $config['options'];
|
||||||
|
|
||||||
@@ -65,13 +65,16 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
$options['UID'] = (string) $options['UID'];
|
$options['UID'] = (string) $options['UID'];
|
||||||
$options['Database'] = (string) $options['Database'];
|
$options['Database'] = (string) $options['Database'];
|
||||||
|
|
||||||
|
sqlsrv_configure('WarningsReturnAsErrors', 0);
|
||||||
$this->connection = sqlsrv_connect($config['host'], $options);
|
$this->connection = sqlsrv_connect($config['host'], $options);
|
||||||
|
if (!is_resource($this->connection)) {
|
||||||
|
$info = sqlsrv_errors(SQLSRV_ERR_ERRORS);
|
||||||
|
throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_resource($this->connection)) {
|
|
||||||
$info = sqlsrv_errors();
|
|
||||||
throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
|
|
||||||
}
|
|
||||||
$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
|
$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,8 +103,11 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
|
|
||||||
} elseif (is_resource($res)) {
|
} elseif (is_resource($res)) {
|
||||||
$this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
|
$this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
|
||||||
return sqlsrv_num_fields($res) ? $this->createResultDriver($res) : null;
|
return sqlsrv_num_fields($res)
|
||||||
|
? $this->createResultDriver($res)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +131,7 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
$row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
|
$row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
|
||||||
return Dibi\Helpers::intVal($row[0]);
|
return Dibi\Helpers::intVal($row[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +140,7 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function begin(string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
sqlsrv_begin_transaction($this->connection);
|
sqlsrv_begin_transaction($this->connection);
|
||||||
}
|
}
|
||||||
@@ -143,7 +150,7 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function commit(string $savepoint = null): void
|
public function commit(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
sqlsrv_commit($this->connection);
|
sqlsrv_commit($this->connection);
|
||||||
}
|
}
|
||||||
@@ -153,7 +160,7 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
* @throws Dibi\DriverException
|
* @throws Dibi\DriverException
|
||||||
*/
|
*/
|
||||||
public function rollback(string $savepoint = null): void
|
public function rollback(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
sqlsrv_rollback($this->connection);
|
sqlsrv_rollback($this->connection);
|
||||||
}
|
}
|
||||||
@@ -196,13 +203,13 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function escapeText(string $value): string
|
public function escapeText(string $value): string
|
||||||
{
|
{
|
||||||
return "'" . str_replace("'", "''", $value) . "'";
|
return "N'" . str_replace("'", "''", $value) . "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function escapeBinary(string $value): string
|
public function escapeBinary(string $value): string
|
||||||
{
|
{
|
||||||
return "'" . str_replace("'", "''", $value) . "'";
|
return '0x' . bin2hex($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -219,37 +226,31 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDate(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDate($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return $value->format("'Y-m-d'");
|
return $value->format("'Y-m-d'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
public function escapeDateTime($value): string
|
|
||||||
{
|
{
|
||||||
if (!$value instanceof \DateTimeInterface) {
|
|
||||||
$value = new Dibi\DateTime($value);
|
|
||||||
}
|
|
||||||
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
|
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function escapeDateInterval(\DateInterval $value): string
|
||||||
|
{
|
||||||
|
throw new Dibi\NotImplementedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes string for use in a LIKE statement.
|
* Encodes string for use in a LIKE statement.
|
||||||
*/
|
*/
|
||||||
public function escapeLike(string $value, int $pos): string
|
public function escapeLike(string $value, int $pos): string
|
||||||
{
|
{
|
||||||
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -268,7 +269,6 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
} elseif ($limit !== null) {
|
} elseif ($limit !== null) {
|
||||||
$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
|
$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
} elseif ($limit !== null) {
|
} elseif ($limit !== null) {
|
||||||
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
||||||
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
|
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
|
||||||
|
@@ -42,6 +42,7 @@ class SqlsrvReflector implements Dibi\Reflector
|
|||||||
'view' => isset($row[1]) && $row[1] === 'VIEW',
|
'view' => isset($row[1]) && $row[1] === 'VIEW',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tables;
|
return $tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +92,7 @@ class SqlsrvReflector implements Dibi\Reflector
|
|||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +102,7 @@ class SqlsrvReflector implements Dibi\Reflector
|
|||||||
*/
|
*/
|
||||||
public function getIndexes(string $table): array
|
public function getIndexes(string $table): array
|
||||||
{
|
{
|
||||||
$keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = N%s', $this->driver->escapeText($table)));
|
$keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = %s', $this->driver->escapeText($table)));
|
||||||
$keyUsages = [];
|
$keyUsages = [];
|
||||||
while ($row = $keyUsagesRes->fetch(true)) {
|
while ($row = $keyUsagesRes->fetch(true)) {
|
||||||
$keyUsages[$row['index_name']] = explode(',', $row['index_keys']);
|
$keyUsages[$row['index_name']] = explode(',', $row['index_keys']);
|
||||||
@@ -114,6 +116,7 @@ class SqlsrvReflector implements Dibi\Reflector
|
|||||||
$indexes[$row['name']]['primary'] = $row['is_primary_key'] === 1;
|
$indexes[$row['name']]['primary'] = $row['is_primary_key'] === 1;
|
||||||
$indexes[$row['name']]['columns'] = $keyUsages[$row['name']] ?? [];
|
$indexes[$row['name']]['columns'] = $keyUsages[$row['name']] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values($indexes);
|
return array_values($indexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,9 +22,6 @@ class SqlsrvResult implements Dibi\ResultDriver
|
|||||||
/** @var resource */
|
/** @var resource */
|
||||||
private $resultSet;
|
private $resultSet;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $autoFree = true;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $resultSet
|
* @param resource $resultSet
|
||||||
@@ -35,17 +32,6 @@ class SqlsrvResult implements Dibi\ResultDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically frees the resources allocated for this result set.
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if ($this->autoFree && $this->getResultResource()) {
|
|
||||||
$this->free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of rows in a result set.
|
* Returns the number of rows in a result set.
|
||||||
*/
|
*/
|
||||||
@@ -61,7 +47,7 @@ class SqlsrvResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function fetch(bool $assoc): ?array
|
public function fetch(bool $assoc): ?array
|
||||||
{
|
{
|
||||||
return sqlsrv_fetch_array($this->resultSet, $assoc ? SQLSRV_FETCH_ASSOC : SQLSRV_FETCH_NUMERIC);
|
return Dibi\Helpers::false2Null(sqlsrv_fetch_array($this->resultSet, $assoc ? SQLSRV_FETCH_ASSOC : SQLSRV_FETCH_NUMERIC));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -96,6 +82,7 @@ class SqlsrvResult implements Dibi\ResultDriver
|
|||||||
'nativetype' => $fieldMetadata['Type'],
|
'nativetype' => $fieldMetadata['Type'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +93,6 @@ class SqlsrvResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource()
|
public function getResultResource()
|
||||||
{
|
{
|
||||||
$this->autoFree = false;
|
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
return is_resource($this->resultSet) ? $this->resultSet : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -53,7 +53,7 @@ class Event
|
|||||||
public $source;
|
public $source;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(Connection $connection, int $type, string $sql = null)
|
public function __construct(Connection $connection, int $type, ?string $sql = null)
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
@@ -70,7 +70,11 @@ class Event
|
|||||||
|
|
||||||
$dibiDir = dirname((new \ReflectionClass('dibi'))->getFileName()) . DIRECTORY_SEPARATOR;
|
$dibiDir = dirname((new \ReflectionClass('dibi'))->getFileName()) . DIRECTORY_SEPARATOR;
|
||||||
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $row) {
|
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $row) {
|
||||||
if (isset($row['file']) && is_file($row['file']) && strpos($row['file'], $dibiDir) !== 0) {
|
if (
|
||||||
|
isset($row['file'])
|
||||||
|
&& preg_match('~\.(php.?|phtml)$~', $row['file'])
|
||||||
|
&& substr($row['file'], 0, strlen($dibiDir)) !== $dibiDir
|
||||||
|
) {
|
||||||
$this->source = [$row['file'], (int) $row['line']];
|
$this->source = [$row['file'], (int) $row['line']];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ namespace Dibi;
|
|||||||
*
|
*
|
||||||
* @method Fluent select(...$field)
|
* @method Fluent select(...$field)
|
||||||
* @method Fluent distinct()
|
* @method Fluent distinct()
|
||||||
* @method Fluent from($table, ...$args)
|
* @method Fluent from($table, ...$args = null)
|
||||||
* @method Fluent where(...$cond)
|
* @method Fluent where(...$cond)
|
||||||
* @method Fluent groupBy(...$field)
|
* @method Fluent groupBy(...$field)
|
||||||
* @method Fluent having(...$cond)
|
* @method Fluent having(...$cond)
|
||||||
@@ -27,9 +27,21 @@ namespace Dibi;
|
|||||||
* @method Fluent innerJoin(...$table)
|
* @method Fluent innerJoin(...$table)
|
||||||
* @method Fluent rightJoin(...$table)
|
* @method Fluent rightJoin(...$table)
|
||||||
* @method Fluent outerJoin(...$table)
|
* @method Fluent outerJoin(...$table)
|
||||||
|
* @method Fluent union(Fluent $fluent)
|
||||||
|
* @method Fluent unionAll(Fluent $fluent)
|
||||||
* @method Fluent as(...$field)
|
* @method Fluent as(...$field)
|
||||||
* @method Fluent on(...$cond)
|
* @method Fluent on(...$cond)
|
||||||
|
* @method Fluent and(...$cond)
|
||||||
|
* @method Fluent or(...$cond)
|
||||||
* @method Fluent using(...$cond)
|
* @method Fluent using(...$cond)
|
||||||
|
* @method Fluent update(...$cond)
|
||||||
|
* @method Fluent insert(...$cond)
|
||||||
|
* @method Fluent delete(...$cond)
|
||||||
|
* @method Fluent into(...$cond)
|
||||||
|
* @method Fluent values(...$cond)
|
||||||
|
* @method Fluent set(...$args)
|
||||||
|
* @method Fluent asc()
|
||||||
|
* @method Fluent desc()
|
||||||
*/
|
*/
|
||||||
class Fluent implements IDataSource
|
class Fluent implements IDataSource
|
||||||
{
|
{
|
||||||
@@ -109,7 +121,7 @@ class Fluent implements IDataSource
|
|||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
|
|
||||||
if (self::$normalizer === null) {
|
if (self::$normalizer === null) {
|
||||||
self::$normalizer = new HashMap([__CLASS__, '_formatClause']);
|
self::$normalizer = new HashMap([self::class, '_formatClause']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +138,7 @@ class Fluent implements IDataSource
|
|||||||
if (isset(self::$masks[$clause])) {
|
if (isset(self::$masks[$clause])) {
|
||||||
$this->clauses = array_fill_keys(self::$masks[$clause], null);
|
$this->clauses = array_fill_keys(self::$masks[$clause], null);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cursor = &$this->clauses[$clause];
|
$this->cursor = &$this->clauses[$clause];
|
||||||
$this->cursor = [];
|
$this->cursor = [];
|
||||||
$this->command = $clause;
|
$this->command = $clause;
|
||||||
@@ -155,7 +168,6 @@ class Fluent implements IDataSource
|
|||||||
$this->cursor[] = $sep;
|
$this->cursor[] = $sep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// append to currect flow
|
// append to currect flow
|
||||||
if ($args === [self::REMOVE]) {
|
if ($args === [self::REMOVE]) {
|
||||||
@@ -193,6 +205,7 @@ class Fluent implements IDataSource
|
|||||||
if ($arg instanceof self) {
|
if ($arg instanceof self) {
|
||||||
$arg = new Literal("($arg)");
|
$arg = new Literal("($arg)");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cursor[] = $arg;
|
$this->cursor[] = $arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +248,7 @@ class Fluent implements IDataSource
|
|||||||
} else {
|
} else {
|
||||||
unset($this->flags[$flag]);
|
unset($this->flags[$flag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +295,7 @@ class Fluent implements IDataSource
|
|||||||
* @return Result|int|null result set or number of affected rows
|
* @return Result|int|null result set or number of affected rows
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function execute(string $return = null)
|
public function execute(?string $return = null)
|
||||||
{
|
{
|
||||||
$res = $this->query($this->_export());
|
$res = $this->query($this->_export());
|
||||||
switch ($return) {
|
switch ($return) {
|
||||||
@@ -301,11 +315,9 @@ class Fluent implements IDataSource
|
|||||||
*/
|
*/
|
||||||
public function fetch()
|
public function fetch()
|
||||||
{
|
{
|
||||||
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
|
return $this->command === 'SELECT' && !$this->clauses['LIMIT']
|
||||||
return $this->query($this->_export(null, ['%lmt', 1]))->fetch();
|
? $this->query($this->_export(null, ['%lmt', 1]))->fetch()
|
||||||
} else {
|
: $this->query($this->_export())->fetch();
|
||||||
return $this->query($this->_export())->fetch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -315,18 +327,16 @@ class Fluent implements IDataSource
|
|||||||
*/
|
*/
|
||||||
public function fetchSingle()
|
public function fetchSingle()
|
||||||
{
|
{
|
||||||
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
|
return $this->command === 'SELECT' && !$this->clauses['LIMIT']
|
||||||
return $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle();
|
? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle()
|
||||||
} else {
|
: $this->query($this->_export())->fetchSingle();
|
||||||
return $this->query($this->_export())->fetchSingle();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all records from table.
|
* Fetches all records from table.
|
||||||
*/
|
*/
|
||||||
public function fetchAll(int $offset = null, int $limit = null): array
|
public function fetchAll(?int $offset = null, ?int $limit = null): array
|
||||||
{
|
{
|
||||||
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll();
|
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll();
|
||||||
}
|
}
|
||||||
@@ -345,7 +355,7 @@ class Fluent implements IDataSource
|
|||||||
/**
|
/**
|
||||||
* Fetches all records from table like $key => $value pairs.
|
* Fetches all records from table like $key => $value pairs.
|
||||||
*/
|
*/
|
||||||
public function fetchPairs(string $key = null, string $value = null): array
|
public function fetchPairs(?string $key = null, ?string $value = null): array
|
||||||
{
|
{
|
||||||
return $this->query($this->_export())->fetchPairs($key, $value);
|
return $this->query($this->_export())->fetchPairs($key, $value);
|
||||||
}
|
}
|
||||||
@@ -354,7 +364,7 @@ class Fluent implements IDataSource
|
|||||||
/**
|
/**
|
||||||
* Required by the IteratorAggregate interface.
|
* Required by the IteratorAggregate interface.
|
||||||
*/
|
*/
|
||||||
public function getIterator(int $offset = null, int $limit = null): ResultIterator
|
public function getIterator(?int $offset = null, ?int $limit = null): ResultIterator
|
||||||
{
|
{
|
||||||
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator();
|
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator();
|
||||||
}
|
}
|
||||||
@@ -363,7 +373,7 @@ class Fluent implements IDataSource
|
|||||||
/**
|
/**
|
||||||
* Generates and prints SQL query or it's part.
|
* Generates and prints SQL query or it's part.
|
||||||
*/
|
*/
|
||||||
public function test(string $clause = null): bool
|
public function test(?string $clause = null): bool
|
||||||
{
|
{
|
||||||
return $this->connection->test($this->_export($clause));
|
return $this->connection->test($this->_export($clause));
|
||||||
}
|
}
|
||||||
@@ -384,6 +394,7 @@ class Fluent implements IDataSource
|
|||||||
$method = array_shift($setup);
|
$method = array_shift($setup);
|
||||||
$res->$method(...$setup);
|
$res->$method(...$setup);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,15 +425,14 @@ class Fluent implements IDataSource
|
|||||||
/**
|
/**
|
||||||
* Generates parameters for Translator.
|
* Generates parameters for Translator.
|
||||||
*/
|
*/
|
||||||
protected function _export(string $clause = null, array $args = []): array
|
protected function _export(?string $clause = null, array $args = []): array
|
||||||
{
|
{
|
||||||
if ($clause === null) {
|
if ($clause === null) {
|
||||||
$data = $this->clauses;
|
$data = $this->clauses;
|
||||||
if ($this->command === 'SELECT' && ($data['LIMIT'] || $data['OFFSET'])) {
|
if ($this->command === 'SELECT' && ($data['LIMIT'] || $data['OFFSET'])) {
|
||||||
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0], $data['OFFSET'][0]], $args);
|
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args);
|
||||||
unset($data['LIMIT'], $data['OFFSET']);
|
unset($data['LIMIT'], $data['OFFSET']);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$clause = self::$normalizer->$clause;
|
$clause = self::$normalizer->$clause;
|
||||||
if (array_key_exists($clause, $this->clauses)) {
|
if (array_key_exists($clause, $this->clauses)) {
|
||||||
@@ -438,6 +448,7 @@ class Fluent implements IDataSource
|
|||||||
if ($clause === $this->command && $this->flags) {
|
if ($clause === $this->command && $this->flags) {
|
||||||
$args[] = implode(' ', array_keys($this->flags));
|
$args[] = implode(' ', array_keys($this->flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($statement as $arg) {
|
foreach ($statement as $arg) {
|
||||||
$args[] = $arg;
|
$args[] = $arg;
|
||||||
}
|
}
|
||||||
@@ -458,6 +469,7 @@ class Fluent implements IDataSource
|
|||||||
$s .= 'By';
|
$s .= 'By';
|
||||||
trigger_error("Did you mean '$s'?", E_USER_NOTICE);
|
trigger_error("Did you mean '$s'?", E_USER_NOTICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
|
return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,6 +481,7 @@ class Fluent implements IDataSource
|
|||||||
$this->clauses[$clause] = &$val;
|
$this->clauses[$clause] = &$val;
|
||||||
unset($val);
|
unset($val);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cursor = &$foo;
|
$this->cursor = &$foo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ namespace Dibi;
|
|||||||
* Lazy cached storage.
|
* Lazy cached storage.
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
#[\AllowDynamicProperties]
|
||||||
abstract class HashMapBase
|
abstract class HashMapBase
|
||||||
{
|
{
|
||||||
/** @var callable */
|
/** @var callable */
|
||||||
@@ -50,6 +51,7 @@ final class HashMap extends HashMapBase
|
|||||||
if ($nm === '') {
|
if ($nm === '') {
|
||||||
$nm = "\xFF";
|
$nm = "\xFF";
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->$nm = $val;
|
$this->$nm = $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ final class HashMap extends HashMapBase
|
|||||||
{
|
{
|
||||||
if ($nm === '') {
|
if ($nm === '') {
|
||||||
$nm = "\xFF";
|
$nm = "\xFF";
|
||||||
return isset($this->$nm) ? $this->$nm : $this->$nm = $this->getCallback()('');
|
return isset($this->$nm) && true ? $this->$nm : $this->$nm = $this->getCallback()('');
|
||||||
} else {
|
} else {
|
||||||
return $this->$nm = $this->getCallback()($nm);
|
return $this->$nm = $this->getCallback()($nm);
|
||||||
}
|
}
|
||||||
|
@@ -41,6 +41,7 @@ class Helpers
|
|||||||
$spaces = $maxLen - mb_strlen($col) + 2;
|
$spaces = $maxLen - mb_strlen($col) + 2;
|
||||||
echo "$col" . str_repeat(' ', $spaces) . "$val\n";
|
echo "$col" . str_repeat(' ', $spaces) . "$val\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "\n";
|
echo "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ class Helpers
|
|||||||
foreach ($row as $col => $foo) {
|
foreach ($row as $col => $foo) {
|
||||||
echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n";
|
echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "\t</tr>\n</thead>\n<tbody>\n";
|
echo "\t</tr>\n</thead>\n<tbody>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ class Helpers
|
|||||||
foreach ($row as $col) {
|
foreach ($row as $col) {
|
||||||
echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n";
|
echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "\t</tr>\n";
|
echo "\t</tr>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +107,7 @@ class Helpers
|
|||||||
}
|
}
|
||||||
}, $sql);
|
}, $sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo trim($sql) . "\n\n";
|
echo trim($sql) . "\n\n";
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -143,13 +147,14 @@ class Helpers
|
|||||||
{
|
{
|
||||||
$best = null;
|
$best = null;
|
||||||
$min = (strlen($value) / 4 + 1) * 10 + .1;
|
$min = (strlen($value) / 4 + 1) * 10 + .1;
|
||||||
foreach (array_unique($items, SORT_REGULAR) as $item) {
|
$items = array_map('strval', $items);
|
||||||
$item = is_object($item) ? $item->getName() : $item;
|
foreach (array_unique($items) as $item) {
|
||||||
if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
|
if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
|
||||||
$min = $len;
|
$min = $len;
|
||||||
$best = $item;
|
$best = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $best;
|
return $best;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +186,7 @@ class Helpers
|
|||||||
{
|
{
|
||||||
static $patterns = [
|
static $patterns = [
|
||||||
'^_' => Type::TEXT, // PostgreSQL arrays
|
'^_' => Type::TEXT, // PostgreSQL arrays
|
||||||
|
'RANGE$' => Type::TEXT, // PostgreSQL range types
|
||||||
'BYTEA|BLOB|BIN' => Type::BINARY,
|
'BYTEA|BLOB|BIN' => Type::BINARY,
|
||||||
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
|
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
|
||||||
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
|
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
|
||||||
@@ -197,18 +203,18 @@ class Helpers
|
|||||||
return $val;
|
return $val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @internal */
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public static function getTypeCache(): HashMap
|
public static function getTypeCache(): HashMap
|
||||||
{
|
{
|
||||||
if (self::$types === null) {
|
if (self::$types === null) {
|
||||||
self::$types = new HashMap([__CLASS__, 'detectType']);
|
self::$types = new HashMap([self::class, 'detectType']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$types;
|
return self::$types;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +240,7 @@ class Helpers
|
|||||||
* Import SQL dump from file.
|
* Import SQL dump from file.
|
||||||
* @return int count of sql commands
|
* @return int count of sql commands
|
||||||
*/
|
*/
|
||||||
public static function loadFromFile(Connection $connection, string $file, callable $onProgress = null): int
|
public static function loadFromFile(Connection $connection, string $file, ?callable $onProgress = null): int
|
||||||
{
|
{
|
||||||
@set_time_limit(0); // intentionally @
|
@set_time_limit(0); // intentionally @
|
||||||
|
|
||||||
@@ -261,7 +267,6 @@ class Helpers
|
|||||||
if ($onProgress) {
|
if ($onProgress) {
|
||||||
$onProgress($count, isset($stat['size']) ? $size * 100 / $stat['size'] : null);
|
$onProgress($count, isset($stat['size']) ? $size * 100 / $stat['size'] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$sql .= $s;
|
$sql .= $s;
|
||||||
}
|
}
|
||||||
@@ -274,23 +279,20 @@ class Helpers
|
|||||||
$onProgress($count, isset($stat['size']) ? 100 : null);
|
$onProgress($count, isset($stat['size']) ? 100 : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
return $count;
|
return $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @internal */
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public static function false2Null($val)
|
public static function false2Null($val)
|
||||||
{
|
{
|
||||||
return $val === false ? null : $val;
|
return $val === false ? null : $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @internal */
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public static function intVal($value): int
|
public static function intVal($value): int
|
||||||
{
|
{
|
||||||
if (is_int($value)) {
|
if (is_int($value)) {
|
||||||
@@ -299,6 +301,7 @@ class Helpers
|
|||||||
if (is_float($value * 1)) {
|
if (is_float($value * 1)) {
|
||||||
throw new Exception("Number $value is greater than integer.");
|
throw new Exception("Number $value is greater than integer.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) $value;
|
return (int) $value;
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("Expected number, '$value' given.");
|
throw new Exception("Expected number, '$value' given.");
|
||||||
|
@@ -25,11 +25,15 @@ class FileLogger
|
|||||||
/** @var int */
|
/** @var int */
|
||||||
public $filter;
|
public $filter;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $errorsOnly;
|
||||||
|
|
||||||
public function __construct(string $file, int $filter = null)
|
|
||||||
|
public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false)
|
||||||
{
|
{
|
||||||
$this->file = $file;
|
$this->file = $file;
|
||||||
$this->filter = $filter ?: Dibi\Event::QUERY;
|
$this->filter = $filter ?: Dibi\Event::QUERY;
|
||||||
|
$this->errorsOnly = $errorsOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -38,39 +42,43 @@ class FileLogger
|
|||||||
*/
|
*/
|
||||||
public function logEvent(Dibi\Event $event): void
|
public function logEvent(Dibi\Event $event): void
|
||||||
{
|
{
|
||||||
if (($event->type & $this->filter) === 0) {
|
if (
|
||||||
|
(($event->type & $this->filter) === 0)
|
||||||
|
|| ($this->errorsOnly === true && !$event->result instanceof \Exception)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$handle = fopen($this->file, 'a');
|
|
||||||
if (!$handle) {
|
|
||||||
return; // or throw exception?
|
|
||||||
}
|
|
||||||
flock($handle, LOCK_EX);
|
|
||||||
|
|
||||||
if ($event->result instanceof \Exception) {
|
if ($event->result instanceof \Exception) {
|
||||||
$message = $event->result->getMessage();
|
$message = $event->result->getMessage();
|
||||||
if ($code = $event->result->getCode()) {
|
if ($code = $event->result->getCode()) {
|
||||||
$message = "[$code] $message";
|
$message = "[$code] $message";
|
||||||
}
|
}
|
||||||
fwrite($handle,
|
|
||||||
|
$this->writeToFile(
|
||||||
|
$event,
|
||||||
"ERROR: $message"
|
"ERROR: $message"
|
||||||
. "\n-- SQL: " . $event->sql
|
. "\n-- SQL: " . $event->sql
|
||||||
. "\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name')
|
|
||||||
. ";\n-- " . date('Y-m-d H:i:s')
|
|
||||||
. "\n\n"
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
fwrite($handle,
|
$this->writeToFile(
|
||||||
|
$event,
|
||||||
'OK: ' . $event->sql
|
'OK: ' . $event->sql
|
||||||
. ($event->count ? ";\n-- rows: " . $event->count : '')
|
. ($event->count ? ";\n-- rows: " . $event->count : '')
|
||||||
. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
|
. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
|
||||||
. "\n-- source: " . implode(':', $event->source)
|
. "\n-- source: " . implode(':', $event->source)
|
||||||
. "\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name')
|
|
||||||
. "\n-- " . date('Y-m-d H:i:s')
|
|
||||||
. "\n\n"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fclose($handle);
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function writeToFile(Dibi\Event $event, string $message): void
|
||||||
|
{
|
||||||
|
$driver = $event->connection->getConfig('driver');
|
||||||
|
$message .=
|
||||||
|
"\n-- driver: " . (is_object($driver) ? get_class($driver) : $driver) . '/' . $event->connection->getConfig('name')
|
||||||
|
. "\n-- " . date('Y-m-d H:i:s')
|
||||||
|
. "\n\n";
|
||||||
|
file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@ class Column
|
|||||||
private $info;
|
private $info;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(Dibi\Reflector $reflector = null, array $info)
|
public function __construct(?Dibi\Reflector $reflector, array $info)
|
||||||
{
|
{
|
||||||
$this->reflector = $reflector;
|
$this->reflector = $reflector;
|
||||||
$this->info = $info;
|
$this->info = $info;
|
||||||
@@ -66,17 +66,20 @@ class Column
|
|||||||
if (empty($this->info['table']) || !$this->reflector) {
|
if (empty($this->info['table']) || !$this->reflector) {
|
||||||
throw new Dibi\Exception('Table is unknown or not available.');
|
throw new Dibi\Exception('Table is unknown or not available.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Table($this->reflector, ['name' => $this->info['table']]);
|
return new Table($this->reflector, ['name' => $this->info['table']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getTableName(): ?string
|
public function getTableName(): ?string
|
||||||
{
|
{
|
||||||
return isset($this->info['table']) && $this->info['table'] != null ? $this->info['table'] : null; // intentionally ==
|
return isset($this->info['table']) && $this->info['table'] != null // intentionally ==
|
||||||
|
? $this->info['table']
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getType(): string
|
public function getType(): ?string
|
||||||
{
|
{
|
||||||
return Dibi\Helpers::getTypeCache()->{$this->info['nativetype']};
|
return Dibi\Helpers::getTypeCache()->{$this->info['nativetype']};
|
||||||
}
|
}
|
||||||
@@ -106,18 +109,14 @@ class Column
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return mixed */
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function getDefault()
|
public function getDefault()
|
||||||
{
|
{
|
||||||
return $this->info['default'] ?? null;
|
return $this->info['default'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return mixed */
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function getVendorInfo(string $key)
|
public function getVendorInfo(string $key)
|
||||||
{
|
{
|
||||||
return $this->info['vendor'][$key] ?? null;
|
return $this->info['vendor'][$key] ?? null;
|
||||||
|
@@ -33,7 +33,7 @@ class Database
|
|||||||
private $tables;
|
private $tables;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(Dibi\Reflector $reflector, string $name = null)
|
public function __construct(Dibi\Reflector $reflector, ?string $name = null)
|
||||||
{
|
{
|
||||||
$this->reflector = $reflector;
|
$this->reflector = $reflector;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
@@ -46,9 +46,7 @@ class Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return Table[] */
|
||||||
* @return Table[]
|
|
||||||
*/
|
|
||||||
public function getTables(): array
|
public function getTables(): array
|
||||||
{
|
{
|
||||||
$this->init();
|
$this->init();
|
||||||
@@ -56,9 +54,7 @@ class Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return string[] */
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getTableNames(): array
|
public function getTableNames(): array
|
||||||
{
|
{
|
||||||
$this->init();
|
$this->init();
|
||||||
@@ -66,6 +62,7 @@ class Database
|
|||||||
foreach ($this->tables as $table) {
|
foreach ($this->tables as $table) {
|
||||||
$res[] = $table->getName();
|
$res[] = $table->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@ class Result
|
|||||||
/** @var Column[]|null */
|
/** @var Column[]|null */
|
||||||
private $columns;
|
private $columns;
|
||||||
|
|
||||||
/** @var string[]|null */
|
/** @var Column[]|null */
|
||||||
private $names;
|
private $names;
|
||||||
|
|
||||||
|
|
||||||
@@ -38,9 +38,7 @@ class Result
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return Column[] */
|
||||||
* @return Column[]
|
|
||||||
*/
|
|
||||||
public function getColumns(): array
|
public function getColumns(): array
|
||||||
{
|
{
|
||||||
$this->initColumns();
|
$this->initColumns();
|
||||||
@@ -48,9 +46,7 @@ class Result
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return string[] */
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getColumnNames(bool $fullNames = false): array
|
public function getColumnNames(bool $fullNames = false): array
|
||||||
{
|
{
|
||||||
$this->initColumns();
|
$this->initColumns();
|
||||||
@@ -58,6 +54,7 @@ class Result
|
|||||||
foreach ($this->columns as $column) {
|
foreach ($this->columns as $column) {
|
||||||
$res[] = $fullNames ? $column->getFullName() : $column->getName();
|
$res[] = $fullNames ? $column->getFullName() : $column->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +83,9 @@ class Result
|
|||||||
{
|
{
|
||||||
if ($this->columns === null) {
|
if ($this->columns === null) {
|
||||||
$this->columns = [];
|
$this->columns = [];
|
||||||
$reflector = $this->driver instanceof Dibi\Reflector ? $this->driver : null;
|
$reflector = $this->driver instanceof Dibi\Reflector
|
||||||
|
? $this->driver
|
||||||
|
: null;
|
||||||
foreach ($this->driver->getResultColumns() as $info) {
|
foreach ($this->driver->getResultColumns() as $info) {
|
||||||
$this->columns[] = $this->names[strtolower($info['name'])] = new Column($reflector, $info);
|
$this->columns[] = $this->names[strtolower($info['name'])] = new Column($reflector, $info);
|
||||||
}
|
}
|
||||||
|
@@ -69,9 +69,7 @@ class Table
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return Column[] */
|
||||||
* @return Column[]
|
|
||||||
*/
|
|
||||||
public function getColumns(): array
|
public function getColumns(): array
|
||||||
{
|
{
|
||||||
$this->initColumns();
|
$this->initColumns();
|
||||||
@@ -79,9 +77,7 @@ class Table
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return string[] */
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getColumnNames(): array
|
public function getColumnNames(): array
|
||||||
{
|
{
|
||||||
$this->initColumns();
|
$this->initColumns();
|
||||||
@@ -89,6 +85,7 @@ class Table
|
|||||||
foreach ($this->columns as $column) {
|
foreach ($this->columns as $column) {
|
||||||
$res[] = $column->getName();
|
$res[] = $column->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +110,7 @@ class Table
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return ForeignKey[] */
|
||||||
* @return ForeignKey[]
|
|
||||||
*/
|
|
||||||
public function getForeignKeys(): array
|
public function getForeignKeys(): array
|
||||||
{
|
{
|
||||||
$this->initForeignKeys();
|
$this->initForeignKeys();
|
||||||
@@ -123,9 +118,7 @@ class Table
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return Index[] */
|
||||||
* @return Index[]
|
|
||||||
*/
|
|
||||||
public function getIndexes(): array
|
public function getIndexes(): array
|
||||||
{
|
{
|
||||||
$this->initIndexes();
|
$this->initIndexes();
|
||||||
@@ -160,6 +153,7 @@ class Table
|
|||||||
foreach ($info['columns'] as $key => $name) {
|
foreach ($info['columns'] as $key => $name) {
|
||||||
$info['columns'][$key] = $this->columns[strtolower($name)];
|
$info['columns'][$key] = $this->columns[strtolower($name)];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->indexes[strtolower($info['name'])] = new Index($info);
|
$this->indexes[strtolower($info['name'])] = new Index($info);
|
||||||
if (!empty($info['primary'])) {
|
if (!empty($info['primary'])) {
|
||||||
$this->primaryKey = $this->indexes[strtolower($info['name'])];
|
$this->primaryKey = $this->indexes[strtolower($info['name'])];
|
||||||
|
@@ -19,7 +19,7 @@ class Result implements IDataSource
|
|||||||
{
|
{
|
||||||
use Strict;
|
use Strict;
|
||||||
|
|
||||||
/** @var ResultDriver */
|
/** @var ResultDriver|null */
|
||||||
private $driver;
|
private $driver;
|
||||||
|
|
||||||
/** @var array Translate table */
|
/** @var array Translate table */
|
||||||
@@ -41,10 +41,12 @@ class Result implements IDataSource
|
|||||||
private $formats = [];
|
private $formats = [];
|
||||||
|
|
||||||
|
|
||||||
public function __construct(ResultDriver $driver)
|
public function __construct(ResultDriver $driver, bool $normalize = true)
|
||||||
{
|
{
|
||||||
$this->driver = $driver;
|
$this->driver = $driver;
|
||||||
$this->detectTypes();
|
if ($normalize) {
|
||||||
|
$this->detectTypes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +85,9 @@ class Result implements IDataSource
|
|||||||
*/
|
*/
|
||||||
final public function seek(int $row): bool
|
final public function seek(int $row): bool
|
||||||
{
|
{
|
||||||
return ($row !== 0 || $this->fetched) ? $this->getResultDriver()->seek($row) : true;
|
return ($row !== 0 || $this->fetched)
|
||||||
|
? $this->getResultDriver()->seek($row)
|
||||||
|
: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -166,6 +170,7 @@ class Result implements IDataSource
|
|||||||
if ($row === null) {
|
if ($row === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->fetched = true;
|
$this->fetched = true;
|
||||||
$this->normalize($row);
|
$this->normalize($row);
|
||||||
if ($this->rowFactory) {
|
if ($this->rowFactory) {
|
||||||
@@ -173,6 +178,7 @@ class Result implements IDataSource
|
|||||||
} elseif ($this->rowClass) {
|
} elseif ($this->rowClass) {
|
||||||
return new $this->rowClass($row);
|
return new $this->rowClass($row);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +193,7 @@ class Result implements IDataSource
|
|||||||
if ($row === null) {
|
if ($row === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->fetched = true;
|
$this->fetched = true;
|
||||||
$this->normalize($row);
|
$this->normalize($row);
|
||||||
return reset($row);
|
return reset($row);
|
||||||
@@ -197,9 +204,9 @@ class Result implements IDataSource
|
|||||||
* Fetches all records from table.
|
* Fetches all records from table.
|
||||||
* @return Row[]|array[]
|
* @return Row[]|array[]
|
||||||
*/
|
*/
|
||||||
final public function fetchAll(int $offset = null, int $limit = null): array
|
final public function fetchAll(?int $offset = null, ?int $limit = null): array
|
||||||
{
|
{
|
||||||
$limit = $limit === null ? -1 : $limit;
|
$limit = $limit ?? -1;
|
||||||
$this->seek($offset ?: 0);
|
$this->seek($offset ?: 0);
|
||||||
$row = $this->fetch();
|
$row = $this->fetch();
|
||||||
if (!$row) {
|
if (!$row) {
|
||||||
@@ -211,6 +218,7 @@ class Result implements IDataSource
|
|||||||
if ($limit === 0) {
|
if ($limit === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$limit--;
|
$limit--;
|
||||||
$data[] = $row;
|
$data[] = $row;
|
||||||
} while ($row = $this->fetch());
|
} while ($row = $this->fetch());
|
||||||
@@ -242,6 +250,9 @@ class Result implements IDataSource
|
|||||||
|
|
||||||
$data = null;
|
$data = null;
|
||||||
$assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
$assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||||
|
if (!$assoc) {
|
||||||
|
throw new \InvalidArgumentException("Invalid descriptor '$assoc'.");
|
||||||
|
}
|
||||||
|
|
||||||
// check columns
|
// check columns
|
||||||
foreach ($assoc as $as) {
|
foreach ($assoc as $as) {
|
||||||
@@ -280,9 +291,8 @@ class Result implements IDataSource
|
|||||||
} else {
|
} else {
|
||||||
$x = &$x->{$assoc[$i + 1]};
|
$x = &$x->{$assoc[$i + 1]};
|
||||||
}
|
}
|
||||||
|
|
||||||
} elseif ($as !== '|') { // associative-array node
|
} elseif ($as !== '|') { // associative-array node
|
||||||
$x = &$x[$row->$as];
|
$x = &$x[(string) $row->$as];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,13 +302,12 @@ class Result implements IDataSource
|
|||||||
} while ($row = $this->fetch());
|
} while ($row = $this->fetch());
|
||||||
|
|
||||||
unset($x);
|
unset($x);
|
||||||
|
/** @var mixed[] $data */
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @deprecated */
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
private function oldFetchAssoc(string $assoc)
|
private function oldFetchAssoc(string $assoc)
|
||||||
{
|
{
|
||||||
$this->seek(0);
|
$this->seek(0);
|
||||||
@@ -339,7 +348,6 @@ class Result implements IDataSource
|
|||||||
} else {
|
} else {
|
||||||
$x = &$x[$assoc[$i + 1]];
|
$x = &$x[$assoc[$i + 1]];
|
||||||
}
|
}
|
||||||
|
|
||||||
} elseif ($as === '@') { // "object" node
|
} elseif ($as === '@') { // "object" node
|
||||||
if ($x === null) {
|
if ($x === null) {
|
||||||
$x = clone $row;
|
$x = clone $row;
|
||||||
@@ -348,18 +356,15 @@ class Result implements IDataSource
|
|||||||
} else {
|
} else {
|
||||||
$x = &$x->{$assoc[$i + 1]};
|
$x = &$x->{$assoc[$i + 1]};
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // associative-array node
|
} else { // associative-array node
|
||||||
$x = &$x[$row->$as];
|
$x = &$x[(string) $row->$as];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($x === null) { // build leaf
|
if ($x === null) { // build leaf
|
||||||
if ($leaf === '=') {
|
$x = $leaf === '='
|
||||||
$x = $row->toArray();
|
? $row->toArray()
|
||||||
} else {
|
: $row;
|
||||||
$x = $row;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} while ($row = $this->fetch());
|
} while ($row = $this->fetch());
|
||||||
|
|
||||||
@@ -372,7 +377,7 @@ class Result implements IDataSource
|
|||||||
* Fetches all records from table like $key => $value pairs.
|
* Fetches all records from table like $key => $value pairs.
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
final public function fetchPairs(string $key = null, string $value = null): array
|
final public function fetchPairs(?string $key = null, ?string $value = null): array
|
||||||
{
|
{
|
||||||
$this->seek(0);
|
$this->seek(0);
|
||||||
$row = $this->fetch();
|
$row = $this->fetch();
|
||||||
@@ -394,6 +399,7 @@ class Result implements IDataSource
|
|||||||
do {
|
do {
|
||||||
$data[] = $row[$key];
|
$data[] = $row[$key];
|
||||||
} while ($row = $this->fetch());
|
} while ($row = $this->fetch());
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,6 +414,7 @@ class Result implements IDataSource
|
|||||||
do {
|
do {
|
||||||
$data[] = $row[$value];
|
$data[] = $row[$value];
|
||||||
} while ($row = $this->fetch());
|
} while ($row = $this->fetch());
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,8 +458,14 @@ class Result implements IDataSource
|
|||||||
if (!isset($row[$key])) { // null
|
if (!isset($row[$key])) { // null
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = $row[$key];
|
$value = $row[$key];
|
||||||
if ($type === Type::TEXT) {
|
$format = $this->formats[$type] ?? null;
|
||||||
|
|
||||||
|
if ($type === null || $format === 'native') {
|
||||||
|
$row[$key] = $value;
|
||||||
|
|
||||||
|
} elseif ($type === Type::TEXT) {
|
||||||
$row[$key] = (string) $value;
|
$row[$key] = (string) $value;
|
||||||
|
|
||||||
} elseif ($type === Type::INTEGER) {
|
} elseif ($type === Type::INTEGER) {
|
||||||
@@ -463,12 +476,17 @@ class Result implements IDataSource
|
|||||||
} elseif ($type === Type::FLOAT) {
|
} elseif ($type === Type::FLOAT) {
|
||||||
$value = ltrim((string) $value, '0');
|
$value = ltrim((string) $value, '0');
|
||||||
$p = strpos($value, '.');
|
$p = strpos($value, '.');
|
||||||
if ($p !== false) {
|
$e = strpos($value, 'e');
|
||||||
|
if ($p !== false && $e === false) {
|
||||||
$value = rtrim(rtrim($value, '0'), '.');
|
$value = rtrim(rtrim($value, '0'), '.');
|
||||||
|
} elseif ($p !== false && $e !== false) {
|
||||||
|
$value = rtrim($value, '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value === '' || $value[0] === '.') {
|
if ($value === '' || $value[0] === '.') {
|
||||||
$value = '0' . $value;
|
$value = '0' . $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
|
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
|
||||||
? $float
|
? $float
|
||||||
: $value;
|
: $value;
|
||||||
@@ -477,23 +495,31 @@ class Result implements IDataSource
|
|||||||
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
|
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
|
||||||
|
|
||||||
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
|
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
|
||||||
if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ...
|
if ($value && substr((string) $value, 0, 7) !== '0000-00') { // '', null, false, '0000-00-00', ...
|
||||||
$value = new DateTime($value);
|
$value = new DateTime($value);
|
||||||
$row[$key] = empty($this->formats[$type]) ? $value : $value->format($this->formats[$type]);
|
$row[$key] = $format ? $value->format($format) : $value;
|
||||||
} else {
|
} else {
|
||||||
$row[$key] = null;
|
$row[$key] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
} elseif ($type === Type::TIME_INTERVAL) {
|
} elseif ($type === Type::TIME_INTERVAL) {
|
||||||
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
|
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
|
||||||
$row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
|
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
|
||||||
$row[$key]->invert = (int) (bool) $m[1];
|
$value->invert = (int) (bool) $m[1];
|
||||||
|
$row[$key] = $format ? $value->format($format) : $value;
|
||||||
|
|
||||||
} elseif ($type === Type::BINARY) {
|
} elseif ($type === Type::BINARY) {
|
||||||
$row[$key] = is_string($value) ? $this->getResultDriver()->unescapeBinary($value) : $value;
|
$row[$key] = is_string($value)
|
||||||
|
? $this->getResultDriver()->unescapeBinary($value)
|
||||||
|
: $value;
|
||||||
|
|
||||||
} elseif ($type === Type::JSON) {
|
} elseif ($type === Type::JSON) {
|
||||||
$row[$key] = json_decode($value, true);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,9 +527,9 @@ class Result implements IDataSource
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Define column type.
|
* Define column type.
|
||||||
* @param string $type use constant Type::*
|
* @param string|null $type use constant Type::*
|
||||||
*/
|
*/
|
||||||
final public function setType(string $column, string $type): self
|
final public function setType(string $column, ?string $type): self
|
||||||
{
|
{
|
||||||
$this->types[$column] = $type;
|
$this->types[$column] = $type;
|
||||||
return $this;
|
return $this;
|
||||||
@@ -513,14 +539,23 @@ class Result implements IDataSource
|
|||||||
/**
|
/**
|
||||||
* Returns column type.
|
* Returns column type.
|
||||||
*/
|
*/
|
||||||
final public function getType(string $column): string
|
final public function getType(string $column): ?string
|
||||||
{
|
{
|
||||||
return $this->types[$column] ?? null;
|
return $this->types[$column] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets date format.
|
* Returns columns type.
|
||||||
|
*/
|
||||||
|
final public function getTypes(): array
|
||||||
|
{
|
||||||
|
return $this->types;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets type format.
|
||||||
*/
|
*/
|
||||||
final public function setFormat(string $type, ?string $format): self
|
final public function setFormat(string $type, ?string $format): self
|
||||||
{
|
{
|
||||||
@@ -529,6 +564,16 @@ class Result implements IDataSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets type formats.
|
||||||
|
*/
|
||||||
|
final public function setFormats(array $formats): self
|
||||||
|
{
|
||||||
|
$this->formats = $formats;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns data format.
|
* Returns data format.
|
||||||
*/
|
*/
|
||||||
@@ -549,13 +594,12 @@ class Result implements IDataSource
|
|||||||
if ($this->meta === null) {
|
if ($this->meta === null) {
|
||||||
$this->meta = new Reflection\Result($this->getResultDriver());
|
$this->meta = new Reflection\Result($this->getResultDriver());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->meta;
|
return $this->meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @return Reflection\Column[] */
|
||||||
* @return Reflection\Column[]
|
|
||||||
*/
|
|
||||||
final public function getColumns(): array
|
final public function getColumns(): array
|
||||||
{
|
{
|
||||||
return $this->getInfo()->getColumns();
|
return $this->getInfo()->getColumns();
|
||||||
|
@@ -48,6 +48,7 @@ class ResultIterator implements \Iterator, \Countable
|
|||||||
* Returns the key of the current element.
|
* Returns the key of the current element.
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function key()
|
public function key()
|
||||||
{
|
{
|
||||||
return $this->pointer;
|
return $this->pointer;
|
||||||
@@ -58,6 +59,7 @@ class ResultIterator implements \Iterator, \Countable
|
|||||||
* Returns the current element.
|
* Returns the current element.
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function current()
|
public function current()
|
||||||
{
|
{
|
||||||
return $this->row;
|
return $this->row;
|
||||||
|
@@ -13,6 +13,7 @@ namespace Dibi;
|
|||||||
/**
|
/**
|
||||||
* Result set single row.
|
* Result set single row.
|
||||||
*/
|
*/
|
||||||
|
#[\AllowDynamicProperties]
|
||||||
class Row implements \ArrayAccess, \IteratorAggregate, \Countable
|
class Row implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||||
{
|
{
|
||||||
public function __construct(array $arr)
|
public function __construct(array $arr)
|
||||||
@@ -33,19 +34,24 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||||||
* Converts value to DateTime object.
|
* Converts value to DateTime object.
|
||||||
* @return DateTime|string|null
|
* @return DateTime|string|null
|
||||||
*/
|
*/
|
||||||
public function asDateTime(string $key, string $format = null)
|
public function asDateTime(string $key, ?string $format = null)
|
||||||
{
|
{
|
||||||
$time = $this[$key];
|
$time = $this[$key];
|
||||||
if (!$time instanceof DateTime) {
|
if (!$time instanceof DateTime) {
|
||||||
if (!$time || substr((string) $time, 0, 3) === '000') { // '', null, false, '0000-00-00', ...
|
if (!$time || substr((string) $time, 0, 7) === '0000-00') { // '', null, false, '0000-00-00', ...
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$time = new DateTime($time);
|
$time = new DateTime($time);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $format === null ? $time : $time->format($format);
|
return $format === null ? $time : $time->format($format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
public function __get(string $key)
|
public function __get(string $key)
|
||||||
{
|
{
|
||||||
$hint = Helpers::getSuggestion(array_keys((array) $this), $key);
|
$hint = Helpers::getSuggestion(array_keys((array) $this), $key);
|
||||||
@@ -53,40 +59,47 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function __isset(string $key): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/********************* interfaces ArrayAccess, Countable & IteratorAggregate ****************d*g**/
|
/********************* interfaces ArrayAccess, Countable & IteratorAggregate ****************d*g**/
|
||||||
|
|
||||||
|
|
||||||
final public function count()
|
final public function count(): int
|
||||||
{
|
{
|
||||||
return count((array) $this);
|
return count((array) $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final public function getIterator()
|
final public function getIterator(): \ArrayIterator
|
||||||
{
|
{
|
||||||
return new \ArrayIterator($this);
|
return new \ArrayIterator($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final public function offsetSet($nm, $val)
|
final public function offsetSet($nm, $val): void
|
||||||
{
|
{
|
||||||
$this->$nm = $val;
|
$this->$nm = $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
final public function offsetGet($nm)
|
final public function offsetGet($nm)
|
||||||
{
|
{
|
||||||
return $this->$nm;
|
return $this->$nm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final public function offsetExists($nm)
|
final public function offsetExists($nm): bool
|
||||||
{
|
{
|
||||||
return isset($this->$nm);
|
return isset($this->$nm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final public function offsetUnset($nm)
|
final public function offsetUnset($nm): void
|
||||||
{
|
{
|
||||||
unset($this->$nm);
|
unset($this->$nm);
|
||||||
}
|
}
|
||||||
|
@@ -29,15 +29,12 @@ trait Strict
|
|||||||
*/
|
*/
|
||||||
public function __call(string $name, array $args)
|
public function __call(string $name, array $args)
|
||||||
{
|
{
|
||||||
$class = get_class($this);
|
$class = method_exists($this, $name) ? 'parent' : static::class;
|
||||||
if ($cb = self::extensionMethod($class . '::' . $name)) { // back compatiblity
|
|
||||||
trigger_error("Extension methods such as $class::$name() are deprecated", E_USER_DEPRECATED);
|
|
||||||
array_unshift($args, $this);
|
|
||||||
return $cb(...$args);
|
|
||||||
}
|
|
||||||
$class = method_exists($this, $name) ? 'parent' : get_class($this);
|
|
||||||
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
|
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||||
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
|
$items = array_map(function ($item) { return $item->getName(); }, $items);
|
||||||
|
$hint = ($t = Helpers::getSuggestion($items, $name))
|
||||||
|
? ", did you mean $t()?"
|
||||||
|
: '.';
|
||||||
throw new \LogicException("Call to undefined method $class::$name()$hint");
|
throw new \LogicException("Call to undefined method $class::$name()$hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +45,12 @@ trait Strict
|
|||||||
*/
|
*/
|
||||||
public static function __callStatic(string $name, array $args)
|
public static function __callStatic(string $name, array $args)
|
||||||
{
|
{
|
||||||
$rc = new ReflectionClass(get_called_class());
|
$rc = new ReflectionClass(static::class);
|
||||||
$items = array_intersect($rc->getMethods(ReflectionMethod::IS_PUBLIC), $rc->getMethods(ReflectionMethod::IS_STATIC));
|
$items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), function ($m) { return $m->isPublic(); });
|
||||||
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
|
$items = array_map(function ($item) { return $item->getName(); }, $items);
|
||||||
|
$hint = ($t = Helpers::getSuggestion($items, $name))
|
||||||
|
? ", did you mean $t()?"
|
||||||
|
: '.';
|
||||||
throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
|
throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +67,13 @@ trait Strict
|
|||||||
$ret = $this->$m();
|
$ret = $this->$m();
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rc = new ReflectionClass($this);
|
$rc = new ReflectionClass($this);
|
||||||
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
|
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); });
|
||||||
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
|
$items = array_map(function ($item) { return $item->getName(); }, $items);
|
||||||
|
$hint = ($t = Helpers::getSuggestion($items, $name))
|
||||||
|
? ", did you mean $$t?"
|
||||||
|
: '.';
|
||||||
throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
|
throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +85,11 @@ trait Strict
|
|||||||
public function __set(string $name, $value)
|
public function __set(string $name, $value)
|
||||||
{
|
{
|
||||||
$rc = new ReflectionClass($this);
|
$rc = new ReflectionClass($this);
|
||||||
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
|
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); });
|
||||||
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
|
$items = array_map(function ($item) { return $item->getName(); }, $items);
|
||||||
|
$hint = ($t = Helpers::getSuggestion($items, $name))
|
||||||
|
? ", did you mean $$t?"
|
||||||
|
: '.';
|
||||||
throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
|
throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,42 +106,7 @@ trait Strict
|
|||||||
*/
|
*/
|
||||||
public function __unset(string $name)
|
public function __unset(string $name)
|
||||||
{
|
{
|
||||||
$class = get_class($this);
|
$class = static::class;
|
||||||
throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
|
throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -69,6 +69,7 @@ final class Translator
|
|||||||
while (count($args) === 1 && is_array($args[0])) { // implicit array expansion
|
while (count($args) === 1 && is_array($args[0])) { // implicit array expansion
|
||||||
$args = array_values($args[0]);
|
$args = array_values($args[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->args = $args;
|
$this->args = $args;
|
||||||
$this->errors = [];
|
$this->errors = [];
|
||||||
|
|
||||||
@@ -92,28 +93,31 @@ final class Translator
|
|||||||
$sql[] = $arg;
|
$sql[] = $arg;
|
||||||
} else {
|
} else {
|
||||||
$sql[] = substr($arg, 0, $toSkip)
|
$sql[] = substr($arg, 0, $toSkip)
|
||||||
/*
|
// note: this can change $this->args & $this->cursor & ...
|
||||||
. preg_replace_callback('/
|
. preg_replace_callback(
|
||||||
(?=[`[\'":%?]) ## speed-up
|
<<<'XX'
|
||||||
(?:
|
/
|
||||||
`(.+?)`| ## 1) `identifier`
|
(?=[`['":%?]) ## speed-up
|
||||||
\[(.+?)\]| ## 2) [identifier]
|
(?:
|
||||||
(\')((?:\'\'|[^\'])*)\'| ## 3,4) 'string'
|
`(.+?)`| ## 1) `identifier`
|
||||||
(")((?:""|[^"])*)"| ## 5,6) "string"
|
\[(.+?)\]| ## 2) [identifier]
|
||||||
(\'|")| ## 7) lone quote
|
(')((?:''|[^'])*)'| ## 3,4) string
|
||||||
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
|
(")((?:""|[^"])*)"| ## 5,6) "string"
|
||||||
%([a-zA-Z~][a-zA-Z0-9~]{0,5})|## 10) modifier
|
('|")| ## 7) lone quote
|
||||||
(\?) ## 11) placeholder
|
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
|
||||||
)/xs',
|
%([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier
|
||||||
*/ // note: this can change $this->args & $this->cursor & ...
|
(\?) ## 11) placeholder
|
||||||
. preg_replace_callback('/(?=[`[\'":%?])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?)|%([a-zA-Z~][a-zA-Z0-9~]{0,5})|(\?))/s',
|
)/xs
|
||||||
|
XX
|
||||||
|
,
|
||||||
[$this, 'cb'],
|
[$this, 'cb'],
|
||||||
substr($arg, $toSkip)
|
substr($arg, $toSkip)
|
||||||
);
|
);
|
||||||
if (preg_last_error()) {
|
if (preg_last_error()) {
|
||||||
throw new PcreException;
|
throw new PcreException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,8 +140,10 @@ final class Translator
|
|||||||
if ($lastArr === $cursor - 1) {
|
if ($lastArr === $cursor - 1) {
|
||||||
$sql[] = ',';
|
$sql[] = ',';
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
|
$sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
|
||||||
}
|
}
|
||||||
|
|
||||||
$lastArr = $cursor;
|
$lastArr = $cursor;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -146,12 +152,11 @@ final class Translator
|
|||||||
$sql[] = $this->formatValue($arg, null);
|
$sql[] = $this->formatValue($arg, null);
|
||||||
} // while
|
} // while
|
||||||
|
|
||||||
|
|
||||||
if ($comment) {
|
if ($comment) {
|
||||||
$sql[] = '*/';
|
$sql[] = '*/';
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = implode(' ', $sql);
|
$sql = trim(implode(' ', $sql), ' ');
|
||||||
|
|
||||||
if ($this->errors) {
|
if ($this->errors) {
|
||||||
throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql);
|
throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql);
|
||||||
@@ -212,13 +217,14 @@ final class Translator
|
|||||||
} else {
|
} else {
|
||||||
$op = '= ';
|
$op = '= ';
|
||||||
}
|
}
|
||||||
|
|
||||||
$vx[] = $k . $op . $v;
|
$vx[] = $k . $op . $v;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$vx[] = $this->formatValue($v, 'ex');
|
$vx[] = $this->formatValue($v, 'ex');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')';
|
return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')';
|
||||||
|
|
||||||
case 'n': // key, key, ... identifier names
|
case 'n': // key, key, ... identifier names
|
||||||
@@ -230,6 +236,7 @@ final class Translator
|
|||||||
$vx[] = $this->identifiers->{$pair[0]};
|
$vx[] = $this->identifiers->{$pair[0]};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(', ', $vx);
|
return implode(', ', $vx);
|
||||||
|
|
||||||
|
|
||||||
@@ -239,6 +246,7 @@ final class Translator
|
|||||||
$vx[] = $this->identifiers->{$pair[0]} . '='
|
$vx[] = $this->identifiers->{$pair[0]} . '='
|
||||||
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(', ', $vx);
|
return implode(', ', $vx);
|
||||||
|
|
||||||
|
|
||||||
@@ -248,6 +256,7 @@ final class Translator
|
|||||||
$pair = explode('%', (string) $k, 2); // split into identifier & modifier
|
$pair = explode('%', (string) $k, 2); // split into identifier & modifier
|
||||||
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')';
|
return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')';
|
||||||
|
|
||||||
|
|
||||||
@@ -257,6 +266,7 @@ final class Translator
|
|||||||
$kx[] = $this->identifiers->{$pair[0]};
|
$kx[] = $this->identifiers->{$pair[0]};
|
||||||
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
|
return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
|
||||||
|
|
||||||
case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ...
|
case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ...
|
||||||
@@ -279,9 +289,11 @@ final class Translator
|
|||||||
$vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null));
|
$vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($vx as $k => $v) {
|
foreach ($vx as $k => $v) {
|
||||||
$vx[$k] = '(' . implode(', ', $v) . ')';
|
$vx[$k] = '(' . implode(', ', $v) . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx);
|
return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx);
|
||||||
|
|
||||||
case 'by': // key ASC, key DESC
|
case 'by': // key ASC, key DESC
|
||||||
@@ -289,12 +301,13 @@ final class Translator
|
|||||||
if (is_array($v)) {
|
if (is_array($v)) {
|
||||||
$vx[] = $this->formatValue($v, 'ex');
|
$vx[] = $this->formatValue($v, 'ex');
|
||||||
} elseif (is_string($k)) {
|
} elseif (is_string($k)) {
|
||||||
$v = (is_string($v) && strncasecmp($v, 'd', 1)) || $v > 0 ? 'ASC' : 'DESC';
|
$v = (is_string($v) ? strncasecmp($v, 'd', 1) : $v > 0) ? 'ASC' : 'DESC';
|
||||||
$vx[] = $this->identifiers->$k . ' ' . $v;
|
$vx[] = $this->identifiers->$k . ' ' . $v;
|
||||||
} else {
|
} else {
|
||||||
$vx[] = $this->identifiers->$v;
|
$vx[] = $this->identifiers->$v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(', ', $vx);
|
return implode(', ', $vx);
|
||||||
|
|
||||||
case 'ex!':
|
case 'ex!':
|
||||||
@@ -308,10 +321,15 @@ final class Translator
|
|||||||
foreach ($value as $v) {
|
foreach ($value as $v) {
|
||||||
$vx[] = $this->formatValue($v, $modifier);
|
$vx[] = $this->formatValue($v, $modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(', ', $vx);
|
return implode(', ', $vx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// object-to-scalar procession
|
||||||
|
if ($value instanceof \BackedEnum && is_scalar($value->value)) {
|
||||||
|
$value = $value->value;
|
||||||
|
}
|
||||||
|
|
||||||
// with modifier procession
|
// with modifier procession
|
||||||
if ($modifier) {
|
if ($modifier) {
|
||||||
@@ -322,7 +340,13 @@ final class Translator
|
|||||||
} elseif ($value instanceof Expression && $modifier === 'ex') {
|
} elseif ($value instanceof Expression && $modifier === 'ex') {
|
||||||
return $this->connection->translate(...$value->getValues());
|
return $this->connection->translate(...$value->getValues());
|
||||||
|
|
||||||
} elseif ($value instanceof \DateTimeInterface && ($modifier === 'd' || $modifier === 't' || $modifier === 'dt')) {
|
} elseif (
|
||||||
|
$value instanceof \DateTimeInterface
|
||||||
|
&& ($modifier === 'd'
|
||||||
|
|| $modifier === 't'
|
||||||
|
|| $modifier === 'dt'
|
||||||
|
)
|
||||||
|
) {
|
||||||
// continue
|
// continue
|
||||||
} else {
|
} else {
|
||||||
$type = is_object($value) ? get_class($value) : gettype($value);
|
$type = is_object($value) ? get_class($value) : gettype($value);
|
||||||
@@ -332,21 +356,29 @@ final class Translator
|
|||||||
|
|
||||||
switch ($modifier) {
|
switch ($modifier) {
|
||||||
case 's': // string
|
case 's': // string
|
||||||
return $value === null ? 'NULL' : $this->driver->escapeText((string) $value);
|
return $value === null
|
||||||
|
? 'NULL'
|
||||||
|
: $this->driver->escapeText((string) $value);
|
||||||
|
|
||||||
case 'bin':// binary
|
case 'bin':// binary
|
||||||
return $value === null ? 'NULL' : $this->driver->escapeBinary($value);
|
return $value === null
|
||||||
|
? 'NULL'
|
||||||
|
: $this->driver->escapeBinary($value);
|
||||||
|
|
||||||
case 'b': // boolean
|
case 'b': // boolean
|
||||||
return $value === null ? 'NULL' : $this->driver->escapeBool((bool) $value);
|
return $value === null
|
||||||
|
? 'NULL'
|
||||||
|
: $this->driver->escapeBool((bool) $value);
|
||||||
|
|
||||||
case 'sN': // string or null
|
case 'sN': // string or null
|
||||||
case 'sn':
|
case 'sn':
|
||||||
return $value == '' ? 'NULL' : $this->driver->escapeText((string) $value); // notice two equal signs
|
return $value === '' || $value === 0 || $value === null
|
||||||
|
? 'NULL'
|
||||||
|
: $this->driver->escapeText((string) $value);
|
||||||
|
|
||||||
case 'iN': // signed int or null
|
case 'iN': // signed int or null
|
||||||
if ($value == '') {
|
if ($value === '' || $value === 0 || $value === null) {
|
||||||
$value = null;
|
return 'NULL';
|
||||||
}
|
}
|
||||||
// break omitted
|
// break omitted
|
||||||
case 'i': // signed int
|
case 'i': // signed int
|
||||||
@@ -381,10 +413,14 @@ final class Translator
|
|||||||
case 'dt': // datetime
|
case 'dt': // datetime
|
||||||
if ($value === null) {
|
if ($value === null) {
|
||||||
return 'NULL';
|
return 'NULL';
|
||||||
} else {
|
} elseif (!$value instanceof \DateTimeInterface) {
|
||||||
return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value);
|
$value = new DateTime($value);
|
||||||
}
|
}
|
||||||
// break omitted
|
|
||||||
|
return $modifier === 'd'
|
||||||
|
? $this->driver->escapeDate($value)
|
||||||
|
: $this->driver->escapeDateTime($value);
|
||||||
|
|
||||||
case 'by':
|
case 'by':
|
||||||
case 'n': // composed identifier name
|
case 'n': // composed identifier name
|
||||||
return $this->identifiers->$value;
|
return $this->identifiers->$value;
|
||||||
@@ -399,27 +435,43 @@ final class Translator
|
|||||||
$toSkip = strcspn($value, '`[\'":');
|
$toSkip = strcspn($value, '`[\'":');
|
||||||
if (strlen($value) !== $toSkip) {
|
if (strlen($value) !== $toSkip) {
|
||||||
$value = substr($value, 0, $toSkip)
|
$value = substr($value, 0, $toSkip)
|
||||||
. preg_replace_callback(
|
. preg_replace_callback(
|
||||||
'/(?=[`[\'":])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?))/s',
|
<<<'XX'
|
||||||
[$this, 'cb'],
|
/
|
||||||
substr($value, $toSkip)
|
(?=[`['":])
|
||||||
);
|
(?:
|
||||||
|
`(.+?)`|
|
||||||
|
\[(.+?)\]|
|
||||||
|
(')((?:''|[^'])*)'|
|
||||||
|
(")((?:""|[^"])*)"|
|
||||||
|
('|")|
|
||||||
|
:(\S*?:)([a-zA-Z0-9._]?)
|
||||||
|
)/sx
|
||||||
|
XX
|
||||||
|
,
|
||||||
|
[$this, 'cb'],
|
||||||
|
substr($value, $toSkip)
|
||||||
|
);
|
||||||
if (preg_last_error()) {
|
if (preg_last_error()) {
|
||||||
throw new PcreException;
|
throw new PcreException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
|
|
||||||
case 'SQL': // preserve as real SQL (TODO: rename to %sql)
|
case 'SQL': // preserve as real SQL (TODO: rename to %sql)
|
||||||
return (string) $value;
|
return (string) $value;
|
||||||
|
|
||||||
case 'like~': // LIKE string%
|
case 'like~': // LIKE string%
|
||||||
return $this->driver->escapeLike($value, 1);
|
return $this->driver->escapeLike($value, 2);
|
||||||
|
|
||||||
case '~like': // LIKE %string
|
case '~like': // LIKE %string
|
||||||
return $this->driver->escapeLike($value, -1);
|
return $this->driver->escapeLike($value, 1);
|
||||||
|
|
||||||
case '~like~': // LIKE %string%
|
case '~like~': // LIKE %string%
|
||||||
|
return $this->driver->escapeLike($value, 3);
|
||||||
|
|
||||||
|
case 'like': // LIKE string
|
||||||
return $this->driver->escapeLike($value, 0);
|
return $this->driver->escapeLike($value, 0);
|
||||||
|
|
||||||
case 'and':
|
case 'and':
|
||||||
@@ -435,7 +487,6 @@ final class Translator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// without modifier procession
|
// without modifier procession
|
||||||
if (is_string($value)) {
|
if (is_string($value)) {
|
||||||
return $this->driver->escapeText($value);
|
return $this->driver->escapeText($value);
|
||||||
@@ -455,6 +506,9 @@ final class Translator
|
|||||||
} elseif ($value instanceof \DateTimeInterface) {
|
} elseif ($value instanceof \DateTimeInterface) {
|
||||||
return $this->driver->escapeDateTime($value);
|
return $this->driver->escapeDateTime($value);
|
||||||
|
|
||||||
|
} elseif ($value instanceof \DateInterval) {
|
||||||
|
return $this->driver->escapeDateInterval($value);
|
||||||
|
|
||||||
} elseif ($value instanceof Literal) {
|
} elseif ($value instanceof Literal) {
|
||||||
return (string) $value;
|
return (string) $value;
|
||||||
|
|
||||||
@@ -514,6 +568,7 @@ final class Translator
|
|||||||
$this->comment = true;
|
$this->comment = true;
|
||||||
return '/*';
|
return '/*';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
||||||
} elseif ($mod === 'else') {
|
} elseif ($mod === 'else') {
|
||||||
@@ -526,7 +581,6 @@ final class Translator
|
|||||||
$this->comment = true;
|
$this->comment = true;
|
||||||
return '/*';
|
return '/*';
|
||||||
}
|
}
|
||||||
|
|
||||||
} elseif ($mod === 'end') {
|
} elseif ($mod === 'end') {
|
||||||
$this->ifLevel--;
|
$this->ifLevel--;
|
||||||
if ($this->ifLevelStart === $this->ifLevel + 1) {
|
if ($this->ifLevelStart === $this->ifLevel + 1) {
|
||||||
@@ -535,6 +589,7 @@ final class Translator
|
|||||||
$this->comment = false;
|
$this->comment = false;
|
||||||
return '*/';
|
return '*/';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
||||||
} elseif ($mod === 'ex') { // array expansion
|
} elseif ($mod === 'ex') { // array expansion
|
||||||
@@ -549,6 +604,7 @@ final class Translator
|
|||||||
} else {
|
} else {
|
||||||
$this->limit = Helpers::intVal($arg);
|
$this->limit = Helpers::intVal($arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
||||||
} elseif ($mod === 'ofs') { // apply offset
|
} elseif ($mod === 'ofs') { // apply offset
|
||||||
@@ -559,6 +615,7 @@ final class Translator
|
|||||||
} else {
|
} else {
|
||||||
$this->offset = Helpers::intVal($arg);
|
$this->offset = Helpers::intVal($arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
||||||
} else { // default processing
|
} else { // default processing
|
||||||
@@ -590,7 +647,9 @@ final class Translator
|
|||||||
if ($matches[8]) { // SQL identifier substitution
|
if ($matches[8]) { // SQL identifier substitution
|
||||||
$m = substr($matches[8], 0, -1);
|
$m = substr($matches[8], 0, -1);
|
||||||
$m = $this->connection->getSubstitutes()->$m;
|
$m = $this->connection->getSubstitutes()->$m;
|
||||||
return $matches[9] == '' ? $this->formatValue($m, null) : $m . $matches[9]; // value or identifier
|
return $matches[9] === ''
|
||||||
|
? $this->formatValue($m, null)
|
||||||
|
: $m . $matches[9]; // value or identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \Exception('this should be never executed');
|
throw new \Exception('this should be never executed');
|
||||||
@@ -610,6 +669,7 @@ final class Translator
|
|||||||
$v = $this->driver->escapeIdentifier($v);
|
$v = $this->driver->escapeIdentifier($v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode('.', $parts);
|
return implode('.', $parts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,6 @@ class Type
|
|||||||
|
|
||||||
final public function __construct()
|
final public function __construct()
|
||||||
{
|
{
|
||||||
throw new \LogicException('Cannot instantiate static class ' . __CLASS__);
|
throw new \LogicException('Cannot instantiate static class ' . self::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,28 +11,29 @@ declare(strict_types=1);
|
|||||||
/**
|
/**
|
||||||
* Static container class for Dibi connections.
|
* Static container class for Dibi connections.
|
||||||
*
|
*
|
||||||
* @method void disconnect()
|
* @method static void disconnect()
|
||||||
* @method Dibi\Result query(...$args)
|
* @method static Dibi\Result query(...$args)
|
||||||
* @method Dibi\Result nativeQuery(...$args)
|
* @method static Dibi\Result nativeQuery(...$args)
|
||||||
* @method bool test(...$args)
|
* @method static bool test(...$args)
|
||||||
* @method Dibi\DataSource dataSource(...$args)
|
* @method static Dibi\DataSource dataSource(...$args)
|
||||||
* @method Dibi\Row|null fetch(...$args)
|
* @method static Dibi\Row|null fetch(...$args)
|
||||||
* @method array fetchAll(...$args)
|
* @method static array fetchAll(...$args)
|
||||||
* @method mixed fetchSingle(...$args)
|
* @method static mixed fetchSingle(...$args)
|
||||||
* @method array fetchPairs(...$args)
|
* @method static array fetchPairs(...$args)
|
||||||
* @method int getAffectedRows()
|
* @method static int getAffectedRows()
|
||||||
* @method int getInsertId(string $sequence = null)
|
* @method static int getInsertId(string $sequence = null)
|
||||||
* @method void begin(string $savepoint = null)
|
* @method static void begin(string $savepoint = null)
|
||||||
* @method void commit(string $savepoint = null)
|
* @method static void commit(string $savepoint = null)
|
||||||
* @method void rollback(string $savepoint = null)
|
* @method static void rollback(string $savepoint = null)
|
||||||
* @method Dibi\Reflection\Database getDatabaseInfo()
|
* @method static mixed transaction(callable $callback)
|
||||||
* @method Dibi\Fluent command()
|
* @method static Dibi\Reflection\Database getDatabaseInfo()
|
||||||
* @method Dibi\Fluent select(...$args)
|
* @method static Dibi\Fluent command()
|
||||||
* @method Dibi\Fluent update(string $table, array $args)
|
* @method static Dibi\Fluent select(...$args)
|
||||||
* @method Dibi\Fluent insert(string $table, array $args)
|
* @method static Dibi\Fluent update(string|string[] $table, array $args)
|
||||||
* @method Dibi\Fluent delete(string $table)
|
* @method static Dibi\Fluent insert(string $table, array $args)
|
||||||
* @method Dibi\HashMap getSubstitutes()
|
* @method static Dibi\Fluent delete(string $table)
|
||||||
* @method int loadFile(string $file)
|
* @method static Dibi\HashMap getSubstitutes()
|
||||||
|
* @method static int loadFile(string $file)
|
||||||
*/
|
*/
|
||||||
class dibi
|
class dibi
|
||||||
{
|
{
|
||||||
@@ -43,8 +44,7 @@ class dibi
|
|||||||
IDENTIFIER = 'n';
|
IDENTIFIER = 'n';
|
||||||
|
|
||||||
/** version */
|
/** version */
|
||||||
public const
|
public const VERSION = '4.2.8';
|
||||||
VERSION = '4.0.0';
|
|
||||||
|
|
||||||
/** sorting order */
|
/** sorting order */
|
||||||
public const
|
public const
|
||||||
@@ -75,7 +75,7 @@ class dibi
|
|||||||
*/
|
*/
|
||||||
final public function __construct()
|
final public function __construct()
|
||||||
{
|
{
|
||||||
throw new LogicException('Cannot instantiate static class ' . get_class($this));
|
throw new LogicException('Cannot instantiate static class ' . static::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ class dibi
|
|||||||
* Retrieve active connection.
|
* Retrieve active connection.
|
||||||
* @throws Dibi\Exception
|
* @throws Dibi\Exception
|
||||||
*/
|
*/
|
||||||
public static function getConnection(string $name = null): Dibi\Connection
|
public static function getConnection(?string $name = null): Dibi\Connection
|
||||||
{
|
{
|
||||||
if ($name === null) {
|
if ($name === null) {
|
||||||
if (self::$connection === null) {
|
if (self::$connection === null) {
|
||||||
@@ -145,26 +145,6 @@ class dibi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public static function affectedRows(): int
|
|
||||||
{
|
|
||||||
trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
|
|
||||||
return self::getConnection()->getAffectedRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public static function insertId(string $sequence = null): int
|
|
||||||
{
|
|
||||||
trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
|
|
||||||
return self::getConnection()->getInsertId($sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/********************* misc tools ****************d*g**/
|
/********************* misc tools ****************d*g**/
|
||||||
|
|
||||||
|
|
||||||
@@ -182,7 +162,7 @@ class dibi
|
|||||||
/**
|
/**
|
||||||
* Strips microseconds part.
|
* Strips microseconds part.
|
||||||
*/
|
*/
|
||||||
public static function stripMicroseconds(\DateTimeInterface $dt): \DateTimeInterface
|
public static function stripMicroseconds(DateTimeInterface $dt): DateTimeInterface
|
||||||
{
|
{
|
||||||
$class = get_class($dt);
|
$class = get_class($dt);
|
||||||
return new $class($dt->format('Y-m-d H:i:s'), $dt->getTimezone());
|
return new $class($dt->format('Y-m-d H:i:s'), $dt->getTimezone());
|
||||||
|
@@ -22,7 +22,7 @@ class Exception extends \Exception
|
|||||||
/**
|
/**
|
||||||
* @param int|string $code
|
* @param int|string $code
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message = '', $code = 0, string $sql = null, \Throwable $previous = null)
|
public function __construct(string $message = '', $code = 0, ?string $sql = null, ?\Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, 0, $previous);
|
parent::__construct($message, 0, $previous);
|
||||||
$this->code = $code;
|
$this->code = $code;
|
||||||
@@ -93,7 +93,7 @@ class ProcedureException extends Exception
|
|||||||
/**
|
/**
|
||||||
* Construct the exception.
|
* Construct the exception.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message = '', int $code = 0, string $severity = '', string $sql = null)
|
public function __construct(string $message = '', int $code = 0, string $severity = '', ?string $sql = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $code, $sql);
|
parent::__construct($message, $code, $sql);
|
||||||
$this->severity = $severity;
|
$this->severity = $severity;
|
||||||
|
@@ -51,19 +51,19 @@ interface Driver
|
|||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
* @throws DriverException
|
* @throws DriverException
|
||||||
*/
|
*/
|
||||||
function begin(string $savepoint = null): void;
|
function begin(?string $savepoint = null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
* @throws DriverException
|
* @throws DriverException
|
||||||
*/
|
*/
|
||||||
function commit(string $savepoint = null): void;
|
function commit(?string $savepoint = null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
* @throws DriverException
|
* @throws DriverException
|
||||||
*/
|
*/
|
||||||
function rollback(string $savepoint = null): void;
|
function rollback(?string $savepoint = null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the connection resource.
|
* Returns the connection resource.
|
||||||
@@ -87,15 +87,11 @@ interface Driver
|
|||||||
|
|
||||||
function escapeBool(bool $value): string;
|
function escapeBool(bool $value): string;
|
||||||
|
|
||||||
/**
|
function escapeDate(\DateTimeInterface $value): string;
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
|
||||||
function escapeDate($value): string;
|
|
||||||
|
|
||||||
/**
|
function escapeDateTime(\DateTimeInterface $value): string;
|
||||||
* @param \DateTimeInterface|string|int $value
|
|
||||||
*/
|
function escapeDateInterval(\DateInterval $value): string;
|
||||||
function escapeDateTime($value): string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes string for use in a LIKE statement.
|
* Encodes string for use in a LIKE statement.
|
||||||
@@ -228,20 +224,20 @@ interface IConnection
|
|||||||
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
function getInsertId(string $sequence = null): int;
|
function getInsertId(?string $sequence = null): int;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begins a transaction (if supported).
|
* Begins a transaction (if supported).
|
||||||
*/
|
*/
|
||||||
function begin(string $savepoint = null): void;
|
function begin(?string $savepoint = null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits statements in a transaction.
|
* Commits statements in a transaction.
|
||||||
*/
|
*/
|
||||||
function commit(string $savepoint = null): void;
|
function commit(?string $savepoint = null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rollback changes in a transaction.
|
* Rollback changes in a transaction.
|
||||||
*/
|
*/
|
||||||
function rollback(string $savepoint = null): void;
|
function rollback(?string $savepoint = null): void;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
# for php-coveralls
|
# for php-coveralls
|
||||||
service_name: travis-ci
|
service_name: github-actions
|
||||||
coverage_clover: coverage.xml
|
coverage_clover: coverage.xml
|
||||||
json_path: coverage.json
|
json_path: coverage.json
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
includes:
|
|
||||||
- ../temp/coding-standard/coding-standard-php71.neon
|
|
||||||
|
|
||||||
parameters:
|
|
||||||
skip:
|
|
||||||
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer:
|
|
||||||
- src/Dibi/HashMap.php # issue #260
|
|
@@ -1,5 +1,5 @@
|
|||||||
[sqlite] ; default
|
[sqlite] ; default
|
||||||
driver = sqlite3
|
driver = sqlite
|
||||||
database = :memory:
|
database = :memory:
|
||||||
system = sqlite
|
system = sqlite
|
||||||
|
|
||||||
|
89
tests/databases.github.ini
Normal file
89
tests/databases.github.ini
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
[sqlite] ; default
|
||||||
|
driver = sqlite
|
||||||
|
database = :memory:
|
||||||
|
system = sqlite
|
||||||
|
|
||||||
|
[sqlite pdo]
|
||||||
|
driver = pdo
|
||||||
|
dsn = "sqlite::memory:"
|
||||||
|
system = sqlite
|
||||||
|
|
||||||
|
[mysql 5.7]
|
||||||
|
driver = mysqli
|
||||||
|
host = "127.0.0.1"
|
||||||
|
database = dibi_test
|
||||||
|
username = root
|
||||||
|
password = root
|
||||||
|
port = 3306
|
||||||
|
system = mysql
|
||||||
|
|
||||||
|
[mysql 5.7pdo]
|
||||||
|
driver = pdo
|
||||||
|
dsn = "mysql:host=127.0.0.1;port=3306;dbname=dibi_test"
|
||||||
|
user = root
|
||||||
|
password = root
|
||||||
|
system = mysql
|
||||||
|
|
||||||
|
[mysql 8.0]
|
||||||
|
driver = mysqli
|
||||||
|
host = "127.0.0.1"
|
||||||
|
database = dibi_test
|
||||||
|
username = root
|
||||||
|
password = root
|
||||||
|
port = 3307
|
||||||
|
system = mysql
|
||||||
|
|
||||||
|
[mysql 8.0pdo]
|
||||||
|
driver = pdo
|
||||||
|
dsn = "mysql:host=127.0.0.1;port=3307;dbname=dibi_test"
|
||||||
|
user = root
|
||||||
|
password = root
|
||||||
|
system = mysql
|
||||||
|
|
||||||
|
[postgre 9.6]
|
||||||
|
driver = postgre
|
||||||
|
host = "127.0.0.1"
|
||||||
|
database = dibi_test
|
||||||
|
username = postgres
|
||||||
|
password = postgres
|
||||||
|
port = 5432
|
||||||
|
system = postgre
|
||||||
|
|
||||||
|
[postgre 9.6pdo]
|
||||||
|
driver = pdo
|
||||||
|
dsn = "pgsql:host=127.0.0.1;port=5432;dbname=dibi_test"
|
||||||
|
user = postgres
|
||||||
|
password = postgres
|
||||||
|
system = postgre
|
||||||
|
|
||||||
|
[postgre 13]
|
||||||
|
driver = postgre
|
||||||
|
host = "127.0.0.1"
|
||||||
|
database = dibi_test
|
||||||
|
username = postgres
|
||||||
|
password = postgres
|
||||||
|
port = 5433
|
||||||
|
system = postgre
|
||||||
|
|
||||||
|
[postgre 13pdo]
|
||||||
|
driver = pdo
|
||||||
|
dsn = "pgsql:host=127.0.0.1;port=5433;dbname=dibi_test"
|
||||||
|
user = postgres
|
||||||
|
password = postgres
|
||||||
|
system = postgre
|
||||||
|
|
||||||
|
[sqlsrv]
|
||||||
|
driver = sqlsrv
|
||||||
|
host = "localhost"
|
||||||
|
username = SA
|
||||||
|
password = "YourStrong!Passw0rd"
|
||||||
|
database = dibi_test
|
||||||
|
port = 1433
|
||||||
|
system = sqlsrv
|
||||||
|
|
||||||
|
;[sqlsrv pdo]
|
||||||
|
;driver = pdo
|
||||||
|
;dsn = "sqlsrv:Server=localhost,1433;Database=dibi_test"
|
||||||
|
;user = SA
|
||||||
|
;password = "YourStrong!Passw0rd"
|
||||||
|
;system = sqlsrv
|
@@ -1,5 +1,5 @@
|
|||||||
[sqlite] ; default
|
[sqlite] ; default
|
||||||
driver = sqlite3
|
driver = sqlite
|
||||||
database = :memory:
|
database = :memory:
|
||||||
system = sqlite
|
system = sqlite
|
||||||
|
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
[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
|
|
@@ -12,7 +12,7 @@ use Tester\Assert;
|
|||||||
require __DIR__ . '/bootstrap.php';
|
require __DIR__ . '/bootstrap.php';
|
||||||
|
|
||||||
|
|
||||||
test(function () use ($config) {
|
test('', function () use ($config) {
|
||||||
$conn = new Connection($config);
|
$conn = new Connection($config);
|
||||||
Assert::true($conn->isConnected());
|
Assert::true($conn->isConnected());
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ test(function () use ($config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () use ($config) { // lazy
|
test('lazy', function () use ($config) {
|
||||||
$conn = new Connection($config + ['lazy' => true]);
|
$conn = new Connection($config + ['lazy' => true]);
|
||||||
Assert::false($conn->isConnected());
|
Assert::false($conn->isConnected());
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ test(function () use ($config) { // lazy
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () use ($config) {
|
test('', function () use ($config) {
|
||||||
$conn = new Connection($config);
|
$conn = new Connection($config);
|
||||||
Assert::true($conn->isConnected());
|
Assert::true($conn->isConnected());
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ test(function () use ($config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () use ($config) {
|
test('', function () use ($config) {
|
||||||
$conn = new Connection($config);
|
$conn = new Connection($config);
|
||||||
Assert::true($conn->isConnected());
|
Assert::true($conn->isConnected());
|
||||||
|
|
||||||
@@ -50,3 +50,47 @@ test(function () use ($config) {
|
|||||||
$conn->disconnect();
|
$conn->disconnect();
|
||||||
Assert::false($conn->isConnected());
|
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());
|
||||||
|
});
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Tester\Assert;
|
use Tester\Assert;
|
||||||
|
@@ -30,21 +30,102 @@ Assert::exception(function () use ($conn) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
$conn->begin();
|
test('begin() & rollback()', function () use ($conn) {
|
||||||
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
$conn->begin();
|
||||||
$conn->query('INSERT INTO [products]', [
|
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
'title' => 'Test product',
|
$conn->query('INSERT INTO [products]', [
|
||||||
]);
|
'title' => 'Test product',
|
||||||
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
]);
|
||||||
$conn->rollback();
|
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
$conn->rollback();
|
||||||
|
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('begin() & commit()', function () use ($conn) {
|
||||||
|
$conn->begin();
|
||||||
|
$conn->query('INSERT INTO [products]', [
|
||||||
|
'title' => 'Test product',
|
||||||
|
]);
|
||||||
|
$conn->commit();
|
||||||
|
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
$conn->begin();
|
test('transaction() fail', function () use ($conn) {
|
||||||
$conn->query('INSERT INTO [products]', [
|
Assert::exception(function () use ($conn) {
|
||||||
'title' => 'Test product',
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
]);
|
$connection->query('INSERT INTO [products]', [
|
||||||
$conn->commit();
|
'title' => 'Test product',
|
||||||
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
]);
|
||||||
|
throw new Exception('my exception');
|
||||||
|
});
|
||||||
|
}, Throwable::class, 'my exception');
|
||||||
|
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('transaction() success', function () use ($conn) {
|
||||||
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
|
$connection->query('INSERT INTO [products]', [
|
||||||
|
'title' => 'Test product',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('nested transaction() call fail', function () use ($conn) {
|
||||||
|
Assert::exception(function () use ($conn) {
|
||||||
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
|
$connection->query('INSERT INTO [products]', [
|
||||||
|
'title' => 'Test product',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$connection->transaction(function (Dibi\Connection $connection2) {
|
||||||
|
$connection2->query('INSERT INTO [products]', [
|
||||||
|
'title' => 'Test product',
|
||||||
|
]);
|
||||||
|
throw new Exception('my exception');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, Throwable::class, 'my exception');
|
||||||
|
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('nested transaction() call success', function () use ($conn) {
|
||||||
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
|
$connection->query('INSERT INTO [products]', [
|
||||||
|
'title' => 'Test product',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$connection->transaction(function (Dibi\Connection $connection2) {
|
||||||
|
$connection2->query('INSERT INTO [products]', [
|
||||||
|
'title' => 'Test product',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Assert::same(7, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) {
|
||||||
|
Assert::exception(function () use ($conn) {
|
||||||
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
|
$connection->begin();
|
||||||
|
});
|
||||||
|
}, LogicException::class, Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback');
|
||||||
|
|
||||||
|
Assert::exception(function () use ($conn) {
|
||||||
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
|
$connection->commit();
|
||||||
|
});
|
||||||
|
}, LogicException::class, Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback');
|
||||||
|
|
||||||
|
Assert::exception(function () use ($conn) {
|
||||||
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
|
$connection->rollback();
|
||||||
|
});
|
||||||
|
}, LogicException::class, Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback');
|
||||||
|
});
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Dibi\Row;
|
use Dibi\Row;
|
||||||
@@ -77,7 +78,7 @@ SELECT [product_id]
|
|||||||
FROM (SELECT * FROM products) t
|
FROM (SELECT * FROM products) t
|
||||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||||
ORDER BY [product_id] ASC
|
ORDER BY [product_id] ASC
|
||||||
) t"), dibi::$sql);
|
) t"), dibi::$sql);
|
||||||
Assert::same(1, $ds->toDataSource()->count());
|
Assert::same(1, $ds->toDataSource()->count());
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ SELECT [product_id]
|
|||||||
FROM (SELECT * FROM products) t
|
FROM (SELECT * FROM products) t
|
||||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||||
ORDER BY [product_id] ASC
|
ORDER BY [product_id] ASC
|
||||||
"),
|
"),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ SELECT [product_id]
|
|||||||
FROM (SELECT * FROM products) t
|
FROM (SELECT * FROM products) t
|
||||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||||
ORDER BY [product_id] ASC
|
ORDER BY [product_id] ASC
|
||||||
) t"),
|
) t"),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -14,8 +14,6 @@ Assert::same('1978-01-23 11:40:00.000000', (string) new DateTime(254400000));
|
|||||||
Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp(254400000));
|
Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp(254400000));
|
||||||
Assert::same(254400000, (new DateTime(254400000))->getTimestamp());
|
Assert::same(254400000, (new DateTime(254400000))->getTimestamp());
|
||||||
|
|
||||||
Assert::same('2050-08-13 11:40:00.000000', (string) new DateTime(2544000000));
|
|
||||||
Assert::same('2050-08-13 11:40:00.000000', (string) (new DateTime)->setTimestamp(2544000000));
|
|
||||||
Assert::same(is_int(2544000000) ? 2544000000 : '2544000000', (new DateTime(2544000000))->getTimestamp()); // 64 bit
|
Assert::same(is_int(2544000000) ? 2544000000 : '2544000000', (new DateTime(2544000000))->getTimestamp()); // 64 bit
|
||||||
|
|
||||||
Assert::same('1978-05-05 00:00:00.000000', (string) new DateTime('1978-05-05'));
|
Assert::same('1978-05-05 00:00:00.000000', (string) new DateTime('1978-05-05'));
|
||||||
|
21
tests/dibi/Event.source.phpt
Normal file
21
tests/dibi/Event.source.phpt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Tester\Assert;
|
||||||
|
|
||||||
|
require __DIR__ . '/bootstrap.php';
|
||||||
|
|
||||||
|
|
||||||
|
$conn = new Dibi\Connection($config);
|
||||||
|
|
||||||
|
$event = new Dibi\Event($conn, Dibi\Event::CONNECT);
|
||||||
|
Assert::same([__FILE__, __LINE__ - 1], $event->source);
|
||||||
|
|
||||||
|
eval('$event = new Dibi\Event($conn, Dibi\Event::CONNECT);');
|
||||||
|
Assert::same([__FILE__, __LINE__ - 1], $event->source);
|
||||||
|
|
||||||
|
array_map(function () use ($conn) {
|
||||||
|
$event = new Dibi\Event($conn, Dibi\Event::CONNECT);
|
||||||
|
Assert::same([__FILE__, __LINE__ - 1], $event->source);
|
||||||
|
}, [null]);
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Dibi\Fluent;
|
use Dibi\Fluent;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Tester\Assert;
|
use Tester\Assert;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Tester\Assert;
|
use Tester\Assert;
|
||||||
@@ -56,28 +57,28 @@ $fluent = $conn->select('*')
|
|||||||
->orderBy('customer_id');
|
->orderBy('customer_id');
|
||||||
|
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
'SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t',
|
'SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t',
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchAll(0, 3);
|
$fluent->fetchAll(0, 3);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (3) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (3) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -85,16 +86,16 @@ Assert::same(
|
|||||||
$fluent->limit(0);
|
$fluent->limit(0);
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (0) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (0) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (0) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,12 +104,12 @@ $fluent->removeClause('limit');
|
|||||||
$fluent->removeClause('offset');
|
$fluent->removeClause('offset');
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
|
@@ -22,28 +22,28 @@ $fluent = $conn->select('*')
|
|||||||
->orderBy('customer_id');
|
->orderBy('customer_id');
|
||||||
|
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchAll(2, 3);
|
$fluent->fetchAll(2, 3);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 3 OFFSET 2'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 3 OFFSET 2'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -51,16 +51,16 @@ Assert::same(
|
|||||||
$fluent->limit(0);
|
$fluent->limit(0);
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -68,16 +68,16 @@ Assert::same(
|
|||||||
$fluent->removeClause('limit');
|
$fluent->removeClause('limit');
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 18446744073709551615 OFFSET 3'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 18446744073709551615 OFFSET 3'),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -85,12 +85,12 @@ Assert::same(
|
|||||||
$fluent->removeClause('offset');
|
$fluent->removeClause('offset');
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||||
dibi::$sql
|
dibi::$sql
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Tester\Assert;
|
use Tester\Assert;
|
||||||
|
@@ -95,9 +95,9 @@ $fluent = $conn->select('*')
|
|||||||
|
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat([
|
reformat([
|
||||||
'odbc' => 'SELECT TOP 1 * FROM ( SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123) t',
|
'odbc' => 'SELECT TOP 1 * FROM (SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123) t',
|
||||||
'sqlsrv' => ' SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY',
|
'sqlsrv' => 'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY',
|
||||||
' SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 LIMIT 1',
|
'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 LIMIT 1',
|
||||||
]),
|
]),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
@@ -134,7 +134,10 @@ $fluent = $conn->select('*')
|
|||||||
->where(['x' => 'a', 'b', 'c']);
|
->where(['x' => 'a', 'b', 'c']);
|
||||||
|
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = \'a\') AND (b) AND (c)'),
|
reformat([
|
||||||
|
'sqlsrv' => "SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = N'a') AND (b) AND (c)",
|
||||||
|
"SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = 'a') AND (b) AND (c)",
|
||||||
|
]),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Tester\Assert;
|
use Tester\Assert;
|
||||||
@@ -29,3 +30,15 @@ Assert::same(
|
|||||||
reformat('UPDATE IGNORE DELAYED [table] SET [title]=\'Super Product\', [price]=12, [brand]=NULL , [another]=123'),
|
reformat('UPDATE IGNORE DELAYED [table] SET [title]=\'Super Product\', [price]=12, [brand]=NULL , [another]=123'),
|
||||||
(string) $fluent
|
(string) $fluent
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
$arr = [
|
||||||
|
'table1.title' => 'Super Product',
|
||||||
|
'table2.price' => 12,
|
||||||
|
'table2.brand' => null,
|
||||||
|
];
|
||||||
|
$fluent = $conn->update(['table1', 'table2'], $arr);
|
||||||
|
Assert::same(
|
||||||
|
reformat('UPDATE [table1], [table2] SET [table1].[title]=\'Super Product\', [table2].[price]=12, [table2].[brand]=NULL'),
|
||||||
|
(string) $fluent
|
||||||
|
);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
|
35
tests/dibi/PdoDriver.providedConnection.phpt
Normal file
35
tests/dibi/PdoDriver.providedConnection.phpt
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Tester\Assert;
|
||||||
|
|
||||||
|
require __DIR__ . '/bootstrap.php';
|
||||||
|
|
||||||
|
|
||||||
|
function buildPdoDriver(?int $errorMode)
|
||||||
|
{
|
||||||
|
$pdo = new PDO('sqlite::memory:');
|
||||||
|
if ($errorMode !== null) {
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
new Dibi\Drivers\PdoDriver(['resource' => $pdo]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// PDO error mode: exception
|
||||||
|
Assert::exception(function () {
|
||||||
|
buildPdoDriver(PDO::ERRMODE_EXCEPTION);
|
||||||
|
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.');
|
||||||
|
|
||||||
|
|
||||||
|
// PDO error mode: warning
|
||||||
|
Assert::exception(function () {
|
||||||
|
buildPdoDriver(PDO::ERRMODE_WARNING);
|
||||||
|
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.');
|
||||||
|
|
||||||
|
|
||||||
|
test('PDO error mode: explicitly set silent', function () {
|
||||||
|
buildPdoDriver(PDO::ERRMODE_SILENT);
|
||||||
|
});
|
@@ -32,7 +32,7 @@ Assert::same(
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (!in_array($config['driver'], ['sqlite3', 'pdo', 'sqlsrv'], true)) {
|
if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) {
|
||||||
Assert::same(
|
Assert::same(
|
||||||
['products.product_id', 'orders.order_id', 'customers.name', 'xXx'],
|
['products.product_id', 'orders.order_id', 'customers.name', 'xXx'],
|
||||||
$info->getColumnNames(true)
|
$info->getColumnNames(true)
|
||||||
@@ -43,7 +43,7 @@ if (!in_array($config['driver'], ['sqlite3', 'pdo', 'sqlsrv'], true)) {
|
|||||||
$columns = $info->getColumns();
|
$columns = $info->getColumns();
|
||||||
|
|
||||||
Assert::same('product_id', $columns[0]->getName());
|
Assert::same('product_id', $columns[0]->getName());
|
||||||
if (!in_array($config['driver'], ['sqlite3', 'pdo', 'sqlsrv'], true)) {
|
if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) {
|
||||||
Assert::same('products', $columns[0]->getTableName());
|
Assert::same('products', $columns[0]->getTableName());
|
||||||
}
|
}
|
||||||
Assert::null($columns[0]->getVendorInfo('xxx'));
|
Assert::null($columns[0]->getVendorInfo('xxx'));
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Dibi\Type;
|
use Dibi\Type;
|
||||||
@@ -24,7 +25,18 @@ class MockResult extends Dibi\Result
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
test(function () {
|
test('', function () {
|
||||||
|
$result = new MockResult;
|
||||||
|
$result->setType('col', Type::TEXT);
|
||||||
|
$result->setFormat(Type::TEXT, 'native');
|
||||||
|
|
||||||
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
|
Assert::same(['col' => true], $result->test(['col' => true]));
|
||||||
|
Assert::same(['col' => false], $result->test(['col' => false]));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::BOOL);
|
$result->setType('col', Type::BOOL);
|
||||||
|
|
||||||
@@ -46,7 +58,7 @@ test(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::TEXT);
|
$result->setType('col', Type::TEXT);
|
||||||
|
|
||||||
@@ -62,7 +74,7 @@ test(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::FLOAT);
|
$result->setType('col', Type::FLOAT);
|
||||||
|
|
||||||
@@ -100,6 +112,11 @@ test(function () {
|
|||||||
Assert::same(['col' => 1.0], $result->test(['col' => 1]));
|
Assert::same(['col' => 1.0], $result->test(['col' => 1]));
|
||||||
Assert::same(['col' => 1.0], $result->test(['col' => 1.0]));
|
Assert::same(['col' => 1.0], $result->test(['col' => 1.0]));
|
||||||
|
|
||||||
|
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '1.1e+10']));
|
||||||
|
Assert::same(['col' => '1.1e-10'], $result->test(['col' => '1.1e-10']));
|
||||||
|
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10']));
|
||||||
|
Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.1e+10']));
|
||||||
|
|
||||||
setlocale(LC_ALL, 'de_DE@euro', 'de_DE', 'deu_deu');
|
setlocale(LC_ALL, 'de_DE@euro', 'de_DE', 'deu_deu');
|
||||||
Assert::same(['col' => 0.0], $result->test(['col' => '']));
|
Assert::same(['col' => 0.0], $result->test(['col' => '']));
|
||||||
Assert::same(['col' => 0.0], $result->test(['col' => '0']));
|
Assert::same(['col' => 0.0], $result->test(['col' => '0']));
|
||||||
@@ -134,7 +151,7 @@ test(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::INTEGER);
|
$result->setType('col', Type::INTEGER);
|
||||||
|
|
||||||
@@ -142,7 +159,14 @@ test(function () {
|
|||||||
Assert::same(['col' => 1], $result->test(['col' => true]));
|
Assert::same(['col' => 1], $result->test(['col' => true]));
|
||||||
Assert::same(['col' => 0], $result->test(['col' => false]));
|
Assert::same(['col' => 0], $result->test(['col' => false]));
|
||||||
|
|
||||||
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning in PHP 7.1
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
|
||||||
|
} else {
|
||||||
|
Assert::exception(function () use ($result) {
|
||||||
|
Assert::same(['col' => 0], $result->test(['col' => '']));
|
||||||
|
}, TypeError::class);
|
||||||
|
}
|
||||||
|
|
||||||
Assert::same(['col' => 0], $result->test(['col' => '0']));
|
Assert::same(['col' => 0], $result->test(['col' => '0']));
|
||||||
Assert::same(['col' => 1], $result->test(['col' => '1']));
|
Assert::same(['col' => 1], $result->test(['col' => '1']));
|
||||||
Assert::same(['col' => 10], $result->test(['col' => '10']));
|
Assert::same(['col' => 10], $result->test(['col' => '10']));
|
||||||
@@ -160,7 +184,7 @@ test(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::DATETIME);
|
$result->setType('col', Type::DATETIME);
|
||||||
|
|
||||||
@@ -178,7 +202,7 @@ test(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::DATETIME);
|
$result->setType('col', Type::DATETIME);
|
||||||
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
|
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
|
||||||
@@ -197,7 +221,7 @@ test(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::DATE);
|
$result->setType('col', Type::DATE);
|
||||||
|
|
||||||
@@ -213,7 +237,7 @@ test(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test(function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::TIME);
|
$result->setType('col', Type::TIME);
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user