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

Compare commits

..

32 Commits

Author SHA1 Message Date
David Grudl
4ea907a48c used attribute Deprecated 2025-08-07 00:33:05 +02:00
David Grudl
3beee64a30 drivers: escape*() methods moved to Engine [WIP] 2025-08-07 00:33:05 +02:00
David Grudl
33da2b839e renamed driver classes 2025-08-07 00:33:05 +02:00
David Grudl
8c34f8df43 renamed interfaces (BC break)
Dibi\Driver => Dibi\Drivers\Connection
Dibi\ResultDriver => Dibi\Drivers\Result
Dibi\Reflector => Dibi\Drivers\Engine
2025-08-07 00:33:05 +02:00
David Grudl
0834c12eaf removed IConnection (BC break) 2025-08-07 00:33:05 +02:00
David Grudl
cdeafe9b27 opened 6.0-dev 2025-08-07 00:33:05 +02:00
David Grudl
32b6976209 removed support for SQLServer < 2012, PostgreSQL < 9.3 2025-08-07 00:26:19 +02:00
David Grudl
f484630e56 readonly properties 2025-08-07 00:26:19 +02:00
David Grudl
611e051c02 optimized global function calls 2025-08-07 00:26:19 +02:00
David Grudl
7595a6d5bd composer: added psr-4 loader 2025-08-07 00:25:10 +02:00
David Grudl
494d7c1c21 composer: require stable packages outside of nette 2025-08-07 00:25:10 +02:00
David Grudl
658dbe388a support for PHP 8.5 2025-08-07 00:10:17 +02:00
David Grudl
c7fe0fef21 uses PHP 8.2 features 2025-08-07 00:10:17 +02:00
David Grudl
9151d1eb9c requires PHP 8.2 2025-08-07 00:08:29 +02:00
David Grudl
d76f40c2a4 opened 5.1-dev 2025-08-07 00:04:06 +02:00
David Grudl
befde664fe tests: improved descriptions 2025-08-07 00:00:40 +02:00
David Grudl
e1c4cbaece exception: use natural explanatory style 2025-08-07 00:00:40 +02:00
David Grudl
1df20ced10 cs 2025-08-07 00:00:40 +02:00
David Grudl
ce1ba4668b uses promoted properties 2025-08-07 00:00:40 +02:00
David Grudl
0f21a6ab3d removed dead code 2025-08-07 00:00:40 +02:00
David Grudl
78f552fe8e github actions updated 2025-08-07 00:00:40 +02:00
David Grudl
97053089e0 Released version 5.0.2 2024-09-03 03:18:11 +02:00
Lukáš Kotržena
2c7b35c29d Result::normalize() Fixed normalization of "-." numbers 2024-09-03 03:16:52 +02:00
Marek Bartoš
c04d2197e3 Translator: fixed numeric column formatting 2024-09-03 03:10:05 +02:00
Matěj Koubík
d342d8d78f DibiExtension3: fixed schema 2024-09-03 03:10:05 +02:00
David Grudl
7d8c39f42a cs 2024-09-03 03:00:38 +02:00
David Grudl
29b58d64dd PdoDriver: applied #332 #287 2024-09-03 03:00:38 +02:00
David Grudl
0a32bb5bdf support for PHP 8.4 2024-09-03 03:00:38 +02:00
David Grudl
d707b4ba0e PascalCase constants 2024-09-03 03:00:38 +02:00
David Grudl
490cf143ba GitHub actions fixed
- https://learn.microsoft.com/en-us/answers/questions/1853144/error-failed-to-initialize-container-mcr-microsoft
- https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility?view=sql-server-ver16&tabs=go%2Cwindows&pivots=cs1-bash
2024-09-03 03:00:38 +02:00
stanley89
12ffa0ffd1 Tracy\Panel: fixed type error 2024-09-02 23:53:15 +02:00
David Grudl
23f65ef837 Revert "SqliteDriver: disables exceptions (is enabled since PHP 8.3)"
This reverts commit bb1f7d4b93.
2024-09-02 23:53:15 +02:00
76 changed files with 1591 additions and 1779 deletions

View File

@@ -7,10 +7,10 @@ jobs:
name: Nette Code Checker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
php-version: 8.3
coverage: none
- run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress
@@ -21,10 +21,10 @@ jobs:
name: Nette Coding Standard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
php-version: 8.3
coverage: none
- run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress

View File

@@ -10,10 +10,10 @@ jobs:
name: PHPStan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
php-version: 8.2
coverage: none
- run: composer install --no-progress --prefer-dist

View File

@@ -3,7 +3,7 @@ name: Tests
on: [push, pull_request]
env:
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.10.0beta2, pdo_sqlsrv-5.10.0beta2
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.12.0, pdo_sqlsrv-5.12.0
php-tools: "composer:v2, pecl"
jobs:
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.0', '8.1', '8.2', '8.3']
php: ['8.2', '8.3', '8.4', '8.5']
fail-fast: false
@@ -43,7 +43,6 @@ jobs:
--health-retries=5
-e MYSQL_ROOT_PASSWORD=root
-e MYSQL_DATABASE=dibi_test
--entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password"
postgres96:
image: postgres:9.6
@@ -83,13 +82,13 @@ jobs:
- 1433:1433
options: >-
--name=mssql
--health-cmd "/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'"
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1' -N -C"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
@@ -101,19 +100,19 @@ jobs:
run: cp ./tests/databases.github.ini ./tests/databases.ini
- name: Create MS SQL Database
run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test'
run: docker exec -i mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test' -N -C
- run: composer install --no-progress --prefer-dist
- run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src
- if: failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: output
name: output-${{ matrix.php }}
path: tests/**/output
- name: Save Code Coverage
if: ${{ matrix.php == '8.0' }}
if: ${{ matrix.php == '8.2' }}
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |

View File

@@ -11,28 +11,32 @@
}
],
"require": {
"php": "8.0 - 8.3"
"php": "8.2 - 8.5"
},
"require-dev": {
"tracy/tracy": "^2.9",
"nette/tester": "^2.5",
"nette/di": "^3.1",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-nette": "^2.0@stable",
"jetbrains/phpstorm-attributes": "^1.0"
},
"replace": {
"dg/dibi": "*"
},
"autoload": {
"classmap": ["src/"]
"classmap": ["src/"],
"psr-4": {
"Dibi\\": "src/Dibi"
}
},
"minimum-stability": "dev",
"scripts": {
"phpstan": "phpstan analyse",
"tester": "tester tests -s"
},
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
"dev-master": "6.0-dev"
}
}
}

View File

@@ -27,9 +27,9 @@ $dibi = new Dibi\Connection([
// using manual hints
$res = $dibi->query('SELECT * FROM [customers]');
$res->setType('customer_id', Type::INTEGER)
->setType('added', Type::DATETIME)
->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
$res->setType('customer_id', Type::Integer)
->setType('added', Type::DateTime)
->setFormat(Type::DateTime, 'Y-m-d H:i:s');
Tracy\Dumper::dump($res->fetch());

View File

@@ -45,7 +45,7 @@ function substFallBack($expr)
// define callback
$dibi->getSubstitutes()->setCallback('substFallBack');
$dibi->getSubstitutes()->setCallback(substFallBack(...));
// define substitutes as constants
define('SUBST_ACCOUNT', 'eshop_');

View File

@@ -34,7 +34,7 @@ Install Dibi via Composer:
composer require dibi/dibi
```
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.3.
The Dibi 6.0 requires PHP version 8.2 and supports PHP up to 8.5.
Usage
@@ -608,7 +608,7 @@ $database->query("UPDATE [:blog:items] SET [text]='Hello World'");
Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the `Dibi\Type` class.
```php
$result->setType('id', Dibi\Type::INTEGER); // id will be integer
$result->setType('id', Dibi\Type::Integer); // id will be integer
$row = $result->fetch();
is_int($row->id) // true

View File

@@ -19,14 +19,10 @@ use Tracy;
*/
class DibiExtension22 extends Nette\DI\CompilerExtension
{
private ?bool $debugMode;
private ?bool $cliMode;
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
{
$this->debugMode = $debugMode;
$this->cliMode = $cliMode;
public function __construct(
private ?bool $debugMode = null,
private ?bool $cliMode = null,
) {
}

View File

@@ -13,6 +13,7 @@ use Dibi;
use Nette;
use Nette\Schema\Expect;
use Tracy;
use function is_array;
/**
@@ -20,14 +21,10 @@ use Tracy;
*/
class DibiExtension3 extends Nette\DI\CompilerExtension
{
private ?bool $debugMode;
private ?bool $cliMode;
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
{
$this->debugMode = $debugMode;
$this->cliMode = $cliMode;
public function __construct(
private ?bool $debugMode = null,
private ?bool $cliMode = null,
) {
}
@@ -49,7 +46,7 @@ class DibiExtension3 extends Nette\DI\CompilerExtension
'formatDateTime' => Expect::string(),
'formatTimeInterval' => Expect::string(),
'formatJson' => Expect::string(),
]),
])->castTo('array'),
])->otherItems(Expect::type('mixed'))
->castTo('array');
}

View File

