1
0
mirror of https://github.com/dg/dibi.git synced 2025-08-29 08:49:50 +02:00

Compare commits

...

15 Commits
v5.0 ... master

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
67 changed files with 1304 additions and 1485 deletions

View File

@@ -13,7 +13,7 @@ jobs:
- 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

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.0', '8.1', '8.2', '8.3', '8.4']
php: ['8.2', '8.3', '8.4', '8.5']
fail-fast: false
@@ -112,7 +112,7 @@ jobs:
- 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,20 +11,23 @@
}
],
"require": {
"php": "8.0 - 8.4"
"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": {
@@ -33,7 +36,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
"dev-master": "6.0-dev"
}
}
}

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.4.
The Dibi 6.0 requires PHP version 8.2 and supports PHP up to 8.5.
Usage

View File

@@ -13,6 +13,7 @@ use Dibi;
use Nette;
use Nette\Schema\Expect;
use Tracy;
use function is_array;
/**

View File

@@ -13,6 +13,7 @@ use Dibi;
use Dibi\Event;
use Dibi\Helpers;
use Tracy;
use function count, is_string, strlen;
/**
@@ -35,8 +36,8 @@ class Panel implements Tracy\IBarPanel
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(...);
}

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> */
@@ -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,18 +7,18 @@
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
{
public function __construct(
private int $rows,
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,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 Firebird/InterBase database.
*/
class FirebirdReflector implements Dibi\Reflector
class FirebirdEngine implements Engine
{
public function __construct(
private Dibi\Driver $driver,
private readonly Connection $driver,
) {
}
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->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 . ')';
}
}
/**
* Returns list of tables.
*/

View File

@@ -7,23 +7,85 @@
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
{
public function __construct(
private Dibi\Driver $driver,
private readonly Connection $driver,
) {
}
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 : '');
}
}
/**
* Returns list of tables.
*/
@@ -47,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']);
@@ -72,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,81 @@
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
{
public function __construct(
private Dibi\Driver $driver,
private readonly Connection $driver,
) {
}
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#'); // 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';
}
}
/**
* Returns list of tables.
*/

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,23 +7,86 @@
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
{
public function __construct(
private Dibi\Driver $driver,
private string $version,
private readonly Connection $driver,
) {
}
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 = $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;
}
}
/**
* Returns list of tables.
*/
@@ -39,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 = [];
@@ -67,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 *
@@ -127,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,
@@ -177,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,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 Microsoft SQL Server and SQL Azure databases.
*/
class SqlsrvReflector implements Dibi\Reflector
class SQLServerEngine implements Engine
{
public function __construct(
private Dibi\Driver $driver,
private readonly Connection $driver,
) {
}
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 ($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);
}
}
/**
* Returns list of tables.
*/

View File

