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

Compare commits

...

24 Commits
v4.1.3 ... v4.1

Author SHA1 Message Date
David Grudl
49f2bf867f SqlsrvDriver: workaround for "Driver's SQLSetConnectAttr failed on ODBC <=13" bug 2021-03-10 16:42:45 +01:00
David Grudl
0d2f643795 strict comparison 2020-11-02 15:27:54 +01:00
David Grudl
70d4246866 Tracy\Panel: table is sortable 2020-11-02 15:05:55 +01:00
David Grudl
34a1665915 coding style 2020-11-02 15:05:55 +01:00
David Grudl
b27db4a9aa updated gitattributes 2020-11-02 15:05:55 +01:00
David Grudl
212dd1ae55 updated phpstan 2020-11-02 15:05:55 +01:00
David Grudl
b9683f8a3c updated nette/coding-standard 2020-11-02 15:05:55 +01:00
David Grudl
177a800bff PdoDriver: changes error mode do ERRMODE_SILENT (for PHP 8.0) 2020-11-02 15:05:55 +01:00
David Grudl
fa6a1203a9 fixed compatibility with PHP 8 [Closes #379] 2020-11-02 15:05:55 +01:00
David Grudl
3f7171c7a4 tested on PHP 8.0 2020-10-30 13:50:54 +01:00
David Grudl
e4b6e769ee Dibi\Helpers::getSuggestion(): item may be an int, type cast fix (#378) 2020-10-14 12:00:23 +02:00
David Grudl
24d0b069d8 Helpers::getSuggestion(): support for reflection objects moved to Strict 2020-10-14 12:00:04 +02:00
David Grudl
34bc742245 refactoring 2020-10-08 16:49:47 +02:00
Miloslav Hůla
f5fa2255ff FileLogger: fixed object to string conversion with custom driver (#376) (#376)
Co-authored-by: Miloslav Hůla <miloslav.hula@fsv.cvut.cz>
2020-10-08 16:38:35 +02:00
Miloslav Hůla
7b02296f3e Tracy\Panel: fixed object to string converion error with custom driver (#373)
Co-authored-by: Miloslav Hůla <miloslav.hula@fsv.cvut.cz>
2020-10-08 16:38:35 +02:00
David Grudl
df4cddac1f Tracy\Panel: supports multiple connections [Closes #365]
Partially reverts "Tracy\Panel: one panel is used per connection"

This reverts commit ae6c8756b6.
2020-10-08 16:38:35 +02:00
groupnet
cc37121390 Postgre driver - add connect_type config parameter (#370)
- adds the option to pass the connect_type as config parameter
 - PHP default is 0, but to make this change non-breaking defauls to PGSQL_CONNECT_FORCE_NEW
 - see PHP docs: https://www.php.net/manual/en/function.pg-connect.php
2020-10-08 15:55:11 +02:00
LHavlicek
a95b409231 MySqliDriver: fixed DateInterval encoding (#371) 2020-10-08 15:55:11 +02:00
Jakub Bouček
3b057c2e35 MySqliDriver: added support for ping (#372) 2020-10-08 15:55:11 +02:00
David Grudl
f444b5d993 typo 2020-10-08 15:55:11 +02:00
David Grudl
6e41c4223b tests: test() with description 2020-10-08 15:55:11 +02:00
David Grudl
0ee4628712 SqlsrvDriver: improved escapeBinary [Closes #287] 2020-10-07 03:19:03 +02:00
David Grudl
ab3677203c added funding.yml 2020-10-07 03:19:03 +02:00
Jan Barášek
1bdf6e93d0 PhpStan fixes (#363) 2020-05-07 21:41:28 +02:00
43 changed files with 419 additions and 262 deletions

6
.gitattributes vendored
View File

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

1
.github/funding.yml vendored Normal file
View File

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

View File

@@ -4,6 +4,7 @@ php:
- 7.2 - 7.2
- 7.3 - 7.3
- 7.4 - 7.4
- 8.0snapshot
services: services:
- mysql - mysql
@@ -32,7 +33,6 @@ after_failure:
jobs: jobs:
include: include:
- name: Nette Code Checker - name: Nette Code Checker
php: 7.4
install: install:
- travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress
script: script:
@@ -42,9 +42,9 @@ jobs:
- name: Nette Coding Standard - name: Nette Coding Standard
php: 7.4 php: 7.4
install: 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: 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) - stage: Static Analysis (informative)
@@ -67,7 +67,7 @@ jobs:
- stage: Code Coverage - stage: Code Coverage
sudo: false dist: xenial
cache: cache:
directories: directories:

View File

@@ -16,6 +16,7 @@
"require-dev": { "require-dev": {
"tracy/tracy": "~2.2", "tracy/tracy": "~2.2",
"nette/tester": "~2.0", "nette/tester": "~2.0",
"nette/di": "^3.0",
"phpstan/phpstan": "^0.12" "phpstan/phpstan": "^0.12"
}, },
"replace": { "replace": {
@@ -25,7 +26,7 @@
"classmap": ["src/"] "classmap": ["src/"]
}, },
"scripts": { "scripts": {
"phpstan": "phpstan analyse --autoload-file vendor/autoload.php --level 5 --configuration tests/phpstan.neon src", "phpstan": "phpstan analyse",
"tester": "tester tests -s" "tester": "tester tests -s"
}, },
"extra": { "extra": {

21
ecs.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
/**
* Rules for Nette Coding Standard
* https://github.com/nette/coding-standard
*/
declare(strict_types=1);
return function (Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->import(PRESET_DIR . '/php71.php');
$parameters = $containerConfigurator->parameters();
$parameters->set('skip', [
// issue #260
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer::class => ['src/Dibi/HashMap.php'],
SlevomatCodingStandard\Sniffs\ControlStructures\RequireNullCoalesceOperatorSniff::class => ['src/Dibi/HashMap.php'],
]);
};

View File

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

View File

@@ -14,7 +14,13 @@ Introduction
Database access functions in PHP are not standardised. This library Database access functions in PHP are not standardised. This library
hides the differences between them, and above all, it gives you a very handy interface. hides the differences between them, and above all, it gives you a very handy interface.
If you like Dibi, **[please make a donation now](https://nette.org/make-donation?to=dibi)**. Thank you!
Support Project
---------------
Do you like Dibi? Are you looking forward to the new features?
[![Donate](https://files.nette.org/icons/donation-1.svg?)](https://nette.org/make-donation?to=dibi)
Installation Installation
@@ -26,7 +32,7 @@ Install Dibi via Composer:
composer require dibi/dibi composer require dibi/dibi
``` ```
The Dibi 4.1 requires PHP version 7.1 and supports PHP up to 7.4. The Dibi 4.1 requires PHP version 7.1 and supports PHP up to 8.0.
Usage Usage
@@ -42,11 +48,11 @@ The database connection is represented by the object `Dibi\Connection`:
```php ```php
$database = new Dibi\Connection([ $database = new Dibi\Connection([
'driver' => 'mysqli', 'driver' => 'mysqli',
'host' => 'localhost', 'host' => 'localhost',
'username' => 'root', 'username' => 'root',
'password' => '***', 'password' => '***',
'database' => 'table', 'database' => 'table',
]); ]);
$result = $database->query('SELECT * FROM users'); $result = $database->query('SELECT * FROM users');
@@ -56,12 +62,12 @@ Alternatively, you can use the `dibi` static register, which maintains a connect
```php ```php
dibi::connect([ dibi::connect([
'driver' => 'mysqli', 'driver' => 'mysqli',
'host' => 'localhost', 'host' => 'localhost',
'username' => 'root', 'username' => 'root',
'password' => '***', 'password' => '***',
'database' => 'test', 'database' => 'test',
'charset' => 'utf8', 'charset' => 'utf8',
]); ]);
$result = dibi::query('SELECT * FROM users'); $result = dibi::query('SELECT * FROM users');
@@ -229,8 +235,8 @@ Example:
```php ```php
$arr = [ $arr = [
'a' => 'hello', 'a' => 'hello',
'b' => true, 'b' => true,
]; ];
$database->query('INSERT INTO table %v', $arr); $database->query('INSERT INTO table %v', $arr);
@@ -502,7 +508,7 @@ $all = $result->fetchAssoc('customer_id|order_id');
// we will iterate like this: // we will iterate like this:
foreach ($all as $customerId => $orders) { foreach ($all as $customerId => $orders) {
foreach ($orders as $orderId => $order) { foreach ($orders as $orderId => $order) {
... ...
} }
} }
``` ```
@@ -534,7 +540,7 @@ $all = $result->fetchAssoc('name[]order_id');
// we get all the Arnolds in the results // we get all the Arnolds in the results
foreach ($all['Arnold Rimmer'] as $arnoldOrders) { foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
foreach ($arnoldOrders as $orderId => $order) { foreach ($arnoldOrders as $orderId => $order) {
... ...
} }
} }
``` ```
@@ -548,8 +554,8 @@ foreach ($all as $customerId => $orders) {
echo "Customer $customerId": echo "Customer $customerId":
foreach ($orders as $orderId => $order) { foreach ($orders as $orderId => $order) {
echo "ID number: $order->number"; echo "ID number: $order->number";
// customer name is in $order->name // customer name is in $order->name
} }
} }
``` ```
@@ -571,7 +577,7 @@ foreach ($all as $customerId => $row) {
echo "Customer $row->name": echo "Customer $row->name":
foreach ($row->order_id as $orderId => $order) { foreach ($row->order_id as $orderId => $order) {
echo "ID number: $order->number"; echo "ID number: $order->number";
} }
} }
``` ```

View File

@@ -45,7 +45,7 @@ class Panel implements Tracy\IBarPanel
public function register(Dibi\Connection $connection): void public function register(Dibi\Connection $connection): void
{ {
Tracy\Debugger::getBar()->addPanel($this); Tracy\Debugger::getBar()->addPanel($this);
Tracy\Debugger::getBlueScreen()->addPanel([__CLASS__, 'renderException']); Tracy\Debugger::getBlueScreen()->addPanel([self::class, 'renderException']);
$connection->onEvent[] = [$this, 'logEvent']; $connection->onEvent[] = [$this, 'logEvent'];
} }
@@ -105,6 +105,15 @@ class Panel implements Tracy\IBarPanel
} }
$totalTime = $s = null; $totalTime = $s = null;
$singleConnection = reset($this->events)->connection;
foreach ($this->events as $event) {
if ($event->connection !== $singleConnection) {
$singleConnection = null;
break;
}
}
foreach ($this->events as $event) { foreach ($this->events as $event) {
$totalTime += $event->time; $totalTime += $event->time;
$connection = $event->connection; $connection = $event->connection;
@@ -112,7 +121,9 @@ class Panel implements Tracy\IBarPanel
if ($this->explain && $event->type === Event::SELECT) { if ($this->explain && $event->type === Event::SELECT) {
$backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime]; $backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime];
$connection->onEvent = null; $connection->onEvent = null;
$cmd = is_string($this->explain) ? $this->explain : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN'); $cmd = is_string($this->explain)
? $this->explain
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
try { try {
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true); $explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
} catch (Dibi\Exception $e) { } catch (Dibi\Exception $e) {
@@ -120,7 +131,7 @@ class Panel implements Tracy\IBarPanel
[$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup; [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup;
} }
$s .= '<tr><td>' . number_format($event->time * 1000, 3, '.', ''); $s .= '<tr><td data-order="' . $event->time . '">' . number_format($event->time * 1000, 3, '.', '');
if ($explain) { if ($explain) {
static $counter; static $counter;
$counter++; $counter++;
@@ -132,10 +143,13 @@ class Panel implements Tracy\IBarPanel
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>"; $s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
} }
if ($event->source) { if ($event->source) {
$s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]);//->class('tracy-DibiProfiler-source'); $s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]); //->class('tracy-DibiProfiler-source');
} }
$s .= "</td><td>{$event->count}</td></tr>"; $s .= "</td><td>{$event->count}</td>";
if (!$singleConnection) {
$s .= '<td>' . htmlspecialchars($this->getConnectionName($connection)) . '</td></tr>';
}
} }
return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important } return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important }
@@ -143,12 +157,21 @@ class Panel implements Tracy\IBarPanel
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style> #tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
<h1>Queries: ' . count($this->events) <h1>Queries: ' . count($this->events)
. ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', '') . 'ms') . ', ' . ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', '') . 'ms') . ', '
. htmlspecialchars($connection->getConfig('driver') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '') . htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
. ($connection->getConfig('host') ? '@' . $connection->getConfig('host') : '')) . '</h1>
<div class="tracy-inner tracy-DibiProfiler"> <div class="tracy-inner tracy-DibiProfiler">
<table> <table class="tracy-sortable">
<tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th></tr>' . $s . ' <tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>
' . $s . '
</table> </table>
</div>'; </div>';
} }
private function getConnectionName(Dibi\Connection $connection): string
{
$driver = $connection->getConfig('driver');
return (is_object($driver) ? get_class($driver) : $driver)
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? '@' . $connection->getConfig('host') : '');
}
} }

