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

Compare commits

..

1 Commits

Author SHA1 Message Date
David Grudl
dbbf0ca673 Revert "PhpStan fixes (#363)"
This reverts commit 68f9d0981f194931e475e2c93730d8e8785c20e4.
2020-05-07 21:42:36 +02:00
49 changed files with 335 additions and 650 deletions

6
.gitattributes vendored
View File

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

1
.github/funding.yml vendored
View File

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

View File

@@ -1,9 +1,9 @@
language: php language: php
php: php:
- 7.1
- 7.2 - 7.2
- 7.3 - 7.3
- 7.4 - 7.4
- 8.0snapshot
services: services:
- mysql - mysql
@@ -32,6 +32,7 @@ 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:
@@ -41,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 ^3 --no-progress - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress
script: script:
- php temp/coding-standard/ecs check src tests - php temp/coding-standard/ecs check src tests examples --config tests/coding-standard.yml
- stage: Static Analysis (informative) - stage: Static Analysis (informative)
@@ -66,7 +67,7 @@ jobs:
- stage: Code Coverage - stage: Code Coverage
dist: xenial sudo: false
cache: cache:
directories: directories:

View File

@@ -15,17 +15,17 @@ init:
- SET ANSICON=121x90 (121x90) - SET ANSICON=121x90 (121x90)
install: install:
# Install PHP 7.2 # Install PHP 7.1
- IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1) - IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1)
- IF %PHP%==1 mkdir c:\php7 - IF %PHP%==1 mkdir c:\php7
- IF %PHP%==1 cd c:\php7 - IF %PHP%==1 cd c:\php7
- IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.2.18-Win32-VC15-x64.zip --output php.zip - IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.1.5-Win32-VC14-x64.zip --output php.zip
- IF %PHP%==1 7z x php.zip >nul - IF %PHP%==1 7z x php.zip >nul
- IF %PHP%==1 echo extension_dir=ext >> php.ini - IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- IF %PHP%==1 curl https://github.com/microsoft/msphpsql/releases/download/v5.8.0/Windows-7.2.zip -L --output sqlsrv.zip - IF %PHP%==1 appveyor DownloadFile https://files.nette.org/misc/php-sqlsrv.zip
- IF %PHP%==1 7z x sqlsrv.zip >nul - IF %PHP%==1 7z x php-sqlsrv.zip >nul
- IF %PHP%==1 copy Windows-7.2\x64\php_sqlsrv_72_ts.dll ext\php_sqlsrv_ts.dll - IF %PHP%==1 copy SQLSRV\php_sqlsrv_71_ts.dll ext\php_sqlsrv_71_ts.dll
- IF %PHP%==1 del /Q *.zip - IF %PHP%==1 del /Q *.zip
# Install Microsoft Access Database Engine x64 # Install Microsoft Access Database Engine x64

View File

@@ -11,12 +11,11 @@
} }
], ],
"require": { "require": {
"php": ">=7.2" "php": ">=7.1"
}, },
"require-dev": { "require-dev": {
"tracy/tracy": "~2.2", "tracy/tracy": "~2.2",
"nette/tester": "~2.0", "nette/tester": "~2.0",
"nette/di": "^3.0",
"phpstan/phpstan": "^0.12" "phpstan/phpstan": "^0.12"
}, },
"replace": { "replace": {
@@ -26,12 +25,12 @@
"classmap": ["src/"] "classmap": ["src/"]
}, },
"scripts": { "scripts": {
"phpstan": "phpstan analyse", "phpstan": "phpstan analyse --autoload-file vendor/autoload.php --level 5 --configuration tests/phpstan.neon src",
"tester": "tester tests -s" "tester": "tester tests -s"
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.2-dev" "dev-master": "4.1-dev"
} }
} }
} }

21
ecs.php
View File

@@ -1,21 +0,0 @@
<?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

