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

Compare commits

...

26 Commits
v5.0.1 ... v5.1

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,8 @@ namespace Dibi;
use JetBrains\PhpStorm\Language;
use Traversable;
use function array_key_exists, is_array, sprintf;
use const PHP_SAPI;
/**
@@ -74,10 +76,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
@@ -673,7 +675,7 @@ class Connection implements IConnection
/**
* Prevents unserialization.
*/
public function __wakeup()
public function __unserialize($_)
{
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
}
@@ -682,7 +684,7 @@ class Connection implements IConnection
/**
* Prevents serialization.
*/
public function __sleep()
public function __serialize()
{
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
}

View File

@@ -9,14 +9,16 @@ declare(strict_types=1);
namespace Dibi;
use function func_get_args, is_array, strpbrk;
/**
* Default implementation of IDataSource.
*/
class DataSource implements IDataSource
{
private Connection $connection;
private string $sql;
private readonly Connection $connection;
private readonly string $sql;
private ?Result $result = null;
private ?int $count = null;
private ?int $totalCount = null;

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
use function is_resource;
/**
@@ -26,7 +27,10 @@ use Dibi\Helpers;
*/
class FirebirdDriver implements Dibi\Driver
{
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;

View File

@@ -17,12 +17,9 @@ use Dibi;
*/
class FirebirdReflector implements Dibi\Reflector
{
private Dibi\Driver $driver;
public function __construct(Dibi\Driver $driver)
{
$this->driver = $driver;
public function __construct(
private readonly Dibi\Driver $driver,
) {
}

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
use function is_resource;
/**
@@ -18,16 +19,10 @@ use Dibi\Helpers;
*/
class FirebirdResult implements Dibi\ResultDriver
{
/** @var resource */
private $resultSet;
/**
* @param resource $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
/** @var resource */
private $resultSet,
) {
}

View File

@@ -18,12 +18,9 @@ use Dibi;
*/
class MySqlReflector implements Dibi\Reflector
{
private Dibi\Driver $driver;
public function __construct(Dibi\Driver $driver)
{
$this->driver = $driver;
public function __construct(
private readonly Dibi\Driver $driver,
) {
}

View File

@@ -10,6 +10,8 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use function in_array;
use const MYSQLI_REPORT_OFF, MYSQLI_STORE_RESULT, MYSQLI_USE_RESULT, PREG_SET_ORDER;
/**
@@ -32,9 +34,18 @@ use Dibi;
*/
class MySqliDriver implements Dibi\Driver
{
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;

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use const MYSQLI_TYPE_LONG, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_TIME, MYSQLI_TYPE_TINY;
/**
@@ -17,14 +18,10 @@ use Dibi;
*/
class MySqliResult implements Dibi\ResultDriver
{
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 +100,7 @@ class MySqliResult implements Dibi\ResultDriver
'table' => $row['orgtable'],
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'nativetype' => $types[$row['type']] ?? $row['type'],
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TIME_INTERVAL : null,
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TimeInterval : null,
'vendor' => $row,
];
}

View File

@@ -17,12 +17,9 @@ use Dibi;
*/
class NoDataResult implements Dibi\ResultDriver
{
private int $rows;
public function __construct(int $rows)
{
$this->rows = $rows;
public function __construct(
private readonly int $rows,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use function is_resource;
/**

View File

@@ -17,12 +17,9 @@ use Dibi;
*/
class OdbcReflector implements Dibi\Reflector
{
private Dibi\Driver $driver;
public function __construct(Dibi\Driver $driver)
{
$this->driver = $driver;
public function __construct(
private readonly Dibi\Driver $driver,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use function is_resource;
/**
@@ -17,17 +18,13 @@ use Dibi;
*/
class OdbcResult implements Dibi\ResultDriver
{
/** @var resource */
private $resultSet;
private int $row = 0;
/**
* @param resource $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
/** @var resource */
private $resultSet,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use function in_array, is_resource;
/**

View File

@@ -17,12 +17,9 @@ use Dibi;
*/
class OracleReflector implements Dibi\Reflector
{
private Dibi\Driver $driver;
public function __construct(Dibi\Driver $driver)
{
$this->driver = $driver;
public function __construct(
private readonly Dibi\Driver $driver,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use function is_resource;
/**
@@ -17,16 +18,10 @@ use Dibi;
*/
class OracleResult implements Dibi\ResultDriver
{
/** @var resource */
private $resultSet;
/**
* @param resource $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
/** @var resource */
private $resultSet,
) {
}
@@ -80,7 +75,7 @@ class OracleResult implements Dibi\ResultDriver
'name' => oci_field_name($this->resultSet, $i),
'table' => null,
'fullname' => oci_field_name($this->resultSet, $i),
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null,
'type' => $type === 'LONG' ? Dibi\Type::Text : null,
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
];
}

View File

@@ -12,6 +12,7 @@ namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
use PDO;
use function sprintf;
/**
@@ -23,14 +24,12 @@ use PDO;
* - 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 */
@@ -65,7 +64,6 @@ class PdoDriver implements Dibi\Driver
}
$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
}
@@ -176,27 +174,14 @@ class PdoDriver implements Dibi\Driver
*/
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;
}
return match ($this->driverName) {
'mysql' => new MySqlReflector($this),
'oci' => new OracleReflector($this),
'pgsql' => new PostgreReflector($this),
'sqlite' => new SqliteReflector($this),
'mssql', 'dblib', 'sqlsrv' => new SqlsrvReflector($this),
default => throw new Dibi\NotSupportedException,
};
}
@@ -217,44 +202,34 @@ class PdoDriver implements Dibi\Driver
*/
public function escapeText(string $value): string
{
return $this->driverName === 'odbc'
? "'" . str_replace("'", "''", $value) . "'"
: $this->connection->quote($value, PDO::PARAM_STR);
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 $this->driverName === 'odbc'
? "'" . str_replace("'", "''", $value) . "'"
: $this->connection->quote($value, PDO::PARAM_LOB);
return match ($this->driverName) {
'odbc' => "'" . str_replace("'", "''", $value) . "'",
'sqlsrv' => '0x' . bin2hex($value),
default => $this->connection->quote($value, PDO::PARAM_LOB),
};
}
public function escapeIdentifier(string $value): string
{
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;
}
return match ($this->driverName) {
'mysql' => '`' . str_replace('`', '``', $value) . '`',
'oci', 'pgsql' => '"' . str_replace('"', '""', $value) . '"',
'sqlite' => '[' . strtr($value, '[]', ' ') . ']',
'odbc', 'mssql' => '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']',
'dblib', 'sqlsrv' => '[' . str_replace(']', ']]', $value) . ']',
default => $value,
};
}
@@ -276,16 +251,11 @@ class PdoDriver implements Dibi\Driver
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'");
}
return match ($this->driverName) {
'odbc' => $value->format('#m/d/Y H:i:s.u#'),
'mssql', 'dblib', 'sqlsrv' => 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')',
default => $value->format("'Y-m-d H:i:s.u'"),
};
}
@@ -387,17 +357,15 @@ class PdoDriver implements Dibi\Driver
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;
// 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 omitted
break;
case 'odbc':
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');

View File

@@ -19,14 +19,10 @@ use PDO;
*/
class PdoResult implements Dibi\ResultDriver
{
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 +86,7 @@ class PdoResult implements Dibi\ResultDriver
'name' => $row['name'],
'table' => $row['table'],
'nativetype' => $row['native_type'],
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TIME_INTERVAL : null,
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TimeInterval : null,
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'vendor' => $row,
];

View File

@@ -12,6 +12,7 @@ namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
use PgSql;
use function in_array, is_array, is_resource, strlen;
/**
@@ -23,13 +24,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
{
/** @var resource|PgSql\Connection */
private $connection;
private PgSql\Connection $connection;
private ?int $affectedRows;
@@ -72,7 +72,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.');
}
@@ -118,7 +118,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,13 +222,10 @@ 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;
}
@@ -237,15 +234,14 @@ class PostgreDriver implements Dibi\Driver
*/
public function getReflector(): Dibi\Reflector
{
return new PostgreReflector($this, pg_parameter_status($this->connection, 'server_version'));
return new PostgreReflector($this);
}
/**
* Result set driver factory.
* @param resource $resource
*/
public function createResultDriver($resource): PostgreResult
public function createResultDriver(PgSql\Result $resource): PostgreResult
{
return new PostgreResult($resource);
}

View File

@@ -17,14 +17,9 @@ use Dibi;
*/
class PostgreReflector implements Dibi\Reflector
{
private Dibi\Driver $driver;
private string $version;
public function __construct(Dibi\Driver $driver, string $version)
{
$this->driver = $driver;
$this->version = $version;
public function __construct(
private readonly Dibi\Driver $driver,
) {
}
@@ -43,18 +38,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 = [];

View File

@@ -19,16 +19,9 @@ use PgSql;
*/
class PostgreResult implements Dibi\ResultDriver
{
/** @var resource|PgSql\Result */
private $resultSet;
/**
* @param resource|PgSql\Result $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
private readonly PgSql\Result $resultSet,
) {
}
@@ -94,13 +87,10 @@ class PostgreResult implements Dibi\ResultDriver
/**
* Returns the result set resource.
* @return resource|PgSql\Result|null
*/
public function getResultResource(): mixed
public function getResultResource(): PgSql\Result
{
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
? $this->resultSet
: null;
return $this->resultSet;
}

View File

@@ -56,11 +56,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');
}

View File

@@ -17,12 +17,9 @@ use Dibi;
*/
class SqliteReflector implements Dibi\Reflector
{
private Dibi\Driver $driver;
public function __construct(Dibi\Driver $driver)
{
$this->driver = $driver;
public function __construct(
private readonly Dibi\Driver $driver,
) {
}

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
use const SQLITE3_ASSOC, SQLITE3_BLOB, SQLITE3_FLOAT, SQLITE3_INTEGER, SQLITE3_NULL, SQLITE3_NUM, SQLITE3_TEXT;
/**
@@ -18,12 +19,9 @@ use Dibi\Helpers;
*/
class SqliteResult implements Dibi\ResultDriver
{
private \SQLite3Result $resultSet;
public function __construct(\SQLite3Result $resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
private readonly \SQLite3Result $resultSet,
) {
}

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi;
use Dibi\Helpers;
use function is_resource, sprintf;
/**
@@ -30,7 +31,6 @@ class SqlsrvDriver implements Dibi\Driver
/** @var resource */
private $connection;
private ?int $affectedRows;
private string $version = '';
/** @throws Dibi\NotSupportedException */
@@ -68,8 +68,6 @@ class SqlsrvDriver implements Dibi\Driver
sqlsrv_configure('WarningsReturnAsErrors', 1);
}
$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
}
@@ -256,13 +254,6 @@ class SqlsrvDriver implements Dibi\Driver
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);

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use function sprintf;
/**
@@ -17,12 +18,9 @@ use Dibi;
*/
class SqlsrvReflector implements Dibi\Reflector
{
private Dibi\Driver $driver;
public function __construct(Dibi\Driver $driver)
{
$this->driver = $driver;
public function __construct(
private readonly Dibi\Driver $driver,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Drivers;
use Dibi;
use function is_resource;
/**
@@ -17,16 +18,10 @@ use Dibi;
*/
class SqlsrvResult implements Dibi\ResultDriver
{
/** @var resource */
private $resultSet;
/**
* @param resource $resultSet
*/
public function __construct($resultSet)
{
$this->resultSet = $resultSet;
public function __construct(
/** @var resource */
private $resultSet,
) {
}

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,9 @@ declare(strict_types=1);
namespace Dibi;
use function array_map, array_unique, explode, fclose, fgets, fopen, fstat, getenv, htmlspecialchars, is_float, is_int, is_string, levenshtein, max, mb_strlen, ob_end_flush, ob_get_clean, ob_start, preg_match, preg_replace, preg_replace_callback, rtrim, set_time_limit, str_ends_with, str_repeat, str_starts_with, strlen, strtoupper, substr, trim, wordwrap;
use const PHP_SAPI;
class Helpers
{
@@ -159,12 +162,12 @@ class Helpers
public static function escape(Driver $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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Reflection;
use Dibi;
use function array_values, strtolower;
/**
@@ -20,8 +21,6 @@ use Dibi;
*/
class Result
{
private Dibi\ResultDriver $driver;
/** @var Column[]|null */
private ?array $columns;
@@ -29,9 +28,9 @@ class Result
private ?array $names;
public function __construct(Dibi\ResultDriver $driver)
{
$this->driver = $driver;
public function __construct(
private readonly Dibi\ResultDriver $driver,
) {
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace Dibi\Reflection;
use Dibi;
use function array_values, strtolower;
/**
@@ -25,7 +26,7 @@ use Dibi;
*/
class Table
{
private Dibi\Reflector $reflector;
private readonly Dibi\Reflector $reflector;
private string $name;
private bool $view;

View File

@@ -9,6 +9,9 @@ declare(strict_types=1);
namespace Dibi;
use function array_keys, array_pop, count, explode, is_float, is_string, json_decode, ltrim, preg_match, preg_split, property_exists, reset, rtrim, str_contains, str_replace, str_starts_with, strpos;
use const PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
/**
* Query result.
@@ -457,16 +460,22 @@ class Result implements IDataSource
if ($type === null || $format === 'native') {
$row[$key] = $value;
} elseif ($type === Type::TEXT) {
} elseif ($type === Type::Text) {
$row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) {
} elseif ($type === Type::Integer) {
$row[$key] = is_float($tmp = $value * 1)
? (is_string($value) ? $value : (int) $value)
: $tmp;
} elseif ($type === Type::FLOAT) {
$value = ltrim((string) $value, '0');
} elseif ($type === Type::Float) {
if (!is_string($value)) {
$row[$key] = (float) $value;
continue;
}
$negative = ($value[0] ?? null) === '-';
$value = ltrim($value, '0-');
$p = strpos($value, '.');
$e = strpos($value, 'e');
if ($p !== false && $e === false) {
@@ -479,27 +488,31 @@ class Result implements IDataSource
$value = '0' . $value;
}
if ($negative) {
$value = '-' . $value;
}
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float
: $value;
} elseif ($type === Type::BOOL) {
} elseif ($type === Type::Bool) {
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
} elseif ($type === Type::DateTime || $type === Type::Date || $type === Type::Time) {
if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
$value = new DateTime($value);
$row[$key] = $format ? $value->format($format) : $value;
} else {
$row[$key] = null;
}
} elseif ($type === Type::TIME_INTERVAL) {
} elseif ($type === Type::TimeInterval) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$value->invert = (int) (bool) $m[1];
$row[$key] = $format ? $value->format($format) : $value;
} elseif ($type === Type::BINARY) {
} elseif ($type === Type::Binary) {
$row[$key] = is_string($value)
? $this->getResultDriver()->unescapeBinary($value)
: $value;

View File

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

View File

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

View File

@@ -9,14 +9,16 @@ 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 Driver $driver;
private int $cursor = 0;
private array $args;
@@ -34,7 +36,7 @@ final class Translator
{
$this->connection = $connection;
$this->driver = $connection->getDriver();
$this->identifiers = new HashMap([$this, 'delimite']);
$this->identifiers = new HashMap($this->delimite(...));
}
@@ -88,7 +90,7 @@ final class Translator
(\?) ## 11) placeholder
)/xs
XX,
[$this, 'cb'],
$this->cb(...),
substr($arg, $toSkip),
);
if (preg_last_error()) {
@@ -219,7 +221,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));
}
@@ -434,7 +436,7 @@ final class Translator
:(\S*?:)([a-zA-Z0-9._]?)
)/sx
XX,
[$this, 'cb'],
$this->cb(...),
substr($value, $toSkip),
);
if (preg_last_error()) {

View File

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

View File

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

View File

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

View File

@@ -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,7 +30,7 @@ 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());
@@ -40,7 +40,7 @@ test('', function () use ($config) {
});
test('', function () use ($config) {
test('idempotent disconnect calls', function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
@@ -52,7 +52,7 @@ test('', function () use ($config) {
});
test('', function () use ($config) {
test('reconnect after disconnection', function () use ($config) {
$conn = new Connection($config);
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
@@ -63,7 +63,7 @@ test('', function () use ($config) {
});
test('', function () use ($config) {
test('destructor disconnects active connection', function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
@@ -72,7 +72,7 @@ test('', function () use ($config) {
});
test('', function () use ($config) {
test('invalid onConnect option triggers exceptions', function () use ($config) {
Assert::exception(
fn() => new Connection($config + ['onConnect' => '']),
InvalidArgumentException::class,

View File

@@ -57,28 +57,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 +86,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 +104,12 @@ $fluent->removeClause('limit');
$fluent->removeClause('offset');
$fluent->fetch();
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
dibi::$sql,
);
Assert::same(

View File

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

View File

@@ -36,5 +36,5 @@ Assert::exception(
test(
'PDO error mode: explicitly set silent',
fn() => buildPdoDriver(PDO::ERRMODE_SILENT)
fn() => buildPdoDriver(PDO::ERRMODE_SILENT),
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "a'a", "a'"));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', "b'", "%'"));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "%'", "%'"));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a\\a', 'a\\'));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a\a', 'a\\'));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'b\\', '%\\'));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%\\', '%\\'));
@@ -60,9 +60,9 @@ Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "a'a", "'a"));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', "'b", "'%"));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "'%", "'%"));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'a\\a', '\\a'));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '\\b', '\\%'));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', '\\%', '\\%'));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'a\a', '\a'));
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '\b', '\%'));
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', '\%', '\%'));
// contains