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

Compare commits

...

20 Commits

Author SHA1 Message Date
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
David Grudl
86a71dde28 Released version 5.0.1 2023-11-25 14:08:47 +01:00
David Grudl
bb1f7d4b93 SqliteDriver: disables exceptions (is enabled since PHP 8.3) 2023-11-25 14:08:47 +01:00
David Grudl
680026747e added DibiExtension3 2023-11-25 14:08:47 +01:00
Jan Rössler
7ca47508cb PostgreReflector: detect IDENTITY columns as autoincrement 2023-11-05 20:38:26 +01:00
Jan Rössler
beba7b3592 PostgreReflector: fix autoincrement column detection 2023-11-05 20:38:26 +01:00
David Grudl
6cc7ce8e44 used PhpStorm Language attribute 2023-11-05 20:38:25 +01:00
Petr Hubík
92b8e6077e fix: PDO::errorInfo() can return NULL as a code, but Exception does not accept NULL code 2023-09-29 15:55:05 +02:00
David Grudl
e45638eab4 cs 2023-09-29 15:55:05 +02:00
Marek Bartoš
8564217bc1 Fluent: execute() has conditional return type 2023-09-05 12:40:51 +02:00
32 changed files with 396 additions and 211 deletions

View File

@@ -7,7 +7,7 @@ jobs:
name: Nette Code Checker name: Nette Code Checker
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.0
@@ -21,7 +21,7 @@ jobs:
name: Nette Coding Standard name: Nette Coding Standard
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.0

View File

@@ -10,7 +10,7 @@ jobs:
name: PHPStan name: PHPStan
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.0

View File

@@ -3,7 +3,7 @@ name: Tests
on: [push, pull_request] on: [push, pull_request]
env: 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" php-tools: "composer:v2, pecl"
jobs: jobs:
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
php: ['8.0', '8.1', '8.2', '8.3'] php: ['8.0', '8.1', '8.2', '8.3', '8.4']
fail-fast: false fail-fast: false
@@ -43,7 +43,6 @@ jobs:
--health-retries=5 --health-retries=5
-e MYSQL_ROOT_PASSWORD=root -e MYSQL_ROOT_PASSWORD=root
-e MYSQL_DATABASE=dibi_test -e MYSQL_DATABASE=dibi_test
--entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password"
postgres96: postgres96:
image: postgres:9.6 image: postgres:9.6
@@ -83,13 +82,13 @@ jobs:
- 1433:1433 - 1433:1433
options: >- options: >-
--name=mssql --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-interval 10s
--health-timeout 5s --health-timeout 5s
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
@@ -101,7 +100,7 @@ jobs:
run: cp ./tests/databases.github.ini ./tests/databases.ini run: cp ./tests/databases.github.ini ./tests/databases.ini
- name: Create MS SQL Database - 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: composer install --no-progress --prefer-dist
- run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src

View File