@@ -14,15 +14,7 @@ Introduction
Database access functions in PHP are not standardised. This library Database access functions in PHP are not standardised. This library
hides the differences between them, and above all, it gives you a very handy interface. hides the differences between them, and above all, it gives you a very handy interface.
If you like Dibi, **[please make a donation now](https://nette.org/make-donation?to=dibi)**. Thank you!
Support Me
----------
Do you like Dibi? Are you looking forward to the new features?
[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
Thank you!
Installation Installation
@@ -34,7 +26,7 @@ Install Dibi via Composer:
composer require dibi/dibi composer require dibi/dibi
``` ```
The Dibi 4.2 requires PHP version 7.2 and supports PHP up to 8.0. The Dibi 4.1 requires PHP version 7.1 and supports PHP up to 7.4.
Usage Usage
@@ -50,11 +42,11 @@ The database connection is represented by the object `Dibi\Connection`:
```php ```php
$database = new Dibi\Connection([ $database = new Dibi\Connection([
'driver' => 'mysqli', 'driver' => 'mysqli',
'host' => 'localhost', 'host' => 'localhost',
'username' => 'root', 'username' => 'root',
'password' => '***', 'password' => '***',
'database' => 'table', 'database' => 'table',
]); ]);
$result = $database->query('SELECT * FROM users'); $result = $database->query('SELECT * FROM users');
@@ -64,12 +56,12 @@ Alternatively, you can use the `dibi` static register, which maintains a connect
```php ```php
dibi::connect([ dibi::connect([
'driver' => 'mysqli', 'driver' => 'mysqli',
'host' => 'localhost', 'host' => 'localhost',
'username' => 'root', 'username' => 'root',
'password' => '***', 'password' => '***',
'database' => 'test', 'database' => 'test',
'charset' => 'utf8', 'charset' => 'utf8',
]); ]);
$result = dibi::query('SELECT * FROM users'); $result = dibi::query('SELECT * FROM users');
@@ -237,8 +229,8 @@ Example:
```php ```php
$arr = [ $arr = [
'a' => 'hello', 'a' => 'hello',
'b' => true, 'b' => true,
]; ];
$database->query('INSERT INTO table %v', $arr); $database->query('INSERT INTO table %v', $arr);
@@ -510,7 +502,7 @@ $all = $result->fetchAssoc('customer_id|order_id');
// we will iterate like this: // we will iterate like this:
foreach ($all as $customerId => $orders) { foreach ($all as $customerId => $orders) {
foreach ($orders as $orderId => $order) { foreach ($orders as $orderId => $order) {
... ...
} }
} }
``` ```
@@ -542,7 +534,7 @@ $all = $result->fetchAssoc('name[]order_id');
// we get all the Arnolds in the results // we get all the Arnolds in the results
foreach ($all['Arnold Rimmer'] as $arnoldOrders) { foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
foreach ($arnoldOrders as $orderId => $order) { foreach ($arnoldOrders as $orderId => $order) {
... ...
} }
} }
``` ```
@@ -556,8 +548,8 @@ foreach ($all as $customerId => $orders) {
echo "Customer $customerId": echo "Customer $customerId":
foreach ($orders as $orderId => $order) { foreach ($orders as $orderId => $order) {
echo "ID number: $order->number"; echo "ID number: $order->number";
// customer name is in $order->name // customer name is in $order->name
} }
} }
``` ```
@@ -579,7 +571,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([self::class, 'renderException']); Tracy\Debugger::getBlueScreen()->addPanel([__CLASS__, 'renderException']);
$connection->onEvent[] = [$this, 'logEvent']; $connection->onEvent[] = [$this, 'logEvent'];
} }
@@ -105,15 +105,6 @@ class Panel implements Tracy\IBarPanel
} }
$totalTime = $s = null; $totalTime = $s = null;
$singleConnection = reset($this->events)->connection;
foreach ($this->events as $event) {
if ($event->connection !== $singleConnection) {
$singleConnection = null;
break;
}
}
foreach ($this->events as $event) { foreach ($this->events as $event) {
$totalTime += $event->time; $totalTime += $event->time;
$connection = $event->connection; $connection = $event->connection;
@@ -121,9 +112,7 @@ class Panel implements Tracy\IBarPanel
if ($this->explain && $event->type === Event::SELECT) { if ($this->explain && $event->type === Event::SELECT) {
$backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime]; $backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime];
$connection->onEvent = null; $connection->onEvent = null;
$cmd = is_string($this->explain) $cmd = is_string($this->explain) ? $this->explain : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
? $this->explain
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
try { try {
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true); $explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
} catch (Dibi\Exception $e) { } catch (Dibi\Exception $e) {
@@ -131,7 +120,7 @@ class Panel implements Tracy\IBarPanel
[$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup; [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup;
} }
$s .= '<tr><td data-order="' . $event->time . '">' . number_format($event->time * 1000, 3, '.', ''); $s .= '<tr><td>' . number_format($event->time * 1000, 3, '.', '');
if ($explain) { if ($explain) {
static $counter; static $counter;
$counter++; $counter++;
@@ -143,13 +132,10 @@ 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>"; $s .= "</td><td>{$event->count}</td></tr>";
if (!$singleConnection) {
$s .= '<td>' . htmlspecialchars($this->getConnectionName($connection)) . '</td></tr>';
}
} }
return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important } return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important }
@@ -157,21 +143,12 @@ 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($this->getConnectionName($singleConnection)) . '</h1> . htmlspecialchars($connection->getConfig('driver') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? '@' . $connection->getConfig('host') : '')) . '</h1>
<div class="tracy-inner tracy-DibiProfiler"> <div class="tracy-inner tracy-DibiProfiler">
<table class="tracy-sortable"> <table>
<tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr> <tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th></tr>' . $s . '
' . $s . '
</table> </table>
</div>'; </div>';
} }
private function getConnectionName(Dibi\Connection $connection): string
{
$driver = $connection->getConfig('driver');
return (is_object($driver) ? get_class($driver) : $driver)
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? '@' . $connection->getConfig('host') : '');
}
} }

View File