View File

@@ -562,7 +562,7 @@ class Connection implements IConnection
*/ */
public function __wakeup() public function __wakeup()
{ {
throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.'); throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
} }
@@ -571,7 +571,7 @@ class Connection implements IConnection
*/ */
public function __sleep() public function __sleep()
{ {
throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.'); throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -130,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. * Executes the SQL query.
* @throws Dibi\DriverException * @throws Dibi\DriverException
@@ -148,6 +157,9 @@ class MySqliDriver implements Dibi\Driver
} }
/**
* @param int|string $code
*/
public static function createException(string $message, $code, string $sql): Dibi\DriverException public static function createException(string $message, $code, string $sql): Dibi\DriverException
{ {
if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) { if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
@@ -188,7 +200,9 @@ class MySqliDriver implements Dibi\Driver
*/ */
public function getAffectedRows(): ?int public function getAffectedRows(): ?int
{ {
return $this->connection->affected_rows === -1 ? null : $this->connection->affected_rows; return $this->connection->affected_rows === -1
? null
: $this->connection->affected_rows;
} }
@@ -305,7 +319,7 @@ class MySqliDriver implements Dibi\Driver
if ($value->y || $value->m || $value->d) { if ($value->y || $value->m || $value->d) {
throw new Dibi\NotSupportedException('Only time interval is supported.'); throw new Dibi\NotSupportedException('Only time interval is supported.');
} }
return $value->format('%r%H:%I:%S.%f'); return $value->format("'%r%H:%I:%S.%f'");
} }