@@ -11,13 +11,14 @@
} }
], ],
"require": { "require": {
"php": "8.0 - 8.3" "php": "8.0 - 8.4"
}, },
"require-dev": { "require-dev": {
"tracy/tracy": "^2.9", "tracy/tracy": "^2.9",
"nette/tester": "^2.5", "nette/tester": "^2.5",
"nette/di": "^3.1", "nette/di": "^3.1",
"phpstan/phpstan": "^1.0" "phpstan/phpstan": "^1.0",
"jetbrains/phpstorm-attributes": "^1.0"
}, },
"replace": { "replace": {
"dg/dibi": "*" "dg/dibi": "*"
@@ -25,6 +26,7 @@
"autoload": { "autoload": {
"classmap": ["src/"] "classmap": ["src/"]
}, },
"minimum-stability": "dev",
"scripts": { "scripts": {
"phpstan": "phpstan analyse", "phpstan": "phpstan analyse",
"tester": "tester tests -s" "tester": "tester tests -s"

View File

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

View File

@@ -34,7 +34,7 @@ Install Dibi via Composer:
composer require dibi/dibi composer require dibi/dibi
``` ```
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.3. The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.4.
Usage 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. 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 ```php
$result->setType('id', Dibi\Type::INTEGER); // id will be integer $result->setType('id', Dibi\Type::Integer); // id will be integer
$row = $result->fetch(); $row = $result->fetch();
is_int($row->id) // true is_int($row->id) // true
@@ -639,7 +639,7 @@ In the configuration file, we will register the DI extensions and add the `dibi`
```neon ```neon
extensions: extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22 dibi: Dibi\Bridges\Nette\DibiExtension3
dibi: dibi:
host: localhost host: localhost

View File

@@ -0,0 +1,96 @@
<?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\Bridges\Nette;
use Dibi;
use Nette;
use Nette\Schema\Expect;
use Tracy;
/**
* Dibi extension for Nette Framework 3. Creates 'connection' & 'panel' services.
*/
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 getConfigSchema(): Nette\Schema\Schema
{
return Expect::structure([
'autowired' => Expect::bool(true),
'flags' => Expect::anyOf(Expect::arrayOf('string'), Expect::type('dynamic')),
'profiler' => Expect::bool(),
'explain' => Expect::bool(true),
'filter' => Expect::bool(true),
'driver' => Expect::string()->dynamic(),
'name' => Expect::string()->dynamic(),
'lazy' => Expect::bool(false)->dynamic(),
'onConnect' => Expect::array()->dynamic(),
'substitutes' => Expect::arrayOf('string')->dynamic(),
'result' => Expect::structure([
'normalize' => Expect::bool(true),
'formatDateTime' => Expect::string(),
'formatTimeInterval' => Expect::string(),
'formatJson' => Expect::string(),
])->castTo('array'),
])->otherItems(Expect::type('mixed'))
->castTo('array');
}
public function loadConfiguration()
{
$container = $this->getContainerBuilder();
$config = $this->getConfig();
$this->debugMode ??= $container->parameters['debugMode'];
$this->cliMode ??= $container->parameters['consoleMode'];
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode && !$this->cliMode);
unset($config['profiler']);
if (is_array($config['flags'])) {
$flags = 0;
foreach ((array) $config['flags'] as $flag) {
$flags |= constant($flag);
}
$config['flags'] = $flags;
}
$connection = $container->addDefinition($this->prefix('connection'))
->setCreator(Dibi\Connection::class, [$config])
->setAutowired($config['autowired']);
if (class_exists(Tracy\Debugger::class)) {
$connection->addSetup(
[new Nette\DI\Definitions\Statement('Tracy\Debugger::getBlueScreen'), 'addPanel'],
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']],
);
}
if ($useProfiler) {
$panel = $container->addDefinition($this->prefix('panel'))
->setCreator(Dibi\Bridges\Tracy\Panel::class, [
$config['explain'],
$config['filter'] ? Dibi\Event::QUERY : Dibi\Event::ALL,
]);
$connection->addSetup([$panel, 'register'], [$connection]);
}
}
}

View File

@@ -1,8 +1,8 @@
# This will create service named 'dibi.connection'. # This will create service named 'dibi.connection'.
# Requires Nette Framework 2.2 or later # Requires Nette Framework 3 or later
extensions: extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22 dibi: Dibi\Bridges\Nette\DibiExtension3
dibi: dibi:
host: localhost host: localhost

View File

@@ -62,7 +62,7 @@ class Panel implements Tracy\IBarPanel
if ($e instanceof Dibi\Exception && $e->getSql()) { if ($e instanceof Dibi\Exception && $e->getSql()) {
return [ return [
'tab' => 'SQL', 'tab' => 'SQL',
'panel' => Helpers::dump($e->getSql(), true), 'panel' => Helpers::dump($e->getSql(), return: true),
]; ];
} }
@@ -118,7 +118,7 @@ class Panel implements Tracy\IBarPanel
? $this->explain ? $this->explain
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN'); : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
try { try {
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true); $explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), return: true);
} catch (Dibi\Exception $e) { } catch (Dibi\Exception $e) {
} }
@@ -132,7 +132,7 @@ class Panel implements Tracy\IBarPanel
$s .= "<br /><a href='#tracy-debug-DibiProfiler-row-$counter' class='tracy-toggle tracy-collapsed' rel='#tracy-debug-DibiProfiler-row-$counter'>explain</a>"; $s .= "<br /><a href='#tracy-debug-DibiProfiler-row-$counter' class='tracy-toggle tracy-collapsed' rel='#tracy-debug-DibiProfiler-row-$counter'>explain</a>";
} }
$s .= '</td><td class="tracy-DibiProfiler-sql">' . Helpers::dump(strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql, true); $s .= '</td><td class="tracy-DibiProfiler-sql">' . Helpers::dump(strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql, return: true);
if ($explain) { if ($explain) {
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>"; $s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
} }
@@ -151,8 +151,8 @@ class Panel implements Tracy\IBarPanel
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important } #tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style> #tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
<h1>Queries:' . "\u{a0}" . count($this->events) <h1>Queries:' . "\u{a0}" . count($this->events)
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', ' . ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms")
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1> . ($singleConnection === null ? '' : ', ' . htmlspecialchars($this->getConnectionName($singleConnection))) . '</h1>
<div class="tracy-inner tracy-DibiProfiler"> <div class="tracy-inner tracy-DibiProfiler">
<table class="tracy-sortable"> <table class="tracy-sortable">
<tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr> <tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>

View File

@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Dibi; namespace Dibi;
use JetBrains\PhpStorm\Language;
use Traversable; use Traversable;
@@ -73,10 +74,10 @@ class Connection implements IConnection
$this->config = $config; $this->config = $config;
$this->formats = [ $this->formats = [
Type::DATE => $this->config['result']['formatDate'], Type::Date => $this->config['result']['formatDate'],
Type::DATETIME => $this->config['result']['formatDateTime'], Type::DateTime => $this->config['result']['formatDateTime'],
Type::JSON => $this->config['result']['formatJson'] ?? 'array', Type::JSON => $this->config['result']['formatJson'] ?? 'array',
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null, Type::TimeInterval => $this->config['result']['formatTimeInterval'] ?? null,
]; ];
// profiler // profiler
@@ -209,7 +210,7 @@ class Connection implements IConnection
* Generates (translates) and executes SQL query. * Generates (translates) and executes SQL query.
* @throws Exception * @throws Exception
*/ */
final public function query(mixed ...$args): Result final public function query(#[Language('GenericSQL')] mixed ...$args): Result
{ {
return $this->nativeQuery($this->translate(...$args)); return $this->nativeQuery($this->translate(...$args));
} }
@@ -219,7 +220,7 @@ class Connection implements IConnection
* Generates SQL query. * Generates SQL query.
* @throws Exception * @throws Exception
*/ */
final public function translate(mixed ...$args): string final public function translate(#[Language('GenericSQL')] mixed ...$args): string
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
@@ -232,7 +233,7 @@ class Connection implements IConnection
/** /**
* Generates and prints SQL query. * Generates and prints SQL query.
*/ */
final public function test(mixed ...$args): bool final public function test(#[Language('GenericSQL')] mixed ...$args): bool
{ {
try { try {
Helpers::dump($this->translate(...$args)); Helpers::dump($this->translate(...$args));
@@ -254,7 +255,7 @@ class Connection implements IConnection
* Generates (translates) and returns SQL query as DataSource. * Generates (translates) and returns SQL query as DataSource.
* @throws Exception * @throws Exception
*/ */
final public function dataSource(mixed ...$args): DataSource final public function dataSource(#[Language('GenericSQL')] mixed ...$args): DataSource
{ {
return new DataSource($this->translate(...$args), $this); return new DataSource($this->translate(...$args), $this);
} }
@@ -264,7 +265,7 @@ class Connection implements IConnection
* Executes the SQL query. * Executes the SQL query.
* @throws Exception * @throws Exception
*/ */
final public function nativeQuery(string $sql): Result final public function nativeQuery(#[Language('SQL')] string $sql): Result
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
@@ -593,7 +594,7 @@ class Connection implements IConnection
* Executes SQL query and fetch result - shortcut for query() & fetch(). * Executes SQL query and fetch result - shortcut for query() & fetch().
* @throws Exception * @throws Exception
*/ */
public function fetch(mixed ...$args): ?Row public function fetch(#[Language('GenericSQL')] mixed ...$args): ?Row
{ {
return $this->query($args)->fetch(); return $this->query($args)->fetch();
} }
@@ -604,7 +605,7 @@ class Connection implements IConnection
* @return Row[]|array[] * @return Row[]|array[]
* @throws Exception * @throws Exception
*/ */
public function fetchAll(mixed ...$args): array public function fetchAll(#[Language('GenericSQL')] mixed ...$args): array
{ {
return $this->query($args)->fetchAll(); return $this->query($args)->fetchAll();
} }
@@ -614,7 +615,7 @@ class Connection implements IConnection
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle(). * Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
* @throws Exception * @throws Exception
*/ */
public function fetchSingle(mixed ...$args): mixed public function fetchSingle(#[Language('GenericSQL')] mixed ...$args): mixed
{ {
return $this->query($args)->fetchSingle(); return $this->query($args)->fetchSingle();
} }
@@ -624,7 +625,7 @@ class Connection implements IConnection
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs(). * Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
* @throws Exception * @throws Exception
*/ */
public function fetchPairs(mixed ...$args): array public function fetchPairs(#[Language('GenericSQL')] mixed ...$args): array
{ {
return $this->query($args)->fetchPairs(); return $this->query($args)->fetchPairs();
} }

View File

@@ -26,7 +26,10 @@ use Dibi\Helpers;
*/ */
class FirebirdDriver implements Dibi\Driver 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 */ /** @var resource */
private $connection; private $connection;

View File

@@ -32,9 +32,18 @@ use Dibi;
*/ */
class MySqliDriver implements Dibi\Driver class MySqliDriver implements Dibi\Driver
{ {
public const ERROR_ACCESS_DENIED = 1045; public const ErrorAccessDenied = 1045;
public const ERROR_DUPLICATE_ENTRY = 1062; public const ErrorDuplicateEntry = 1062;
public const ERROR_DATA_TRUNCATED = 1265; 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 \mysqli $connection;
private bool $buffered = false; private bool $buffered = false;

View File

@@ -103,7 +103,7 @@ class MySqliResult implements Dibi\ResultDriver
'table' => $row['orgtable'], 'table' => $row['orgtable'],
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'], 'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'nativetype' => $types[$row['type']] ?? $row['type'], '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, 'vendor' => $row,
]; ];
} }

View File

@@ -119,7 +119,7 @@ class OdbcDriver implements Dibi\Driver
*/ */
public function begin(?string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
if (!odbc_autocommit($this->connection, false)) { if (!odbc_autocommit($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
} }

View File

@@ -80,7 +80,7 @@ class OracleResult implements Dibi\ResultDriver
'name' => oci_field_name($this->resultSet, $i), 'name' => oci_field_name($this->resultSet, $i),
'table' => null, 'table' => null,
'fullname' => oci_field_name($this->resultSet, $i), '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, 'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
]; ];
} }

View File

@@ -93,23 +93,15 @@ class PdoDriver implements Dibi\Driver
$this->affectedRows = null; $this->affectedRows = null;
[$sqlState, $code, $message] = $this->connection->errorInfo(); [$sqlState, $code, $message] = $this->connection->errorInfo();
$code ??= 0;
$message = "SQLSTATE[$sqlState]: $message"; $message = "SQLSTATE[$sqlState]: $message";
switch ($this->driverName) { throw match ($this->driverName) {
case 'mysql': 'mysql' => MySqliDriver::createException($message, $code, $sql),
throw MySqliDriver::createException($message, $code, $sql); 'oci' => OracleDriver::createException($message, $code, $sql),
'pgsql' => PostgreDriver::createException($message, $sqlState, $sql),
case 'oci': 'sqlite' => SqliteDriver::createException($message, $code, $sql),
throw OracleDriver::createException($message, $code, $sql); default => new Dibi\DriverException($message, $code, $sql),
};
case 'pgsql':
throw PostgreDriver::createException($message, $sqlState, $sql);
case 'sqlite':
throw SqliteDriver::createException($message, $code, $sql);
default:
throw new Dibi\DriverException($message, $code, $sql);
}
} }
@@ -139,7 +131,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->beginTransaction()) { if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -152,7 +144,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->commit()) { if (!$this->connection->commit()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -165,7 +157,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->rollBack()) { if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -184,27 +176,14 @@ class PdoDriver implements Dibi\Driver
*/ */
public function getReflector(): Dibi\Reflector public function getReflector(): Dibi\Reflector
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'mysql': 'mysql' => new MySqlReflector($this),
return new MySqlReflector($this); 'oci' => new OracleReflector($this),
'pgsql' => new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)),
case 'oci': 'sqlite' => new SqliteReflector($this),
return new OracleReflector($this); 'mssql', 'dblib', 'sqlsrv' => new SqlsrvReflector($this),
default => throw new Dibi\NotSupportedException,
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;
}
} }
@@ -225,44 +204,34 @@ class PdoDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
return $this->driverName === 'odbc' return match ($this->driverName) {
? "'" . str_replace("'", "''", $value) . "'" 'odbc' => "'" . str_replace("'", "''", $value) . "'",
: $this->connection->quote($value, PDO::PARAM_STR); 'sqlsrv' => "N'" . str_replace("'", "''", $value) . "'",
default => $this->connection->quote($value, PDO::PARAM_STR),
};
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
return $this->driverName === 'odbc' return match ($this->driverName) {
? "'" . str_replace("'", "''", $value) . "'" 'odbc' => "'" . str_replace("'", "''", $value) . "'",
: $this->connection->quote($value, PDO::PARAM_LOB); 'sqlsrv' => '0x' . bin2hex($value),
default => $this->connection->quote($value, PDO::PARAM_LOB),
};
} }
public function escapeIdentifier(string $value): string public function escapeIdentifier(string $value): string
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'mysql': 'mysql' => '`' . str_replace('`', '``', $value) . '`',
return '`' . str_replace('`', '``', $value) . '`'; 'oci', 'pgsql' => '"' . str_replace('"', '""', $value) . '"',
'sqlite' => '[' . strtr($value, '[]', ' ') . ']',
case 'oci': 'odbc', 'mssql' => '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']',
case 'pgsql': 'dblib', 'sqlsrv' => '[' . str_replace(']', ']]', $value) . ']',
return '"' . str_replace('"', '""', $value) . '"'; default => $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;
}
} }
@@ -284,16 +253,11 @@ class PdoDriver implements Dibi\Driver
public function escapeDateTime(\DateTimeInterface $value): string public function escapeDateTime(\DateTimeInterface $value): string
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'odbc': 'odbc' => $value->format('#m/d/Y H:i:s.u#'),
return $value->format('#m/d/Y H:i:s.u#'); 'mssql', 'dblib', 'sqlsrv' => 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')',
case 'mssql': default => $value->format("'Y-m-d H:i:s.u'"),
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'");
}
} }