@@ -28,9 +28,6 @@ class Connection implements IConnection
/** @var array Current connection configuration */ /** @var array Current connection configuration */
private $config; private $config;
/** @var string[] resultset formats */
private $formats;
/** @var Driver|null */ /** @var Driver|null */
private $driver; private $driver;
@@ -40,26 +37,17 @@ class Connection implements IConnection
/** @var HashMap Substitutes for identifiers */ /** @var HashMap Substitutes for identifiers */
private $substitutes; private $substitutes;
private $transactionDepth = 0;
/** /**
* Connection options: (see driver-specific options too) * Connection options: (see driver-specific options too)
* - lazy (bool) => if true, connection will be established only when required * - lazy (bool) => if true, connection will be established only when required
* - result (array) => result set options * - result (array) => result set options
* - normalize => normalizes result fields (default: true) * - formatDateTime => date-time format (if empty, DateTime objects will be returned)
* - formatDateTime => date-time format * - formatJson => json format (
* empty for decoding as Dibi\DateTime (default) * "string" for leaving value as is,
* "..." formatted according to given format, see https://www.php.net/manual/en/datetime.format.php * "object" for decoding json as \stdClass,
* "native" for leaving value as is * "array" for decoding json as an array - default
* - formatTimeInterval => time-interval format * )
* empty for decoding as DateInterval (default)
* "..." formatted according to given format, see https://www.php.net/manual/en/dateinterval.format.php
* "native" for leaving value as is
* - formatJson => json format
* "array" for decoding json as an array (default)
* "object" for decoding json as \stdClass
* "native" for leaving value as is
* - profiler (array) * - profiler (array)
* - run (bool) => enable profiler? * - run (bool) => enable profiler?
* - file => file to log * - file => file to log
@@ -77,15 +65,9 @@ class Connection implements IConnection
Helpers::alias($config, 'result|formatDateTime', 'resultDateTime'); Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
$config['driver'] = $config['driver'] ?? 'mysqli'; $config['driver'] = $config['driver'] ?? 'mysqli';
$config['name'] = $name; $config['name'] = $name;
$config['result']['formatJson'] = $config['result']['formatJson'] ?? 'array';
$this->config = $config; $this->config = $config;
$this->formats = [
Type::DATE => $this->config['result']['formatDate'],
Type::DATETIME => $this->config['result']['formatDateTime'],
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
];
// profiler // profiler
if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) { if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) {
$filter = $config['profiler']['filter'] ?? Event::QUERY; $filter = $config['profiler']['filter'] ?? Event::QUERY;
@@ -218,7 +200,7 @@ class Connection implements IConnection
*/ */
final public function query(...$args): Result final public function query(...$args): Result
{ {
return $this->nativeQuery($this->translate(...$args)); return $this->nativeQuery($this->translateArgs($args));
} }
@@ -229,10 +211,7 @@ class Connection implements IConnection
*/ */
final public function translate(...$args): string final public function translate(...$args): string
{ {
if (!$this->driver) { return $this->translateArgs($args);
$this->connect();
}
return (clone $this->translator)->translate($args);
} }
@@ -243,7 +222,7 @@ class Connection implements IConnection
final public function test(...$args): bool final public function test(...$args): bool
{ {
try { try {
Helpers::dump($this->translate(...$args)); Helpers::dump($this->translateArgs($args));
return true; return true;
} catch (Exception $e) { } catch (Exception $e) {
@@ -264,7 +243,19 @@ class Connection implements IConnection
*/ */
final public function dataSource(...$args): DataSource final public function dataSource(...$args): DataSource
{ {
return new DataSource($this->translate(...$args), $this); return new DataSource($this->translateArgs($args), $this);
}
/**
* Generates SQL query.
*/
protected function translateArgs(array $args): string
{
if (!$this->driver) {
$this->connect();
}
return (clone $this->translator)->translate($args);
} }
@@ -337,10 +328,6 @@ class Connection implements IConnection
*/ */
public function begin(string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
@@ -365,10 +352,6 @@ class Connection implements IConnection
*/ */
public function commit(string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
@@ -393,10 +376,6 @@ class Connection implements IConnection
*/ */
public function rollback(string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
@@ -416,42 +395,15 @@ class Connection implements IConnection
} }
/**
* @return mixed
*/
public function transaction(callable $callback)
{
if ($this->transactionDepth === 0) {
$this->begin();
}
$this->transactionDepth++;
try {
$res = $callback($this);
} catch (\Throwable $e) {
$this->transactionDepth--;
if ($this->transactionDepth === 0) {
$this->rollback();
}
throw $e;
}
$this->transactionDepth--;
if ($this->transactionDepth === 0) {
$this->commit();
}
return $res;
}
/** /**
* Result set factory. * Result set factory.
*/ */
public function createResultSet(ResultDriver $resultDriver): Result public function createResultSet(ResultDriver $resultDriver): Result
{ {
return (new Result($resultDriver, $this->config['result']['normalize'] ?? true)) $res = new Result($resultDriver);
->setFormats($this->formats); return $res->setFormat(Type::DATE, $this->config['result']['formatDate'])
->setFormat(Type::DATETIME, $this->config['result']['formatDateTime'])
->setFormat(Type::JSON, $this->config['result']['formatJson']);
} }
@@ -610,7 +562,7 @@ class Connection implements IConnection
*/ */
public function __wakeup() public function __wakeup()
{ {
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.'); throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
} }
@@ -619,7 +571,7 @@ class Connection implements IConnection
*/ */
public function __sleep() public function __sleep()
{ {
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.'); throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
} }

View File

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

View File

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

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,8 +84,9 @@ class FirebirdReflector implements Dibi\Reflector
FROM RDB\$RELATION_FIELDS r FROM RDB\$RELATION_FIELDS r
LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
WHERE r.RDB\$RELATION_NAME = '$table' WHERE r.RDB\$RELATION_NAME = '$table'
ORDER BY r.RDB\$FIELD_POSITION; ORDER BY r.RDB\$FIELD_POSITION;"
");
);
$columns = []; $columns = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$key = $row['FIELD_NAME']; $key = $row['FIELD_NAME'];
@@ -120,8 +121,8 @@ class FirebirdReflector implements Dibi\Reflector
LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table' WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
ORDER BY s.RDB\$FIELD_POSITION ORDER BY s.RDB\$FIELD_POSITION"
"); );
$indexes = []; $indexes = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$key = $row['INDEX_NAME']; $key = $row['INDEX_NAME'];
@@ -148,8 +149,8 @@ class FirebirdReflector implements Dibi\Reflector
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table' WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY' AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY'
ORDER BY s.RDB\$FIELD_POSITION ORDER BY s.RDB\$FIELD_POSITION"
"); );
$keys = []; $keys = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$key = $row['INDEX_NAME']; $key = $row['INDEX_NAME'];
@@ -173,8 +174,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];
@@ -195,8 +196,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];
@@ -211,8 +212,7 @@ 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,9 +261,7 @@ 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 $q .= $table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table')";
? ';'
: " AND RDB\$RELATION_NAME = UPPER('$table')";
$res = $this->driver->query($q); $res = $this->driver->query($q);
$triggers = []; $triggers = [];
@@ -309,8 +307,8 @@ class FirebirdReflector implements Dibi\Reflector
p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER
FROM RDB\$PROCEDURE_PARAMETERS p FROM RDB\$PROCEDURE_PARAMETERS p
LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE
ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER; ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;"
"); );
$procedures = []; $procedures = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$key = $row['PROCEDURE_NAME']; $key = $row['PROCEDURE_NAME'];
@@ -332,8 +330,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];
@@ -350,8 +348,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];
@@ -368,8 +366,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,12 +62,10 @@ class FirebirdResult implements Dibi\ResultDriver
*/ */
public function fetch(bool $assoc): ?array public function fetch(bool $assoc): ?array
{ {
$result = $assoc $result = $assoc ? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT) : @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT)
: @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
if (ibase_errcode()) { if (ibase_errcode()) {
if (ibase_errcode() === FirebirdDriver::ERROR_EXCEPTION_THROWN) { if (ibase_errcode() == FirebirdDriver::ERROR_EXCEPTION_THROWN) {
preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match); preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
throw new Dibi\ProcedureException($match[3], $match[1], $match[2]); throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);

View File

@@ -130,15 +130,6 @@ class MySqliDriver implements Dibi\Driver
} }
/**
* Pings a server connection, or tries to reconnect if the connection has gone down.
*/
public function ping(): bool
{
return $this->connection->ping();
}
/** /**
* Executes the SQL query. * Executes the SQL query.
* @throws Dibi\DriverException * @throws Dibi\DriverException
@@ -157,9 +148,6 @@ 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)) {
@@ -200,9 +188,7 @@ class MySqliDriver implements Dibi\Driver
*/ */
public function getAffectedRows(): ?int public function getAffectedRows(): ?int
{ {
return $this->connection->affected_rows === -1 return $this->connection->affected_rows === -1 ? null : $this->connection->affected_rows;
? null
: $this->connection->affected_rows;
} }
@@ -319,7 +305,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,9 +54,11 @@ class OdbcDriver implements Dibi\Driver
'dsn' => ini_get('odbc.default_db'), 'dsn' => ini_get('odbc.default_db'),
]; ];
$this->connection = empty($config['persistent']) if (empty($config['persistent'])) {
? @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? '') // intentionally @ $this->connection = @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
: @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @ } else {
$this->connection = @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
}
} }
if (!is_resource($this->connection)) { if (!is_resource($this->connection)) {
@@ -92,9 +94,7 @@ class OdbcDriver implements Dibi\Driver
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
$this->affectedRows = Dibi\Helpers::false2Null(odbc_num_rows($res)); $this->affectedRows = Dibi\Helpers::false2Null(odbc_num_rows($res));
return odbc_num_fields($res) return odbc_num_fields($res) ? $this->createResultDriver($res) : null;
? $this->createResultDriver($res)
: null;
} }
return null; return null;
} }
@@ -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, PHP_VERSION_ID < 80000 ? 0 : false)) { if (!odbc_autocommit($this->connection, 0/*false*/)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
} }
@@ -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, PHP_VERSION_ID < 80000 ? 1 : true); odbc_autocommit($this->connection, 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, PHP_VERSION_ID < 80000 ? 1 : true); odbc_autocommit($this->connection, 1/*true*/);
} }