@@ -13,6 +13,7 @@ use Dibi;
use Dibi\Event;
use Dibi\Helpers;
use Tracy;
use function count, is_string, strlen;
/**
@@ -21,23 +22,22 @@ use Tracy;
class Panel implements Tracy\IBarPanel
{
public static int $maxLength = 1000;
public bool|string $explain;
public int $filter;
private array $events = [];
public function __construct(bool $explain = true, ?int $filter = null)
{
$this->filter = $filter ?: Event::QUERY;
$this->explain = $explain;
public function __construct(
public bool|string $explain = true,
public int $filter = Event::QUERY,
) {
}
public function register(Dibi\Connection $connection): void
{
Tracy\Debugger::getBar()->addPanel($this);
Tracy\Debugger::getBlueScreen()->addPanel([self::class, 'renderException']);
$connection->onEvent[] = [$this, 'logEvent'];
Tracy\Debugger::getBlueScreen()->addPanel(self::renderException(...));
$connection->onEvent[] = $this->logEvent(...);
}
@@ -151,8 +151,8 @@ class Panel implements Tracy\IBarPanel
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
<h1>Queries:' . "\u{a0}" . count($this->events)
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', '
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms")
. ($singleConnection === null ? '' : ', ' . htmlspecialchars($this->getConnectionName($singleConnection))) . '</h1>
<div class="tracy-inner tracy-DibiProfiler">
<table class="tracy-sortable">
<tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>

View File

@@ -11,6 +11,8 @@ namespace Dibi;
use JetBrains\PhpStorm\Language;
use Traversable;
use function array_key_exists, is_array, sprintf;
use const PHP_SAPI;
/**
@@ -19,15 +21,28 @@ use Traversable;
* @property-read int $affectedRows
* @property-read int $insertId
*/
class Connection implements IConnection
class Connection
{
private const Drivers = [
'firebird' => Drivers\Ibase\Connection::class,
'mysqli' => Drivers\MySQLi\Connection::class,
'odbc' => Drivers\ODBC\Connection::class,
'oracle' => Drivers\OCI8\Connection::class,
'pdo' => Drivers\PDO\Connection::class,
'postgre' => Drivers\PgSQL\Connection::class,
'sqlite3' => Drivers\SQLite3\Connection::class,
'sqlite' => Drivers\SQLite3\Connection::class,
'sqlsrv' => Drivers\SQLSrv\Connection::class,
];
/** function (Event $event); Occurs after query is executed */
public ?array $onEvent = [];
private array $config;
/** @var string[] resultset formats */
private array $formats;
private ?Driver $driver = null;
private ?Drivers\Connection $driver = null;
private Drivers\Engine $engine;
private ?Translator $translator = null;
/** @var array<string, callable(object): Expression | null> */
@@ -74,10 +89,10 @@ class Connection implements IConnection
$this->config = $config;
$this->formats = [
Type::DATE => $this->config['result']['formatDate'],
Type::DATETIME => $this->config['result']['formatDateTime'],
Type::Date => $this->config['result']['formatDate'],
Type::DateTime => $this->config['result']['formatDateTime'],
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
Type::TimeInterval => $this->config['result']['formatTimeInterval'] ?? null,
];
// profiler
@@ -120,17 +135,16 @@ class Connection implements IConnection
*/
final public function connect(): void
{
if ($this->config['driver'] instanceof Driver) {
if ($this->config['driver'] instanceof Drivers\Connection) {
$this->driver = $this->config['driver'];
$this->translator = new Translator($this);
return;
} elseif (is_subclass_of($this->config['driver'], Driver::class)) {
} elseif (is_subclass_of($this->config['driver'], Drivers\Connection::class)) {
$class = $this->config['driver'];
} else {
$class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($this->config['driver'])));
$class = "Dibi\\Drivers\\{$class}Driver";
$class = self::Drivers[strtolower($this->config['driver'])] ?? throw new Exception("Unknown driver '{$this->config['driver']}'.");
if (!class_exists($class)) {
throw new Exception("Unable to create instance of Dibi driver '$class'.");
}
@@ -196,7 +210,7 @@ class Connection implements IConnection
/**
* Returns the driver and connects to a database in lazy mode.
*/
final public function getDriver(): Driver
final public function getDriver(): Drivers\Connection
{
if (!$this->driver) {
$this->connect();
@@ -284,7 +298,7 @@ class Connection implements IConnection
throw $e;
}
$res = $this->createResultSet($res ?: new Drivers\NoDataResult(max(0, $this->driver->getAffectedRows())));
$res = $this->createResultSet($res ?: new Drivers\Dummy\Result(max(0, $this->driver->getAffectedRows())));
if ($event) {
$this->onEvent($event->done($res));
}
@@ -448,7 +462,7 @@ class Connection implements IConnection
/**
* Result set factory.
*/
public function createResultSet(ResultDriver $resultDriver): Result
public function createResultSet(Drivers\Result $resultDriver): Result
{
return (new Result($resultDriver, $this->config['result']['normalize'] ?? true))
->setFormats($this->formats);
@@ -657,6 +671,15 @@ class Connection implements IConnection
}
public function getDatabaseEngine(): Drivers\Engine
{
if (!$this->driver) { // TODO
$this->connect();
}
return $this->engine ??= $this->driver->getReflector();
}
/**
* Gets a information about the current database.
*/
@@ -666,14 +689,14 @@ class Connection implements IConnection
$this->connect();
}
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
return new Reflection\Database($this->getDatabaseEngine(), $this->config['database'] ?? null);
}
/**
* Prevents unserialization.
*/
public function __wakeup()
public function __unserialize($_)
{
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
}
@@ -682,7 +705,7 @@ class Connection implements IConnection
/**
* Prevents serialization.
*/
public function __sleep()
public function __serialize()
{
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
}

View File

@@ -9,14 +9,16 @@ declare(strict_types=1);
namespace Dibi;
use function func_get_args, is_array, strpbrk;
/**
* Default implementation of IDataSource.
*/
class DataSource implements IDataSource
{
private Connection $connection;
private string $sql;
private readonly Connection $connection;
private readonly string $sql;
private ?Result $result = null;
private ?int $count = null;
private ?int $totalCount = null;
@@ -33,7 +35,7 @@ class DataSource implements IDataSource
public function __construct(string $sql, Connection $connection)
{
$this->sql = strpbrk($sql, " \t\r\n") === false
? $connection->getDriver()->escapeIdentifier($sql) // table name
? $connection->getDatabaseEngine()->escapeIdentifier($sql) // table name
: '(' . $sql . ') t'; // SQL command
$this->connection = $connection;
}

View File

@@ -0,0 +1,77 @@
<?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\DriverException;
use Dibi\Exception;
/**
* Database connection driver.
*/
interface Connection
{
/**
* Disconnects from a database.
* @throws Exception
*/
function disconnect(): void;
/**
* Internal: Executes the SQL query.
* @throws DriverException
*/
function query(string $sql): ?Result;
/**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
*/
function getAffectedRows(): ?int;
/**
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
*/
function getInsertId(?string $sequence): ?int;
/**
* Begins a transaction (if supported).
* @throws DriverException
*/
function begin(?string $savepoint = null): void;
/**
* Commits statements in a transaction.
* @throws DriverException
*/
function commit(?string $savepoint = null): void;
/**
* Rollback changes in a transaction.
* @throws DriverException
*/
function rollback(?string $savepoint = null): void;
/**
* Returns the connection resource.
*/
function getResource(): mixed;
/**
* Returns the connection reflector.
*/
function getReflector(): Engine;
/**
* Encodes data for use in a SQL statement.
*/
function escapeText(string $value): string;
function escapeBinary(string $value): string;
}

View File

@@ -7,22 +7,23 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Dummy;
use Dibi;
use Dibi\Drivers;
/**
* The dummy driver for testing purposes.
*/
class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
class Connection implements Drivers\Connection, Drivers\Result, Drivers\Engine
{
public function disconnect(): void
{
}
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
return null;
}
@@ -64,7 +65,7 @@ class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
public function getReflector(): Drivers\Engine
{
return $this;
}

View File

@@ -7,22 +7,19 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Dummy;
use Dibi;
use Dibi\Drivers;
/**
* The driver for no result set.
*/
class NoDataResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
private int $rows;
public function __construct(int $rows)
{
$this->rows = $rows;
public function __construct(
private readonly int $rows,
) {
}

View File

@@ -0,0 +1,60 @@
<?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;
/**
* Engine-specific behaviors.
*/
interface Engine
{
function escapeIdentifier(string $value): string;
function escapeBool(bool $value): string;
function escapeDate(\DateTimeInterface $value): string;
function escapeDateTime(\DateTimeInterface $value): string;
function escapeDateInterval(\DateInterval $value): string;
/**
* Encodes string for use in a LIKE statement.
*/
function escapeLike(string $value, int $pos): string;
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
function applyLimit(string &$sql, ?int $limit, ?int $offset): void;
/**
* Returns list of tables.
* @return array of {name [, (bool) view ]}
*/
function getTables(): array;
/**
* Returns metadata for all columns in a table.
* @return array of {name, nativetype [, table, fullname, (int) size, (bool) nullable, (mixed) default, (bool) autoincrement, (array) vendor ]}
*/
function getColumns(string $table): array;
/**
* Returns metadata for all indexes in a table.
* @return array of {name, (array of names) columns [, (bool) unique, (bool) primary ]}
*/
function getIndexes(string $table): array;
/**
* Returns metadata for all foreign keys in a table.
*/
function getForeignKeys(string $table): array;
}

View File

@@ -7,22 +7,73 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
/**
* The reflector for Firebird/InterBase database.
*/
class FirebirdReflector implements Dibi\Reflector
class FirebirdEngine implements Engine
{
private Dibi\Driver $driver;
public function __construct(
private readonly Connection $driver,
) {
}
public function __construct(Dibi\Driver $driver)
public function escapeIdentifier(string $value): string
{
$this->driver = $driver;
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes($this->driver->escapeText($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit > 0 || $offset > 0) {
// http://www.firebirdsql.org/refdocs/langrefupd20-select.html
$sql = 'SELECT ' . ($limit > 0 ? 'FIRST ' . $limit : '') . ($offset > 0 ? ' SKIP ' . $offset : '') . ' * FROM (' . $sql . ')';
}
}

View File

@@ -7,23 +7,82 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
/**
* The reflector for MySQL databases.
* @internal
*/
class MySqlReflector implements Dibi\Reflector
class MySQLEngine implements Engine
{
private Dibi\Driver $driver;
public function __construct(
private readonly Connection $driver,
) {
}
public function __construct(Dibi\Driver $driver)
public function escapeIdentifier(string $value): string
{
$this->driver = $driver;
return '`' . str_replace('`', '``', $value) . '`';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
if ($value->y || $value->m || $value->d) {
throw new Dibi\NotSupportedException('Only time interval is supported.');
}
return $value->format("'%r%H:%I:%S.%f'");
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null || $offset) {
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
. ($offset ? ' OFFSET ' . $offset : '');
}
}
@@ -50,7 +109,7 @@ class MySqlReflector implements Dibi\Reflector
*/
public function getColumns(string $table): array
{
$res = $this->driver->query("SHOW FULL COLUMNS FROM {$this->driver->escapeIdentifier($table)}");
$res = $this->driver->query("SHOW FULL COLUMNS FROM {$this->escapeIdentifier($table)}");
$columns = [];
while ($row = $res->fetch(true)) {
$type = explode('(', $row['Type']);
@@ -75,7 +134,7 @@ class MySqlReflector implements Dibi\Reflector
*/
public function getIndexes(string $table): array
{
$res = $this->driver->query("SHOW INDEX FROM {$this->driver->escapeIdentifier($table)}");
$res = $this->driver->query("SHOW INDEX FROM {$this->escapeIdentifier($table)}");
$indexes = [];
while ($row = $res->fetch(true)) {
$indexes[$row['Key_name']]['name'] = $row['Key_name'];

View File

@@ -7,22 +7,78 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
/**
* The reflector for ODBC connections.
*/
class OdbcReflector implements Dibi\Reflector
class ODBCEngine implements Engine
{
private Dibi\Driver $driver;
public function __construct(
private readonly Connection $driver,
) {
}
public function __construct(Dibi\Driver $driver)
public function escapeIdentifier(string $value): string
{
$this->driver = $driver;
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format('#m/d/Y#');
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#'); // TODO
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
} elseif ($limit < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null) {
$sql = 'SELECT TOP ' . $limit . ' * FROM (' . $sql . ') t';
}
}

View File

@@ -0,0 +1,153 @@
<?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\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
/**
* The reflector for Oracle database.
*/
class OracleEngine implements Engine
{
public function __construct(
private readonly Connection $driver,
) {
}
public function escapeIdentifier(string $value): string
{
// @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $this->nativeDate // TODO
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
: $value->format('U');
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $this->nativeDate // TODO
? "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.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($offset) {
// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
. ($limit !== null ? 'WHERE ROWNUM <= ' . ($offset + $limit) : '')
. ') WHERE "__rnum" > ' . $offset;
} elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
}
}
/**
* Returns list of tables.
*/
public function getTables(): array
{
$res = $this->driver->query('SELECT * FROM cat');
$tables = [];
while ($row = $res->fetch(false)) {
if ($row[1] === 'TABLE' || $row[1] === 'VIEW') {
$tables[] = [
'name' => $row[0],
'view' => $row[1] === 'VIEW',
];
}
}
return $tables;
}
/**
* Returns metadata for all columns in a table.
*/
public function getColumns(string $table): array
{
$res = $this->driver->query('SELECT * FROM "ALL_TAB_COLUMNS" WHERE "TABLE_NAME" = ' . $this->driver->escapeText($table));
$columns = [];
while ($row = $res->fetch(true)) {
$columns[] = [
'table' => $row['TABLE_NAME'],
'name' => $row['COLUMN_NAME'],
'nativetype' => $row['DATA_TYPE'],
'size' => $row['DATA_LENGTH'] ?? null,
'nullable' => $row['NULLABLE'] === 'Y',
'default' => $row['DATA_DEFAULT'],
'vendor' => $row,
];
}
return $columns;
}
/**
* Returns metadata for all indexes in a table.
*/
public function getIndexes(string $table): array
{
throw new Dibi\NotImplementedException;
}
/**
* Returns metadata for all foreign keys in a table.
*/
public function getForeignKeys(string $table): array
{
throw new Dibi\NotImplementedException;
}
}

View File

@@ -7,24 +7,83 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
/**
* The reflector for PostgreSQL database.
*/
class PostgreReflector implements Dibi\Reflector
class PostgreSQLEngine implements Engine
{
private Dibi\Driver $driver;
private string $version;
public function __construct(
private readonly Connection $driver,
) {
}
public function __construct(Dibi\Driver $driver, string $version)
public function escapeIdentifier(string $value): string
{
$this->driver = $driver;
$this->version = $version;
// @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? 'TRUE' : 'FALSE';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$bs = $this->driver->escapeText('\\'); // standard_conforming_strings = on/off
$value = $this->driver->escapeText($value);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
}
if ($limit !== null) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}
}
@@ -43,18 +102,15 @@ class PostgreReflector implements Dibi\Reflector
FROM
information_schema.tables
WHERE
table_schema = ANY (current_schemas(false))";
table_schema = ANY (current_schemas(false))
if ($this->version >= 9.3) {
$query .= '
UNION ALL
SELECT
matviewname, 1
FROM
pg_matviews
WHERE
schemaname = ANY (current_schemas(false))';
}
UNION ALL
SELECT
matviewname, 1
FROM
pg_matviews
WHERE
schemaname = ANY (current_schemas(false))";
$res = $this->driver->query($query);
$tables = [];
@@ -71,7 +127,7 @@ class PostgreReflector implements Dibi\Reflector
*/
public function getColumns(string $table): array
{
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
$res = $this->driver->query("
SELECT *
@@ -131,7 +187,7 @@ class PostgreReflector implements Dibi\Reflector
*/
public function getIndexes(string $table): array
{
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
$res = $this->driver->query("
SELECT
a.attnum AS ordinal_position,
@@ -181,7 +237,7 @@ class PostgreReflector implements Dibi\Reflector
*/
public function getForeignKeys(string $table): array
{
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
$res = $this->driver->query("
SELECT

View File

@@ -7,22 +7,80 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
/**
* The reflector for Microsoft SQL Server and SQL Azure databases.
*/
class SqlsrvReflector implements Dibi\Reflector
class SQLServerEngine implements Engine
{
private Dibi\Driver $driver;
public function __construct(
private readonly Connection $driver,
) {
}
public function __construct(Dibi\Driver $driver)
public function escapeIdentifier(string $value): string
{
$this->driver = $driver;
// @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
return '[' . str_replace(']', ']]', $value) . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
} elseif ($offset) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
}
}

View File

@@ -7,22 +7,76 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
/**
* The reflector for SQLite database.
*/
class SqliteReflector implements Dibi\Reflector
class SQLiteEngine implements Engine
{
private Dibi\Driver $driver;
public function __construct(
private readonly Connection $driver,
) {
}
public function __construct(Dibi\Driver $driver)
public function escapeIdentifier(string $value): string
{
$this->driver = $driver;
return '[' . strtr($value, '[]', ' ') . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format($this->fmtDate); // TODO
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format($this->fmtDateTime); // TODO
}
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->driver->escapeText($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " 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 : '');
}
}
@@ -51,7 +105,7 @@ class SqliteReflector implements Dibi\Reflector
*/
public function getColumns(string $table): array
{
$res = $this->driver->query("PRAGMA table_info({$this->driver->escapeIdentifier($table)})");
$res = $this->driver->query("PRAGMA table_info({$this->escapeIdentifier($table)})");
$columns = [];
while ($row = $res->fetch(true)) {
$column = $row['name'];
@@ -78,7 +132,7 @@ class SqliteReflector implements Dibi\Reflector
*/
public function getIndexes(string $table): array
{
$res = $this->driver->query("PRAGMA index_list({$this->driver->escapeIdentifier($table)})");
$res = $this->driver->query("PRAGMA index_list({$this->escapeIdentifier($table)})");
$indexes = [];
while ($row = $res->fetch(true)) {
$indexes[$row['name']]['name'] = $row['name'];
@@ -86,7 +140,7 @@ class SqliteReflector implements Dibi\Reflector
}
foreach ($indexes as $index => $values) {
$res = $this->driver->query("PRAGMA index_info({$this->driver->escapeIdentifier($index)})");
$res = $this->driver->query("PRAGMA index_info({$this->escapeIdentifier($index)})");
while ($row = $res->fetch(true)) {
$indexes[$index]['columns'][$row['seqno']] = $row['name'];
}
@@ -129,7 +183,7 @@ class SqliteReflector implements Dibi\Reflector
*/
public function getForeignKeys(string $table): array
{
$res = $this->driver->query("PRAGMA foreign_key_list({$this->driver->escapeIdentifier($table)})");
$res = $this->driver->query("PRAGMA foreign_key_list({$this->escapeIdentifier($table)})");
$keys = [];
while ($row = $res->fetch(true)) {
$keys[$row['id']]['name'] = $row['id']; // foreign key name

View File

@@ -7,10 +7,12 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Ibase;
use Dibi;
use Dibi\Drivers;
use Dibi\Helpers;
use function is_resource;
/**
@@ -24,9 +26,12 @@ use Dibi\Helpers;
* - buffers (int) => buffers is the number of database buffers to allocate for the server-side cache. If 0 or omitted, server chooses its own default.
* - resource (resource) => existing connection resource
*/
class FirebirdDriver implements Dibi\Driver
class Connection implements Drivers\Connection
{
public const ERROR_EXCEPTION_THROWN = -836;
public const ErrorExceptionThrown = -836;
#[\Deprecated('use FirebirdDriver::ErrorExceptionThrown')]
public const ERROR_EXCEPTION_THROWN = self::ErrorExceptionThrown;
/** @var resource */
private $connection;
@@ -82,7 +87,7 @@ class FirebirdDriver implements Dibi\Driver
* Executes the SQL query.
* @throws Dibi\DriverException|Dibi\Exception
*/
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
$resource = $this->inTransaction
? $this->transaction
@@ -196,9 +201,9 @@ class FirebirdDriver implements Dibi\Driver
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
public function getReflector(): Drivers\Engine
{
return new FirebirdReflector($this);
return new Drivers\Engines\FirebirdEngine($this);
}
@@ -206,9 +211,9 @@ class FirebirdDriver implements Dibi\Driver
* Result set driver factory.
* @param resource $resource
*/
public function createResultDriver($resource): FirebirdResult
public function createResultDriver($resource): Result
{
return new FirebirdResult($resource);
return new Result($resource);
}
@@ -228,56 +233,4 @@ class FirebirdDriver implements Dibi\Driver
{
return "'" . str_replace("'", "''", $value) . "'";
}
public function escapeIdentifier(string $value): string
{
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes($this->escapeText($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit > 0 || $offset > 0) {
// http://www.firebirdsql.org/refdocs/langrefupd20-select.html
$sql = 'SELECT ' . ($limit > 0 ? 'FIRST ' . $limit : '') . ($offset > 0 ? ' SKIP ' . $offset : '') . ' * FROM (' . $sql . ')';
}
}
}

View File

@@ -7,27 +7,23 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\Ibase;
use Dibi;
use Dibi\Drivers;
use Dibi\Helpers;
use function is_resource;
/**
* The driver for Firebird/InterBase result set.
*/
class FirebirdResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
/** @var resource */
private $resultSet;
/**
* @param resource $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
/** @var resource */
private $resultSet,
) {
}
@@ -51,7 +47,7 @@ class FirebirdResult implements Dibi\ResultDriver
: @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
if (ibase_errcode()) {
if (ibase_errcode() === FirebirdDriver::ERROR_EXCEPTION_THROWN) {
if (ibase_errcode() === Connection::ERROR_EXCEPTION_THROWN) {
preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);

View File

@@ -7,9 +7,12 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\MySQLi;
use Dibi;
use Dibi\Drivers;
use function in_array;
use const MYSQLI_REPORT_OFF, MYSQLI_STORE_RESULT, MYSQLI_USE_RESULT, PREG_SET_ORDER;
/**
@@ -30,11 +33,20 @@ use Dibi;
* - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
* - resource (mysqli) => existing connection resource
*/
class MySqliDriver implements Dibi\Driver
class Connection implements Drivers\Connection
{
public const ERROR_ACCESS_DENIED = 1045;
public const ERROR_DUPLICATE_ENTRY = 1062;
public const ERROR_DATA_TRUNCATED = 1265;
public const ErrorAccessDenied = 1045;
public const ErrorDuplicateEntry = 1062;
public const ErrorDataTruncated = 1265;
#[\Deprecated('use MySqliDriver::ErrorAccessDenied')]
public const ERROR_ACCESS_DENIED = self::ErrorAccessDenied;
#[\Deprecated('use MySqliDriver::ErrorDuplicateEntry')]
public const ERROR_DUPLICATE_ENTRY = self::ErrorDuplicateEntry;
#[\Deprecated('use MySqliDriver::ErrorDataTruncated')]
public const ERROR_DATA_TRUNCATED = self::ErrorDataTruncated;
private \mysqli $connection;
private bool $buffered = false;
@@ -137,7 +149,7 @@ class MySqliDriver implements Dibi\Driver
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
$res = @$this->connection->query($sql, $this->buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); // intentionally @
@@ -254,18 +266,18 @@ class MySqliDriver implements Dibi\Driver
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
public function getReflector(): Drivers\Engine
{
return new MySqlReflector($this);
return new Drivers\Engines\MySQLEngine($this);
}
/**
* Result set driver factory.
*/
public function createResultDriver(\mysqli_result $result): MySqliResult
public function createResultDriver(\mysqli_result $result): Result
{
return new MySqliResult($result, $this->buffered);
return new Result($result, $this->buffered);
}
@@ -285,64 +297,4 @@ class MySqliDriver implements Dibi\Driver
{
return "_binary'" . $this->connection->escape_string($value) . "'";
}
public function escapeIdentifier(string $value): string
{
return '`' . str_replace('`', '``', $value) . '`';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
if ($value->y || $value->m || $value->d) {
throw new Dibi\NotSupportedException('Only time interval is supported.');
}
return $value->format("'%r%H:%I:%S.%f'");
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null || $offset) {
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
. ($offset ? ' OFFSET ' . $offset : '');
}
}
}

View File

@@ -7,24 +7,22 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\MySQLi;
use Dibi;
use Dibi\Drivers;
use const MYSQLI_TYPE_LONG, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_TIME, MYSQLI_TYPE_TINY;
/**
* The driver for MySQL result set.
*/
class MySqliResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
private \mysqli_result $resultSet;
private bool $buffered;
public function __construct(\mysqli_result $resultSet, bool $buffered)
{
$this->resultSet = $resultSet;
$this->buffered = $buffered;
public function __construct(
private readonly \mysqli_result $resultSet,
private readonly bool $buffered,
) {
}
@@ -103,7 +101,7 @@ class MySqliResult implements Dibi\ResultDriver
'table' => $row['orgtable'],
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'nativetype' => $types[$row['type']] ?? $row['type'],
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TIME_INTERVAL : null,
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TimeInterval : null,
'vendor' => $row,
];
}

View File

@@ -7,9 +7,11 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\OCI8;
use Dibi;
use Dibi\Drivers;
use function in_array, is_resource;
/**
@@ -25,7 +27,7 @@ use Dibi;
* - resource (resource) => existing connection resource
* - persistent => Creates persistent connections with oci_pconnect instead of oci_new_connect
*/
class OracleDriver implements Dibi\Driver
class Connection implements Drivers\Connection
{
/** @var resource */
private $connection;
@@ -76,7 +78,7 @@ class OracleDriver implements Dibi\Driver
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
$this->affectedRows = null;
$res = oci_parse($this->connection, $sql);
@@ -189,9 +191,9 @@ class OracleDriver implements Dibi\Driver
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
public function getReflector(): Drivers\Engine
{
return new OracleReflector($this);
return new Drivers\Engines\OracleEngine($this);
}
@@ -199,9 +201,9 @@ class OracleDriver implements Dibi\Driver
* Result set driver factory.
* @param resource $resource
*/
public function createResultDriver($resource): OracleResult
public function createResultDriver($resource): Result
{
return new OracleResult($resource);
return new Result($resource);
}
@@ -221,70 +223,4 @@ class OracleDriver implements Dibi\Driver
{
return "'" . str_replace("'", "''", $value) . "'"; // TODO: not tested
}
public function escapeIdentifier(string $value): string
{
// @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $this->nativeDate
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
: $value->format('U');
}
public function escapeDateTime(\DateTimeInterface $value): string
{
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.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($offset) {
// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
. ($limit !== null ? 'WHERE ROWNUM <= ' . ($offset + $limit) : '')
. ') WHERE "__rnum" > ' . $offset;
} elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
}
}
}

View File

@@ -7,26 +7,22 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\OCI8;
use Dibi;
use Dibi\Drivers;
use function is_resource;
/**
* The driver for Oracle result set.
*/
class OracleResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
/** @var resource */
private $resultSet;
/**
* @param resource $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
/** @var resource */
private $resultSet,
) {
}
@@ -80,7 +76,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,
'type' => $type === 'LONG' ? Dibi\Type::Text : null,
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
];
}

View File

@@ -7,9 +7,11 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\ODBC;
use Dibi;
use Dibi\Drivers;
use function is_resource;
/**
@@ -23,7 +25,7 @@ use Dibi;
* - resource (resource) => existing connection resource
* - microseconds (bool) => use microseconds in datetime format?
*/
class OdbcDriver implements Dibi\Driver
class Connection implements Drivers\Connection
{
/** @var resource */
private $connection;
@@ -76,7 +78,7 @@ class OdbcDriver implements Dibi\Driver
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
$this->affectedRows = null;
$res = @odbc_exec($this->connection, $sql); // intentionally @
@@ -175,9 +177,9 @@ class OdbcDriver implements Dibi\Driver
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
public function getReflector(): Drivers\Engine
{
return new OdbcReflector($this);
return new Drivers\Engines\ODBCEngine($this);
}
@@ -185,9 +187,9 @@ class OdbcDriver implements Dibi\Driver
* Result set driver factory.
* @param resource $resource
*/
public function createResultDriver($resource): OdbcResult
public function createResultDriver($resource): Result
{
return new OdbcResult($resource);
return new Result($resource);
}
@@ -207,61 +209,4 @@ class OdbcDriver implements Dibi\Driver
{
return "'" . str_replace("'", "''", $value) . "'";
}
public function escapeIdentifier(string $value): string
{
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format('#m/d/Y#');
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#');
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
} elseif ($limit < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null) {
$sql = 'SELECT TOP ' . $limit . ' * FROM (' . $sql . ') t';
}
}
}

View File

@@ -7,27 +7,25 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\ODBC;
use Dibi;
use Dibi\Drivers;
use function is_resource;
/**
* The driver interacting with result set via ODBC connections.
*/
class OdbcResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
/** @var resource */
private $resultSet;
private int $row = 0;
/**
* @param resource $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
/** @var resource */
private $resultSet,
) {
}

View File

@@ -1,88 +0,0 @@
<?php
/**
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
/**
* The reflector for Oracle database.
*/
class OracleReflector implements Dibi\Reflector
{
private Dibi\Driver $driver;
public function __construct(Dibi\Driver $driver)
{
$this->driver = $driver;
}
/**
* Returns list of tables.
*/
public function getTables(): array
{
$res = $this->driver->query('SELECT * FROM cat');
$tables = [];
while ($row = $res->fetch(false)) {
if ($row[1] === 'TABLE' || $row[1] === 'VIEW') {
$tables[] = [
'name' => $row[0],
'view' => $row[1] === 'VIEW',
];
}
}
return $tables;
}
/**
* Returns metadata for all columns in a table.
*/
public function getColumns(string $table): array
{
$res = $this->driver->query('SELECT * FROM "ALL_TAB_COLUMNS" WHERE "TABLE_NAME" = ' . $this->driver->escapeText($table));
$columns = [];
while ($row = $res->fetch(true)) {
$columns[] = [
'table' => $row['TABLE_NAME'],
'name' => $row['COLUMN_NAME'],
'nativetype' => $row['DATA_TYPE'],
'size' => $row['DATA_LENGTH'] ?? null,
'nullable' => $row['NULLABLE'] === 'Y',
'default' => $row['DATA_DEFAULT'],
'vendor' => $row,
];
}
return $columns;
}
/**
* Returns metadata for all indexes in a table.
*/
public function getIndexes(string $table): array
{
throw new Dibi\NotImplementedException;
}
/**
* Returns metadata for all foreign keys in a table.
*/
public function getForeignKeys(string $table): array
{
throw new Dibi\NotImplementedException;
}
}

View File

@@ -0,0 +1,223 @@
<?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\PDO;
use Dibi;
use Dibi\Drivers;
use Dibi\Drivers\Engines;
use Dibi\Helpers;
use PDO;
use function sprintf;
/**
* The driver for PDO.
*
* Driver options:
* - dsn => driver specific DSN
* - username (or user)
* - password (or pass)
* - options (array) => driver specific options {@see PDO::__construct}
* - resource (PDO) => existing connection
*/
class Connection implements Drivers\Connection
{
private ?PDO $connection;
private ?int $affectedRows;
private string $driverName;
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
{
if (!extension_loaded('pdo')) {
throw new Dibi\NotSupportedException("PHP extension 'pdo' is not loaded.");
}
$foo = &$config['dsn'];
$foo = &$config['options'];
Helpers::alias($config, 'resource', 'pdo');
if ($config['resource'] instanceof PDO) {
$this->connection = $config['resource'];
unset($config['resource'], $config['pdo']);
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
}
} else {
try {
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
} catch (\PDOException $e) {
if ($e->getMessage() === 'could not find driver') {
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
}
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
}
}
$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
}
/**
* Disconnects from a database.
*/
public function disconnect(): void
{
$this->connection = null;
}
/**
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Result
{
$res = $this->connection->query($sql);
if ($res) {
$this->affectedRows = $res->rowCount();
return $res->columnCount() ? $this->createResultDriver($res) : null;
}
$this->affectedRows = null;
[$sqlState, $code, $message] = $this->connection->errorInfo();
$code ??= 0;
$message = "SQLSTATE[$sqlState]: $message";
throw match ($this->driverName) {
'mysql' => Drivers\MySQLi\Connection::createException($message, $code, $sql),
'oci' => Drivers\OCI8\Connection::createException($message, $code, $sql),
'pgsql' => Drivers\PgSQL\Connection::createException($message, $sqlState, $sql),
'sqlite' => Drivers\SQLite3\Connection::createException($message, $code, $sql),
default => new Dibi\DriverException($message, $code, $sql),
};
}
/**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
*/
public function getAffectedRows(): ?int
{
return $this->affectedRows;
}
/**
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
*/
public function getInsertId(?string $sequence): ?int
{
return Helpers::intVal($this->connection->lastInsertId($sequence));
}
/**
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(?string $savepoint = null): void
{
if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
}
}
/**
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
{
if (!$this->connection->commit()) {
$err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
}
}
/**
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(?string $savepoint = null): void
{
if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
}
}
/**
* Returns the connection resource.
*/
public function getResource(): ?PDO
{
return $this->connection;
}
/**
* Returns the connection reflector.
*/
public function getReflector(): Drivers\Engine
{
return match ($this->driverName) {
'mysql' => new Engines\MySQLEngine($this),
'oci' => new Engines\OracleEngine($this),
'pgsql' => new Engines\PostgreSQLEngine($this),
'sqlite' => new Engines\SQLiteEngine($this),
'mssql', 'dblib', 'sqlsrv' => new Engines\SQLServerEngine($this),
default => throw new Dibi\NotSupportedException,
};
}
/**
* Result set driver factory.
*/
public function createResultDriver(\PDOStatement $result): Result
{
return new Result($result, $this->driverName);
}
/********************* SQL ****************d*g**/
/**
* Encodes data for use in a SQL statement.
*/
public function escapeText(string $value): string
{
return match ($this->driverName) {
'odbc' => "'" . str_replace("'", "''", $value) . "'",
'sqlsrv' => "N'" . str_replace("'", "''", $value) . "'",
default => $this->connection->quote($value, PDO::PARAM_STR),
};
}
public function escapeBinary(string $value): string
{
return match ($this->driverName) {
'odbc' => "'" . str_replace("'", "''", $value) . "'",
'sqlsrv' => '0x' . bin2hex($value),
default => $this->connection->quote($value, PDO::PARAM_LOB),
};
}
}

View File

@@ -7,9 +7,10 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\PDO;
use Dibi;
use Dibi\Drivers;
use Dibi\Helpers;
use PDO;
@@ -17,16 +18,12 @@ use PDO;
/**
* The driver for PDO result set.
*/
class PdoResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
private ?\PDOStatement $resultSet;
private string $driverName;
public function __construct(\PDOStatement $resultSet, string $driverName)
{
$this->resultSet = $resultSet;
$this->driverName = $driverName;
public function __construct(
private ?\PDOStatement $resultSet,
private readonly string $driverName,
) {
}
@@ -90,7 +87,7 @@ class PdoResult implements Dibi\ResultDriver
'name' => $row['name'],
'table' => $row['table'],
'nativetype' => $row['native_type'],
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TIME_INTERVAL : null,
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TimeInterval : null,
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'vendor' => $row,
];

View File

@@ -1,414 +0,0 @@
<?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 PDO;
/**
* The driver for PDO.
*
* Driver options:
* - dsn => driver specific DSN
* - username (or user)
* - password (or pass)
* - options (array) => driver specific options {@see PDO::__construct}
* - resource (PDO) => existing connection
* - version
*/
class PdoDriver implements Dibi\Driver
{
private ?PDO $connection;
private ?int $affectedRows;
private string $driverName;
private string $serverVersion = '';
/** @throws Dibi\NotSupportedException */
public function __construct(array $config)
{
if (!extension_loaded('pdo')) {
throw new Dibi\NotSupportedException("PHP extension 'pdo' is not loaded.");
}
$foo = &$config['dsn'];
$foo = &$config['options'];
Helpers::alias($config, 'resource', 'pdo');
if ($config['resource'] instanceof PDO) {
$this->connection = $config['resource'];
unset($config['resource'], $config['pdo']);
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
}
} else {
try {
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
} catch (\PDOException $e) {
if ($e->getMessage() === 'could not find driver') {
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
}
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
}
}
$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
}
/**
* Disconnects from a database.
*/
public function disconnect(): void
{
$this->connection = null;
}
/**
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
{
$res = $this->connection->query($sql);
if ($res) {
$this->affectedRows = $res->rowCount();
return $res->columnCount() ? $this->createResultDriver($res) : null;
}
$this->affectedRows = null;
[$sqlState, $code, $message] = $this->connection->errorInfo();
$code ??= 0;
$message = "SQLSTATE[$sqlState]: $message";
throw match ($this->driverName) {
'mysql' => MySqliDriver::createException($message, $code, $sql),
'oci' => OracleDriver::createException($message, $code, $sql),
'pgsql' => PostgreDriver::createException($message, $sqlState, $sql),
'sqlite' => SqliteDriver::createException($message, $code, $sql),
default => new Dibi\DriverException($message, $code, $sql),
};
}
/**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
*/
public function getAffectedRows(): ?int
{
return $this->affectedRows;
}
/**
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
*/
public function getInsertId(?string $sequence): ?int
{
return Helpers::intVal($this->connection->lastInsertId($sequence));
}
/**
* Begins a transaction (if supported).
* @throws Dibi\DriverException
*/
public function begin(?string $savepoint = null): void
{
if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
}
}
/**
* Commits statements in a transaction.
* @throws Dibi\DriverException
*/
public function commit(?string $savepoint = null): void
{
if (!$this->connection->commit()) {
$err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
}
}
/**
* Rollback changes in a transaction.
* @throws Dibi\DriverException
*/
public function rollback(?string $savepoint = null): void
{
if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
}
}
/**
* Returns the connection resource.
*/
public function getResource(): ?PDO
{
return $this->connection;
}
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
{
switch ($this->driverName) {
case 'mysql':
return new MySqlReflector($this);
case 'oci':
return new OracleReflector($this);
case 'pgsql':
return new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION));
case 'sqlite':
return new SqliteReflector($this);
case 'mssql':
case 'dblib':
case 'sqlsrv':
return new SqlsrvReflector($this);
default:
throw new Dibi\NotSupportedException;
}
}
/**
* Result set driver factory.
*/
public function createResultDriver(\PDOStatement $result): PdoResult
{
return new PdoResult($result, $this->driverName);
}
/********************* SQL ****************d*g**/
/**
* Encodes data for use in a SQL statement.
*/
public function escapeText(string $value): string
{
return $this->driverName === 'odbc'
? "'" . str_replace("'", "''", $value) . "'"
: $this->connection->quote($value, PDO::PARAM_STR);
}
public function escapeBinary(string $value): string
{
return $this->driverName === 'odbc'
? "'" . str_replace("'", "''", $value) . "'"
: $this->connection->quote($value, PDO::PARAM_LOB);
}
public function escapeIdentifier(string $value): string
{
switch ($this->driverName) {
case 'mysql':
return '`' . str_replace('`', '``', $value) . '`';
case 'oci':
case 'pgsql':
return '"' . str_replace('"', '""', $value) . '"';
case 'sqlite':
return '[' . strtr($value, '[]', ' ') . ']';
case 'odbc':
case 'mssql':
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
case 'dblib':
case 'sqlsrv':
return '[' . str_replace(']', ']]', $value) . ']';
default:
return $value;
}
}
public function escapeBool(bool $value): string
{
if ($this->driverName === 'pgsql') {
return $value ? 'TRUE' : 'FALSE';
} else {
return $value ? '1' : '0';
}
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
switch ($this->driverName) {
case 'odbc':
return $value->format('#m/d/Y H:i:s.u#');
case 'mssql':
case 'dblib':
case 'sqlsrv':
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
default:
return $value->format("'Y-m-d H:i:s.u'");
}
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
switch ($this->driverName) {
case 'mysql':
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
case 'oci':
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
case 'pgsql':
$bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
case 'sqlite':
$value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
case 'odbc':
case 'mssql':
case 'dblib':
case 'sqlsrv':
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
default:
throw new Dibi\NotImplementedException;
}
}
/**
* 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.');
}
switch ($this->driverName) {
case 'mysql':
if ($limit !== null || $offset) {
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
. ($offset ? ' OFFSET ' . $offset : '');
}
break;
case 'pgsql':
if ($limit !== null) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}
break;
case 'sqlite':
if ($limit !== null || $offset) {
$sql .= ' LIMIT ' . ($limit ?? '-1')
. ($offset ? ' OFFSET ' . $offset : '');
}
break;
case 'oci':
if ($offset) {
// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
. ($limit !== null ? 'WHERE ROWNUM <= ' . ($offset + $limit) : '')
. ') WHERE "__rnum" > ' . $offset;
} elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
}
break;
case 'mssql':
case 'sqlsrv':
case 'dblib':
if (version_compare($this->serverVersion, '11.0') >= 0) { // 11 == SQL Server 2012
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
if ($limit !== null) {
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
} elseif ($offset) {
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
}
break;
}
// break omitted
case 'odbc':
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
} elseif ($limit !== null) {
$sql = 'SELECT TOP ' . $limit . ' * FROM (' . $sql . ') t';
break;
}
// break omitted
default:
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
}
}
}

View File

@@ -7,11 +7,13 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\PgSQL;
use Dibi;
use Dibi\Drivers;
use Dibi\Helpers;
use PgSql;
use function in_array, is_array, is_resource, strlen;
/**
@@ -23,13 +25,12 @@ use PgSql;
* - schema => the schema search path
* - charset => character encoding to set (default is utf8)
* - persistent (bool) => try to find a persistent link?
* - resource (resource) => existing connection resource
* - resource (PgSql\Connection) => existing connection resource
* - connect_type (int) => see pg_connect()
*/
class PostgreDriver implements Dibi\Driver
class Connection implements Drivers\Connection
{
/** @var resource|PgSql\Connection */
private $connection;
private PgSql\Connection $connection;
private ?int $affectedRows;
@@ -72,7 +73,7 @@ class PostgreDriver implements Dibi\Driver
restore_error_handler();
}
if (!is_resource($this->connection) && !$this->connection instanceof PgSql\Connection) {
if (!$this->connection instanceof PgSql\Connection) {
throw new Dibi\DriverException($error ?: 'Connecting error.');
}
@@ -110,7 +111,7 @@ class PostgreDriver implements Dibi\Driver
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
$this->affectedRows = null;
$res = @pg_query($this->connection, $sql); // intentionally @
@@ -118,7 +119,7 @@ class PostgreDriver implements Dibi\Driver
if ($res === false) {
throw static::createException(pg_last_error($this->connection), null, $sql);
} elseif (is_resource($res) || $res instanceof PgSql\Result) {
} elseif ($res instanceof PgSql\Result) {
$this->affectedRows = Helpers::false2Null(pg_affected_rows($res));
if (pg_num_fields($res)) {
return $this->createResultDriver($res);
@@ -222,32 +223,28 @@ class PostgreDriver implements Dibi\Driver
/**
* Returns the connection resource.
* @return resource|null
*/
public function getResource(): mixed
public function getResource(): PgSql\Connection
{
return is_resource($this->connection) || $this->connection instanceof PgSql\Connection
? $this->connection
: null;
return $this->connection;
}
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
public function getReflector(): Drivers\Engine
{
return new PostgreReflector($this, pg_parameter_status($this->connection, 'server_version'));
return new Drivers\Engines\PostgreSQLEngine($this);
}
/**
* Result set driver factory.
* @param resource $resource
*/
public function createResultDriver($resource): PostgreResult
public function createResultDriver(PgSql\Result $resource): Result
{
return new PostgreResult($resource);
return new Result($resource);
}
@@ -275,66 +272,4 @@ class PostgreDriver implements Dibi\Driver
return "'" . pg_escape_bytea($this->connection, $value) . "'";
}
public function escapeIdentifier(string $value): string
{
// @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? 'TRUE' : 'FALSE';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
$value = pg_escape_string($this->connection, $value);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
}
if ($limit !== null) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}
}
}

View File

@@ -7,9 +7,9 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\PgSQL;
use Dibi;
use Dibi\Drivers;
use Dibi\Helpers;
use PgSql;
@@ -17,18 +17,11 @@ use PgSql;
/**
* The driver for PostgreSQL result set.
*/
class PostgreResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
/** @var resource|PgSql\Result */
private $resultSet;
/**
* @param resource|PgSql\Result $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
private readonly PgSql\Result $resultSet,
) {
}
@@ -94,13 +87,10 @@ class PostgreResult implements Dibi\ResultDriver
/**
* Returns the result set resource.
* @return resource|PgSql\Result|null
*/
public function getResultResource(): mixed
public function getResultResource(): PgSql\Result
{
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
? $this->resultSet
: null;
return $this->resultSet;
}

View File

@@ -0,0 +1,58 @@
<?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\Exception;
/**
* Database result driver.
*/
interface Result
{
/**
* Returns the number of rows in a result set.
*/
function getRowCount(): int;
/**
* Moves cursor position without fetching row.
* @throws Exception
*/
function seek(int $row): bool;
/**
* Fetches the row at current position and moves the internal cursor to the next position.
* @param bool $type true for associative array, false for numeric
* @internal
*/
function fetch(bool $type): ?array;
/**
* Frees the resources allocated for this result set.
*/
function free(): void;
/**
* Returns metadata for all columns in a result set.
* @return array of {name, nativetype [, table, fullname, (int) size, (bool) nullable, (mixed) default, (bool) autoincrement, (array) vendor ]}
*/
function getResultColumns(): array;
/**
* Returns the result set resource.
*/
function getResultResource(): mixed;
/**
* Decodes data from result set.
*/
function unescapeBinary(string $value): string;
}

View File

@@ -7,10 +7,12 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\SQLSrv;
use Dibi;
use Dibi\Drivers;
use Dibi\Helpers;
use function is_resource, sprintf;
/**
@@ -25,12 +27,11 @@ use Dibi\Helpers;
* - charset => character encoding to set (default is UTF-8)
* - resource (resource) => existing connection resource
*/
class SqlsrvDriver implements Dibi\Driver
class Connection implements Drivers\Connection
{
/** @var resource */
private $connection;
private ?int $affectedRows;
private string $version = '';
/** @throws Dibi\NotSupportedException */
@@ -68,8 +69,6 @@ class SqlsrvDriver implements Dibi\Driver
sqlsrv_configure('WarningsReturnAsErrors', 1);
}
$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
}
@@ -86,7 +85,7 @@ class SqlsrvDriver implements Dibi\Driver
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
$this->affectedRows = null;
$res = sqlsrv_query($this->connection, $sql);
@@ -173,9 +172,9 @@ class SqlsrvDriver implements Dibi\Driver
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
public function getReflector(): Drivers\Engine
{
return new SqlsrvReflector($this);
return new Drivers\Engines\SQLServerEngine($this);
}
@@ -183,9 +182,9 @@ class SqlsrvDriver implements Dibi\Driver
* Result set driver factory.
* @param resource $resource
*/
public function createResultDriver($resource): SqlsrvResult
public function createResultDriver($resource): Result
{
return new SqlsrvResult($resource);
return new Result($resource);
}
@@ -205,70 +204,4 @@ class SqlsrvDriver implements Dibi\Driver
{
return '0x' . bin2hex($value);
}
public function escapeIdentifier(string $value): string
{
// @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
return '[' . str_replace(']', ']]', $value) . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif (version_compare($this->version, '11', '<')) { // 11 == SQL Server 2012
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
} elseif ($limit !== null) {
$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
}
} elseif ($limit !== null) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
} elseif ($offset) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
}
}
}