View File

@@ -90,7 +90,7 @@ class PdoResult implements Dibi\ResultDriver
'name' => $row['name'], 'name' => $row['name'],
'table' => $row['table'], 'table' => $row['table'],
'nativetype' => $row['native_type'], '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'], 'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'vendor' => $row, 'vendor' => $row,
]; ];

View File

@@ -72,13 +72,6 @@ class PostgreReflector implements Dibi\Reflector
public function getColumns(string $table): array public function getColumns(string $table): array
{ {
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table)); $_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$res = $this->driver->query("
SELECT indkey
FROM pg_class
LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid AND pg_index.indisprimary
WHERE pg_class.oid = $_table::regclass
");
$primary = (int) $res->fetch(true)['indkey'];
$res = $this->driver->query(" $res = $this->driver->query("
SELECT * SELECT *
@@ -98,7 +91,8 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length, a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable, NOT a.attnotnull AS is_nullable,
a.attnum AS ordinal_position, a.attnum AS ordinal_position,
pg_get_expr(adef.adbin, adef.adrelid) AS column_default pg_get_expr(adef.adbin, adef.adrelid) AS column_default,
CASE WHEN a.attidentity IN ('a', 'd') THEN 'YES' ELSE 'NO' END AS is_identity
FROM FROM
pg_attribute a pg_attribute a
JOIN pg_type ON a.atttypid = pg_type.oid JOIN pg_type ON a.atttypid = pg_type.oid
@@ -123,7 +117,7 @@ class PostgreReflector implements Dibi\Reflector
'size' => $size > 0 ? $size : null, 'size' => $size > 0 ? $size : null,
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true, 'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
'default' => $row['column_default'], 'default' => $row['column_default'],
'autoincrement' => (int) $row['ordinal_position'] === $primary && str_starts_with($row['column_default'] ?? '', 'nextval'), 'autoincrement' => $row['is_identity'] === 'YES' || str_starts_with($row['column_default'] ?? '', 'nextval('),
'vendor' => $row, 'vendor' => $row,
]; ];
} }