View File

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

View File

@@ -56,15 +56,9 @@ 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.');
@@ -73,6 +67,10 @@ 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
} }
@@ -234,17 +232,21 @@ class PdoDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
return $this->driverName === 'odbc' if ($this->driverName === 'odbc') {
? "'" . str_replace("'", "''", $value) . "'" return "'" . str_replace("'", "''", $value) . "'";
: $this->connection->quote($value, PDO::PARAM_STR); } else {
return $this->connection->quote($value, PDO::PARAM_STR);
}
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
return $this->driverName === 'odbc' if ($this->driverName === 'odbc') {
? "'" . str_replace("'", "''", $value) . "'" return "'" . str_replace("'", "''", $value) . "'";
: $this->connection->quote($value, PDO::PARAM_LOB); } else {
return $this->connection->quote($value, PDO::PARAM_LOB);
}
} }

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,7 +23,6 @@ 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
{ {
@@ -63,14 +62,15 @@ class PostgreDriver implements Dibi\Driver
} }
} }
} }
$connectType = $config['connect_type'] ?? PGSQL_CONNECT_FORCE_NEW;
set_error_handler(function (int $severity, string $message) use (&$error) { set_error_handler(function (int $severity, string $message) use (&$error) {
$error = $message; $error = $message;
}); });
$this->connection = empty($config['persistent']) if (empty($config['persistent'])) {
? pg_connect($string, $connectType) $this->connection = pg_connect($string, PGSQL_CONNECT_FORCE_NEW);
: pg_pconnect($string, $connectType); } else {
$this->connection = pg_pconnect($string);
}
restore_error_handler(); restore_error_handler();
} }
@@ -169,9 +169,12 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function getInsertId(?string $sequence): ?int public function getInsertId(?string $sequence): ?int
{ {
$res = $sequence === null if ($sequence === null) {
? $this->query('SELECT LASTVAL()') // PostgreSQL 8.1 is needed // PostgreSQL 8.1 is needed
: $this->query("SELECT CURRVAL('$sequence')"); $res = $this->query('SELECT LASTVAL()');
} else {
$res = $this->query("SELECT CURRVAL('$sequence')");
}
if (!$res) { if (!$res) {
return null; return null;
@@ -188,7 +191,7 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function begin(string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
$this->query($savepoint ? "SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'START TRANSACTION'); $this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
} }
@@ -198,7 +201,7 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function commit(string $savepoint = null): void public function commit(string $savepoint = null): void
{ {
$this->query($savepoint ? "RELEASE SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'COMMIT'); $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
} }
@@ -208,7 +211,7 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function rollback(string $savepoint = null): void public function rollback(string $savepoint = null): void
{ {
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'ROLLBACK'); $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
} }

View File

@@ -28,6 +28,9 @@ class PostgreReflector implements Dibi\Reflector
public function __construct(Dibi\Driver $driver, string $version) public function __construct(Dibi\Driver $driver, string $version)
{ {
if ($version < 7.4) {
throw new Dibi\DriverException('Reflection requires PostgreSQL 7.4 and newer.');
}
$this->driver = $driver; $this->driver = $driver;
$this->version = $version; $this->version = $version;
} }
@@ -248,10 +251,7 @@ class PostgreReflector implements Dibi\Reflector
$references[$row['name']] = array_combine($l, $f); $references[$row['name']] = array_combine($l, $f);
} }
if ( if (isset($references[$row['name']][$row['lnum']]) && $references[$row['name']][$row['lnum']] === $row['fnum']) {
isset($references[$row['name']][$row['lnum']])
&& $references[$row['name']][$row['lnum']] === $row['fnum']
) {
$fKeys[$row['name']]['local'][] = $row['local']; $fKeys[$row['name']]['local'][] = $row['local'];
$fKeys[$row['name']]['foreign'][] = $row['foreign']; $fKeys[$row['name']]['foreign'][] = $row['foreign'];
} }

View File

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

View File

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

View File

@@ -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,13 +63,11 @@ 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(SQLSRV_ERR_ERRORS); $info = sqlsrv_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'];
@@ -100,9 +98,7 @@ class SqlsrvDriver implements Dibi\Driver
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
$this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res)); $this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
return sqlsrv_num_fields($res) return sqlsrv_num_fields($res) ? $this->createResultDriver($res) : null;
? $this->createResultDriver($res)
: null;
} }
return null; return null;
} }
@@ -204,7 +200,7 @@ class SqlsrvDriver implements Dibi\Driver
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
return '0x' . bin2hex($value); return "'" . str_replace("'", "''", $value) . "'";
} }