View File

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

View File

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

View File

@@ -56,9 +56,15 @@ class PdoDriver implements Dibi\Driver
if ($config['resource'] instanceof PDO) { if ($config['resource'] instanceof PDO) {
$this->connection = $config['resource']; $this->connection = $config['resource'];
unset($config['resource'], $config['pdo']); unset($config['resource'], $config['pdo']);
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
}
} else { } else {
try { try {
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']); $this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
} catch (\PDOException $e) { } catch (\PDOException $e) {
if ($e->getMessage() === 'could not find driver') { if ($e->getMessage() === 'could not find driver') {
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.'); throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
@@ -67,10 +73,6 @@ class PdoDriver implements Dibi\Driver
} }
} }
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
}
$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME); $this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
$this->serverVersion = (string) ($config['version'] ?? @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)); // @ - may be not supported $this->serverVersion = (string) ($config['version'] ?? @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)); // @ - may be not supported
} }
@@ -232,21 +234,17 @@ class PdoDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
if ($this->driverName === 'odbc') { return $this->driverName === 'odbc'
return "'" . str_replace("'", "''", $value) . "'"; ? "'" . str_replace("'", "''", $value) . "'"
} else { : $this->connection->quote($value, PDO::PARAM_STR);
return $this->connection->quote($value, PDO::PARAM_STR);
}
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
if ($this->driverName === 'odbc') { return $this->driverName === 'odbc'
return "'" . str_replace("'", "''", $value) . "'"; ? "'" . str_replace("'", "''", $value) . "'"
} else { : $this->connection->quote($value, PDO::PARAM_LOB);
return $this->connection->quote($value, PDO::PARAM_LOB);
}
} }