View File

@@ -45,7 +45,13 @@ namespace Dibi;
*/ */
class Fluent implements IDataSource 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 = [ public static array $masks = [
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY', 'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
@@ -140,7 +146,7 @@ class Fluent implements IDataSource
$this->cursor = &$this->clauses[$clause]; $this->cursor = &$this->clauses[$clause];
// TODO: really delete? // TODO: really delete?
if ($args === [self::REMOVE]) { if ($args === [self::Remove]) {
$this->cursor = null; $this->cursor = null;
return $this; return $this;
} }
@@ -156,7 +162,7 @@ class Fluent implements IDataSource
} }
} else { } else {
// append to currect flow // append to currect flow
if ($args === [self::REMOVE]) { if ($args === [self::Remove]) {
return $this; return $this;
} }
@@ -279,19 +285,17 @@ class Fluent implements IDataSource
/** /**
* Generates and executes SQL query. * Generates and executes SQL query.
* Returns result set or number of affected rows * Returns result set or number of affected rows
* @return ($return is self::Identifier|self::AffectedRows ? int : Result)
* @throws Exception * @throws Exception
*/ */
public function execute(?string $return = null): Result|int|null public function execute(?string $return = null): Result|int|null
{ {
$res = $this->query($this->_export()); $res = $this->query($this->_export());
switch ($return) { return match ($return) {
case \dibi::IDENTIFIER: self::Identifier => $this->connection->getInsertId(),
return $this->connection->getInsertId(); self::AffectedRows => $this->connection->getAffectedRows(),
case \dibi::AFFECTED_ROWS: default => $res,
return $this->connection->getAffectedRows(); };
default:
return $res;
}
} }

View File

@@ -46,7 +46,7 @@ abstract class HashMapBase
*/ */
final class HashMap extends HashMapBase final class HashMap extends HashMapBase
{ {
public function __set(string $nm, $val) public function __set(string $nm, mixed $val): void
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
@@ -56,7 +56,7 @@ final class HashMap extends HashMapBase
} }
public function __get(string $nm) public function __get(string $nm): mixed
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";

View File

@@ -159,12 +159,12 @@ class Helpers
public static function escape(Driver $driver, $value, string $type): string public static function escape(Driver $driver, $value, string $type): string
{ {
$types = [ $types = [
Type::TEXT => 'text', Type::Text => 'text',
Type::BINARY => 'binary', Type::Binary => 'binary',
Type::BOOL => 'bool', Type::Bool => 'bool',
Type::DATE => 'date', Type::Date => 'date',
Type::DATETIME => 'datetime', Type::DateTime => 'datetime',
\dibi::IDENTIFIER => 'identifier', Fluent::Identifier => 'identifier',
]; ];
if (isset($types[$type])) { if (isset($types[$type])) {
return $driver->{'escape' . $types[$type]}($value); return $driver->{'escape' . $types[$type]}($value);
@@ -181,16 +181,16 @@ class Helpers
public static function detectType(string $type): ?string public static function detectType(string $type): ?string
{ {
$patterns = [ $patterns = [
'^_' => Type::TEXT, // PostgreSQL arrays '^_' => Type::Text, // PostgreSQL arrays
'RANGE$' => Type::TEXT, // PostgreSQL range types 'RANGE$' => Type::Text, // PostgreSQL range types
'BYTEA|BLOB|BIN' => Type::BINARY, 'BYTEA|BLOB|BIN' => Type::Binary,
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT, 'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::Text,
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER, 'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::Integer,
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::FLOAT, 'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::Float,
'^TIME$' => Type::TIME, '^TIME$' => Type::Time,
'TIME' => Type::DATETIME, // DATETIME, TIMESTAMP 'TIME' => Type::DateTime, // DATETIME, TIMESTAMP
'DATE' => Type::DATE, 'DATE' => Type::Date,
'BOOL' => Type::BOOL, 'BOOL' => Type::Bool,
'JSON' => Type::JSON, 'JSON' => Type::JSON,
]; ];

View File

@@ -457,16 +457,22 @@ class Result implements IDataSource
if ($type === null || $format === 'native') { if ($type === null || $format === 'native') {
$row[$key] = $value; $row[$key] = $value;
} elseif ($type === Type::TEXT) { } elseif ($type === Type::Text) {
$row[$key] = (string) $value; $row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) { } elseif ($type === Type::Integer) {
$row[$key] = is_float($tmp = $value * 1) $row[$key] = is_float($tmp = $value * 1)
? (is_string($value) ? $value : (int) $value) ? (is_string($value) ? $value : (int) $value)
: $tmp; : $tmp;
} elseif ($type === Type::FLOAT) { } elseif ($type === Type::Float) {
$value = ltrim((string) $value, '0'); if (!is_string($value)) {
$row[$key] = (float) $value;
continue;
}
$negative = ($value[0] ?? null) === '-';
$value = ltrim($value, '0-');
$p = strpos($value, '.'); $p = strpos($value, '.');
$e = strpos($value, 'e'); $e = strpos($value, 'e');
if ($p !== false && $e === false) { if ($p !== false && $e === false) {
@@ -479,27 +485,31 @@ class Result implements IDataSource
$value = '0' . $value; $value = '0' . $value;
} }
if ($negative) {
$value = '-' . $value;
}
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value)) $row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float ? $float
: $value; : $value;
} elseif ($type === Type::BOOL) { } elseif ($type === Type::Bool) {
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F'; $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', ... if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
$value = new DateTime($value); $value = new DateTime($value);
$row[$key] = $format ? $value->format($format) : $value; $row[$key] = $format ? $value->format($format) : $value;
} else { } else {
$row[$key] = null; $row[$key] = null;
} }
} elseif ($type === Type::TIME_INTERVAL) { } elseif ($type === Type::TimeInterval) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m); preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S"); $value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$value->invert = (int) (bool) $m[1]; $value->invert = (int) (bool) $m[1];
$row[$key] = $format ? $value->format($format) : $value; $row[$key] = $format ? $value->format($format) : $value;
} elseif ($type === Type::BINARY) { } elseif ($type === Type::Binary) {
$row[$key] = is_string($value) $row[$key] = is_string($value)
? $this->getResultDriver()->unescapeBinary($value) ? $this->getResultDriver()->unescapeBinary($value)
: $value; : $value;

View File

@@ -48,10 +48,11 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
} }
public function __get(string $key) public function __get(string $key): mixed
{ {
$hint = Helpers::getSuggestion(array_keys((array) $this), $key); $hint = Helpers::getSuggestion(array_keys((array) $this), $key);
trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE); trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE);
return null;
} }