View File

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

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;
$items = array_map('strval', $items); foreach (array_unique($items, SORT_REGULAR) as $item) {
foreach (array_unique($items) as $item) { $item = is_object($item) ? $item->getName() : $item;
if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) { if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
$min = $len; $min = $len;
$best = $item; $best = $item;
@@ -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([self::class, 'detectType']); self::$types = new HashMap([__CLASS__, 'detectType']);
} }
return self::$types; return self::$types;
} }

View File

@@ -73,9 +73,8 @@ 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: " . (is_object($driver) ? get_class($driver) : $driver) . '/' . $event->connection->getConfig('name') "\n-- driver: " . $event->connection->getConfig('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,9 +72,7 @@ class Column
public function getTableName(): ?string public function getTableName(): ?string
{ {
return isset($this->info['table']) && $this->info['table'] != null // intentionally == return isset($this->info['table']) && $this->info['table'] != null ? $this->info['table'] : null; // intentionally ==
? $this->info['table']
: null;
} }

View File

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

View File

@@ -19,7 +19,7 @@ class Result implements IDataSource
{ {
use Strict; use Strict;
/** @var ResultDriver|null */ /** @var ResultDriver */
private $driver; private $driver;
/** @var array Translate table */ /** @var array Translate table */
@@ -41,12 +41,10 @@ class Result implements IDataSource
private $formats = []; private $formats = [];
public function __construct(ResultDriver $driver, bool $normalize = true) public function __construct(ResultDriver $driver)
{ {
$this->driver = $driver; $this->driver = $driver;
if ($normalize) { $this->detectTypes();
$this->detectTypes();
}
} }
@@ -85,9 +83,7 @@ class Result implements IDataSource
*/ */
final public function seek(int $row): bool final public function seek(int $row): bool
{ {
return ($row !== 0 || $this->fetched) return ($row !== 0 || $this->fetched) ? $this->getResultDriver()->seek($row) : true;
? $this->getResultDriver()->seek($row)
: true;
} }
@@ -203,7 +199,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 ?? -1; $limit = $limit === null ? -1 : $limit;
$this->seek($offset ?: 0); $this->seek($offset ?: 0);
$row = $this->fetch(); $row = $this->fetch();
if (!$row) { if (!$row) {
@@ -299,7 +295,6 @@ class Result implements IDataSource
} while ($row = $this->fetch()); } while ($row = $this->fetch());
unset($x); unset($x);
/** @var mixed[] $data */
return $data; return $data;
} }
@@ -361,9 +356,11 @@ class Result implements IDataSource
} }
if ($x === null) { // build leaf if ($x === null) { // build leaf
$x = $leaf === '=' if ($leaf === '=') {
? $row->toArray() $x = $row->toArray();
: $row; } else {
$x = $row;
}
} }
} while ($row = $this->fetch()); } while ($row = $this->fetch());
@@ -456,12 +453,8 @@ class Result implements IDataSource
continue; continue;
} }
$value = $row[$key]; $value = $row[$key];
$format = $this->formats[$type] ?? null;
if ($type === null || $format === 'native') { if ($type === Type::TEXT) {
$row[$key] = $value;
} elseif ($type === Type::TEXT) {
$row[$key] = (string) $value; $row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) { } elseif ($type === Type::INTEGER) {
@@ -491,29 +484,29 @@ 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] = $format ? $value->format($format) : $value; $row[$key] = empty($this->formats[$type]) ? $value : $value->format($this->formats[$type]);
} else { } else {
$row[$key] = null; $row[$key] = null;
} }
} elseif ($type === Type::TIME_INTERVAL) { } elseif ($type === Type::TIME_INTERVAL) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m); preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S"); $row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$value->invert = (int) (bool) $m[1]; $row[$key]->invert = (int) (bool) $m[1];
$row[$key] = $format ? $value->format($format) : $value;
} elseif ($type === Type::BINARY) { } elseif ($type === Type::BINARY) {
$row[$key] = is_string($value) $row[$key] = is_string($value) ? $this->getResultDriver()->unescapeBinary($value) : $value;
? $this->getResultDriver()->unescapeBinary($value)
: $value;
} elseif ($type === Type::JSON) { } elseif ($type === Type::JSON) {
if ($format === 'string') { // back compatibility with 'native' if ($this->formats[$type] === 'string') {
$row[$key] = $value; $row[$key] = $value;
} else { } else {
$row[$key] = json_decode($value, $format === 'array'); $row[$key] = json_decode($value, $this->formats[$type] === 'array');
} }
} elseif ($type === null) {
$row[$key] = $value;
} else { } else {
throw new \RuntimeException('Unexpected type ' . $type); throw new \RuntimeException('Unexpected type ' . $type);
} }
@@ -551,7 +544,7 @@ class Result implements IDataSource
/** /**
* Sets type format. * Sets date format.
*/ */
final public function setFormat(string $type, ?string $format): self final public function setFormat(string $type, ?string $format): self
{ {
@@ -560,16 +553,6 @@ class Result implements IDataSource
} }
/**
* Sets type formats.
*/
final public function setFormats(array $formats): self
{
$this->formats = $formats;
return $this;
}
/** /**
* Returns data format. * Returns data format.
*/ */