View File

@@ -85,7 +85,7 @@ class PdoResult implements Dibi\ResultDriver
if ($row === false) { if ($row === false) {
throw new Dibi\NotSupportedException('Driver does not support meta data.'); throw new Dibi\NotSupportedException('Driver does not support meta data.');
} }
$row = $row + [ $row += [
'table' => null, 'table' => null,
'native_type' => 'VAR_STRING', 'native_type' => 'VAR_STRING',
]; ];

View File

@@ -23,6 +23,7 @@ use Dibi\Helpers;
* - charset => character encoding to set (default is utf8) * - charset => character encoding to set (default is utf8)
* - persistent (bool) => try to find a persistent link? * - persistent (bool) => try to find a persistent link?
* - resource (resource) => existing connection resource * - resource (resource) => existing connection resource
* - connect_type (int) => see pg_connect()
*/ */
class PostgreDriver implements Dibi\Driver class PostgreDriver implements Dibi\Driver
{ {
@@ -62,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) { set_error_handler(function (int $severity, string $message) use (&$error) {
$error = $message; $error = $message;
}); });
if (empty($config['persistent'])) { $this->connection = empty($config['persistent'])
$this->connection = pg_connect($string, PGSQL_CONNECT_FORCE_NEW); ? pg_connect($string, $connectType)
} else { : pg_pconnect($string, $connectType);
$this->connection = pg_pconnect($string);
}
restore_error_handler(); restore_error_handler();
} }
@@ -169,12 +169,9 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function getInsertId(?string $sequence): ?int public function getInsertId(?string $sequence): ?int
{ {
if ($sequence === null) { $res = $sequence === null
// PostgreSQL 8.1 is needed ? $this->query('SELECT LASTVAL()') // PostgreSQL 8.1 is needed
$res = $this->query('SELECT LASTVAL()'); : $this->query("SELECT CURRVAL('$sequence')");
} else {
$res = $this->query("SELECT CURRVAL('$sequence')");
}
if (!$res) { if (!$res) {
return null; return null;

View File

@@ -251,7 +251,10 @@ class PostgreReflector implements Dibi\Reflector
$references[$row['name']] = array_combine($l, $f); $references[$row['name']] = array_combine($l, $f);
} }
if (isset($references[$row['name']][$row['lnum']]) && $references[$row['name']][$row['lnum']] === $row['fnum']) { if (
isset($references[$row['name']][$row['lnum']])
&& $references[$row['name']][$row['lnum']] === $row['fnum']
) {
$fKeys[$row['name']]['local'][] = $row['local']; $fKeys[$row['name']]['local'][] = $row['local'];
$fKeys[$row['name']]['foreign'][] = $row['foreign']; $fKeys[$row['name']]['foreign'][] = $row['foreign'];
} }

View File

@@ -97,7 +97,9 @@ class PostgreResult implements Dibi\ResultDriver
'table' => pg_field_table($this->resultSet, $i), 'table' => pg_field_table($this->resultSet, $i),
'nativetype' => pg_field_type($this->resultSet, $i), 'nativetype' => pg_field_type($this->resultSet, $i),
]; ];
$row['fullname'] = $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name']; $row['fullname'] = $row['table']
? $row['table'] . '.' . $row['name']
: $row['name'];
$columns[] = $row; $columns[] = $row;
} }
return $columns; return $columns;