View File

@@ -7,26 +7,22 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\SQLSrv;
use Dibi;
use Dibi\Drivers;
use function is_resource;
/**
* The driver for Microsoft SQL Server and SQL Azure result set.
*/
class SqlsrvResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
/** @var resource */
private $resultSet;
/**
* @param resource $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
/** @var resource */
private $resultSet,
) {
}

View File

@@ -7,9 +7,10 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\SQLite3;
use Dibi;
use Dibi\Drivers;
use Dibi\Helpers;
use SQLite3;
@@ -23,7 +24,7 @@ use SQLite3;
* - formatDateTime => how to format datetime in SQL (@see date)
* - resource (SQLite3) => existing connection resource
*/
class SqliteDriver implements Dibi\Driver
class Connection implements Drivers\Connection
{
private SQLite3 $connection;
private string $fmtDate;
@@ -56,11 +57,7 @@ class SqliteDriver implements Dibi\Driver
}
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
$version = SQLite3::version();
$this->connection->enableExceptions(false);
if ($version['versionNumber'] >= '3006019') {
$this->query('PRAGMA foreign_keys = ON');
}
$this->query('PRAGMA foreign_keys = ON');
}
@@ -77,7 +74,7 @@ class SqliteDriver implements Dibi\Driver
* Executes the SQL query.
* @throws Dibi\DriverException
*/
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
$res = @$this->connection->query($sql); // intentionally @
if ($code = $this->connection->lastErrorCode()) {
@@ -178,18 +175,18 @@ class SqliteDriver implements Dibi\Driver
/**
* Returns the connection reflector.
*/
public function getReflector(): Dibi\Reflector
public function getReflector(): Drivers\Engine
{
return new SqliteReflector($this);
return new Drivers\Engines\SQLiteEngine($this);
}
/**
* Result set driver factory.
*/
public function createResultDriver(\SQLite3Result $result): SqliteResult
public function createResultDriver(\SQLite3Result $result): Result
{
return new SqliteResult($result);
return new Result($result);
}
@@ -211,61 +208,6 @@ class SqliteDriver implements Dibi\Driver
}
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 & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " 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**/