View File

@@ -53,12 +53,6 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
} }
public function __isset(string $key): bool
{
return false;
}
/********************* interfaces ArrayAccess, Countable & IteratorAggregate ****************d*g**/ /********************* interfaces ArrayAccess, Countable & IteratorAggregate ****************d*g**/

View File

@@ -29,12 +29,9 @@ trait Strict
*/ */
public function __call(string $name, array $args) public function __call(string $name, array $args)
{ {
$class = method_exists($this, $name) ? 'parent' : static::class; $class = method_exists($this, $name) ? 'parent' : get_class($this);
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC); $items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
$items = array_map(function ($item) { return $item->getName(); }, $items); $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?"
: '.';
throw new \LogicException("Call to undefined method $class::$name()$hint"); throw new \LogicException("Call to undefined method $class::$name()$hint");
} }
@@ -45,12 +42,9 @@ trait Strict
*/ */
public static function __callStatic(string $name, array $args) public static function __callStatic(string $name, array $args)
{ {
$rc = new ReflectionClass(static::class); $rc = new ReflectionClass(get_called_class());
$items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), function ($m) { return $m->isPublic(); }); $items = array_intersect($rc->getMethods(ReflectionMethod::IS_PUBLIC), $rc->getMethods(ReflectionMethod::IS_STATIC));
$items = array_map(function ($item) { return $item->getName(); }, $items); $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?"
: '.';
throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint"); throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
} }
@@ -68,11 +62,8 @@ trait Strict
return $ret; return $ret;
} }
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }); $items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
$items = array_map(function ($item) { return $item->getName(); }, $items); $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?"
: '.';
throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint"); throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
} }
@@ -84,11 +75,8 @@ trait Strict
public function __set(string $name, $value) public function __set(string $name, $value)
{ {
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }); $items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
$items = array_map(function ($item) { return $item->getName(); }, $items); $hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?"
: '.';
throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint"); throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
} }
@@ -105,7 +93,7 @@ trait Strict
*/ */
public function __unset(string $name) public function __unset(string $name)
{ {
$class = static::class; $class = get_class($this);
throw new \LogicException("Attempt to unset undeclared property $class::$$name."); throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
} }
} }