View File

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

View File

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

View File

@@ -63,11 +63,13 @@ class SqlsrvDriver implements Dibi\Driver
$options['UID'] = (string) $options['UID']; $options['UID'] = (string) $options['UID'];
$options['Database'] = (string) $options['Database']; $options['Database'] = (string) $options['Database'];
sqlsrv_configure('WarningsReturnAsErrors', 0);
$this->connection = sqlsrv_connect($config['host'], $options); $this->connection = sqlsrv_connect($config['host'], $options);
sqlsrv_configure('WarningsReturnAsErrors', 1);
} }
if (!is_resource($this->connection)) { if (!is_resource($this->connection)) {
$info = sqlsrv_errors(); $info = sqlsrv_errors(SQLSRV_ERR_ERRORS);
throw new Dibi\DriverException($info[0]['message'], $info[0]['code']); throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
} }
$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion']; $this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
@@ -98,7 +100,9 @@ class SqlsrvDriver implements Dibi\Driver
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
$this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res)); $this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
return sqlsrv_num_fields($res) ? $this->createResultDriver($res) : null; return sqlsrv_num_fields($res)
? $this->createResultDriver($res)
: null;
} }
return null; return null;
} }
@@ -200,7 +204,7 @@ class SqlsrvDriver implements Dibi\Driver
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
return "'" . str_replace("'", "''", $value) . "'"; return '0x' . bin2hex($value);
} }

View File

@@ -32,6 +32,12 @@ namespace Dibi;
* @method Fluent and(...$cond) * @method Fluent and(...$cond)
* @method Fluent or(...$cond) * @method Fluent or(...$cond)
* @method Fluent using(...$cond) * @method Fluent using(...$cond)
* @method Fluent update(...$cond)
* @method Fluent insert(...$cond)
* @method Fluent delete(...$cond)
* @method Fluent into(...$cond)
* @method Fluent values(...$cond)
* @method Fluent set(...$args)
* @method Fluent asc() * @method Fluent asc()
* @method Fluent desc() * @method Fluent desc()
*/ */
@@ -113,7 +119,7 @@ class Fluent implements IDataSource
$this->connection = $connection; $this->connection = $connection;
if (self::$normalizer === null) { if (self::$normalizer === null) {
self::$normalizer = new HashMap([__CLASS__, '_formatClause']); self::$normalizer = new HashMap([self::class, '_formatClause']);
} }
} }
@@ -305,11 +311,9 @@ class Fluent implements IDataSource
*/ */
public function fetch() public function fetch()
{ {
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) { return $this->command === 'SELECT' && !$this->clauses['LIMIT']
return $this->query($this->_export(null, ['%lmt', 1]))->fetch(); ? $this->query($this->_export(null, ['%lmt', 1]))->fetch()
} else { : $this->query($this->_export())->fetch();
return $this->query($this->_export())->fetch();
}
} }
@@ -319,11 +323,9 @@ class Fluent implements IDataSource
*/ */
public function fetchSingle() public function fetchSingle()
{ {
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) { return $this->command === 'SELECT' && !$this->clauses['LIMIT']
return $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle(); ? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle()
} else { : $this->query($this->_export())->fetchSingle();
return $this->query($this->_export())->fetchSingle();
}
} }

View File