View File

@@ -7,23 +7,22 @@
declare(strict_types=1);
namespace Dibi\Drivers;
namespace Dibi\Drivers\SQLite3;
use Dibi;
use Dibi\Drivers;
use Dibi\Helpers;
use const SQLITE3_ASSOC, SQLITE3_BLOB, SQLITE3_FLOAT, SQLITE3_INTEGER, SQLITE3_NULL, SQLITE3_NUM, SQLITE3_TEXT;
/**
* The driver for SQLite result set.
*/
class SqliteResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
private \SQLite3Result $resultSet;
public function __construct(\SQLite3Result $resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
private readonly \SQLite3Result $resultSet,
) {
}

View File

@@ -1,18 +0,0 @@
<?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;
/**
* Alias for SqliteDriver driver.
*/
class Sqlite3Driver extends SqliteDriver
{
}

View File

@@ -1,18 +0,0 @@
<?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;
/**
* Alias for SqliteResult driver.
*/
class Sqlite3Result extends SqliteResult
{
}

View File

@@ -9,6 +9,9 @@ declare(strict_types=1);
namespace Dibi;
use function count, dirname, microtime, preg_match, str_starts_with, strtoupper, trim;
use const DIRECTORY_SEPARATOR;
/**
* Profiler & logger event.
@@ -29,10 +32,10 @@ class Event
TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK
ALL = 1023;
public Connection $connection;
public readonly Connection $connection;
public int $type;
public string $sql;
public Result|DriverException|null $result;
public readonly string $sql;
public readonly Result|DriverException|null $result;
public float $time;
public ?int $count = null;
public ?array $source = null;

View File

@@ -15,7 +15,7 @@ namespace Dibi;
*/
class Expression
{
private array $values;
private readonly array $values;
public function __construct(...$values)

View File

@@ -9,6 +9,8 @@ declare(strict_types=1);
namespace Dibi;
use function array_key_exists, count, func_get_args, is_array, is_string;
/**
* SQL builder via fluent interfaces.
@@ -45,7 +47,13 @@ namespace Dibi;
*/
class Fluent implements IDataSource
{
public const REMOVE = false;
public const
AffectedRows = 'a',
Identifier = 'n',
Remove = false;
#[\Deprecated('use Fluent::Remove')]
public const REMOVE = self::Remove;
public static array $masks = [
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
@@ -91,7 +99,7 @@ class Fluent implements IDataSource
'RIGHT JOIN' => 'FROM',
];
private Connection $connection;
private readonly Connection $connection;
private array $setups = [];
private ?string $command = null;
private array $clauses = [];
@@ -107,7 +115,7 @@ class Fluent implements IDataSource
$this->connection = $connection;
if (!isset(self::$normalizer)) {
self::$normalizer = new HashMap([self::class, '_formatClause']);
self::$normalizer = new HashMap(self::_formatClause(...));
}
}
@@ -140,7 +148,7 @@ class Fluent implements IDataSource
$this->cursor = &$this->clauses[$clause];
// TODO: really delete?
if ($args === [self::REMOVE]) {
if ($args === [self::Remove]) {
$this->cursor = null;
return $this;
}
@@ -156,7 +164,7 @@ class Fluent implements IDataSource
}
} else {
// append to currect flow
if ($args === [self::REMOVE]) {
if ($args === [self::Remove]) {
return $this;
}
@@ -279,15 +287,15 @@ class Fluent implements IDataSource
/**
* Generates and executes SQL query.
* Returns result set or number of affected rows
* @return ($return is \dibi::IDENTIFIER|\dibi::AFFECTED_ROWS ? int : Result)
* @return ($return is self::Identifier|self::AffectedRows ? int : Result)
* @throws Exception
*/
public function execute(?string $return = null): Result|int|null
{
$res = $this->query($this->_export());
return match ($return) {
\dibi::IDENTIFIER => $this->connection->getInsertId(),
\dibi::AFFECTED_ROWS => $this->connection->getAffectedRows(),
self::Identifier => $this->connection->getInsertId(),
self::AffectedRows => $this->connection->getAffectedRows(),
default => $res,
};
}

View File

@@ -9,6 +9,9 @@ declare(strict_types=1);
namespace Dibi;
use function array_map, array_unique, explode, fclose, fgets, fopen, fstat, getenv, htmlspecialchars, is_float, is_int, is_string, levenshtein, max, mb_strlen, ob_end_flush, ob_get_clean, ob_start, preg_match, preg_replace, preg_replace_callback, rtrim, set_time_limit, str_ends_with, str_repeat, str_starts_with, strlen, strtoupper, substr, trim, wordwrap;
use const PHP_SAPI;
class Helpers
{
@@ -156,15 +159,15 @@ class Helpers
/** @internal */
public static function escape(Driver $driver, $value, string $type): string
public static function escape(Drivers\Connection $driver, $value, string $type): string
{
$types = [
Type::TEXT => 'text',
Type::BINARY => 'binary',
Type::BOOL => 'bool',
Type::DATE => 'date',
Type::DATETIME => 'datetime',
\dibi::IDENTIFIER => 'identifier',
Type::Text => 'text',
Type::Binary => 'binary',
Type::Bool => 'bool',
Type::Date => 'date',
Type::DateTime => 'datetime',
Fluent::Identifier => 'identifier',
];
if (isset($types[$type])) {
return $driver->{'escape' . $types[$type]}($value);
@@ -181,16 +184,16 @@ class Helpers
public static function detectType(string $type): ?string
{
$patterns = [
'^_' => Type::TEXT, // PostgreSQL arrays
'RANGE$' => Type::TEXT, // PostgreSQL range types
'BYTEA|BLOB|BIN' => Type::BINARY,
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::FLOAT,
'^TIME$' => Type::TIME,
'TIME' => Type::DATETIME, // DATETIME, TIMESTAMP
'DATE' => Type::DATE,
'BOOL' => Type::BOOL,
'^_' => Type::Text, // PostgreSQL arrays
'RANGE$' => Type::Text, // PostgreSQL range types
'BYTEA|BLOB|BIN' => Type::Binary,
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::Text,
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::Integer,
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::Float,
'^TIME$' => Type::Time,
'TIME' => Type::DateTime, // DATETIME, TIMESTAMP
'DATE' => Type::Date,
'BOOL' => Type::Bool,
'JSON' => Type::JSON,
];
@@ -208,7 +211,7 @@ class Helpers
public static function getTypeCache(): HashMap
{
if (!isset(self::$types)) {
self::$types = new HashMap([self::class, 'detectType']);
self::$types = new HashMap(self::detectType(...));
}
return self::$types;

20
src/Dibi/IDataSource.php Normal file
View File

@@ -0,0 +1,20 @@
<?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;
/**
* Provides an interface between a dataset and data-aware components.
*/
interface IDataSource extends \Countable, \IteratorAggregate
{
//function \IteratorAggregate::getIterator();
//function \Countable::count();
}

View File

@@ -15,7 +15,7 @@ namespace Dibi;
*/
class Literal
{
private string $value;
private readonly string $value;
public function __construct($value)

View File

@@ -10,6 +10,8 @@ declare(strict_types=1);
namespace Dibi\Loggers;
use Dibi;
use function sprintf;
use const FILE_APPEND, LOCK_EX;
/**
@@ -17,17 +19,11 @@ use Dibi;
*/
class FileLogger
{
/** Name of the file where SQL errors should be logged */
public string $file;
public int $filter;
private bool $errorsOnly;
public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false)
{
$this->file = $file;
$this->filter = $filter ?: Dibi\Event::QUERY;
$this->errorsOnly = $errorsOnly;
public function __construct(
public string $file,
public int $filter = Dibi\Event::QUERY,
private bool $errorsOnly = false,
) {
}

View File

@@ -27,17 +27,10 @@ use Dibi;
*/
class Column
{
/** when created by Result */
private ?Dibi\Reflector $reflector;
/** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */
private array $info;
public function __construct(?Dibi\Reflector $reflector, array $info)
{
$this->reflector = $reflector;
$this->info = $info;
public function __construct(
private readonly ?Dibi\Drivers\Engine $reflector,
private array $info,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Reflection;
use Dibi;
use function array_values, strtolower;
/**
@@ -21,17 +22,14 @@ use Dibi;
*/
class Database
{
private Dibi\Reflector $reflector;
private ?string $name;
/** @var Table[] */
private array $tables;
public function __construct(Dibi\Reflector $reflector, ?string $name = null)
{
$this->reflector = $reflector;
$this->name = $name;
public function __construct(
private readonly Dibi\Drivers\Engine $reflector,
private ?string $name = null,
) {
}

View File

@@ -19,16 +19,10 @@ namespace Dibi\Reflection;
*/
class ForeignKey
{
private string $name;
/** @var array of [local, foreign, onDelete, onUpdate] */
private array $references;
public function __construct(string $name, array $references)
{
$this->name = $name;
$this->references = $references;
public function __construct(
private readonly string $name,
private readonly array $references,
) {
}

View File

@@ -21,13 +21,9 @@ namespace Dibi\Reflection;
*/
class Index
{
/** @var array (name, columns, [unique], [primary]) */
private array $info;
public function __construct(array $info)
{
$this->info = $info;
public function __construct(
private readonly array $info,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Reflection;
use Dibi;
use function array_values, strtolower;
/**
@@ -20,8 +21,6 @@ use Dibi;
*/
class Result
{
private Dibi\ResultDriver $driver;
/** @var Column[]|null */
private ?array $columns;
@@ -29,9 +28,9 @@ class Result
private ?array $names;
public function __construct(Dibi\ResultDriver $driver)
{
$this->driver = $driver;
public function __construct(
private readonly Dibi\Drivers\Result $driver,
) {
}
@@ -80,7 +79,7 @@ class Result
{
if (!isset($this->columns)) {
$this->columns = [];
$reflector = $this->driver instanceof Dibi\Reflector
$reflector = $this->driver instanceof Dibi\Drivers\Engine
? $this->driver
: null;
foreach ($this->driver->getResultColumns() as $info) {

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Reflection;
use Dibi;
use function array_values, strtolower;
/**
@@ -25,7 +26,7 @@ use Dibi;
*/
class Table
{
private Dibi\Reflector $reflector;
private readonly Dibi\Drivers\Engine $reflector;
private string $name;
private bool $view;
@@ -40,7 +41,7 @@ class Table
private ?Index $primaryKey;
public function __construct(Dibi\Reflector $reflector, array $info)
public function __construct(Dibi\Drivers\Engine $reflector, array $info)
{
$this->reflector = $reflector;
$this->name = $info['name'];

View File

@@ -9,6 +9,9 @@ declare(strict_types=1);
namespace Dibi;
use function array_keys, array_pop, count, explode, is_float, is_string, json_decode, ltrim, preg_match, preg_split, property_exists, reset, rtrim, str_contains, str_replace, str_starts_with, strpos;
use const PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
/**
* Query result.
@@ -17,7 +20,7 @@ namespace Dibi;
*/
class Result implements IDataSource
{
private ?ResultDriver $driver;
private ?Drivers\Result $driver;
/** Translate table */
private array $types = [];
@@ -34,7 +37,7 @@ class Result implements IDataSource
private array $formats = [];
public function __construct(ResultDriver $driver, bool $normalize = true)
public function __construct(Drivers\Result $driver, bool $normalize = true)
{
$this->driver = $driver;
if ($normalize) {
@@ -59,7 +62,7 @@ class Result implements IDataSource
* Safe access to property $driver.
* @throws \RuntimeException
*/
final public function getResultDriver(): ResultDriver
final public function getResultDriver(): Drivers\Result
{
if ($this->driver === null) {
throw new \RuntimeException('Result-set was released from memory.');
@@ -457,16 +460,22 @@ class Result implements IDataSource
if ($type === null || $format === 'native') {
$row[$key] = $value;
} elseif ($type === Type::TEXT) {
} elseif ($type === Type::Text) {
$row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) {
} elseif ($type === Type::Integer) {
$row[$key] = is_float($tmp = $value * 1)
? (is_string($value) ? $value : (int) $value)
: $tmp;
} elseif ($type === Type::FLOAT) {
$value = ltrim((string) $value, '0');
} elseif ($type === Type::Float) {
if (!is_string($value)) {
$row[$key] = (float) $value;
continue;
}
$negative = ($value[0] ?? null) === '-';
$value = ltrim($value, '0-');
$p = strpos($value, '.');
$e = strpos($value, 'e');
if ($p !== false && $e === false) {
@@ -479,27 +488,31 @@ class Result implements IDataSource
$value = '0' . $value;
}
if ($negative) {
$value = '-' . $value;
}
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float
: $value;
} elseif ($type === Type::BOOL) {
} elseif ($type === Type::Bool) {
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
} elseif ($type === Type::DateTime || $type === Type::Date || $type === Type::Time) {
if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
$value = new DateTime($value);
$row[$key] = $format ? $value->format($format) : $value;
} else {
$row[$key] = null;
}
} elseif ($type === Type::TIME_INTERVAL) {
} elseif ($type === Type::TimeInterval) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$value->invert = (int) (bool) $m[1];
$row[$key] = $format ? $value->format($format) : $value;
} elseif ($type === Type::BINARY) {
} elseif ($type === Type::Binary) {
$row[$key] = is_string($value)
? $this->getResultDriver()->unescapeBinary($value)
: $value;

View File

@@ -15,14 +15,13 @@ namespace Dibi;
*/
class ResultIterator implements \Iterator, \Countable
{
private Result $result;
private mixed $row;
private int $pointer = 0;
public function __construct(Result $result)
{
$this->result = $result;
public function __construct(
private readonly Result $result,
) {
}

View File

@@ -9,6 +9,8 @@ declare(strict_types=1);
namespace Dibi;
use function array_keys, count, str_starts_with;
/**
* Result set single row.

View File

@@ -9,14 +9,17 @@ declare(strict_types=1);
namespace Dibi;
use function array_filter, array_keys, array_splice, array_values, count, explode, get_debug_type, gettype, implode, is_array, is_bool, is_float, is_int, is_numeric, is_object, is_scalar, is_string, iterator_to_array, key, ltrim, number_format, preg_last_error, preg_match, preg_replace_callback, reset, rtrim, str_contains, str_replace, strcspn, strlen, strncasecmp, strtoupper, substr, trim;
/**
* SQL translator.
*/
final class Translator
{
private Connection $connection;
private Driver $driver;
private readonly Connection $connection;
private readonly Drivers\Connection $driver;
private readonly Drivers\Engine $engine;
private int $cursor = 0;
private array $args;
@@ -34,7 +37,8 @@ final class Translator
{
$this->connection = $connection;
$this->driver = $connection->getDriver();
$this->identifiers = new HashMap([$this, 'delimite']);
$this->engine = $connection->getDatabaseEngine();
$this->identifiers = new HashMap($this->delimite(...));
}
@@ -88,7 +92,7 @@ final class Translator
(\?) ## 11) placeholder
)/xs
XX,
[$this, 'cb'],
$this->cb(...),
substr($arg, $toSkip),
);
if (preg_last_error()) {
@@ -142,7 +146,7 @@ final class Translator
// apply limit
if ($this->limit !== null || $this->offset !== null) {
$this->driver->applyLimit($sql, $this->limit, $this->offset);
$this->engine->applyLimit($sql, $this->limit, $this->offset);
}
return $sql;
@@ -207,7 +211,7 @@ final class Translator
case 'n': // key, key, ... identifier names
foreach ($value as $k => $v) {
if (is_string($k)) {
$vx[] = $this->identifiers->$k . (empty($v) ? '' : ' AS ' . $this->driver->escapeIdentifier($v));
$vx[] = $this->identifiers->$k . (empty($v) ? '' : ' AS ' . $this->engine->escapeIdentifier($v));
} else {
$pair = explode('%', $v, 2); // split into identifier & modifier
$vx[] = $this->identifiers->{$pair[0]};
@@ -219,7 +223,7 @@ final class Translator
case 'a': // key=val, key=val, ...
foreach ($value as $k => $v) {
$pair = explode('%', $k, 2); // split into identifier & modifier
$pair = explode('%', (string) $k, 2); // split into identifier & modifier
$vx[] = $this->identifiers->{$pair[0]} . '='
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
}
@@ -344,7 +348,7 @@ final class Translator
case 's': // string
return $value === null
? 'NULL'
: $this->driver->escapeText((string) $value);
: $this->engine->escapeText((string) $value);
case 'bin':// binary
return $value === null
@@ -354,7 +358,7 @@ final class Translator
case 'b': // boolean
return $value === null
? 'NULL'
: $this->driver->escapeBool((bool) $value);
: $this->engine->escapeBool((bool) $value);
case 'sN': // string or null
case 'sn':
@@ -404,15 +408,15 @@ final class Translator
}
return $modifier === 'd'
? $this->driver->escapeDate($value)
: $this->driver->escapeDateTime($value);
? $this->engine->escapeDate($value)
: $this->engine->escapeDateTime($value);
case 'by':
case 'n': // composed identifier name
return $this->identifiers->$value;
case 'N': // identifier name
return $this->driver->escapeIdentifier($value);
return $this->engine->escapeIdentifier($value);
case 'ex':
case 'sql': // preserve as dibi-SQL (TODO: leave only %ex)
@@ -434,7 +438,7 @@ final class Translator
:(\S*?:)([a-zA-Z0-9._]?)
)/sx
XX,
[$this, 'cb'],
$this->cb(...),
substr($value, $toSkip),
);
if (preg_last_error()) {
@@ -448,16 +452,16 @@ final class Translator
return (string) $value;
case 'like~': // LIKE string%
return $this->driver->escapeLike($value, 2);
return $this->engine->escapeLike($value, 2);
case '~like': // LIKE %string
return $this->driver->escapeLike($value, 1);
return $this->engine->escapeLike($value, 1);
case '~like~': // LIKE %string%
return $this->driver->escapeLike($value, 3);
return $this->engine->escapeLike($value, 3);
case 'like': // LIKE string
return $this->driver->escapeLike($value, 0);
return $this->engine->escapeLike($value, 0);
case 'and':
case 'or':
@@ -483,16 +487,16 @@ final class Translator
return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
} elseif (is_bool($value)) {
return $this->driver->escapeBool($value);
return $this->engine->escapeBool($value);
} elseif ($value === null) {
return 'NULL';
} elseif ($value instanceof \DateTimeInterface) {
return $this->driver->escapeDateTime($value);
return $this->engine->escapeDateTime($value);
} elseif ($value instanceof \DateInterval) {
return $this->driver->escapeDateInterval($value);
return $this->engine->escapeDateInterval($value);
} elseif ($value instanceof Literal) {
return (string) $value;
@@ -651,7 +655,7 @@ final class Translator
$parts = explode('.', $value);
foreach ($parts as &$v) {
if ($v !== '*') {
$v = $this->driver->escapeIdentifier($v);
$v = $this->engine->escapeIdentifier($v);
}
}

View File

@@ -16,16 +16,43 @@ namespace Dibi;
class Type
{
public const
TEXT = 's', // as 'string'
BINARY = 'bin',
Text = 's', // as 'string'
Binary = 'bin',
JSON = 'json',
BOOL = 'b',
INTEGER = 'i',
FLOAT = 'f',
DATE = 'd',
DATETIME = 'dt',
TIME = 't',
TIME_INTERVAL = 'ti';
Bool = 'b',
Integer = 'i',
Float = 'f',
Date = 'd',
DateTime = 'dt',
Time = 't',
TimeInterval = 'ti';
#[\Deprecated('use Type::Text')]
public const TEXT = self::Text;
#[\Deprecated('use Type::Binary')]
public const BINARY = self::Binary;
#[\Deprecated('use Type::Bool')]
public const BOOL = self::Bool;
#[\Deprecated('use Type::Integer')]
public const INTEGER = self::Integer;
#[\Deprecated('use Type::Float')]
public const FLOAT = self::Float;
#[\Deprecated('use Type::Date')]
public const DATE = self::Date;
#[\Deprecated('use Type::DateTime')]
public const DATETIME = self::DateTime;
#[\Deprecated('use Type::Time')]
public const TIME = self::Time;
#[\Deprecated('use Type::TimeInterval')]
public const TIME_INTERVAL = self::TimeInterval;
final public function __construct()

View File

@@ -37,12 +37,13 @@ declare(strict_types=1);
*/
class dibi
{
public const
AFFECTED_ROWS = 'a',
IDENTIFIER = 'n';
public const Version = '6.0-dev';
/** version */
public const VERSION = '5.0.1';
public const VERSION = self::Version;
public const AFFECTED_ROWS = Dibi\Fluent::AffectedRows;
public const IDENTIFIER = Dibi\Fluent::Identifier;
/** sorting order */
public const

View File

@@ -11,7 +11,7 @@ namespace Dibi;
/**
* Dibi common exception.
* A database operation failed.
*/
class Exception extends \Exception
{
@@ -44,7 +44,7 @@ class Exception extends \Exception
/**
* database server exception.
* The database server reported an error.
*/
class DriverException extends Exception
{
@@ -52,7 +52,7 @@ class DriverException extends Exception
/**
* PCRE exception.
* Regular expression pattern or execution failed.
*/
class PcreException extends Exception
{
@@ -63,18 +63,24 @@ class PcreException extends Exception
}
/**
* The requested feature is not implemented.
*/
class NotImplementedException extends Exception
{
}
/**
* The requested operation is not supported.
*/
class NotSupportedException extends Exception
{
}
/**
* Database procedure exception.
* A database stored procedure failed.
*/
class ProcedureException extends Exception
{
@@ -102,7 +108,7 @@ class ProcedureException extends Exception
/**
* Base class for all constraint violation related exceptions.
* A database constraint was violated.
*/
class ConstraintViolationException extends DriverException
{
@@ -110,7 +116,7 @@ class ConstraintViolationException extends DriverException
/**
* Exception for a foreign key constraint violation.
* The foreign key constraint check failed.
*/
class ForeignKeyConstraintViolationException extends ConstraintViolationException
{
@@ -118,7 +124,7 @@ class ForeignKeyConstraintViolationException extends ConstraintViolationExceptio
/**
* Exception for a NOT NULL constraint violation.
* The NOT NULL constraint check failed.
*/
class NotNullConstraintViolationException extends ConstraintViolationException
{
@@ -126,7 +132,7 @@ class NotNullConstraintViolationException extends ConstraintViolationException
/**
* Exception for a unique constraint violation.
* The unique constraint check failed.
*/
class UniqueConstraintViolationException extends ConstraintViolationException
{

View File

@@ -1,240 +0,0 @@
<?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;
/**
* Provides an interface between a dataset and data-aware components.
*/
interface IDataSource extends \Countable, \IteratorAggregate
{
//function \IteratorAggregate::getIterator();
//function \Countable::count();
}
/**
* Driver interface.
*/
interface Driver
{
/**
* Disconnects from a database.
* @throws Exception
*/
function disconnect(): void;
/**
* Internal: Executes the SQL query.
* @throws DriverException
*/
function query(string $sql): ?ResultDriver;
/**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
*/
function getAffectedRows(): ?int;
/**
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
*/
function getInsertId(?string $sequence): ?int;
/**
* Begins a transaction (if supported).
* @throws DriverException
*/
function begin(?string $savepoint = null): void;
/**
* Commits statements in a transaction.
* @throws DriverException
*/
function commit(?string $savepoint = null): void;
/**
* Rollback changes in a transaction.
* @throws DriverException
*/
function rollback(?string $savepoint = null): void;
/**
* Returns the connection resource.
*/
function getResource(): mixed;
/**
* Returns the connection reflector.
*/
function getReflector(): Reflector;
/**
* Encodes data for use in a SQL statement.
*/
function escapeText(string $value): string;
function escapeBinary(string $value): string;
function escapeIdentifier(string $value): string;
function escapeBool(bool $value): string;
function escapeDate(\DateTimeInterface $value): string;
function escapeDateTime(\DateTimeInterface $value): string;
function escapeDateInterval(\DateInterval $value): string;
/**
* Encodes string for use in a LIKE statement.
*/
function escapeLike(string $value, int $pos): string;
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
function applyLimit(string &$sql, ?int $limit, ?int $offset): void;
}
/**
* Result set driver interface.
*/
interface ResultDriver
{
/**
* Returns the number of rows in a result set.
*/
function getRowCount(): int;
/**
* Moves cursor position without fetching row.
* @throws Exception
*/
function seek(int $row): bool;
/**
* Fetches the row at current position and moves the internal cursor to the next position.
* @param bool $type true for associative array, false for numeric
* @internal
*/
function fetch(bool $type): ?array;
/**
* Frees the resources allocated for this result set.
*/
function free(): void;
/**
* Returns metadata for all columns in a result set.
* @return array of {name, nativetype [, table, fullname, (int) size, (bool) nullable, (mixed) default, (bool) autoincrement, (array) vendor ]}
*/
function getResultColumns(): array;
/**
* Returns the result set resource.
*/
function getResultResource(): mixed;
/**
* Decodes data from result set.
*/
function unescapeBinary(string $value): string;
}
/**
* Reflection driver.
*/
interface Reflector
{
/**
* Returns list of tables.
* @return array of {name [, (bool) view ]}
*/
function getTables(): array;
/**
* Returns metadata for all columns in a table.
* @return array of {name, nativetype [, table, fullname, (int) size, (bool) nullable, (mixed) default, (bool) autoincrement, (array) vendor ]}
*/
function getColumns(string $table): array;
/**
* Returns metadata for all indexes in a table.
* @return array of {name, (array of names) columns [, (bool) unique, (bool) primary ]}
*/
function getIndexes(string $table): array;
/**
* Returns metadata for all foreign keys in a table.
*/
function getForeignKeys(string $table): array;
}
/**
* Dibi connection.
*/
interface IConnection
{
/**
* Connects to a database.
*/
function connect(): void;
/**
* Disconnects from a database.
*/
function disconnect(): void;
/**
* Returns true when connection was established.
*/
function isConnected(): bool;
/**
* Returns the driver and connects to a database in lazy mode.
*/
function getDriver(): Driver;
/**
* Generates (translates) and executes SQL query.
* @throws Exception
*/
function query(...$args): Result;
/**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
* @throws Exception
*/
function getAffectedRows(): int;
/**
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
* @throws Exception
*/
function getInsertId(?string $sequence = null): int;
/**
* Begins a transaction (if supported).
*/
function begin(?string $savepoint = null): void;
/**
* Commits statements in a transaction.
*/
function commit(?string $savepoint = null): void;
/**
* Rollback changes in a transaction.
*/
function rollback(?string $savepoint = null): void;
}

View File

@@ -12,7 +12,7 @@ use Tester\Assert;
require __DIR__ . '/bootstrap.php';
test('', function () use ($config) {
test('immediate connection and disconnection state', function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
@@ -21,7 +21,7 @@ test('', function () use ($config) {
});
test('lazy', function () use ($config) {
test('lazy connection initiated on first query', function () use ($config) {
$conn = new Connection($config + ['lazy' => true]);
Assert::false($conn->isConnected());
@@ -30,17 +30,17 @@ test('lazy', function () use ($config) {
});
test('', function () use ($config) {
test('config retrieval and driver instance access', function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
Assert::null($conn->getConfig('lazy'));
Assert::same($config['driver'], $conn->getConfig('driver'));
Assert::type(Dibi\Driver::class, $conn->getDriver());
Assert::type(Dibi\Drivers\Connection::class, $conn->getDriver());
});
test('', function () use ($config) {
test('idempotent disconnect calls', function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
@@ -52,7 +52,7 @@ test('', function () use ($config) {
});
test('', function () use ($config) {
test('reconnect after disconnection', function () use ($config) {
$conn = new Connection($config);
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
@@ -63,7 +63,7 @@ test('', function () use ($config) {
});
test('', function () use ($config) {
test('destructor disconnects active connection', function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
@@ -72,7 +72,7 @@ test('', function () use ($config) {
});
test('', function () use ($config) {
test('invalid onConnect option triggers exceptions', function () use ($config) {
Assert::exception(
fn() => new Connection($config + ['onConnect' => '']),
InvalidArgumentException::class,

View File

@@ -49,7 +49,7 @@ test('DateTime', function () use ($conn) {
// Without object translator, DateTime child is translated by driver
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->getDatabaseEngine()->escapeDateTime($stamp),
$conn->translate('?', $stamp),
);
@@ -67,15 +67,15 @@ test('DateTime', function () use ($conn) {
// With modifier, it is still translated by driver
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->getDatabaseEngine()->escapeDateTime($stamp),
$conn->translate('%dt', $stamp),
);
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->getDatabaseEngine()->escapeDateTime($stamp),
$conn->translate('%t', $stamp),
);
Assert::same(
$conn->getDriver()->escapeDate($stamp),
$conn->getDatabaseEngine()->escapeDate($stamp),
$conn->translate('%d', $stamp),
);
@@ -83,7 +83,7 @@ test('DateTime', function () use ($conn) {
// DateTimeImmutable as a Time parent is not affected and still translated by driver
$dt = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
Assert::same(
$conn->getDriver()->escapeDateTime($dt),
$conn->getDatabaseEngine()->escapeDateTime($dt),
$conn->translate('?', $dt),
);

View File

@@ -2,12 +2,14 @@
declare(strict_types=1);
use Dibi\Drivers\SQLSrv\Connection;
use Dibi\Drivers\SQLSrv\Result;
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
class MockDriver extends Dibi\Drivers\SqlsrvDriver
class MockDriver extends Connection
{
public function __construct()
{
@@ -19,14 +21,14 @@ class MockDriver extends Dibi\Drivers\SqlsrvDriver
}
public function query(string $sql): ?Dibi\ResultDriver
public function query(string $sql): ?Result
{
return new MockResult;
}
}
class MockResult extends Dibi\Drivers\SqlsrvResult
class MockResult extends Result
{
public function __construct()
{
@@ -57,28 +59,28 @@ $fluent = $conn->select('*')
->orderBy('customer_id');
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
(string) $fluent,
);
$fluent->fetch();
Assert::same(
'SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t',
'SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY',
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
dibi::$sql,
);
$fluent->fetchAll(0, 3);
Assert::same(
reformat('SELECT TOP (3) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY'),
dibi::$sql,
);
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
(string) $fluent,
);
@@ -86,16 +88,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 * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 0 ROWS ONLY'),
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 0 ROWS ONLY'),
dibi::$sql,
);
Assert::same(
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 0 ROWS ONLY'),
(string) $fluent,
);
@@ -104,12 +106,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 * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
dibi::$sql,
);
Assert::same(

View File

@@ -74,7 +74,7 @@ Assert::same(
(string) $fluent,
);
$fluent->orderBy(Dibi\Fluent::REMOVE);
$fluent->orderBy(Dibi\Fluent::Remove);
Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col WHERE col > 10 OR col < 5 AND active = 1 AND [col] IN (1, 2, 3)'),

View File

@@ -14,7 +14,7 @@ function buildPdoDriver(?int $errorMode)
$pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
}
new Dibi\Drivers\PdoDriver(['resource' => $pdo]);
new Dibi\Drivers\PDO\Connection(['resource' => $pdo]);
}
@@ -36,5 +36,5 @@ Assert::exception(
test(
'PDO error mode: explicitly set silent',
fn() => buildPdoDriver(PDO::ERRMODE_SILENT)
fn() => buildPdoDriver(PDO::ERRMODE_SILENT),
);

View File

@@ -18,9 +18,9 @@ $tests = function ($conn) {
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A%B')->fetchSingle());
Assert::true($conn->query("SELECT 'AA%BB' LIKE %~like~", 'A%B')->fetchSingle());
Assert::same('AA\\BB', $conn->query("SELECT 'AA\\BB'")->fetchSingle());
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A\\B')->fetchSingle());
Assert::true($conn->query("SELECT 'AA\\BB' LIKE %~like~", 'A\\B')->fetchSingle());
Assert::same('AA\BB', $conn->query("SELECT 'AA\\BB'")->fetchSingle());
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A\B')->fetchSingle());
Assert::true($conn->query("SELECT 'AA\\BB' LIKE %~like~", 'A\B')->fetchSingle());
};
$conn = new Dibi\Connection($config);

View File

@@ -25,10 +25,10 @@ class MockResult extends Dibi\Result
}
test('', function () {
test('native text conversion preserves boolean values', function () {
$result = new MockResult;
$result->setType('col', Type::TEXT);
$result->setFormat(Type::TEXT, 'native');
$result->setType('col', Type::Text);
$result->setFormat(Type::Text, 'native');
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true]));
@@ -36,9 +36,9 @@ test('', function () {
});
test('', function () {
test('boolean conversion from diverse representations', function () {
$result = new MockResult;
$result->setType('col', Type::BOOL);
$result->setType('col', Type::Bool);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true]));
@@ -58,9 +58,9 @@ test('', function () {
});
test('', function () {
test('text conversion of booleans and numerics', function () {
$result = new MockResult;
$result->setType('col', Type::TEXT);
$result->setType('col', Type::Text);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => '1'], $result->test(['col' => true]));
@@ -74,9 +74,9 @@ test('', function () {
});
test('', function () {
test('float conversion with various numeric formats', function () {
$result = new MockResult;
$result->setType('col', Type::FLOAT);
$result->setType('col', Type::Float);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => 1.0], $result->test(['col' => true]));
@@ -117,6 +117,37 @@ test('', function () {
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10']));
Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.1e+10']));
// negative
Assert::same(['col' => -0.0], $result->test(['col' => '-']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-.0']));
Assert::same(['col' => -0.1], $result->test(['col' => '-.1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0.0']));
Assert::same(['col' => -0.1], $result->test(['col' => '-0.1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0.000']));
Assert::same(['col' => -0.1], $result->test(['col' => '-0.100']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1.0']));
Assert::same(['col' => -1.1], $result->test(['col' => '-1.1']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1.000']));
Assert::same(['col' => -1.1], $result->test(['col' => '-1.100']));
Assert::same(['col' => -1.0], $result->test(['col' => '-001.000']));
Assert::same(['col' => -1.1], $result->test(['col' => '-001.100']));
Assert::same(['col' => -10.0], $result->test(['col' => '-10']));
Assert::same(['col' => -11.0], $result->test(['col' => '-11']));
Assert::same(['col' => -10.0], $result->test(['col' => '-0010']));
Assert::same(['col' => -11.0], $result->test(['col' => '-0011']));
Assert::same(['col' => '-0.00000000000000000001'], $result->test(['col' => '-0.00000000000000000001']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-012345678901234567890']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890.000']));
Assert::same(['col' => '-12345678901234567890.1'], $result->test(['col' => '-012345678901234567890.100']));
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']));
@@ -147,26 +178,54 @@ test('', function () {
Assert::same(['col' => 0.0], $result->test(['col' => 0.0]));
Assert::same(['col' => 1.0], $result->test(['col' => 1]));
Assert::same(['col' => 1.0], $result->test(['col' => 1.0]));
// Same but negative
Assert::same(['col' => -0.0], $result->test(['col' => '-']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-.0']));
Assert::same(['col' => -0.1], $result->test(['col' => '-.1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0.0']));
Assert::same(['col' => -0.1], $result->test(['col' => '-0.1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0.000']));
Assert::same(['col' => -0.1], $result->test(['col' => '-0.100']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1.0']));
Assert::same(['col' => -1.1], $result->test(['col' => '-1.1']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1.000']));
Assert::same(['col' => -1.1], $result->test(['col' => '-1.100']));
Assert::same(['col' => -1.0], $result->test(['col' => '-001.000']));
Assert::same(['col' => -1.1], $result->test(['col' => '-001.100']));
Assert::same(['col' => -10.0], $result->test(['col' => '-10']));
Assert::same(['col' => -11.0], $result->test(['col' => '-11']));
Assert::same(['col' => -10.0], $result->test(['col' => '-0010']));
Assert::same(['col' => -11.0], $result->test(['col' => '-0011']));
Assert::same(['col' => '-0.00000000000000000001'], $result->test(['col' => '-0.00000000000000000001']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-012345678901234567890']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890.000']));
Assert::same(['col' => '-12345678901234567890.1'], $result->test(['col' => '-012345678901234567890.100']));
Assert::same(['col' => -0.0], $result->test(['col' => -0]));
Assert::same(['col' => -0.0], $result->test(['col' => -0.0]));
Assert::same(['col' => -1.0], $result->test(['col' => -1]));
Assert::same(['col' => -1.0], $result->test(['col' => -1.0]));
setlocale(LC_NUMERIC, 'C');
});
test('', function () {
test('strict integer conversion with error on empty string', function () {
$result = new MockResult;
$result->setType('col', Type::INTEGER);
$result->setType('col', Type::Integer);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => 1], $result->test(['col' => true]));
Assert::same(['col' => 0], $result->test(['col' => false]));
if (PHP_VERSION_ID < 80000) {
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
} else {
Assert::exception(
fn() => Assert::same(['col' => 0], $result->test(['col' => ''])),
TypeError::class,
);
}
Assert::exception(
fn() => Assert::same(['col' => 0], $result->test(['col' => ''])),
TypeError::class,
);
Assert::same(['col' => 0], $result->test(['col' => '0']));
Assert::same(['col' => 1], $result->test(['col' => '1']));
@@ -185,9 +244,9 @@ test('', function () {
});
test('', function () {
test('dateTime conversion with object instantiation', function () {
$result = new MockResult;
$result->setType('col', Type::DATETIME);
$result->setType('col', Type::DateTime);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(
@@ -204,10 +263,10 @@ test('', function () {
});
test('', function () {
test('dateTime conversion using custom format', function () {
$result = new MockResult;
$result->setType('col', Type::DATETIME);
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
$result->setType('col', Type::DateTime);
$result->setFormat(Type::DateTime, 'Y-m-d H:i:s');
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(
@@ -224,9 +283,9 @@ test('', function () {
});
test('', function () {
test('date conversion to DateTime instance', function () {
$result = new MockResult;
$result->setType('col', Type::DATE);
$result->setType('col', Type::Date);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(
@@ -241,9 +300,9 @@ test('', function () {
});
test('', function () {
test('time conversion to DateTime instance', function () {
$result = new MockResult;
$result->setType('col', Type::TIME);
$result->setType('col', Type::Time);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(

View File

@@ -12,7 +12,7 @@ $conn->loadFile(__DIR__ . "/data/$config[system].sql");
$res = $conn->query('SELECT * FROM [customers]');
// auto-converts this column to integer
$res->setType('customer_id', Dibi\Type::DATETIME);
$res->setType('customer_id', Dibi\Type::DateTime);
Assert::equal(new Dibi\Row([
'customer_id' => new Dibi\DateTime('1970-01-01 01:00:01'),

View File

@@ -11,82 +11,59 @@ use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$tests = function ($conn) {
$resource = $conn->getDriver()->getResource();
$version = is_resource($resource)
? sqlsrv_server_info($resource)['SQLServerVersion']
: $resource->getAttribute(PDO::ATTR_SERVER_VERSION);
// Limit and offset
Assert::same(
'SELECT 1 OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY',
$conn->translate('SELECT 1 %ofs %lmt', 10, 10),
);
// MsSQL2012+
if (version_compare($version, '11.0') >= 0) {
// Limit and offset
Assert::same(
'SELECT 1 OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY',
$conn->translate('SELECT 1 %ofs %lmt', 10, 10),
);
// Limit only
Assert::same(
'SELECT 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
$conn->translate('SELECT 1 %lmt', 10),
);
// Limit only
Assert::same(
'SELECT 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
$conn->translate('SELECT 1 %lmt', 10),
);
// Offset only
Assert::same(
'SELECT 1 OFFSET 10 ROWS',
$conn->translate('SELECT 1 %ofs', 10),
);
// Offset only
Assert::same(
'SELECT 1 OFFSET 10 ROWS',
$conn->translate('SELECT 1 %ofs', 10),
);
// Offset invalid
Assert::error(
function () use ($conn) {
$conn->translate('SELECT 1 %ofs', -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.',
);
// Offset invalid
Assert::error(
function () use ($conn) {
$conn->translate('SELECT 1 %ofs', -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.',
);
// Limit invalid
Assert::error(
function () use ($conn) {
$conn->translate('SELECT 1 %lmt', -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.',
);
// Limit invalid
Assert::error(
function () use ($conn) {
$conn->translate('SELECT 1 %lmt', -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.',
);
// Limit invalid, offset valid
Assert::error(
function () use ($conn) {
$conn->translate('SELECT 1 %ofs %lmt', 10, -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.',
);
// Limit invalid, offset valid
Assert::error(
function () use ($conn) {
$conn->translate('SELECT 1 %ofs %lmt', 10, -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.',
);
// Limit valid, offset invalid
Assert::error(
function () use ($conn) {
$conn->translate('SELECT 1 %ofs %lmt', -10, 10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.',
);
} else {
Assert::same(
'SELECT TOP (1) * FROM (SELECT 1) t',
$conn->translate('SELECT 1 %lmt', 1),
);
Assert::same(
'SELECT 1',
$conn->translate('SELECT 1 %lmt', -10),
);
Assert::exception(
fn() => $conn->translate('SELECT 1 %ofs %lmt', 10, 10),
Dibi\NotSupportedException::class,
);
}
// Limit valid, offset invalid
Assert::error(
function () use ($conn) {
$conn->translate('SELECT 1 %ofs %lmt', -10, 10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.',
);
};
$conn = new Dibi\Connection($config);

View File

@@ -1,7 +1,6 @@
<?php
/**
* @phpVersion 8.1
* @dataProvider ../databases.ini
*/
@@ -33,7 +32,7 @@ enum PureEnum
Assert::equal('1', $translator->formatValue(EnumInt::One, null));
Assert::equal(match ($config['driver']) {
Assert::equal(match ($config['system']) {
'sqlsrv' => "N'one'",
default => "'one'",
}, $translator->formatValue(EnumString::One, null));

View File

@@ -32,7 +32,7 @@ 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::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a\a', 'a\\'));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'b\\', '%\\'));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%\\', '%\\'));
@@ -60,9 +60,9 @@ 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