View File

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

View File

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

View File

@@ -25,7 +25,6 @@ declare(strict_types=1);
* @method static void begin(string $savepoint = null) * @method static void begin(string $savepoint = null)
* @method static void commit(string $savepoint = null) * @method static void commit(string $savepoint = null)
* @method static void rollback(string $savepoint = null) * @method static void rollback(string $savepoint = null)
* @method static mixed transaction(callable $callback)
* @method static Dibi\Reflection\Database getDatabaseInfo() * @method static Dibi\Reflection\Database getDatabaseInfo()
* @method static Dibi\Fluent command() * @method static Dibi\Fluent command()
* @method static Dibi\Fluent select(...$args) * @method static Dibi\Fluent select(...$args)
@@ -45,7 +44,7 @@ class dibi
/** version */ /** version */
public const public const
VERSION = '4.2.2'; VERSION = '4.1.3';
/** sorting order */ /** sorting order */
public const public const
@@ -76,7 +75,7 @@ class dibi
*/ */
final public function __construct() final public function __construct()
{ {
throw new LogicException('Cannot instantiate static class ' . static::class); throw new LogicException('Cannot instantiate static class ' . get_class($this));
} }

View File

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

View File

@@ -12,7 +12,7 @@ use Tester\Assert;
require __DIR__ . '/bootstrap.php'; require __DIR__ . '/bootstrap.php';
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config); $conn = new Connection($config);
Assert::true($conn->isConnected()); Assert::true($conn->isConnected());
@@ -21,7 +21,7 @@ test('', function () use ($config) {
}); });
test('lazy', function () use ($config) { test(function () use ($config) { // lazy
$conn = new Connection($config + ['lazy' => true]); $conn = new Connection($config + ['lazy' => true]);
Assert::false($conn->isConnected()); Assert::false($conn->isConnected());
@@ -30,7 +30,7 @@ test('lazy', function () use ($config) {
}); });
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config); $conn = new Connection($config);
Assert::true($conn->isConnected()); Assert::true($conn->isConnected());
@@ -40,7 +40,7 @@ test('', function () use ($config) {
}); });
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config); $conn = new Connection($config);
Assert::true($conn->isConnected()); Assert::true($conn->isConnected());
@@ -52,7 +52,7 @@ test('', function () use ($config) {
}); });
test('', function () use ($config) { test(function () use ($config) {
$conn = new Connection($config); $conn = new Connection($config);
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle()); Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
@@ -63,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

@@ -30,102 +30,21 @@ Assert::exception(function () use ($conn) {
*/ */
test('begin() & rollback()', function () use ($conn) { $conn->begin();
$conn->begin(); Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); $conn->query('INSERT INTO [products]', [
$conn->query('INSERT INTO [products]', [ 'title' => 'Test product',
'title' => 'Test product', ]);
]); Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); $conn->rollback();
$conn->rollback(); Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('begin() & commit()', function () use ($conn) {
$conn->begin();
$conn->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
$conn->commit();
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('transaction() fail', function () use ($conn) { $conn->begin();
Assert::exception(function () use ($conn) { $conn->query('INSERT INTO [products]', [
$conn->transaction(function (Dibi\Connection $connection) { 'title' => 'Test product',
$connection->query('INSERT INTO [products]', [ ]);
'title' => 'Test product', $conn->commit();
]); Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
throw new Exception('my exception');
});
}, \Throwable::class, 'my exception');
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('transaction() success', function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
});
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('nested transaction() call fail', function () use ($conn) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
$connection->transaction(function (Dibi\Connection $connection2) {
$connection2->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
throw new Exception('my exception');
});
});
}, \Throwable::class, 'my exception');
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('nested transaction() call success', function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
$connection->transaction(function (Dibi\Connection $connection2) {
$connection2->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
});
});
Assert::same(7, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->begin();
});
}, \LogicException::class, Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback');
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->commit();
});
}, \LogicException::class, Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback');
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->rollback();
});
}, \LogicException::class, Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback');
});

