mirror of
https://github.com/dg/dibi.git
synced 2025-08-30 09:19:48 +02:00
Compare commits
47 Commits
fetchsingl
...
v4.1.1
Author | SHA1 | Date | |
---|---|---|---|
|
9d4bef53d3 | ||
|
1db63d81e9 | ||
|
c7dee4d822 | ||
|
f2927a1b08 | ||
|
b5a66fdb26 | ||
|
c38f6991b0 | ||
|
faab306418 | ||
|
74ba6cfd34 | ||
|
f46b7f4d79 | ||
|
c1640c5e7b | ||
|
a2afac80f2 | ||
|
0535d57e6b | ||
|
369768a62a | ||
|
78d6603bb0 | ||
|
7f22279333 | ||
|
e66cb84cb5 | ||
|
5ab8afc704 | ||
|
219882a962 | ||
|
7e127f5914 | ||
|
79f841ec90 | ||
|
69eaa71fde | ||
|
f895493016 | ||
|
19172c801e | ||
|
2b5683d0f2 | ||
|
76593b7da4 | ||
|
dd2fd654be | ||
|
12cbbb3140 | ||
|
811974139e | ||
|
7a49609468 | ||
|
89987f0cee | ||
|
b7e467ecac | ||
|
fe0e7510af | ||
|
eaf2494d90 | ||
|
168971292d | ||
|
95c424a71d | ||
|
4b85f0a973 | ||
|
e7539102cb | ||
|
0ad2dd70bc | ||
|
4abe874ce9 | ||
|
15df96bb22 | ||
|
2870fb9b31 | ||
|
c8dfb1f863 | ||
|
9840c31995 | ||
|
25fda3f8f1 | ||
|
38128fbf9e | ||
|
73790f4321 | ||
|
3930dafe3f |
4
.github/ISSUE_TEMPLATE/Support_us.md
vendored
4
.github/ISSUE_TEMPLATE/Support_us.md
vendored
@@ -6,14 +6,12 @@ about: "If you would like to support our efforts in maintaining this project
|
||||
|
||||
--------------^ Click "Preview" for a nicer view!
|
||||
|
||||
> https://nette.org/donate
|
||||
|
||||
Help support Dibi!
|
||||
|
||||
We develop Dibi for more than 14 years. In order to make your life more comfortable. Dibi cares about the safety of your sites. Dibi saves you time. Dibi earns you money. And is absolutely free.
|
||||
|
||||
To ensure future development and improving the documentation, we need your donation.
|
||||
|
||||
[Please make a donation now](https://nette.org/donate).
|
||||
**[Please make a donation now](https://nette.org/make-donation?to=dibi)**.
|
||||
|
||||
Thank you!
|
||||
|
35
.travis.yml
35
.travis.yml
@@ -2,6 +2,12 @@ language: php
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4
|
||||
|
||||
services:
|
||||
- mysql
|
||||
- postgresql
|
||||
|
||||
before_install:
|
||||
# turn off XDebug
|
||||
@@ -25,16 +31,27 @@ after_failure:
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Code Standard Checker
|
||||
php: 7.1
|
||||
- name: Nette Code Checker
|
||||
install:
|
||||
# Install Nette Code Checker
|
||||
- travis_retry composer create-project nette/code-checker temp/code-checker ~2 --no-progress
|
||||
# Install Nette Coding Standard
|
||||
- travis_retry composer create-project nette/coding-standard temp/coding-standard --no-progress
|
||||
- travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress
|
||||
script:
|
||||
- php temp/code-checker/src/code-checker.php --short-arrays --strict-types
|
||||
- php temp/coding-standard/ecs check src tests examples --config tests/coding-standard.neon
|
||||
- php temp/code-checker/code-checker --strict-types
|
||||
|
||||
|
||||
- name: Nette Coding Standard
|
||||
install:
|
||||
- travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress
|
||||
script:
|
||||
- php temp/coding-standard/ecs check src tests examples --config tests/coding-standard.yml
|
||||
|
||||
|
||||
- stage: Static Analysis (informative)
|
||||
install:
|
||||
# Install PHPStan
|
||||
- travis_retry composer create-project phpstan/phpstan-shim temp/phpstan --no-progress
|
||||
- travis_retry composer install --no-progress --prefer-dist
|
||||
script:
|
||||
- php temp/phpstan/phpstan.phar analyse --autoload-file vendor/autoload.php --level 5 src
|
||||
|
||||
|
||||
- stage: Code Coverage
|
||||
@@ -46,7 +63,7 @@ jobs:
|
||||
|
||||
|
||||
allow_failures:
|
||||
- php: 7.2
|
||||
- stage: Static Analysis (informative)
|
||||
- stage: Code Coverage
|
||||
|
||||
|
||||
|
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.0-dev"
|
||||
"dev-master": "4.1-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
||||
echo '<p>Connecting to Sqlite: ';
|
||||
try {
|
||||
dibi::connect([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
echo 'OK';
|
||||
@@ -30,7 +30,7 @@ echo "</p>\n";
|
||||
echo '<p>Connecting to Sqlite: ';
|
||||
try {
|
||||
$connection = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
echo 'OK';
|
||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -15,7 +15,7 @@ Tracy\Debugger::enable();
|
||||
<?php
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague');
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -19,7 +19,7 @@ date_default_timezone_set('Europe/Prague');
|
||||
<?php
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -11,7 +11,7 @@ Tracy\Debugger::enable();
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -11,7 +11,7 @@ Tracy\Debugger::enable();
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -16,7 +16,7 @@ date_default_timezone_set('Europe/Prague');
|
||||
|
||||
// CHANGE TO REAL PARAMETERS!
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
'formatDate' => "'Y-m-d'",
|
||||
'formatDateTime' => "'Y-m-d H-i-s'",
|
||||
|
@@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague');
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague');
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
// enable query logging to this file
|
||||
'profiler' => [
|
||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
|
||||
|
||||
|
||||
$dibi = new Dibi\Connection([
|
||||
'driver' => 'sqlite3',
|
||||
'driver' => 'sqlite',
|
||||
'database' => 'data/sample.s3db',
|
||||
]);
|
||||
|
||||
|
11
readme.md
11
readme.md
@@ -1,4 +1,4 @@
|
||||
[Dibi](https://dibiphp.com) - smart database layer for PHP [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9XXL5ZJHAYQUN)
|
||||
[Dibi](https://dibiphp.com) - smart database layer for PHP [](https://nette.org/make-donation?to=dibi)
|
||||
=========================================================
|
||||
|
||||
[](https://packagist.org/packages/dibi/dibi)
|
||||
@@ -14,6 +14,8 @@ Introduction
|
||||
Database access functions in PHP are not standardised. This library
|
||||
hides the differences between them, and above all, it gives you a very handy interface.
|
||||
|
||||
If you like Dibi, **[please make a donation now](https://nette.org/make-donation?to=dibi)**. Thank you!
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -24,7 +26,7 @@ Install Dibi via Composer:
|
||||
composer require dibi/dibi
|
||||
```
|
||||
|
||||
The Dibi 4.0 requires PHP version 7.1 and supports PHP up to 7.2. Older Dibi 3.x requires PHP 5.4 and supports PHP up to 7.2.
|
||||
The Dibi 4.1 requires PHP version 7.1 and supports PHP up to 7.4.
|
||||
|
||||
|
||||
Usage
|
||||
@@ -108,7 +110,7 @@ $ids = [10, 20, 30];
|
||||
$result = $database->query('SELECT * FROM users WHERE id IN (?)', $ids);
|
||||
```
|
||||
|
||||
**WARNING, never concencate parameters to SQL, the vulnerability would arise [SQL injection](https://en.wikipedia.org/wiki/SQL_injection)**
|
||||
**WARNING: Never concatenate parameters to SQL. It would create a [SQL injection](https://en.wikipedia.org/wiki/SQL_injection)** vulnerability.
|
||||
```
|
||||
$result = $database->query('SELECT * FROM users WHERE id = ' . $id); // BAD!!!
|
||||
```
|
||||
@@ -145,7 +147,7 @@ $name = $database->fetchSingle('SELECT name FROM users WHERE id = ?', $id);
|
||||
|
||||
### Modifiers
|
||||
|
||||
In addition to the `?` wild char, we can also use modifiers:
|
||||
In addition to the `?` wildcard char, we can also use modifiers:
|
||||
|
||||
| modifier | description
|
||||
|----------|-----
|
||||
@@ -159,6 +161,7 @@ In addition to the `?` wild char, we can also use modifiers:
|
||||
| %d | date (accepts DateTime, string or UNIX timestamp)
|
||||
| %dt | datetime (accepts DateTime, string or UNIX timestamp)
|
||||
| %n | identifier, ie the name of the table or column
|
||||
| %N | identifier, treats period as a common character, ie alias or a database name (`%n AS %N` or `DROP DATABASE %N`)
|
||||
| %SQL | SQL - directly inserts into SQL (the alternative is Dibi\Literal)
|
||||
| %ex | SQL expression or array of expressions
|
||||
| %lmt | special - adds LIMIT to the query
|
||||
|
@@ -51,7 +51,7 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
|
||||
}
|
||||
|
||||
$connection = $container->addDefinition($this->prefix('connection'))
|
||||
->setClass(Dibi\Connection::class, [$config])
|
||||
->setFactory(Dibi\Connection::class, [$config])
|
||||
->setAutowired($config['autowired'] ?? true);
|
||||
|
||||
if (class_exists(Tracy\Debugger::class)) {
|
||||
@@ -62,7 +62,7 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
|
||||
}
|
||||
if ($useProfiler) {
|
||||
$panel = $container->addDefinition($this->prefix('panel'))
|
||||
->setClass(Dibi\Bridges\Tracy\Panel::class, [
|
||||
->setFactory(Dibi\Bridges\Tracy\Panel::class, [
|
||||
$config['explain'] ?? true,
|
||||
isset($config['filter']) && $config['filter'] === false ? Dibi\Event::ALL : Dibi\Event::QUERY,
|
||||
]);
|
||||
|
@@ -43,31 +43,20 @@ class Connection implements IConnection
|
||||
* - lazy (bool) => if true, connection will be established only when required
|
||||
* - result (array) => result set options
|
||||
* - formatDateTime => date-time format (if empty, DateTime objects will be returned)
|
||||
* - formatJson => json format (
|
||||
* "string" for leaving value as is,
|
||||
* "object" for decoding json as \stdClass,
|
||||
* "array" for decoding json as an array - default
|
||||
* )
|
||||
* - profiler (array)
|
||||
* - run (bool) => enable profiler?
|
||||
* - file => file to log
|
||||
* - substitutes (array) => map of driver specific substitutes (under development)
|
||||
* @param array $config connection parameters
|
||||
* - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($config, string $name = null)
|
||||
public function __construct(array $config, string $name = null)
|
||||
{
|
||||
if (is_string($config)) {
|
||||
trigger_error(__METHOD__ . '() Configuration should be array.', E_USER_DEPRECATED);
|
||||
parse_str($config, $config);
|
||||
|
||||
} elseif ($config instanceof Traversable) {
|
||||
trigger_error(__METHOD__ . '() Configuration should be array.', E_USER_DEPRECATED);
|
||||
$tmp = [];
|
||||
foreach ($config as $key => $val) {
|
||||
$tmp[$key] = $val instanceof Traversable ? iterator_to_array($val) : $val;
|
||||
}
|
||||
$config = $tmp;
|
||||
|
||||
} elseif (!is_array($config)) {
|
||||
throw new \InvalidArgumentException('Configuration must be array.');
|
||||
}
|
||||
|
||||
Helpers::alias($config, 'username', 'user');
|
||||
Helpers::alias($config, 'password', 'pass');
|
||||
Helpers::alias($config, 'host', 'hostname');
|
||||
@@ -75,6 +64,7 @@ class Connection implements IConnection
|
||||
Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
|
||||
$config['driver'] = $config['driver'] ?? 'mysqli';
|
||||
$config['name'] = $name;
|
||||
$config['result']['formatJson'] = $config['result']['formatJson'] ?? 'array';
|
||||
$this->config = $config;
|
||||
|
||||
// profiler
|
||||
@@ -90,6 +80,10 @@ class Connection implements IConnection
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['onConnect']) && !is_array($config['onConnect'])) {
|
||||
throw new \InvalidArgumentException("Configuration option 'onConnect' must be array.");
|
||||
}
|
||||
|
||||
if (empty($config['lazy'])) {
|
||||
$this->connect();
|
||||
}
|
||||
@@ -112,8 +106,13 @@ class Connection implements IConnection
|
||||
*/
|
||||
final public function connect(): void
|
||||
{
|
||||
if (is_subclass_of($this->config['driver'], Driver::class)) {
|
||||
if ($this->config['driver'] instanceof Driver) {
|
||||
$this->driver = $this->config['driver'];
|
||||
return;
|
||||
|
||||
} elseif (is_subclass_of($this->config['driver'], Driver::class)) {
|
||||
$class = $this->config['driver'];
|
||||
|
||||
} else {
|
||||
$class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($this->config['driver'])));
|
||||
$class = "Dibi\\Drivers\\{$class}Driver";
|
||||
@@ -128,6 +127,11 @@ class Connection implements IConnection
|
||||
if ($event) {
|
||||
$this->onEvent($event->done());
|
||||
}
|
||||
if (isset($this->config['onConnect'])) {
|
||||
foreach ($this->config['onConnect'] as $sql) {
|
||||
$this->query($sql);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (DriverException $e) {
|
||||
if ($event) {
|
||||
@@ -301,16 +305,6 @@ class Connection implements IConnection
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function affectedRows(): int
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
|
||||
return $this->getAffectedRows();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
||||
* @throws Exception
|
||||
@@ -321,23 +315,13 @@ class Connection implements IConnection
|
||||
$this->connect();
|
||||
}
|
||||
$id = $this->driver->getInsertId($sequence);
|
||||
if ($id < 1) {
|
||||
if ($id === null) {
|
||||
throw new Exception('Cannot retrieve last generated ID.');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function insertId(string $sequence = null): int
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
|
||||
return $this->getInsertId($sequence);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins a transaction (if supported).
|
||||
*/
|
||||
@@ -417,7 +401,8 @@ class Connection implements IConnection
|
||||
{
|
||||
$res = new Result($resultDriver);
|
||||
return $res->setFormat(Type::DATE, $this->config['result']['formatDate'])
|
||||
->setFormat(Type::DATETIME, $this->config['result']['formatDateTime']);
|
||||
->setFormat(Type::DATETIME, $this->config['result']['formatDateTime'])
|
||||
->setFormat(Type::JSON, $this->config['result']['formatJson']);
|
||||
}
|
||||
|
||||
|
||||
@@ -436,7 +421,10 @@ class Connection implements IConnection
|
||||
}
|
||||
|
||||
|
||||
public function update(string $table, iterable $args): Fluent
|
||||
/**
|
||||
* @param string|string[] $table
|
||||
*/
|
||||
public function update($table, iterable $args): Fluent
|
||||
{
|
||||
return $this->command()->update('%n', $table)->set($args);
|
||||
}
|
||||
|
@@ -161,7 +161,7 @@ class DataSource implements IDataSource
|
||||
|
||||
/**
|
||||
* Like fetch(), but returns only first field.
|
||||
* @return mixed value on success, false if no next record
|
||||
* @return mixed value on success, null if no next record
|
||||
*/
|
||||
public function fetchSingle()
|
||||
{
|
||||
|
@@ -32,77 +32,8 @@ class DateTime extends \DateTimeImmutable
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use modify() */
|
||||
public function modifyClone(string $modify = ''): self
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use modify()', E_USER_DEPRECATED);
|
||||
$dolly = clone $this;
|
||||
return $modify ? $dolly->modify($modify) : $dolly;
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->format('Y-m-d H:i:s.u');
|
||||
}
|
||||
|
||||
|
||||
/********************* immutable usage detector ****************d*g**/
|
||||
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
if (isset($trace[0]['file'], $trace[1]['function']) && $trace[0]['file'] === __FILE__ && $trace[1]['function'] !== '__construct') {
|
||||
trigger_error(__CLASS__ . ' is immutable now, check how it is used in ' . $trace[1]['file'] . ':' . $trace[1]['line'], E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function add($interval)
|
||||
{
|
||||
return parent::add($interval);
|
||||
}
|
||||
|
||||
|
||||
public function modify($modify)
|
||||
{
|
||||
return parent::modify($modify);
|
||||
}
|
||||
|
||||
|
||||
public function setDate($year, $month, $day)
|
||||
{
|
||||
return parent::setDate($year, $month, $day);
|
||||
}
|
||||
|
||||
|
||||
public function setISODate($year, $week, $day = 1)
|
||||
{
|
||||
return parent::setISODate($year, $week, $day);
|
||||
}
|
||||
|
||||
|
||||
public function setTime($hour, $minute, $second = 0, $micro = 0)
|
||||
{
|
||||
return parent::setTime($hour, $minute, $second, $micro);
|
||||
}
|
||||
|
||||
|
||||
public function setTimestamp($unixtimestamp)
|
||||
{
|
||||
return parent::setTimestamp($unixtimestamp);
|
||||
}
|
||||
|
||||
|
||||
public function setTimezone($timezone)
|
||||
{
|
||||
return parent::setTimezone($timezone);
|
||||
}
|
||||
|
||||
|
||||
public function sub($interval)
|
||||
{
|
||||
return parent::sub($interval);
|
||||
}
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ class FirebirdDriver implements Dibi\Driver
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array &$config)
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('interbase')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'interbase' is not loaded.");
|
||||
@@ -247,36 +247,31 @@ class FirebirdDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
$value = addcslashes($this->escapeText($value), '%_\\');
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
|
@@ -50,7 +50,7 @@ class MySqliDriver implements Dibi\Driver
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array &$config)
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('mysqli')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'mysqli' is not loaded.");
|
||||
@@ -199,7 +199,7 @@ class MySqliDriver implements Dibi\Driver
|
||||
*/
|
||||
public function getInsertId(?string $sequence): ?int
|
||||
{
|
||||
return $this->connection->insert_id;
|
||||
return $this->connection->insert_id ?: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -290,30 +290,27 @@ class MySqliDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d H:i:s.u'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
if ($value->y || $value->m || $value->d) {
|
||||
throw new Dibi\NotSupportedException('Only time interval is supported.');
|
||||
}
|
||||
return $value->format('%r%H:%I:%S.%f');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
@@ -334,7 +331,7 @@ class MySqliDriver implements Dibi\Driver
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : $limit)
|
||||
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ use Dibi;
|
||||
* - password (or pass)
|
||||
* - persistent (bool) => try to find a persistent link?
|
||||
* - resource (resource) => existing connection resource
|
||||
* - microseconds (bool) => use microseconds in datetime format?
|
||||
*/
|
||||
class OdbcDriver implements Dibi\Driver
|
||||
{
|
||||
@@ -32,11 +33,14 @@ class OdbcDriver implements Dibi\Driver
|
||||
/** @var int|null Affected rows */
|
||||
private $affectedRows;
|
||||
|
||||
/** @var bool */
|
||||
private $microseconds = true;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array &$config)
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('odbc')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'odbc' is not loaded.");
|
||||
@@ -62,6 +66,10 @@ class OdbcDriver implements Dibi\Driver
|
||||
if (!is_resource($this->connection)) {
|
||||
throw new Dibi\DriverException(odbc_errormsg() . ' ' . odbc_error());
|
||||
}
|
||||
|
||||
if (isset($config['microseconds'])) {
|
||||
$this->microseconds = (bool) $config['microseconds'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -218,27 +226,21 @@ class OdbcDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format('#m/d/Y#');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format('#m/d/Y H:i:s.u#');
|
||||
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#');
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -45,17 +45,13 @@ class OracleDriver implements Dibi\Driver
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array &$config)
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('oci8')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'oci8' is not loaded.");
|
||||
}
|
||||
|
||||
$foo = &$config['charset'];
|
||||
|
||||
if (isset($config['formatDate']) || isset($config['formatDateTime'])) {
|
||||
trigger_error('OracleDriver: options formatDate and formatDateTime are deprecated.', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->nativeDate = $config['nativeDate'] ?? true;
|
||||
|
||||
if (isset($config['resource'])) {
|
||||
@@ -245,34 +241,28 @@ class OracleDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $this->nativeDate
|
||||
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
|
||||
: $value->format('U');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $this->nativeDate
|
||||
? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')"
|
||||
: $value->format('U');
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
|
@@ -96,6 +96,7 @@ class OracleResult implements Dibi\ResultDriver
|
||||
'name' => oci_field_name($this->resultSet, $i),
|
||||
'table' => null,
|
||||
'fullname' => oci_field_name($this->resultSet, $i),
|
||||
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null,
|
||||
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
|
||||
];
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ class PdoDriver implements Dibi\Driver
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array &$config)
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('pdo')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'pdo' is not loaded.");
|
||||
@@ -69,6 +69,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->serverVersion = (string) ($config['version'] ?? @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)); // @ - may be not supported
|
||||
}
|
||||
@@ -110,7 +114,7 @@ class PdoDriver implements Dibi\Driver
|
||||
throw PostgreDriver::createException($message, $sqlState, $sql);
|
||||
|
||||
case 'sqlite':
|
||||
throw Sqlite3Driver::createException($message, $code, $sql);
|
||||
throw SqliteDriver::createException($message, $code, $sql);
|
||||
|
||||
default:
|
||||
throw new Dibi\DriverException($message, $code, $sql);
|
||||
@@ -132,7 +136,7 @@ class PdoDriver implements Dibi\Driver
|
||||
*/
|
||||
public function getInsertId(?string $sequence): ?int
|
||||
{
|
||||
return Helpers::false2Null($this->connection->lastInsertId());
|
||||
return Helpers::intVal($this->connection->lastInsertId($sequence));
|
||||
}
|
||||
|
||||
|
||||
@@ -285,26 +289,14 @@ class PdoDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
switch ($this->driverName) {
|
||||
case 'odbc':
|
||||
return $value->format('#m/d/Y H:i:s.u#');
|
||||
@@ -318,6 +310,12 @@ class PdoDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
@@ -369,7 +367,7 @@ class PdoDriver implements Dibi\Driver
|
||||
case 'mysql':
|
||||
if ($limit !== null || $offset) {
|
||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : $limit)
|
||||
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
break;
|
||||
@@ -385,7 +383,7 @@ class PdoDriver implements Dibi\Driver
|
||||
|
||||
case 'sqlite':
|
||||
if ($limit !== null || $offset) {
|
||||
$sql .= ' LIMIT ' . ($limit === null ? '-1' : $limit)
|
||||
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
break;
|
||||
|
@@ -38,7 +38,7 @@ class PostgreDriver implements Dibi\Driver
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array &$config)
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('pgsql')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'pgsql' is not loaded.");
|
||||
@@ -292,30 +292,24 @@ class PostgreDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d H:i:s.u'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
|
@@ -130,7 +130,7 @@ class PostgreReflector implements Dibi\Reflector
|
||||
'size' => $size > 0 ? $size : null,
|
||||
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
|
||||
'default' => $row['column_default'],
|
||||
'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'], 0, 7) === 'nextval',
|
||||
'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'] ?? '', 0, 7) === 'nextval',
|
||||
'vendor' => $row,
|
||||
];
|
||||
}
|
||||
@@ -176,8 +176,11 @@ class PostgreReflector implements Dibi\Reflector
|
||||
$indexes[$row['relname']]['name'] = $row['relname'];
|
||||
$indexes[$row['relname']]['unique'] = $row['indisunique'] === 't' || $row['indisunique'] === true;
|
||||
$indexes[$row['relname']]['primary'] = $row['indisprimary'] === 't' || $row['indisprimary'] === true;
|
||||
$indexes[$row['relname']]['columns'] = [];
|
||||
foreach (explode(' ', $row['indkey']) as $index) {
|
||||
$indexes[$row['relname']]['columns'][] = $columns[$index];
|
||||
if (isset($columns[$index])) {
|
||||
$indexes[$row['relname']]['columns'][] = $columns[$index];
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_values($indexes);
|
||||
|
@@ -9,293 +9,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Helpers;
|
||||
use SQLite3;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for SQLite3 database.
|
||||
*
|
||||
* Driver options:
|
||||
* - database (or file) => the filename of the SQLite3 database
|
||||
* - formatDate => how to format date in SQL (@see date)
|
||||
* - formatDateTime => how to format datetime in SQL (@see date)
|
||||
* - resource (SQLite3) => existing connection resource
|
||||
* Alias for SqliteDriver driver.
|
||||
*/
|
||||
class Sqlite3Driver implements Dibi\Driver
|
||||
class Sqlite3Driver extends SqliteDriver
|
||||
{
|
||||
use Dibi\Strict;
|
||||
|
||||
/** @var SQLite3 */
|
||||
private $connection;
|
||||
|
||||
/** @var string Date format */
|
||||
private $fmtDate;
|
||||
|
||||
/** @var string Datetime format */
|
||||
private $fmtDateTime;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array &$config)
|
||||
{
|
||||
if (!extension_loaded('sqlite3')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'sqlite3' is not loaded.");
|
||||
}
|
||||
|
||||
if (isset($config['dbcharset']) || isset($config['charset'])) {
|
||||
throw new Dibi\NotSupportedException('Options dbcharset and charset are not longer supported.');
|
||||
}
|
||||
|
||||
Helpers::alias($config, 'database', 'file');
|
||||
$this->fmtDate = $config['formatDate'] ?? 'U';
|
||||
$this->fmtDateTime = $config['formatDateTime'] ?? 'U';
|
||||
|
||||
if (isset($config['resource']) && $config['resource'] instanceof SQLite3) {
|
||||
$this->connection = $config['resource'];
|
||||
} else {
|
||||
try {
|
||||
$this->connection = new SQLite3($config['database']);
|
||||
} catch (\Exception $e) {
|
||||
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
|
||||
$version = SQLite3::version();
|
||||
if ($version['versionNumber'] >= '3006019') {
|
||||
$this->query('PRAGMA foreign_keys = ON');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects from a database.
|
||||
*/
|
||||
public function disconnect(): void
|
||||
{
|
||||
$this->connection->close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the SQL query.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function query(string $sql): ?Dibi\ResultDriver
|
||||
{
|
||||
$res = @$this->connection->query($sql); // intentionally @
|
||||
if ($code = $this->connection->lastErrorCode()) {
|
||||
throw static::createException($this->connection->lastErrorMsg(), $code, $sql);
|
||||
|
||||
} elseif ($res instanceof \SQLite3Result && $res->numColumns()) {
|
||||
return $this->createResultDriver($res);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static function createException(string $message, $code, string $sql): Dibi\DriverException
|
||||
{
|
||||
if ($code !== 19) {
|
||||
return new Dibi\DriverException($message, $code, $sql);
|
||||
|
||||
} elseif (strpos($message, 'must be unique') !== false
|
||||
|| strpos($message, 'is not unique') !== false
|
||||
|| strpos($message, 'UNIQUE constraint failed') !== false
|
||||
) {
|
||||
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
|
||||
|
||||
} elseif (strpos($message, 'may not be null') !== false
|
||||
|| strpos($message, 'NOT NULL constraint failed') !== false
|
||||
) {
|
||||
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
|
||||
|
||||
} elseif (strpos($message, 'foreign key constraint failed') !== false
|
||||
|| strpos($message, 'FOREIGN KEY constraint failed') !== false
|
||||
) {
|
||||
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
|
||||
|
||||
} else {
|
||||
return new Dibi\ConstraintViolationException($message, $code, $sql);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
|
||||
*/
|
||||
public function getAffectedRows(): ?int
|
||||
{
|
||||
return $this->connection->changes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
||||
*/
|
||||
public function getInsertId(?string $sequence): ?int
|
||||
{
|
||||
return $this->connection->lastInsertRowID();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins a transaction (if supported).
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function begin(string $savepoint = null): void
|
||||
{
|
||||
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Commits statements in a transaction.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function commit(string $savepoint = null): void
|
||||
{
|
||||
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rollback changes in a transaction.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function rollback(string $savepoint = null): void
|
||||
{
|
||||
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection resource.
|
||||
*/
|
||||
public function getResource(): ?SQLite3
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection reflector.
|
||||
*/
|
||||
public function getReflector(): Dibi\Reflector
|
||||
{
|
||||
return new SqliteReflector($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Result set driver factory.
|
||||
*/
|
||||
public function createResultDriver(\SQLite3Result $result): Sqlite3Result
|
||||
{
|
||||
return new Sqlite3Result($result);
|
||||
}
|
||||
|
||||
|
||||
/********************* SQL ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Encodes data for use in a SQL statement.
|
||||
*/
|
||||
public function escapeText(string $value): string
|
||||
{
|
||||
return "'" . $this->connection->escapeString($value) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeBinary(string $value): string
|
||||
{
|
||||
return "X'" . bin2hex($value) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
return '[' . strtr($value, '[]', ' ') . ']';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format($this->fmtDate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format($this->fmtDateTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes($this->connection->escapeString($value), '%_\\');
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
$sql .= ' LIMIT ' . ($limit === null ? '-1' : $limit)
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************* user defined functions ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Registers an user defined function for use in SQL statements.
|
||||
*/
|
||||
public function registerFunction(string $name, callable $callback, int $numArgs = -1): void
|
||||
{
|
||||
$this->connection->createFunction($name, $callback, $numArgs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers an aggregating user defined function for use in SQL statements.
|
||||
*/
|
||||
public function registerAggregateFunction(string $name, callable $rowCallback, callable $agrCallback, int $numArgs = -1): void
|
||||
{
|
||||
$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
|
||||
}
|
||||
}
|
||||
|
@@ -9,115 +9,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Helpers;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for SQLite3 result set.
|
||||
* Alias for SqliteResult driver.
|
||||
*/
|
||||
class Sqlite3Result implements Dibi\ResultDriver
|
||||
class Sqlite3Result extends SqliteResult
|
||||
{
|
||||
use Dibi\Strict;
|
||||
|
||||
/** @var \SQLite3Result */
|
||||
private $resultSet;
|
||||
|
||||
/** @var bool */
|
||||
private $autoFree = true;
|
||||
|
||||
|
||||
public function __construct(\SQLite3Result $resultSet)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Automatically frees the resources allocated for this result set.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->autoFree && $this->getResultResource()) {
|
||||
@$this->free();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of rows in a result set.
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function getRowCount(): int
|
||||
{
|
||||
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the row at current position and moves the internal cursor to the next position.
|
||||
* @param bool $assoc true for associative array, false for numeric
|
||||
*/
|
||||
public function fetch(bool $assoc): ?array
|
||||
{
|
||||
return Helpers::false2Null($this->resultSet->fetchArray($assoc ? SQLITE3_ASSOC : SQLITE3_NUM));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves cursor position without fetching row.
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function seek(int $row): bool
|
||||
{
|
||||
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Frees the resources allocated for this result set.
|
||||
*/
|
||||
public function free(): void
|
||||
{
|
||||
$this->resultSet->finalize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns metadata for all columns in a result set.
|
||||
*/
|
||||
public function getResultColumns(): array
|
||||
{
|
||||
$count = $this->resultSet->numColumns();
|
||||
$columns = [];
|
||||
static $types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null'];
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$columns[] = [
|
||||
'name' => $this->resultSet->columnName($i),
|
||||
'table' => null,
|
||||
'fullname' => $this->resultSet->columnName($i),
|
||||
'nativetype' => $types[$this->resultSet->columnType($i)],
|
||||
];
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the result set resource.
|
||||
*/
|
||||
public function getResultResource(): \SQLite3Result
|
||||
{
|
||||
$this->autoFree = false;
|
||||
return $this->resultSet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes data from result set.
|
||||
*/
|
||||
public function unescapeBinary(string $value): string
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
295
src/Dibi/Drivers/SqliteDriver.php
Normal file
295
src/Dibi/Drivers/SqliteDriver.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
|
||||
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Helpers;
|
||||
use SQLite3;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for SQLite v3 database.
|
||||
*
|
||||
* Driver options:
|
||||
* - database (or file) => the filename of the SQLite3 database
|
||||
* - formatDate => how to format date in SQL (@see date)
|
||||
* - formatDateTime => how to format datetime in SQL (@see date)
|
||||
* - resource (SQLite3) => existing connection resource
|
||||
*/
|
||||
class SqliteDriver implements Dibi\Driver
|
||||
{
|
||||
use Dibi\Strict;
|
||||
|
||||
/** @var SQLite3 */
|
||||
private $connection;
|
||||
|
||||
/** @var string Date format */
|
||||
private $fmtDate;
|
||||
|
||||
/** @var string Datetime format */
|
||||
private $fmtDateTime;
|
||||
|
||||
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('sqlite3')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'sqlite3' is not loaded.");
|
||||
}
|
||||
|
||||
if (isset($config['dbcharset']) || isset($config['charset'])) {
|
||||
throw new Dibi\NotSupportedException('Options dbcharset and charset are not longer supported.');
|
||||
}
|
||||
|
||||
Helpers::alias($config, 'database', 'file');
|
||||
$this->fmtDate = $config['formatDate'] ?? 'U';
|
||||
$this->fmtDateTime = $config['formatDateTime'] ?? 'U';
|
||||
|
||||
if (isset($config['resource']) && $config['resource'] instanceof SQLite3) {
|
||||
$this->connection = $config['resource'];
|
||||
} else {
|
||||
try {
|
||||
$this->connection = new SQLite3($config['database']);
|
||||
} catch (\Exception $e) {
|
||||
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
|
||||
$version = SQLite3::version();
|
||||
if ($version['versionNumber'] >= '3006019') {
|
||||
$this->query('PRAGMA foreign_keys = ON');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects from a database.
|
||||
*/
|
||||
public function disconnect(): void
|
||||
{
|
||||
$this->connection->close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the SQL query.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function query(string $sql): ?Dibi\ResultDriver
|
||||
{
|
||||
$res = @$this->connection->query($sql); // intentionally @
|
||||
if ($code = $this->connection->lastErrorCode()) {
|
||||
throw static::createException($this->connection->lastErrorMsg(), $code, $sql);
|
||||
|
||||
} elseif ($res instanceof \SQLite3Result && $res->numColumns()) {
|
||||
return $this->createResultDriver($res);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static function createException(string $message, $code, string $sql): Dibi\DriverException
|
||||
{
|
||||
if ($code !== 19) {
|
||||
return new Dibi\DriverException($message, $code, $sql);
|
||||
|
||||
} elseif (strpos($message, 'must be unique') !== false
|
||||
|| strpos($message, 'is not unique') !== false
|
||||
|| strpos($message, 'UNIQUE constraint failed') !== false
|
||||
) {
|
||||
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
|
||||
|
||||
} elseif (strpos($message, 'may not be null') !== false
|
||||
|| strpos($message, 'NOT NULL constraint failed') !== false
|
||||
) {
|
||||
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
|
||||
|
||||
} elseif (strpos($message, 'foreign key constraint failed') !== false
|
||||
|| strpos($message, 'FOREIGN KEY constraint failed') !== false
|
||||
) {
|
||||
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
|
||||
|
||||
} else {
|
||||
return new Dibi\ConstraintViolationException($message, $code, $sql);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
|
||||
*/
|
||||
public function getAffectedRows(): ?int
|
||||
{
|
||||
return $this->connection->changes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
||||
*/
|
||||
public function getInsertId(?string $sequence): ?int
|
||||
{
|
||||
return $this->connection->lastInsertRowID() ?: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins a transaction (if supported).
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function begin(string $savepoint = null): void
|
||||
{
|
||||
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Commits statements in a transaction.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function commit(string $savepoint = null): void
|
||||
{
|
||||
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rollback changes in a transaction.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function rollback(string $savepoint = null): void
|
||||
{
|
||||
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection resource.
|
||||
*/
|
||||
public function getResource(): ?SQLite3
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection reflector.
|
||||
*/
|
||||
public function getReflector(): Dibi\Reflector
|
||||
{
|
||||
return new SqliteReflector($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Result set driver factory.
|
||||
*/
|
||||
public function createResultDriver(\SQLite3Result $result): SqliteResult
|
||||
{
|
||||
return new SqliteResult($result);
|
||||
}
|
||||
|
||||
|
||||
/********************* SQL ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Encodes data for use in a SQL statement.
|
||||
*/
|
||||
public function escapeText(string $value): string
|
||||
{
|
||||
return "'" . $this->connection->escapeString($value) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeBinary(string $value): string
|
||||
{
|
||||
return "X'" . bin2hex($value) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
return '[' . strtr($value, '[]', ' ') . ']';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format($this->fmtDate);
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format($this->fmtDateTime);
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes($this->connection->escapeString($value), '%_\\');
|
||||
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************* user defined functions ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Registers an user defined function for use in SQL statements.
|
||||
*/
|
||||
public function registerFunction(string $name, callable $callback, int $numArgs = -1): void
|
||||
{
|
||||
$this->connection->createFunction($name, $callback, $numArgs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers an aggregating user defined function for use in SQL statements.
|
||||
*/
|
||||
public function registerAggregateFunction(string $name, callable $rowCallback, callable $agrCallback, int $numArgs = -1): void
|
||||
{
|
||||
$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
|
||||
}
|
||||
}
|
123
src/Dibi/Drivers/SqliteResult.php
Normal file
123
src/Dibi/Drivers/SqliteResult.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
|
||||
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Helpers;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for SQLite result set.
|
||||
*/
|
||||
class SqliteResult implements Dibi\ResultDriver
|
||||
{
|
||||
use Dibi\Strict;
|
||||
|
||||
/** @var \SQLite3Result */
|
||||
private $resultSet;
|
||||
|
||||
/** @var bool */
|
||||
private $autoFree = true;
|
||||
|
||||
|
||||
public function __construct(\SQLite3Result $resultSet)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Automatically frees the resources allocated for this result set.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->autoFree && $this->getResultResource()) {
|
||||
@$this->free();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of rows in a result set.
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function getRowCount(): int
|
||||
{
|
||||
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the row at current position and moves the internal cursor to the next position.
|
||||
* @param bool $assoc true for associative array, false for numeric
|
||||
*/
|
||||
public function fetch(bool $assoc): ?array
|
||||
{
|
||||
return Helpers::false2Null($this->resultSet->fetchArray($assoc ? SQLITE3_ASSOC : SQLITE3_NUM));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves cursor position without fetching row.
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function seek(int $row): bool
|
||||
{
|
||||
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Frees the resources allocated for this result set.
|
||||
*/
|
||||
public function free(): void
|
||||
{
|
||||
$this->resultSet->finalize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns metadata for all columns in a result set.
|
||||
*/
|
||||
public function getResultColumns(): array
|
||||
{
|
||||
$count = $this->resultSet->numColumns();
|
||||
$columns = [];
|
||||
static $types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null'];
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$columns[] = [
|
||||
'name' => $this->resultSet->columnName($i),
|
||||
'table' => null,
|
||||
'fullname' => $this->resultSet->columnName($i),
|
||||
'nativetype' => $types[$this->resultSet->columnType($i)],
|
||||
];
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the result set resource.
|
||||
*/
|
||||
public function getResultResource(): \SQLite3Result
|
||||
{
|
||||
$this->autoFree = false;
|
||||
return $this->resultSet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes data from result set.
|
||||
*/
|
||||
public function unescapeBinary(string $value): string
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
@@ -42,7 +42,7 @@ class SqlsrvDriver implements Dibi\Driver
|
||||
/**
|
||||
* @throws Dibi\NotSupportedException
|
||||
*/
|
||||
public function __construct(array &$config)
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('sqlsrv')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'sqlsrv' is not loaded.");
|
||||
@@ -196,7 +196,7 @@ class SqlsrvDriver implements Dibi\Driver
|
||||
*/
|
||||
public function escapeText(string $value): string
|
||||
{
|
||||
return "'" . str_replace("'", "''", $value) . "'";
|
||||
return "N'" . str_replace("'", "''", $value) . "'";
|
||||
}
|
||||
|
||||
|
||||
@@ -219,30 +219,24 @@ class SqlsrvDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDate($value): string
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
public function escapeDateTime($value): string
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
$value = new Dibi\DateTime($value);
|
||||
}
|
||||
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
|
@@ -100,7 +100,7 @@ class SqlsrvReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getIndexes(string $table): array
|
||||
{
|
||||
$keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = N%s', $this->driver->escapeText($table)));
|
||||
$keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = %s', $this->driver->escapeText($table)));
|
||||
$keyUsages = [];
|
||||
while ($row = $keyUsagesRes->fetch(true)) {
|
||||
$keyUsages[$row['index_name']] = explode(',', $row['index_keys']);
|
||||
|
@@ -61,7 +61,7 @@ class SqlsrvResult implements Dibi\ResultDriver
|
||||
*/
|
||||
public function fetch(bool $assoc): ?array
|
||||
{
|
||||
return sqlsrv_fetch_array($this->resultSet, $assoc ? SQLSRV_FETCH_ASSOC : SQLSRV_FETCH_NUMERIC);
|
||||
return Dibi\Helpers::false2Null(sqlsrv_fetch_array($this->resultSet, $assoc ? SQLSRV_FETCH_ASSOC : SQLSRV_FETCH_NUMERIC));
|
||||
}
|
||||
|
||||
|
||||
|
@@ -15,7 +15,7 @@ namespace Dibi;
|
||||
*
|
||||
* @method Fluent select(...$field)
|
||||
* @method Fluent distinct()
|
||||
* @method Fluent from($table, ...$args)
|
||||
* @method Fluent from($table, ...$args = null)
|
||||
* @method Fluent where(...$cond)
|
||||
* @method Fluent groupBy(...$field)
|
||||
* @method Fluent having(...$cond)
|
||||
@@ -29,7 +29,11 @@ namespace Dibi;
|
||||
* @method Fluent outerJoin(...$table)
|
||||
* @method Fluent as(...$field)
|
||||
* @method Fluent on(...$cond)
|
||||
* @method Fluent and(...$cond)
|
||||
* @method Fluent or(...$cond)
|
||||
* @method Fluent using(...$cond)
|
||||
* @method Fluent asc()
|
||||
* @method Fluent desc()
|
||||
*/
|
||||
class Fluent implements IDataSource
|
||||
{
|
||||
@@ -311,7 +315,7 @@ class Fluent implements IDataSource
|
||||
|
||||
/**
|
||||
* Like fetch(), but returns only first field.
|
||||
* @return mixed value on success, false if no next record
|
||||
* @return mixed value on success, null if no next record
|
||||
*/
|
||||
public function fetchSingle()
|
||||
{
|
||||
@@ -419,7 +423,7 @@ class Fluent implements IDataSource
|
||||
if ($clause === null) {
|
||||
$data = $this->clauses;
|
||||
if ($this->command === 'SELECT' && ($data['LIMIT'] || $data['OFFSET'])) {
|
||||
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0], $data['OFFSET'][0]], $args);
|
||||
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args);
|
||||
unset($data['LIMIT'], $data['OFFSET']);
|
||||
}
|
||||
|
||||
|
@@ -76,7 +76,7 @@ class Column
|
||||
}
|
||||
|
||||
|
||||
public function getType(): string
|
||||
public function getType(): ?string
|
||||
{
|
||||
return Dibi\Helpers::getTypeCache()->{$this->info['nativetype']};
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ class Result
|
||||
/** @var Column[]|null */
|
||||
private $columns;
|
||||
|
||||
/** @var string[]|null */
|
||||
/** @var Column[]|null */
|
||||
private $names;
|
||||
|
||||
|
||||
|
@@ -179,13 +179,13 @@ class Result implements IDataSource
|
||||
|
||||
/**
|
||||
* Like fetch(), but returns only first field.
|
||||
* @return mixed value on success, false if no next record
|
||||
* @return mixed value on success, null if no next record
|
||||
*/
|
||||
final public function fetchSingle()
|
||||
{
|
||||
$row = $this->getResultDriver()->fetch(true);
|
||||
if ($row === null) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
$this->fetched = true;
|
||||
$this->normalize($row);
|
||||
@@ -463,8 +463,11 @@ class Result implements IDataSource
|
||||
} elseif ($type === Type::FLOAT) {
|
||||
$value = ltrim((string) $value, '0');
|
||||
$p = strpos($value, '.');
|
||||
if ($p !== false) {
|
||||
$e = strpos($value, 'e');
|
||||
if ($p !== false && $e === false) {
|
||||
$value = rtrim(rtrim($value, '0'), '.');
|
||||
} elseif ($p !== false && $e !== false) {
|
||||
$value = rtrim($value, '.');
|
||||
}
|
||||
if ($value === '' || $value[0] === '.') {
|
||||
$value = '0' . $value;
|
||||
@@ -493,7 +496,11 @@ class Result implements IDataSource
|
||||
$row[$key] = is_string($value) ? $this->getResultDriver()->unescapeBinary($value) : $value;
|
||||
|
||||
} elseif ($type === Type::JSON) {
|
||||
$row[$key] = json_decode($value, true);
|
||||
if ($this->formats[$type] === 'string') {
|
||||
$row[$key] = $value;
|
||||
} else {
|
||||
$row[$key] = json_decode($value, $this->formats[$type] === 'array');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,9 +508,9 @@ class Result implements IDataSource
|
||||
|
||||
/**
|
||||
* Define column type.
|
||||
* @param string $type use constant Type::*
|
||||
* @param string|null $type use constant Type::*
|
||||
*/
|
||||
final public function setType(string $column, string $type): self
|
||||
final public function setType(string $column, ?string $type): self
|
||||
{
|
||||
$this->types[$column] = $type;
|
||||
return $this;
|
||||
@@ -513,12 +520,21 @@ class Result implements IDataSource
|
||||
/**
|
||||
* Returns column type.
|
||||
*/
|
||||
final public function getType(string $column): string
|
||||
final public function getType(string $column): ?string
|
||||
{
|
||||
return $this->types[$column] ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns columns type.
|
||||
*/
|
||||
final public function getTypes(): array
|
||||
{
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets date format.
|
||||
*/
|
||||
|
@@ -29,12 +29,6 @@ trait Strict
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$class = get_class($this);
|
||||
if ($cb = self::extensionMethod($class . '::' . $name)) { // back compatiblity
|
||||
trigger_error("Extension methods such as $class::$name() are deprecated", E_USER_DEPRECATED);
|
||||
array_unshift($args, $this);
|
||||
return $cb(...$args);
|
||||
}
|
||||
$class = method_exists($this, $name) ? 'parent' : get_class($this);
|
||||
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
|
||||
@@ -102,39 +96,4 @@ trait Strict
|
||||
$class = get_class($this);
|
||||
throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @deprecated
|
||||
*/
|
||||
public static function extensionMethod(string $name, callable $callback = null)
|
||||
{
|
||||
if (strpos($name, '::') === false) {
|
||||
$class = get_called_class();
|
||||
} else {
|
||||
[$class, $name] = explode('::', $name);
|
||||
$class = (new ReflectionClass($class))->getName();
|
||||
}
|
||||
|
||||
$list = &self::$extMethods[strtolower($name)];
|
||||
if ($callback === null) { // getter
|
||||
$cache = &$list[''][$class];
|
||||
if (isset($cache)) {
|
||||
return $cache;
|
||||
}
|
||||
|
||||
foreach ([$class] + class_parents($class) + class_implements($class) as $cl) {
|
||||
if (isset($list[$cl])) {
|
||||
return $cache = $list[$cl];
|
||||
}
|
||||
}
|
||||
return $cache = false;
|
||||
|
||||
} else { // setter
|
||||
trigger_error("Extension methods such as $class::$name() are deprecated", E_USER_DEPRECATED);
|
||||
$list[$class] = $callback;
|
||||
$list[''] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -151,7 +151,7 @@ final class Translator
|
||||
$sql[] = '*/';
|
||||
}
|
||||
|
||||
$sql = implode(' ', $sql);
|
||||
$sql = trim(implode(' ', $sql), ' ');
|
||||
|
||||
if ($this->errors) {
|
||||
throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql);
|
||||
@@ -381,10 +381,11 @@ final class Translator
|
||||
case 'dt': // datetime
|
||||
if ($value === null) {
|
||||
return 'NULL';
|
||||
} else {
|
||||
return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value);
|
||||
} elseif (!$value instanceof \DateTimeInterface) {
|
||||
$value = new DateTime($value);
|
||||
}
|
||||
// break omitted
|
||||
return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value);
|
||||
|
||||
case 'by':
|
||||
case 'n': // composed identifier name
|
||||
return $this->identifiers->$value;
|
||||
@@ -455,6 +456,9 @@ final class Translator
|
||||
} elseif ($value instanceof \DateTimeInterface) {
|
||||
return $this->driver->escapeDateTime($value);
|
||||
|
||||
} elseif ($value instanceof \DateInterval) {
|
||||
return $this->driver->escapeDateInterval($value);
|
||||
|
||||
} elseif ($value instanceof Literal) {
|
||||
return (string) $value;
|
||||
|
||||
|
@@ -11,28 +11,28 @@ declare(strict_types=1);
|
||||
/**
|
||||
* Static container class for Dibi connections.
|
||||
*
|
||||
* @method void disconnect()
|
||||
* @method Dibi\Result query(...$args)
|
||||
* @method Dibi\Result nativeQuery(...$args)
|
||||
* @method bool test(...$args)
|
||||
* @method Dibi\DataSource dataSource(...$args)
|
||||
* @method Dibi\Row|null fetch(...$args)
|
||||
* @method array fetchAll(...$args)
|
||||
* @method mixed fetchSingle(...$args)
|
||||
* @method array fetchPairs(...$args)
|
||||
* @method int getAffectedRows()
|
||||
* @method int getInsertId(string $sequence = null)
|
||||
* @method void begin(string $savepoint = null)
|
||||
* @method void commit(string $savepoint = null)
|
||||
* @method void rollback(string $savepoint = null)
|
||||
* @method Dibi\Reflection\Database getDatabaseInfo()
|
||||
* @method Dibi\Fluent command()
|
||||
* @method Dibi\Fluent select(...$args)
|
||||
* @method Dibi\Fluent update(string $table, array $args)
|
||||
* @method Dibi\Fluent insert(string $table, array $args)
|
||||
* @method Dibi\Fluent delete(string $table)
|
||||
* @method Dibi\HashMap getSubstitutes()
|
||||
* @method int loadFile(string $file)
|
||||
* @method static void disconnect()
|
||||
* @method static Dibi\Result query(...$args)
|
||||
* @method static Dibi\Result nativeQuery(...$args)
|
||||
* @method static bool test(...$args)
|
||||
* @method static Dibi\DataSource dataSource(...$args)
|
||||
* @method static Dibi\Row|null fetch(...$args)
|
||||
* @method static array fetchAll(...$args)
|
||||
* @method static mixed fetchSingle(...$args)
|
||||
* @method static array fetchPairs(...$args)
|
||||
* @method static int getAffectedRows()
|
||||
* @method static int getInsertId(string $sequence = null)
|
||||
* @method static void begin(string $savepoint = null)
|
||||
* @method static void commit(string $savepoint = null)
|
||||
* @method static void rollback(string $savepoint = null)
|
||||
* @method static Dibi\Reflection\Database getDatabaseInfo()
|
||||
* @method static Dibi\Fluent command()
|
||||
* @method static Dibi\Fluent select(...$args)
|
||||
* @method static Dibi\Fluent update(string|string[] $table, array $args)
|
||||
* @method static Dibi\Fluent insert(string $table, array $args)
|
||||
* @method static Dibi\Fluent delete(string $table)
|
||||
* @method static Dibi\HashMap getSubstitutes()
|
||||
* @method static int loadFile(string $file)
|
||||
*/
|
||||
class dibi
|
||||
{
|
||||
@@ -44,7 +44,7 @@ class dibi
|
||||
|
||||
/** version */
|
||||
public const
|
||||
VERSION = '4.0.0';
|
||||
VERSION = '4.1.0';
|
||||
|
||||
/** sorting order */
|
||||
public const
|
||||
@@ -145,26 +145,6 @@ class dibi
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function affectedRows(): int
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
|
||||
return self::getConnection()->getAffectedRows();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function insertId(string $sequence = null): int
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
|
||||
return self::getConnection()->getInsertId($sequence);
|
||||
}
|
||||
|
||||
|
||||
/********************* misc tools ****************d*g**/
|
||||
|
||||
|
||||
|
@@ -87,15 +87,11 @@ interface Driver
|
||||
|
||||
function escapeBool(bool $value): string;
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
function escapeDate($value): string;
|
||||
function escapeDate(\DateTimeInterface $value): string;
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string|int $value
|
||||
*/
|
||||
function escapeDateTime($value): string;
|
||||
function escapeDateTime(\DateTimeInterface $value): string;
|
||||
|
||||
function escapeDateInterval(\DateInterval $value): string;
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
|
@@ -1,7 +0,0 @@
|
||||
includes:
|
||||
- ../temp/coding-standard/coding-standard-php71.neon
|
||||
|
||||
parameters:
|
||||
skip:
|
||||
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer:
|
||||
- src/Dibi/HashMap.php # issue #260
|
7
tests/coding-standard.yml
Normal file
7
tests/coding-standard.yml
Normal 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
|
@@ -1,5 +1,5 @@
|
||||
[sqlite] ; default
|
||||
driver = sqlite3
|
||||
driver = sqlite
|
||||
database = :memory:
|
||||
system = sqlite
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
[sqlite] ; default
|
||||
driver = sqlite3
|
||||
driver = sqlite
|
||||
database = :memory:
|
||||
system = sqlite
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
[sqlite] ; default
|
||||
driver = sqlite3
|
||||
driver = sqlite
|
||||
database = :memory:
|
||||
system = sqlite
|
||||
|
||||
|
@@ -50,3 +50,27 @@ test(function () use ($config) {
|
||||
$conn->disconnect();
|
||||
Assert::false($conn->isConnected());
|
||||
});
|
||||
|
||||
|
||||
test(function () use ($config) {
|
||||
Assert::exception(function () use ($config) {
|
||||
new Connection($config + ['onConnect' => '']);
|
||||
}, InvalidArgumentException::class, "Configuration option 'onConnect' must be array.");
|
||||
|
||||
$e = Assert::exception(function () use ($config) {
|
||||
new Connection($config + ['onConnect' => ['STOP']]);
|
||||
}, Dibi\DriverException::class);
|
||||
Assert::same('STOP', $e->getSql());
|
||||
|
||||
$e = Assert::exception(function () use ($config) {
|
||||
new Connection($config + ['onConnect' => [['STOP %i', 123]]]);
|
||||
}, Dibi\DriverException::class);
|
||||
Assert::same('STOP 123', $e->getSql());
|
||||
|
||||
// lazy
|
||||
$conn = new Connection($config + ['lazy' => true, 'onConnect' => ['STOP']]);
|
||||
$e = Assert::exception(function () use ($conn) {
|
||||
$conn->query('SELECT 1');
|
||||
}, Dibi\DriverException::class);
|
||||
Assert::same('STOP', $e->getSql());
|
||||
});
|
||||
|
@@ -18,9 +18,6 @@ $conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
||||
// fetch a single value
|
||||
$res = $conn->query('SELECT [title] FROM [products] ORDER BY [product_id]');
|
||||
Assert::same('Chair', $res->fetchSingle());
|
||||
Assert::same('Table', $res->fetchSingle());
|
||||
Assert::same('Computer', $res->fetchSingle());
|
||||
Assert::false($res->fetchSingle());
|
||||
|
||||
|
||||
// fetch complete result set
|
||||
|
@@ -77,7 +77,7 @@ SELECT [product_id]
|
||||
FROM (SELECT * FROM products) t
|
||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||
ORDER BY [product_id] ASC
|
||||
) t"), dibi::$sql);
|
||||
) t"), dibi::$sql);
|
||||
Assert::same(1, $ds->toDataSource()->count());
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ SELECT [product_id]
|
||||
FROM (SELECT * FROM products) t
|
||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||
ORDER BY [product_id] ASC
|
||||
"),
|
||||
"),
|
||||
dibi::$sql
|
||||
);
|
||||
|
||||
@@ -106,7 +106,7 @@ SELECT [product_id]
|
||||
FROM (SELECT * FROM products) t
|
||||
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
|
||||
ORDER BY [product_id] ASC
|
||||
) t"),
|
||||
) t"),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
|
@@ -56,28 +56,28 @@ $fluent = $conn->select('*')
|
||||
->orderBy('customer_id');
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
|
||||
$fluent->fetch();
|
||||
Assert::same(
|
||||
'SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t',
|
||||
'SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t',
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchAll(0, 3);
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (3) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (3) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
dibi::$sql
|
||||
);
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
@@ -85,16 +85,16 @@ Assert::same(
|
||||
$fluent->limit(0);
|
||||
$fluent->fetch();
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (0) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (0) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
dibi::$sql
|
||||
);
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (0) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
@@ -103,12 +103,12 @@ $fluent->removeClause('limit');
|
||||
$fluent->removeClause('offset');
|
||||
$fluent->fetch();
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
Assert::same(
|
||||
reformat('SELECT TOP (1) * FROM ( SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||
dibi::$sql
|
||||
);
|
||||
Assert::same(
|
||||
|
@@ -22,28 +22,28 @@ $fluent = $conn->select('*')
|
||||
->orderBy('customer_id');
|
||||
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
|
||||
$fluent->fetch();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchAll(2, 3);
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 3 OFFSET 2'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 3 OFFSET 2'),
|
||||
dibi::$sql
|
||||
);
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
@@ -51,16 +51,16 @@ Assert::same(
|
||||
$fluent->limit(0);
|
||||
$fluent->fetch();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||
dibi::$sql
|
||||
);
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
@@ -68,16 +68,16 @@ Assert::same(
|
||||
$fluent->removeClause('limit');
|
||||
$fluent->fetch();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
|
||||
dibi::$sql
|
||||
);
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 18446744073709551615 OFFSET 3'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 18446744073709551615 OFFSET 3'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
@@ -85,12 +85,12 @@ Assert::same(
|
||||
$fluent->removeClause('offset');
|
||||
$fluent->fetch();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||
dibi::$sql
|
||||
);
|
||||
$fluent->fetchSingle();
|
||||
Assert::same(
|
||||
reformat(' SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
|
||||
dibi::$sql
|
||||
);
|
||||
Assert::same(
|
||||
|
@@ -95,9 +95,9 @@ $fluent = $conn->select('*')
|
||||
|
||||
Assert::same(
|
||||
reformat([
|
||||
'odbc' => 'SELECT TOP 1 * FROM ( SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123) t',
|
||||
'sqlsrv' => ' SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY',
|
||||
' SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 LIMIT 1',
|
||||
'odbc' => 'SELECT TOP 1 * FROM (SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123) t',
|
||||
'sqlsrv' => 'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY',
|
||||
'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 LIMIT 1',
|
||||
]),
|
||||
(string) $fluent
|
||||
);
|
||||
@@ -134,7 +134,10 @@ $fluent = $conn->select('*')
|
||||
->where(['x' => 'a', 'b', 'c']);
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = \'a\') AND (b) AND (c)'),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = N'a') AND (b) AND (c)",
|
||||
"SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = 'a') AND (b) AND (c)",
|
||||
]),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
|
@@ -29,3 +29,15 @@ Assert::same(
|
||||
reformat('UPDATE IGNORE DELAYED [table] SET [title]=\'Super Product\', [price]=12, [brand]=NULL , [another]=123'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
||||
|
||||
$arr = [
|
||||
'table1.title' => 'Super Product',
|
||||
'table2.price' => 12,
|
||||
'table2.brand' => null,
|
||||
];
|
||||
$fluent = $conn->update(['table1', 'table2'], $arr);
|
||||
Assert::same(
|
||||
reformat('UPDATE [table1], [table2] SET [table1].[title]=\'Super Product\', [table2].[price]=12, [table2].[brand]=NULL'),
|
||||
(string) $fluent
|
||||
);
|
||||
|
41
tests/dibi/PdoDriver.providedConnection.phpt
Normal file
41
tests/dibi/PdoDriver.providedConnection.phpt
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Tester\Assert;
|
||||
|
||||
require __DIR__ . '/bootstrap.php';
|
||||
|
||||
|
||||
function buildPdoDriver(?int $errorMode)
|
||||
{
|
||||
$pdo = new PDO('sqlite::memory:');
|
||||
if ($errorMode !== null) {
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
|
||||
}
|
||||
new Dibi\Drivers\PdoDriver(['resource' => $pdo]);
|
||||
}
|
||||
|
||||
|
||||
// PDO error mode: exception
|
||||
Assert::exception(function () {
|
||||
buildPdoDriver(PDO::ERRMODE_EXCEPTION);
|
||||
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.');
|
||||
|
||||
|
||||
// PDO error mode: warning
|
||||
Assert::exception(function () {
|
||||
buildPdoDriver(PDO::ERRMODE_WARNING);
|
||||
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.');
|
||||
|
||||
|
||||
// PDO error mode: explicitly set silent
|
||||
test(function () {
|
||||
buildPdoDriver(PDO::ERRMODE_SILENT);
|
||||
});
|
||||
|
||||
|
||||
// PDO error mode: implicitly set silent
|
||||
test(function () {
|
||||
buildPdoDriver(null);
|
||||
});
|
@@ -32,7 +32,7 @@ Assert::same(
|
||||
);
|
||||
|
||||
|
||||
if (!in_array($config['driver'], ['sqlite3', 'pdo', 'sqlsrv'], true)) {
|
||||
if (!in_array($config['driver'], ['sqlite', 'pdo', 'sqlsrv'], true)) {
|
||||
Assert::same(
|
||||
['products.product_id', 'orders.order_id', 'customers.name', 'xXx'],
|
||||
$info->getColumnNames(true)
|
||||
@@ -43,7 +43,7 @@ if (!in_array($config['driver'], ['sqlite3', 'pdo', 'sqlsrv'], true)) {
|
||||
$columns = $info->getColumns();
|
||||
|
||||
Assert::same('product_id', $columns[0]->getName());
|
||||
if (!in_array($config['driver'], ['sqlite3', 'pdo', 'sqlsrv'], true)) {
|
||||
if (!in_array($config['driver'], ['sqlite', 'pdo', 'sqlsrv'], true)) {
|
||||
Assert::same('products', $columns[0]->getTableName());
|
||||
}
|
||||
Assert::null($columns[0]->getVendorInfo('xxx'));
|
||||
|
@@ -100,6 +100,11 @@ test(function () {
|
||||
Assert::same(['col' => 1.0], $result->test(['col' => 1]));
|
||||
Assert::same(['col' => 1.0], $result->test(['col' => 1.0]));
|
||||
|
||||
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '1.1e+10']));
|
||||
Assert::same(['col' => '1.1e-10'], $result->test(['col' => '1.1e-10']));
|
||||
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10']));
|
||||
Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.1e+10']));
|
||||
|
||||
setlocale(LC_ALL, 'de_DE@euro', 'de_DE', 'deu_deu');
|
||||
Assert::same(['col' => 0.0], $result->test(['col' => '']));
|
||||
Assert::same(['col' => 0.0], $result->test(['col' => '0']));
|
||||
|
25
tests/dibi/Translator.DateInterval.phpt
Normal file
25
tests/dibi/Translator.DateInterval.phpt
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Tester\Assert;
|
||||
|
||||
require __DIR__ . '/bootstrap.php';
|
||||
|
||||
$conn = new Dibi\Connection($config);
|
||||
$translator = new Dibi\Translator($conn);
|
||||
|
||||
switch ($config['system']) {
|
||||
case 'mysql':
|
||||
Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null));
|
||||
Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null));
|
||||
Assert::exception(function () use ($translator) {
|
||||
$translator->formatValue(new DateInterval('P2Y4DT6H8M'), null);
|
||||
}, Dibi\NotSupportedException::class, 'Only time interval is supported.');
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert::exception(function () use ($translator) {
|
||||
$translator->formatValue(new DateInterval('PT10H20M30S'), null);
|
||||
}, Dibi\Exception::class);
|
||||
}
|
@@ -60,13 +60,22 @@ WHERE [id] > 0
|
||||
|
||||
// nested condition
|
||||
Assert::match(
|
||||
reformat("
|
||||
reformat([
|
||||
'sqlsrv' => "
|
||||
SELECT *
|
||||
FROM [customers]
|
||||
WHERE
|
||||
[name] LIKE N'xxx'
|
||||
/* AND ...=1 */
|
||||
/* 1 LIMIT 10 */",
|
||||
"
|
||||
SELECT *
|
||||
FROM [customers]
|
||||
WHERE
|
||||
[name] LIKE 'xxx'
|
||||
/* AND ...=1 */
|
||||
/* 1 LIMIT 10 */"),
|
||||
/* 1 LIMIT 10 */",
|
||||
]),
|
||||
|
||||
$conn->translate('
|
||||
SELECT *
|
||||
|
73
tests/dibi/Translator.like.phpt
Normal file
73
tests/dibi/Translator.like.phpt
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @dataProvider ../databases.ini
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Tester\Assert;
|
||||
|
||||
require __DIR__ . '/bootstrap.php';
|
||||
|
||||
|
||||
$conn = new Dibi\Connection($config);
|
||||
|
||||
// starts with
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'a', 'b'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'baa', 'aa'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'aab', 'aa'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'bba', '%a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', '%ba', '%a'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%ab', '%a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'aa', '_a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', '_b', '_a'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '_ab', '_a'));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a"a', 'a"'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'b"', '%"'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%"', '%"'));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "a'a", "a'"));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', "b'", "%'"));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "%'", "%'"));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a\\a', 'a\\'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'b\\', '%\\'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%\\', '%\\'));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a[a', 'a['));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'b[', '%['));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%[', '%['));
|
||||
|
||||
|
||||
// ends with
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', 'a', 'b'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'baa', 'aa'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', 'aab', 'aa'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', 'bba', '%a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', 'a%b', '%a'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'b%a', '%a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', 'aa', '_a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '_b', '_a'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'b_a', '_a'));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'a"a', '"a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '"b', '"%'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', '"%', '"%'));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "a'a", "'a"));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', "'b", "'%"));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "'%", "'%"));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'a\\a', '\\a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '\\b', '\\%'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', '\\%', '\\%'));
|
||||
|
||||
|
||||
// contains
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like~', 'a', 'b'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'baa', 'aa'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'aab', 'aa'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like~', 'bba', '%a'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like~', 'b%a', '%a'));
|
@@ -16,7 +16,10 @@ $conn = new Dibi\Connection($config + ['formatDateTime' => "'Y-m-d H:i:s.u'", 'f
|
||||
|
||||
// Dibi detects INSERT or REPLACE command & booleans
|
||||
Assert::same(
|
||||
reformat("REPLACE INTO [products] ([title], [price]) VALUES ('Drticka', 318)"),
|
||||
reformat([
|
||||
'sqlsrv' => "REPLACE INTO [products] ([title], [price]) VALUES (N'Drticka', 318)",
|
||||
"REPLACE INTO [products] ([title], [price]) VALUES ('Drticka', 318)",
|
||||
]),
|
||||
$conn->translate('REPLACE INTO [products]', [
|
||||
'title' => 'Drticka',
|
||||
'price' => 318,
|
||||
@@ -31,7 +34,10 @@ $array = [
|
||||
'brand' => null,
|
||||
];
|
||||
Assert::same(
|
||||
reformat('INSERT INTO [products] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'),
|
||||
reformat([
|
||||
'sqlsrv' => "INSERT INTO [products] ([title], [price], [brand]) VALUES (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL)",
|
||||
"INSERT INTO [products] ([title], [price], [brand]) VALUES ('Super Product', 12, NULL) , ('Super Product', 12, NULL) , ('Super Product', 12, NULL)",
|
||||
]),
|
||||
$conn->translate('INSERT INTO [products]', $array, $array, $array)
|
||||
);
|
||||
|
||||
@@ -43,14 +49,20 @@ $array = [
|
||||
['pole' => 'hodnota3', 'bit' => 1],
|
||||
];
|
||||
Assert::same(
|
||||
reformat('INSERT INTO [products] ([pole], [bit]) VALUES (\'hodnota1\', 1) , (\'hodnota2\', 1) , (\'hodnota3\', 1)'),
|
||||
reformat([
|
||||
'sqlsrv' => "INSERT INTO [products] ([pole], [bit]) VALUES (N'hodnota1', 1) , (N'hodnota2', 1) , (N'hodnota3', 1)",
|
||||
"INSERT INTO [products] ([pole], [bit]) VALUES ('hodnota1', 1) , ('hodnota2', 1) , ('hodnota3', 1)",
|
||||
]),
|
||||
$conn->translate('INSERT INTO [products] %ex', $array)
|
||||
);
|
||||
|
||||
|
||||
// Dibi detects UPDATE command
|
||||
Assert::same(
|
||||
reformat("UPDATE [colors] SET [color]='blue', [order]=12 WHERE [id]=123"),
|
||||
reformat([
|
||||
'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [order]=12 WHERE [id]=123",
|
||||
"UPDATE [colors] SET [color]='blue', [order]=12 WHERE [id]=123",
|
||||
]),
|
||||
$conn->translate('UPDATE [colors] SET', [
|
||||
'color' => 'blue',
|
||||
'order' => 12,
|
||||
@@ -85,17 +97,26 @@ $e = Assert::exception(function () use ($conn) {
|
||||
Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql());
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT * FROM [table] WHERE id=10 AND name=\'ahoj\''),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT * FROM [table] WHERE id=10 AND name=N'ahoj'",
|
||||
"SELECT * FROM [table] WHERE id=10 AND name='ahoj'",
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [table] WHERE id=%i AND name=%s', 10, 'ahoj')
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('TEST ([cond] > 2) OR ([cond2] = \'3\') OR (cond3 < RAND())'),
|
||||
reformat([
|
||||
'sqlsrv' => "TEST ([cond] > 2) OR ([cond2] = N'3') OR (cond3 < RAND())",
|
||||
"TEST ([cond] > 2) OR ([cond2] = '3') OR (cond3 < RAND())",
|
||||
]),
|
||||
$conn->translate('TEST %or', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'])
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('TEST ([cond] > 2) AND ([cond2] = \'3\') AND (cond3 < RAND())'),
|
||||
reformat([
|
||||
'sqlsrv' => "TEST ([cond] > 2) AND ([cond2] = N'3') AND (cond3 < RAND())",
|
||||
"TEST ([cond] > 2) AND ([cond2] = '3') AND (cond3 < RAND())",
|
||||
]),
|
||||
$conn->translate('TEST %and', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'])
|
||||
);
|
||||
|
||||
@@ -114,7 +135,10 @@ $where['age'] = null;
|
||||
$where['email'] = 'ahoj';
|
||||
$where['id%l'] = [10, 20, 30];
|
||||
Assert::same(
|
||||
reformat('SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = \'ahoj\') AND ([id] IN (10, 20, 30))'),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = N'ahoj') AND ([id] IN (10, 20, 30))",
|
||||
"SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = 'ahoj') AND ([id] IN (10, 20, 30))",
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [table] WHERE %and', $where)
|
||||
);
|
||||
|
||||
@@ -144,9 +168,9 @@ Assert::same(
|
||||
// with limit = 2
|
||||
Assert::same(
|
||||
reformat([
|
||||
'odbc' => 'SELECT TOP 2 * FROM (SELECT * FROM [products] ) t',
|
||||
'odbc' => 'SELECT TOP 2 * FROM (SELECT * FROM [products]) t',
|
||||
'sqlsrv' => 'SELECT * FROM [products] OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY',
|
||||
'SELECT * FROM [products] LIMIT 2',
|
||||
'SELECT * FROM [products] LIMIT 2',
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [products] %lmt', 2)
|
||||
);
|
||||
@@ -160,7 +184,7 @@ if ($config['system'] === 'odbc') {
|
||||
Assert::same(
|
||||
reformat([
|
||||
'sqlsrv' => 'SELECT * FROM [products] OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY',
|
||||
'SELECT * FROM [products] LIMIT 2 OFFSET 1',
|
||||
'SELECT * FROM [products] LIMIT 2 OFFSET 1',
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1)
|
||||
);
|
||||
@@ -168,10 +192,10 @@ if ($config['system'] === 'odbc') {
|
||||
// with offset = 50
|
||||
Assert::same(
|
||||
reformat([
|
||||
'mysql' => 'SELECT * FROM `products` LIMIT 18446744073709551615 OFFSET 50',
|
||||
'postgre' => 'SELECT * FROM "products" OFFSET 50',
|
||||
'mysql' => 'SELECT * FROM `products` LIMIT 18446744073709551615 OFFSET 50',
|
||||
'postgre' => 'SELECT * FROM "products" OFFSET 50',
|
||||
'sqlsrv' => 'SELECT * FROM [products] OFFSET 50 ROWS',
|
||||
'SELECT * FROM [products] LIMIT -1 OFFSET 50',
|
||||
'SELECT * FROM [products] LIMIT -1 OFFSET 50',
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [products] %ofs', 50)
|
||||
);
|
||||
@@ -259,7 +283,13 @@ INTO OUTFILE '/tmp/result\'.txt'
|
||||
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\\\"'
|
||||
LINES TERMINATED BY '\\\\n'
|
||||
",
|
||||
"SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
|
||||
'sqlsrv' => "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
|
||||
CONCAT(last_name, N', ', first_name) AS full_name
|
||||
GROUP BY [user]
|
||||
HAVING MAX(salary) > %i 123
|
||||
INTO OUTFILE N'/tmp/result''.txt'
|
||||
FIELDS TERMINATED BY N',' OPTIONALLY ENCLOSED BY N'\"'
|
||||
LINES TERMINATED BY N'\\n'", "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
|
||||
CONCAT(last_name, ', ', first_name) AS full_name
|
||||
GROUP BY [user]
|
||||
HAVING MAX(salary) > %i 123
|
||||
@@ -321,6 +351,27 @@ WHERE (`test`.`a` LIKE '1995-03-01'
|
||||
OR `false`= 0
|
||||
OR `str_null`=NULL
|
||||
OR `str_not_null`='hello'
|
||||
LIMIT 10",
|
||||
'sqlsrv' => "SELECT *
|
||||
FROM [db].[table]
|
||||
WHERE ([test].[a] LIKE '1995-03-01'
|
||||
OR [b1] IN ( 1, 2, 3 )
|
||||
OR [b2] IN (N'1', N'2', N'3' )
|
||||
OR [b3] IN ( )
|
||||
OR [b4] IN ( N'one', N'two', N'three' )
|
||||
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
|
||||
OR [b6] IN (N'one', N'two', N'thr.ee')
|
||||
OR [b7] IN (NULL)
|
||||
OR [b8] IN (RAND() [col1] > [col2] )
|
||||
OR [b9] IN (RAND(), [col1] > [col2] )
|
||||
OR [b10] IN ( )
|
||||
AND [c] = N'embedded '' string'
|
||||
OR [d]=10
|
||||
OR [e]=NULL
|
||||
OR [true]= 1
|
||||
OR [false]= 0
|
||||
OR [str_null]=NULL
|
||||
OR [str_not_null]=N'hello'
|
||||
LIMIT 10",
|
||||
'postgre' => 'SELECT *
|
||||
FROM "db"."table"
|
||||
@@ -412,7 +463,10 @@ LIMIT 10')
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat('TEST [cond] > 2 [cond2] = \'3\' cond3 < RAND() 123'),
|
||||
reformat([
|
||||
'sqlsrv' => "TEST [cond] > 2 [cond2] = N'3' cond3 < RAND() 123",
|
||||
"TEST [cond] > 2 [cond2] = '3' cond3 < RAND() 123",
|
||||
]),
|
||||
$conn->translate('TEST %ex', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'], 123)
|
||||
);
|
||||
|
||||
@@ -430,16 +484,19 @@ Assert::same(
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat('TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE \'string\')'),
|
||||
reformat([
|
||||
'sqlsrv' => "TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE N'string')",
|
||||
"TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE 'string')",
|
||||
]),
|
||||
$conn->translate('TEST %or', ['cond1%ex' => 3, 'cond2%ex' => 'RAND()', 'cond3%ex' => ['LIKE %s', 'string']])
|
||||
);
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat([
|
||||
'odbc' => 'SELECT TOP 10 * FROM (SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' ) t',
|
||||
'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
|
||||
'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10',
|
||||
'odbc' => 'SELECT TOP 10 * FROM (SELECT * FROM [test] WHERE [id] LIKE \'%d%t\') t',
|
||||
'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE N\'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
|
||||
'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10',
|
||||
]),
|
||||
$conn->translate("SELECT * FROM [test] WHERE %n LIKE '%d%t' %lmt", 'id', 10)
|
||||
);
|
||||
@@ -455,23 +512,32 @@ Assert::same(
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT FROM ... '),
|
||||
reformat('SELECT FROM ...'),
|
||||
$conn->translate('SELECT FROM ... %lmt', null)
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT \'%i\''),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT N'%i'",
|
||||
"SELECT '%i'",
|
||||
]),
|
||||
$conn->translate("SELECT '%i'")
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT \'%i\''),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT N'%i'",
|
||||
"SELECT '%i'",
|
||||
]),
|
||||
$conn->translate('SELECT "%i"')
|
||||
);
|
||||
|
||||
|
||||
Assert::same(
|
||||
reformat('INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1(\'Test product\')) , (1, SHA1(\'Test product\'))'),
|
||||
reformat([
|
||||
'sqlsrv' => "INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1(N'Test product')) , (1, SHA1(N'Test product'))",
|
||||
"INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1('Test product')) , (1, SHA1('Test product'))",
|
||||
]),
|
||||
$conn->translate('INSERT INTO [products]', [
|
||||
'product_id' => 1,
|
||||
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
|
||||
@@ -482,7 +548,10 @@ Assert::same(
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('UPDATE [products] [product_id]=1, [title]=SHA1(\'Test product\')'),
|
||||
reformat([
|
||||
'sqlsrv' => "UPDATE [products] [product_id]=1, [title]=SHA1(N'Test product')",
|
||||
"UPDATE [products] [product_id]=1, [title]=SHA1('Test product')",
|
||||
]),
|
||||
$conn->translate('UPDATE [products]', [
|
||||
'product_id' => 1,
|
||||
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
|
||||
@@ -490,7 +559,10 @@ Assert::same(
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('UPDATE [products] [product_id]=1, [title]=SHA1(\'Test product\')'),
|
||||
reformat([
|
||||
'sqlsrv' => "UPDATE [products] [product_id]=1, [title]=SHA1(N'Test product')",
|
||||
"UPDATE [products] [product_id]=1, [title]=SHA1('Test product')",
|
||||
]),
|
||||
$conn->translate('UPDATE [products]', [
|
||||
'product_id' => 1,
|
||||
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
|
||||
@@ -498,7 +570,10 @@ Assert::same(
|
||||
);
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1(\'Test product\')'),
|
||||
reformat([
|
||||
'sqlsrv' => "SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1(N'Test product')",
|
||||
"SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1('Test product')",
|
||||
]),
|
||||
$conn->translate('SELECT * FROM [products] WHERE', [
|
||||
'product_id' => 1,
|
||||
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
|
||||
@@ -535,7 +610,10 @@ $array6 = [
|
||||
];
|
||||
|
||||
Assert::same(
|
||||
reformat('INSERT INTO test ([id], [text], [num]) VALUES (1, \'ahoj\', 1), (2, \'jak\', -1), (3, \'se\', 10), (4, SUM(5), 1)'),
|
||||
reformat([
|
||||
'sqlsrv' => "INSERT INTO test ([id], [text], [num]) VALUES (1, N'ahoj', 1), (2, N'jak', -1), (3, N'se', 10), (4, SUM(5), 1)",
|
||||
"INSERT INTO test ([id], [text], [num]) VALUES (1, 'ahoj', 1), (2, 'jak', -1), (3, 'se', 10), (4, SUM(5), 1)",
|
||||
]),
|
||||
$conn->translate('INSERT INTO test %m', $array6)
|
||||
);
|
||||
|
||||
@@ -589,7 +667,10 @@ Assert::same(
|
||||
setlocale(LC_ALL, 'czech');
|
||||
|
||||
Assert::same(
|
||||
reformat("UPDATE [colors] SET [color]='blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5"),
|
||||
reformat([
|
||||
'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5",
|
||||
"UPDATE [colors] SET [color]='blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5",
|
||||
]),
|
||||
|
||||
$conn->translate('UPDATE [colors] SET', [
|
||||
'color' => 'blue',
|
||||
|
@@ -68,7 +68,7 @@ function reformat($s)
|
||||
function num($n)
|
||||
{
|
||||
global $config;
|
||||
if (substr($config['dsn'] ?? '', 0, 5) === 'odbc:' || $config['driver'] === 'sqlite') {
|
||||
if (substr($config['dsn'] ?? '', 0, 5) === 'odbc:') {
|
||||
$n = is_float($n) ? "$n.0" : (string) $n;
|
||||
}
|
||||
return $n;
|
||||
|
@@ -17,7 +17,7 @@ $conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
||||
|
||||
$e = Assert::exception(function () use ($conn) {
|
||||
$conn->query('SELECT');
|
||||
}, Dibi\DriverException::class, '%a% syntax error', 1);
|
||||
}, Dibi\DriverException::class, '%a%', 1);
|
||||
|
||||
Assert::same('SELECT', $e->getSql());
|
||||
|
||||
|
Reference in New Issue
Block a user