View File

@@ -219,7 +219,7 @@ final class Translator
case 'a': // key=val, key=val, ... case 'a': // key=val, key=val, ...
foreach ($value as $k => $v) { 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]} . '=' $vx[] = $this->identifiers->{$pair[0]} . '='
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); . $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }

View File

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

View File

@@ -37,12 +37,16 @@ declare(strict_types=1);
*/ */
class dibi class dibi
{ {
public const public const Version = '5.0.2';
AFFECTED_ROWS = 'a',
IDENTIFIER = 'n';
/** version */ /** @deprecated use dibi::Version */
public const VERSION = '5.0.0'; 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 */ /** sorting order */
public const public const

View File

@@ -74,7 +74,7 @@ Assert::same(
(string) $fluent, (string) $fluent,
); );
$fluent->orderBy(Dibi\Fluent::REMOVE); $fluent->orderBy(Dibi\Fluent::Remove);
Assert::same( 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)'), 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

@@ -27,8 +27,8 @@ class MockResult extends Dibi\Result
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TEXT); $result->setType('col', Type::Text);
$result->setFormat(Type::TEXT, 'native'); $result->setFormat(Type::Text, 'native');
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true])); Assert::same(['col' => true], $result->test(['col' => true]));
@@ -38,7 +38,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::BOOL); $result->setType('col', Type::Bool);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true])); Assert::same(['col' => true], $result->test(['col' => true]));
@@ -60,7 +60,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TEXT); $result->setType('col', Type::Text);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => '1'], $result->test(['col' => true])); Assert::same(['col' => '1'], $result->test(['col' => true]));
@@ -76,7 +76,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::FLOAT); $result->setType('col', Type::Float);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => 1.0], $result->test(['col' => true])); 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::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10']));
Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.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'); 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' => '']));
Assert::same(['col' => 0.0], $result->test(['col' => '0'])); Assert::same(['col' => 0.0], $result->test(['col' => '0']));
@@ -147,13 +178,45 @@ test('', function () {
Assert::same(['col' => 0.0], $result->test(['col' => 0.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]));
Assert::same(['col' => 1.0], $result->test(['col' => 1.0])); 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'); setlocale(LC_NUMERIC, 'C');
}); });
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::INTEGER); $result->setType('col', Type::Integer);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => 1], $result->test(['col' => true])); Assert::same(['col' => 1], $result->test(['col' => true]));
@@ -187,7 +250,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DateTime);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception( Assert::exception(
@@ -206,8 +269,8 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DateTime);
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s'); $result->setFormat(Type::DateTime, 'Y-m-d H:i:s');
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception( Assert::exception(
@@ -226,7 +289,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATE); $result->setType('col', Type::Date);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception( Assert::exception(
@@ -243,7 +306,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TIME); $result->setType('col', Type::Time);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception( Assert::exception(

View File

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

View File

@@ -24,13 +24,17 @@ Assert::true(isset($row['title']));
// missing // missing
Assert::error(function () use ($row) { Assert::error(
$x = $row->missing; fn() => $x = $row->missing,
}, E_USER_NOTICE, "Attempt to read missing column 'missing'."); E_USER_NOTICE,
"Attempt to read missing column 'missing'.",
);
Assert::error(function () use ($row) { Assert::error(
$x = $row['missing']; fn() => $x = $row['missing'],
}, E_USER_NOTICE, "Attempt to read missing column 'missing'."); E_USER_NOTICE,
"Attempt to read missing column 'missing'.",
);
Assert::false(isset($row->missing)); Assert::false(isset($row->missing));
Assert::false(isset($row['missing'])); Assert::false(isset($row['missing']));
@@ -41,13 +45,17 @@ Assert::same(123, $row['missing'] ?? 123);
// suggestions // suggestions
Assert::error(function () use ($row) { Assert::error(
$x = $row->tilte; fn() => $x = $row->tilte,
}, E_USER_NOTICE, "Attempt to read missing column 'tilte', did you mean 'title'?"); E_USER_NOTICE,
"Attempt to read missing column 'tilte', did you mean 'title'?",
);
Assert::error(function () use ($row) { Assert::error(
$x = $row['tilte']; fn() => $x = $row['tilte'],
}, E_USER_NOTICE, "Attempt to read missing column 'tilte', did you mean 'title'?"); E_USER_NOTICE,
"Attempt to read missing column 'tilte', did you mean 'title'?",
);
// to array // to array

View File

@@ -33,7 +33,7 @@ enum PureEnum
Assert::equal('1', $translator->formatValue(EnumInt::One, null)); Assert::equal('1', $translator->formatValue(EnumInt::One, null));
Assert::equal(match ($config['driver']) { Assert::equal(match ($config['system']) {
'sqlsrv' => "N'one'", 'sqlsrv' => "N'one'",
default => "'one'", default => "'one'",
}, $translator->formatValue(EnumString::One, null)); }, $translator->formatValue(EnumString::One, null));

View File

@@ -20,7 +20,7 @@ date_default_timezone_set('Europe/Prague');
try { try {
$config = Tester\Environment::loadData(); $config = Tester\Environment::loadData();
} catch (Throwable $e) { } catch (Throwable $e) {
$config = parse_ini_file(__DIR__ . '/../databases.ini', true); $config = parse_ini_file(__DIR__ . '/../databases.ini', process_sections: true);
$config = reset($config); $config = reset($config);
} }