View File

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

View File

@@ -32,7 +32,7 @@ Assert::same(
); );
if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) { if (!in_array($config['driver'], ['sqlite', 'pdo', 'sqlsrv'], true)) {
Assert::same( Assert::same(
['products.product_id', 'orders.order_id', 'customers.name', 'xXx'], ['products.product_id', 'orders.order_id', 'customers.name', 'xXx'],
$info->getColumnNames(true) $info->getColumnNames(true)
@@ -43,7 +43,7 @@ if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true))
$columns = $info->getColumns(); $columns = $info->getColumns();
Assert::same('product_id', $columns[0]->getName()); Assert::same('product_id', $columns[0]->getName());
if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) { if (!in_array($config['driver'], ['sqlite', 'pdo', 'sqlsrv'], true)) {
Assert::same('products', $columns[0]->getTableName()); Assert::same('products', $columns[0]->getTableName());
} }
Assert::null($columns[0]->getVendorInfo('xxx')); Assert::null($columns[0]->getVendorInfo('xxx'));

View File

@@ -24,18 +24,7 @@ class MockResult extends Dibi\Result
} }
test('', function () { test(function () {
$result = new MockResult;
$result->setType('col', Type::TEXT);
$result->setFormat(Type::TEXT, 'native');
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true]));
Assert::same(['col' => false], $result->test(['col' => false]));
});
test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::BOOL); $result->setType('col', Type::BOOL);
@@ -57,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);
@@ -73,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);
@@ -150,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);
@@ -158,14 +147,7 @@ test('', function () {
Assert::same(['col' => 1], $result->test(['col' => true])); Assert::same(['col' => 1], $result->test(['col' => true]));
Assert::same(['col' => 0], $result->test(['col' => false])); Assert::same(['col' => 0], $result->test(['col' => false]));
if (PHP_VERSION_ID < 80000) { Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning in PHP 7.1
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
} else {
Assert::exception(function () use ($result) {
Assert::same(['col' => 0], $result->test(['col' => '']));
}, TypeError::class);
}
Assert::same(['col' => 0], $result->test(['col' => '0'])); Assert::same(['col' => 0], $result->test(['col' => '0']));
Assert::same(['col' => 1], $result->test(['col' => '1'])); Assert::same(['col' => 1], $result->test(['col' => '1']));
Assert::same(['col' => 10], $result->test(['col' => '10'])); Assert::same(['col' => 10], $result->test(['col' => '10']));
@@ -183,7 +165,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DATETIME);
@@ -201,7 +183,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DATETIME);
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s'); $result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
@@ -220,7 +202,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATE); $result->setType('col', Type::DATE);
@@ -236,7 +218,7 @@ test('', function () {
}); });
test('', function () { test(function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TIME); $result->setType('col', Type::TIME);

View File

@@ -35,10 +35,6 @@ Assert::error(function () use ($row) {
Assert::false(isset($row->missing)); Assert::false(isset($row->missing));
Assert::false(isset($row['missing'])); Assert::false(isset($row['missing']));
// ??
Assert::same(123, $row->missing ?? 123);
Assert::same(123, $row['missing'] ?? 123);
// suggestions // suggestions
Assert::error(function () use ($row) { Assert::error(function () use ($row) {

View File

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

View File

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

View File

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

View File

@@ -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,6 +437,7 @@ WHERE ([test].[a] LIKE '1995-03-01'
OR [str_not_null]='hello' OR [str_not_null]='hello'
LIMIT 10", LIMIT 10",
]), ]),
$conn->translate('SELECT * $conn->translate('SELECT *
FROM [db.table] FROM [db.table]
WHERE ([test.a] LIKE %d', '1995-03-01', ' WHERE ([test.a] LIKE %d', '1995-03-01', '
@@ -670,6 +671,7 @@ 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(string $title, Closure $function): void function test(Closure $function)
{ {
$function(); $function();
} }

View File

@@ -12,5 +12,5 @@ extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll extension=php_pdo_sqlite.dll
extension=php_pgsql.dll extension=php_pgsql.dll
extension=php_sqlite3.dll extension=php_sqlite3.dll
extension=php_sqlsrv_ts.dll extension=php_sqlsrv_71_ts.dll
extension=php_odbc.dll extension=php_odbc.dll

View File

@@ -1,9 +1,4 @@
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#'