@@ -143,8 +143,8 @@ class Helpers
{ {
$best = null; $best = null;
$min = (strlen($value) / 4 + 1) * 10 + .1; $min = (strlen($value) / 4 + 1) * 10 + .1;
foreach (array_unique($items, SORT_REGULAR) as $item) { $items = array_map('strval', $items);
$item = is_object($item) ? $item->getName() : $item; foreach (array_unique($items) as $item) {
if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) { if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
$min = $len; $min = $len;
$best = $item; $best = $item;
@@ -205,7 +205,7 @@ class Helpers
public static function getTypeCache(): HashMap public static function getTypeCache(): HashMap
{ {
if (self::$types === null) { if (self::$types === null) {
self::$types = new HashMap([__CLASS__, 'detectType']); self::$types = new HashMap([self::class, 'detectType']);
} }
return self::$types; return self::$types;
} }

View File

@@ -73,8 +73,9 @@ class FileLogger
private function writeToFile(Dibi\Event $event, string $message): void private function writeToFile(Dibi\Event $event, string $message): void
{ {
$driver = $event->connection->getConfig('driver');
$message .= $message .=
"\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name') "\n-- driver: " . (is_object($driver) ? get_class($driver) : $driver) . '/' . $event->connection->getConfig('name')
. "\n-- " . date('Y-m-d H:i:s') . "\n-- " . date('Y-m-d H:i:s')
. "\n\n"; . "\n\n";
file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX); file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX);

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ class Result implements IDataSource
{ {
use Strict; use Strict;
/** @var ResultDriver */ /** @var ResultDriver|null */
private $driver; private $driver;
/** @var array Translate table */ /** @var array Translate table */
@@ -83,7 +83,9 @@ class Result implements IDataSource
*/ */
final public function seek(int $row): bool final public function seek(int $row): bool
{ {
return ($row !== 0 || $this->fetched) ? $this->getResultDriver()->seek($row) : true; return ($row !== 0 || $this->fetched)
? $this->getResultDriver()->seek($row)
: true;
} }
@@ -199,7 +201,7 @@ class Result implements IDataSource
*/ */
final public function fetchAll(int $offset = null, int $limit = null): array final public function fetchAll(int $offset = null, int $limit = null): array
{ {
$limit = $limit === null ? -1 : $limit; $limit = $limit ?? -1;
$this->seek($offset ?: 0); $this->seek($offset ?: 0);
$row = $this->fetch(); $row = $this->fetch();
if (!$row) { if (!$row) {
@@ -242,6 +244,9 @@ class Result implements IDataSource
$data = null; $data = null;
$assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
if (!$assoc) {
throw new \InvalidArgumentException("Invalid descriptor '$assoc'.");
}
// check columns // check columns
foreach ($assoc as $as) { foreach ($assoc as $as) {
@@ -292,6 +297,7 @@ class Result implements IDataSource
} while ($row = $this->fetch()); } while ($row = $this->fetch());
unset($x); unset($x);
/** @var mixed[] $data */
return $data; return $data;
} }
@@ -353,11 +359,9 @@ class Result implements IDataSource
} }
if ($x === null) { // build leaf if ($x === null) { // build leaf
if ($leaf === '=') { $x = $leaf === '='
$x = $row->toArray(); ? $row->toArray()
} else { : $row;
$x = $row;
}
} }
} while ($row = $this->fetch()); } while ($row = $this->fetch());
@@ -481,7 +485,9 @@ class Result implements IDataSource
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) { } elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ... if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ...
$value = new DateTime($value); $value = new DateTime($value);
$row[$key] = empty($this->formats[$type]) ? $value : $value->format($this->formats[$type]); $row[$key] = empty($this->formats[$type])
? $value
: $value->format($this->formats[$type]);
} else { } else {
$row[$key] = null; $row[$key] = null;
} }
@@ -492,7 +498,9 @@ class Result implements IDataSource
$row[$key]->invert = (int) (bool) $m[1]; $row[$key]->invert = (int) (bool) $m[1];
} elseif ($type === Type::BINARY) { } elseif ($type === Type::BINARY) {
$row[$key] = is_string($value) ? $this->getResultDriver()->unescapeBinary($value) : $value; $row[$key] = is_string($value)
? $this->getResultDriver()->unescapeBinary($value)
: $value;
} elseif ($type === Type::JSON) { } elseif ($type === Type::JSON) {
if ($this->formats[$type] === 'string') { if ($this->formats[$type] === 'string') {

View File

@@ -29,9 +29,12 @@ trait Strict
*/ */
public function __call(string $name, array $args) public function __call(string $name, array $args)
{ {
$class = method_exists($this, $name) ? 'parent' : get_class($this); $class = method_exists($this, $name) ? 'parent' : static::class;
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC); $items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.'; $items = array_map(function ($item) { return $item->getName(); }, $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?"
: '.';
throw new \LogicException("Call to undefined method $class::$name()$hint"); throw new \LogicException("Call to undefined method $class::$name()$hint");
} }
@@ -42,9 +45,12 @@ trait Strict
*/ */
public static function __callStatic(string $name, array $args) public static function __callStatic(string $name, array $args)
{ {
$rc = new ReflectionClass(get_called_class()); $rc = new ReflectionClass(static::class);
$items = array_intersect($rc->getMethods(ReflectionMethod::IS_PUBLIC), $rc->getMethods(ReflectionMethod::IS_STATIC)); $items = array_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"); throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
} }
@@ -63,7 +69,10 @@ trait Strict
} }
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC)); $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"); throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
} }
@@ -76,7 +85,10 @@ trait Strict
{ {
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC)); $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"); throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
} }
@@ -93,7 +105,7 @@ trait Strict
*/ */
public function __unset(string $name) public function __unset(string $name)
{ {
$class = get_class($this); $class = static::class;
throw new \LogicException("Attempt to unset undeclared property $class::$$name."); throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
} }
} }

View File

