mirror of
https://github.com/dg/dibi.git
synced 2025-09-03 02:52:47 +02:00
Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4ea907a48c | ||
|
3beee64a30 | ||
|
33da2b839e | ||
|
8c34f8df43 | ||
|
0834c12eaf | ||
|
cdeafe9b27 | ||
|
32b6976209 | ||
|
f484630e56 | ||
|
611e051c02 | ||
|
7595a6d5bd | ||
|
494d7c1c21 | ||
|
658dbe388a | ||
|
c7fe0fef21 | ||
|
9151d1eb9c | ||
|
d76f40c2a4 | ||
|
befde664fe | ||
|
e1c4cbaece | ||
|
1df20ced10 | ||
|
ce1ba4668b | ||
|
0f21a6ab3d | ||
|
78f552fe8e | ||
|
97053089e0 | ||
|
2c7b35c29d | ||
|
c04d2197e3 | ||
|
d342d8d78f | ||
|
7d8c39f42a | ||
|
29b58d64dd | ||
|
0a32bb5bdf | ||
|
d707b4ba0e | ||
|
490cf143ba | ||
|
12ffa0ffd1 | ||
|
23f65ef837 |
8
.github/workflows/coding-style.yml
vendored
8
.github/workflows/coding-style.yml
vendored
@@ -7,10 +7,10 @@ jobs:
|
||||
name: Nette Code Checker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.0
|
||||
php-version: 8.3
|
||||
coverage: none
|
||||
|
||||
- run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress
|
||||
@@ -21,10 +21,10 @@ jobs:
|
||||
name: Nette Coding Standard
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.0
|
||||
php-version: 8.3
|
||||
coverage: none
|
||||
|
||||
- run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
|
||||
|
4
.github/workflows/static-analysis.yml
vendored
4
.github/workflows/static-analysis.yml
vendored
@@ -10,10 +10,10 @@ jobs:
|
||||
name: PHPStan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.0
|
||||
php-version: 8.2
|
||||
coverage: none
|
||||
|
||||
- run: composer install --no-progress --prefer-dist
|
||||
|
17
.github/workflows/tests.yml
vendored
17
.github/workflows/tests.yml
vendored
@@ -3,7 +3,7 @@ name: Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.10.0beta2, pdo_sqlsrv-5.10.0beta2
|
||||
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.12.0, pdo_sqlsrv-5.12.0
|
||||
php-tools: "composer:v2, pecl"
|
||||
|
||||
jobs:
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['8.0', '8.1', '8.2', '8.3']
|
||||
php: ['8.2', '8.3', '8.4', '8.5']
|
||||
|
||||
fail-fast: false
|
||||
|
||||
@@ -43,7 +43,6 @@ jobs:
|
||||
--health-retries=5
|
||||
-e MYSQL_ROOT_PASSWORD=root
|
||||
-e MYSQL_DATABASE=dibi_test
|
||||
--entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password"
|
||||
|
||||
postgres96:
|
||||
image: postgres:9.6
|
||||
@@ -83,13 +82,13 @@ jobs:
|
||||
- 1433:1433
|
||||
options: >-
|
||||
--name=mssql
|
||||
--health-cmd "/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'"
|
||||
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1' -N -C"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
@@ -101,19 +100,19 @@ jobs:
|
||||
run: cp ./tests/databases.github.ini ./tests/databases.ini
|
||||
|
||||
- name: Create MS SQL Database
|
||||
run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test'
|
||||
run: docker exec -i mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test' -N -C
|
||||
|
||||
- run: composer install --no-progress --prefer-dist
|
||||
- run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src
|
||||
- if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: output
|
||||
name: output-${{ matrix.php }}
|
||||
path: tests/**/output
|
||||
|
||||
|
||||
- name: Save Code Coverage
|
||||
if: ${{ matrix.php == '8.0' }}
|
||||
if: ${{ matrix.php == '8.2' }}
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
|
@@ -11,28 +11,32 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "8.0 - 8.3"
|
||||
"php": "8.2 - 8.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"tracy/tracy": "^2.9",
|
||||
"nette/tester": "^2.5",
|
||||
"nette/di": "^3.1",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-nette": "^2.0@stable",
|
||||
"jetbrains/phpstorm-attributes": "^1.0"
|
||||
},
|
||||
"replace": {
|
||||
"dg/dibi": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
"classmap": ["src/"],
|
||||
"psr-4": {
|
||||
"Dibi\\": "src/Dibi"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0-dev"
|
||||
"dev-master": "6.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,9 +27,9 @@ $dibi = new Dibi\Connection([
|
||||
// using manual hints
|
||||
$res = $dibi->query('SELECT * FROM [customers]');
|
||||
|
||||
$res->setType('customer_id', Type::INTEGER)
|
||||
->setType('added', Type::DATETIME)
|
||||
->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
|
||||
$res->setType('customer_id', Type::Integer)
|
||||
->setType('added', Type::DateTime)
|
||||
->setFormat(Type::DateTime, 'Y-m-d H:i:s');
|
||||
|
||||
|
||||
Tracy\Dumper::dump($res->fetch());
|
||||
|
@@ -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_');
|
||||
|
@@ -34,7 +34,7 @@ Install Dibi via Composer:
|
||||
composer require dibi/dibi
|
||||
```
|
||||
|
||||
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.3.
|
||||
The Dibi 6.0 requires PHP version 8.2 and supports PHP up to 8.5.
|
||||
|
||||
|
||||
Usage
|
||||
@@ -608,7 +608,7 @@ $database->query("UPDATE [:blog:items] SET [text]='Hello World'");
|
||||
Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the `Dibi\Type` class.
|
||||
|
||||
```php
|
||||
$result->setType('id', Dibi\Type::INTEGER); // id will be integer
|
||||
$result->setType('id', Dibi\Type::Integer); // id will be integer
|
||||
$row = $result->fetch();
|
||||
|
||||
is_int($row->id) // true
|
||||
|
@@ -19,14 +19,10 @@ use Tracy;
|
||||
*/
|
||||
class DibiExtension22 extends Nette\DI\CompilerExtension
|
||||
{
|
||||
private ?bool $debugMode;
|
||||
private ?bool $cliMode;
|
||||
|
||||
|
||||
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
|
||||
{
|
||||
$this->debugMode = $debugMode;
|
||||
$this->cliMode = $cliMode;
|
||||
public function __construct(
|
||||
private ?bool $debugMode = null,
|
||||
private ?bool $cliMode = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
@@ -13,6 +13,7 @@ use Dibi;
|
||||
use Nette;
|
||||
use Nette\Schema\Expect;
|
||||
use Tracy;
|
||||
use function is_array;
|
||||
|
||||
|
||||
/**
|
||||
@@ -20,14 +21,10 @@ use Tracy;
|
||||
*/
|
||||
class DibiExtension3 extends Nette\DI\CompilerExtension
|
||||
{
|
||||
private ?bool $debugMode;
|
||||
private ?bool $cliMode;
|
||||
|
||||
|
||||
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
|
||||
{
|
||||
$this->debugMode = $debugMode;
|
||||
$this->cliMode = $cliMode;
|
||||
public function __construct(
|
||||
private ?bool $debugMode = null,
|
||||
private ?bool $cliMode = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +46,7 @@ class DibiExtension3 extends Nette\DI\CompilerExtension
|
||||
'formatDateTime' => Expect::string(),
|
||||
'formatTimeInterval' => Expect::string(),
|
||||
'formatJson' => Expect::string(),
|
||||
]),
|
||||
])->castTo('array'),
|
||||
])->otherItems(Expect::type('mixed'))
|
||||
->castTo('array');
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ use Dibi;
|
||||
use Dibi\Event;
|
||||
use Dibi\Helpers;
|
||||
use Tracy;
|
||||
use function count, is_string, strlen;
|
||||
|
||||
|
||||
/**
|
||||
@@ -21,23 +22,22 @@ use Tracy;
|
||||
class Panel implements Tracy\IBarPanel
|
||||
{
|
||||
public static int $maxLength = 1000;
|
||||
public bool|string $explain;
|
||||
public int $filter;
|
||||
|
||||
private array $events = [];
|
||||
|
||||
|
||||
public function __construct(bool $explain = true, ?int $filter = null)
|
||||
{
|
||||
$this->filter = $filter ?: Event::QUERY;
|
||||
$this->explain = $explain;
|
||||
public function __construct(
|
||||
public bool|string $explain = true,
|
||||
public int $filter = Event::QUERY,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public function register(Dibi\Connection $connection): void
|
||||
{
|
||||
Tracy\Debugger::getBar()->addPanel($this);
|
||||
Tracy\Debugger::getBlueScreen()->addPanel([self::class, 'renderException']);
|
||||
$connection->onEvent[] = [$this, 'logEvent'];
|
||||
Tracy\Debugger::getBlueScreen()->addPanel(self::renderException(...));
|
||||
$connection->onEvent[] = $this->logEvent(...);
|
||||
}
|
||||
|
||||
|
||||
@@ -151,8 +151,8 @@ class Panel implements Tracy\IBarPanel
|
||||
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
|
||||
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
|
||||
<h1>Queries:' . "\u{a0}" . count($this->events)
|
||||
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', '
|
||||
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
|
||||
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms")
|
||||
. ($singleConnection === null ? '' : ', ' . htmlspecialchars($this->getConnectionName($singleConnection))) . '</h1>
|
||||
<div class="tracy-inner tracy-DibiProfiler">
|
||||
<table class="tracy-sortable">
|
||||
<tr><th>Time ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>
|
||||
|
@@ -11,6 +11,8 @@ namespace Dibi;
|
||||
|
||||
use JetBrains\PhpStorm\Language;
|
||||
use Traversable;
|
||||
use function array_key_exists, is_array, sprintf;
|
||||
use const PHP_SAPI;
|
||||
|
||||
|
||||
/**
|
||||
@@ -19,15 +21,28 @@ use Traversable;
|
||||
* @property-read int $affectedRows
|
||||
* @property-read int $insertId
|
||||
*/
|
||||
class Connection implements IConnection
|
||||
class Connection
|
||||
{
|
||||
private const Drivers = [
|
||||
'firebird' => Drivers\Ibase\Connection::class,
|
||||
'mysqli' => Drivers\MySQLi\Connection::class,
|
||||
'odbc' => Drivers\ODBC\Connection::class,
|
||||
'oracle' => Drivers\OCI8\Connection::class,
|
||||
'pdo' => Drivers\PDO\Connection::class,
|
||||
'postgre' => Drivers\PgSQL\Connection::class,
|
||||
'sqlite3' => Drivers\SQLite3\Connection::class,
|
||||
'sqlite' => Drivers\SQLite3\Connection::class,
|
||||
'sqlsrv' => Drivers\SQLSrv\Connection::class,
|
||||
];
|
||||
|
||||
/** function (Event $event); Occurs after query is executed */
|
||||
public ?array $onEvent = [];
|
||||
private array $config;
|
||||
|
||||
/** @var string[] resultset formats */
|
||||
private array $formats;
|
||||
private ?Driver $driver = null;
|
||||
private ?Drivers\Connection $driver = null;
|
||||
private Drivers\Engine $engine;
|
||||
private ?Translator $translator = null;
|
||||
|
||||
/** @var array<string, callable(object): Expression | null> */
|
||||
@@ -74,10 +89,10 @@ class Connection implements IConnection
|
||||
$this->config = $config;
|
||||
|
||||
$this->formats = [
|
||||
Type::DATE => $this->config['result']['formatDate'],
|
||||
Type::DATETIME => $this->config['result']['formatDateTime'],
|
||||
Type::Date => $this->config['result']['formatDate'],
|
||||
Type::DateTime => $this->config['result']['formatDateTime'],
|
||||
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
|
||||
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
|
||||
Type::TimeInterval => $this->config['result']['formatTimeInterval'] ?? null,
|
||||
];
|
||||
|
||||
// profiler
|
||||
@@ -120,17 +135,16 @@ class Connection implements IConnection
|
||||
*/
|
||||
final public function connect(): void
|
||||
{
|
||||
if ($this->config['driver'] instanceof Driver) {
|
||||
if ($this->config['driver'] instanceof Drivers\Connection) {
|
||||
$this->driver = $this->config['driver'];
|
||||
$this->translator = new Translator($this);
|
||||
return;
|
||||
|
||||
} elseif (is_subclass_of($this->config['driver'], Driver::class)) {
|
||||
} elseif (is_subclass_of($this->config['driver'], Drivers\Connection::class)) {
|
||||
$class = $this->config['driver'];
|
||||
|
||||
} else {
|
||||
$class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($this->config['driver'])));
|
||||
$class = "Dibi\\Drivers\\{$class}Driver";
|
||||
$class = self::Drivers[strtolower($this->config['driver'])] ?? throw new Exception("Unknown driver '{$this->config['driver']}'.");
|
||||
if (!class_exists($class)) {
|
||||
throw new Exception("Unable to create instance of Dibi driver '$class'.");
|
||||
}
|
||||
@@ -196,7 +210,7 @@ class Connection implements IConnection
|
||||
/**
|
||||
* Returns the driver and connects to a database in lazy mode.
|
||||
*/
|
||||
final public function getDriver(): Driver
|
||||
final public function getDriver(): Drivers\Connection
|
||||
{
|
||||
if (!$this->driver) {
|
||||
$this->connect();
|
||||
@@ -284,7 +298,7 @@ class Connection implements IConnection
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$res = $this->createResultSet($res ?: new Drivers\NoDataResult(max(0, $this->driver->getAffectedRows())));
|
||||
$res = $this->createResultSet($res ?: new Drivers\Dummy\Result(max(0, $this->driver->getAffectedRows())));
|
||||
if ($event) {
|
||||
$this->onEvent($event->done($res));
|
||||
}
|
||||
@@ -448,7 +462,7 @@ class Connection implements IConnection
|
||||
/**
|
||||
* Result set factory.
|
||||
*/
|
||||
public function createResultSet(ResultDriver $resultDriver): Result
|
||||
public function createResultSet(Drivers\Result $resultDriver): Result
|
||||
{
|
||||
return (new Result($resultDriver, $this->config['result']['normalize'] ?? true))
|
||||
->setFormats($this->formats);
|
||||
@@ -657,6 +671,15 @@ class Connection implements IConnection
|
||||
}
|
||||
|
||||
|
||||
public function getDatabaseEngine(): Drivers\Engine
|
||||
{
|
||||
if (!$this->driver) { // TODO
|
||||
$this->connect();
|
||||
}
|
||||
return $this->engine ??= $this->driver->getReflector();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a information about the current database.
|
||||
*/
|
||||
@@ -666,14 +689,14 @@ class Connection implements IConnection
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
|
||||
return new Reflection\Database($this->getDatabaseEngine(), $this->config['database'] ?? null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevents unserialization.
|
||||
*/
|
||||
public function __wakeup()
|
||||
public function __unserialize($_)
|
||||
{
|
||||
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
||||
}
|
||||
@@ -682,7 +705,7 @@ class Connection implements IConnection
|
||||
/**
|
||||
* Prevents serialization.
|
||||
*/
|
||||
public function __sleep()
|
||||
public function __serialize()
|
||||
{
|
||||
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
77
src/Dibi/Drivers/Connection.php
Normal file
77
src/Dibi/Drivers/Connection.php
Normal 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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -7,22 +7,19 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Dummy;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for no result set.
|
||||
*/
|
||||
class NoDataResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
private int $rows;
|
||||
|
||||
|
||||
public function __construct(int $rows)
|
||||
{
|
||||
$this->rows = $rows;
|
||||
public function __construct(
|
||||
private readonly int $rows,
|
||||
) {
|
||||
}
|
||||
|
||||
|
60
src/Dibi/Drivers/Engine.php
Normal file
60
src/Dibi/Drivers/Engine.php
Normal 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;
|
||||
}
|
@@ -7,22 +7,73 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Engines;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers\Connection;
|
||||
use Dibi\Drivers\Engine;
|
||||
|
||||
|
||||
/**
|
||||
* The reflector for Firebird/InterBase database.
|
||||
*/
|
||||
class FirebirdReflector implements Dibi\Reflector
|
||||
class FirebirdEngine implements Engine
|
||||
{
|
||||
private Dibi\Driver $driver;
|
||||
public function __construct(
|
||||
private readonly Connection $driver,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public function __construct(Dibi\Driver $driver)
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
$this->driver = $driver;
|
||||
return '"' . str_replace('"', '""', $value) . '"';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes($this->driver->escapeText($value), '%_\\');
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit > 0 || $offset > 0) {
|
||||
// http://www.firebirdsql.org/refdocs/langrefupd20-select.html
|
||||
$sql = 'SELECT ' . ($limit > 0 ? 'FIRST ' . $limit : '') . ($offset > 0 ? ' SKIP ' . $offset : '') . ' * FROM (' . $sql . ')';
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,23 +7,82 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Engines;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers\Connection;
|
||||
use Dibi\Drivers\Engine;
|
||||
|
||||
|
||||
/**
|
||||
* The reflector for MySQL databases.
|
||||
* @internal
|
||||
*/
|
||||
class MySqlReflector implements Dibi\Reflector
|
||||
class MySQLEngine implements Engine
|
||||
{
|
||||
private Dibi\Driver $driver;
|
||||
public function __construct(
|
||||
private readonly Connection $driver,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public function __construct(Dibi\Driver $driver)
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
$this->driver = $driver;
|
||||
return '`' . str_replace('`', '``', $value) . '`';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d H:i:s.u'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
if ($value->y || $value->m || $value->d) {
|
||||
throw new Dibi\NotSupportedException('Only time interval is supported.');
|
||||
}
|
||||
|
||||
return $value->format("'%r%H:%I:%S.%f'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +109,7 @@ class MySqlReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getColumns(string $table): array
|
||||
{
|
||||
$res = $this->driver->query("SHOW FULL COLUMNS FROM {$this->driver->escapeIdentifier($table)}");
|
||||
$res = $this->driver->query("SHOW FULL COLUMNS FROM {$this->escapeIdentifier($table)}");
|
||||
$columns = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$type = explode('(', $row['Type']);
|
||||
@@ -75,7 +134,7 @@ class MySqlReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getIndexes(string $table): array
|
||||
{
|
||||
$res = $this->driver->query("SHOW INDEX FROM {$this->driver->escapeIdentifier($table)}");
|
||||
$res = $this->driver->query("SHOW INDEX FROM {$this->escapeIdentifier($table)}");
|
||||
$indexes = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$indexes[$row['Key_name']]['name'] = $row['Key_name'];
|
@@ -7,22 +7,78 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Engines;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers\Connection;
|
||||
use Dibi\Drivers\Engine;
|
||||
|
||||
|
||||
/**
|
||||
* The reflector for ODBC connections.
|
||||
*/
|
||||
class OdbcReflector implements Dibi\Reflector
|
||||
class ODBCEngine implements Engine
|
||||
{
|
||||
private Dibi\Driver $driver;
|
||||
public function __construct(
|
||||
private readonly Connection $driver,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public function __construct(Dibi\Driver $driver)
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
$this->driver = $driver;
|
||||
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format('#m/d/Y#');
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#'); // TODO
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($offset) {
|
||||
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
|
||||
|
||||
} elseif ($limit < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null) {
|
||||
$sql = 'SELECT TOP ' . $limit . ' * FROM (' . $sql . ') t';
|
||||
}
|
||||
}
|
||||
|
||||
|
153
src/Dibi/Drivers/Engines/OracleEngine.php
Normal file
153
src/Dibi/Drivers/Engines/OracleEngine.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -7,24 +7,83 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Engines;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers\Connection;
|
||||
use Dibi\Drivers\Engine;
|
||||
|
||||
|
||||
/**
|
||||
* The reflector for PostgreSQL database.
|
||||
*/
|
||||
class PostgreReflector implements Dibi\Reflector
|
||||
class PostgreSQLEngine implements Engine
|
||||
{
|
||||
private Dibi\Driver $driver;
|
||||
private string $version;
|
||||
public function __construct(
|
||||
private readonly Connection $driver,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public function __construct(Dibi\Driver $driver, string $version)
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
$this->driver = $driver;
|
||||
$this->version = $version;
|
||||
// @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
return '"' . str_replace('"', '""', $value) . '"';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d H:i:s.u'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$bs = $this->driver->escapeText('\\'); // standard_conforming_strings = on/off
|
||||
$value = $this->driver->escapeText($value);
|
||||
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
}
|
||||
|
||||
if ($limit !== null) {
|
||||
$sql .= ' LIMIT ' . $limit;
|
||||
}
|
||||
|
||||
if ($offset) {
|
||||
$sql .= ' OFFSET ' . $offset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,18 +102,15 @@ class PostgreReflector implements Dibi\Reflector
|
||||
FROM
|
||||
information_schema.tables
|
||||
WHERE
|
||||
table_schema = ANY (current_schemas(false))";
|
||||
table_schema = ANY (current_schemas(false))
|
||||
|
||||
if ($this->version >= 9.3) {
|
||||
$query .= '
|
||||
UNION ALL
|
||||
SELECT
|
||||
matviewname, 1
|
||||
FROM
|
||||
pg_matviews
|
||||
WHERE
|
||||
schemaname = ANY (current_schemas(false))';
|
||||
}
|
||||
UNION ALL
|
||||
SELECT
|
||||
matviewname, 1
|
||||
FROM
|
||||
pg_matviews
|
||||
WHERE
|
||||
schemaname = ANY (current_schemas(false))";
|
||||
|
||||
$res = $this->driver->query($query);
|
||||
$tables = [];
|
||||
@@ -71,7 +127,7 @@ class PostgreReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getColumns(string $table): array
|
||||
{
|
||||
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
|
||||
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
|
||||
|
||||
$res = $this->driver->query("
|
||||
SELECT *
|
||||
@@ -131,7 +187,7 @@ class PostgreReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getIndexes(string $table): array
|
||||
{
|
||||
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
|
||||
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
|
||||
$res = $this->driver->query("
|
||||
SELECT
|
||||
a.attnum AS ordinal_position,
|
||||
@@ -181,7 +237,7 @@ class PostgreReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getForeignKeys(string $table): array
|
||||
{
|
||||
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
|
||||
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
|
||||
|
||||
$res = $this->driver->query("
|
||||
SELECT
|
@@ -7,22 +7,80 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Engines;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers\Connection;
|
||||
use Dibi\Drivers\Engine;
|
||||
|
||||
|
||||
/**
|
||||
* The reflector for Microsoft SQL Server and SQL Azure databases.
|
||||
*/
|
||||
class SqlsrvReflector implements Dibi\Reflector
|
||||
class SQLServerEngine implements Engine
|
||||
{
|
||||
private Dibi\Driver $driver;
|
||||
public function __construct(
|
||||
private readonly Connection $driver,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public function __construct(Dibi\Driver $driver)
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
$this->driver = $driver;
|
||||
// @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
|
||||
return '[' . str_replace(']', ']]', $value) . ']';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null) {
|
||||
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
||||
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
|
||||
} elseif ($offset) {
|
||||
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
||||
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,22 +7,76 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Engines;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers\Connection;
|
||||
use Dibi\Drivers\Engine;
|
||||
|
||||
|
||||
/**
|
||||
* The reflector for SQLite database.
|
||||
*/
|
||||
class SqliteReflector implements Dibi\Reflector
|
||||
class SQLiteEngine implements Engine
|
||||
{
|
||||
private Dibi\Driver $driver;
|
||||
public function __construct(
|
||||
private readonly Connection $driver,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public function __construct(Dibi\Driver $driver)
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
$this->driver = $driver;
|
||||
return '[' . strtr($value, '[]', ' ') . ']';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format($this->fmtDate); // TODO
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format($this->fmtDateTime); // TODO
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes($this->driver->escapeText($value), '%_\\');
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +105,7 @@ class SqliteReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getColumns(string $table): array
|
||||
{
|
||||
$res = $this->driver->query("PRAGMA table_info({$this->driver->escapeIdentifier($table)})");
|
||||
$res = $this->driver->query("PRAGMA table_info({$this->escapeIdentifier($table)})");
|
||||
$columns = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$column = $row['name'];
|
||||
@@ -78,7 +132,7 @@ class SqliteReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getIndexes(string $table): array
|
||||
{
|
||||
$res = $this->driver->query("PRAGMA index_list({$this->driver->escapeIdentifier($table)})");
|
||||
$res = $this->driver->query("PRAGMA index_list({$this->escapeIdentifier($table)})");
|
||||
$indexes = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$indexes[$row['name']]['name'] = $row['name'];
|
||||
@@ -86,7 +140,7 @@ class SqliteReflector implements Dibi\Reflector
|
||||
}
|
||||
|
||||
foreach ($indexes as $index => $values) {
|
||||
$res = $this->driver->query("PRAGMA index_info({$this->driver->escapeIdentifier($index)})");
|
||||
$res = $this->driver->query("PRAGMA index_info({$this->escapeIdentifier($index)})");
|
||||
while ($row = $res->fetch(true)) {
|
||||
$indexes[$index]['columns'][$row['seqno']] = $row['name'];
|
||||
}
|
||||
@@ -129,7 +183,7 @@ class SqliteReflector implements Dibi\Reflector
|
||||
*/
|
||||
public function getForeignKeys(string $table): array
|
||||
{
|
||||
$res = $this->driver->query("PRAGMA foreign_key_list({$this->driver->escapeIdentifier($table)})");
|
||||
$res = $this->driver->query("PRAGMA foreign_key_list({$this->escapeIdentifier($table)})");
|
||||
$keys = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$keys[$row['id']]['name'] = $row['id']; // foreign key name
|
@@ -7,10 +7,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Ibase;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use Dibi\Helpers;
|
||||
use function is_resource;
|
||||
|
||||
|
||||
/**
|
||||
@@ -24,9 +26,12 @@ use Dibi\Helpers;
|
||||
* - buffers (int) => buffers is the number of database buffers to allocate for the server-side cache. If 0 or omitted, server chooses its own default.
|
||||
* - resource (resource) => existing connection resource
|
||||
*/
|
||||
class FirebirdDriver implements Dibi\Driver
|
||||
class Connection implements Drivers\Connection
|
||||
{
|
||||
public const ERROR_EXCEPTION_THROWN = -836;
|
||||
public const ErrorExceptionThrown = -836;
|
||||
|
||||
#[\Deprecated('use FirebirdDriver::ErrorExceptionThrown')]
|
||||
public const ERROR_EXCEPTION_THROWN = self::ErrorExceptionThrown;
|
||||
|
||||
/** @var resource */
|
||||
private $connection;
|
||||
@@ -82,7 +87,7 @@ class FirebirdDriver implements Dibi\Driver
|
||||
* Executes the SQL query.
|
||||
* @throws Dibi\DriverException|Dibi\Exception
|
||||
*/
|
||||
public function query(string $sql): ?Dibi\ResultDriver
|
||||
public function query(string $sql): ?Result
|
||||
{
|
||||
$resource = $this->inTransaction
|
||||
? $this->transaction
|
||||
@@ -196,9 +201,9 @@ class FirebirdDriver implements Dibi\Driver
|
||||
/**
|
||||
* Returns the connection reflector.
|
||||
*/
|
||||
public function getReflector(): Dibi\Reflector
|
||||
public function getReflector(): Drivers\Engine
|
||||
{
|
||||
return new FirebirdReflector($this);
|
||||
return new Drivers\Engines\FirebirdEngine($this);
|
||||
}
|
||||
|
||||
|
||||
@@ -206,9 +211,9 @@ class FirebirdDriver implements Dibi\Driver
|
||||
* Result set driver factory.
|
||||
* @param resource $resource
|
||||
*/
|
||||
public function createResultDriver($resource): FirebirdResult
|
||||
public function createResultDriver($resource): Result
|
||||
{
|
||||
return new FirebirdResult($resource);
|
||||
return new Result($resource);
|
||||
}
|
||||
|
||||
|
||||
@@ -228,56 +233,4 @@ class FirebirdDriver implements Dibi\Driver
|
||||
{
|
||||
return "'" . str_replace("'", "''", $value) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
return '"' . str_replace('"', '""', $value) . '"';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes($this->escapeText($value), '%_\\');
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit > 0 || $offset > 0) {
|
||||
// http://www.firebirdsql.org/refdocs/langrefupd20-select.html
|
||||
$sql = 'SELECT ' . ($limit > 0 ? 'FIRST ' . $limit : '') . ($offset > 0 ? ' SKIP ' . $offset : '') . ' * FROM (' . $sql . ')';
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,27 +7,23 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\Ibase;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use Dibi\Helpers;
|
||||
use function is_resource;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for Firebird/InterBase result set.
|
||||
*/
|
||||
class FirebirdResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
/** @var resource */
|
||||
private $resultSet;
|
||||
|
||||
|
||||
/**
|
||||
* @param resource $resultSet
|
||||
*/
|
||||
public function __construct($resultSet)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
public function __construct(
|
||||
/** @var resource */
|
||||
private $resultSet,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +47,7 @@ class FirebirdResult implements Dibi\ResultDriver
|
||||
: @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
|
||||
|
||||
if (ibase_errcode()) {
|
||||
if (ibase_errcode() === FirebirdDriver::ERROR_EXCEPTION_THROWN) {
|
||||
if (ibase_errcode() === Connection::ERROR_EXCEPTION_THROWN) {
|
||||
preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
|
||||
throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);
|
||||
|
@@ -7,9 +7,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\MySQLi;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use function in_array;
|
||||
use const MYSQLI_REPORT_OFF, MYSQLI_STORE_RESULT, MYSQLI_USE_RESULT, PREG_SET_ORDER;
|
||||
|
||||
|
||||
/**
|
||||
@@ -30,11 +33,20 @@ use Dibi;
|
||||
* - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
|
||||
* - resource (mysqli) => existing connection resource
|
||||
*/
|
||||
class MySqliDriver implements Dibi\Driver
|
||||
class Connection implements Drivers\Connection
|
||||
{
|
||||
public const ERROR_ACCESS_DENIED = 1045;
|
||||
public const ERROR_DUPLICATE_ENTRY = 1062;
|
||||
public const ERROR_DATA_TRUNCATED = 1265;
|
||||
public const ErrorAccessDenied = 1045;
|
||||
public const ErrorDuplicateEntry = 1062;
|
||||
public const ErrorDataTruncated = 1265;
|
||||
|
||||
#[\Deprecated('use MySqliDriver::ErrorAccessDenied')]
|
||||
public const ERROR_ACCESS_DENIED = self::ErrorAccessDenied;
|
||||
|
||||
#[\Deprecated('use MySqliDriver::ErrorDuplicateEntry')]
|
||||
public const ERROR_DUPLICATE_ENTRY = self::ErrorDuplicateEntry;
|
||||
|
||||
#[\Deprecated('use MySqliDriver::ErrorDataTruncated')]
|
||||
public const ERROR_DATA_TRUNCATED = self::ErrorDataTruncated;
|
||||
|
||||
private \mysqli $connection;
|
||||
private bool $buffered = false;
|
||||
@@ -137,7 +149,7 @@ class MySqliDriver implements Dibi\Driver
|
||||
* Executes the SQL query.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function query(string $sql): ?Dibi\ResultDriver
|
||||
public function query(string $sql): ?Result
|
||||
{
|
||||
$res = @$this->connection->query($sql, $this->buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); // intentionally @
|
||||
|
||||
@@ -254,18 +266,18 @@ class MySqliDriver implements Dibi\Driver
|
||||
/**
|
||||
* Returns the connection reflector.
|
||||
*/
|
||||
public function getReflector(): Dibi\Reflector
|
||||
public function getReflector(): Drivers\Engine
|
||||
{
|
||||
return new MySqlReflector($this);
|
||||
return new Drivers\Engines\MySQLEngine($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Result set driver factory.
|
||||
*/
|
||||
public function createResultDriver(\mysqli_result $result): MySqliResult
|
||||
public function createResultDriver(\mysqli_result $result): Result
|
||||
{
|
||||
return new MySqliResult($result, $this->buffered);
|
||||
return new Result($result, $this->buffered);
|
||||
}
|
||||
|
||||
|
||||
@@ -285,64 +297,4 @@ class MySqliDriver implements Dibi\Driver
|
||||
{
|
||||
return "_binary'" . $this->connection->escape_string($value) . "'";
|
||||
}
|
||||
|
||||
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
return '`' . str_replace('`', '``', $value) . '`';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format("'Y-m-d H:i:s.u'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
if ($value->y || $value->m || $value->d) {
|
||||
throw new Dibi\NotSupportedException('Only time interval is supported.');
|
||||
}
|
||||
|
||||
return $value->format("'%r%H:%I:%S.%f'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,24 +7,22 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\MySQLi;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use const MYSQLI_TYPE_LONG, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_TIME, MYSQLI_TYPE_TINY;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for MySQL result set.
|
||||
*/
|
||||
class MySqliResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
private \mysqli_result $resultSet;
|
||||
private bool $buffered;
|
||||
|
||||
|
||||
public function __construct(\mysqli_result $resultSet, bool $buffered)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
$this->buffered = $buffered;
|
||||
public function __construct(
|
||||
private readonly \mysqli_result $resultSet,
|
||||
private readonly bool $buffered,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +101,7 @@ class MySqliResult implements Dibi\ResultDriver
|
||||
'table' => $row['orgtable'],
|
||||
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
|
||||
'nativetype' => $types[$row['type']] ?? $row['type'],
|
||||
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TIME_INTERVAL : null,
|
||||
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TimeInterval : null,
|
||||
'vendor' => $row,
|
||||
];
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,26 +7,22 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\OCI8;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use function is_resource;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for Oracle result set.
|
||||
*/
|
||||
class OracleResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
/** @var resource */
|
||||
private $resultSet;
|
||||
|
||||
|
||||
/**
|
||||
* @param resource $resultSet
|
||||
*/
|
||||
public function __construct($resultSet)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
public function __construct(
|
||||
/** @var resource */
|
||||
private $resultSet,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +76,7 @@ class OracleResult implements Dibi\ResultDriver
|
||||
'name' => oci_field_name($this->resultSet, $i),
|
||||
'table' => null,
|
||||
'fullname' => oci_field_name($this->resultSet, $i),
|
||||
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null,
|
||||
'type' => $type === 'LONG' ? Dibi\Type::Text : null,
|
||||
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
|
||||
];
|
||||
}
|
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,27 +7,25 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\ODBC;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use function is_resource;
|
||||
|
||||
|
||||
/**
|
||||
* The driver interacting with result set via ODBC connections.
|
||||
*/
|
||||
class OdbcResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
/** @var resource */
|
||||
private $resultSet;
|
||||
private int $row = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @param resource $resultSet
|
||||
*/
|
||||
public function __construct($resultSet)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
public function __construct(
|
||||
/** @var resource */
|
||||
private $resultSet,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
|
||||
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
|
||||
use Dibi;
|
||||
|
||||
|
||||
/**
|
||||
* The reflector for Oracle database.
|
||||
*/
|
||||
class OracleReflector implements Dibi\Reflector
|
||||
{
|
||||
private Dibi\Driver $driver;
|
||||
|
||||
|
||||
public function __construct(Dibi\Driver $driver)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns list of tables.
|
||||
*/
|
||||
public function getTables(): array
|
||||
{
|
||||
$res = $this->driver->query('SELECT * FROM cat');
|
||||
$tables = [];
|
||||
while ($row = $res->fetch(false)) {
|
||||
if ($row[1] === 'TABLE' || $row[1] === 'VIEW') {
|
||||
$tables[] = [
|
||||
'name' => $row[0],
|
||||
'view' => $row[1] === 'VIEW',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns metadata for all columns in a table.
|
||||
*/
|
||||
public function getColumns(string $table): array
|
||||
{
|
||||
$res = $this->driver->query('SELECT * FROM "ALL_TAB_COLUMNS" WHERE "TABLE_NAME" = ' . $this->driver->escapeText($table));
|
||||
$columns = [];
|
||||
while ($row = $res->fetch(true)) {
|
||||
$columns[] = [
|
||||
'table' => $row['TABLE_NAME'],
|
||||
'name' => $row['COLUMN_NAME'],
|
||||
'nativetype' => $row['DATA_TYPE'],
|
||||
'size' => $row['DATA_LENGTH'] ?? null,
|
||||
'nullable' => $row['NULLABLE'] === 'Y',
|
||||
'default' => $row['DATA_DEFAULT'],
|
||||
'vendor' => $row,
|
||||
];
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns metadata for all indexes in a table.
|
||||
*/
|
||||
public function getIndexes(string $table): array
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns metadata for all foreign keys in a table.
|
||||
*/
|
||||
public function getForeignKeys(string $table): array
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
}
|
223
src/Dibi/Drivers/PDO/Connection.php
Normal file
223
src/Dibi/Drivers/PDO/Connection.php
Normal 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),
|
||||
};
|
||||
}
|
||||
}
|
@@ -7,9 +7,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\PDO;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use Dibi\Helpers;
|
||||
use PDO;
|
||||
|
||||
@@ -17,16 +18,12 @@ use PDO;
|
||||
/**
|
||||
* The driver for PDO result set.
|
||||
*/
|
||||
class PdoResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
private ?\PDOStatement $resultSet;
|
||||
private string $driverName;
|
||||
|
||||
|
||||
public function __construct(\PDOStatement $resultSet, string $driverName)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
$this->driverName = $driverName;
|
||||
public function __construct(
|
||||
private ?\PDOStatement $resultSet,
|
||||
private readonly string $driverName,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +87,7 @@ class PdoResult implements Dibi\ResultDriver
|
||||
'name' => $row['name'],
|
||||
'table' => $row['table'],
|
||||
'nativetype' => $row['native_type'],
|
||||
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TIME_INTERVAL : null,
|
||||
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TimeInterval : null,
|
||||
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
|
||||
'vendor' => $row,
|
||||
];
|
@@ -1,414 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
|
||||
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Helpers;
|
||||
use PDO;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for PDO.
|
||||
*
|
||||
* Driver options:
|
||||
* - dsn => driver specific DSN
|
||||
* - username (or user)
|
||||
* - password (or pass)
|
||||
* - options (array) => driver specific options {@see PDO::__construct}
|
||||
* - resource (PDO) => existing connection
|
||||
* - version
|
||||
*/
|
||||
class PdoDriver implements Dibi\Driver
|
||||
{
|
||||
private ?PDO $connection;
|
||||
private ?int $affectedRows;
|
||||
private string $driverName;
|
||||
private string $serverVersion = '';
|
||||
|
||||
|
||||
/** @throws Dibi\NotSupportedException */
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('pdo')) {
|
||||
throw new Dibi\NotSupportedException("PHP extension 'pdo' is not loaded.");
|
||||
}
|
||||
|
||||
$foo = &$config['dsn'];
|
||||
$foo = &$config['options'];
|
||||
Helpers::alias($config, 'resource', 'pdo');
|
||||
|
||||
if ($config['resource'] instanceof PDO) {
|
||||
$this->connection = $config['resource'];
|
||||
unset($config['resource'], $config['pdo']);
|
||||
|
||||
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
|
||||
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
|
||||
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
|
||||
} catch (\PDOException $e) {
|
||||
if ($e->getMessage() === 'could not find driver') {
|
||||
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
|
||||
}
|
||||
|
||||
throw new Dibi\DriverException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||
$this->serverVersion = (string) ($config['version'] ?? @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)); // @ - may be not supported
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects from a database.
|
||||
*/
|
||||
public function disconnect(): void
|
||||
{
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the SQL query.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function query(string $sql): ?Dibi\ResultDriver
|
||||
{
|
||||
$res = $this->connection->query($sql);
|
||||
if ($res) {
|
||||
$this->affectedRows = $res->rowCount();
|
||||
return $res->columnCount() ? $this->createResultDriver($res) : null;
|
||||
}
|
||||
|
||||
$this->affectedRows = null;
|
||||
|
||||
[$sqlState, $code, $message] = $this->connection->errorInfo();
|
||||
$code ??= 0;
|
||||
$message = "SQLSTATE[$sqlState]: $message";
|
||||
throw match ($this->driverName) {
|
||||
'mysql' => MySqliDriver::createException($message, $code, $sql),
|
||||
'oci' => OracleDriver::createException($message, $code, $sql),
|
||||
'pgsql' => PostgreDriver::createException($message, $sqlState, $sql),
|
||||
'sqlite' => SqliteDriver::createException($message, $code, $sql),
|
||||
default => new Dibi\DriverException($message, $code, $sql),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
|
||||
*/
|
||||
public function getAffectedRows(): ?int
|
||||
{
|
||||
return $this->affectedRows;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
||||
*/
|
||||
public function getInsertId(?string $sequence): ?int
|
||||
{
|
||||
return Helpers::intVal($this->connection->lastInsertId($sequence));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins a transaction (if supported).
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function begin(?string $savepoint = null): void
|
||||
{
|
||||
if (!$this->connection->beginTransaction()) {
|
||||
$err = $this->connection->errorInfo();
|
||||
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Commits statements in a transaction.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function commit(?string $savepoint = null): void
|
||||
{
|
||||
if (!$this->connection->commit()) {
|
||||
$err = $this->connection->errorInfo();
|
||||
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rollback changes in a transaction.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function rollback(?string $savepoint = null): void
|
||||
{
|
||||
if (!$this->connection->rollBack()) {
|
||||
$err = $this->connection->errorInfo();
|
||||
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection resource.
|
||||
*/
|
||||
public function getResource(): ?PDO
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection reflector.
|
||||
*/
|
||||
public function getReflector(): Dibi\Reflector
|
||||
{
|
||||
switch ($this->driverName) {
|
||||
case 'mysql':
|
||||
return new MySqlReflector($this);
|
||||
|
||||
case 'oci':
|
||||
return new OracleReflector($this);
|
||||
|
||||
case 'pgsql':
|
||||
return new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION));
|
||||
|
||||
case 'sqlite':
|
||||
return new SqliteReflector($this);
|
||||
|
||||
case 'mssql':
|
||||
case 'dblib':
|
||||
case 'sqlsrv':
|
||||
return new SqlsrvReflector($this);
|
||||
|
||||
default:
|
||||
throw new Dibi\NotSupportedException;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Result set driver factory.
|
||||
*/
|
||||
public function createResultDriver(\PDOStatement $result): PdoResult
|
||||
{
|
||||
return new PdoResult($result, $this->driverName);
|
||||
}
|
||||
|
||||
|
||||
/********************* SQL ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Encodes data for use in a SQL statement.
|
||||
*/
|
||||
public function escapeText(string $value): string
|
||||
{
|
||||
return $this->driverName === 'odbc'
|
||||
? "'" . str_replace("'", "''", $value) . "'"
|
||||
: $this->connection->quote($value, PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
|
||||
public function escapeBinary(string $value): string
|
||||
{
|
||||
return $this->driverName === 'odbc'
|
||||
? "'" . str_replace("'", "''", $value) . "'"
|
||||
: $this->connection->quote($value, PDO::PARAM_LOB);
|
||||
}
|
||||
|
||||
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
switch ($this->driverName) {
|
||||
case 'mysql':
|
||||
return '`' . str_replace('`', '``', $value) . '`';
|
||||
|
||||
case 'oci':
|
||||
case 'pgsql':
|
||||
return '"' . str_replace('"', '""', $value) . '"';
|
||||
|
||||
case 'sqlite':
|
||||
return '[' . strtr($value, '[]', ' ') . ']';
|
||||
|
||||
case 'odbc':
|
||||
case 'mssql':
|
||||
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
|
||||
|
||||
case 'dblib':
|
||||
case 'sqlsrv':
|
||||
return '[' . str_replace(']', ']]', $value) . ']';
|
||||
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
if ($this->driverName === 'pgsql') {
|
||||
return $value ? 'TRUE' : 'FALSE';
|
||||
} else {
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
switch ($this->driverName) {
|
||||
case 'odbc':
|
||||
return $value->format('#m/d/Y H:i:s.u#');
|
||||
case 'mssql':
|
||||
case 'dblib':
|
||||
case 'sqlsrv':
|
||||
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
|
||||
default:
|
||||
return $value->format("'Y-m-d H:i:s.u'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
switch ($this->driverName) {
|
||||
case 'mysql':
|
||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
|
||||
case 'oci':
|
||||
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
|
||||
$value = str_replace("'", "''", $value);
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
|
||||
case 'pgsql':
|
||||
$bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
|
||||
$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
|
||||
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
|
||||
case 'sqlite':
|
||||
$value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\');
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
|
||||
case 'odbc':
|
||||
case 'mssql':
|
||||
case 'dblib':
|
||||
case 'sqlsrv':
|
||||
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
|
||||
|
||||
default:
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
}
|
||||
|
||||
switch ($this->driverName) {
|
||||
case 'mysql':
|
||||
if ($limit !== null || $offset) {
|
||||
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'pgsql':
|
||||
if ($limit !== null) {
|
||||
$sql .= ' LIMIT ' . $limit;
|
||||
}
|
||||
|
||||
if ($offset) {
|
||||
$sql .= ' OFFSET ' . $offset;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'sqlite':
|
||||
if ($limit !== null || $offset) {
|
||||
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'oci':
|
||||
if ($offset) {
|
||||
// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
|
||||
$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
|
||||
. ($limit !== null ? 'WHERE ROWNUM <= ' . ($offset + $limit) : '')
|
||||
. ') WHERE "__rnum" > ' . $offset;
|
||||
|
||||
} elseif ($limit !== null) {
|
||||
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'mssql':
|
||||
case 'sqlsrv':
|
||||
case 'dblib':
|
||||
if (version_compare($this->serverVersion, '11.0') >= 0) { // 11 == SQL Server 2012
|
||||
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
||||
if ($limit !== null) {
|
||||
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
|
||||
} elseif ($offset) {
|
||||
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// break omitted
|
||||
case 'odbc':
|
||||
if ($offset) {
|
||||
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
|
||||
|
||||
} elseif ($limit !== null) {
|
||||
$sql = 'SELECT TOP ' . $limit . ' * FROM (' . $sql . ') t';
|
||||
break;
|
||||
}
|
||||
// break omitted
|
||||
default:
|
||||
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,9 +7,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\PgSQL;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use Dibi\Helpers;
|
||||
use PgSql;
|
||||
|
||||
@@ -17,18 +17,11 @@ use PgSql;
|
||||
/**
|
||||
* The driver for PostgreSQL result set.
|
||||
*/
|
||||
class PostgreResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
/** @var resource|PgSql\Result */
|
||||
private $resultSet;
|
||||
|
||||
|
||||
/**
|
||||
* @param resource|PgSql\Result $resultSet
|
||||
*/
|
||||
public function __construct($resultSet)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
public function __construct(
|
||||
private readonly PgSql\Result $resultSet,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -94,13 +87,10 @@ class PostgreResult implements Dibi\ResultDriver
|
||||
|
||||
/**
|
||||
* Returns the result set resource.
|
||||
* @return resource|PgSql\Result|null
|
||||
*/
|
||||
public function getResultResource(): mixed
|
||||
public function getResultResource(): PgSql\Result
|
||||
{
|
||||
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
|
||||
? $this->resultSet
|
||||
: null;
|
||||
return $this->resultSet;
|
||||
}
|
||||
|
||||
|
58
src/Dibi/Drivers/Result.php
Normal file
58
src/Dibi/Drivers/Result.php
Normal 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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,26 +7,22 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\SQLSrv;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use function is_resource;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for Microsoft SQL Server and SQL Azure result set.
|
||||
*/
|
||||
class SqlsrvResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
/** @var resource */
|
||||
private $resultSet;
|
||||
|
||||
|
||||
/**
|
||||
* @param resource $resultSet
|
||||
*/
|
||||
public function __construct($resultSet)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
public function __construct(
|
||||
/** @var resource */
|
||||
private $resultSet,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@@ -7,9 +7,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\SQLite3;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use Dibi\Helpers;
|
||||
use SQLite3;
|
||||
|
||||
@@ -23,7 +24,7 @@ use SQLite3;
|
||||
* - formatDateTime => how to format datetime in SQL (@see date)
|
||||
* - resource (SQLite3) => existing connection resource
|
||||
*/
|
||||
class SqliteDriver implements Dibi\Driver
|
||||
class Connection implements Drivers\Connection
|
||||
{
|
||||
private SQLite3 $connection;
|
||||
private string $fmtDate;
|
||||
@@ -56,11 +57,7 @@ class SqliteDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
|
||||
$version = SQLite3::version();
|
||||
$this->connection->enableExceptions(false);
|
||||
if ($version['versionNumber'] >= '3006019') {
|
||||
$this->query('PRAGMA foreign_keys = ON');
|
||||
}
|
||||
$this->query('PRAGMA foreign_keys = ON');
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +74,7 @@ class SqliteDriver implements Dibi\Driver
|
||||
* Executes the SQL query.
|
||||
* @throws Dibi\DriverException
|
||||
*/
|
||||
public function query(string $sql): ?Dibi\ResultDriver
|
||||
public function query(string $sql): ?Result
|
||||
{
|
||||
$res = @$this->connection->query($sql); // intentionally @
|
||||
if ($code = $this->connection->lastErrorCode()) {
|
||||
@@ -178,18 +175,18 @@ class SqliteDriver implements Dibi\Driver
|
||||
/**
|
||||
* Returns the connection reflector.
|
||||
*/
|
||||
public function getReflector(): Dibi\Reflector
|
||||
public function getReflector(): Drivers\Engine
|
||||
{
|
||||
return new SqliteReflector($this);
|
||||
return new Drivers\Engines\SQLiteEngine($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Result set driver factory.
|
||||
*/
|
||||
public function createResultDriver(\SQLite3Result $result): SqliteResult
|
||||
public function createResultDriver(\SQLite3Result $result): Result
|
||||
{
|
||||
return new SqliteResult($result);
|
||||
return new Result($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -211,61 +208,6 @@ class SqliteDriver implements Dibi\Driver
|
||||
}
|
||||
|
||||
|
||||
public function escapeIdentifier(string $value): string
|
||||
{
|
||||
return '[' . strtr($value, '[]', ' ') . ']';
|
||||
}
|
||||
|
||||
|
||||
public function escapeBool(bool $value): string
|
||||
{
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
public function escapeDate(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format($this->fmtDate);
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateTime(\DateTimeInterface $value): string
|
||||
{
|
||||
return $value->format($this->fmtDateTime);
|
||||
}
|
||||
|
||||
|
||||
public function escapeDateInterval(\DateInterval $value): string
|
||||
{
|
||||
throw new Dibi\NotImplementedException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes string for use in a LIKE statement.
|
||||
*/
|
||||
public function escapeLike(string $value, int $pos): string
|
||||
{
|
||||
$value = addcslashes($this->connection->escapeString($value), '%_\\');
|
||||
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injects LIMIT/OFFSET to the SQL query.
|
||||
*/
|
||||
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
|
||||
{
|
||||
if ($limit < 0 || $offset < 0) {
|
||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||
|
||||
} elseif ($limit !== null || $offset) {
|
||||
$sql .= ' LIMIT ' . ($limit ?? '-1')
|
||||
. ($offset ? ' OFFSET ' . $offset : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************* user defined functions ****************d*g**/
|
||||
|
||||
|
@@ -7,23 +7,22 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dibi\Drivers;
|
||||
namespace Dibi\Drivers\SQLite3;
|
||||
|
||||
use Dibi;
|
||||
use Dibi\Drivers;
|
||||
use Dibi\Helpers;
|
||||
use const SQLITE3_ASSOC, SQLITE3_BLOB, SQLITE3_FLOAT, SQLITE3_INTEGER, SQLITE3_NULL, SQLITE3_NUM, SQLITE3_TEXT;
|
||||
|
||||
|
||||
/**
|
||||
* The driver for SQLite result set.
|
||||
*/
|
||||
class SqliteResult implements Dibi\ResultDriver
|
||||
class Result implements Drivers\Result
|
||||
{
|
||||
private \SQLite3Result $resultSet;
|
||||
|
||||
|
||||
public function __construct(\SQLite3Result $resultSet)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
public function __construct(
|
||||
private readonly \SQLite3Result $resultSet,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
{
|
||||
}
|
@@ -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
|
||||
{
|
||||
}
|
@@ -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;
|
||||
|
@@ -15,7 +15,7 @@ namespace Dibi;
|
||||
*/
|
||||
class Expression
|
||||
{
|
||||
private array $values;
|
||||
private readonly array $values;
|
||||
|
||||
|
||||
public function __construct(...$values)
|
||||
|
@@ -9,6 +9,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Dibi;
|
||||
|
||||
use function array_key_exists, count, func_get_args, is_array, is_string;
|
||||
|
||||
|
||||
/**
|
||||
* SQL builder via fluent interfaces.
|
||||
@@ -45,7 +47,13 @@ namespace Dibi;
|
||||
*/
|
||||
class Fluent implements IDataSource
|
||||
{
|
||||
public const REMOVE = false;
|
||||
public const
|
||||
AffectedRows = 'a',
|
||||
Identifier = 'n',
|
||||
Remove = false;
|
||||
|
||||
#[\Deprecated('use Fluent::Remove')]
|
||||
public const REMOVE = self::Remove;
|
||||
|
||||
public static array $masks = [
|
||||
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
|
||||
@@ -91,7 +99,7 @@ class Fluent implements IDataSource
|
||||
'RIGHT JOIN' => 'FROM',
|
||||
];
|
||||
|
||||
private Connection $connection;
|
||||
private readonly Connection $connection;
|
||||
private array $setups = [];
|
||||
private ?string $command = null;
|
||||
private array $clauses = [];
|
||||
@@ -107,7 +115,7 @@ class Fluent implements IDataSource
|
||||
$this->connection = $connection;
|
||||
|
||||
if (!isset(self::$normalizer)) {
|
||||
self::$normalizer = new HashMap([self::class, '_formatClause']);
|
||||
self::$normalizer = new HashMap(self::_formatClause(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +148,7 @@ class Fluent implements IDataSource
|
||||
$this->cursor = &$this->clauses[$clause];
|
||||
|
||||
// TODO: really delete?
|
||||
if ($args === [self::REMOVE]) {
|
||||
if ($args === [self::Remove]) {
|
||||
$this->cursor = null;
|
||||
return $this;
|
||||
}
|
||||
@@ -156,7 +164,7 @@ class Fluent implements IDataSource
|
||||
}
|
||||
} else {
|
||||
// append to currect flow
|
||||
if ($args === [self::REMOVE]) {
|
||||
if ($args === [self::Remove]) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -279,15 +287,15 @@ class Fluent implements IDataSource
|
||||
/**
|
||||
* Generates and executes SQL query.
|
||||
* Returns result set or number of affected rows
|
||||
* @return ($return is \dibi::IDENTIFIER|\dibi::AFFECTED_ROWS ? int : Result)
|
||||
* @return ($return is self::Identifier|self::AffectedRows ? int : Result)
|
||||
* @throws Exception
|
||||
*/
|
||||
public function execute(?string $return = null): Result|int|null
|
||||
{
|
||||
$res = $this->query($this->_export());
|
||||
return match ($return) {
|
||||
\dibi::IDENTIFIER => $this->connection->getInsertId(),
|
||||
\dibi::AFFECTED_ROWS => $this->connection->getAffectedRows(),
|
||||
self::Identifier => $this->connection->getInsertId(),
|
||||
self::AffectedRows => $this->connection->getAffectedRows(),
|
||||
default => $res,
|
||||
};
|
||||
}
|
||||
|
@@ -9,6 +9,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Dibi;
|
||||
|
||||
use function array_map, array_unique, explode, fclose, fgets, fopen, fstat, getenv, htmlspecialchars, is_float, is_int, is_string, levenshtein, max, mb_strlen, ob_end_flush, ob_get_clean, ob_start, preg_match, preg_replace, preg_replace_callback, rtrim, set_time_limit, str_ends_with, str_repeat, str_starts_with, strlen, strtoupper, substr, trim, wordwrap;
|
||||
use const PHP_SAPI;
|
||||
|
||||
|
||||
class Helpers
|
||||
{
|
||||
@@ -156,15 +159,15 @@ class Helpers
|
||||
|
||||
|
||||
/** @internal */
|
||||
public static function escape(Driver $driver, $value, string $type): string
|
||||
public static function escape(Drivers\Connection $driver, $value, string $type): string
|
||||
{
|
||||
$types = [
|
||||
Type::TEXT => 'text',
|
||||
Type::BINARY => 'binary',
|
||||
Type::BOOL => 'bool',
|
||||
Type::DATE => 'date',
|
||||
Type::DATETIME => 'datetime',
|
||||
\dibi::IDENTIFIER => 'identifier',
|
||||
Type::Text => 'text',
|
||||
Type::Binary => 'binary',
|
||||
Type::Bool => 'bool',
|
||||
Type::Date => 'date',
|
||||
Type::DateTime => 'datetime',
|
||||
Fluent::Identifier => 'identifier',
|
||||
];
|
||||
if (isset($types[$type])) {
|
||||
return $driver->{'escape' . $types[$type]}($value);
|
||||
@@ -181,16 +184,16 @@ class Helpers
|
||||
public static function detectType(string $type): ?string
|
||||
{
|
||||
$patterns = [
|
||||
'^_' => Type::TEXT, // PostgreSQL arrays
|
||||
'RANGE$' => Type::TEXT, // PostgreSQL range types
|
||||
'BYTEA|BLOB|BIN' => Type::BINARY,
|
||||
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
|
||||
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
|
||||
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::FLOAT,
|
||||
'^TIME$' => Type::TIME,
|
||||
'TIME' => Type::DATETIME, // DATETIME, TIMESTAMP
|
||||
'DATE' => Type::DATE,
|
||||
'BOOL' => Type::BOOL,
|
||||
'^_' => Type::Text, // PostgreSQL arrays
|
||||
'RANGE$' => Type::Text, // PostgreSQL range types
|
||||
'BYTEA|BLOB|BIN' => Type::Binary,
|
||||
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::Text,
|
||||
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::Integer,
|
||||
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::Float,
|
||||
'^TIME$' => Type::Time,
|
||||
'TIME' => Type::DateTime, // DATETIME, TIMESTAMP
|
||||
'DATE' => Type::Date,
|
||||
'BOOL' => Type::Bool,
|
||||
'JSON' => Type::JSON,
|
||||
];
|
||||
|
||||
@@ -208,7 +211,7 @@ class Helpers
|
||||
public static function getTypeCache(): HashMap
|
||||
{
|
||||
if (!isset(self::$types)) {
|
||||
self::$types = new HashMap([self::class, 'detectType']);
|
||||
self::$types = new HashMap(self::detectType(...));
|
||||
}
|
||||
|
||||
return self::$types;
|
||||
|
20
src/Dibi/IDataSource.php
Normal file
20
src/Dibi/IDataSource.php
Normal 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();
|
||||
}
|
@@ -15,7 +15,7 @@ namespace Dibi;
|
||||
*/
|
||||
class Literal
|
||||
{
|
||||
private string $value;
|
||||
private readonly string $value;
|
||||
|
||||
|
||||
public function __construct($value)
|
||||
|
@@ -10,6 +10,8 @@ declare(strict_types=1);
|
||||
namespace Dibi\Loggers;
|
||||
|
||||
use Dibi;
|
||||
use function sprintf;
|
||||
use const FILE_APPEND, LOCK_EX;
|
||||
|
||||
|
||||
/**
|
||||
@@ -17,17 +19,11 @@ use Dibi;
|
||||
*/
|
||||
class FileLogger
|
||||
{
|
||||
/** Name of the file where SQL errors should be logged */
|
||||
public string $file;
|
||||
public int $filter;
|
||||
private bool $errorsOnly;
|
||||
|
||||
|
||||
public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->filter = $filter ?: Dibi\Event::QUERY;
|
||||
$this->errorsOnly = $errorsOnly;
|
||||
public function __construct(
|
||||
public string $file,
|
||||
public int $filter = Dibi\Event::QUERY,
|
||||
private bool $errorsOnly = false,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
@@ -27,17 +27,10 @@ use Dibi;
|
||||
*/
|
||||
class Column
|
||||
{
|
||||
/** when created by Result */
|
||||
private ?Dibi\Reflector $reflector;
|
||||
|
||||
/** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */
|
||||
private array $info;
|
||||
|
||||
|
||||
public function __construct(?Dibi\Reflector $reflector, array $info)
|
||||
{
|
||||
$this->reflector = $reflector;
|
||||
$this->info = $info;
|
||||
public function __construct(
|
||||
private readonly ?Dibi\Drivers\Engine $reflector,
|
||||
private array $info,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
@@ -10,6 +10,7 @@ declare(strict_types=1);
|
||||
namespace Dibi\Reflection;
|
||||
|
||||
use Dibi;
|
||||
use function array_values, strtolower;
|
||||
|
||||
|
||||
/**
|
||||
@@ -21,17 +22,14 @@ use Dibi;
|
||||
*/
|
||||
class Database
|
||||
{
|
||||
private Dibi\Reflector $reflector;
|
||||
private ?string $name;
|
||||
|
||||
/** @var Table[] */
|
||||
private array $tables;
|
||||
|
||||
|
||||
public function __construct(Dibi\Reflector $reflector, ?string $name = null)
|
||||
{
|
||||
$this->reflector = $reflector;
|
||||
$this->name = $name;
|
||||
public function __construct(
|
||||
private readonly Dibi\Drivers\Engine $reflector,
|
||||
private ?string $name = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
@@ -19,16 +19,10 @@ namespace Dibi\Reflection;
|
||||
*/
|
||||
class ForeignKey
|
||||
{
|
||||
private string $name;
|
||||
|
||||
/** @var array of [local, foreign, onDelete, onUpdate] */
|
||||
private array $references;
|
||||
|
||||
|
||||
public function __construct(string $name, array $references)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->references = $references;
|
||||
public function __construct(
|
||||
private readonly string $name,
|
||||
private readonly array $references,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
@@ -21,13 +21,9 @@ namespace Dibi\Reflection;
|
||||
*/
|
||||
class Index
|
||||
{
|
||||
/** @var array (name, columns, [unique], [primary]) */
|
||||
private array $info;
|
||||
|
||||
|
||||
public function __construct(array $info)
|
||||
{
|
||||
$this->info = $info;
|
||||
public function __construct(
|
||||
private readonly array $info,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
@@ -10,6 +10,7 @@ declare(strict_types=1);
|
||||
namespace Dibi\Reflection;
|
||||
|
||||
use Dibi;
|
||||
use function array_values, strtolower;
|
||||
|
||||
|
||||
/**
|
||||
@@ -20,8 +21,6 @@ use Dibi;
|
||||
*/
|
||||
class Result
|
||||
{
|
||||
private Dibi\ResultDriver $driver;
|
||||
|
||||
/** @var Column[]|null */
|
||||
private ?array $columns;
|
||||
|
||||
@@ -29,9 +28,9 @@ class Result
|
||||
private ?array $names;
|
||||
|
||||
|
||||
public function __construct(Dibi\ResultDriver $driver)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
public function __construct(
|
||||
private readonly Dibi\Drivers\Result $driver,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +79,7 @@ class Result
|
||||
{
|
||||
if (!isset($this->columns)) {
|
||||
$this->columns = [];
|
||||
$reflector = $this->driver instanceof Dibi\Reflector
|
||||
$reflector = $this->driver instanceof Dibi\Drivers\Engine
|
||||
? $this->driver
|
||||
: null;
|
||||
foreach ($this->driver->getResultColumns() as $info) {
|
||||
|
@@ -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'];
|
||||
|
@@ -9,6 +9,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Dibi;
|
||||
|
||||
use function array_keys, array_pop, count, explode, is_float, is_string, json_decode, ltrim, preg_match, preg_split, property_exists, reset, rtrim, str_contains, str_replace, str_starts_with, strpos;
|
||||
use const PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
|
||||
|
||||
|
||||
/**
|
||||
* Query result.
|
||||
@@ -17,7 +20,7 @@ namespace Dibi;
|
||||
*/
|
||||
class Result implements IDataSource
|
||||
{
|
||||
private ?ResultDriver $driver;
|
||||
private ?Drivers\Result $driver;
|
||||
|
||||
/** Translate table */
|
||||
private array $types = [];
|
||||
@@ -34,7 +37,7 @@ class Result implements IDataSource
|
||||
private array $formats = [];
|
||||
|
||||
|
||||
public function __construct(ResultDriver $driver, bool $normalize = true)
|
||||
public function __construct(Drivers\Result $driver, bool $normalize = true)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
if ($normalize) {
|
||||
@@ -59,7 +62,7 @@ class Result implements IDataSource
|
||||
* Safe access to property $driver.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
final public function getResultDriver(): ResultDriver
|
||||
final public function getResultDriver(): Drivers\Result
|
||||
{
|
||||
if ($this->driver === null) {
|
||||
throw new \RuntimeException('Result-set was released from memory.');
|
||||
@@ -457,16 +460,22 @@ class Result implements IDataSource
|
||||
if ($type === null || $format === 'native') {
|
||||
$row[$key] = $value;
|
||||
|
||||
} elseif ($type === Type::TEXT) {
|
||||
} elseif ($type === Type::Text) {
|
||||
$row[$key] = (string) $value;
|
||||
|
||||
} elseif ($type === Type::INTEGER) {
|
||||
} elseif ($type === Type::Integer) {
|
||||
$row[$key] = is_float($tmp = $value * 1)
|
||||
? (is_string($value) ? $value : (int) $value)
|
||||
: $tmp;
|
||||
|
||||
} elseif ($type === Type::FLOAT) {
|
||||
$value = ltrim((string) $value, '0');
|
||||
} elseif ($type === Type::Float) {
|
||||
if (!is_string($value)) {
|
||||
$row[$key] = (float) $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$negative = ($value[0] ?? null) === '-';
|
||||
$value = ltrim($value, '0-');
|
||||
$p = strpos($value, '.');
|
||||
$e = strpos($value, 'e');
|
||||
if ($p !== false && $e === false) {
|
||||
@@ -479,27 +488,31 @@ class Result implements IDataSource
|
||||
$value = '0' . $value;
|
||||
}
|
||||
|
||||
if ($negative) {
|
||||
$value = '-' . $value;
|
||||
}
|
||||
|
||||
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
|
||||
? $float
|
||||
: $value;
|
||||
|
||||
} elseif ($type === Type::BOOL) {
|
||||
} elseif ($type === Type::Bool) {
|
||||
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
|
||||
|
||||
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
|
||||
} elseif ($type === Type::DateTime || $type === Type::Date || $type === Type::Time) {
|
||||
if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
|
||||
$value = new DateTime($value);
|
||||
$row[$key] = $format ? $value->format($format) : $value;
|
||||
} else {
|
||||
$row[$key] = null;
|
||||
}
|
||||
} elseif ($type === Type::TIME_INTERVAL) {
|
||||
} elseif ($type === Type::TimeInterval) {
|
||||
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
|
||||
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
|
||||
$value->invert = (int) (bool) $m[1];
|
||||
$row[$key] = $format ? $value->format($format) : $value;
|
||||
|
||||
} elseif ($type === Type::BINARY) {
|
||||
} elseif ($type === Type::Binary) {
|
||||
$row[$key] = is_string($value)
|
||||
? $this->getResultDriver()->unescapeBinary($value)
|
||||
: $value;
|
||||
|
@@ -15,14 +15,13 @@ namespace Dibi;
|
||||
*/
|
||||
class ResultIterator implements \Iterator, \Countable
|
||||
{
|
||||
private Result $result;
|
||||
private mixed $row;
|
||||
private int $pointer = 0;
|
||||
|
||||
|
||||
public function __construct(Result $result)
|
||||
{
|
||||
$this->result = $result;
|
||||
public function __construct(
|
||||
private readonly Result $result,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
@@ -9,6 +9,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Dibi;
|
||||
|
||||
use function array_keys, count, str_starts_with;
|
||||
|
||||
|
||||
/**
|
||||
* Result set single row.
|
||||
|
@@ -9,14 +9,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace Dibi;
|
||||
|
||||
use function array_filter, array_keys, array_splice, array_values, count, explode, get_debug_type, gettype, implode, is_array, is_bool, is_float, is_int, is_numeric, is_object, is_scalar, is_string, iterator_to_array, key, ltrim, number_format, preg_last_error, preg_match, preg_replace_callback, reset, rtrim, str_contains, str_replace, strcspn, strlen, strncasecmp, strtoupper, substr, trim;
|
||||
|
||||
|
||||
/**
|
||||
* SQL translator.
|
||||
*/
|
||||
final class Translator
|
||||
{
|
||||
private Connection $connection;
|
||||
private Driver $driver;
|
||||
private readonly Connection $connection;
|
||||
private readonly Drivers\Connection $driver;
|
||||
private readonly Drivers\Engine $engine;
|
||||
private int $cursor = 0;
|
||||
private array $args;
|
||||
|
||||
@@ -34,7 +37,8 @@ final class Translator
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->driver = $connection->getDriver();
|
||||
$this->identifiers = new HashMap([$this, 'delimite']);
|
||||
$this->engine = $connection->getDatabaseEngine();
|
||||
$this->identifiers = new HashMap($this->delimite(...));
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +92,7 @@ final class Translator
|
||||
(\?) ## 11) placeholder
|
||||
)/xs
|
||||
XX,
|
||||
[$this, 'cb'],
|
||||
$this->cb(...),
|
||||
substr($arg, $toSkip),
|
||||
);
|
||||
if (preg_last_error()) {
|
||||
@@ -142,7 +146,7 @@ final class Translator
|
||||
|
||||
// apply limit
|
||||
if ($this->limit !== null || $this->offset !== null) {
|
||||
$this->driver->applyLimit($sql, $this->limit, $this->offset);
|
||||
$this->engine->applyLimit($sql, $this->limit, $this->offset);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
@@ -207,7 +211,7 @@ final class Translator
|
||||
case 'n': // key, key, ... identifier names
|
||||
foreach ($value as $k => $v) {
|
||||
if (is_string($k)) {
|
||||
$vx[] = $this->identifiers->$k . (empty($v) ? '' : ' AS ' . $this->driver->escapeIdentifier($v));
|
||||
$vx[] = $this->identifiers->$k . (empty($v) ? '' : ' AS ' . $this->engine->escapeIdentifier($v));
|
||||
} else {
|
||||
$pair = explode('%', $v, 2); // split into identifier & modifier
|
||||
$vx[] = $this->identifiers->{$pair[0]};
|
||||
@@ -219,7 +223,7 @@ final class Translator
|
||||
|
||||
case 'a': // key=val, key=val, ...
|
||||
foreach ($value as $k => $v) {
|
||||
$pair = explode('%', $k, 2); // split into identifier & modifier
|
||||
$pair = explode('%', (string) $k, 2); // split into identifier & modifier
|
||||
$vx[] = $this->identifiers->{$pair[0]} . '='
|
||||
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
||||
}
|
||||
@@ -344,7 +348,7 @@ final class Translator
|
||||
case 's': // string
|
||||
return $value === null
|
||||
? 'NULL'
|
||||
: $this->driver->escapeText((string) $value);
|
||||
: $this->engine->escapeText((string) $value);
|
||||
|
||||
case 'bin':// binary
|
||||
return $value === null
|
||||
@@ -354,7 +358,7 @@ final class Translator
|
||||
case 'b': // boolean
|
||||
return $value === null
|
||||
? 'NULL'
|
||||
: $this->driver->escapeBool((bool) $value);
|
||||
: $this->engine->escapeBool((bool) $value);
|
||||
|
||||
case 'sN': // string or null
|
||||
case 'sn':
|
||||
@@ -404,15 +408,15 @@ final class Translator
|
||||
}
|
||||
|
||||
return $modifier === 'd'
|
||||
? $this->driver->escapeDate($value)
|
||||
: $this->driver->escapeDateTime($value);
|
||||
? $this->engine->escapeDate($value)
|
||||
: $this->engine->escapeDateTime($value);
|
||||
|
||||
case 'by':
|
||||
case 'n': // composed identifier name
|
||||
return $this->identifiers->$value;
|
||||
|
||||
case 'N': // identifier name
|
||||
return $this->driver->escapeIdentifier($value);
|
||||
return $this->engine->escapeIdentifier($value);
|
||||
|
||||
case 'ex':
|
||||
case 'sql': // preserve as dibi-SQL (TODO: leave only %ex)
|
||||
@@ -434,7 +438,7 @@ final class Translator
|
||||
:(\S*?:)([a-zA-Z0-9._]?)
|
||||
)/sx
|
||||
XX,
|
||||
[$this, 'cb'],
|
||||
$this->cb(...),
|
||||
substr($value, $toSkip),
|
||||
);
|
||||
if (preg_last_error()) {
|
||||
@@ -448,16 +452,16 @@ final class Translator
|
||||
return (string) $value;
|
||||
|
||||
case 'like~': // LIKE string%
|
||||
return $this->driver->escapeLike($value, 2);
|
||||
return $this->engine->escapeLike($value, 2);
|
||||
|
||||
case '~like': // LIKE %string
|
||||
return $this->driver->escapeLike($value, 1);
|
||||
return $this->engine->escapeLike($value, 1);
|
||||
|
||||
case '~like~': // LIKE %string%
|
||||
return $this->driver->escapeLike($value, 3);
|
||||
return $this->engine->escapeLike($value, 3);
|
||||
|
||||
case 'like': // LIKE string
|
||||
return $this->driver->escapeLike($value, 0);
|
||||
return $this->engine->escapeLike($value, 0);
|
||||
|
||||
case 'and':
|
||||
case 'or':
|
||||
@@ -483,16 +487,16 @@ final class Translator
|
||||
return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
|
||||
|
||||
} elseif (is_bool($value)) {
|
||||
return $this->driver->escapeBool($value);
|
||||
return $this->engine->escapeBool($value);
|
||||
|
||||
} elseif ($value === null) {
|
||||
return 'NULL';
|
||||
|
||||
} elseif ($value instanceof \DateTimeInterface) {
|
||||
return $this->driver->escapeDateTime($value);
|
||||
return $this->engine->escapeDateTime($value);
|
||||
|
||||
} elseif ($value instanceof \DateInterval) {
|
||||
return $this->driver->escapeDateInterval($value);
|
||||
return $this->engine->escapeDateInterval($value);
|
||||
|
||||
} elseif ($value instanceof Literal) {
|
||||
return (string) $value;
|
||||
@@ -651,7 +655,7 @@ final class Translator
|
||||
$parts = explode('.', $value);
|
||||
foreach ($parts as &$v) {
|
||||
if ($v !== '*') {
|
||||
$v = $this->driver->escapeIdentifier($v);
|
||||
$v = $this->engine->escapeIdentifier($v);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -16,16 +16,43 @@ namespace Dibi;
|
||||
class Type
|
||||
{
|
||||
public const
|
||||
TEXT = 's', // as 'string'
|
||||
BINARY = 'bin',
|
||||
Text = 's', // as 'string'
|
||||
Binary = 'bin',
|
||||
JSON = 'json',
|
||||
BOOL = 'b',
|
||||
INTEGER = 'i',
|
||||
FLOAT = 'f',
|
||||
DATE = 'd',
|
||||
DATETIME = 'dt',
|
||||
TIME = 't',
|
||||
TIME_INTERVAL = 'ti';
|
||||
Bool = 'b',
|
||||
Integer = 'i',
|
||||
Float = 'f',
|
||||
Date = 'd',
|
||||
DateTime = 'dt',
|
||||
Time = 't',
|
||||
TimeInterval = 'ti';
|
||||
|
||||
#[\Deprecated('use Type::Text')]
|
||||
public const TEXT = self::Text;
|
||||
|
||||
#[\Deprecated('use Type::Binary')]
|
||||
public const BINARY = self::Binary;
|
||||
|
||||
#[\Deprecated('use Type::Bool')]
|
||||
public const BOOL = self::Bool;
|
||||
|
||||
#[\Deprecated('use Type::Integer')]
|
||||
public const INTEGER = self::Integer;
|
||||
|
||||
#[\Deprecated('use Type::Float')]
|
||||
public const FLOAT = self::Float;
|
||||
|
||||
#[\Deprecated('use Type::Date')]
|
||||
public const DATE = self::Date;
|
||||
|
||||
#[\Deprecated('use Type::DateTime')]
|
||||
public const DATETIME = self::DateTime;
|
||||
|
||||
#[\Deprecated('use Type::Time')]
|
||||
public const TIME = self::Time;
|
||||
|
||||
#[\Deprecated('use Type::TimeInterval')]
|
||||
public const TIME_INTERVAL = self::TimeInterval;
|
||||
|
||||
|
||||
final public function __construct()
|
||||
|
@@ -37,12 +37,13 @@ declare(strict_types=1);
|
||||
*/
|
||||
class dibi
|
||||
{
|
||||
public const
|
||||
AFFECTED_ROWS = 'a',
|
||||
IDENTIFIER = 'n';
|
||||
public const Version = '6.0-dev';
|
||||
|
||||
/** version */
|
||||
public const VERSION = '5.0.1';
|
||||
public const VERSION = self::Version;
|
||||
|
||||
public const AFFECTED_ROWS = Dibi\Fluent::AffectedRows;
|
||||
|
||||
public const IDENTIFIER = Dibi\Fluent::Identifier;
|
||||
|
||||
/** sorting order */
|
||||
public const
|
||||
|
@@ -11,7 +11,7 @@ namespace Dibi;
|
||||
|
||||
|
||||
/**
|
||||
* Dibi common exception.
|
||||
* A database operation failed.
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
@@ -44,7 +44,7 @@ class Exception extends \Exception
|
||||
|
||||
|
||||
/**
|
||||
* database server exception.
|
||||
* The database server reported an error.
|
||||
*/
|
||||
class DriverException extends Exception
|
||||
{
|
||||
@@ -52,7 +52,7 @@ class DriverException extends Exception
|
||||
|
||||
|
||||
/**
|
||||
* PCRE exception.
|
||||
* Regular expression pattern or execution failed.
|
||||
*/
|
||||
class PcreException extends Exception
|
||||
{
|
||||
@@ -63,18 +63,24 @@ class PcreException extends Exception
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The requested feature is not implemented.
|
||||
*/
|
||||
class NotImplementedException extends Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The requested operation is not supported.
|
||||
*/
|
||||
class NotSupportedException extends Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Database procedure exception.
|
||||
* A database stored procedure failed.
|
||||
*/
|
||||
class ProcedureException extends Exception
|
||||
{
|
||||
@@ -102,7 +108,7 @@ class ProcedureException extends Exception
|
||||
|
||||
|
||||
/**
|
||||
* Base class for all constraint violation related exceptions.
|
||||
* A database constraint was violated.
|
||||
*/
|
||||
class ConstraintViolationException extends DriverException
|
||||
{
|
||||
@@ -110,7 +116,7 @@ class ConstraintViolationException extends DriverException
|
||||
|
||||
|
||||
/**
|
||||
* Exception for a foreign key constraint violation.
|
||||
* The foreign key constraint check failed.
|
||||
*/
|
||||
class ForeignKeyConstraintViolationException extends ConstraintViolationException
|
||||
{
|
||||
@@ -118,7 +124,7 @@ class ForeignKeyConstraintViolationException extends ConstraintViolationExceptio
|
||||
|
||||
|
||||
/**
|
||||
* Exception for a NOT NULL constraint violation.
|
||||
* The NOT NULL constraint check failed.
|
||||
*/
|
||||
class NotNullConstraintViolationException extends ConstraintViolationException
|
||||
{
|
||||
@@ -126,7 +132,7 @@ class NotNullConstraintViolationException extends ConstraintViolationException
|
||||
|
||||
|
||||
/**
|
||||
* Exception for a unique constraint violation.
|
||||
* The unique constraint check failed.
|
||||
*/
|
||||
class UniqueConstraintViolationException extends ConstraintViolationException
|
||||
{
|
||||
|
@@ -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;
|
||||
}
|
@@ -12,7 +12,7 @@ use Tester\Assert;
|
||||
require __DIR__ . '/bootstrap.php';
|
||||
|
||||
|
||||
test('', function () use ($config) {
|
||||
test('immediate connection and disconnection state', function () use ($config) {
|
||||
$conn = new Connection($config);
|
||||
Assert::true($conn->isConnected());
|
||||
|
||||
@@ -21,7 +21,7 @@ test('', function () use ($config) {
|
||||
});
|
||||
|
||||
|
||||
test('lazy', function () use ($config) {
|
||||
test('lazy connection initiated on first query', function () use ($config) {
|
||||
$conn = new Connection($config + ['lazy' => true]);
|
||||
Assert::false($conn->isConnected());
|
||||
|
||||
@@ -30,17 +30,17 @@ test('lazy', function () use ($config) {
|
||||
});
|
||||
|
||||
|
||||
test('', function () use ($config) {
|
||||
test('config retrieval and driver instance access', function () use ($config) {
|
||||
$conn = new Connection($config);
|
||||
Assert::true($conn->isConnected());
|
||||
|
||||
Assert::null($conn->getConfig('lazy'));
|
||||
Assert::same($config['driver'], $conn->getConfig('driver'));
|
||||
Assert::type(Dibi\Driver::class, $conn->getDriver());
|
||||
Assert::type(Dibi\Drivers\Connection::class, $conn->getDriver());
|
||||
});
|
||||
|
||||
|
||||
test('', function () use ($config) {
|
||||
test('idempotent disconnect calls', function () use ($config) {
|
||||
$conn = new Connection($config);
|
||||
Assert::true($conn->isConnected());
|
||||
|
||||
@@ -52,7 +52,7 @@ test('', function () use ($config) {
|
||||
});
|
||||
|
||||
|
||||
test('', function () use ($config) {
|
||||
test('reconnect after disconnection', function () use ($config) {
|
||||
$conn = new Connection($config);
|
||||
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
|
||||
|
||||
@@ -63,7 +63,7 @@ test('', function () use ($config) {
|
||||
});
|
||||
|
||||
|
||||
test('', function () use ($config) {
|
||||
test('destructor disconnects active connection', function () use ($config) {
|
||||
$conn = new Connection($config);
|
||||
Assert::true($conn->isConnected());
|
||||
|
||||
@@ -72,7 +72,7 @@ test('', function () use ($config) {
|
||||
});
|
||||
|
||||
|
||||
test('', function () use ($config) {
|
||||
test('invalid onConnect option triggers exceptions', function () use ($config) {
|
||||
Assert::exception(
|
||||
fn() => new Connection($config + ['onConnect' => '']),
|
||||
InvalidArgumentException::class,
|
||||
|
@@ -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),
|
||||
);
|
||||
|
||||
|
@@ -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(
|
||||
|
@@ -74,7 +74,7 @@ Assert::same(
|
||||
(string) $fluent,
|
||||
);
|
||||
|
||||
$fluent->orderBy(Dibi\Fluent::REMOVE);
|
||||
$fluent->orderBy(Dibi\Fluent::Remove);
|
||||
|
||||
Assert::same(
|
||||
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col WHERE col > 10 OR col < 5 AND active = 1 AND [col] IN (1, 2, 3)'),
|
||||
|
@@ -14,7 +14,7 @@ function buildPdoDriver(?int $errorMode)
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
|
||||
}
|
||||
|
||||
new Dibi\Drivers\PdoDriver(['resource' => $pdo]);
|
||||
new Dibi\Drivers\PDO\Connection(['resource' => $pdo]);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,5 +36,5 @@ Assert::exception(
|
||||
|
||||
test(
|
||||
'PDO error mode: explicitly set silent',
|
||||
fn() => buildPdoDriver(PDO::ERRMODE_SILENT)
|
||||
fn() => buildPdoDriver(PDO::ERRMODE_SILENT),
|
||||
);
|
||||
|
@@ -18,9 +18,9 @@ $tests = function ($conn) {
|
||||
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A%B')->fetchSingle());
|
||||
Assert::true($conn->query("SELECT 'AA%BB' LIKE %~like~", 'A%B')->fetchSingle());
|
||||
|
||||
Assert::same('AA\\BB', $conn->query("SELECT 'AA\\BB'")->fetchSingle());
|
||||
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A\\B')->fetchSingle());
|
||||
Assert::true($conn->query("SELECT 'AA\\BB' LIKE %~like~", 'A\\B')->fetchSingle());
|
||||
Assert::same('AA\BB', $conn->query("SELECT 'AA\\BB'")->fetchSingle());
|
||||
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A\B')->fetchSingle());
|
||||
Assert::true($conn->query("SELECT 'AA\\BB' LIKE %~like~", 'A\B')->fetchSingle());
|
||||
};
|
||||
|
||||
$conn = new Dibi\Connection($config);
|
||||
|
@@ -25,10 +25,10 @@ class MockResult extends Dibi\Result
|
||||
}
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('native text conversion preserves boolean values', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::TEXT);
|
||||
$result->setFormat(Type::TEXT, 'native');
|
||||
$result->setType('col', Type::Text);
|
||||
$result->setFormat(Type::Text, 'native');
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::same(['col' => true], $result->test(['col' => true]));
|
||||
@@ -36,9 +36,9 @@ test('', function () {
|
||||
});
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('boolean conversion from diverse representations', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::BOOL);
|
||||
$result->setType('col', Type::Bool);
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::same(['col' => true], $result->test(['col' => true]));
|
||||
@@ -58,9 +58,9 @@ test('', function () {
|
||||
});
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('text conversion of booleans and numerics', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::TEXT);
|
||||
$result->setType('col', Type::Text);
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::same(['col' => '1'], $result->test(['col' => true]));
|
||||
@@ -74,9 +74,9 @@ test('', function () {
|
||||
});
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('float conversion with various numeric formats', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::FLOAT);
|
||||
$result->setType('col', Type::Float);
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::same(['col' => 1.0], $result->test(['col' => true]));
|
||||
@@ -117,6 +117,37 @@ test('', function () {
|
||||
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10']));
|
||||
Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.1e+10']));
|
||||
|
||||
// negative
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-']));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0']));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1']));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-.0']));
|
||||
Assert::same(['col' => -0.1], $result->test(['col' => '-.1']));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0.0']));
|
||||
Assert::same(['col' => -0.1], $result->test(['col' => '-0.1']));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0.000']));
|
||||
Assert::same(['col' => -0.1], $result->test(['col' => '-0.100']));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1.0']));
|
||||
Assert::same(['col' => -1.1], $result->test(['col' => '-1.1']));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1.000']));
|
||||
Assert::same(['col' => -1.1], $result->test(['col' => '-1.100']));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => '-001.000']));
|
||||
Assert::same(['col' => -1.1], $result->test(['col' => '-001.100']));
|
||||
Assert::same(['col' => -10.0], $result->test(['col' => '-10']));
|
||||
Assert::same(['col' => -11.0], $result->test(['col' => '-11']));
|
||||
Assert::same(['col' => -10.0], $result->test(['col' => '-0010']));
|
||||
Assert::same(['col' => -11.0], $result->test(['col' => '-0011']));
|
||||
Assert::same(['col' => '-0.00000000000000000001'], $result->test(['col' => '-0.00000000000000000001']));
|
||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890']));
|
||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-012345678901234567890']));
|
||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890.000']));
|
||||
Assert::same(['col' => '-12345678901234567890.1'], $result->test(['col' => '-012345678901234567890.100']));
|
||||
|
||||
Assert::same(['col' => '-1.1e+10'], $result->test(['col' => '-1.1e+10']));
|
||||
Assert::same(['col' => '-1.1e-10'], $result->test(['col' => '-1.1e-10']));
|
||||
Assert::same(['col' => '-1.1e+10'], $result->test(['col' => '-001.1e+10']));
|
||||
Assert::notSame(['col' => '-1.1e+1'], $result->test(['col' => '-1.1e+10']));
|
||||
|
||||
setlocale(LC_ALL, 'de_DE@euro', 'de_DE', 'deu_deu');
|
||||
Assert::same(['col' => 0.0], $result->test(['col' => '']));
|
||||
Assert::same(['col' => 0.0], $result->test(['col' => '0']));
|
||||
@@ -147,26 +178,54 @@ test('', function () {
|
||||
Assert::same(['col' => 0.0], $result->test(['col' => 0.0]));
|
||||
Assert::same(['col' => 1.0], $result->test(['col' => 1]));
|
||||
Assert::same(['col' => 1.0], $result->test(['col' => 1.0]));
|
||||
|
||||
// Same but negative
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-']));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0']));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1']));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-.0']));
|
||||
Assert::same(['col' => -0.1], $result->test(['col' => '-.1']));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0.0']));
|
||||
Assert::same(['col' => -0.1], $result->test(['col' => '-0.1']));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0.000']));
|
||||
Assert::same(['col' => -0.1], $result->test(['col' => '-0.100']));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1.0']));
|
||||
Assert::same(['col' => -1.1], $result->test(['col' => '-1.1']));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1.000']));
|
||||
Assert::same(['col' => -1.1], $result->test(['col' => '-1.100']));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => '-001.000']));
|
||||
Assert::same(['col' => -1.1], $result->test(['col' => '-001.100']));
|
||||
Assert::same(['col' => -10.0], $result->test(['col' => '-10']));
|
||||
Assert::same(['col' => -11.0], $result->test(['col' => '-11']));
|
||||
Assert::same(['col' => -10.0], $result->test(['col' => '-0010']));
|
||||
Assert::same(['col' => -11.0], $result->test(['col' => '-0011']));
|
||||
Assert::same(['col' => '-0.00000000000000000001'], $result->test(['col' => '-0.00000000000000000001']));
|
||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890']));
|
||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-012345678901234567890']));
|
||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890.000']));
|
||||
Assert::same(['col' => '-12345678901234567890.1'], $result->test(['col' => '-012345678901234567890.100']));
|
||||
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => -0]));
|
||||
Assert::same(['col' => -0.0], $result->test(['col' => -0.0]));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => -1]));
|
||||
Assert::same(['col' => -1.0], $result->test(['col' => -1.0]));
|
||||
|
||||
setlocale(LC_NUMERIC, 'C');
|
||||
});
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('strict integer conversion with error on empty string', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::INTEGER);
|
||||
$result->setType('col', Type::Integer);
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::same(['col' => 1], $result->test(['col' => true]));
|
||||
Assert::same(['col' => 0], $result->test(['col' => false]));
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
|
||||
} else {
|
||||
Assert::exception(
|
||||
fn() => Assert::same(['col' => 0], $result->test(['col' => ''])),
|
||||
TypeError::class,
|
||||
);
|
||||
}
|
||||
Assert::exception(
|
||||
fn() => Assert::same(['col' => 0], $result->test(['col' => ''])),
|
||||
TypeError::class,
|
||||
);
|
||||
|
||||
Assert::same(['col' => 0], $result->test(['col' => '0']));
|
||||
Assert::same(['col' => 1], $result->test(['col' => '1']));
|
||||
@@ -185,9 +244,9 @@ test('', function () {
|
||||
});
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('dateTime conversion with object instantiation', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::DATETIME);
|
||||
$result->setType('col', Type::DateTime);
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::exception(
|
||||
@@ -204,10 +263,10 @@ test('', function () {
|
||||
});
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('dateTime conversion using custom format', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::DATETIME);
|
||||
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
|
||||
$result->setType('col', Type::DateTime);
|
||||
$result->setFormat(Type::DateTime, 'Y-m-d H:i:s');
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::exception(
|
||||
@@ -224,9 +283,9 @@ test('', function () {
|
||||
});
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('date conversion to DateTime instance', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::DATE);
|
||||
$result->setType('col', Type::Date);
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::exception(
|
||||
@@ -241,9 +300,9 @@ test('', function () {
|
||||
});
|
||||
|
||||
|
||||
test('', function () {
|
||||
test('time conversion to DateTime instance', function () {
|
||||
$result = new MockResult;
|
||||
$result->setType('col', Type::TIME);
|
||||
$result->setType('col', Type::Time);
|
||||
|
||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||
Assert::exception(
|
||||
|
@@ -12,7 +12,7 @@ $conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
||||
$res = $conn->query('SELECT * FROM [customers]');
|
||||
|
||||
// auto-converts this column to integer
|
||||
$res->setType('customer_id', Dibi\Type::DATETIME);
|
||||
$res->setType('customer_id', Dibi\Type::DateTime);
|
||||
|
||||
Assert::equal(new Dibi\Row([
|
||||
'customer_id' => new Dibi\DateTime('1970-01-01 01:00:01'),
|
||||
|
@@ -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);
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @phpVersion 8.1
|
||||
* @dataProvider ../databases.ini
|
||||
*/
|
||||
|
||||
@@ -33,7 +32,7 @@ enum PureEnum
|
||||
|
||||
Assert::equal('1', $translator->formatValue(EnumInt::One, null));
|
||||
|
||||
Assert::equal(match ($config['driver']) {
|
||||
Assert::equal(match ($config['system']) {
|
||||
'sqlsrv' => "N'one'",
|
||||
default => "'one'",
|
||||
}, $translator->formatValue(EnumString::One, null));
|
||||
|
@@ -32,7 +32,7 @@ Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "a'a", "a'"));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', "b'", "%'"));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "%'", "%'"));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a\\a', 'a\\'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a\a', 'a\\'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'b\\', '%\\'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%\\', '%\\'));
|
||||
|
||||
@@ -60,9 +60,9 @@ Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "a'a", "'a"));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', "'b", "'%"));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "'%", "'%"));
|
||||
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'a\\a', '\\a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '\\b', '\\%'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', '\\%', '\\%'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'a\a', '\a'));
|
||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '\b', '\%'));
|
||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', '\%', '\%'));
|
||||
|
||||
|
||||
// contains
|
||||
|
Reference in New Issue
Block a user