1
0
mirror of https://github.com/dg/dibi.git synced 2025-08-31 09:41:43 +02:00

Compare commits

...

9 Commits

Author SHA1 Message Date
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
16 changed files with 162 additions and 70 deletions

View File

@@ -17,7 +17,8 @@
"tracy/tracy": "^2.9",
"nette/tester": "^2.5",
"nette/di": "^3.1",
"phpstan/phpstan": "^1.0"
"phpstan/phpstan": "^1.0",
"jetbrains/phpstorm-attributes": "^1.0"
},
"replace": {
"dg/dibi": "*"

View File

@@ -639,7 +639,7 @@ In the configuration file, we will register the DI extensions and add the `dibi`
```neon
extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22
dibi: Dibi\Bridges\Nette\DibiExtension3
dibi:
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(),
]),
])->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'.
# Requires Nette Framework 2.2 or later
# Requires Nette Framework 3 or later
extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22
dibi: Dibi\Bridges\Nette\DibiExtension3
dibi:
host: localhost

View File

@@ -62,7 +62,7 @@ class Panel implements Tracy\IBarPanel
if ($e instanceof Dibi\Exception && $e->getSql()) {
return [
'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
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
try {
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), return: true);
} 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 .= '</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) {
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
}

View File

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

View File

@@ -119,7 +119,7 @@ class OdbcDriver implements Dibi\Driver
*/
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));
}
}

View File

@@ -93,23 +93,15 @@ class PdoDriver implements Dibi\Driver
$this->affectedRows = null;
[$sqlState, $code, $message] = $this->connection->errorInfo();
$code ??= 0;
$message = "SQLSTATE[$sqlState]: $message";
switch ($this->driverName) {
case 'mysql':
throw MySqliDriver::createException($message, $code, $sql);
case 'oci':
throw OracleDriver::createException($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);
}
throw match ($this->driverName) {
'mysql' => MySqliDriver::createException($message, $code, $sql),
'oci' => OracleDriver::createException($message, $code, $sql),
'pgsql' => PostgreDriver::createException($message, $sqlState, $sql),
'sqlite' => SqliteDriver::createException($message, $code, $sql),
default => new Dibi\DriverException($message, $code, $sql),
};
}
@@ -139,7 +131,7 @@ class PdoDriver implements Dibi\Driver
{
if (!$this->connection->beginTransaction()) {
$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()) {
$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()) {
$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);
}
}

View File

@@ -72,13 +72,6 @@ class PostgreReflector implements Dibi\Reflector
public function getColumns(string $table): array
{
$_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("
SELECT *
@@ -98,7 +91,8 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable,
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
pg_attribute a
JOIN pg_type ON a.atttypid = pg_type.oid
@@ -123,7 +117,7 @@ class PostgreReflector implements Dibi\Reflector
'size' => $size > 0 ? $size : null,
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
'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,
];
}

View File

@@ -57,6 +57,7 @@ class SqliteDriver implements Dibi\Driver
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
$version = SQLite3::version();
$this->connection->enableExceptions(false);
if ($version['versionNumber'] >= '3006019') {
$this->query('PRAGMA foreign_keys = ON');
}

View File

@@ -279,19 +279,17 @@ 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)
* @throws Exception
*/
public function execute(?string $return = null): Result|int|null
{
$res = $this->query($this->_export());
switch ($return) {
case \dibi::IDENTIFIER:
return $this->connection->getInsertId();
case \dibi::AFFECTED_ROWS:
return $this->connection->getAffectedRows();
default:
return $res;
}
return match ($return) {
\dibi::IDENTIFIER => $this->connection->getInsertId(),
\dibi::AFFECTED_ROWS => $this->connection->getAffectedRows(),
default => $res,
};
}

View File

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

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);
trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE);
return null;
}

View File

@@ -42,7 +42,7 @@ class dibi
IDENTIFIER = 'n';
/** version */
public const VERSION = '5.0.0';
public const VERSION = '5.0.1';
/** sorting order */
public const

View File

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

View File

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