@@ -92,24 +92,26 @@ final class Translator
$sql[] = $arg; $sql[] = $arg;
} else { } else {
$sql[] = substr($arg, 0, $toSkip) $sql[] = substr($arg, 0, $toSkip)
/* // note: this can change $this->args & $this->cursor & ...
. preg_replace_callback('/ . preg_replace_callback(
(?=[`[\'":%?]) ## speed-up <<<'XX'
(?: /
`(.+?)`| ## 1) `identifier` (?=[`['":%?]) ## speed-up
\[(.+?)\]| ## 2) [identifier] (?:
(\')((?:\'\'|[^\'])*)\'| ## 3,4) 'string' `(.+?)`| ## 1) `identifier`
(")((?:""|[^"])*)"| ## 5,6) "string" \[(.+?)\]| ## 2) [identifier]
(\'|")| ## 7) lone quote (')((?:''|[^'])*)'| ## 3,4) string
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution: (")((?:""|[^"])*)"| ## 5,6) "string"
%([a-zA-Z~][a-zA-Z0-9~]{0,5})|## 10) modifier ('|")| ## 7) lone quote
(\?) ## 11) placeholder :(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
)/xs', %([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier
*/ // note: this can change $this->args & $this->cursor & ... (\?) ## 11) placeholder
. preg_replace_callback('/(?=[`[\'":%?])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?)|%([a-zA-Z~][a-zA-Z0-9~]{0,5})|(\?))/s', )/xs
XX
,
[$this, 'cb'], [$this, 'cb'],
substr($arg, $toSkip) substr($arg, $toSkip)
); );
if (preg_last_error()) { if (preg_last_error()) {
throw new PcreException; throw new PcreException;
} }
@@ -289,7 +291,7 @@ final class Translator
if (is_array($v)) { if (is_array($v)) {
$vx[] = $this->formatValue($v, 'ex'); $vx[] = $this->formatValue($v, 'ex');
} elseif (is_string($k)) { } elseif (is_string($k)) {
$v = (is_string($v) && strncasecmp($v, 'd', 1)) || $v > 0 ? 'ASC' : 'DESC'; $v = (is_string($v) ? strncasecmp($v, 'd', 1) : $v > 0) ? 'ASC' : 'DESC';
$vx[] = $this->identifiers->$k . ' ' . $v; $vx[] = $this->identifiers->$k . ' ' . $v;
} else { } else {
$vx[] = $this->identifiers->$v; $vx[] = $this->identifiers->$v;
@@ -322,7 +324,13 @@ final class Translator
} elseif ($value instanceof Expression && $modifier === 'ex') { } elseif ($value instanceof Expression && $modifier === 'ex') {
return $this->connection->translate(...$value->getValues()); return $this->connection->translate(...$value->getValues());
} elseif ($value instanceof \DateTimeInterface && ($modifier === 'd' || $modifier === 't' || $modifier === 'dt')) { } elseif (
$value instanceof \DateTimeInterface
&& ($modifier === 'd'
|| $modifier === 't'
|| $modifier === 'dt'
)
) {
// continue // continue
} else { } else {
$type = is_object($value) ? get_class($value) : gettype($value); $type = is_object($value) ? get_class($value) : gettype($value);
@@ -332,21 +340,29 @@ final class Translator
switch ($modifier) { switch ($modifier) {
case 's': // string case 's': // string
return $value === null ? 'NULL' : $this->driver->escapeText((string) $value); return $value === null
? 'NULL'
: $this->driver->escapeText((string) $value);
case 'bin':// binary case 'bin':// binary
return $value === null ? 'NULL' : $this->driver->escapeBinary($value); return $value === null
? 'NULL'
: $this->driver->escapeBinary($value);
case 'b': // boolean case 'b': // boolean
return $value === null ? 'NULL' : $this->driver->escapeBool((bool) $value); return $value === null
? 'NULL'
: $this->driver->escapeBool((bool) $value);
case 'sN': // string or null case 'sN': // string or null
case 'sn': case 'sn':
return $value == '' ? 'NULL' : $this->driver->escapeText((string) $value); // notice two equal signs return $value === '' || $value === 0 || $value === null
? 'NULL'
: $this->driver->escapeText((string) $value);
case 'iN': // signed int or null case 'iN': // signed int or null
if ($value == '') { if ($value === '' || $value === 0 || $value === null) {
$value = null; return 'NULL';
} }
// break omitted // break omitted
case 'i': // signed int case 'i': // signed int
@@ -384,7 +400,9 @@ final class Translator
} elseif (!$value instanceof \DateTimeInterface) { } elseif (!$value instanceof \DateTimeInterface) {
$value = new DateTime($value); $value = new DateTime($value);
} }
return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value); return $modifier === 'd'
? $this->driver->escapeDate($value)
: $this->driver->escapeDateTime($value);
case 'by': case 'by':
case 'n': // composed identifier name case 'n': // composed identifier name
@@ -400,11 +418,23 @@ final class Translator
$toSkip = strcspn($value, '`[\'":'); $toSkip = strcspn($value, '`[\'":');
if (strlen($value) !== $toSkip) { if (strlen($value) !== $toSkip) {
$value = substr($value, 0, $toSkip) $value = substr($value, 0, $toSkip)
. preg_replace_callback( . preg_replace_callback(
'/(?=[`[\'":])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?))/s', <<<'XX'
[$this, 'cb'], /
substr($value, $toSkip) (?=[`['":])
); (?:
`(.+?)`|
\[(.+?)\]|
(')((?:''|[^'])*)'|
(")((?:""|[^"])*)"|
('|")|
:(\S*?:)([a-zA-Z0-9._]?)
)/sx
XX
,
[$this, 'cb'],
substr($value, $toSkip)
);
if (preg_last_error()) { if (preg_last_error()) {
throw new PcreException; throw new PcreException;
} }
@@ -597,7 +627,9 @@ final class Translator
if ($matches[8]) { // SQL identifier substitution if ($matches[8]) { // SQL identifier substitution
$m = substr($matches[8], 0, -1); $m = substr($matches[8], 0, -1);
$m = $this->connection->getSubstitutes()->$m; $m = $this->connection->getSubstitutes()->$m;
return $matches[9] == '' ? $this->formatValue($m, null) : $m . $matches[9]; // value or identifier return $matches[9] === ''
? $this->formatValue($m, null)
: $m . $matches[9]; // value or identifier
} }
throw new \Exception('this should be never executed'); throw new \Exception('this should be never executed');

View File

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

View File

@@ -75,7 +75,7 @@ class dibi
*/ */
final public function __construct() final public function __construct()
{ {
throw new LogicException('Cannot instantiate static class ' . get_class($this)); throw new LogicException('Cannot instantiate static class ' . static::class);
} }

View File

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

View File

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

View File

@@ -29,13 +29,6 @@ Assert::exception(function () {
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.'); }, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.');
// PDO error mode: explicitly set silent test('PDO error mode: explicitly set silent', function () {
test(function () {
buildPdoDriver(PDO::ERRMODE_SILENT); buildPdoDriver(PDO::ERRMODE_SILENT);
}); });
// PDO error mode: implicitly set silent
test(function () {
buildPdoDriver(null);
});

View File

@@ -24,7 +24,7 @@ class MockResult extends Dibi\Result
} }
test(function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::BOOL); $result->setType('col', Type::BOOL);
@@ -46,7 +46,7 @@ test(function () {
}); });
test(function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TEXT); $result->setType('col', Type::TEXT);
@@ -62,7 +62,7 @@ test(function () {
}); });
test(function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::FLOAT); $result->setType('col', Type::FLOAT);
@@ -139,7 +139,7 @@ test(function () {
}); });
test(function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::INTEGER); $result->setType('col', Type::INTEGER);
@@ -147,7 +147,14 @@ test(function () {
Assert::same(['col' => 1], $result->test(['col' => true])); Assert::same(['col' => 1], $result->test(['col' => true]));
Assert::same(['col' => 0], $result->test(['col' => false])); Assert::same(['col' => 0], $result->test(['col' => false]));
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning in PHP 7.1 if (PHP_VERSION_ID < 80000) {
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
} else {
Assert::exception(function () use ($result) {
Assert::same(['col' => 0], $result->test(['col' => '']));
}, TypeError::class);
}
Assert::same(['col' => 0], $result->test(['col' => '0'])); Assert::same(['col' => 0], $result->test(['col' => '0']));
Assert::same(['col' => 1], $result->test(['col' => '1'])); Assert::same(['col' => 1], $result->test(['col' => '1']));
Assert::same(['col' => 10], $result->test(['col' => '10'])); Assert::same(['col' => 10], $result->test(['col' => '10']));
@@ -165,7 +172,7 @@ test(function () {
}); });
test(function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DATETIME);
@@ -183,7 +190,7 @@ test(function () {
}); });
test(function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DATETIME);
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s'); $result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
@@ -202,7 +209,7 @@ test(function () {
}); });
test(function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATE); $result->setType('col', Type::DATE);
@@ -218,7 +225,7 @@ test(function () {
}); });
test(function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TIME); $result->setType('col', Type::TIME);

View File

@@ -23,7 +23,9 @@ Assert::equal(1, $conn->getInsertId());
$conn->query( $conn->query(
'CREATE TRIGGER %n ON %n AFTER INSERT AS INSERT INTO %n DEFAULT VALUES', 'CREATE TRIGGER %n ON %n AFTER INSERT AS INSERT INTO %n DEFAULT VALUES',
'UpdAAB', 'aab', 'aaa' 'UpdAAB',
'aab',
'aaa'
); );
$conn->query('INSERT INTO %n DEFAULT VALUES', 'aab'); $conn->query('INSERT INTO %n DEFAULT VALUES', 'aab');

View File

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

View File

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

View File

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

View File

@@ -37,7 +37,7 @@ if ($config['system'] === 'odbc') {
} }
function test(Closure $function) function test(string $title, Closure $function): void
{ {
$function(); $function();
} }