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

Compare commits

..

40 Commits

Author SHA1 Message Date
David Grudl
44b420f70d Released version 4.0.3 2020-03-26 03:54:02 +01:00
David Grudl
4689101b88 SqliteResult: workaround for PHP bug 79414 2020-03-26 03:54:02 +01:00
David Grudl
db16b9e87a Result: does not drop the value if detection fails 2020-03-26 03:54:02 +01:00
Adam Klvač
803c9d539c MySqliDriver: coalesced password to an empty string (#360)
mysqli::real_connect() expects parameter 3 to be string, yields an error on NULL.
2020-03-26 03:54:02 +01:00
David Grudl
e137dfa28b Result::fetchAssoc() DateTime in key is converted to string [Closes #359] 2020-03-26 03:54:02 +01:00
Milan Pála
9651835f5b tests: added reconnect test (#352) 2020-03-26 03:54:02 +01:00
David Grudl
2cbebc02c4 Connection: translator is created/destructed in connect/disconnect [Closes #352][Closes #354] 2020-03-26 03:54:02 +01:00
David Grudl
4d647c2aed travis: fixed databases 2020-03-26 03:51:13 +01:00
David Grudl
5c5838aee4 travis: added PHP 7.4 2020-03-26 03:47:28 +01:00
David Grudl
7df72fd6cf fixes for PHP 7.4 2020-03-26 03:45:50 +01:00
magikstm
219882a962 readme: some minor corrections (#331) 2019-05-29 13:44:50 +02:00
Jiří Zralý
7e127f5914 Documentation: Added %N identifier (#330) 2019-04-16 21:50:55 +02:00
David Grudl
79f841ec90 Released version 4.0.2 2019-03-11 21:38:25 +01:00
pepa-linha
69eaa71fde Dibi\Fluent: add annotation for methods or() (#328) 2019-02-11 12:37:24 +01:00
David Grudl
f895493016 travis: finely segmented matrix, added PhpStan 2019-02-05 22:11:08 +01:00
Radovan Kepák
19172c801e Added new getTypes method (#327)
Added getTypes method, to get all columns and its types, this is faster then write foreach for getting all columns, and getColumns do not get column type (there is null)
2019-01-26 15:37:58 +01:00
Jan Kuchař
2b5683d0f2 PostgreDriver: fix indexes reflection for indexes on expressions (#323) 2018-12-15 00:47:26 +07:00
Jan Kuchař
76593b7da4 dibi: fix dibi::*() static methods annotations (#324) 2018-12-12 07:42:02 +07:00
Chrudos Vorlicek
dd2fd654be Result::normalize() Fix select float in format of e-notation (#317) master (#321) 2018-10-25 22:33:09 +02:00
David Grudl
12cbbb3140 travis: added PHP 7.3 2018-10-17 17:47:27 +02:00
David Grudl
811974139e Released version 4.0.1 2018-09-17 13:50:54 +02:00
David Grudl
7a49609468 test: fix for PHP 7.3 2018-09-17 13:45:50 +02:00
Jan Kuchař
89987f0cee PdoDriver: check for misconfigured PDO connections resource (#294) 2018-09-17 13:45:50 +02:00
David Grudl
b7e467ecac drivers: config in not passed via reference (BC break) 2018-09-17 13:45:50 +02:00
jan-oliva
fe0e7510af Firebird: Add escapeLike() (#300,#305) 2018-09-17 13:45:50 +02:00
Miloslav Hůla
eaf2494d90 Connection: added config option onConnect (#303)
onConnect option is an array of SQL queries run by Connection::query() right after a database connection is established.
2018-09-17 13:45:50 +02:00
Dalibor Korpar
168971292d Connection::update() Support updating multiple tables at once (#316) 2018-09-17 13:45:49 +02:00
David Grudl
95c424a71d Connection: accepts Driver instance (fixes 51fa3b9) [Closes #315] 2018-09-13 13:10:24 +02:00
David Grudl
4b85f0a973 travis: uses NCS 2 2018-09-13 03:28:02 +02:00
Jan Kuchař
e7539102cb fix: setType should accept null as type (#309) 2018-08-22 15:43:11 +02:00
David Grudl
0ad2dd70bc tests: added complex test for %like 2018-08-22 13:45:39 +02:00
Jan Kuchař
4abe874ce9 fix TypeError: substr() expects parameter 1 to be string, null given (#306) 2018-08-22 12:08:35 +02:00
Jan Kuchař
15df96bb22 fixed typehint (#307) 2018-08-22 12:08:03 +02:00
David Grudl
2870fb9b31 OdbcDriver: added option 'microseconds' 2018-08-09 22:55:09 +02:00
Pavel Kácha
c8dfb1f863 Dibi\Fluent: add annotations for methods and(), asc(), desc() #298 (#299) 2018-06-28 14:25:20 +02:00
David Grudl
9840c31995 updated donation links 2018-06-22 11:47:44 +02:00
Miroslav Koula
25fda3f8f1 DibiExtension: compatibility with Nette DI 3.x (#297)
Nette DI 3.x require $container->setFactory() usage except of $container->setClass()
2018-06-14 18:17:37 +02:00
Jan Endel
38128fbf9e typo
for example fluent:

$dibiConnection->select(‘id’)
    ->from(‘users’);

will not pass static analysis although is completely valid code.
2018-06-13 11:47:55 +02:00
Josef Drábek
73790f4321 PdoDriver::getInsertId() fixed 2018-05-30 14:00:54 +02:00
David Grudl
3930dafe3f Sqlite3Driver becomes alias for SqliteDriver 2018-05-23 17:14:32 +02:00
52 changed files with 772 additions and 502 deletions

View File

@@ -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!

View File

@@ -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

View File

@@ -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';

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -15,7 +15,7 @@ Tracy\Debugger::enable();
<?php
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague');
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -19,7 +19,7 @@ date_default_timezone_set('Europe/Prague');
<?php
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -11,7 +11,7 @@ Tracy\Debugger::enable();
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -11,7 +11,7 @@ Tracy\Debugger::enable();
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -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'",

View File

@@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague');
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -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' => [

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -13,7 +13,7 @@ if (@!include __DIR__ . '/../vendor/autoload.php') {
$dibi = new Dibi\Connection([
'driver' => 'sqlite3',
'driver' => 'sqlite',
'database' => 'data/sample.s3db',
]);

View File

@@ -1,4 +1,4 @@
[Dibi](https://dibiphp.com) - smart database layer for PHP [![Buy me a coffee](https://files.nette.org/images/coffee1s.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9XXL5ZJHAYQUN)
[Dibi](https://dibiphp.com) - smart database layer for PHP [![Buy me a coffee](https://files.nette.org/images/coffee1s.png)](https://nette.org/make-donation?to=dibi)
=========================================================
[![Downloads this Month](https://img.shields.io/packagist/dm/dibi/dibi.svg)](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.0 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

View File

@@ -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,
]);

View File

@@ -47,6 +47,7 @@ class Connection implements IConnection
* - run (bool) => enable profiler?
* - file => file to log
* - substitutes (array) => map of driver specific substitutes (under development)
* - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
* @param array $config connection parameters
* @throws Exception
*/
@@ -90,6 +91,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 +117,14 @@ 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'];
$this->translator = new Translator($this);
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";
@@ -125,9 +136,16 @@ class Connection implements IConnection
$event = $this->onEvent ? new Event($this, Event::CONNECT) : null;
try {
$this->driver = new $class($this->config);
$this->translator = new Translator($this);
if ($event) {
$this->onEvent($event->done());
}
if (isset($this->config['onConnect'])) {
foreach ($this->config['onConnect'] as $sql) {
$this->query($sql);
}
}
} catch (DriverException $e) {
if ($event) {
@@ -145,7 +163,7 @@ class Connection implements IConnection
{
if ($this->driver) {
$this->driver->disconnect();
$this->driver = null;
$this->driver = $this->translator = null;
}
}
@@ -246,11 +264,7 @@ class Connection implements IConnection
if (!$this->driver) {
$this->connect();
}
if (!$this->translator) {
$this->translator = new Translator($this);
}
$translator = clone $this->translator;
return $translator->translate($args);
return (clone $this->translator)->translate($args);
}
@@ -436,7 +450,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);
}

View File

@@ -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()
{

View File

@@ -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.");
@@ -276,7 +276,8 @@ class FirebirdDriver implements Dibi\Driver
*/
public function escapeLike(string $value, int $pos): string
{
throw new Dibi\NotImplementedException;
$value = addcslashes($this->escapeText($value), '%_\\');
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
}

View File

@@ -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.");
@@ -93,7 +93,7 @@ class MySqliDriver implements Dibi\Driver
@$this->connection->real_connect( // intentionally @
(empty($config['persistent']) ? '' : 'p:') . $config['host'],
$config['username'],
$config['password'],
$config['password'] ?? '',
$config['database'] ?? '',
$config['port'] ?? 0,
$config['socket'],

View File

@@ -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'];
}
}
@@ -238,7 +246,7 @@ class OdbcDriver implements Dibi\Driver
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#');
}

View File

@@ -45,7 +45,7 @@ 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.");

View File

@@ -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));
}

View File

@@ -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.");

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,301 @@
<?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();
}
/**
* 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';
}
/**
* @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);
}
}

View 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)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414
];
}
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;
}
}

View File

@@ -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.");

View File

@@ -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']);
}

View File

@@ -76,7 +76,7 @@ class Column
}
public function getType(): string
public function getType(): ?string
{
return Dibi\Helpers::getTypeCache()->{$this->info['nativetype']};
}

View File

@@ -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);
@@ -282,7 +282,7 @@ class Result implements IDataSource
}
} elseif ($as !== '|') { // associative-array node
$x = &$x[$row->$as];
$x = &$x[(string) $row->$as];
}
}
@@ -350,7 +350,7 @@ class Result implements IDataSource
}
} else { // associative-array node
$x = &$x[$row->$as];
$x = &$x[(string) $row->$as];
}
}
@@ -452,6 +452,7 @@ class Result implements IDataSource
continue;
}
$value = $row[$key];
if ($type === Type::TEXT) {
$row[$key] = (string) $value;
@@ -463,8 +464,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;
@@ -494,6 +498,9 @@ class Result implements IDataSource
} elseif ($type === Type::JSON) {
$row[$key] = json_decode($value, true);
} else {
$row[$key] = $value;
}
}
}
@@ -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.
*/

View File

@@ -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.0.3';
/** sorting order */
public const

View File

@@ -1,7 +0,0 @@
includes:
- ../temp/coding-standard/coding-standard-php71.neon
parameters:
skip:
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer:
- src/Dibi/HashMap.php # issue #260

View File

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

View File

@@ -1,5 +1,5 @@
[sqlite] ; default
driver = sqlite3
driver = sqlite
database = :memory:
system = sqlite

View File

@@ -1,5 +1,5 @@
[sqlite] ; default
driver = sqlite3
driver = sqlite
database = :memory:
system = sqlite

View File

@@ -1,5 +1,5 @@
[sqlite] ; default
driver = sqlite3
driver = sqlite
database = :memory:
system = sqlite

View File

@@ -50,3 +50,38 @@ test(function () use ($config) {
$conn->disconnect();
Assert::false($conn->isConnected());
});
test(function () use ($config) {
$conn = new Connection($config);
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
$conn->disconnect();
$conn->connect();
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
});
test(function () use ($config) {
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());
});

View File

@@ -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

View File

@@ -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
);

View 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);
});

View File

@@ -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'));

View File

@@ -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']));

View 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'));

View File

@@ -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;

View File

@@ -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());