@@ -7,22 +7,79 @@
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
{
public function __construct(
private Dibi\Driver $driver,
private readonly Connection $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); // 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 : '');
}
}
/**
* Returns list of tables.
*/
@@ -48,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'];
@@ -75,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'];
@@ -83,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'];
}
@@ -126,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,11 +26,11 @@ 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 ErrorExceptionThrown = -836;
/** @deprecated use FirebirdDriver::ErrorExceptionThrown */
#[\Deprecated('use FirebirdDriver::ErrorExceptionThrown')]
public const ERROR_EXCEPTION_THROWN = self::ErrorExceptionThrown;
/** @var resource */
@@ -85,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
@@ -199,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);
}
@@ -209,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);
}
@@ -231,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,16 +7,18 @@
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
{
public function __construct(
/** @var resource */
@@ -45,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,19 +33,19 @@ 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 ErrorAccessDenied = 1045;
public const ErrorDuplicateEntry = 1062;
public const ErrorDataTruncated = 1265;
/** @deprecated use MySqliDriver::ErrorAccessDenied */
#[\Deprecated('use MySqliDriver::ErrorAccessDenied')]
public const ERROR_ACCESS_DENIED = self::ErrorAccessDenied;
/** @deprecated use MySqliDriver::ErrorDuplicateEntry */
#[\Deprecated('use MySqliDriver::ErrorDuplicateEntry')]
public const ERROR_DUPLICATE_ENTRY = self::ErrorDuplicateEntry;
/** @deprecated use MySqliDriver::ErrorDataTruncated */
#[\Deprecated('use MySqliDriver::ErrorDataTruncated')]
public const ERROR_DATA_TRUNCATED = self::ErrorDataTruncated;
private \mysqli $connection;
@@ -146,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 @
@@ -263,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);
}
@@ -294,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,19 +7,21 @@
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
{
public function __construct(
private \mysqli_result $resultSet,
private bool $buffered,
private readonly \mysqli_result $resultSet,
private readonly bool $buffered,
) {
}

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,15 +7,17 @@
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
{
public function __construct(
/** @var resource */

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,15 +7,17 @@
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
{
private int $row = 0;

View File

@@ -1,85 +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
{
public function __construct(
private Dibi\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,11 +18,11 @@ use PDO;
/**
* The driver for PDO result set.
*/
class PdoResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
public function __construct(
private ?\PDOStatement $resultSet,
private string $driverName,
private readonly string $driverName,
) {
}

View File

@@ -1,386 +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
{
return match ($this->driverName) {
'mysql' => new MySqlReflector($this),
'oci' => new OracleReflector($this),
'pgsql' => new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)),
'sqlite' => new SqliteReflector($this),
'mssql', 'dblib', 'sqlsrv' => 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 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),
};
}
public function escapeIdentifier(string $value): string
{
return match ($this->driverName) {
'mysql' => '`' . str_replace('`', '``', $value) . '`',
'oci', 'pgsql' => '"' . str_replace('"', '""', $value) . '"',
'sqlite' => '[' . strtr($value, '[]', ' ') . ']',
'odbc', 'mssql' => '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']',
'dblib', 'sqlsrv' => '[' . str_replace(']', ']]', $value) . ']',
default => $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
{
return match ($this->driverName) {
'odbc' => $value->format('#m/d/Y H:i:s.u#'),
'mssql', 'dblib', 'sqlsrv' => 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')',
default => $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,11 +17,10 @@ use PgSql;
/**
* The driver for PostgreSQL result set.
*/
class PostgreResult implements Dibi\ResultDriver
class Result implements Drivers\Result
{
public function __construct(
/** @var resource|PgSql\Result */
private $resultSet,
private readonly PgSql\Result $resultSet,
) {
}
@@ -88,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,15 +7,17 @@
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
{
public function __construct(
/** @var resource */

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,10 +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();
if ($version['versionNumber'] >= '3006019') {
$this->query('PRAGMA foreign_keys = ON');
}
$this->query('PRAGMA foreign_keys = ON');
}
@@ -76,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()) {
@@ -177,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);
}
@@ -210,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,19 +7,21 @@
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
{
public function __construct(
private \SQLite3Result $resultSet,
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.
@@ -50,7 +52,7 @@ class Fluent implements IDataSource
Identifier = 'n',
Remove = false;
/** @deprecated use Fluent::Remove */
#[\Deprecated('use Fluent::Remove')]
public const REMOVE = self::Remove;
public static array $masks = [
@@ -97,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 = [];
@@ -113,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(...));
}
}

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,7 +159,7 @@ 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',
@@ -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;
/**

View File

@@ -28,7 +28,7 @@ use Dibi;
class Column
{
public function __construct(
private ?Dibi\Reflector $reflector,
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;
/**
@@ -26,7 +27,7 @@ class Database
public function __construct(
private Dibi\Reflector $reflector,
private readonly Dibi\Drivers\Engine $reflector,
private ?string $name = null,
) {
}

View File

@@ -20,8 +20,8 @@ namespace Dibi\Reflection;
class ForeignKey
{
public function __construct(
private string $name,
private array $references,
private readonly string $name,
private readonly array $references,
) {
}

View File

@@ -22,7 +22,7 @@ namespace Dibi\Reflection;
class Index
{
public function __construct(
private array $info,
private readonly array $info,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Reflection;
use Dibi;
use function array_values, strtolower;
/**
@@ -28,7 +29,7 @@ class Result
public function __construct(
private Dibi\ResultDriver $driver,
private readonly Dibi\Drivers\Result $driver,
) {
}
@@ -78,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.');

View File

@@ -20,7 +20,7 @@ class ResultIterator implements \Iterator, \Countable
public function __construct(
private Result $result,
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]};
@@ -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

@@ -27,31 +27,31 @@ class Type
Time = 't',
TimeInterval = 'ti';
/** @deprecated use Type::Text */
#[\Deprecated('use Type::Text')]
public const TEXT = self::Text;
/** @deprecated use Type::Binary */
#[\Deprecated('use Type::Binary')]
public const BINARY = self::Binary;
/** @deprecated use Type::Bool */
#[\Deprecated('use Type::Bool')]
public const BOOL = self::Bool;
/** @deprecated use Type::Integer */
#[\Deprecated('use Type::Integer')]
public const INTEGER = self::Integer;
/** @deprecated use Type::Float */
#[\Deprecated('use Type::Float')]
public const FLOAT = self::Float;
/** @deprecated use Type::Date */
#[\Deprecated('use Type::Date')]
public const DATE = self::Date;
/** @deprecated use Type::DateTime */
#[\Deprecated('use Type::DateTime')]
public const DATETIME = self::DateTime;
/** @deprecated use Type::Time */
#[\Deprecated('use Type::Time')]
public const TIME = self::Time;
/** @deprecated use Type::TimeInterval */
#[\Deprecated('use Type::TimeInterval')]
public const TIME_INTERVAL = self::TimeInterval;

View File

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

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

@@ -36,7 +36,7 @@ test('config retrieval and driver instance access', function () use ($config) {
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());
});

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

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

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
*/