mirror of
https://github.com/dg/dibi.git
synced 2025-08-30 01:09:50 +02:00
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -2,5 +2,9 @@
|
||||
.gitignore export-ignore
|
||||
.github export-ignore
|
||||
.travis.yml export-ignore
|
||||
appveyor.yml export-ignore
|
||||
ecs.php export-ignore
|
||||
phpstan.neon export-ignore
|
||||
tests/ export-ignore
|
||||
|
||||
*.sh eol=lf
|
||||
*.php* diff=php linguist-language=PHP
|
||||
|
1
.github/funding.yml
vendored
Normal file
1
.github/funding.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: dg
|
22
.travis.yml
22
.travis.yml
@@ -1,8 +1,13 @@
|
||||
language: php
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4
|
||||
- 8.0snapshot
|
||||
|
||||
services:
|
||||
- mysql
|
||||
- postgresql
|
||||
|
||||
before_install:
|
||||
# turn off XDebug
|
||||
@@ -34,22 +39,21 @@ jobs:
|
||||
|
||||
|
||||
- name: Nette Coding Standard
|
||||
php: 7.4
|
||||
install:
|
||||
- travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress
|
||||
- travis_retry composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
|
||||
script:
|
||||
- php temp/coding-standard/ecs check src tests examples --config tests/coding-standard.yml
|
||||
- php temp/coding-standard/ecs check src tests
|
||||
|
||||
|
||||
- stage: Static Analysis (informative)
|
||||
install:
|
||||
# Install PHPStan
|
||||
- travis_retry composer create-project phpstan/phpstan-shim temp/phpstan --no-progress
|
||||
- travis_retry composer install --no-progress --prefer-dist
|
||||
php: 7.4
|
||||
script:
|
||||
- php temp/phpstan/phpstan.phar analyse --autoload-file vendor/autoload.php --level 5 src
|
||||
- composer run-script phpstan
|
||||
|
||||
|
||||
- stage: Code Coverage
|
||||
php: 7.4
|
||||
script:
|
||||
- vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src
|
||||
after_script:
|
||||
@@ -62,7 +66,7 @@ jobs:
|
||||
- stage: Code Coverage
|
||||
|
||||
|
||||
sudo: false
|
||||
dist: xenial
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"description": "Dibi is Database Abstraction Library for PHP",
|
||||
"keywords": ["database", "dbal", "mysql", "postgresql", "sqlite", "mssql", "sqlsrv", "oracle", "access", "pdo", "odbc"],
|
||||
"homepage": "https://dibiphp.com",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
@@ -11,11 +11,13 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"tracy/tracy": "~2.2",
|
||||
"nette/tester": "~2.0"
|
||||
"nette/tester": "~2.0",
|
||||
"nette/di": "^3.0",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"replace": {
|
||||
"dg/dibi": "*"
|
||||
@@ -23,9 +25,13 @@
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.0-dev"
|
||||
"dev-master": "4.2-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
ecs.php
Normal file
21
ecs.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Rules for Nette Coding Standard
|
||||
* https://github.com/nette/coding-standard
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
return function (Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator $containerConfigurator): void {
|
||||
$containerConfigurator->import(PRESET_DIR . '/php71.php');
|
||||
|
||||
$parameters = $containerConfigurator->parameters();
|
||||
|
||||
$parameters->set('skip', [
|
||||
// issue #260
|
||||
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer::class => ['src/Dibi/HashMap.php'],
|
||||
SlevomatCodingStandard\Sniffs\ControlStructures\RequireNullCoalesceOperatorSniff::class => ['src/Dibi/HashMap.php'],
|
||||
]);
|
||||
};
|
@@ -20,6 +20,7 @@ $dibi = new Dibi\Connection([
|
||||
// enable query logging to this file
|
||||
'profiler' => [
|
||||
'file' => 'log/log.sql',
|
||||
'errorsOnly' => false,
|
||||
],
|
||||
]);
|
||||
|
||||
|
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#'
|
56
readme.md
56
readme.md
@@ -14,7 +14,13 @@ Introduction
|
||||
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.
|
||||
|
||||
If you like Dibi, **[please make a donation now](https://nette.org/make-donation?to=dibi)**. Thank you!
|
||||
|
||||
Support Project
|
||||
---------------
|
||||
|
||||
Do you like Dibi? Are you looking forward to the new features?
|
||||
|
||||
[](https://nette.org/make-donation?to=dibi)
|
||||
|
||||
|
||||
Installation
|
||||
@@ -26,7 +32,7 @@ Install Dibi via Composer:
|
||||
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.0.
|
||||
|
||||
|
||||
Usage
|
||||
@@ -42,11 +48,11 @@ The database connection is represented by the object `Dibi\Connection`:
|
||||
|
||||
```php
|
||||
$database = new Dibi\Connection([
|
||||
'driver' => 'mysqli',
|
||||
'host' => 'localhost',
|
||||
'username' => 'root',
|
||||
'password' => '***',
|
||||
'database' => 'table',
|
||||
'driver' => 'mysqli',
|
||||
'host' => 'localhost',
|
||||
'username' => 'root',
|
||||
'password' => '***',
|
||||
'database' => 'table',
|
||||
]);
|
||||
|
||||
$result = $database->query('SELECT * FROM users');
|
||||
@@ -56,12 +62,12 @@ Alternatively, you can use the `dibi` static register, which maintains a connect
|
||||
|
||||
```php
|
||||
dibi::connect([
|
||||
'driver' => 'mysqli',
|
||||
'host' => 'localhost',
|
||||
'username' => 'root',
|
||||
'password' => '***',
|
||||
'database' => 'test',
|
||||
'charset' => 'utf8',
|
||||
'driver' => 'mysqli',
|
||||
'host' => 'localhost',
|
||||
'username' => 'root',
|
||||
'password' => '***',
|
||||
'database' => 'test',
|
||||
'charset' => 'utf8',
|
||||
]);
|
||||
|
||||
$result = dibi::query('SELECT * FROM users');
|
||||
@@ -75,6 +81,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`.
|
||||
|
||||
You can try all the examples [online at the playground](https://repl.it/@DavidGrudl/dibi-playground).
|
||||
|
||||
```php
|
||||
$result = $database->query('SELECT * FROM users');
|
||||
|
||||
@@ -110,7 +118,7 @@ $ids = [10, 20, 30];
|
||||
$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!!!
|
||||
```
|
||||
@@ -147,7 +155,7 @@ $name = $database->fetchSingle('SELECT name FROM users WHERE id = ?', $id);
|
||||
|
||||
### 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
|
||||
|----------|-----
|
||||
@@ -161,6 +169,7 @@ In addition to the `?` wild char, we can also use modifiers:
|
||||
| %d | date (accepts DateTime, string or UNIX timestamp)
|
||||
| %dt | datetime (accepts DateTime, string or UNIX timestamp)
|
||||
| %n | identifier, ie the name of the table or column
|
||||
| %N | identifier, treats period as a common character, ie alias or a database name (`%n AS %N` or `DROP DATABASE %N`)
|
||||
| %SQL | SQL - directly inserts into SQL (the alternative is Dibi\Literal)
|
||||
| %ex | SQL expression or array of expressions
|
||||
| %lmt | special - adds LIMIT to the query
|
||||
@@ -182,7 +191,7 @@ $result = $database->query('SELECT * FROM users WHERE id IN (%i)', $ids);
|
||||
// SELECT * FROM users WHERE id IN (10, 20, 30)
|
||||
```
|
||||
|
||||
The modifier '%n' is used if the table or column name is a variable. (Beware, do not allow the user to manipulate the content of such a variable):
|
||||
The modifier `%n` is used if the table or column name is a variable. (Beware, do not allow the user to manipulate the content of such a variable):
|
||||
|
||||
```php
|
||||
$table = 'blog.users';
|
||||
@@ -198,6 +207,7 @@ Three special modifiers are available for LIKE:
|
||||
| `%like~` | the expression starts with a string
|
||||
| `%~like` | the expression ends with a string
|
||||
| `%~like~` | the expression contains a string
|
||||
| `%like` | the expression matches a string
|
||||
|
||||
Search for names beginning with a string:
|
||||
|
||||
@@ -225,8 +235,8 @@ Example:
|
||||
|
||||
```php
|
||||
$arr = [
|
||||
'a' => 'hello',
|
||||
'b' => true,
|
||||
'a' => 'hello',
|
||||
'b' => true,
|
||||
];
|
||||
|
||||
$database->query('INSERT INTO table %v', $arr);
|
||||
@@ -498,7 +508,7 @@ $all = $result->fetchAssoc('customer_id|order_id');
|
||||
// we will iterate like this:
|
||||
foreach ($all as $customerId => $orders) {
|
||||
foreach ($orders as $orderId => $order) {
|
||||
...
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -530,7 +540,7 @@ $all = $result->fetchAssoc('name[]order_id');
|
||||
// we get all the Arnolds in the results
|
||||
foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
|
||||
foreach ($arnoldOrders as $orderId => $order) {
|
||||
...
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -544,8 +554,8 @@ foreach ($all as $customerId => $orders) {
|
||||
echo "Customer $customerId":
|
||||
|
||||
foreach ($orders as $orderId => $order) {
|
||||
echo "ID number: $order->number";
|
||||
// customer name is in $order->name
|
||||
echo "ID number: $order->number";
|
||||
// customer name is in $order->name
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -567,7 +577,7 @@ foreach ($all as $customerId => $row) {
|
||||
echo "Customer $row->name":
|
||||
|
||||
foreach ($row->order_id as $orderId => $order) {
|
||||
echo "ID number: $order->number";
|
||||
echo "ID number: $order->number";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@@ -45,7 +45,7 @@ class Panel implements Tracy\IBarPanel
|
||||
public function register(Dibi\Connection $connection): void
|
||||
{
|
||||
Tracy\Debugger::getBar()->addPanel($this);
|
||||
Tracy\Debugger::getBlueScreen()->addPanel([__CLASS__, 'renderException']);
|
||||
Tracy\Debugger::getBlueScreen()->addPanel([self::class, 'renderException']);
|
||||
$connection->onEvent[] = [$this, 'logEvent'];
|
||||
}
|
||||
|
||||
@@ -105,6 +105,15 @@ class Panel implements Tracy\IBarPanel
|
||||
}
|
||||
|
||||
$totalTime = $s = null;
|
||||
|
||||
$singleConnection = reset($this->events)->connection;
|
||||
foreach ($this->events as $event) {
|
||||
if ($event->connection !== $singleConnection) {
|
||||
$singleConnection = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->events as $event) {
|
||||
$totalTime += $event->time;
|
||||
$connection = $event->connection;
|
||||
@@ -112,7 +121,9 @@ class Panel implements Tracy\IBarPanel
|
||||
if ($this->explain && $event->type === Event::SELECT) {
|
||||
$backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime];
|
||||
$connection->onEvent = null;
|
||||
$cmd = is_string($this->explain) ? $this->explain : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
|
||||
$cmd = is_string($this->explain)
|
||||
? $this->explain
|
||||
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
|
||||
try {
|
||||
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
|
||||
} catch (Dibi\Exception $e) {
|
||||
@@ -120,7 +131,7 @@ class Panel implements Tracy\IBarPanel
|
||||
[$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup;
|
||||
}
|
||||
|
||||
$s .= '<tr><td>' . number_format($event->time * 1000, 3, '.', ' ');
|
||||
$s .= '<tr><td data-order="' . $event->time . '">' . number_format($event->time * 1000, 3, '.', ' ');
|
||||
if ($explain) {
|
||||
static $counter;
|
||||
$counter++;
|
||||
@@ -132,10 +143,13 @@ class Panel implements Tracy\IBarPanel
|
||||
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
|
||||
}
|
||||
if ($event->source) {
|
||||
$s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]);//->class('tracy-DibiProfiler-source');
|
||||
$s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]); //->class('tracy-DibiProfiler-source');
|
||||
}
|
||||
|
||||
$s .= "</td><td>{$event->count}</td></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 }
|
||||
@@ -143,12 +157,21 @@ class Panel implements Tracy\IBarPanel
|
||||
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
|
||||
<h1>Queries: ' . count($this->events)
|
||||
. ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', ' ') . ' ms') . ', '
|
||||
. htmlspecialchars($connection->getConfig('driver') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
|
||||
. ($connection->getConfig('host') ? ' @ ' . $connection->getConfig('host') : '')) . '</h1>
|
||||
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
|
||||
<div class="tracy-inner tracy-DibiProfiler">
|
||||
<table>
|
||||
<tr><th>Time ms</th><th>SQL Statement</th><th>Rows</th></tr>' . $s . '
|
||||
<table class="tracy-sortable">
|
||||
<tr><th>Time ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>
|
||||
' . $s . '
|
||||
</table>
|
||||
</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') ? ' @ ' . $connection->getConfig('host') : '');
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,9 @@ class Connection implements IConnection
|
||||
/** @var array Current connection configuration */
|
||||
private $config;
|
||||
|
||||
/** @var string[] resultset formats */
|
||||
private $formats;
|
||||
|
||||
/** @var Driver|null */
|
||||
private $driver;
|
||||
|
||||
@@ -42,33 +45,29 @@ class Connection implements IConnection
|
||||
* Connection options: (see driver-specific options too)
|
||||
* - lazy (bool) => if true, connection will be established only when required
|
||||
* - 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)
|
||||
* - run (bool) => enable profiler?
|
||||
* - file => file to log
|
||||
* - errorsOnly (bool) => log only errors
|
||||
* - substitutes (array) => map of driver specific substitutes (under development)
|
||||
* - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
|
||||
* @param array $config connection parameters
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($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, 'password', 'pass');
|
||||
Helpers::alias($config, 'host', 'hostname');
|
||||
@@ -78,10 +77,18 @@ class Connection implements IConnection
|
||||
$config['name'] = $name;
|
||||
$this->config = $config;
|
||||
|
||||
$this->formats = [
|
||||
Type::DATE => $this->config['result']['formatDate'],
|
||||
Type::DATETIME => $this->config['result']['formatDateTime'],
|
||||
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
|
||||
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
|
||||
];
|
||||
|
||||
// profiler
|
||||
if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) {
|
||||
$filter = $config['profiler']['filter'] ?? Event::QUERY;
|
||||
$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:"; });
|
||||
@@ -119,6 +126,7 @@ class Connection implements IConnection
|
||||
{
|
||||
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)) {
|
||||
@@ -135,6 +143,8 @@ class Connection implements IConnection
|
||||
$event = $this->onEvent ? new Event($this, Event::CONNECT) : null;
|
||||
try {
|
||||
$this->driver = new $class($this->config);
|
||||
$this->translator = new Translator($this);
|
||||
|
||||
if ($event) {
|
||||
$this->onEvent($event->done());
|
||||
}
|
||||
@@ -160,7 +170,7 @@ class Connection implements IConnection
|
||||
{
|
||||
if ($this->driver) {
|
||||
$this->driver->disconnect();
|
||||
$this->driver = null;
|
||||
$this->driver = $this->translator = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +216,7 @@ class Connection implements IConnection
|
||||
*/
|
||||
final public function query(...$args): Result
|
||||
{
|
||||
return $this->nativeQuery($this->translateArgs($args));
|
||||
return $this->nativeQuery($this->translate(...$args));
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +227,10 @@ class Connection implements IConnection
|
||||
*/
|
||||
final public function translate(...$args): string
|
||||
{
|
||||
return $this->translateArgs($args);
|
||||
if (!$this->driver) {
|
||||
$this->connect();
|
||||
}
|
||||
return (clone $this->translator)->translate($args);
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +241,7 @@ class Connection implements IConnection
|
||||
final public function test(...$args): bool
|
||||
{
|
||||
try {
|
||||
Helpers::dump($this->translateArgs($args));
|
||||
Helpers::dump($this->translate(...$args));
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
@@ -249,23 +262,7 @@ class Connection implements IConnection
|
||||
*/
|
||||
final public function dataSource(...$args): DataSource
|
||||
{
|
||||
return new DataSource($this->translateArgs($args), $this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates SQL query.
|
||||
*/
|
||||
protected function translateArgs(array $args): string
|
||||
{
|
||||
if (!$this->driver) {
|
||||
$this->connect();
|
||||
}
|
||||
if (!$this->translator) {
|
||||
$this->translator = new Translator($this);
|
||||
}
|
||||
$translator = clone $this->translator;
|
||||
return $translator->translate($args);
|
||||
return new DataSource($this->translate(...$args), $this);
|
||||
}
|
||||
|
||||
|
||||
@@ -316,16 +313,6 @@ class Connection implements IConnection
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function affectedRows(): int
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
|
||||
return $this->getAffectedRows();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
||||
* @throws Exception
|
||||
@@ -336,23 +323,13 @@ class Connection implements IConnection
|
||||
$this->connect();
|
||||
}
|
||||
$id = $this->driver->getInsertId($sequence);
|
||||
if ($id < 1) {
|
||||
if ($id === null) {
|
||||
throw new Exception('Cannot retrieve last generated ID.');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function insertId(string $sequence = null): int
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
|
||||
return $this->getInsertId($sequence);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins a transaction (if supported).
|
||||
*/
|
||||
@@ -425,14 +402,30 @@ class Connection implements IConnection
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function transaction(callable $callback)
|
||||
{
|
||||
$this->begin();
|
||||
try {
|
||||
$res = $callback();
|
||||
} catch (\Throwable $e) {
|
||||
$this->rollback();
|
||||
throw $e;
|
||||
}
|
||||
$this->commit();
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Result set factory.
|
||||
*/
|
||||
public function createResultSet(ResultDriver $resultDriver): Result
|
||||
{
|
||||
$res = new Result($resultDriver);
|
||||
return $res->setFormat(Type::DATE, $this->config['result']['formatDate'])
|
||||
->setFormat(Type::DATETIME, $this->config['result']['formatDateTime']);
|
||||
return (new Result($resultDriver, $this->config['result']['normalize'] ?? true))
|
||||
->setFormats($this->formats);
|
||||
}
|
||||
|
||||
|
||||
@@ -591,7 +584,7 @@ class Connection implements IConnection
|
||||
*/
|
||||
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.');
|
||||
}
|
||||
|
||||
|
||||
@@ -600,7 +593,7 @@ class Connection implements IConnection
|
||||
*/
|
||||
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)
|
||||
{
|
||||
if (strpbrk($sql, " \t\r\n") === false) {
|
||||
$this->sql = $connection->getDriver()->escapeIdentifier($sql); // table name
|
||||
} else {
|
||||
$this->sql = '(' . $sql . ') t'; // SQL command
|
||||
}
|
||||
$this->sql = strpbrk($sql, " \t\r\n") === false
|
||||
? $connection->getDriver()->escapeIdentifier($sql) // table name
|
||||
: '(' . $sql . ') t'; // SQL command
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
@@ -84,12 +82,9 @@ class DataSource implements IDataSource
|
||||
*/
|
||||
public function where($cond): self
|
||||
{
|
||||
if (is_array($cond)) {
|
||||
// TODO: not consistent with select and orderBy
|
||||
$this->conds[] = $cond;
|
||||
} else {
|
||||
$this->conds[] = func_get_args();
|
||||
}
|
||||
$this->conds[] = is_array($cond)
|
||||
? $cond // TODO: not consistent with select and orderBy
|
||||
: func_get_args();
|
||||
$this->result = $this->count = null;
|
||||
return $this;
|
||||
}
|
||||
@@ -232,12 +227,18 @@ class DataSource implements IDataSource
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return $this->connection->translate('
|
||||
SELECT %n', (empty($this->cols) ? '*' : $this->cols), '
|
||||
FROM %SQL', $this->sql, '
|
||||
%ex', $this->conds ? ['WHERE %and', $this->conds] : null, '
|
||||
%ex', $this->sorting ? ['ORDER BY %by', $this->sorting] : null, '
|
||||
%ofs %lmt', $this->offset, $this->limit
|
||||
return $this->connection->translate(
|
||||
"\nSELECT %n",
|
||||
(empty($this->cols) ? '*' : $this->cols),
|
||||
"\nFROM %SQL",
|
||||
$this->sql,
|
||||
"\n%ex",
|
||||
$this->conds ? ['WHERE %and', $this->conds] : null,
|
||||
"\n%ex",
|
||||
$this->sorting ? ['ORDER BY %by', $this->sorting] : null,
|
||||
"\n%ofs %lmt",
|
||||
$this->offset,
|
||||
$this->limit
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
trigger_error($e->getMessage(), E_USER_ERROR);
|
||||
|
@@ -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
|
||||
{
|
||||
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,9 +40,7 @@ class FirebirdDriver implements Dibi\Driver
|
||||
private $inTransaction = false;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('interbase')) {
|
||||
@@ -64,11 +62,9 @@ class FirebirdDriver implements Dibi\Driver
|
||||
'buffers' => 0,
|
||||
];
|
||||
|
||||
if (empty($config['persistent'])) {
|
||||
$this->connection = @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
|
||||
} else {
|
||||
$this->connection = @ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
|
||||
}
|
||||
$this->connection = empty($config['persistent'])
|
||||
? @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']) // intentionally @
|
||||
: @ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
|
||||
|
||||
if (!is_resource($this->connection)) {
|
||||
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode());
|
||||
@@ -92,11 +88,13 @@ class FirebirdDriver implements Dibi\Driver
|
||||
*/
|
||||
public function query(string $sql): ?Dibi\ResultDriver
|
||||
{
|
||||
$resource = $this->inTransaction ? $this->transaction : $this->connection;
|
||||
$resource = $this->inTransaction
|
||||
? $this->transaction
|
||||
: $this->connection;
|
||||
$res = ibase_query($resource, $sql);
|
||||
|
||||
if ($res === false) {
|
||||
if (ibase_errcode() == self::ERROR_EXCEPTION_THROWN) {
|
||||
if (ibase_errcode() === self::ERROR_EXCEPTION_THROWN) {
|
||||
preg_match('/exception (\d+) (\w+) (.*)/i', ibase_errmsg(), $match);
|
||||
throw new Dibi\ProcedureException($match[3], $match[1], $match[2], $sql);
|
||||
|
||||
@@ -247,37 +245,31 @@ class FirebirdDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes($this->escapeText($value), '%_\\');
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
|
@@ -38,8 +38,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
SELECT TRIM(RDB\$RELATION_NAME),
|
||||
CASE RDB\$VIEW_BLR WHEN NULL THEN 'TRUE' ELSE 'FALSE' END
|
||||
FROM RDB\$RELATIONS
|
||||
WHERE RDB\$SYSTEM_FLAG = 0;"
|
||||
);
|
||||
WHERE RDB\$SYSTEM_FLAG = 0;
|
||||
");
|
||||
$tables = [];
|
||||
while ($row = $res->fetch(false)) {
|
||||
$tables[] = [
|
||||
@@ -84,9 +84,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
FROM RDB\$RELATION_FIELDS r
|
||||
LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
|
||||
WHERE r.RDB\$RELATION_NAME = '$table'
|
||||
ORDER BY r.RDB\$FIELD_POSITION;"
|
||||
|
||||
);
|
||||
ORDER BY r.RDB\$FIELD_POSITION;
|
||||
");
|
||||
$columns = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$key = $row['FIELD_NAME'];
|
||||
@@ -121,8 +120,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
||||
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
||||
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
|
||||
ORDER BY s.RDB\$FIELD_POSITION"
|
||||
);
|
||||
ORDER BY s.RDB\$FIELD_POSITION
|
||||
");
|
||||
$indexes = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$key = $row['INDEX_NAME'];
|
||||
@@ -149,8 +148,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
|
||||
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
|
||||
AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY'
|
||||
ORDER BY s.RDB\$FIELD_POSITION"
|
||||
);
|
||||
ORDER BY s.RDB\$FIELD_POSITION
|
||||
");
|
||||
$keys = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$key = $row['INDEX_NAME'];
|
||||
@@ -174,8 +173,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
FROM RDB\$INDICES
|
||||
WHERE RDB\$RELATION_NAME = UPPER('$table')
|
||||
AND RDB\$UNIQUE_FLAG IS NULL
|
||||
AND RDB\$FOREIGN_KEY IS NULL;"
|
||||
);
|
||||
AND RDB\$FOREIGN_KEY IS NULL;
|
||||
");
|
||||
$indices = [];
|
||||
while ($row = $res->fetch(false)) {
|
||||
$indices[] = $row[0];
|
||||
@@ -196,8 +195,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
AND (
|
||||
RDB\$UNIQUE_FLAG IS NOT NULL
|
||||
OR RDB\$FOREIGN_KEY IS NOT NULL
|
||||
);"
|
||||
);
|
||||
);
|
||||
");
|
||||
$constraints = [];
|
||||
while ($row = $res->fetch(false)) {
|
||||
$constraints[] = $row[0];
|
||||
@@ -212,7 +211,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getTriggersMeta(string $table = null): array
|
||||
{
|
||||
$res = $this->driver->query("
|
||||
$res = $this->driver->query(
|
||||
"
|
||||
SELECT TRIM(RDB\$TRIGGER_NAME) AS TRIGGER_NAME,
|
||||
TRIM(RDB\$RELATION_NAME) AS TABLE_NAME,
|
||||
CASE RDB\$TRIGGER_TYPE
|
||||
@@ -261,7 +261,9 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
$q = 'SELECT TRIM(RDB$TRIGGER_NAME)
|
||||
FROM RDB$TRIGGERS
|
||||
WHERE RDB$SYSTEM_FLAG = 0';
|
||||
$q .= $table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table')";
|
||||
$q .= $table === null
|
||||
? ';'
|
||||
: " AND RDB\$RELATION_NAME = UPPER('$table')";
|
||||
|
||||
$res = $this->driver->query($q);
|
||||
$triggers = [];
|
||||
@@ -307,8 +309,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER
|
||||
FROM RDB\$PROCEDURE_PARAMETERS p
|
||||
LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE
|
||||
ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;"
|
||||
);
|
||||
ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;
|
||||
");
|
||||
$procedures = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$key = $row['PROCEDURE_NAME'];
|
||||
@@ -330,8 +332,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
{
|
||||
$res = $this->driver->query('
|
||||
SELECT TRIM(RDB$PROCEDURE_NAME)
|
||||
FROM RDB$PROCEDURES;'
|
||||
);
|
||||
FROM RDB$PROCEDURES;
|
||||
');
|
||||
$procedures = [];
|
||||
while ($row = $res->fetch(false)) {
|
||||
$procedures[] = $row[0];
|
||||
@@ -348,8 +350,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
$res = $this->driver->query('
|
||||
SELECT TRIM(RDB$GENERATOR_NAME)
|
||||
FROM RDB$GENERATORS
|
||||
WHERE RDB$SYSTEM_FLAG = 0;'
|
||||
);
|
||||
WHERE RDB$SYSTEM_FLAG = 0;
|
||||
');
|
||||
$generators = [];
|
||||
while ($row = $res->fetch(false)) {
|
||||
$generators[] = $row[0];
|
||||
@@ -366,8 +368,8 @@ class FirebirdReflector implements Dibi\Reflector
|
||||
$res = $this->driver->query('
|
||||
SELECT TRIM(RDB$FUNCTION_NAME)
|
||||
FROM RDB$FUNCTIONS
|
||||
WHERE RDB$SYSTEM_FLAG = 0;'
|
||||
);
|
||||
WHERE RDB$SYSTEM_FLAG = 0;
|
||||
');
|
||||
$functions = [];
|
||||
while ($row = $res->fetch(false)) {
|
||||
$functions[] = $row[0];
|
||||
|
@@ -62,10 +62,12 @@ class FirebirdResult implements Dibi\ResultDriver
|
||||
*/
|
||||
public function fetch(bool $assoc): ?array
|
||||
{
|
||||
$result = $assoc ? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT) : @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
|
||||
$result = $assoc
|
||||
? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT)
|
||||
: @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
|
||||
|
||||
if (ibase_errcode()) {
|
||||
if (ibase_errcode() == FirebirdDriver::ERROR_EXCEPTION_THROWN) {
|
||||
if (ibase_errcode() === FirebirdDriver::ERROR_EXCEPTION_THROWN) {
|
||||
preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
|
||||
throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);
|
||||
|
||||
|
@@ -47,9 +47,7 @@ class MySqliDriver implements Dibi\Driver
|
||||
private $buffered;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('mysqli')) {
|
||||
@@ -93,7 +91,7 @@ class MySqliDriver implements Dibi\Driver
|
||||
@$this->connection->real_connect( // intentionally @
|
||||
(empty($config['persistent']) ? '' : 'p:') . $config['host'],
|
||||
$config['username'],
|
||||
$config['password'],
|
||||
$config['password'] ?? '',
|
||||
$config['database'] ?? '',
|
||||
$config['port'] ?? 0,
|
||||
$config['socket'],
|
||||
@@ -132,6 +130,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.
|
||||
* @throws Dibi\DriverException
|
||||
@@ -150,6 +157,9 @@ class MySqliDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int|string $code
|
||||
*/
|
||||
public static function createException(string $message, $code, string $sql): Dibi\DriverException
|
||||
{
|
||||
if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
|
||||
@@ -190,7 +200,9 @@ class MySqliDriver implements Dibi\Driver
|
||||
*/
|
||||
public function getAffectedRows(): ?int
|
||||
{
|
||||
return $this->connection->affected_rows === -1 ? null : $this->connection->affected_rows;
|
||||
return $this->connection->affected_rows === -1
|
||||
? null
|
||||
: $this->connection->affected_rows;
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +211,7 @@ class MySqliDriver implements Dibi\Driver
|
||||
*/
|
||||
public function getInsertId(?string $sequence): ?int
|
||||
{
|
||||
return $this->connection->insert_id;
|
||||
return $this->connection->insert_id ?: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -290,37 +302,34 @@ class MySqliDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d H:i:s.u'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
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.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
@@ -334,7 +343,7 @@ class MySqliDriver implements Dibi\Driver
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : $limit)
|
||||
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
|
@@ -37,9 +37,7 @@ class OdbcDriver implements Dibi\Driver
|
||||
private $microseconds = true;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('odbc')) {
|
||||
@@ -56,11 +54,9 @@ class OdbcDriver implements Dibi\Driver
|
||||
'dsn' => ini_get('odbc.default_db'),
|
||||
];
|
||||
|
||||
if (empty($config['persistent'])) {
|
||||
$this->connection = @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
|
||||
} else {
|
||||
$this->connection = @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
|
||||
}
|
||||
$this->connection = empty($config['persistent'])
|
||||
? @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? '') // intentionally @
|
||||
: @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
|
||||
}
|
||||
|
||||
if (!is_resource($this->connection)) {
|
||||
@@ -96,7 +92,9 @@ class OdbcDriver implements Dibi\Driver
|
||||
|
||||
} elseif (is_resource($res)) {
|
||||
$this->affectedRows = Dibi\Helpers::false2Null(odbc_num_rows($res));
|
||||
return odbc_num_fields($res) ? $this->createResultDriver($res) : null;
|
||||
return odbc_num_fields($res)
|
||||
? $this->createResultDriver($res)
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -126,7 +124,7 @@ class OdbcDriver implements Dibi\Driver
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -141,7 +139,7 @@ class OdbcDriver implements Dibi\Driver
|
||||
if (!odbc_commit($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);
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +152,7 @@ class OdbcDriver implements Dibi\Driver
|
||||
if (!odbc_rollback($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);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,37 +224,31 @@ class OdbcDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format('#m/d/Y#');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#');
|
||||
}
|
||||
|
||||
|
||||
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 <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
|
@@ -42,9 +42,7 @@ class OracleDriver implements Dibi\Driver
|
||||
private $affectedRows;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('oci8')) {
|
||||
@@ -52,10 +50,6 @@ class OracleDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
$foo = &$config['charset'];
|
||||
|
||||
if (isset($config['formatDate']) || isset($config['formatDateTime'])) {
|
||||
trigger_error('OracleDriver: options formatDate and formatDateTime are deprecated.', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->nativeDate = $config['nativeDate'] ?? true;
|
||||
|
||||
if (isset($config['resource'])) {
|
||||
@@ -102,7 +96,9 @@ class OracleDriver implements Dibi\Driver
|
||||
|
||||
} elseif (is_resource($res)) {
|
||||
$this->affectedRows = Dibi\Helpers::false2Null(oci_num_rows($res));
|
||||
return oci_num_fields($res) ? $this->createResultDriver($res) : null;
|
||||
return oci_num_fields($res)
|
||||
? $this->createResultDriver($res)
|
||||
: null;
|
||||
}
|
||||
} else {
|
||||
$err = oci_error($this->connection);
|
||||
@@ -245,34 +241,28 @@ class OracleDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $this->nativeDate
|
||||
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
|
||||
: $value->format('U');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $this->nativeDate
|
||||
? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')"
|
||||
: $value->format('U');
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
@@ -280,7 +270,7 @@ class OracleDriver implements Dibi\Driver
|
||||
{
|
||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
|
||||
$value = str_replace("'", "''", $value);
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
|
@@ -96,6 +96,7 @@ class OracleResult implements Dibi\ResultDriver
|
||||
'name' => oci_field_name($this->resultSet, $i),
|
||||
'table' => null,
|
||||
'fullname' => oci_field_name($this->resultSet, $i),
|
||||
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null,
|
||||
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
|
||||
];
|
||||
}
|
||||
|
@@ -42,9 +42,7 @@ class PdoDriver implements Dibi\Driver
|
||||
private $serverVersion = '';
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('pdo')) {
|
||||
@@ -58,9 +56,15 @@ class PdoDriver implements Dibi\Driver
|
||||
if ($config['resource'] instanceof PDO) {
|
||||
$this->connection = $config['resource'];
|
||||
unset($config['resource'], $config['pdo']);
|
||||
|
||||
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
|
||||
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
|
||||
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
|
||||
} catch (\PDOException $e) {
|
||||
if ($e->getMessage() === 'could not find driver') {
|
||||
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
|
||||
@@ -69,10 +73,6 @@ class PdoDriver implements Dibi\Driver
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
|
||||
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
|
||||
}
|
||||
|
||||
$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||
$this->serverVersion = (string) ($config['version'] ?? @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)); // @ - may be not supported
|
||||
}
|
||||
@@ -234,21 +234,17 @@ class PdoDriver implements Dibi\Driver
|
||||
*/
|
||||
public function escapeText(string $value): string
|
||||
{
|
||||
if ($this->driverName === 'odbc') {
|
||||
return "'" . str_replace("'", "''", $value) . "'";
|
||||
} else {
|
||||
return $this->connection->quote($value, PDO::PARAM_STR);
|
||||
}
|
||||
return $this->driverName === 'odbc'
|
||||
? "'" . str_replace("'", "''", $value) . "'"
|
||||
: $this->connection->quote($value, PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
|
||||
public function escapeBinary(string $value): string
|
||||
{
|
||||
if ($this->driverName === 'odbc') {
|
||||
return "'" . str_replace("'", "''", $value) . "'";
|
||||
} else {
|
||||
return $this->connection->quote($value, PDO::PARAM_LOB);
|
||||
}
|
||||
return $this->driverName === 'odbc'
|
||||
? "'" . str_replace("'", "''", $value) . "'"
|
||||
: $this->connection->quote($value, PDO::PARAM_LOB);
|
||||
}
|
||||
|
||||
|
||||
@@ -289,26 +285,14 @@ class PdoDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
switch ($this->driverName) {
|
||||
case 'odbc':
|
||||
return $value->format('#m/d/Y H:i:s.u#');
|
||||
@@ -322,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.
|
||||
*/
|
||||
@@ -330,29 +320,29 @@ class PdoDriver implements Dibi\Driver
|
||||
switch ($this->driverName) {
|
||||
case 'mysql':
|
||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
|
||||
case 'oci':
|
||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
|
||||
$value = str_replace("'", "''", $value);
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
|
||||
case 'pgsql':
|
||||
$bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
|
||||
$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
|
||||
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
|
||||
case 'sqlite':
|
||||
$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 'mssql':
|
||||
case 'dblib':
|
||||
case 'sqlsrv':
|
||||
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
|
||||
default:
|
||||
throw new Dibi\NotImplementedException;
|
||||
@@ -373,7 +363,7 @@ class PdoDriver implements Dibi\Driver
|
||||
case 'mysql':
|
||||
if ($limit !== null || $offset) {
|
||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : $limit)
|
||||
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
break;
|
||||
@@ -389,7 +379,7 @@ class PdoDriver implements Dibi\Driver
|
||||
|
||||
case 'sqlite':
|
||||
if ($limit !== null || $offset) {
|
||||
$sql .= ' LIMIT ' . ($limit === null ? '-1' : $limit)
|
||||
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
break;
|
||||
|
@@ -85,7 +85,7 @@ class PdoResult implements Dibi\ResultDriver
|
||||
if ($row === false) {
|
||||
throw new Dibi\NotSupportedException('Driver does not support meta data.');
|
||||
}
|
||||
$row = $row + [
|
||||
$row += [
|
||||
'table' => null,
|
||||
'native_type' => 'VAR_STRING',
|
||||
];
|
||||
|
@@ -23,6 +23,7 @@ use Dibi\Helpers;
|
||||
* - charset => character encoding to set (default is utf8)
|
||||
* - persistent (bool) => try to find a persistent link?
|
||||
* - resource (resource) => existing connection resource
|
||||
* - connect_type (int) => see pg_connect()
|
||||
*/
|
||||
class PostgreDriver implements Dibi\Driver
|
||||
{
|
||||
@@ -35,9 +36,7 @@ class PostgreDriver implements Dibi\Driver
|
||||
private $affectedRows;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('pgsql')) {
|
||||
@@ -64,15 +63,14 @@ class PostgreDriver implements Dibi\Driver
|
||||
}
|
||||
}
|
||||
}
|
||||
$connectType = $config['connect_type'] ?? PGSQL_CONNECT_FORCE_NEW;
|
||||
|
||||
set_error_handler(function (int $severity, string $message) use (&$error) {
|
||||
$error = $message;
|
||||
});
|
||||
if (empty($config['persistent'])) {
|
||||
$this->connection = pg_connect($string, PGSQL_CONNECT_FORCE_NEW);
|
||||
} else {
|
||||
$this->connection = pg_pconnect($string, PGSQL_CONNECT_FORCE_NEW);
|
||||
}
|
||||
$this->connection = empty($config['persistent'])
|
||||
? pg_connect($string, $connectType)
|
||||
: pg_pconnect($string, $connectType);
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
@@ -171,12 +169,9 @@ class PostgreDriver implements Dibi\Driver
|
||||
*/
|
||||
public function getInsertId(?string $sequence): ?int
|
||||
{
|
||||
if ($sequence === null) {
|
||||
// PostgreSQL 8.1 is needed
|
||||
$res = $this->query('SELECT LASTVAL()');
|
||||
} else {
|
||||
$res = $this->query("SELECT CURRVAL('$sequence')");
|
||||
}
|
||||
$res = $sequence === null
|
||||
? $this->query('SELECT LASTVAL()') // PostgreSQL 8.1 is needed
|
||||
: $this->query("SELECT CURRVAL('$sequence')");
|
||||
|
||||
if (!$res) {
|
||||
return null;
|
||||
@@ -292,30 +287,24 @@ class PostgreDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d H:i:s.u'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
@@ -324,7 +313,7 @@ class PostgreDriver implements Dibi\Driver
|
||||
$bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
|
||||
$value = pg_escape_string($this->connection, $value);
|
||||
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
|
@@ -251,7 +251,10 @@ class PostgreReflector implements Dibi\Reflector
|
||||
$references[$row['name']] = array_combine($l, $f);
|
||||
}
|
||||
|
||||
if (isset($references[$row['name']][$row['lnum']]) && $references[$row['name']][$row['lnum']] === $row['fnum']) {
|
||||
if (
|
||||
isset($references[$row['name']][$row['lnum']])
|
||||
&& $references[$row['name']][$row['lnum']] === $row['fnum']
|
||||
) {
|
||||
$fKeys[$row['name']]['local'][] = $row['local'];
|
||||
$fKeys[$row['name']]['foreign'][] = $row['foreign'];
|
||||
}
|
||||
|
@@ -97,7 +97,9 @@ class PostgreResult implements Dibi\ResultDriver
|
||||
'table' => pg_field_table($this->resultSet, $i),
|
||||
'nativetype' => pg_field_type($this->resultSet, $i),
|
||||
];
|
||||
$row['fullname'] = $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'];
|
||||
$row['fullname'] = $row['table']
|
||||
? $row['table'] . '.' . $row['name']
|
||||
: $row['name'];
|
||||
$columns[] = $row;
|
||||
}
|
||||
return $columns;
|
||||
|
@@ -37,9 +37,7 @@ class SqliteDriver implements Dibi\Driver
|
||||
private $fmtDateTime;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('sqlite3')) {
|
||||
@@ -139,7 +137,7 @@ class SqliteDriver implements Dibi\Driver
|
||||
*/
|
||||
public function getInsertId(?string $sequence): ?int
|
||||
{
|
||||
return $this->connection->lastInsertRowID();
|
||||
return $this->connection->lastInsertRowID() ?: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -230,37 +228,31 @@ class SqliteDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $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
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
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 <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
@@ -273,7 +265,7 @@ class SqliteDriver implements Dibi\Driver
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
$sql .= ' LIMIT ' . ($limit === null ? '-1' : $limit)
|
||||
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
@@ -294,8 +286,12 @@ class SqliteDriver implements Dibi\Driver
|
||||
/**
|
||||
* Registers an aggregating user defined function for use in SQL statements.
|
||||
*/
|
||||
public function registerAggregateFunction(string $name, callable $rowCallback, callable $agrCallback, int $numArgs = -1): void
|
||||
{
|
||||
public function registerAggregateFunction(
|
||||
string $name,
|
||||
callable $rowCallback,
|
||||
callable $agrCallback,
|
||||
int $numArgs = -1
|
||||
): void {
|
||||
$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
|
||||
}
|
||||
}
|
||||
|
@@ -64,7 +64,7 @@ class SqliteReflector implements Dibi\Reflector
|
||||
'fullname' => "$table.$column",
|
||||
'nativetype' => strtoupper($type[0]),
|
||||
'size' => isset($type[1]) ? (int) $type[1] : null,
|
||||
'nullable' => $row['notnull'] == '0',
|
||||
'nullable' => $row['notnull'] === 0,
|
||||
'default' => $row['dflt_value'],
|
||||
'autoincrement' => $row['pk'] && $type[0] === 'INTEGER',
|
||||
'vendor' => $row,
|
||||
@@ -98,7 +98,7 @@ class SqliteReflector implements Dibi\Reflector
|
||||
$column = $indexes[$index]['columns'][0];
|
||||
$primary = false;
|
||||
foreach ($columns as $info) {
|
||||
if ($column == $info['name']) {
|
||||
if ($column === $info['name']) {
|
||||
$primary = $info['vendor']['pk'];
|
||||
break;
|
||||
}
|
||||
|
@@ -96,7 +96,7 @@ class SqliteResult implements Dibi\ResultDriver
|
||||
'name' => $this->resultSet->columnName($i),
|
||||
'table' => null,
|
||||
'fullname' => $this->resultSet->columnName($i),
|
||||
'nativetype' => $types[$this->resultSet->columnType($i)],
|
||||
'nativetype' => $types[$this->resultSet->columnType($i)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414
|
||||
];
|
||||
}
|
||||
return $columns;
|
||||
|
@@ -39,9 +39,7 @@ class SqlsrvDriver implements Dibi\Driver
|
||||
private $version = '';
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('sqlsrv')) {
|
||||
@@ -100,7 +98,9 @@ class SqlsrvDriver implements Dibi\Driver
|
||||
|
||||
} elseif (is_resource($res)) {
|
||||
$this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
|
||||
return sqlsrv_num_fields($res) ? $this->createResultDriver($res) : null;
|
||||
return sqlsrv_num_fields($res)
|
||||
? $this->createResultDriver($res)
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -196,13 +196,13 @@ class SqlsrvDriver implements Dibi\Driver
|
||||
*/
|
||||
public function escapeText(string $value): string
|
||||
{
|
||||
return "'" . str_replace("'", "''", $value) . "'";
|
||||
return "N'" . str_replace("'", "''", $value) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeBinary(string $value): string
|
||||
{
|
||||
return "'" . str_replace("'", "''", $value) . "'";
|
||||
return '0x' . bin2hex($value);
|
||||
}
|
||||
|
||||
|
||||
@@ -219,37 +219,31 @@ class SqlsrvDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
|
@@ -100,7 +100,7 @@ class SqlsrvReflector implements Dibi\Reflector
|
||||
*/
|
||||
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 = [];
|
||||
while ($row = $keyUsagesRes->fetch(true)) {
|
||||
$keyUsages[$row['index_name']] = explode(',', $row['index_keys']);
|
||||
|
@@ -61,7 +61,7 @@ class SqlsrvResult implements Dibi\ResultDriver
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
@@ -32,6 +32,12 @@ namespace Dibi;
|
||||
* @method Fluent and(...$cond)
|
||||
* @method Fluent or(...$cond)
|
||||
* @method Fluent using(...$cond)
|
||||
* @method Fluent update(...$cond)
|
||||
* @method Fluent insert(...$cond)
|
||||
* @method Fluent delete(...$cond)
|
||||
* @method Fluent into(...$cond)
|
||||
* @method Fluent values(...$cond)
|
||||
* @method Fluent set(...$args)
|
||||
* @method Fluent asc()
|
||||
* @method Fluent desc()
|
||||
*/
|
||||
@@ -113,7 +119,7 @@ class Fluent implements IDataSource
|
||||
$this->connection = $connection;
|
||||
|
||||
if (self::$normalizer === null) {
|
||||
self::$normalizer = new HashMap([__CLASS__, '_formatClause']);
|
||||
self::$normalizer = new HashMap([self::class, '_formatClause']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,11 +311,9 @@ class Fluent implements IDataSource
|
||||
*/
|
||||
public function fetch()
|
||||
{
|
||||
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
|
||||
return $this->query($this->_export(null, ['%lmt', 1]))->fetch();
|
||||
} else {
|
||||
return $this->query($this->_export())->fetch();
|
||||
}
|
||||
return $this->command === 'SELECT' && !$this->clauses['LIMIT']
|
||||
? $this->query($this->_export(null, ['%lmt', 1]))->fetch()
|
||||
: $this->query($this->_export())->fetch();
|
||||
}
|
||||
|
||||
|
||||
@@ -319,11 +323,9 @@ class Fluent implements IDataSource
|
||||
*/
|
||||
public function fetchSingle()
|
||||
{
|
||||
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
|
||||
return $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle();
|
||||
} else {
|
||||
return $this->query($this->_export())->fetchSingle();
|
||||
}
|
||||
return $this->command === 'SELECT' && !$this->clauses['LIMIT']
|
||||
? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle()
|
||||
: $this->query($this->_export())->fetchSingle();
|
||||
}
|
||||
|
||||
|
||||
@@ -423,7 +425,7 @@ class Fluent implements IDataSource
|
||||
if ($clause === null) {
|
||||
$data = $this->clauses;
|
||||
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']);
|
||||
}
|
||||
|
||||
|
@@ -143,8 +143,8 @@ class Helpers
|
||||
{
|
||||
$best = null;
|
||||
$min = (strlen($value) / 4 + 1) * 10 + .1;
|
||||
foreach (array_unique($items, SORT_REGULAR) as $item) {
|
||||
$item = is_object($item) ? $item->getName() : $item;
|
||||
$items = array_map('strval', $items);
|
||||
foreach (array_unique($items) as $item) {
|
||||
if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
|
||||
$min = $len;
|
||||
$best = $item;
|
||||
@@ -201,13 +201,11 @@ class Helpers
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @internal */
|
||||
public static function getTypeCache(): HashMap
|
||||
{
|
||||
if (self::$types === null) {
|
||||
self::$types = new HashMap([__CLASS__, 'detectType']);
|
||||
self::$types = new HashMap([self::class, 'detectType']);
|
||||
}
|
||||
return self::$types;
|
||||
}
|
||||
@@ -279,18 +277,14 @@ class Helpers
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @internal */
|
||||
public static function false2Null($val)
|
||||
{
|
||||
return $val === false ? null : $val;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @internal */
|
||||
public static function intVal($value): int
|
||||
{
|
||||
if (is_int($value)) {
|
||||
|
@@ -25,11 +25,15 @@ class FileLogger
|
||||
/** @var int */
|
||||
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->filter = $filter ?: Dibi\Event::QUERY;
|
||||
$this->errorsOnly = $errorsOnly;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,39 +42,42 @@ class FileLogger
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
$handle = fopen($this->file, 'a');
|
||||
if (!$handle) {
|
||||
return; // or throw exception?
|
||||
}
|
||||
flock($handle, LOCK_EX);
|
||||
|
||||
if ($event->result instanceof \Exception) {
|
||||
$message = $event->result->getMessage();
|
||||
if ($code = $event->result->getCode()) {
|
||||
$message = "[$code] $message";
|
||||
}
|
||||
fwrite($handle,
|
||||
$this->writeToFile(
|
||||
$event,
|
||||
"ERROR: $message"
|
||||
. "\n-- SQL: " . $event->sql
|
||||
. "\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name')
|
||||
. ";\n-- " . date('Y-m-d H:i:s')
|
||||
. "\n\n"
|
||||
. "\n-- SQL: " . $event->sql
|
||||
);
|
||||
} else {
|
||||
fwrite($handle,
|
||||
$this->writeToFile(
|
||||
$event,
|
||||
'OK: ' . $event->sql
|
||||
. ($event->count ? ";\n-- rows: " . $event->count : '')
|
||||
. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
|
||||
. "\n-- source: " . implode(':', $event->source)
|
||||
. "\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name')
|
||||
. "\n-- " . date('Y-m-d H:i:s')
|
||||
. "\n\n"
|
||||
. ($event->count ? ";\n-- rows: " . $event->count : '')
|
||||
. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
|
||||
. "\n-- source: " . implode(':', $event->source)
|
||||
);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -72,7 +72,9 @@ class Column
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,18 +108,14 @@ class Column
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
/** @return mixed */
|
||||
public function getDefault()
|
||||
{
|
||||
return $this->info['default'] ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
/** @return mixed */
|
||||
public function getVendorInfo(string $key)
|
||||
{
|
||||
return $this->info['vendor'][$key] ?? null;
|
||||
|
@@ -46,9 +46,7 @@ class Database
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Table[]
|
||||
*/
|
||||
/** @return Table[] */
|
||||
public function getTables(): array
|
||||
{
|
||||
$this->init();
|
||||
@@ -56,9 +54,7 @@ class Database
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
/** @return string[] */
|
||||
public function getTableNames(): array
|
||||
{
|
||||
$this->init();
|
||||
|
@@ -28,7 +28,7 @@ class Result
|
||||
/** @var Column[]|null */
|
||||
private $columns;
|
||||
|
||||
/** @var string[]|null */
|
||||
/** @var Column[]|null */
|
||||
private $names;
|
||||
|
||||
|
||||
@@ -38,9 +38,7 @@ class Result
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Column[]
|
||||
*/
|
||||
/** @return Column[] */
|
||||
public function getColumns(): array
|
||||
{
|
||||
$this->initColumns();
|
||||
@@ -48,9 +46,7 @@ class Result
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
/** @return string[] */
|
||||
public function getColumnNames(bool $fullNames = false): array
|
||||
{
|
||||
$this->initColumns();
|
||||
@@ -86,7 +82,9 @@ class Result
|
||||
{
|
||||
if ($this->columns === null) {
|
||||
$this->columns = [];
|
||||
$reflector = $this->driver instanceof Dibi\Reflector ? $this->driver : null;
|
||||
$reflector = $this->driver instanceof Dibi\Reflector
|
||||
? $this->driver
|
||||
: null;
|
||||
foreach ($this->driver->getResultColumns() as $info) {
|
||||
$this->columns[] = $this->names[strtolower($info['name'])] = new Column($reflector, $info);
|
||||
}
|
||||
|
@@ -69,9 +69,7 @@ class Table
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Column[]
|
||||
*/
|
||||
/** @return Column[] */
|
||||
public function getColumns(): array
|
||||
{
|
||||
$this->initColumns();
|
||||
@@ -79,9 +77,7 @@ class Table
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
/** @return string[] */
|
||||
public function getColumnNames(): array
|
||||
{
|
||||
$this->initColumns();
|
||||
@@ -113,9 +109,7 @@ class Table
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return ForeignKey[]
|
||||
*/
|
||||
/** @return ForeignKey[] */
|
||||
public function getForeignKeys(): array
|
||||
{
|
||||
$this->initForeignKeys();
|
||||
@@ -123,9 +117,7 @@ class Table
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Index[]
|
||||
*/
|
||||
/** @return Index[] */
|
||||
public function getIndexes(): array
|
||||
{
|
||||
$this->initIndexes();
|
||||
|
@@ -19,7 +19,7 @@ class Result implements IDataSource
|
||||
{
|
||||
use Strict;
|
||||
|
||||
/** @var ResultDriver */
|
||||
/** @var ResultDriver|null */
|
||||
private $driver;
|
||||
|
||||
/** @var array Translate table */
|
||||
@@ -41,10 +41,12 @@ class Result implements IDataSource
|
||||
private $formats = [];
|
||||
|
||||
|
||||
public function __construct(ResultDriver $driver)
|
||||
public function __construct(ResultDriver $driver, bool $normalize = true)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
$this->detectTypes();
|
||||
if ($normalize) {
|
||||
$this->detectTypes();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +85,9 @@ class Result implements IDataSource
|
||||
*/
|
||||
final public function seek(int $row): bool
|
||||
{
|
||||
return ($row !== 0 || $this->fetched) ? $this->getResultDriver()->seek($row) : true;
|
||||
return ($row !== 0 || $this->fetched)
|
||||
? $this->getResultDriver()->seek($row)
|
||||
: true;
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +203,7 @@ class Result implements IDataSource
|
||||
*/
|
||||
final public function fetchAll(int $offset = null, int $limit = null): array
|
||||
{
|
||||
$limit = $limit === null ? -1 : $limit;
|
||||
$limit = $limit ?? -1;
|
||||
$this->seek($offset ?: 0);
|
||||
$row = $this->fetch();
|
||||
if (!$row) {
|
||||
@@ -242,6 +246,9 @@ class Result implements IDataSource
|
||||
|
||||
$data = null;
|
||||
$assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
if (!$assoc) {
|
||||
throw new \InvalidArgumentException("Invalid descriptor '$assoc'.");
|
||||
}
|
||||
|
||||
// check columns
|
||||
foreach ($assoc as $as) {
|
||||
@@ -282,7 +289,7 @@ class Result implements IDataSource
|
||||
}
|
||||
|
||||
} elseif ($as !== '|') { // associative-array node
|
||||
$x = &$x[$row->$as];
|
||||
$x = &$x[(string) $row->$as];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,13 +299,12 @@ class Result implements IDataSource
|
||||
} while ($row = $this->fetch());
|
||||
|
||||
unset($x);
|
||||
/** @var mixed[] $data */
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
/** @deprecated */
|
||||
private function oldFetchAssoc(string $assoc)
|
||||
{
|
||||
$this->seek(0);
|
||||
@@ -350,16 +356,14 @@ class Result implements IDataSource
|
||||
}
|
||||
|
||||
} else { // associative-array node
|
||||
$x = &$x[$row->$as];
|
||||
$x = &$x[(string) $row->$as];
|
||||
}
|
||||
}
|
||||
|
||||
if ($x === null) { // build leaf
|
||||
if ($leaf === '=') {
|
||||
$x = $row->toArray();
|
||||
} else {
|
||||
$x = $row;
|
||||
}
|
||||
$x = $leaf === '='
|
||||
? $row->toArray()
|
||||
: $row;
|
||||
}
|
||||
} while ($row = $this->fetch());
|
||||
|
||||
@@ -452,7 +456,12 @@ class Result implements IDataSource
|
||||
continue;
|
||||
}
|
||||
$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;
|
||||
|
||||
} elseif ($type === Type::INTEGER) {
|
||||
@@ -482,21 +491,31 @@ class Result implements IDataSource
|
||||
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
|
||||
if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ...
|
||||
$value = new DateTime($value);
|
||||
$row[$key] = empty($this->formats[$type]) ? $value : $value->format($this->formats[$type]);
|
||||
$row[$key] = $format ? $value->format($format) : $value;
|
||||
} else {
|
||||
$row[$key] = null;
|
||||
}
|
||||
|
||||
} elseif ($type === Type::TIME_INTERVAL) {
|
||||
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
|
||||
$row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
|
||||
$row[$key]->invert = (int) (bool) $m[1];
|
||||
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
|
||||
$value->invert = (int) (bool) $m[1];
|
||||
$row[$key] = $format ? $value->format($format) : $value;
|
||||
|
||||
} 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) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,7 +551,7 @@ class Result implements IDataSource
|
||||
|
||||
|
||||
/**
|
||||
* Sets date format.
|
||||
* Sets type format.
|
||||
*/
|
||||
final public function setFormat(string $type, ?string $format): self
|
||||
{
|
||||
@@ -541,6 +560,16 @@ class Result implements IDataSource
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets type formats.
|
||||
*/
|
||||
final public function setFormats(array $formats): self
|
||||
{
|
||||
$this->formats = $formats;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns data format.
|
||||
*/
|
||||
@@ -565,9 +594,7 @@ class Result implements IDataSource
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Reflection\Column[]
|
||||
*/
|
||||
/** @return Reflection\Column[] */
|
||||
final public function getColumns(): array
|
||||
{
|
||||
return $this->getInfo()->getColumns();
|
||||
|
@@ -29,15 +29,12 @@ trait Strict
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$class = get_class($this);
|
||||
if ($cb = self::extensionMethod($class . '::' . $name)) { // back compatiblity
|
||||
trigger_error("Extension methods such as $class::$name() are deprecated", E_USER_DEPRECATED);
|
||||
array_unshift($args, $this);
|
||||
return $cb(...$args);
|
||||
}
|
||||
$class = method_exists($this, $name) ? 'parent' : get_class($this);
|
||||
$class = method_exists($this, $name) ? 'parent' : static::class;
|
||||
$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");
|
||||
}
|
||||
|
||||
@@ -48,9 +45,12 @@ trait Strict
|
||||
*/
|
||||
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));
|
||||
$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");
|
||||
}
|
||||
|
||||
@@ -69,7 +69,10 @@ trait Strict
|
||||
}
|
||||
$rc = new ReflectionClass($this);
|
||||
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
|
||||
$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");
|
||||
}
|
||||
|
||||
@@ -82,7 +85,10 @@ trait Strict
|
||||
{
|
||||
$rc = new ReflectionClass($this);
|
||||
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
|
||||
$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");
|
||||
}
|
||||
|
||||
@@ -99,42 +105,7 @@ trait Strict
|
||||
*/
|
||||
public function __unset(string $name)
|
||||
{
|
||||
$class = get_class($this);
|
||||
$class = static::class;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -92,24 +92,26 @@ final class Translator
|
||||
$sql[] = $arg;
|
||||
} else {
|
||||
$sql[] = substr($arg, 0, $toSkip)
|
||||
/*
|
||||
. preg_replace_callback('/
|
||||
(?=[`[\'":%?]) ## speed-up
|
||||
(?:
|
||||
`(.+?)`| ## 1) `identifier`
|
||||
\[(.+?)\]| ## 2) [identifier]
|
||||
(\')((?:\'\'|[^\'])*)\'| ## 3,4) 'string'
|
||||
(")((?:""|[^"])*)"| ## 5,6) "string"
|
||||
(\'|")| ## 7) lone quote
|
||||
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
|
||||
%([a-zA-Z~][a-zA-Z0-9~]{0,5})|## 10) modifier
|
||||
(\?) ## 11) placeholder
|
||||
)/xs',
|
||||
*/ // note: this can change $this->args & $this->cursor & ...
|
||||
. preg_replace_callback('/(?=[`[\'":%?])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?)|%([a-zA-Z~][a-zA-Z0-9~]{0,5})|(\?))/s',
|
||||
// note: this can change $this->args & $this->cursor & ...
|
||||
. preg_replace_callback(
|
||||
<<<'XX'
|
||||
/
|
||||
(?=[`['":%?]) ## speed-up
|
||||
(?:
|
||||
`(.+?)`| ## 1) `identifier`
|
||||
\[(.+?)\]| ## 2) [identifier]
|
||||
(')((?:''|[^'])*)'| ## 3,4) string
|
||||
(")((?:""|[^"])*)"| ## 5,6) "string"
|
||||
('|")| ## 7) lone quote
|
||||
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
|
||||
%([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier
|
||||
(\?) ## 11) placeholder
|
||||
)/xs
|
||||
XX
|
||||
,
|
||||
[$this, 'cb'],
|
||||
substr($arg, $toSkip)
|
||||
);
|
||||
);
|
||||
if (preg_last_error()) {
|
||||
throw new PcreException;
|
||||
}
|
||||
@@ -151,7 +153,7 @@ final class Translator
|
||||
$sql[] = '*/';
|
||||
}
|
||||
|
||||
$sql = implode(' ', $sql);
|
||||
$sql = trim(implode(' ', $sql), ' ');
|
||||
|
||||
if ($this->errors) {
|
||||
throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql);
|
||||
@@ -289,7 +291,7 @@ final class Translator
|
||||
if (is_array($v)) {
|
||||
$vx[] = $this->formatValue($v, 'ex');
|
||||
} elseif (is_string($k)) {
|
||||
$v = (is_string($v) && strncasecmp($v, 'd', 1)) || $v > 0 ? 'ASC' : 'DESC';
|
||||
$v = (is_string($v) ? strncasecmp($v, 'd', 1) : $v > 0) ? 'ASC' : 'DESC';
|
||||
$vx[] = $this->identifiers->$k . ' ' . $v;
|
||||
} else {
|
||||
$vx[] = $this->identifiers->$v;
|
||||
@@ -322,7 +324,13 @@ final class Translator
|
||||
} elseif ($value instanceof Expression && $modifier === 'ex') {
|
||||
return $this->connection->translate(...$value->getValues());
|
||||
|
||||
} elseif ($value instanceof \DateTimeInterface && ($modifier === 'd' || $modifier === 't' || $modifier === 'dt')) {
|
||||
} elseif (
|
||||
$value instanceof \DateTimeInterface
|
||||
&& ($modifier === 'd'
|
||||
|| $modifier === 't'
|
||||
|| $modifier === 'dt'
|
||||
)
|
||||
) {
|
||||
// continue
|
||||
} else {
|
||||
$type = is_object($value) ? get_class($value) : gettype($value);
|
||||
@@ -332,21 +340,29 @@ final class Translator
|
||||
|
||||
switch ($modifier) {
|
||||
case 's': // string
|
||||
return $value === null ? 'NULL' : $this->driver->escapeText((string) $value);
|
||||
return $value === null
|
||||
? 'NULL'
|
||||
: $this->driver->escapeText((string) $value);
|
||||
|
||||
case 'bin':// binary
|
||||
return $value === null ? 'NULL' : $this->driver->escapeBinary($value);
|
||||
return $value === null
|
||||
? 'NULL'
|
||||
: $this->driver->escapeBinary($value);
|
||||
|
||||
case 'b': // boolean
|
||||
return $value === null ? 'NULL' : $this->driver->escapeBool((bool) $value);
|
||||
return $value === null
|
||||
? 'NULL'
|
||||
: $this->driver->escapeBool((bool) $value);
|
||||
|
||||
case 'sN': // string or null
|
||||
case 'sn':
|
||||
return $value == '' ? '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
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
if ($value === '' || $value === 0 || $value === null) {
|
||||
return 'NULL';
|
||||
}
|
||||
// break omitted
|
||||
case 'i': // signed int
|
||||
@@ -381,10 +397,13 @@ final class Translator
|
||||
case 'dt': // datetime
|
||||
if ($value === null) {
|
||||
return 'NULL';
|
||||
} else {
|
||||
return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value);
|
||||
} elseif (!$value instanceof \DateTimeInterface) {
|
||||
$value = new DateTime($value);
|
||||
}
|
||||
// break omitted
|
||||
return $modifier === 'd'
|
||||
? $this->driver->escapeDate($value)
|
||||
: $this->driver->escapeDateTime($value);
|
||||
|
||||
case 'by':
|
||||
case 'n': // composed identifier name
|
||||
return $this->identifiers->$value;
|
||||
@@ -399,11 +418,23 @@ final class Translator
|
||||
$toSkip = strcspn($value, '`[\'":');
|
||||
if (strlen($value) !== $toSkip) {
|
||||
$value = substr($value, 0, $toSkip)
|
||||
. preg_replace_callback(
|
||||
'/(?=[`[\'":])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?))/s',
|
||||
[$this, 'cb'],
|
||||
substr($value, $toSkip)
|
||||
);
|
||||
. preg_replace_callback(
|
||||
<<<'XX'
|
||||
/
|
||||
(?=[`['":])
|
||||
(?:
|
||||
`(.+?)`|
|
||||
\[(.+?)\]|
|
||||
(')((?:''|[^'])*)'|
|
||||
(")((?:""|[^"])*)"|
|
||||
('|")|
|
||||
:(\S*?:)([a-zA-Z0-9._]?)
|
||||
)/sx
|
||||
XX
|
||||
,
|
||||
[$this, 'cb'],
|
||||
substr($value, $toSkip)
|
||||
);
|
||||
if (preg_last_error()) {
|
||||
throw new PcreException;
|
||||
}
|
||||
@@ -414,12 +445,15 @@ final class Translator
|
||||
return (string) $value;
|
||||
|
||||
case 'like~': // LIKE string%
|
||||
return $this->driver->escapeLike($value, 1);
|
||||
return $this->driver->escapeLike($value, 2);
|
||||
|
||||
case '~like': // LIKE %string
|
||||
return $this->driver->escapeLike($value, -1);
|
||||
return $this->driver->escapeLike($value, 1);
|
||||
|
||||
case '~like~': // LIKE %string%
|
||||
return $this->driver->escapeLike($value, 3);
|
||||
|
||||
case 'like': // LIKE string
|
||||
return $this->driver->escapeLike($value, 0);
|
||||
|
||||
case 'and':
|
||||
@@ -455,6 +489,9 @@ final class Translator
|
||||
} elseif ($value instanceof \DateTimeInterface) {
|
||||
return $this->driver->escapeDateTime($value);
|
||||
|
||||
} elseif ($value instanceof \DateInterval) {
|
||||
return $this->driver->escapeDateInterval($value);
|
||||
|
||||
} elseif ($value instanceof Literal) {
|
||||
return (string) $value;
|
||||
|
||||
@@ -590,7 +627,9 @@ final class Translator
|
||||
if ($matches[8]) { // SQL identifier substitution
|
||||
$m = substr($matches[8], 0, -1);
|
||||
$m = $this->connection->getSubstitutes()->$m;
|
||||
return $matches[9] == '' ? $this->formatValue($m, null) : $m . $matches[9]; // value or identifier
|
||||
return $matches[9] === ''
|
||||
? $this->formatValue($m, null)
|
||||
: $m . $matches[9]; // value or identifier
|
||||
}
|
||||
|
||||
throw new \Exception('this should be never executed');
|
||||
|
@@ -30,6 +30,6 @@ class Type
|
||||
|
||||
final public function __construct()
|
||||
{
|
||||
throw new \LogicException('Cannot instantiate static class ' . __CLASS__);
|
||||
throw new \LogicException('Cannot instantiate static class ' . self::class);
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
* @method static void begin(string $savepoint = null)
|
||||
* @method static void commit(string $savepoint = null)
|
||||
* @method static void rollback(string $savepoint = null)
|
||||
* @method static mixed transaction(callable $callback)
|
||||
* @method static Dibi\Reflection\Database getDatabaseInfo()
|
||||
* @method static Dibi\Fluent command()
|
||||
* @method static Dibi\Fluent select(...$args)
|
||||
@@ -44,7 +45,7 @@ class dibi
|
||||
|
||||
/** version */
|
||||
public const
|
||||
VERSION = '4.0.2';
|
||||
VERSION = '4.2.0';
|
||||
|
||||
/** sorting order */
|
||||
public const
|
||||
@@ -75,7 +76,7 @@ class dibi
|
||||
*/
|
||||
final public function __construct()
|
||||
{
|
||||
throw new LogicException('Cannot instantiate static class ' . get_class($this));
|
||||
throw new LogicException('Cannot instantiate static class ' . static::class);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,26 +146,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**/
|
||||
|
||||
|
||||
|
@@ -87,15 +87,11 @@ interface Driver
|
||||
|
||||
function escapeBool(bool $value): string;
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
function escapeDate($value): string;
|
||||
function escapeDate(\DateTimeInterface $value): string;
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
function escapeDateTime($value): string;
|
||||
function escapeDateTime(\DateTimeInterface $value): string;
|
||||
|
||||
function escapeDateInterval(\DateInterval $value): string;
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
|
@@ -1,7 +0,0 @@
|
||||
imports:
|
||||
- { resource: '../temp/coding-standard/coding-standard-php71.yml' }
|
||||
|
||||
parameters:
|
||||
skip:
|
||||
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer:
|
||||
- src/Dibi/HashMap.php # issue #260
|
@@ -12,7 +12,7 @@ use Tester\Assert;
|
||||
require __DIR__ . '/bootstrap.php';
|
||||
|
||||
|
||||
test(function () use ($config) {
|
||||
test('', function () use ($config) {
|
||||
$conn = new Connection($config);
|
||||
Assert::true($conn->isConnected());
|
||||
|
||||
@@ -21,7 +21,7 @@ test(function () use ($config) {
|
||||
});
|
||||
|
||||
|
||||
test(function () use ($config) { // lazy
|
||||
test('lazy', function () use ($config) {
|
||||
$conn = new Connection($config + ['lazy' => true]);
|
||||
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);
|
||||
Assert::true($conn->isConnected());
|
||||
|
||||
@@ -40,7 +40,7 @@ test(function () use ($config) {
|
||||
});
|
||||
|
||||
|
||||
test(function () use ($config) {
|
||||
test('', function () use ($config) {
|
||||
$conn = new Connection($config);
|
||||
Assert::true($conn->isConnected());
|
||||
|
||||
@@ -52,7 +52,18 @@ test(function () use ($config) {
|
||||
});
|
||||
|
||||
|
||||
test(function () use ($config) {
|
||||
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) {
|
||||
Assert::exception(function () use ($config) {
|
||||
new Connection($config + ['onConnect' => '']);
|
||||
}, InvalidArgumentException::class, "Configuration option 'onConnect' must be array.");
|
||||
|
@@ -48,3 +48,24 @@ $conn->query('INSERT INTO [products]', [
|
||||
]);
|
||||
$conn->commit();
|
||||
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||
|
||||
|
||||
|
||||
Assert::exception(function () use ($conn) {
|
||||
$conn->transaction(function () use ($conn) {
|
||||
$conn->query('INSERT INTO [products]', [
|
||||
'title' => 'Test product',
|
||||
]);
|
||||
throw new Exception('my exception');
|
||||
});
|
||||
}, \Throwable::class, 'my exception');
|
||||
|
||||
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||
|
||||
$conn->transaction(function () use ($conn) {
|
||||
$conn->query('INSERT INTO [products]', [
|
||||
'title' => 'Test product',
|
||||
]);
|
||||
});
|
||||
|
||||
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||
|
@@ -77,7 +77,7 @@ SELECT [product_id]
|
||||
FROM (SELECT * FROM products) t
|
||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||
ORDER BY [product_id] ASC
|
||||
) t"), dibi::$sql);
|
||||
) t"), dibi::$sql);
|
||||
Assert::same(1, $ds->toDataSource()->count());
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ SELECT [product_id]
|
||||
FROM (SELECT * FROM products) t
|
||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||
ORDER BY [product_id] ASC
|
||||
"),
|
||||
"),
|
||||
dibi::$sql
|
||||
);
|
||||
|
||||
@@ -106,7 +106,7 @@ SELECT [product_id]
|
||||
FROM (SELECT * FROM products) t
|
||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||
ORDER BY [product_id] ASC
|
||||
) t"),
|
||||
) t"),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
|
@@ -56,28 +56,28 @@ $fluent = $conn->select('*')
|
||||
->orderBy('customer_id');
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
|
||||
$fluent->fetch();
|
||||
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
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
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
|
||||
);
|
||||
$fluent->fetchAll(0, 3);
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
|
||||
@@ -85,16 +85,16 @@ Assert::same(
|
||||
$fluent->limit(0);
|
||||
$fluent->fetch();
|
||||
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
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
|
||||
@@ -103,12 +103,12 @@ $fluent->removeClause('limit');
|
||||
$fluent->removeClause('offset');
|
||||
$fluent->fetch();
|
||||
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
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
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
|
||||
);
|
||||
Assert::same(
|
||||
|
@@ -22,28 +22,28 @@ $fluent = $conn->select('*')
|
||||
->orderBy('customer_id');
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
|
||||
$fluent->fetch();
|
||||
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
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
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
|
||||
);
|
||||
$fluent->fetchAll(2, 3);
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
|
||||
@@ -51,16 +51,16 @@ Assert::same(
|
||||
$fluent->limit(0);
|
||||
$fluent->fetch();
|
||||
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
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
|
||||
@@ -68,16 +68,16 @@ Assert::same(
|
||||
$fluent->removeClause('limit');
|
||||
$fluent->fetch();
|
||||
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
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
|
||||
@@ -85,12 +85,12 @@ Assert::same(
|
||||
$fluent->removeClause('offset');
|
||||
$fluent->fetch();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||
dibi::$sql
|
||||
);
|
||||
Assert::same(
|
||||
|
@@ -95,9 +95,9 @@ $fluent = $conn->select('*')
|
||||
|
||||
Assert::same(
|
||||
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',
|
||||
'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',
|
||||
'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',
|
||||
'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
|
||||
);
|
||||
@@ -134,7 +134,10 @@ $fluent = $conn->select('*')
|
||||
->where(['x' => 'a', 'b', 'c']);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
|
@@ -29,13 +29,6 @@ Assert::exception(function () {
|
||||
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.');
|
||||
|
||||
|
||||
// PDO error mode: explicitly set silent
|
||||
test(function () {
|
||||
test('PDO error mode: explicitly set silent', function () {
|
||||
buildPdoDriver(PDO::ERRMODE_SILENT);
|
||||
});
|
||||
|
||||
|
||||
// PDO error mode: implicitly set silent
|
||||
test(function () {
|
||||
buildPdoDriver(null);
|
||||
});
|
||||
|
@@ -24,7 +24,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->setType('col', Type::BOOL);
|
||||
|
||||
@@ -46,7 +57,7 @@ test(function () {
|
||||
});
|
||||
|
||||
|
||||
test(function () {
|
||||
test('', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::TEXT);
|
||||
|
||||
@@ -62,7 +73,7 @@ test(function () {
|
||||
});
|
||||
|
||||
|
||||
test(function () {
|
||||
test('', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::FLOAT);
|
||||
|
||||
@@ -139,7 +150,7 @@ test(function () {
|
||||
});
|
||||
|
||||
|
||||
test(function () {
|
||||
test('', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::INTEGER);
|
||||
|
||||
@@ -147,7 +158,14 @@ test(function () {
|
||||
Assert::same(['col' => 1], $result->test(['col' => true]));
|
||||
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' => 1], $result->test(['col' => '1']));
|
||||
Assert::same(['col' => 10], $result->test(['col' => '10']));
|
||||
@@ -165,7 +183,7 @@ test(function () {
|
||||
});
|
||||
|
||||
|
||||
test(function () {
|
||||
test('', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::DATETIME);
|
||||
|
||||
@@ -183,7 +201,7 @@ test(function () {
|
||||
});
|
||||
|
||||
|
||||
test(function () {
|
||||
test('', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::DATETIME);
|
||||
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
|
||||
@@ -202,7 +220,7 @@ test(function () {
|
||||
});
|
||||
|
||||
|
||||
test(function () {
|
||||
test('', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::DATE);
|
||||
|
||||
@@ -218,7 +236,7 @@ test(function () {
|
||||
});
|
||||
|
||||
|
||||
test(function () {
|
||||
test('', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::TIME);
|
||||
|
||||
|
@@ -23,7 +23,9 @@ Assert::equal(1, $conn->getInsertId());
|
||||
|
||||
$conn->query(
|
||||
'CREATE TRIGGER %n ON %n AFTER INSERT AS INSERT INTO %n DEFAULT VALUES',
|
||||
'UpdAAB', 'aab', 'aaa'
|
||||
'UpdAAB',
|
||||
'aab',
|
||||
'aaa'
|
||||
);
|
||||
|
||||
$conn->query('INSERT INTO %n DEFAULT VALUES', 'aab');
|
||||
|
25
tests/dibi/Translator.DateInterval.phpt
Normal file
25
tests/dibi/Translator.DateInterval.phpt
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Tester\Assert;
|
||||
|
||||
require __DIR__ . '/bootstrap.php';
|
||||
|
||||
$conn = new Dibi\Connection($config);
|
||||
$translator = new Dibi\Translator($conn);
|
||||
|
||||
switch ($config['system']) {
|
||||
case 'mysql':
|
||||
Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null));
|
||||
Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null));
|
||||
Assert::exception(function () use ($translator) {
|
||||
$translator->formatValue(new DateInterval('P2Y4DT6H8M'), null);
|
||||
}, Dibi\NotSupportedException::class, 'Only time interval is supported.');
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert::exception(function () use ($translator) {
|
||||
$translator->formatValue(new DateInterval('PT10H20M30S'), null);
|
||||
}, Dibi\Exception::class);
|
||||
}
|
@@ -1,9 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @phpversion 5.5
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Tester\Assert;
|
||||
|
@@ -19,12 +19,17 @@ Assert::same(
|
||||
SELECT *
|
||||
FROM [customers]
|
||||
/* WHERE ... LIKE ... */'),
|
||||
|
||||
$conn->translate('
|
||||
$conn->translate(
|
||||
'
|
||||
SELECT *
|
||||
FROM [customers]
|
||||
%if', isset($name), 'WHERE [name] LIKE %s', 'xxx', '%end'
|
||||
));
|
||||
%if',
|
||||
isset($name),
|
||||
'WHERE [name] LIKE %s',
|
||||
'xxx',
|
||||
'%end'
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// if & else & end (last end is optional)
|
||||
@@ -32,11 +37,14 @@ Assert::same(
|
||||
reformat('
|
||||
SELECT *
|
||||
FROM [customers] /* ... */'),
|
||||
|
||||
$conn->translate('
|
||||
$conn->translate(
|
||||
'
|
||||
SELECT *
|
||||
FROM %if', true, '[customers] %else [products]'
|
||||
));
|
||||
FROM %if',
|
||||
true,
|
||||
'[customers] %else [products]'
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// if & else & (optional) end
|
||||
@@ -48,43 +56,63 @@ WHERE [id] > 0
|
||||
/* AND ...=...
|
||||
*/ AND [bar]=1
|
||||
'),
|
||||
|
||||
$conn->translate('
|
||||
SELECT *
|
||||
FROM [people]
|
||||
WHERE [id] > 0
|
||||
%if', false, 'AND [foo]=%i', 1, '
|
||||
%else %if', true, 'AND [bar]=%i', 1, '
|
||||
'));
|
||||
')
|
||||
);
|
||||
|
||||
|
||||
// nested condition
|
||||
Assert::match(
|
||||
reformat("
|
||||
reformat([
|
||||
'sqlsrv' => "
|
||||
SELECT *
|
||||
FROM [customers]
|
||||
WHERE
|
||||
[name] LIKE N'xxx'
|
||||
/* AND ...=1 */
|
||||
/* 1 LIMIT 10 */",
|
||||
"
|
||||
SELECT *
|
||||
FROM [customers]
|
||||
WHERE
|
||||
[name] LIKE 'xxx'
|
||||
/* AND ...=1 */
|
||||
/* 1 LIMIT 10 */"),
|
||||
|
||||
$conn->translate('
|
||||
/* 1 LIMIT 10 */",
|
||||
]),
|
||||
$conn->translate(
|
||||
'
|
||||
SELECT *
|
||||
FROM [customers]
|
||||
WHERE
|
||||
%if', true, '[name] LIKE %s', 'xxx', '
|
||||
%if', false, 'AND [admin]=1 %end
|
||||
%if',
|
||||
true,
|
||||
'[name] LIKE %s',
|
||||
'xxx',
|
||||
'
|
||||
%if',
|
||||
false,
|
||||
'AND [admin]=1 %end
|
||||
%else 1 LIMIT 10 %end'
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// limit & offset
|
||||
Assert::same(
|
||||
'SELECT * FROM foo /* (limit 3) (offset 5) */',
|
||||
$conn->translate(
|
||||
'SELECT * FROM foo',
|
||||
'%if', false,
|
||||
'%lmt', 3,
|
||||
'%ofs', 5,
|
||||
'%end'
|
||||
));
|
||||
'SELECT * FROM foo',
|
||||
'%if',
|
||||
false,
|
||||
'%lmt',
|
||||
3,
|
||||
'%ofs',
|
||||
5,
|
||||
'%end'
|
||||
)
|
||||
);
|
||||
|
@@ -71,3 +71,9 @@ Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'baa', 'aa'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'aab', 'aa'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like~', 'bba', '%a'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'b%a', '%a'));
|
||||
|
||||
|
||||
// matches
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like', 'a', 'a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like', 'a', 'aa'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like', 'a', 'b'));
|
||||
|
@@ -16,7 +16,10 @@ $conn = new Dibi\Connection($config + ['formatDateTime' => "'Y-m-d H:i:s.u'", 'f
|
||||
|
||||
// Dibi detects INSERT or REPLACE command & booleans
|
||||
Assert::same(
|
||||
reformat("REPLACE INTO [products] ([title], [price]) VALUES ('Drticka', 318)"),
|
||||
reformat([
|
||||
'sqlsrv' => "REPLACE INTO [products] ([title], [price]) VALUES (N'Drticka', 318)",
|
||||
"REPLACE INTO [products] ([title], [price]) VALUES ('Drticka', 318)",
|
||||
]),
|
||||
$conn->translate('REPLACE INTO [products]', [
|
||||
'title' => 'Drticka',
|
||||
'price' => 318,
|
||||
@@ -31,7 +34,10 @@ $array = [
|
||||
'brand' => null,
|
||||
];
|
||||
Assert::same(
|
||||
reformat('INSERT INTO [products] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'),
|
||||
reformat([
|
||||
'sqlsrv' => "INSERT INTO [products] ([title], [price], [brand]) VALUES (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL)",
|
||||
"INSERT INTO [products] ([title], [price], [brand]) VALUES ('Super Product', 12, NULL) , ('Super Product', 12, NULL) , ('Super Product', 12, NULL)",
|
||||
]),
|
||||
$conn->translate('INSERT INTO [products]', $array, $array, $array)
|
||||
);
|
||||
|
||||
@@ -43,14 +49,20 @@ $array = [
|
||||
['pole' => 'hodnota3', 'bit' => 1],
|
||||
];
|
||||
Assert::same(
|
||||
reformat('INSERT INTO [products] ([pole], [bit]) VALUES (\'hodnota1\', 1) , (\'hodnota2\', 1) , (\'hodnota3\', 1)'),
|
||||
reformat([
|
||||
'sqlsrv' => "INSERT INTO [products] ([pole], [bit]) VALUES (N'hodnota1', 1) , (N'hodnota2', 1) , (N'hodnota3', 1)",
|
||||
"INSERT INTO [products] ([pole], [bit]) VALUES ('hodnota1', 1) , ('hodnota2', 1) , ('hodnota3', 1)",
|
||||
]),
|
||||
$conn->translate('INSERT INTO [products] %ex', $array)
|
||||
);
|
||||
|
||||
|
||||
// Dibi detects UPDATE command
|
||||
Assert::same(
|
||||
reformat("UPDATE [colors] SET [color]='blue', [order]=12 WHERE [id]=123"),
|
||||
reformat([
|
||||
'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [order]=12 WHERE [id]=123",
|
||||
"UPDATE [colors] SET [color]='blue', [order]=12 WHERE [id]=123",
|
||||
]),
|
||||
$conn->translate('UPDATE [colors] SET', [
|
||||
'color' => 'blue',
|
||||
'order' => 12,
|
||||
@@ -85,17 +97,26 @@ $e = Assert::exception(function () use ($conn) {
|
||||
Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql());
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT * FROM [table] WHERE id=10 AND name=\'ahoj\''),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT * FROM [table] WHERE id=10 AND name=N'ahoj'",
|
||||
"SELECT * FROM [table] WHERE id=10 AND name='ahoj'",
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [table] WHERE id=%i AND name=%s', 10, 'ahoj')
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('TEST ([cond] > 2) OR ([cond2] = \'3\') OR (cond3 < RAND())'),
|
||||
reformat([
|
||||
'sqlsrv' => "TEST ([cond] > 2) OR ([cond2] = N'3') OR (cond3 < RAND())",
|
||||
"TEST ([cond] > 2) OR ([cond2] = '3') OR (cond3 < RAND())",
|
||||
]),
|
||||
$conn->translate('TEST %or', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'])
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('TEST ([cond] > 2) AND ([cond2] = \'3\') AND (cond3 < RAND())'),
|
||||
reformat([
|
||||
'sqlsrv' => "TEST ([cond] > 2) AND ([cond2] = N'3') AND (cond3 < RAND())",
|
||||
"TEST ([cond] > 2) AND ([cond2] = '3') AND (cond3 < RAND())",
|
||||
]),
|
||||
$conn->translate('TEST %and', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'])
|
||||
);
|
||||
|
||||
@@ -114,7 +135,10 @@ $where['age'] = null;
|
||||
$where['email'] = 'ahoj';
|
||||
$where['id%l'] = [10, 20, 30];
|
||||
Assert::same(
|
||||
reformat('SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = \'ahoj\') AND ([id] IN (10, 20, 30))'),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = N'ahoj') AND ([id] IN (10, 20, 30))",
|
||||
"SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = 'ahoj') AND ([id] IN (10, 20, 30))",
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [table] WHERE %and', $where)
|
||||
);
|
||||
|
||||
@@ -144,9 +168,9 @@ Assert::same(
|
||||
// with limit = 2
|
||||
Assert::same(
|
||||
reformat([
|
||||
'odbc' => 'SELECT TOP 2 * FROM (SELECT * FROM [products] ) t',
|
||||
'odbc' => 'SELECT TOP 2 * FROM (SELECT * FROM [products]) t',
|
||||
'sqlsrv' => 'SELECT * FROM [products] OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY',
|
||||
'SELECT * FROM [products] LIMIT 2',
|
||||
'SELECT * FROM [products] LIMIT 2',
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [products] %lmt', 2)
|
||||
);
|
||||
@@ -160,7 +184,7 @@ if ($config['system'] === 'odbc') {
|
||||
Assert::same(
|
||||
reformat([
|
||||
'sqlsrv' => 'SELECT * FROM [products] OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY',
|
||||
'SELECT * FROM [products] LIMIT 2 OFFSET 1',
|
||||
'SELECT * FROM [products] LIMIT 2 OFFSET 1',
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1)
|
||||
);
|
||||
@@ -168,10 +192,10 @@ if ($config['system'] === 'odbc') {
|
||||
// with offset = 50
|
||||
Assert::same(
|
||||
reformat([
|
||||
'mysql' => 'SELECT * FROM `products` LIMIT 18446744073709551615 OFFSET 50',
|
||||
'postgre' => 'SELECT * FROM "products" OFFSET 50',
|
||||
'mysql' => 'SELECT * FROM `products` LIMIT 18446744073709551615 OFFSET 50',
|
||||
'postgre' => 'SELECT * FROM "products" OFFSET 50',
|
||||
'sqlsrv' => 'SELECT * FROM [products] OFFSET 50 ROWS',
|
||||
'SELECT * FROM [products] LIMIT -1 OFFSET 50',
|
||||
'SELECT * FROM [products] LIMIT -1 OFFSET 50',
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [products] %ofs', 50)
|
||||
);
|
||||
@@ -237,7 +261,7 @@ if ($config['system'] === 'postgre') {
|
||||
'sqlite' => "SELECT * FROM products WHERE (title LIKE 'C%' ESCAPE '\\' AND title LIKE '%r' ESCAPE '\\') OR title LIKE '%a\n\\%\\_\\\\''\"%' ESCAPE '\\'",
|
||||
'odbc' => "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n[%][_]\\''\"%'",
|
||||
'sqlsrv' => "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n[%][_]\\''\"%'",
|
||||
"SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\\n\\%\\_\\\\\\\\\'\"%'",
|
||||
"SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\\n\\%\\_\\\\\\\\\\'\"%'",
|
||||
]),
|
||||
$conn->translate($args[0], $args[1], $args[2], $args[3])
|
||||
);
|
||||
@@ -255,11 +279,17 @@ Assert::match(
|
||||
CONCAT(last_name, ', ', first_name) AS full_name
|
||||
GROUP BY `user`
|
||||
HAVING MAX(salary) > %i 123
|
||||
INTO OUTFILE '/tmp/result\'.txt'
|
||||
INTO OUTFILE '/tmp/result\\'.txt'
|
||||
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\\\"'
|
||||
LINES TERMINATED BY '\\\\n'
|
||||
",
|
||||
"SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
|
||||
'sqlsrv' => "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
|
||||
CONCAT(last_name, N', ', first_name) AS full_name
|
||||
GROUP BY [user]
|
||||
HAVING MAX(salary) > %i 123
|
||||
INTO OUTFILE N'/tmp/result''.txt'
|
||||
FIELDS TERMINATED BY N',' OPTIONALLY ENCLOSED BY N'\"'
|
||||
LINES TERMINATED BY N'\\n'", "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
|
||||
CONCAT(last_name, ', ', first_name) AS full_name
|
||||
GROUP BY [user]
|
||||
HAVING MAX(salary) > %i 123
|
||||
@@ -314,13 +344,34 @@ WHERE (`test`.`a` LIKE '1995-03-01'
|
||||
OR `b8` IN (RAND() `col1` > `col2` )
|
||||
OR `b9` IN (RAND(), [col1] > [col2] )
|
||||
OR `b10` IN ( )
|
||||
AND `c` = 'embedded \' string'
|
||||
AND `c` = 'embedded \\' string'
|
||||
OR `d`=10
|
||||
OR `e`=NULL
|
||||
OR `true`= 1
|
||||
OR `false`= 0
|
||||
OR `str_null`=NULL
|
||||
OR `str_not_null`='hello'
|
||||
LIMIT 10",
|
||||
'sqlsrv' => "SELECT *
|
||||
FROM [db].[table]
|
||||
WHERE ([test].[a] LIKE '1995-03-01'
|
||||
OR [b1] IN ( 1, 2, 3 )
|
||||
OR [b2] IN (N'1', N'2', N'3' )
|
||||
OR [b3] IN ( )
|
||||
OR [b4] IN ( N'one', N'two', N'three' )
|
||||
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
|
||||
OR [b6] IN (N'one', N'two', N'thr.ee')
|
||||
OR [b7] IN (NULL)
|
||||
OR [b8] IN (RAND() [col1] > [col2] )
|
||||
OR [b9] IN (RAND(), [col1] > [col2] )
|
||||
OR [b10] IN ( )
|
||||
AND [c] = N'embedded '' string'
|
||||
OR [d]=10
|
||||
OR [e]=NULL
|
||||
OR [true]= 1
|
||||
OR [false]= 0
|
||||
OR [str_null]=NULL
|
||||
OR [str_not_null]=N'hello'
|
||||
LIMIT 10",
|
||||
'postgre' => 'SELECT *
|
||||
FROM "db"."table"
|
||||
@@ -386,7 +437,6 @@ WHERE ([test].[a] LIKE '1995-03-01'
|
||||
OR [str_not_null]='hello'
|
||||
LIMIT 10",
|
||||
]),
|
||||
|
||||
$conn->translate('SELECT *
|
||||
FROM [db.table]
|
||||
WHERE ([test.a] LIKE %d', '1995-03-01', '
|
||||
@@ -412,7 +462,10 @@ LIMIT 10')
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat('TEST [cond] > 2 [cond2] = \'3\' cond3 < RAND() 123'),
|
||||
reformat([
|
||||
'sqlsrv' => "TEST [cond] > 2 [cond2] = N'3' cond3 < RAND() 123",
|
||||
"TEST [cond] > 2 [cond2] = '3' cond3 < RAND() 123",
|
||||
]),
|
||||
$conn->translate('TEST %ex', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'], 123)
|
||||
);
|
||||
|
||||
@@ -430,16 +483,19 @@ Assert::same(
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat('TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE \'string\')'),
|
||||
reformat([
|
||||
'sqlsrv' => "TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE N'string')",
|
||||
"TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE 'string')",
|
||||
]),
|
||||
$conn->translate('TEST %or', ['cond1%ex' => 3, 'cond2%ex' => 'RAND()', 'cond3%ex' => ['LIKE %s', 'string']])
|
||||
);
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat([
|
||||
'odbc' => 'SELECT TOP 10 * FROM (SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' ) t',
|
||||
'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
|
||||
'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10',
|
||||
'odbc' => 'SELECT TOP 10 * FROM (SELECT * FROM [test] WHERE [id] LIKE \'%d%t\') t',
|
||||
'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE N\'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
|
||||
'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10',
|
||||
]),
|
||||
$conn->translate("SELECT * FROM [test] WHERE %n LIKE '%d%t' %lmt", 'id', 10)
|
||||
);
|
||||
@@ -455,23 +511,32 @@ Assert::same(
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT FROM ... '),
|
||||
reformat('SELECT FROM ...'),
|
||||
$conn->translate('SELECT FROM ... %lmt', null)
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT \'%i\''),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT N'%i'",
|
||||
"SELECT '%i'",
|
||||
]),
|
||||
$conn->translate("SELECT '%i'")
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT \'%i\''),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT N'%i'",
|
||||
"SELECT '%i'",
|
||||
]),
|
||||
$conn->translate('SELECT "%i"')
|
||||
);
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat('INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1(\'Test product\')) , (1, SHA1(\'Test product\'))'),
|
||||
reformat([
|
||||
'sqlsrv' => "INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1(N'Test product')) , (1, SHA1(N'Test product'))",
|
||||
"INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1('Test product')) , (1, SHA1('Test product'))",
|
||||
]),
|
||||
$conn->translate('INSERT INTO [products]', [
|
||||
'product_id' => 1,
|
||||
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
|
||||
@@ -482,7 +547,10 @@ Assert::same(
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('UPDATE [products] [product_id]=1, [title]=SHA1(\'Test product\')'),
|
||||
reformat([
|
||||
'sqlsrv' => "UPDATE [products] [product_id]=1, [title]=SHA1(N'Test product')",
|
||||
"UPDATE [products] [product_id]=1, [title]=SHA1('Test product')",
|
||||
]),
|
||||
$conn->translate('UPDATE [products]', [
|
||||
'product_id' => 1,
|
||||
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
|
||||
@@ -490,7 +558,10 @@ Assert::same(
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('UPDATE [products] [product_id]=1, [title]=SHA1(\'Test product\')'),
|
||||
reformat([
|
||||
'sqlsrv' => "UPDATE [products] [product_id]=1, [title]=SHA1(N'Test product')",
|
||||
"UPDATE [products] [product_id]=1, [title]=SHA1('Test product')",
|
||||
]),
|
||||
$conn->translate('UPDATE [products]', [
|
||||
'product_id' => 1,
|
||||
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
|
||||
@@ -498,7 +569,10 @@ Assert::same(
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1(\'Test product\')'),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1(N'Test product')",
|
||||
"SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1('Test product')",
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [products] WHERE', [
|
||||
'product_id' => 1,
|
||||
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
|
||||
@@ -535,7 +609,10 @@ $array6 = [
|
||||
];
|
||||
|
||||
Assert::same(
|
||||
reformat('INSERT INTO test ([id], [text], [num]) VALUES (1, \'ahoj\', 1), (2, \'jak\', -1), (3, \'se\', 10), (4, SUM(5), 1)'),
|
||||
reformat([
|
||||
'sqlsrv' => "INSERT INTO test ([id], [text], [num]) VALUES (1, N'ahoj', 1), (2, N'jak', -1), (3, N'se', 10), (4, SUM(5), 1)",
|
||||
"INSERT INTO test ([id], [text], [num]) VALUES (1, 'ahoj', 1), (2, 'jak', -1), (3, 'se', 10), (4, SUM(5), 1)",
|
||||
]),
|
||||
$conn->translate('INSERT INTO test %m', $array6)
|
||||
);
|
||||
|
||||
@@ -589,8 +666,10 @@ Assert::same(
|
||||
setlocale(LC_ALL, 'czech');
|
||||
|
||||
Assert::same(
|
||||
reformat("UPDATE [colors] SET [color]='blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5"),
|
||||
|
||||
reformat([
|
||||
'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5",
|
||||
"UPDATE [colors] SET [color]='blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5",
|
||||
]),
|
||||
$conn->translate('UPDATE [colors] SET', [
|
||||
'color' => 'blue',
|
||||
'price' => -12.4,
|
||||
|
@@ -37,7 +37,7 @@ if ($config['system'] === 'odbc') {
|
||||
}
|
||||
|
||||
|
||||
function test(Closure $function)
|
||||
function test(string $title, Closure $function): void
|
||||
{
|
||||
$function();
|
||||
}
|
||||
|
Reference in New Issue
Block a user