diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 59e3468cf..7cf08b7d4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,7 +3,7 @@ name: Tests on: [push, pull_request] env: - php-extensions: mbstring, intl, pdo_sqlsrv-5.10.0beta2 + php-extensions: mbstring, intl, pdo_sqlsrv-5.12.0 php-tools: "composer:v2, pecl" jobs: diff --git a/composer.json b/composer.json index 4dbddd8c6..93277697a 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } } } diff --git a/readme.md b/readme.md index 2f4742165..43f65343d 100644 --- a/readme.md +++ b/readme.md @@ -1,25 +1,26 @@ -Nette Database -============== +[![Nette Database](https://github.com/nette/database/assets/194960/97d8f31b-096c-466c-a76f-f5b9e511ea8d)](https://doc.nette.org/database) [![Downloads this Month](https://img.shields.io/packagist/dm/nette/database.svg)](https://packagist.org/packages/nette/database) -[![Tests](https://github.com/nette/database/actions/workflows/tests.yml/badge.svg?branch=v3.2)](https://github.com/nette/database/actions) +[![Tests](https://github.com/nette/database/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/nette/database/actions) [![Latest Stable Version](https://poser.pugx.org/nette/database/v/stable)](https://github.com/nette/database/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/database/blob/master/license.md) +  Introduction ------------ Nette provides a powerful layer for accessing your database easily. -- composes SQL queries with ease -- easily fetches data -- uses efficient queries and does not transmit unnecessary data +✅ composes SQL queries with ease
+✅ significantly simplifies retrieving data without writing SQL queries
+✅ uses efficient queries and does not transmit unnecessary data -The [Nette Database Core](https://doc.nette.org/database-core) is a wrapper around the PDO and provides core functionality. +The [Nette Database Core](https://doc.nette.org/en/database/core) is a wrapper around the PDO and provides core functionality. -The [Nette Database Explorer](https://doc.nette.org/database-explorer) layer helps you to fetch database data more easily and in a more optimized way. +The [Nette Database Explorer](https://doc.nette.org/en/database/explorer) layer helps you to fetch database data more easily and in a more optimized way. +  [Support Me](https://github.com/sponsors/dg) -------------------------------------------- @@ -30,6 +31,7 @@ Do you like Nette Database? Are you looking forward to the new features? Thank you! +  Installation ------------ @@ -42,12 +44,14 @@ composer require nette/database It requires PHP version 8.1 and supports PHP up to 8.4. +  Usage ----- This is just a piece of documentation. [Please see our website](https://doc.nette.org/database). +  Database Core ------------- @@ -55,7 +59,7 @@ Database Core To create a new database connection just create a new instance of `Nette\Database\Connection` class: ```php -$database = new Nette\Database\Connection($dsn, $user, $password); // the same arguments as uses PDO +$database = new Nette\Database\Explorer($dsn, $user, $password); // the same arguments as uses PDO ``` Connection allows you to easily query your database by calling `query` method: @@ -71,6 +75,8 @@ $database->query('UPDATE users SET ? WHERE id=?', $data, $id); $database->query('SELECT * FROM categories WHERE id=?', 123)->dump(); ``` +  + Database Explorer ----------------- diff --git a/src/Bridges/DatabaseDI/DatabaseExtension.php b/src/Bridges/DatabaseDI/DatabaseExtension.php index 703c18a55..35e5e8257 100644 --- a/src/Bridges/DatabaseDI/DatabaseExtension.php +++ b/src/Bridges/DatabaseDI/DatabaseExtension.php @@ -10,6 +10,7 @@ namespace Nette\Bridges\DatabaseDI; use Nette; +use Nette\DI\Definitions\Statement; use Nette\Schema\Expect; use Tracy; @@ -77,6 +78,10 @@ public function beforeCompile(): void private function setupDatabase(\stdClass $config, string $name): void { + if (!empty($config->reflection)) { + throw new Nette\DeprecatedException('The "reflection" option is deprecated, use "conventions" instead.'); + } + $builder = $this->getContainerBuilder(); foreach ($config->options as $key => $value) { @@ -90,48 +95,33 @@ private function setupDatabase(\stdClass $config, string $name): void } } - $connection = $builder->addDefinition($this->prefix("$name.connection")) - ->setFactory(Nette\Database\Connection::class, [$config->dsn, $config->user, $config->password, $config->options]) - ->setAutowired($config->autowired); + $cacheId = 'Nette.Database.' . hash('xxh128', $name . $config->dsn); + $cache = new Statement(Nette\Caching\Cache::class, [1 => $cacheId]); - $structure = $builder->addDefinition($this->prefix("$name.structure")) - ->setFactory(Nette\Database\Structure::class) - ->setArguments([$connection]) + $explorer = $builder->addDefinition($this->prefix($name)) + ->setFactory(Nette\Database\Explorer::class, [$config->dsn, $config->user, $config->password, $config->options]) + ->addSetup('setCache', [$cache]) ->setAutowired($config->autowired); - if (!empty($config->reflection)) { - $conventionsServiceName = 'reflection'; - $config->conventions = $config->reflection; - if (is_string($config->conventions) && strtolower($config->conventions) === 'conventional') { - $config->conventions = 'Static'; - } - } else { - $conventionsServiceName = 'conventions'; - } - - if (!$config->conventions) { - $conventions = null; + if (!$config->conventions || $config->conventions === 'discovered') { } elseif (is_string($config->conventions)) { - $conventions = $builder->addDefinition($this->prefix("$name.$conventionsServiceName")) + $conventions = $builder->addDefinition($this->prefix("$name.conventions")) ->setFactory(preg_match('#^[a-z]+$#Di', $config->conventions) ? 'Nette\Database\Conventions\\' . ucfirst($config->conventions) . 'Conventions' : $config->conventions) - ->setArguments(strtolower($config->conventions) === 'discovered' ? [$structure] : []) ->setAutowired($config->autowired); + $explorer->addSetup('setConventions', [$conventions]); } else { - $conventions = Nette\DI\Helpers::filterArguments([$config->conventions])[0]; + $explorer->addSetup('setConventions', [Nette\DI\Helpers::filterArguments([$config->conventions])[0]]); } - $builder->addDefinition($this->prefix("$name.explorer")) - ->setFactory(Nette\Database\Explorer::class, [$connection, $structure, $conventions]) - ->setAutowired($config->autowired); - - $builder->addAlias($this->prefix("$name.context"), $this->prefix("$name.explorer")); + $builder->addAlias($this->prefix("$name.connection"), $this->prefix($name)); + $builder->addAlias($this->prefix("$name.context"), $this->prefix($name)); + $builder->addAlias($this->prefix("$name.explorer"), $this->prefix($name)); if ($this->name === 'database') { - $builder->addAlias($this->prefix($name), $this->prefix("$name.connection")); $builder->addAlias("nette.database.$name", $this->prefix($name)); $builder->addAlias("nette.database.$name.context", $this->prefix("$name.explorer")); } diff --git a/src/Bridges/DatabaseTracy/ConnectionPanel.php b/src/Bridges/DatabaseTracy/ConnectionPanel.php index 2460f46e4..ba6abdcc8 100644 --- a/src/Bridges/DatabaseTracy/ConnectionPanel.php +++ b/src/Bridges/DatabaseTracy/ConnectionPanel.php @@ -10,8 +10,10 @@ namespace Nette\Bridges\DatabaseTracy; use Nette; -use Nette\Database\Connection; +use Nette\Database\DriverException; +use Nette\Database\Explorer; use Nette\Database\Helpers; +use Nette\Database\Result; use Tracy; @@ -21,27 +23,19 @@ class ConnectionPanel implements Tracy\IBarPanel { public int $maxQueries = 100; - public string $name; - public bool|string $explain = true; - public bool $disabled = false; - public float $performanceScale = 0.25; - private float $totalTime = 0; - private int $count = 0; - - private array $queries = []; - + private array $events = []; private Tracy\BlueScreen $blueScreen; public static function initialize( - Connection $connection, - bool $addBarPanel = false, + Explorer $explorer, + bool $addBarPanel = true, string $name = '', bool $explain = true, ?Tracy\Bar $bar = null, @@ -52,7 +46,7 @@ public static function initialize( $blueScreen->addPanel(self::renderException(...)); if ($addBarPanel) { - $panel = new self($connection, $blueScreen); + $panel = new self($explorer, $blueScreen); $panel->explain = $explain; $panel->name = $name; $bar ??= Tracy\Debugger::getBar(); @@ -63,14 +57,14 @@ public static function initialize( } - public function __construct(Connection $connection, Tracy\BlueScreen $blueScreen) + public function __construct(Explorer $explorer, Tracy\BlueScreen $blueScreen) { - $connection->onQuery[] = $this->logQuery(...); + $explorer->onQuery[] = $this->logQuery(...); $this->blueScreen = $blueScreen; } - private function logQuery(Connection $connection, $result): void + private function logQuery(Explorer $connection, $result): void { if ($this->disabled) { return; @@ -79,7 +73,7 @@ private function logQuery(Connection $connection, $result): void $this->count++; $source = null; - $trace = $result instanceof \PDOException + $trace = $result instanceof DriverException ? $result->getTrace() : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); foreach ($trace as $row) { @@ -88,40 +82,33 @@ private function logQuery(Connection $connection, $result): void && preg_match('~\.(php.?|phtml)$~', $row['file']) && !$this->blueScreen->isCollapsed($row['file'])) && ($row['class'] ?? '') !== self::class - && !is_a($row['class'] ?? '', Connection::class, allow_string: true) + && !is_a($row['class'] ?? '', Explorer::class, allow_string: true) ) { $source = [$row['file'], (int) $row['line']]; break; } } - if ($result instanceof Nette\Database\ResultSet) { + if ($result instanceof Result) { $this->totalTime += $result->getTime(); if ($this->count < $this->maxQueries) { - $this->queries[] = [$connection, $result->getQueryString(), $result->getParameters(), $source, $result->getTime(), $result->getRowCount(), null]; + $this->events[] = [$connection, $result->getQuery(), $source, $result->getTime(), $result->getRowCount(), null]; } - } elseif ($result instanceof \PDOException && $this->count < $this->maxQueries) { - $this->queries[] = [$connection, $result->queryString, null, $source, null, null, $result->getMessage()]; + } elseif ($result instanceof DriverException && $this->count < $this->maxQueries) { + $this->events[] = [$connection, $result->getQuery(), $source, null, null, $result->getMessage()]; } } public static function renderException(?\Throwable $e): ?array { - if (!$e instanceof \PDOException) { + if (!$e instanceof DriverException) { return null; } - if (isset($e->queryString)) { - $sql = $e->queryString; - - } elseif ($item = Tracy\Helpers::findTrace($e->getTrace(), 'PDO::prepare')) { - $sql = $item['args'][0]; - } - - return isset($sql) ? [ + return $e->getQuery() ? [ 'tab' => 'SQL', - 'panel' => Helpers::dumpSql($sql, $e->params ?? []), + 'panel' => Helpers::dumpSql($e->getQuery()), ] : null; } @@ -143,10 +130,11 @@ public function getPanel(): ?string return null; } - $queries = []; - foreach ($this->queries as $query) { - [$connection, $sql, $params, , , , $error] = $query; + $events = []; + foreach ($this->events as $event) { + [$connection, $query, , , , $error] = $event; $explain = null; + $sql = $query->getSql(); $command = preg_match('#\s*\(?\s*(SELECT|INSERT|UPDATE|DELETE)\s#iA', $sql, $m) ? strtolower($m[1]) : null; @@ -155,17 +143,18 @@ public function getPanel(): ?string $cmd = is_string($this->explain) ? $this->explain : 'EXPLAIN'; - $explain = (new Nette\Database\ResultSet($connection, "$cmd $sql", $params))->fetchAll(); - } catch (\PDOException) { + $rows = $connection->getConnection()->query("$cmd $sql", $query->getParameters()); + for ($explain = []; $row = $rows->fetch(); $explain[] = $row); + } catch (DriverException) { } } - $query[] = $command; - $query[] = $explain; - $queries[] = $query; + $event[] = $command; + $event[] = $explain; + $events[] = $event; } - return Nette\Utils\Helpers::capture(function () use ($queries, $connection) { + return Nette\Utils\Helpers::capture(function () use ($events, $connection) { $name = $this->name; $count = $this->count; $totalTime = $this->totalTime; diff --git a/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml b/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml index df359af94..b2d015084 100644 --- a/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml +++ b/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml @@ -8,15 +8,15 @@ use Tracy\Helpers; ?> -

Queries: Queries:

@@ -24,8 +24,8 @@ use Tracy\Helpers; -
Time msSQL QueryRows
@@ -36,7 +36,7 @@ use Tracy\Helpers;
explain
+ @@ -61,6 +61,6 @@ use Tracy\Helpers;
-

...and more

+

...and more

diff --git a/src/Database/Connection.php b/src/Database/Connection.php deleted file mode 100644 index 4d66a05a1..000000000 --- a/src/Database/Connection.php +++ /dev/null @@ -1,321 +0,0 @@ - Occurs after connection is established */ - public array $onConnect = []; - - /** @var array Occurs after query is executed */ - public array $onQuery = []; - private Driver $driver; - private SqlPreprocessor $preprocessor; - private ?PDO $pdo = null; - - /** @var callable(array, ResultSet): array */ - private $rowNormalizer = [Helpers::class, 'normalizeRow']; - private ?string $sql = null; - private int $transactionDepth = 0; - - - public function __construct( - private readonly string $dsn, - #[\SensitiveParameter] - private readonly ?string $user = null, - #[\SensitiveParameter] - private readonly ?string $password = null, - private readonly array $options = [], - ) { - if (!empty($options['newDateTime'])) { - $this->rowNormalizer = fn($row, $resultSet) => Helpers::normalizeRow($row, $resultSet, DateTime::class); - } - if (empty($options['lazy'])) { - $this->connect(); - } - } - - - public function connect(): void - { - if ($this->pdo) { - return; - } - - try { - $this->pdo = new PDO($this->dsn, $this->user, $this->password, $this->options); - } catch (PDOException $e) { - throw ConnectionException::from($e); - } - - $class = empty($this->options['driverClass']) - ? 'Nette\Database\Drivers\\' . ucfirst(str_replace('sql', 'Sql', $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME))) . 'Driver' - : $this->options['driverClass']; - $this->driver = new $class; - $this->preprocessor = new SqlPreprocessor($this); - $this->driver->initialize($this, $this->options); - Arrays::invoke($this->onConnect, $this); - } - - - public function reconnect(): void - { - $this->disconnect(); - $this->connect(); - } - - - public function disconnect(): void - { - $this->pdo = null; - } - - - public function getDsn(): string - { - return $this->dsn; - } - - - public function getPdo(): PDO - { - $this->connect(); - return $this->pdo; - } - - - public function getDriver(): Driver - { - $this->connect(); - return $this->driver; - } - - - /** @deprecated use getDriver() */ - public function getSupplementalDriver(): Driver - { - $this->connect(); - return $this->driver; - } - - - public function getReflection(): Reflection - { - return new Reflection($this->getDriver()); - } - - - public function setRowNormalizer(?callable $normalizer): static - { - $this->rowNormalizer = $normalizer; - return $this; - } - - - public function getInsertId(?string $sequence = null): string - { - try { - $res = $this->getPdo()->lastInsertId($sequence); - return $res === false ? '0' : $res; - } catch (PDOException $e) { - throw $this->driver->convertException($e); - } - } - - - public function quote(string $string, int $type = PDO::PARAM_STR): string - { - try { - return $this->getPdo()->quote($string, $type); - } catch (PDOException $e) { - throw DriverException::from($e); - } - } - - - public function beginTransaction(): void - { - if ($this->transactionDepth !== 0) { - throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); - } - - $this->query('::beginTransaction'); - } - - - public function commit(): void - { - if ($this->transactionDepth !== 0) { - throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); - } - - $this->query('::commit'); - } - - - public function rollBack(): void - { - if ($this->transactionDepth !== 0) { - throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); - } - - $this->query('::rollBack'); - } - - - public function transaction(callable $callback): mixed - { - if ($this->transactionDepth === 0) { - $this->beginTransaction(); - } - - $this->transactionDepth++; - try { - $res = $callback($this); - } catch (\Throwable $e) { - $this->transactionDepth--; - if ($this->transactionDepth === 0) { - $this->rollback(); - } - - throw $e; - } - - $this->transactionDepth--; - if ($this->transactionDepth === 0) { - $this->commit(); - } - - return $res; - } - - - /** - * Generates and executes SQL query. - * @param literal-string $sql - */ - public function query(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ResultSet - { - [$this->sql, $params] = $this->preprocess($sql, ...$params); - try { - $result = new ResultSet($this, $this->sql, $params, $this->rowNormalizer); - } catch (PDOException $e) { - Arrays::invoke($this->onQuery, $this, $e); - throw $e; - } - - Arrays::invoke($this->onQuery, $this, $result); - return $result; - } - - - /** @deprecated use query() */ - public function queryArgs(string $sql, array $params): ResultSet - { - return $this->query($sql, ...$params); - } - - - /** - * @param literal-string $sql - * @return array{string, array} - */ - public function preprocess(string $sql, ...$params): array - { - $this->connect(); - return $params - ? $this->preprocessor->process(func_get_args()) - : [$sql, []]; - } - - - public function getLastQueryString(): ?string - { - return $this->sql; - } - - - /********************* shortcuts ****************d*g**/ - - - /** - * Shortcut for query()->fetch() - * @param literal-string $sql - */ - public function fetch(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?Row - { - return $this->query($sql, ...$params)->fetch(); - } - - - /** - * Shortcut for query()->fetchAssoc() - * @param literal-string $sql - */ - public function fetchAssoc(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?array - { - return $this->query($sql, ...$params)->fetchAssoc(); - } - - - /** - * Shortcut for query()->fetchField() - * @param literal-string $sql - */ - public function fetchField(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): mixed - { - return $this->query($sql, ...$params)->fetchField(); - } - - - /** - * Shortcut for query()->fetchFields() - * @param literal-string $sql - */ - public function fetchFields(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?array - { - return $this->query($sql, ...$params)->fetchFields(); - } - - - /** - * Shortcut for query()->fetchPairs() - * @param literal-string $sql - */ - public function fetchPairs(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): array - { - return $this->query($sql, ...$params)->fetchPairs(); - } - - - /** - * Shortcut for query()->fetchAll() - * @param literal-string $sql - */ - public function fetchAll(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): array - { - return $this->query($sql, ...$params)->fetchAll(); - } - - - public static function literal(string $value, ...$params): SqlLiteral - { - return new SqlLiteral($value, $params); - } -} diff --git a/src/Database/Conventions.php b/src/Database/Conventions.php index 3e7a68710..72e743667 100644 --- a/src/Database/Conventions.php +++ b/src/Database/Conventions.php @@ -39,6 +39,3 @@ function getHasManyReference(string $table, string $key): ?array; */ function getBelongsToReference(string $table, string $key): ?array; } - - -interface_exists(IConventions::class); diff --git a/src/Database/Conventions/DiscoveredConventions.php b/src/Database/Conventions/DiscoveredConventions.php index 272b942ac..930a889b4 100644 --- a/src/Database/Conventions/DiscoveredConventions.php +++ b/src/Database/Conventions/DiscoveredConventions.php @@ -10,7 +10,7 @@ namespace Nette\Database\Conventions; use Nette\Database\Conventions; -use Nette\Database\IStructure; +use Nette\Database\Structure; /** @@ -19,7 +19,7 @@ class DiscoveredConventions implements Conventions { public function __construct( - protected readonly IStructure $structure, + protected readonly Structure $structure, ) { } diff --git a/src/Database/Driver.php b/src/Database/Driver.php deleted file mode 100644 index 030674007..000000000 --- a/src/Database/Driver.php +++ /dev/null @@ -1,98 +0,0 @@ - */ - function getTables(): array; - - /** @return list */ - function getColumns(string $table): array; - - /** @return list, unique: bool, primary: bool}> */ - function getIndexes(string $table): array; - - /** @return list */ - function getForeignKeys(string $table): array; - - /** - * Returns associative array of detected types (IStructure::FIELD_*) in result set. - * @return array - */ - function getColumnTypes(\PDOStatement $statement): array; - - /** - * Cheks if driver supports specific property - * @param self::Support* $item - */ - function isSupported(string $item): bool; -} - - -interface_exists(ISupplementalDriver::class); diff --git a/src/Database/DriverException.php b/src/Database/DriverException.php index aad02cd6e..700c01186 100644 --- a/src/Database/DriverException.php +++ b/src/Database/DriverException.php @@ -13,51 +13,54 @@ /** * Base class for all errors in the driver or SQL server. */ -class DriverException extends \PDOException +class DriverException extends \Exception { - public ?string $queryString = null; - public ?array $params = null; + public static function from(self $e): static + { + return new static($e->getMessage(), $e->sqlState, $e->getCode(), $e->query, $e); + } - public static function from(\PDOException $src): static - { - $e = new static($src->message, 0, $src); - $e->file = $src->file; - $e->line = $src->line; - if (!$src->errorInfo && preg_match('#SQLSTATE\[(.*?)\] \[(.*?)\] (.*)#A', $src->message, $m)) { - $m[2] = (int) $m[2]; - $e->errorInfo = array_slice($m, 1); - $e->code = $m[1]; - } else { - $e->errorInfo = $src->errorInfo; - $e->code = $src->code; - $e->code = $e->errorInfo[0] ?? $src->code; - } - - return $e; + public function __construct( + string $message, + private readonly ?string $sqlState = null, + int $code = 0, + private readonly ?SqlLiteral $query = null, + ?\Throwable $previous = null, + ) { + parent::__construct($message, $code, $previous); } - public function getDriverCode(): int|string|null + /** @deprecated use getCode() */ + public function getDriverCode(): int { - return $this->errorInfo[1] ?? null; + return $this->getCode(); } public function getSqlState(): ?string { - return $this->errorInfo[0] ?? null; + return $this->sqlState; + } + + + public function getQuery(): ?SqlLiteral + { + return $this->query; } + /** @deprecated use getQuery()->getSql() */ public function getQueryString(): ?string { - return $this->queryString; + return $this->query?->getSql(); } + /** @deprecated use getQuery()->getParameters() */ public function getParameters(): ?array { - return $this->params; + return $this->query?->getParameters(); } } diff --git a/src/Database/Drivers/Accessory/LazyConnection.php b/src/Database/Drivers/Accessory/LazyConnection.php new file mode 100644 index 000000000..2755def89 --- /dev/null +++ b/src/Database/Drivers/Accessory/LazyConnection.php @@ -0,0 +1,81 @@ +callback)(); + } + + + public function query(string $sql, array $params = []): Drivers\Result + { + return $this->getConnection()->query($sql, $params); + } + + + public function execute(string $sql): int + { + return $this->getConnection()->execute($sql); + } + + + public function getNativeConnection(): mixed + { + return $this->getConnection()->getNativeConnection(); + } + + + public function beginTransaction(): void + { + $this->getConnection()->beginTransaction(); + } + + + public function commit(): void + { + $this->getConnection()->commit(); + } + + + public function rollBack(): void + { + $this->getConnection()->rollBack(); + } + + + public function getInsertId(?string $sequence = null): int|string + { + return $this->getConnection()->getInsertId($sequence); + } + + + public function quote(string $string): string + { + return $this->getConnection()->quote($string); + } + + + public function getServerVersion(): string + { + return $this->getConnection()->getServerVersion(); + } +} diff --git a/src/Database/Drivers/Connection.php b/src/Database/Drivers/Connection.php new file mode 100644 index 000000000..03dd4d412 --- /dev/null +++ b/src/Database/Drivers/Connection.php @@ -0,0 +1,45 @@ + + */ + function getTables(): array; + + /** + * Returns detailed information about columns in a table. + * @return list + */ + function getColumns(string $table): array; + + /** + * Returns information about indexes in a table. + * @return list, unique: bool, primary: bool}> + */ + function getIndexes(string $table): array; + + /** + * Returns information about foreign keys in a table. + * @return list, table: string, foreign: list}> + */ + function getForeignKeys(string $table): array; +} diff --git a/src/Database/Drivers/MsSqlDriver.php b/src/Database/Drivers/Engines/MSSQLEngine.php similarity index 76% rename from src/Database/Drivers/MsSqlDriver.php rename to src/Database/Drivers/Engines/MSSQLEngine.php index 4ef886921..afbdca0c6 100644 --- a/src/Database/Drivers/MsSqlDriver.php +++ b/src/Database/Drivers/Engines/MSSQLEngine.php @@ -7,35 +7,41 @@ declare(strict_types=1); -namespace Nette\Database\Drivers; +namespace Nette\Database\Drivers\Engines; use Nette; +use Nette\Database\Drivers\Connection; +use Nette\Database\Drivers\Engine; +use Nette\Database\TypeConverter; /** - * Supplemental MS SQL database driver. + * MS SQL database platform. */ -class MsSqlDriver implements Nette\Database\Driver +class MSSQLEngine implements Engine { - private Nette\Database\Connection $connection; + public function __construct( + private readonly Connection $connection, + ) { + } - public function initialize(Nette\Database\Connection $connection, array $options): void + public function isSupported(string $feature): bool { - $this->connection = $connection; + return $feature === self::SupportSubselect; } - public function convertException(\PDOException $e): Nette\Database\DriverException + public function classifyException(Nette\Database\DriverException $e): ?string { - return Nette\Database\DriverException::from($e); + return null; } /********************* SQL ****************d*g**/ - public function delimite(string $name): string + public function delimit(string $name): string { // @see https://msdn.microsoft.com/en-us/library/ms176027.aspx return '[' . str_replace(['[', ']'], ['[[', ']]'], $name) . ']'; @@ -54,14 +60,7 @@ public function formatDateInterval(\DateInterval $value): string } - public function formatLike(string $value, int $pos): string - { - $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); - return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); - } - - - public function applyLimit(string &$sql, ?int $limit, ?int $offset): void + public function applyLimit(string $sql, ?int $limit, ?int $offset): string { if ($offset) { throw new Nette\NotSupportedException('Offset is not supported by this database.'); @@ -75,6 +74,8 @@ public function applyLimit(string &$sql, ?int $limit, ?int $offset): void throw new Nette\InvalidArgumentException('SQL query must begin with SELECT, UPDATE or DELETE command.'); } } + + return $sql; } @@ -107,6 +108,7 @@ public function getColumns(string $table): array DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, + NUMERIC_SCALE, IS_NULLABLE, COLUMN_DEFAULT, DOMAIN_NAME @@ -115,20 +117,21 @@ public function getColumns(string $table): array WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? - X, $table_schema, $table_name); + X, [$table_schema, $table_name]); while ($row = $rows->fetch()) { $columns[] = [ 'name' => $row['COLUMN_NAME'], 'table' => $table, - 'nativetype' => strtoupper($row['DATA_TYPE']), + 'nativeType' => strtoupper($row['DATA_TYPE']), 'size' => $row['CHARACTER_MAXIMUM_LENGTH'] ?? $row['NUMERIC_PRECISION'], + 'scale' => $row['NUMERIC_SCALE'], 'unsigned' => false, 'nullable' => $row['IS_NULLABLE'] === 'YES', 'default' => $row['COLUMN_DEFAULT'], - 'autoincrement' => $row['DOMAIN_NAME'] === 'COUNTER', + 'autoIncrement' => $row['DOMAIN_NAME'] === 'COUNTER', 'primary' => $row['COLUMN_NAME'] === 'ID', - 'vendor' => (array) $row, + 'vendor' => $row, ]; } @@ -157,7 +160,7 @@ public function getIndexes(string $table): array t.name = ? ORDER BY t.name, ind.name, ind.index_id, ic.index_column_id - X, $table_name); + X, [$table_name]); while ($row = $rows->fetch()) { $id = $row['name_index']; @@ -198,28 +201,22 @@ public function getForeignKeys(string $table): array ON col2.column_id = referenced_column_id AND col2.object_id = tab2.object_id WHERE tab1.name = ? - X, $table_name); + X, [$table_name]); - $id = 0; while ($row = $rows->fetch()) { - $keys[$id]['name'] = $row['fk_name']; - $keys[$id]['local'] = $row['column']; + $id = $row['fk_name']; + $keys[$id]['name'] = $id; + $keys[$id]['local'][] = $row['column']; $keys[$id]['table'] = $table_schema . '.' . $row['referenced_table']; - $keys[$id++]['foreign'] = $row['referenced_column']; + $keys[$id]['foreign'][] = $row['referenced_column']; } return array_values($keys); } - public function getColumnTypes(\PDOStatement $statement): array - { - return Nette\Database\Helpers::detectTypes($statement); - } - - - public function isSupported(string $item): bool + public function convertToPhp(mixed $value, array $meta, TypeConverter $converter): mixed { - return $item === self::SupportSubselect; + return $converter->convertToPhp($value, $meta); } } diff --git a/src/Database/Drivers/Engines/MySQLEngine.php b/src/Database/Drivers/Engines/MySQLEngine.php new file mode 100644 index 000000000..40c0f0dc4 --- /dev/null +++ b/src/Database/Drivers/Engines/MySQLEngine.php @@ -0,0 +1,185 @@ +getCode(); + return match (true) { + in_array($code, [1216, 1217, 1451, 1452, 1701], strict: true) => Nette\Database\ForeignKeyConstraintViolationException::class, + in_array($code, [1062, 1557, 1569, 1586], strict: true) => Nette\Database\UniqueConstraintViolationException::class, + $code >= 2001 && $code <= 2028 => Nette\Database\ConnectionException::class, + in_array($code, [1048, 1121, 1138, 1171, 1252, 1263, 1566], strict: true) => Nette\Database\NotNullConstraintViolationException::class, + default => null, + }; + } + + + /********************* SQL ****************d*g**/ + + + public function delimit(string $name): string + { + // @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html + return '`' . str_replace('`', '``', $name) . '`'; + } + + + public function formatDateTime(\DateTimeInterface $value): string + { + return $value->format("'Y-m-d H:i:s'"); + } + + + public function formatDateInterval(\DateInterval $value): string + { + return $value->format("'%r%h:%I:%S'"); + } + + + public function applyLimit(string $sql, ?int $limit, ?int $offset): string + { + if ($limit < 0 || $offset < 0) { + throw new Nette\InvalidArgumentException('Negative offset or limit.'); + + } elseif ($limit !== null || $offset) { + // see http://dev.mysql.com/doc/refman/5.0/en/select.html + $sql .= ' LIMIT ' . ($limit ?? '18446744073709551615') + . ($offset ? ' OFFSET ' . $offset : ''); + } + + return $sql; + } + + + /********************* reflection ****************d*g**/ + + + public function getTables(): array + { + $tables = []; + $rows = $this->connection->query('SHOW FULL TABLES'); + while ($row = $rows->fetch()) { + $row = array_values($row); + $tables[] = [ + 'name' => $row[0], + 'view' => ($row[1] ?? null) === 'VIEW', + ]; + } + + return $tables; + } + + + public function getColumns(string $table): array + { + $columns = []; + $rows = $this->connection->query('SHOW FULL COLUMNS FROM ' . $this->delimit($table)); + while ($row = $rows->fetch()) { + $row = array_change_key_case($row); + $typeInfo = Nette\Database\Helpers::parseColumnType($row['type']); + $columns[] = [ + 'name' => $row['field'], + 'table' => $table, + 'nativeType' => strtoupper($typeInfo['type']), + 'size' => $typeInfo['size'], + 'scale' => $typeInfo['scale'], + 'nullable' => $row['null'] === 'YES', + 'default' => $row['default'], + 'autoIncrement' => $row['extra'] === 'auto_increment', + 'primary' => $row['key'] === 'PRI', + 'vendor' => $row, + ]; + } + + return $columns; + } + + + public function getIndexes(string $table): array + { + $indexes = []; + $rows = $this->connection->query('SHOW INDEX FROM ' . $this->delimit($table)); + while ($row = $rows->fetch()) { + $id = $row['Key_name']; + $indexes[$id]['name'] = $id; + $indexes[$id]['unique'] = !$row['Non_unique']; + $indexes[$id]['primary'] = $row['Key_name'] === 'PRIMARY'; + $indexes[$id]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name']; + } + + return array_values($indexes); + } + + + public function getForeignKeys(string $table): array + { + $keys = []; + $rows = $this->connection->query(<<<'X' + SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = ? + X, [$table]); + + while ($row = $rows->fetch()) { + $id = $row['CONSTRAINT_NAME']; + $keys[$id]['name'] = $id; + $keys[$id]['local'][] = $row['COLUMN_NAME']; + $keys[$id]['table'] = $row['REFERENCED_TABLE_NAME']; + $keys[$id]['foreign'][] = $row['REFERENCED_COLUMN_NAME']; + } + + return array_values($keys); + } + + + public function convertToPhp(mixed $value, array $meta, TypeConverter $converter): mixed + { + return match ($meta['nativeType']) { + 'TINY' => $meta['size'] === 1 && $converter->convertBoolean + ? $converter->toBool($value) + : $converter->toInt($value), + 'TIME' => $converter->convertDateTime ? $converter->toInterval($value) : $value, + 'DATE', 'DATETIME', 'TIMESTAMP' => $converter->convertDateTime + ? (str_starts_with($value, '0000-00') ? null : $converter->toDateTime($value)) + : $value, + default => $converter->convertToPhp($value, $meta), + }; + } +} diff --git a/src/Database/Drivers/OdbcDriver.php b/src/Database/Drivers/Engines/ODBCEngine.php similarity index 63% rename from src/Database/Drivers/OdbcDriver.php rename to src/Database/Drivers/Engines/ODBCEngine.php index 078a202cc..f6842367a 100644 --- a/src/Database/Drivers/OdbcDriver.php +++ b/src/Database/Drivers/Engines/ODBCEngine.php @@ -7,31 +7,34 @@ declare(strict_types=1); -namespace Nette\Database\Drivers; +namespace Nette\Database\Drivers\Engines; use Nette; +use Nette\Database\Drivers\Engine; +use Nette\Database\TypeConverter; /** - * Supplemental ODBC database driver. + * Microsoft ODBC database platform. */ -class OdbcDriver implements Nette\Database\Driver +class ODBCEngine implements Engine { - public function initialize(Nette\Database\Connection $connection, array $options): void + public function isSupported(string $feature): bool { + return $feature === self::SupportSubselect; } - public function convertException(\PDOException $e): Nette\Database\DriverException + public function classifyException(Nette\Database\DriverException $e): ?string { - return Nette\Database\DriverException::from($e); + return null; } /********************* SQL ****************d*g**/ - public function delimite(string $name): string + public function delimit(string $name): string { return '[' . str_replace(['[', ']'], ['[[', ']]'], $name) . ']'; } @@ -49,14 +52,7 @@ public function formatDateInterval(\DateInterval $value): string } - public function formatLike(string $value, int $pos): string - { - $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); - return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); - } - - - public function applyLimit(string &$sql, ?int $limit, ?int $offset): void + public function applyLimit(string $sql, ?int $limit, ?int $offset): string { if ($offset) { throw new Nette\NotSupportedException('Offset is not supported by this database.'); @@ -70,6 +66,8 @@ public function applyLimit(string &$sql, ?int $limit, ?int $offset): void throw new Nette\InvalidArgumentException('SQL query must begin with SELECT, UPDATE or DELETE command.'); } } + + return $sql; } @@ -100,14 +98,8 @@ public function getForeignKeys(string $table): array } - public function getColumnTypes(\PDOStatement $statement): array - { - return []; - } - - - public function isSupported(string $item): bool + public function convertToPhp(mixed $value, array $meta, TypeConverter $converter): mixed { - return $item === self::SupportSubselect; + return $converter->convertToPhp($value, $meta); } } diff --git a/src/Database/Drivers/OciDriver.php b/src/Database/Drivers/Engines/OracleEngine.php similarity index 55% rename from src/Database/Drivers/OciDriver.php rename to src/Database/Drivers/Engines/OracleEngine.php index 6f5fc7e57..ead792035 100644 --- a/src/Database/Drivers/OciDriver.php +++ b/src/Database/Drivers/Engines/OracleEngine.php @@ -7,49 +7,50 @@ declare(strict_types=1); -namespace Nette\Database\Drivers; +namespace Nette\Database\Drivers\Engines; use Nette; +use Nette\Database\Drivers\Connection; +use Nette\Database\Drivers\Engine; +use Nette\Database\TypeConverter; /** - * Supplemental Oracle database driver. + * Oracle database platform. */ -class OciDriver implements Nette\Database\Driver +class OracleEngine implements Engine { - private Nette\Database\Connection $connection; - private string $fmtDateTime; + public string $formatDateTime = 'U'; - public function initialize(Nette\Database\Connection $connection, array $options): void - { - $this->connection = $connection; - $this->fmtDateTime = $options['formatDateTime'] ?? 'U'; + public function __construct( + private readonly Connection $connection, + ) { } - public function convertException(\PDOException $e): Nette\Database\DriverException + public function isSupported(string $feature): bool { - $code = $e->errorInfo[1] ?? null; - if (in_array($code, [1, 2299, 38911], strict: true)) { - return Nette\Database\UniqueConstraintViolationException::from($e); - - } elseif (in_array($code, [1400], strict: true)) { - return Nette\Database\NotNullConstraintViolationException::from($e); + return $feature === self::SupportSequence || $feature === self::SupportSubselect; + } - } elseif (in_array($code, [2266, 2291, 2292], strict: true)) { - return Nette\Database\ForeignKeyConstraintViolationException::from($e); - } else { - return Nette\Database\DriverException::from($e); - } + public function classifyException(Nette\Database\DriverException $e): ?string + { + $code = $e->getCode(); + return match (true) { + in_array($code, [1, 2299, 38911], strict: true) => Nette\Database\UniqueConstraintViolationException::class, + in_array($code, [1400], strict: true) => Nette\Database\NotNullConstraintViolationException::class, + in_array($code, [2266, 2291, 2292], strict: true) => Nette\Database\ForeignKeyConstraintViolationException::class, + default => null, + }; } /********************* SQL ****************d*g**/ - public function delimite(string $name): string + public function delimit(string $name): string { // @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm return '"' . str_replace('"', '""', $name) . '"'; @@ -58,7 +59,7 @@ public function delimite(string $name): string public function formatDateTime(\DateTimeInterface $value): string { - return $value->format($this->fmtDateTime); + return $value->format($this->formatDateTime); } @@ -68,13 +69,7 @@ public function formatDateInterval(\DateInterval $value): string } - public function formatLike(string $value, int $pos): string - { - throw new Nette\NotImplementedException; - } - - - public function applyLimit(string &$sql, ?int $limit, ?int $offset): void + public function applyLimit(string $sql, ?int $limit, ?int $offset): string { if ($limit < 0 || $offset < 0) { throw new Nette\InvalidArgumentException('Negative offset or limit.'); @@ -88,6 +83,8 @@ public function applyLimit(string &$sql, ?int $limit, ?int $offset): void } elseif ($limit !== null) { $sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit; } + + return $sql; } @@ -99,6 +96,7 @@ public function getTables(): array $tables = []; $rows = $this->connection->query('SELECT * FROM cat'); while ($row = $rows->fetch()) { + $row = array_values($row); if ($row[1] === 'TABLE' || $row[1] === 'VIEW') { $tables[] = [ 'name' => $row[0], @@ -129,14 +127,8 @@ public function getForeignKeys(string $table): array } - public function getColumnTypes(\PDOStatement $statement): array - { - return []; - } - - - public function isSupported(string $item): bool + public function convertToPhp(mixed $value, array $meta, TypeConverter $converter): mixed { - return $item === self::SupportSequence || $item === self::SupportSubselect; + return $converter->convertToPhp($value, $meta); } } diff --git a/src/Database/Drivers/PgSqlDriver.php b/src/Database/Drivers/Engines/PostgreSQLEngine.php similarity index 69% rename from src/Database/Drivers/PgSqlDriver.php rename to src/Database/Drivers/Engines/PostgreSQLEngine.php index bda0fce9a..b297025ec 100644 --- a/src/Database/Drivers/PgSqlDriver.php +++ b/src/Database/Drivers/Engines/PostgreSQLEngine.php @@ -7,53 +7,48 @@ declare(strict_types=1); -namespace Nette\Database\Drivers; +namespace Nette\Database\Drivers\Engines; use Nette; +use Nette\Database\Drivers\Connection; +use Nette\Database\Drivers\Engine; +use Nette\Database\TypeConverter; /** - * Supplemental PostgreSQL database driver. + * PostgreSQL database platform. */ -class PgSqlDriver implements Nette\Database\Driver +class PostgreSQLEngine implements Engine { - private Nette\Database\Connection $connection; + public function __construct( + private readonly Connection $connection, + ) { + } - public function initialize(Nette\Database\Connection $connection, array $options): void + public function isSupported(string $feature): bool { - $this->connection = $connection; + return $feature === self::SupportSequence || $feature === self::SupportSubselect || $feature === self::SupportSchema; } - public function convertException(\PDOException $e): Nette\Database\DriverException + public function classifyException(Nette\Database\DriverException $e): ?string { - $code = $e->errorInfo[0] ?? null; - if ($code === '0A000' && str_contains($e->getMessage(), 'truncate')) { - return Nette\Database\ForeignKeyConstraintViolationException::from($e); - - } elseif ($code === '23502') { - return Nette\Database\NotNullConstraintViolationException::from($e); - - } elseif ($code === '23503') { - return Nette\Database\ForeignKeyConstraintViolationException::from($e); - - } elseif ($code === '23505') { - return Nette\Database\UniqueConstraintViolationException::from($e); - - } elseif ($code === '08006') { - return Nette\Database\ConnectionException::from($e); - - } else { - return Nette\Database\DriverException::from($e); - } + return match ($e->getSqlState()) { + '0A000' => str_contains($e->getMessage(), 'truncate') ? Nette\Database\ForeignKeyConstraintViolationException::class : null, + '23502' => Nette\Database\NotNullConstraintViolationException::class, + '23503' => Nette\Database\ForeignKeyConstraintViolationException::class, + '23505' => Nette\Database\UniqueConstraintViolationException::class, + '08006' => Nette\Database\ConnectionException::class, + default => null, + }; } /********************* SQL ****************d*g**/ - public function delimite(string $name): string + public function delimit(string $name): string { // @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS return '"' . str_replace('"', '""', $name) . '"'; @@ -72,16 +67,7 @@ public function formatDateInterval(\DateInterval $value): string } - public function formatLike(string $value, int $pos): string - { - $bs = substr($this->connection->quote('\\'), 1, -1); // standard_conforming_strings = on/off - $value = substr($this->connection->quote($value), 1, -1); - $value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']); - return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); - } - - - public function applyLimit(string &$sql, ?int $limit, ?int $offset): void + public function applyLimit(string $sql, ?int $limit, ?int $offset): string { if ($limit < 0 || $offset < 0) { throw new Nette\InvalidArgumentException('Negative offset or limit.'); @@ -94,6 +80,8 @@ public function applyLimit(string &$sql, ?int $limit, ?int $offset): void if ($offset) { $sql .= ' OFFSET ' . $offset; } + + return $sql; } @@ -119,7 +107,7 @@ public function getTables(): array X); while ($row = $rows->fetch()) { - $tables[] = (array) $row; + $tables[] = $row; } return $tables; @@ -133,16 +121,20 @@ public function getColumns(string $table): array SELECT a.attname::varchar AS name, c.relname::varchar AS table, - upper(t.typname) AS nativetype, + upper(t.typname) AS "nativeType", CASE WHEN a.atttypid IN (1700, 1231) THEN ((a.atttypmod - 4) >> 16) & 65535 -- precision for numeric/decimal WHEN a.atttypmod > 0 THEN a.atttypmod - 4 -- length for varchar etc. WHEN t.typlen > 0 THEN t.typlen -- length for fixed-length types ELSE NULL END AS size, + CASE + WHEN a.atttypid IN (1700, 1231) THEN (a.atttypmod - 4) & 65535 + ELSE null + END AS scale, NOT (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS nullable, pg_catalog.pg_get_expr(ad.adbin, 'pg_catalog.pg_attrdef'::regclass)::varchar AS default, - coalesce(co.contype = 'p' AND (seq.relname IS NOT NULL OR strpos(pg_catalog.pg_get_expr(ad.adbin, ad.adrelid), 'nextval') = 1), FALSE) AS autoincrement, + coalesce(co.contype = 'p' AND (seq.relname IS NOT NULL OR strpos(pg_catalog.pg_get_expr(ad.adbin, ad.adrelid), 'nextval') = 1), FALSE) AS "autoIncrement", coalesce(co.contype = 'p', FALSE) AS primary, coalesce(seq.relname, substring(pg_catalog.pg_get_expr(ad.adbin, 'pg_catalog.pg_attrdef'::regclass) from 'nextval[(]''"?([^''"]+)')) AS sequence FROM @@ -160,10 +152,10 @@ public function getColumns(string $table): array AND NOT a.attisdropped ORDER BY a.attnum - X, $this->delimiteFQN($table)); + X, [$this->delimitFQN($table)]); while ($row = $rows->fetch()) { - $column = (array) $row; + $column = $row; $column['vendor'] = $column; unset($column['sequence']); @@ -191,7 +183,7 @@ public function getIndexes(string $table): array WHERE c1.relkind IN ('r', 'p') AND c1.oid = ?::regclass - X, $this->delimiteFQN($table)); + X, [$this->delimitFQN($table)]); while ($row = $rows->fetch()) { $id = $row['name']; @@ -226,35 +218,33 @@ public function getForeignKeys(string $table): array co.contype = 'f' AND cl.oid = ?::regclass AND nf.nspname = ANY (pg_catalog.current_schemas(FALSE)) - X, $this->delimiteFQN($table)); + X, [$this->delimitFQN($table)]); while ($row = $rows->fetch()) { - $keys[] = (array) $row; + $id = $row['name']; + $keys[$id]['name'] = $id; + $keys[$id]['local'][] = $row['local']; + $keys[$id]['table'] = $row['table']; + $keys[$id]['foreign'][] = $row['foreign']; } - return $keys; - } - - public function getColumnTypes(\PDOStatement $statement): array - { - static $cache; - $item = &$cache[$statement->queryString]; - $item ??= Nette\Database\Helpers::detectTypes($statement); - return $item; + return array_values($keys); } - public function isSupported(string $item): bool + public function convertToPhp(mixed $value, array $meta, TypeConverter $converter): mixed { - return $item === self::SupportSequence || $item === self::SupportSubselect || $item === self::SupportSchema; + return $meta['nativeType'] === 'bool' + ? ($value && $value !== 'f' && $value !== 'F') + : $converter->convertToPhp($value, $meta); } /** * Converts: schema.name => "schema"."name" */ - private function delimiteFQN(string $name): string + private function delimitFQN(string $name): string { - return implode('.', array_map([$this, 'delimite'], explode('.', $name))); + return implode('.', array_map([$this, 'delimit'], explode('.', $name))); } } diff --git a/src/Database/Drivers/SqlsrvDriver.php b/src/Database/Drivers/Engines/SQLServerEngine.php similarity index 70% rename from src/Database/Drivers/SqlsrvDriver.php rename to src/Database/Drivers/Engines/SQLServerEngine.php index f5e45b360..89f3489d2 100644 --- a/src/Database/Drivers/SqlsrvDriver.php +++ b/src/Database/Drivers/Engines/SQLServerEngine.php @@ -7,35 +7,41 @@ declare(strict_types=1); -namespace Nette\Database\Drivers; +namespace Nette\Database\Drivers\Engines; use Nette; +use Nette\Database\Drivers\Connection; +use Nette\Database\Drivers\Engine; +use Nette\Database\TypeConverter; /** - * Supplemental SQL Server 2005 and later database driver. + * Microsoft SQL Server database platform. */ -class SqlsrvDriver implements Nette\Database\Driver +class SQLServerEngine implements Engine { - private Nette\Database\Connection $connection; + public function __construct( + private readonly Connection $connection, + ) { + } - public function initialize(Nette\Database\Connection $connection, array $options): void + public function isSupported(string $feature): bool { - $this->connection = $connection; + return $feature === self::SupportSubselect; } - public function convertException(\PDOException $e): Nette\Database\DriverException + public function classifyException(Nette\Database\DriverException $e): ?string { - return Nette\Database\DriverException::from($e); + return null; } /********************* SQL ****************d*g**/ - public function delimite(string $name): string + public function delimit(string $name): string { /** @see https://msdn.microsoft.com/en-us/library/ms176027.aspx */ return '[' . str_replace(']', ']]', $name) . ']'; @@ -55,15 +61,7 @@ public function formatDateInterval(\DateInterval $value): string } - public function formatLike(string $value, int $pos): string - { - /** @see https://msdn.microsoft.com/en-us/library/ms179859.aspx */ - $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']); - return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); - } - - - public function applyLimit(string &$sql, ?int $limit, ?int $offset): void + public function applyLimit(string $sql, ?int $limit, ?int $offset): string { if ($limit < 0 || $offset < 0) { throw new Nette\InvalidArgumentException('Negative offset or limit.'); @@ -73,6 +71,8 @@ public function applyLimit(string &$sql, ?int $limit, ?int $offset): void $sql .= ' OFFSET ' . (int) $offset . ' ROWS ' . 'FETCH NEXT ' . (int) $limit . ' ROWS ONLY'; } + + return $sql; } @@ -113,15 +113,16 @@ public function getColumns(string $table): array SELECT c.name AS name, o.name AS [table], - UPPER(t.name) AS nativetype, + UPPER(t.name) AS nativeType, CASE WHEN c.precision <> 0 THEN c.precision WHEN c.max_length <> -1 THEN c.max_length ELSE NULL END AS size, + c.scale AS scale, c.is_nullable AS nullable, OBJECT_DEFINITION(c.default_object_id) AS [default], - c.is_identity AS autoincrement, + c.is_identity AS autoIncrement, CASE WHEN i.index_id IS NULL THEN 0 ELSE 1 @@ -135,13 +136,14 @@ public function getColumns(string $table): array WHERE o.type IN ('U', 'V') AND o.name = ? - X, $table); + X, [$table]); while ($row = $rows->fetch()) { - $row = (array) $row; $row['vendor'] = $row; + $row['size'] = $row['size'] ? (int) $row['size'] : null; + $row['scale'] = $row['scale'] ? (int) $row['scale'] : null; $row['nullable'] = (bool) $row['nullable']; - $row['autoincrement'] = (bool) $row['autoincrement']; + $row['autoIncrement'] = (bool) $row['autoIncrement']; $row['primary'] = (bool) $row['primary']; $columns[] = $row; @@ -173,7 +175,7 @@ public function getIndexes(string $table): array ORDER BY i.index_id, ic.index_column_id - X, $table); + X, [$table]); while ($row = $rows->fetch()) { $id = $row['name']; @@ -206,38 +208,26 @@ public function getForeignKeys(string $table): array JOIN sys.columns cf ON fkc.referenced_object_id = cf.object_id AND fkc.referenced_column_id = cf.column_id WHERE tl.name = ? - X, $table); + X, [$table]); while ($row = $rows->fetch()) { - $keys[$row['name']] = (array) $row; + $id = $row['name']; + $keys[$id]['name'] = $id; + $keys[$id]['local'][] = $row['local']; + $keys[$id]['table'] = $row['table']; + $keys[$id]['foreign'][] = $row['foreign']; } return array_values($keys); } - public function getColumnTypes(\PDOStatement $statement): array - { - $types = []; - $count = $statement->columnCount(); - for ($col = 0; $col < $count; $col++) { - $meta = $statement->getColumnMeta($col); - if ( - isset($meta['sqlsrv:decl_type']) - && $meta['sqlsrv:decl_type'] !== 'timestamp' - ) { // timestamp does not mean time in sqlsrv - $types[$meta['name']] = Nette\Database\Helpers::detectType($meta['sqlsrv:decl_type']); - } elseif (isset($meta['native_type'])) { - $types[$meta['name']] = Nette\Database\Helpers::detectType($meta['native_type']); - } - } - - return $types; - } - - - public function isSupported(string $item): bool + public function convertToPhp(mixed $value, array $meta, TypeConverter $converter): mixed { - return $item === self::SupportSubselect; + return match ($meta['nativeType']) { + 'timestamp' => $value, // timestamp does not mean time in sqlsrv + 'bit' => $converter->convertBoolean ? $converter->toBool($value) : $converter->toInt($value), + default => $converter->convertToPhp($value, $meta), + }; } } diff --git a/src/Database/Drivers/SqliteDriver.php b/src/Database/Drivers/Engines/SQLiteEngine.php similarity index 57% rename from src/Database/Drivers/SqliteDriver.php rename to src/Database/Drivers/Engines/SQLiteEngine.php index 801329fb7..52e582a46 100644 --- a/src/Database/Drivers/SqliteDriver.php +++ b/src/Database/Drivers/Engines/SQLiteEngine.php @@ -7,55 +7,62 @@ declare(strict_types=1); -namespace Nette\Database\Drivers; +namespace Nette\Database\Drivers\Engines; use Nette; +use Nette\Database\DateTime; +use Nette\Database\Drivers\Connection; +use Nette\Database\Drivers\Engine; +use Nette\Database\TypeConverter; /** - * Supplemental SQLite3 database driver. + * SQLite database platform. */ -class SqliteDriver implements Nette\Database\Driver +class SQLiteEngine implements Engine { - private Nette\Database\Connection $connection; - private string $fmtDateTime; + public string $formatDateTime = 'U'; - public function initialize(Nette\Database\Connection $connection, array $options): void + public function __construct( + private readonly Connection $connection, + ) { + } + + + public function isSupported(string $feature): bool { - $this->connection = $connection; - $this->fmtDateTime = $options['formatDateTime'] ?? 'U'; + return $feature === self::SupportMultiInsertAsSelect || $feature === self::SupportSubselect || $feature === self::SupportMultiColumnAsOrCondition; } - public function convertException(\PDOException $e): Nette\Database\DriverException + public function classifyException(Nette\Database\DriverException $e): ?string { - $code = $e->errorInfo[1] ?? null; - $msg = $e->getMessage(); - if ($code !== 19) { - return Nette\Database\DriverException::from($e); + $message = $e->getMessage(); + if ($e->getCode() !== 19) { + return null; } elseif ( - str_contains($msg, 'must be unique') - || str_contains($msg, 'is not unique') - || str_contains($msg, 'UNIQUE constraint failed') + str_contains($message, 'must be unique') + || str_contains($message, 'is not unique') + || str_contains($message, 'UNIQUE constraint failed') ) { - return Nette\Database\UniqueConstraintViolationException::from($e); + return Nette\Database\UniqueConstraintViolationException::class; } elseif ( - str_contains($msg, 'may not be null') - || str_contains($msg, 'NOT NULL constraint failed') + str_contains($message, 'may not be null') + || str_contains($message, 'NOT NULL constraint failed') ) { - return Nette\Database\NotNullConstraintViolationException::from($e); + return Nette\Database\NotNullConstraintViolationException::class; } elseif ( - str_contains($msg, 'foreign key constraint failed') - || str_contains($msg, 'FOREIGN KEY constraint failed') + str_contains($message, 'foreign key constraint failed') + || str_contains($message, 'FOREIGN KEY constraint failed') ) { - return Nette\Database\ForeignKeyConstraintViolationException::from($e); + return Nette\Database\ForeignKeyConstraintViolationException::class; } else { - return Nette\Database\ConstraintViolationException::from($e); + return Nette\Database\ConstraintViolationException::class; } } @@ -63,7 +70,7 @@ public function convertException(\PDOException $e): Nette\Database\DriverExcepti /********************* SQL ****************d*g**/ - public function delimite(string $name): string + public function delimit(string $name): string { return '[' . strtr($name, '[]', ' ') . ']'; } @@ -71,7 +78,7 @@ public function delimite(string $name): string public function formatDateTime(\DateTimeInterface $value): string { - return $value->format($this->fmtDateTime); + return $value->format($this->formatDateTime); } @@ -81,14 +88,7 @@ public function formatDateInterval(\DateInterval $value): string } - public function formatLike(string $value, int $pos): string - { - $value = addcslashes(substr($this->connection->quote($value), 1, -1), '%_\\'); - return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'"; - } - - - public function applyLimit(string &$sql, ?int $limit, ?int $offset): void + public function applyLimit(string $sql, ?int $limit, ?int $offset): string { if ($limit < 0 || $offset < 0) { throw new Nette\InvalidArgumentException('Negative offset or limit.'); @@ -97,6 +97,8 @@ public function applyLimit(string &$sql, ?int $limit, ?int $offset): void $sql .= ' LIMIT ' . ($limit ?? '-1') . ($offset ? ' OFFSET ' . $offset : ''); } + + return $sql; } @@ -138,10 +140,10 @@ public function getColumns(string $table): array SELECT sql FROM sqlite_temp_master WHERE type = 'table' AND name = ? - X, $table, $table)->fetch(); + X, [$table, $table])->fetch(); $columns = []; - $rows = $this->connection->query("PRAGMA table_info({$this->delimite($table)})"); + $rows = $this->connection->query("PRAGMA table_info({$this->delimit($table)})"); while ($row = $rows->fetch()) { $column = $row['name']; $pattern = "/(\"$column\"|`$column`|\\[$column\\]|$column)\\s+[^,]+\\s+PRIMARY\\s+KEY\\s+AUTOINCREMENT/Ui"; @@ -149,13 +151,14 @@ public function getColumns(string $table): array $columns[] = [ 'name' => $column, 'table' => $table, - 'nativetype' => strtoupper($typeInfo['type']), - 'size' => $typeInfo['length'], + 'nativeType' => strtoupper($typeInfo['type']), + 'size' => $typeInfo['size'], + 'scale' => $typeInfo['scale'], 'nullable' => $row['notnull'] == 0, 'default' => $row['dflt_value'], - 'autoincrement' => $createSql && preg_match($pattern, $createSql['sql']), + 'autoIncrement' => $createSql && preg_match($pattern, $createSql['sql']), 'primary' => $row['pk'] > 0, - 'vendor' => (array) $row, + 'vendor' => $row, ]; } @@ -166,7 +169,7 @@ public function getColumns(string $table): array public function getIndexes(string $table): array { $indexes = []; - $rows = $this->connection->query("PRAGMA index_list({$this->delimite($table)})"); + $rows = $this->connection->query("PRAGMA index_list({$this->delimit($table)})"); while ($row = $rows->fetch()) { $id = $row['name']; $indexes[$id]['name'] = $id; @@ -175,7 +178,7 @@ public function getIndexes(string $table): array } foreach ($indexes as $index => $values) { - $res = $this->connection->query("PRAGMA index_info({$this->delimite($index)})"); + $res = $this->connection->query("PRAGMA index_info({$this->delimit($index)})"); while ($row = $res->fetch()) { $indexes[$index]['columns'][] = $row['name']; } @@ -213,40 +216,26 @@ public function getIndexes(string $table): array public function getForeignKeys(string $table): array { $keys = []; - $rows = $this->connection->query("PRAGMA foreign_key_list({$this->delimite($table)})"); + $rows = $this->connection->query("PRAGMA foreign_key_list({$this->delimit($table)})"); while ($row = $rows->fetch()) { $id = $row['id']; $keys[$id]['name'] = $id; - $keys[$id]['local'] = $row['from']; + $keys[$id]['local'][] = $row['from']; $keys[$id]['table'] = $row['table']; - $keys[$id]['foreign'] = $row['to']; - } - - return array_values($keys); - } - - - public function getColumnTypes(\PDOStatement $statement): array - { - $types = []; - $count = $statement->columnCount(); - for ($col = 0; $col < $count; $col++) { - $meta = $statement->getColumnMeta($col); - if (isset($meta['sqlite:decl_type'])) { - $types[$meta['name']] = $this->fmtDateTime === 'U' && in_array($meta['sqlite:decl_type'], ['DATE', 'DATETIME'], strict: true) - ? Nette\Database\IStructure::FIELD_UNIX_TIMESTAMP - : Nette\Database\Helpers::detectType($meta['sqlite:decl_type']); - } elseif (isset($meta['native_type'])) { - $types[$meta['name']] = Nette\Database\Helpers::detectType($meta['native_type']); + $keys[$id]['foreign'][] = $row['to']; + if ($keys[$id]['foreign'][0] == null) { + $keys[$id]['foreign'] = []; } } - return $types; + return array_values($keys); } - public function isSupported(string $item): bool + public function convertToPhp(mixed $value, array $meta, TypeConverter $converter): mixed { - return $item === self::SupportMultiInsertAsSelect || $item === self::SupportSubselect || $item === self::SupportMultiColumnAsOrCond; + return $converter->convertDateTime && in_array($meta['nativeType'], ['DATE', 'DATETIME'], true) + ? (is_int($value) ? (new DateTime)->setTimestamp($value) : new DateTime($value)) + : $converter->convertToPhp($value, $meta); } } diff --git a/src/Database/Drivers/MySqlDriver.php b/src/Database/Drivers/MySqlDriver.php deleted file mode 100644 index cca8735b4..000000000 --- a/src/Database/Drivers/MySqlDriver.php +++ /dev/null @@ -1,224 +0,0 @@ - character encoding to set (default is utf8mb4) - * - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html - * - convertBoolean => converts INT(1) to boolean - */ - public function initialize(Nette\Database\Connection $connection, array $options): void - { - $this->connection = $connection; - $charset = $options['charset'] ?? 'utf8mb4'; - if ($charset) { - $connection->query('SET NAMES ?', $charset); - } - - if (isset($options['sqlmode'])) { - $connection->query('SET sql_mode=?', $options['sqlmode']); - } - - $this->convertBoolean = (bool) ($options['convertBoolean'] ?? $options['supportBooleans'] ?? false); - } - - - public function convertException(\PDOException $e): Nette\Database\DriverException - { - $code = $e->errorInfo[1] ?? null; - if (in_array($code, [1216, 1217, 1451, 1452, 1701], strict: true)) { - return Nette\Database\ForeignKeyConstraintViolationException::from($e); - - } elseif (in_array($code, [1062, 1557, 1569, 1586], strict: true)) { - return Nette\Database\UniqueConstraintViolationException::from($e); - - } elseif ($code >= 2001 && $code <= 2028) { - return Nette\Database\ConnectionException::from($e); - - } elseif (in_array($code, [1048, 1121, 1138, 1171, 1252, 1263, 1566], strict: true)) { - return Nette\Database\NotNullConstraintViolationException::from($e); - - } else { - return Nette\Database\DriverException::from($e); - } - } - - - /********************* SQL ****************d*g**/ - - - public function delimite(string $name): string - { - // @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html - return '`' . str_replace('`', '``', $name) . '`'; - } - - - public function formatDateTime(\DateTimeInterface $value): string - { - return $value->format("'Y-m-d H:i:s'"); - } - - - public function formatDateInterval(\DateInterval $value): string - { - return $value->format("'%r%h:%I:%S'"); - } - - - public function formatLike(string $value, int $pos): string - { - $value = str_replace('\\', '\\\\', $value); - $value = addcslashes(substr($this->connection->quote($value), 1, -1), '%_'); - return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); - } - - - public function applyLimit(string &$sql, ?int $limit, ?int $offset): void - { - if ($limit < 0 || $offset < 0) { - throw new Nette\InvalidArgumentException('Negative offset or limit.'); - - } elseif ($limit !== null || $offset) { - // see http://dev.mysql.com/doc/refman/5.0/en/select.html - $sql .= ' LIMIT ' . ($limit ?? '18446744073709551615') - . ($offset ? ' OFFSET ' . $offset : ''); - } - } - - - /********************* reflection ****************d*g**/ - - - public function getTables(): array - { - $tables = []; - $rows = $this->connection->query('SHOW FULL TABLES'); - while ($row = $rows->fetch()) { - $tables[] = [ - 'name' => $row[0], - 'view' => ($row[1] ?? null) === 'VIEW', - ]; - } - - return $tables; - } - - - public function getColumns(string $table): array - { - $columns = []; - $rows = $this->connection->query('SHOW FULL COLUMNS FROM ' . $this->delimite($table)); - while ($row = $rows->fetch()) { - $row = array_change_key_case((array) $row, CASE_LOWER); - $typeInfo = Nette\Database\Helpers::parseColumnType($row['type']); - $columns[] = [ - 'name' => $row['field'], - 'table' => $table, - 'nativetype' => strtoupper($typeInfo['type']), - 'size' => $typeInfo['length'], - 'nullable' => $row['null'] === 'YES', - 'default' => $row['default'], - 'autoincrement' => $row['extra'] === 'auto_increment', - 'primary' => $row['key'] === 'PRI', - 'vendor' => $row, - ]; - } - - return $columns; - } - - - public function getIndexes(string $table): array - { - $indexes = []; - $rows = $this->connection->query('SHOW INDEX FROM ' . $this->delimite($table)); - while ($row = $rows->fetch()) { - $id = $row['Key_name']; - $indexes[$id]['name'] = $id; - $indexes[$id]['unique'] = !$row['Non_unique']; - $indexes[$id]['primary'] = $row['Key_name'] === 'PRIMARY'; - $indexes[$id]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name']; - } - - return array_values($indexes); - } - - - public function getForeignKeys(string $table): array - { - $keys = []; - $rows = $this->connection->query(<<<'X' - SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME - FROM information_schema.KEY_COLUMN_USAGE - WHERE TABLE_SCHEMA = DATABASE() - AND REFERENCED_TABLE_NAME IS NOT NULL - AND TABLE_NAME = ? - X, $table); - - $id = 0; - while ($row = $rows->fetch()) { - $keys[$id]['name'] = $row['CONSTRAINT_NAME']; - $keys[$id]['local'] = $row['COLUMN_NAME']; - $keys[$id]['table'] = $row['REFERENCED_TABLE_NAME']; - $keys[$id++]['foreign'] = $row['REFERENCED_COLUMN_NAME']; - } - - return array_values($keys); - } - - - public function getColumnTypes(\PDOStatement $statement): array - { - $types = []; - $count = $statement->columnCount(); - for ($col = 0; $col < $count; $col++) { - $meta = $statement->getColumnMeta($col); - if (isset($meta['native_type'])) { - $types[$meta['name']] = match (true) { - $meta['native_type'] === 'NEWDECIMAL' && $meta['precision'] === 0 => Nette\Database\IStructure::FIELD_INTEGER, - $meta['native_type'] === 'TINY' && $meta['len'] === 1 && $this->convertBoolean => Nette\Database\IStructure::FIELD_BOOL, - $meta['native_type'] === 'TIME' => Nette\Database\IStructure::FIELD_TIME_INTERVAL, - default => Nette\Database\Helpers::detectType($meta['native_type']), - }; - } - } - - return $types; - } - - - public function isSupported(string $item): bool - { - // MULTI_COLUMN_AS_OR_COND due to mysql bugs: - // - http://bugs.mysql.com/bug.php?id=31188 - // - http://bugs.mysql.com/bug.php?id=35819 - // and more. - return $item === self::SupportSelectUngroupedColumns || $item === self::SupportMultiColumnAsOrCond; - } -} diff --git a/src/Database/Drivers/PDO/Connection.php b/src/Database/Drivers/PDO/Connection.php new file mode 100644 index 000000000..540b725d5 --- /dev/null +++ b/src/Database/Drivers/PDO/Connection.php @@ -0,0 +1,121 @@ + PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT, 'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL]; + + try { + $statement = $this->pdo->prepare($sql); + foreach ($params as $key => $value) { + $statement->bindValue(is_int($key) ? $key + 1 : $key, $value, $types[gettype($value)] ?? PDO::PARAM_STR); + } + $statement->execute(); + return new ($this->resultClass)($statement, $this); + + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e, new SqlLiteral($sql, $params))); + } + } + + + public function execute(string $sql): int + { + try { + return $this->pdo->exec($sql); + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e, new SqlLiteral($sql))); + } + } + + + public function beginTransaction(): void + { + try { + $this->pdo->beginTransaction(); + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e)); + } + } + + + public function commit(): void + { + try { + $this->pdo->commit(); + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e)); + } + } + + + public function rollBack(): void + { + try { + $this->pdo->rollBack(); + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e)); + } + } + + + public function getInsertId(?string $sequence = null): int|string + { + try { + $id = $this->pdo->lastInsertId($sequence); + if ($id === '0' || $id === '' || $id === false) { + throw new DriverException('Cannot retrieve last generated ID.'); + } + $int = (int) $id; + return $id === (string) $int ? $int : $id; + + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e)); + } + } + + + public function quote(string $string): string + { + return $this->pdo->quote($string); + } + + + public function getServerVersion(): string + { + return $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION); + } + + + public function getNativeConnection(): PDO + { + return $this->pdo; + } +} diff --git a/src/Database/Drivers/PDO/Driver.php b/src/Database/Drivers/PDO/Driver.php new file mode 100644 index 000000000..21b2f1139 --- /dev/null +++ b/src/Database/Drivers/PDO/Driver.php @@ -0,0 +1,48 @@ +dsn, $this->username, $this->password, $this->options); + } catch (PDOException $e) { + throw new DriverException(...self::exceptionArgs($e)); + } + return new Connection($pdo); + } + + + public static function exceptionArgs(PDOException $e, ?SqlLiteral $query = null): array + { + return [$e->getMessage(), $e->errorInfo[0] ?? null, $e->errorInfo[1] ?? 0, $query, $e]; + } +} diff --git a/src/Database/Drivers/PDO/MSSQL/Driver.php b/src/Database/Drivers/PDO/MSSQL/Driver.php new file mode 100644 index 000000000..4c3acd205 --- /dev/null +++ b/src/Database/Drivers/PDO/MSSQL/Driver.php @@ -0,0 +1,25 @@ +charset) { + $connection->execute('SET NAMES ' . $connection->quote($this->charset)); + } + if ($this->sqlmode) { + $connection->execute('SET sql_mode=' . $connection->quote($this->sqlmode)); + } + return $connection; + } + + + public function createEngine(Drivers\Connection $connection): MySQLEngine + { + return new MySQLEngine($connection); + } +} diff --git a/src/Database/Drivers/PDO/OCI/Driver.php b/src/Database/Drivers/PDO/OCI/Driver.php new file mode 100644 index 000000000..606f7df6a --- /dev/null +++ b/src/Database/Drivers/PDO/OCI/Driver.php @@ -0,0 +1,40 @@ +formatDateTime) { + $engine->formatDateTime = $this->formatDateTime; + } + return $engine; + } +} diff --git a/src/Database/Drivers/PDO/ODBC/Driver.php b/src/Database/Drivers/PDO/ODBC/Driver.php new file mode 100644 index 000000000..47371287e --- /dev/null +++ b/src/Database/Drivers/PDO/ODBC/Driver.php @@ -0,0 +1,25 @@ +resultClass = Result::class; + return $connection; + } + + + public function createEngine(Drivers\Connection $connection): PostgreSQLEngine + { + return new PostgreSQLEngine($connection); + } +} diff --git a/src/Database/Drivers/PDO/PgSQL/Result.php b/src/Database/Drivers/PDO/PgSQL/Result.php new file mode 100644 index 000000000..482161829 --- /dev/null +++ b/src/Database/Drivers/PDO/PgSQL/Result.php @@ -0,0 +1,24 @@ +result->queryString] ??= parent::collectColumnsInfo(); + } +} diff --git a/src/Database/Drivers/PDO/Result.php b/src/Database/Drivers/PDO/Result.php new file mode 100644 index 000000000..f17b88cef --- /dev/null +++ b/src/Database/Drivers/PDO/Result.php @@ -0,0 +1,107 @@ +fetchList(); + if (!$row) { + return null; + } + + $res = []; + foreach ($this->getColumnsInfo() as $i => $meta) { + $res[$meta['name']] = $row[$i]; + } + return $res; + } + + + public function fetchList(): ?array + { + try { + $row = $this->result->fetch(\PDO::FETCH_NUM); + if (!$row) { + $this->free(); + return null; + } + return $row; + + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e)); + } + } + + + public function getColumnCount(): int + { + try { + return $this->result->columnCount(); + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e)); + } + } + + + public function getRowCount(): int + { + try { + return $this->result->rowCount(); + } catch (PDOException $e) { + throw new DriverException(...Driver::exceptionArgs($e)); + } + } + + + public function getColumnsInfo(): array + { + return $this->columns ??= $this->collectColumnsInfo(); + } + + + protected function collectColumnsInfo(): array + { + $res = []; + $count = $this->result->columnCount(); + for ($i = 0; $i < $count; $i++) { + $meta = $this->result->getColumnMeta($i) ?: throw new DriverException('Cannot fetch column metadata'); + $res[] = [ + 'name' => $meta['name'], + 'nativeType' => $meta[$this->connection->metaTypeKey] ?? null, + 'size' => $meta['len'], + 'scale' => $meta['precision'], + ]; + } + return $res; + } + + + public function free(): void + { + $this->result->closeCursor(); + } +} diff --git a/src/Database/Drivers/PDO/SQLSrv/Driver.php b/src/Database/Drivers/PDO/SQLSrv/Driver.php new file mode 100644 index 000000000..46909e970 --- /dev/null +++ b/src/Database/Drivers/PDO/SQLSrv/Driver.php @@ -0,0 +1,35 @@ + converts BIT to boolean + */ +class Driver extends Drivers\PDO\Driver +{ + public function connect(): Drivers\PDO\Connection + { + $connection = parent::connect(); + $connection->metaTypeKey = 'sqlsrv:decl_type'; + return $connection; + } + + + public function createEngine(Drivers\Connection $connection): SQLServerEngine + { + return new SQLServerEngine($connection); + } +} diff --git a/src/Database/Drivers/PDO/SQLite/Driver.php b/src/Database/Drivers/PDO/SQLite/Driver.php new file mode 100644 index 000000000..f73619b0c --- /dev/null +++ b/src/Database/Drivers/PDO/SQLite/Driver.php @@ -0,0 +1,48 @@ +metaTypeKey = 'sqlite:decl_type'; + return $connection; + } + + + public function createEngine(Drivers\Connection $connection): SQLiteEngine + { + $engine = new SQLiteEngine($connection); + if ($this->formatDateTime) { + $engine->formatDateTime = $this->formatDateTime; + } + return $engine; + } +} diff --git a/src/Database/Drivers/Result.php b/src/Database/Drivers/Result.php new file mode 100644 index 000000000..e17895e1f --- /dev/null +++ b/src/Database/Drivers/Result.php @@ -0,0 +1,38 @@ + + */ + function getColumnsInfo(): array; + + /** Frees the result set. */ + function free(): void; +} diff --git a/src/Database/Explorer.php b/src/Database/Explorer.php index 12cbd3f25..e500345ea 100644 --- a/src/Database/Explorer.php +++ b/src/Database/Explorer.php @@ -11,54 +11,268 @@ use JetBrains\PhpStorm\Language; use Nette; -use Nette\Database\Conventions\StaticConventions; +use Nette\Caching\Cache; +use Nette\Utils\Arrays; /** - * Database explorer. + * The central access point to Nette Database functionality. */ class Explorer { - private readonly Conventions $conventions; + private const Drivers = [ + 'pdo-mssql' => Drivers\PDO\MSSQL\Driver::class, + 'pdo-mysql' => Drivers\PDO\MySQL\Driver::class, + 'pdo-oci' => Drivers\PDO\OCI\Driver::class, + 'pdo-odbc' => Drivers\PDO\ODBC\Driver::class, + 'pdo-pgsql' => Drivers\PDO\PgSQL\Driver::class, + 'pdo-sqlite' => Drivers\PDO\SQLite\Driver::class, + 'pdo-sqlsrv' => Drivers\PDO\SQLSrv\Driver::class, + ]; + private const TypeConverterOptions = ['convertBoolean', 'convertDateTime', 'convertDecimal', 'newDateTime']; + + /** @var array Occurs after connection is established */ + public array $onConnect = []; + + /** @var array Occurs after query is executed */ + public array $onQuery = []; + private Drivers\Driver $driver; + private ?Drivers\Connection $connection = null; + private Drivers\Engine $engine; + private SqlPreprocessor $preprocessor; + private TypeConverter $typeConverter; + private ?SqlLiteral $lastQuery = null; + private int $transactionDepth = 0; + private ?Cache $cache = null; + private ?Conventions $conventions = null; + private ?Structure $structure = null; + + + public static function createFromParameters( + #[\SensitiveParameter] + ...$params, + ): self + { + $params = count($params) === 1 && is_array($params[0] ?? null) ? $params[0] : $params; + + if ($class = $params['driverClass'] ?? null) { + if (!is_subclass_of($class, Drivers\Driver::class)) { + throw new \LogicException("Driver class '$class' is not subclass of " . Drivers\Driver::class); + } + unset($params['driverClass']); + + } elseif ($driver = $params['driver'] ?? null) { + $class = self::Drivers[$driver] ?? throw new \LogicException("Unknown driver '$driver'."); + unset($params['driver']); + + } elseif ($dsn = $params['dsn'] ?? null) { + $driver = explode(':', $dsn)[0]; + $class = self::Drivers['pdo-' . $driver] ?? throw new \LogicException("Unknown PDO driver '$driver'."); + + } else { + throw new \LogicException("Missing options 'driver', 'driverClass' or 'dsn'."); + } + + $args = array_diff_key($params, array_flip(self::TypeConverterOptions)); + $explorer = new self(new $class(...$args)); + array_map(fn($opt) => isset($params[$opt]) && ($explorer->typeConverter->$opt = (bool) $params[$opt]), self::TypeConverterOptions); + return $explorer; + } + + + public static function createFromDsn( + string $dsn, + ?string $username = null, + #[\SensitiveParameter] + ?string $password = null, + array $options = [], + ): self + { + $params = compact('dsn', 'username', 'password', 'options'); + foreach ($options as $key => $value) { + if (!is_int($key) && $value !== null) { + $params[$key] = $value; + unset($params['options'][$key]); + } + } + unset($params['lazy']); + return self::createFromParameters($params); + } public function __construct( - private readonly Connection $connection, - private readonly IStructure $structure, - ?Conventions $conventions = null, - private readonly ?Nette\Caching\Storage $cacheStorage = null, + Drivers\Driver|string $driver, ) { - $this->conventions = $conventions ?: new StaticConventions; + if (is_string($driver)) { // back compatibility with version 3.x + $explorer = self::createFromDsn(...func_get_args()); + [$this->driver, $this->typeConverter] = [$explorer->driver, $explorer->typeConverter]; + } else { + $this->driver = $driver; + $this->typeConverter = new TypeConverter; + } } - public function beginTransaction(): void + public function connect(): void { - $this->connection->beginTransaction(); + if ($this->connection) { + return; + } + + try { + $this->connection = $this->driver->connect(); + } catch (DriverException $e) { + throw ConnectionException::from($e); + } + + Arrays::invoke($this->onConnect, $this); } - public function commit(): void + public function reconnect(): void { - $this->connection->commit(); + $this->disconnect(); + $this->connect(); } - public function rollBack(): void + public function disconnect(): void { - $this->connection->rollBack(); + $this->connection = null; } - public function transaction(callable $callback): mixed + /** @deprecated */ + public function getDsn(): string + { + throw new Nette\DeprecatedException(__METHOD__ . '() is deprecated.'); + } + + + /** @deprecated use getConnection()->getNativeConnection() */ + public function getPdo(): \PDO + { + trigger_error(__METHOD__ . '() is deprecated, use getConnection()->getNativeConnection()', E_USER_DEPRECATED); + return $this->getConnection()->getNativeConnection(); + } + + + public function getConnection(): Drivers\Connection { - return $this->connection->transaction(fn() => $callback($this)); + $this->connect(); + return $this->connection; } - public function getInsertId(?string $sequence = null): string + /** @deprecated use getConnection() */ + public function getSupplementalDriver(): Drivers\Connection { - return $this->connection->getInsertId($sequence); + trigger_error(__METHOD__ . '() is deprecated, use getConnection()', E_USER_DEPRECATED); + return $this->getConnection(); + } + + + public function getDatabaseEngine(): Drivers\Engine + { + return $this->engine ??= $this->driver->createEngine(new Drivers\Accessory\LazyConnection($this->getConnection(...))); + } + + + public function getServerVersion(): string + { + return $this->getConnection()->getServerVersion(); + } + + + public function getReflection(): Reflection + { + return new Reflection($this->getDatabaseEngine()); + } + + + public function getTypeConverter(): TypeConverter + { + return $this->typeConverter; + } + + + /** @deprecated */ + public function setRowNormalizer(?callable $normalizer): static + { + throw new Nette\DeprecatedException(__METHOD__ . "() is deprecated, configure 'convert*' options instead."); + } + + + public function getInsertId(?string $sequence = null): int|string + { + try { + return $this->getConnection()->getInsertId($sequence); + } catch (DriverException $e) { + throw $this->convertException($e); + } + } + + + public function quote(string $string): string + { + return $this->getConnection()->quote($string); + } + + + public function beginTransaction(): void + { + if ($this->transactionDepth !== 0) { + throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); + } + + $this->logOperation($this->getConnection()->beginTransaction(...), new SqlLiteral('BEGIN TRANSACTION')); + } + + + public function commit(): void + { + if ($this->transactionDepth !== 0) { + throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); + } + + $this->logOperation($this->getConnection()->commit(...), new SqlLiteral('COMMIT')); + } + + + public function rollBack(): void + { + if ($this->transactionDepth !== 0) { + throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); + } + + $this->logOperation($this->getConnection()->rollBack(...), new SqlLiteral('ROLLBACK')); + } + + + public function transaction(callable $callback): mixed + { + if ($this->transactionDepth === 0) { + $this->beginTransaction(); + } + + $this->transactionDepth++; + try { + $res = $callback($this); + } catch (\Throwable $e) { + $this->transactionDepth--; + if ($this->transactionDepth === 0) { + $this->rollback(); + } + + throw $e; + } + + $this->transactionDepth--; + if ($this->transactionDepth === 0) { + $this->commit(); + } + + return $res; } @@ -66,40 +280,74 @@ public function getInsertId(?string $sequence = null): string * Generates and executes SQL query. * @param literal-string $sql */ - public function query(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ResultSet + public function query(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): Result { - return $this->connection->query($sql, ...$params); + [$sql, $params] = $this->preprocess($sql, ...$params); + return $this->logOperation( + fn() => $this->connection->query($sql, $params), + $this->lastQuery = new SqlLiteral($sql, $params), + ); } /** @deprecated use query() */ - public function queryArgs(string $sql, array $params): ResultSet + public function queryArgs(string $sql, array $params): Result { - return $this->connection->query($sql, ...$params); + trigger_error(__METHOD__ . '() is deprecated, use query()', E_USER_DEPRECATED); + return $this->query($sql, ...$params); } - public function table(string $table): Table\Selection + /** + * @param literal-string $sql + * @return array{string, array} + */ + public function preprocess(string $sql, ...$params): array { - return new Table\Selection($this, $this->conventions, $table, $this->cacheStorage); + $this->connect(); + $this->preprocessor ??= new SqlPreprocessor($this); + return $params + ? $this->preprocessor->process(func_get_args()) + : [$sql, []]; } - public function getConnection(): Connection + private function logOperation(\Closure $callback, SqlLiteral $query): Result { - return $this->connection; + try { + $time = microtime(true); + $result = $callback(); + $time = microtime(true) - $time; + } catch (DriverException $e) { + $e = $this->convertException($e); + Arrays::invoke($this->onQuery, $this, $e); + throw $e; + } + + $result = new Result($this, $query, $result, $time); + Arrays::invoke($this->onQuery, $this, $result); + return $result; } - public function getStructure(): IStructure + public function getLastQuery(): ?SqlLiteral { - return $this->structure; + return $this->lastQuery; } - public function getConventions(): Conventions + /** @deprecated use getLastQuery()->getSql() */ + public function getLastQueryString(): ?string + { + trigger_error(__METHOD__ . '() is deprecated, use getLastQuery()->getSql()', E_USER_DEPRECATED); + return $this->lastQuery?->getSql(); + } + + + private function convertException(DriverException $e): DriverException { - return $this->conventions; + $class = $this->getDatabaseEngine()->classifyException($e); + return $class ? $class::from($e) : $e; } @@ -112,7 +360,7 @@ public function getConventions(): Conventions */ public function fetch(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?Row { - return $this->connection->query($sql, ...$params)->fetch(); + return $this->query($sql, ...$params)->fetch(); } @@ -122,7 +370,7 @@ public function fetch(#[Language('SQL')] string $sql, #[Language('GenericSQL')] */ public function fetchAssoc(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?array { - return $this->connection->query($sql, ...$params)->fetchAssoc(); + return $this->query($sql, ...$params)->fetchAssoc(); } @@ -132,17 +380,27 @@ public function fetchAssoc(#[Language('SQL')] string $sql, #[Language('GenericSQ */ public function fetchField(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): mixed { - return $this->connection->query($sql, ...$params)->fetchField(); + return $this->query($sql, ...$params)->fetchField(); + } + + + /** + * Shortcut for query()->fetchList() + * @param literal-string $sql + */ + public function fetchList(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?array + { + return $this->query($sql, ...$params)->fetchList(); } /** - * Shortcut for query()->fetchFields() + * Shortcut for query()->fetchList() * @param literal-string $sql */ public function fetchFields(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?array { - return $this->connection->query($sql, ...$params)->fetchFields(); + return $this->query($sql, ...$params)->fetchList(); } @@ -152,7 +410,7 @@ public function fetchFields(#[Language('SQL')] string $sql, #[Language('GenericS */ public function fetchPairs(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): array { - return $this->connection->query($sql, ...$params)->fetchPairs(); + return $this->query($sql, ...$params)->fetchPairs(); } @@ -162,7 +420,7 @@ public function fetchPairs(#[Language('SQL')] string $sql, #[Language('GenericSQ */ public function fetchAll(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): array { - return $this->connection->query($sql, ...$params)->fetchAll(); + return $this->query($sql, ...$params)->fetchAll(); } @@ -170,7 +428,57 @@ public static function literal(string $value, ...$params): SqlLiteral { return new SqlLiteral($value, $params); } + + + /********************* active row ****************d*g**/ + + + public function table(string $table): Table\Selection + { + return new Table\Selection($this, $table); + } + + + public function setCache(Cache $cache): static + { + if (isset($this->structure)) { + throw new \LogicException('Cannot set cache after structure is created.'); + } + $this->cache = $cache; + return $this; + } + + + /** @internal */ + public function getCache(): ?Cache + { + return $this->cache; + } + + + public function setConventions(Conventions $conventions): static + { + if (isset($this->conventions)) { + throw new \LogicException('Conventions are already set.'); + } + $this->conventions = $conventions; + return $this; + } + + + /** @internal */ + public function getConventions(): Conventions + { + return $this->conventions ??= new Conventions\DiscoveredConventions($this->getStructure()); + } + + + /** @internal */ + public function getStructure(): Structure + { + return $this->structure ??= new Structure($this->getDatabaseEngine(), $this->getCache()); + } } -class_exists(Context::class); +class_exists(Connection::class); diff --git a/src/Database/Helpers.php b/src/Database/Helpers.php index 7f9cf0623..588356ff0 100644 --- a/src/Database/Helpers.php +++ b/src/Database/Helpers.php @@ -24,25 +24,13 @@ class Helpers /** maximum SQL length */ public static int $maxLength = 100; - public static array $typePatterns = [ - '^_' => IStructure::FIELD_TEXT, // PostgreSQL arrays - '(TINY|SMALL|SHORT|MEDIUM|BIG|LONG)(INT)?|INT(EGER|\d+| IDENTITY| UNSIGNED)?|(SMALL|BIG|)SERIAL\d*|COUNTER|YEAR|BYTE|LONGLONG|UNSIGNED BIG INT' => IStructure::FIELD_INTEGER, - '(NEW)?DEC(IMAL)?(\(.*)?|NUMERIC|(SMALL)?MONEY|CURRENCY|NUMBER' => IStructure::FIELD_DECIMAL, - 'REAL|DOUBLE( PRECISION)?|FLOAT\d*' => IStructure::FIELD_FLOAT, - 'BOOL(EAN)?' => IStructure::FIELD_BOOL, - 'TIME' => IStructure::FIELD_TIME, - 'DATE' => IStructure::FIELD_DATE, - '(SMALL)?DATETIME(OFFSET)?\d*|TIME(STAMP.*)?' => IStructure::FIELD_DATETIME, - 'BYTEA|(TINY|MEDIUM|LONG|)BLOB|(LONG )?(VAR)?BINARY|IMAGE' => IStructure::FIELD_BINARY, - ]; - /** * Displays complete result set as HTML table for debug purposes. */ - public static function dumpResult(ResultSet $result): void + public static function dumpResult(Result $result): void { - echo "\n\n\n"; + echo "\n
" . htmlspecialchars($result->getQueryString(), ENT_IGNORE, 'UTF-8') . "
\n\n"; if (!$result->getColumnCount()) { echo "\t\n\t\t\n\t\t\n\t\n
" . htmlspecialchars($result->getQuery()->getSql(), ENT_IGNORE, 'UTF-8') . "
Affected rows:", $result->getRowCount(), "
\n"; return; @@ -87,12 +75,13 @@ public static function dumpResult(ResultSet $result): void /** * Returns syntax highlighted SQL command. */ - public static function dumpSql(string $sql, ?array $params = null, ?Connection $connection = null): string + public static function dumpSql(SqlLiteral $query, ?Explorer $explorer = null): string { $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE'; $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE'; // insert new lines + $sql = $query->getSql(); $sql = " $sql "; $sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql); @@ -120,14 +109,14 @@ public static function dumpSql(string $sql, ?array $params = null, ?Connection $ }, $sql); // parameters - $sql = preg_replace_callback('#\?#', function () use ($params, $connection): string { + $params = $query->getParameters(); + $sql = preg_replace_callback('#\?#', function () use ($params, $explorer): string { static $i = 0; - if (!isset($params[$i])) { + $param = $params[$i++] ?? null; + if ($param === null) { return '?'; - } - $param = $params[$i++]; - if ( + } elseif ( is_string($param) && ( preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $param) @@ -139,7 +128,7 @@ public static function dumpSql(string $sql, ?array $params = null, ?Connection $ } elseif (is_string($param)) { $length = Nette\Utils\Strings::length($param); $truncated = Nette\Utils\Strings::truncate($param, self::$maxLength); - $text = htmlspecialchars($connection ? $connection->quote($truncated) : '\'' . $truncated . '\'', ENT_NOQUOTES, 'UTF-8'); + $text = htmlspecialchars($explorer ? $explorer->quote($truncated) : '\'' . $truncated . '\'', ENT_NOQUOTES, 'UTF-8'); return '' . $text . ''; } elseif (is_resource($param)) { @@ -163,93 +152,12 @@ public static function dumpSql(string $sql, ?array $params = null, ?Connection $ } - /** - * Common column type detection. - */ - public static function detectTypes(\PDOStatement $statement): array - { - $types = []; - $count = $statement->columnCount(); // driver must be meta-aware, see PHP bugs #53782, #54695 - for ($col = 0; $col < $count; $col++) { - $meta = $statement->getColumnMeta($col); - if (isset($meta['native_type'])) { - $types[$meta['name']] = self::detectType($meta['native_type']); - } - } - - return $types; - } - - - /** - * Heuristic column type detection. - * @internal - */ - public static function detectType(string $type): string - { - static $cache; - if (!isset($cache[$type])) { - $cache[$type] = 'string'; - foreach (self::$typePatterns as $s => $val) { - if (preg_match("#^($s)$#i", $type)) { - return $cache[$type] = $val; - } - } - } - - return $cache[$type]; - } - - - /** @internal */ - public static function normalizeRow( - array $row, - ResultSet $resultSet, - $dateTimeClass = Nette\Utils\DateTime::class, - ): array - { - foreach ($resultSet->getColumnTypes() as $key => $type) { - $value = $row[$key]; - if ($value === null || $value === false || $type === IStructure::FIELD_TEXT) { - // do nothing - } elseif ($type === IStructure::FIELD_INTEGER) { - $row[$key] = is_float($tmp = $value * 1) ? $value : $tmp; - - } elseif ($type === IStructure::FIELD_FLOAT || $type === IStructure::FIELD_DECIMAL) { - $row[$key] = (float) $value; - - } elseif ($type === IStructure::FIELD_BOOL) { - $row[$key] = $value && $value !== 'f' && $value !== 'F'; - - } elseif ($type === IStructure::FIELD_DATETIME || $type === IStructure::FIELD_DATE) { - $row[$key] = str_starts_with($value, '0000-00') - ? null - : new $dateTimeClass($value); - - } elseif ($type === IStructure::FIELD_TIME) { - $row[$key] = (new $dateTimeClass($value))->setDate(1, 1, 1); - - } elseif ($type === IStructure::FIELD_TIME_INTERVAL) { - preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)(\.\d+)?$#D', $value, $m); - $row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S"); - $row[$key]->f = isset($m[5]) ? (float) $m[5] : 0.0; - $row[$key]->invert = (int) (bool) $m[1]; - - } elseif ($type === IStructure::FIELD_UNIX_TIMESTAMP) { - $row[$key] = (new $dateTimeClass)->setTimestamp($value); - } - } - - return $row; - } - - /** * Import SQL dump from file - extremely fast. * @param ?array $onProgress * @return int count of commands */ - public static function loadFromFile(Connection $connection, string $file, ?callable $onProgress = null): int + public static function loadFromFile(Explorer $explorer, string $file, ?callable $onProgress = null): int { @set_time_limit(0); // @ function may be disabled @@ -262,7 +170,7 @@ public static function loadFromFile(Connection $connection, string $file, ?calla $count = $size = 0; $delimiter = ';'; $sql = ''; - $pdo = $connection->getPdo(); // native query without logging + $connection = $explorer->getConnection(); // native query without logging while (($s = fgets($handle)) !== false) { $size += strlen($s); if (!strncasecmp($s, 'DELIMITER ', 10)) { @@ -270,7 +178,7 @@ public static function loadFromFile(Connection $connection, string $file, ?calla } elseif (str_ends_with($ts = rtrim($s), $delimiter)) { $sql .= substr($ts, 0, -strlen($delimiter)); - $pdo->exec($sql); + $connection->execute($sql); $sql = ''; $count++; if ($onProgress) { @@ -282,7 +190,7 @@ public static function loadFromFile(Connection $connection, string $file, ?calla } if (rtrim($sql) !== '') { - $pdo->exec($sql); + $connection->execute($sql); $count++; if ($onProgress) { $onProgress($count, isset($stat['size']) ? 100 : null); @@ -296,20 +204,21 @@ public static function loadFromFile(Connection $connection, string $file, ?calla /** @deprecated use Nette\Bridges\DatabaseTracy\ConnectionPanel::initialize() */ public static function createDebugPanel( - Connection $connection, + Explorer $connection, bool $explain, string $name, Tracy\Bar $bar, Tracy\BlueScreen $blueScreen, ): ?ConnectionPanel { + trigger_error(__METHOD__ . '() is deprecated, use Nette\Bridges\DatabaseTracy\ConnectionPanel::initialize()', E_USER_DEPRECATED); return ConnectionPanel::initialize($connection, true, $name, $explain, $bar, $blueScreen); } /** @deprecated use Nette\Bridges\DatabaseTracy\ConnectionPanel::initialize() */ public static function initializeTracy( - Connection $connection, + Explorer $connection, bool $addBarPanel = false, string $name = '', bool $explain = true, @@ -317,6 +226,7 @@ public static function initializeTracy( ?Tracy\BlueScreen $blueScreen = null, ): ?ConnectionPanel { + trigger_error(__METHOD__ . '() is deprecated, use Nette\Bridges\DatabaseTracy\ConnectionPanel::initialize()', E_USER_DEPRECATED); return ConnectionPanel::initialize($connection, $addBarPanel, $name, $explain, $bar, $blueScreen); } @@ -365,36 +275,13 @@ public static function toPairs(array $rows, string|int|\Closure|null $key, strin } - /** - * Finds duplicate columns in select statement - */ - public static function findDuplicates(\PDOStatement $statement): string - { - $cols = []; - for ($i = 0; $i < $statement->columnCount(); $i++) { - $meta = $statement->getColumnMeta($i); - $cols[$meta['name']][] = $meta['table'] ?? ''; - } - - $duplicates = []; - foreach ($cols as $name => $tables) { - if (count($tables) > 1) { - $tables = array_filter(array_unique($tables)); - $duplicates[] = "'$name'" . ($tables ? ' (from ' . implode(', ', $tables) . ')' : ''); - } - } - - return implode(', ', $duplicates); - } - - - /** @return array{type: string, length: ?null, scale: ?null, parameters: ?string} */ + /** @return array{type: string, size: ?int, scale: ?int, parameters: ?string} */ public static function parseColumnType(string $type): array { preg_match('/^([^(]+)(?:\((?:(\d+)(?:,(\d+))?|([^)]+))\))?/', $type, $m, PREG_UNMATCHED_AS_NULL); return [ 'type' => $m[1], - 'length' => isset($m[2]) ? (int) $m[2] : null, + 'size' => isset($m[2]) ? (int) $m[2] : null, 'scale' => isset($m[3]) ? (int) $m[3] : null, 'parameters' => $m[4] ?? null, ]; diff --git a/src/Database/IRow.php b/src/Database/IRow.php deleted file mode 100644 index 8c5ab16f5..000000000 --- a/src/Database/IRow.php +++ /dev/null @@ -1,16 +0,0 @@ -tables); } @@ -67,16 +67,16 @@ private function getFullName(string $name): string /** @internal */ - public function getDriver(): Driver + public function getDatabaseEngine(): Drivers\Engine { - return $this->driver; + return $this->engine; } private function initTables(): void { $res = []; - foreach ($this->driver->getTables() as $row) { + foreach ($this->engine->getTables() as $row) { $res[$row['fullName'] ?? $row['name']] = new Table($this, $row['name'], $row['view'], $row['fullName'] ?? null); } $this->tables = $res; diff --git a/src/Database/Reflection/Column.php b/src/Database/Reflection/Column.php index 48eaa87c7..acb0043e0 100644 --- a/src/Database/Reflection/Column.php +++ b/src/Database/Reflection/Column.php @@ -21,6 +21,7 @@ public function __construct( public readonly ?Table $table = null, public readonly string $nativeType = '', public readonly ?int $size = null, + public readonly ?int $scale = null, public readonly bool $nullable = false, public readonly mixed $default = null, public readonly bool $autoIncrement = false, diff --git a/src/Database/Reflection/Table.php b/src/Database/Reflection/Table.php index d4beb4cc3..948ee2e03 100644 --- a/src/Database/Reflection/Table.php +++ b/src/Database/Reflection/Table.php @@ -48,8 +48,9 @@ public function getColumn(string $name): Column private function initColumns(): void { $res = []; - foreach ($this->reflection->getDriver()->getColumns($this->name) as $row) { - $res[$row['name']] = new Column($row['name'], $this, $row['nativetype'], $row['size'], $row['nullable'], $row['default'], $row['autoincrement'], $row['primary'], $row['vendor']); + foreach ($this->reflection->getDatabaseEngine()->getColumns($this->name) as $row) { + $row['table'] = $this; + $res[$row['name']] = new Column(...$row); } $this->columns = $res; } @@ -64,7 +65,7 @@ private function initIndexes(): void $row['primary'], is_string($row['name']) ? $row['name'] : null, ), - $this->reflection->getDriver()->getIndexes($this->name), + $this->reflection->getDatabaseEngine()->getIndexes($this->name), ); } @@ -82,12 +83,12 @@ private function initPrimaryKey(): void private function initForeignKeys(): void { $tmp = []; - foreach ($this->reflection->getDriver()->getForeignKeys($this->name) as $row) { + foreach ($this->reflection->getDatabaseEngine()->getForeignKeys($this->name) as $row) { $id = $row['name']; $foreignTable = $this->reflection->getTable($row['table']); $tmp[$id][0] = $foreignTable; - $tmp[$id][1][] = $this->getColumn($row['local']); - $tmp[$id][2][] = $foreignTable->getColumn($row['foreign']); + $tmp[$id][1] = array_map(fn($name) => $this->getColumn($name), $row['local']); + $tmp[$id][2] = array_map(fn($name) => $foreignTable->getColumn($name), $row['foreign']); $tmp[$id][3] = is_string($id) ? $id : null; } $this->foreignKeys = array_map(fn($row) => new ForeignKey(...$row), array_values($tmp)); diff --git a/src/Database/Result.php b/src/Database/Result.php new file mode 100644 index 000000000..3de8d3ff2 --- /dev/null +++ b/src/Database/Result.php @@ -0,0 +1,223 @@ +explorer; + } + + + public function getQuery(): SqlLiteral + { + return $this->query; + } + + + /** @deprecated use getQuery()->getSql() */ + public function getQueryString(): string + { + return $this->query->getSql(); + } + + + /** @deprecated use getQuery()->getParameters() */ + public function getParameters(): array + { + return $this->query->getParameters(); + } + + + public function getColumnCount(): ?int + { + return $this->result?->getColumnCount(); + } + + + public function getRowCount(): ?int + { + return $this->result?->getRowCount(); + } + + + public function getTime(): float + { + return $this->time; + } + + + /********************* misc tools ****************d*g**/ + + + /** + * Displays complete result set as HTML table for debug purposes. + */ + public function dump(): void + { + Helpers::dumpResult($this); + } + + + /********************* interface IteratorAggregate ****************d*g**/ + + + /** @return \Generator */ + public function getIterator(): \Generator + { + if ($this->fetched) { + throw new Nette\InvalidStateException(self::class . ' implements only one way iterator.'); + } + + $counter = 0; + while (($row = $this->fetch()) !== null) { + yield $counter++ => $row; + } + } + + + /********************* fetch ****************d*g**/ + + + /** + * Returns the next row as an associative array or null if there are no more rows. + */ + public function fetchAssoc(?string $path = null): ?array + { + if ($path !== null) { + return Arrays::associate($this->fetchAll(), $path); + } + + $data = $this->result?->fetch(); + if ($data === null) { + $this->fetched = true; + return null; + + } elseif (!$this->fetched && count($data) !== $this->result->getColumnCount()) { + $duplicates = array_filter(array_count_values(array_column($this->result->getColumnsInfo(), 'name')), fn($val) => $val > 1); + trigger_error("Found duplicate columns in database result set: '" . implode("', '", array_keys($duplicates)) . "'."); + } + + $this->fetched = true; + return $this->normalizeRow($data); + } + + + /** + * Returns the next row as an object Row or null if there are no more rows. + */ + public function fetch(): ?Row + { + $data = $this->fetchAssoc(); + return $data === null ? null : Arrays::toObject($data, new Row); + } + + + /** + * Returns the first field of the next row or null if there are no more rows. + */ + public function fetchField(): mixed + { + $row = $this->fetchAssoc(); + return $row ? reset($row) : null; + } + + + /** + * Returns the next row as indexes array or null if there are no more rows. + */ + public function fetchList(): ?array + { + $row = $this->fetchAssoc(); + return $row ? array_values($row) : null; + } + + + /** + * Alias for fetchList(). + */ + public function fetchFields(): ?array + { + return $this->fetchList(); + } + + + /** + * Fetches all rows as associative array. + */ + public function fetchPairs(string|int|\Closure|null $keyOrCallback = null, string|int|null $value = null): array + { + return Helpers::toPairs($this->fetchAll(), $keyOrCallback, $value); + } + + + /** + * Fetches all rows. + * @return Row[] + */ + public function fetchAll(): array + { + return $this->rows ??= iterator_to_array($this); + } + + + private function normalizeRow(array $row): array + { + $engine = $this->explorer->getDatabaseEngine(); + $converter = $this->explorer->getTypeConverter(); + $columnsMeta = $this->meta ??= $this->getColumnsMeta(); + foreach ($row as $key => $value) { + $row[$key] = isset($value, $columnsMeta[$key]) + ? $engine->convertToPhp($value, $columnsMeta[$key], $converter) + : $value; + } + + return $row; + } + + + private function getColumnsMeta(): array + { + $res = []; + foreach ($this->result->getColumnsInfo() as $meta) { + $res[$meta['name']] = $meta; + } + return $res; + } +} + + +class_exists(ResultSet::class); diff --git a/src/Database/ResultSet.php b/src/Database/ResultSet.php deleted file mode 100644 index 6c8c1cfe2..000000000 --- a/src/Database/ResultSet.php +++ /dev/null @@ -1,261 +0,0 @@ -normalizer = $normalizer; - $types = ['boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT, 'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL]; - - try { - if (str_starts_with($queryString, '::')) { - $connection->getPdo()->{substr($queryString, 2)}(); - } else { - $this->pdoStatement = $connection->getPdo()->prepare($queryString); - foreach ($params as $key => $value) { - $type = gettype($value); - $this->pdoStatement->bindValue(is_int($key) ? $key + 1 : $key, $value, $types[$type] ?? PDO::PARAM_STR); - } - - $this->pdoStatement->setFetchMode(PDO::FETCH_ASSOC); - $this->pdoStatement->execute(); - } - } catch (\PDOException $e) { - $e = $connection->getDriver()->convertException($e); - $e->queryString = $queryString; - $e->params = $params; - throw $e; - } - - $this->time = microtime(true) - $time; - } - - - /** @deprecated */ - public function getConnection(): Connection - { - return $this->connection; - } - - - /** - * @internal - */ - public function getPdoStatement(): ?\PDOStatement - { - return $this->pdoStatement; - } - - - public function getQueryString(): string - { - return $this->queryString; - } - - - public function getParameters(): array - { - return $this->params; - } - - - public function getColumnCount(): ?int - { - return $this->pdoStatement ? $this->pdoStatement->columnCount() : null; - } - - - public function getRowCount(): ?int - { - return $this->pdoStatement ? $this->pdoStatement->rowCount() : null; - } - - - public function getColumnTypes(): array - { - $this->types ??= $this->connection->getDriver()->getColumnTypes($this->pdoStatement); - return $this->types; - } - - - public function getTime(): float - { - return $this->time; - } - - - /** @internal */ - public function normalizeRow(array $row): array - { - return $this->normalizer - ? ($this->normalizer)($row, $this) - : $row; - } - - - /********************* misc tools ****************d*g**/ - - - /** - * Displays complete result set as HTML table for debug purposes. - */ - public function dump(): void - { - Helpers::dumpResult($this); - } - - - /********************* interface Iterator ****************d*g**/ - - - public function rewind(): void - { - if ($this->lastRow === false) { - throw new Nette\InvalidStateException(self::class . ' implements only one way iterator.'); - } - } - - - public function current(): Row|false|null - { - return $this->lastRow; - } - - - public function key(): int - { - return $this->lastRowKey; - } - - - public function next(): void - { - $this->lastRow = false; - } - - - public function valid(): bool - { - if ($this->lastRow) { - return true; - } - - return $this->fetch() !== null; - } - - - /********************* fetch ****************d*g**/ - - - /** - * Returns the next row as an associative array or null if there are no more rows. - */ - public function fetchAssoc(?string $path = null): ?array - { - if ($path !== null) { - return Arrays::associate($this->fetchAll(), $path); - } - - $data = $this->pdoStatement ? $this->pdoStatement->fetch() : null; - if (!$data) { - $this->pdoStatement->closeCursor(); - return null; - - } elseif ($this->lastRow === null && count($data) !== $this->pdoStatement->columnCount()) { - $duplicates = Helpers::findDuplicates($this->pdoStatement); - trigger_error("Found duplicate columns in database result set: $duplicates."); - } - - return $this->normalizeRow($data); - } - - - /** - * Returns the next row as an object Row or null if there are no more rows. - */ - public function fetch(): ?Row - { - $data = $this->fetchAssoc(); - if ($data === null) { - return null; - } - - $this->lastRowKey++; - return $this->lastRow = Arrays::toObject($data, new Row); - } - - - /** - * Fetches single field. - */ - public function fetchField(): mixed - { - $row = $this->fetchAssoc(); - return $row ? reset($row) : null; - } - - - /** - * Fetches array of fields. - */ - public function fetchFields(): ?array - { - $row = $this->fetchAssoc(); - return $row ? array_values($row) : null; - } - - - /** - * Fetches all rows as associative array. - */ - public function fetchPairs(string|int|\Closure|null $keyOrCallback = null, string|int|null $value = null): array - { - return Helpers::toPairs($this->fetchAll(), $keyOrCallback, $value); - } - - - /** - * Fetches all rows. - * @return Row[] - */ - public function fetchAll(): array - { - $this->rows ??= iterator_to_array($this); - return $this->rows; - } -} diff --git a/src/Database/Row.php b/src/Database/Row.php index 38bad424c..b6e5bb7a4 100644 --- a/src/Database/Row.php +++ b/src/Database/Row.php @@ -15,7 +15,7 @@ /** * Represents a single table row. */ -class Row extends Nette\Utils\ArrayHash implements IRow +class Row extends Nette\Utils\ArrayHash { public function __get(mixed $key): mixed { @@ -62,3 +62,6 @@ public function offsetExists($key): bool return parent::offsetExists($key); } } + + +class_alias(Row::class, IRow::class); diff --git a/src/Database/SqlPreprocessor.php b/src/Database/SqlPreprocessor.php index 876ef3ca3..f9fb1d0ac 100644 --- a/src/Database/SqlPreprocessor.php +++ b/src/Database/SqlPreprocessor.php @@ -48,8 +48,8 @@ class SqlPreprocessor 'EXPLAIN' => 1, ]; - private readonly Connection $connection; - private readonly Driver $driver; + private readonly Drivers\Connection $connection; + private readonly Drivers\Engine $engine; private array $params; private array $remaining; private int $counter; @@ -59,10 +59,10 @@ class SqlPreprocessor private ?string $arrayMode; - public function __construct(Connection $connection) + public function __construct(Explorer $explorer) { - $this->connection = $connection; - $this->driver = $connection->getDriver(); + $this->connection = $explorer->getConnection(); + $this->engine = $explorer->getDatabaseEngine(); } @@ -177,10 +177,10 @@ private function formatValue(mixed $value, ?string $mode = null): string return $res; } elseif ($value instanceof \DateTimeInterface) { - return $this->driver->formatDateTime($value); + return $this->engine->formatDateTime($value); } elseif ($value instanceof \DateInterval) { - return $this->driver->formatDateInterval($value); + return $this->engine->formatDateInterval($value); } elseif ($value instanceof \BackedEnum && is_scalar($value->value)) { $this->remaining[] = $value->value; @@ -231,7 +231,7 @@ private function formatValue(mixed $value, ?string $mode = null): string $vx[] = implode(', ', $vx2); } - $select = $this->driver->isSupported(Driver::SupportMultiInsertAsSelect); + $select = $this->engine->isSupported(Drivers\Engine::SupportMultiInsertAsSelect); return '(' . implode(', ', $kx) . ($select ? ') SELECT ' : ') VALUES (') . implode($select ? ' UNION ALL SELECT ' : '), (', $vx) . ($select ? '' : ')'); } @@ -320,6 +320,6 @@ private function formatValue(mixed $value, ?string $mode = null): string private function delimite(string $name): string { - return implode('.', array_map($this->driver->delimite(...), explode('.', $name))); + return implode('.', array_map($this->engine->delimit(...), explode('.', $name))); } } diff --git a/src/Database/Structure.php b/src/Database/Structure.php index bd5b7a3ce..41427c684 100644 --- a/src/Database/Structure.php +++ b/src/Database/Structure.php @@ -14,21 +14,19 @@ /** * Cached reflection of database structure. + * @internal */ -class Structure implements IStructure +class Structure { - protected readonly Connection $connection; - protected readonly Nette\Caching\Cache $cache; - /** @var array{tables: array, columns: array, primary: array, aliases: array, hasMany: array, belongsTo: array} */ protected array $structure; protected bool $isRebuilt = false; - public function __construct(Connection $connection, Nette\Caching\Storage $cacheStorage) - { - $this->connection = $connection; - $this->cache = new Nette\Caching\Cache($cacheStorage, 'Nette.Database.Structure.' . hash('xxh128', $connection->getDsn())); + public function __construct( + protected readonly Drivers\Engine $engine, + protected readonly Nette\Caching\Cache $cache, + ) { } @@ -66,11 +64,11 @@ public function getPrimaryAutoincrementKey(string $table): ?string return null; } - // Search for autoincrement key from multi primary key + // Search for autoIncrement key from multi primary key if (is_array($primaryKey)) { $keys = array_flip($primaryKey); foreach ($this->getColumns($table) as $column) { - if (isset($keys[$column['name']]) && $column['autoincrement']) { + if (isset($keys[$column['name']]) && $column['autoIncrement']) { return $column['name']; } } @@ -78,10 +76,10 @@ public function getPrimaryAutoincrementKey(string $table): ?string return null; } - // Search for autoincrement key from simple primary key + // Search for auto-increment key from simple primary key foreach ($this->getColumns($table) as $column) { if ($column['name'] === $primaryKey) { - return $column['autoincrement'] ? $column['name'] : null; + return $column['autoIncrement'] ? $column['name'] : null; } } @@ -94,18 +92,18 @@ public function getPrimaryKeySequence(string $table): ?string $this->needStructure(); $table = $this->resolveFQTableName($table); - if (!$this->connection->getDriver()->isSupported(Driver::SupportSequence)) { + if (!$this->engine->isSupported(Drivers\Engine::SupportSequence)) { return null; } - $autoincrementPrimaryKeyName = $this->getPrimaryAutoincrementKey($table); - if (!$autoincrementPrimaryKeyName) { + $autoIncrementPrimaryKeyName = $this->getPrimaryAutoincrementKey($table); + if (!$autoIncrementPrimaryKeyName) { return null; } // Search for sequence from simple primary key foreach ($this->structure['columns'][$table] as $columnMeta) { - if ($columnMeta['name'] === $autoincrementPrimaryKeyName) { + if ($columnMeta['name'] === $autoIncrementPrimaryKeyName) { return $columnMeta['vendor']['sequence'] ?? null; } } @@ -177,10 +175,8 @@ protected function needStructure(): void protected function loadStructure(): array { - $driver = $this->connection->getDriver(); - $structure = []; - $structure['tables'] = $driver->getTables(); + $structure['tables'] = $this->engine->getTables(); foreach ($structure['tables'] as $tablePair) { if (isset($tablePair['fullName'])) { @@ -190,7 +186,7 @@ protected function loadStructure(): array $table = $tablePair['name']; } - $structure['columns'][strtolower($table)] = $columns = $driver->getColumns($table); + $structure['columns'][strtolower($table)] = $columns = $this->engine->getColumns($table); if (!$tablePair['view']) { $structure['primary'][strtolower($table)] = $this->analyzePrimaryKey($columns); @@ -233,19 +229,13 @@ protected function analyzeForeignKeys(array &$structure, string $table): void { $lowerTable = strtolower($table); - $foreignKeys = $this->connection->getDriver()->getForeignKeys($table); - - $fksColumnsCounts = []; - foreach ($foreignKeys as $foreignKey) { - $tmp = &$fksColumnsCounts[$foreignKey['name']]; - $tmp++; - } + $foreignKeys = $this->engine->getForeignKeys($table); - usort($foreignKeys, fn($a, $b): int => $fksColumnsCounts[$b['name']] <=> $fksColumnsCounts[$a['name']]); + usort($foreignKeys, fn($a, $b): int => count($b['local']) <=> count($a['local'])); foreach ($foreignKeys as $row) { - $structure['belongsTo'][$lowerTable][$row['local']] = $row['table']; - $structure['hasMany'][strtolower($row['table'])][$table][] = $row['local']; + $structure['belongsTo'][$lowerTable][$row['local'][0]] = $row['table']; + $structure['hasMany'][strtolower($row['table'])][$table][] = $row['local'][0]; } if (isset($structure['belongsTo'][$lowerTable])) { diff --git a/src/Database/Table/ActiveRow.php b/src/Database/Table/ActiveRow.php index 91778c96a..268ae73ff 100644 --- a/src/Database/Table/ActiveRow.php +++ b/src/Database/Table/ActiveRow.php @@ -16,7 +16,7 @@ * Single row representation. * ActiveRow is based on the great library NotORM http://www.notorm.com written by Jakub Vrana. */ -class ActiveRow implements \IteratorAggregate, IRow +class ActiveRow implements \IteratorAggregate, \ArrayAccess { private bool $dataRefreshed = false; @@ -61,7 +61,7 @@ public function toArray(): array /** * Returns primary key value. - * @return mixed possible int, string, array, object (Nette\Utils\DateTime) + * @return mixed possible int, string, array, object (Nette\Database\DateTime) */ public function getPrimary(bool $throw = true): mixed { diff --git a/src/Database/Table/GroupedSelection.php b/src/Database/Table/GroupedSelection.php index 507ac298a..fa29a86d0 100644 --- a/src/Database/Table/GroupedSelection.php +++ b/src/Database/Table/GroupedSelection.php @@ -10,7 +10,6 @@ namespace Nette\Database\Table; use Nette; -use Nette\Database\Conventions; use Nette\Database\Explorer; @@ -38,15 +37,13 @@ class GroupedSelection extends Selection */ public function __construct( Explorer $explorer, - Conventions $conventions, string $tableName, string $column, Selection $refTable, - ?Nette\Caching\Storage $cacheStorage = null, ) { $this->refTable = $refTable; $this->column = $column; - parent::__construct($explorer, $conventions, $tableName, $cacheStorage); + parent::__construct($explorer, $tableName); } diff --git a/src/Database/Table/IRow.php b/src/Database/Table/IRow.php deleted file mode 100644 index 1a507557f..000000000 --- a/src/Database/Table/IRow.php +++ /dev/null @@ -1,18 +0,0 @@ - + * @implements \IteratorAggregate * @implements \ArrayAccess */ -class Selection implements \Iterator, IRowContainer, \ArrayAccess, \Countable +class Selection implements \IteratorAggregate, \ArrayAccess, \Countable { protected readonly Explorer $explorer; - - /** back compatibility */ - protected Explorer $context; - protected readonly Conventions $conventions; protected readonly ?Nette\Caching\Cache $cache; protected SqlBuilder $sqlBuilder; @@ -63,27 +58,18 @@ class Selection implements \Iterator, IRowContainer, \ArrayAccess, \Countable /** should instance observe accessed columns caching */ protected ?self $observeCache = null; - /** of primary key values */ - protected array $keys = []; - /** * Creates filtered table representation. */ public function __construct( Explorer $explorer, - Conventions $conventions, string $tableName, - ?Nette\Caching\Storage $cacheStorage = null, ) { - $this->explorer = $this->context = $explorer; - $this->conventions = $conventions; + $this->explorer = $explorer; $this->name = $tableName; - - $this->cache = $cacheStorage - ? new Nette\Caching\Cache($cacheStorage, 'Nette.Database.' . hash('xxh128', $explorer->getConnection()->getDsn())) - : null; - $this->primary = $conventions->getPrimary($tableName); + $this->cache = $explorer->getCache(); + $this->primary = $explorer->getConventions()->getPrimary($tableName); $this->sqlBuilder = new SqlBuilder($tableName, $explorer); $this->refCache = &$this->getRefTable($refPath)->globalRefCache[$refPath]; } @@ -528,13 +514,13 @@ protected function execute(): void } } + $key = 0; $this->rows = []; $usedPrimary = true; - foreach ($result->getPdoStatement() as $key => $row) { - $row = $this->createRow($result->normalizeRow($row)); - $primary = $row->getSignature(false); - $usedPrimary = $usedPrimary && $primary !== ''; - $this->rows[$usedPrimary ? $primary : $key] = $row; + while ($row = @$result->fetchAssoc()) { // @ may contain duplicate columns + $row = $this->createRow($row); + $usedPrimary = $usedPrimary && ($primary = $row->getSignature(false)) !== ''; + $this->rows[$usedPrimary ? $primary : $key++] = $row; } $this->data = $this->rows; @@ -555,17 +541,17 @@ protected function createRow(array $row): ActiveRow public function createSelectionInstance(?string $table = null): self { - return new self($this->explorer, $this->conventions, $table ?: $this->name, $this->cache?->getStorage()); + return new self($this->explorer, $table ?: $this->name); } protected function createGroupedSelectionInstance(string $table, string $column): GroupedSelection { - return new GroupedSelection($this->explorer, $this->conventions, $table, $column, $this, $this->cache?->getStorage()); + return new GroupedSelection($this->explorer, $table, $column, $this); } - protected function query(string $query): Nette\Database\ResultSet + protected function query(string $query): Nette\Database\Result { return $this->explorer->query($query, ...$this->sqlBuilder->getParameters()); } @@ -801,7 +787,7 @@ public function insert(iterable $data): ActiveRow|array|int|bool // First check sequence if (!empty($primarySequenceName) && $primaryAutoincrementKey) { - $primaryKey[$primaryAutoincrementKey] = $this->explorer->getInsertId($this->explorer->getConnection()->getDriver()->delimite($primarySequenceName)); + $primaryKey[$primaryAutoincrementKey] = $this->explorer->getInsertId($this->explorer->getDatabaseEngine()->delimit($primarySequenceName)); // Autoincrement primary without sequence } elseif ($primaryAutoincrementKey) { @@ -886,7 +872,7 @@ public function delete(): int public function getReferencedTable(ActiveRow $row, ?string $table, ?string $column = null): ActiveRow|false|null { if (!$column) { - $belongsTo = $this->conventions->getBelongsToReference($this->name, $table); + $belongsTo = $this->explorer->getConventions()->getBelongsToReference($this->name, $table); if (!$belongsTo) { return false; } @@ -939,7 +925,7 @@ public function getReferencingTable( if (str_contains($table, '.')) { [$table, $column] = explode('.', $table); } elseif (!$column) { - $hasMany = $this->conventions->getHasManyReference($this->name, $table); + $hasMany = $this->explorer->getConventions()->getHasManyReference($this->name, $table); if (!$hasMany) { return null; } @@ -959,43 +945,18 @@ public function getReferencingTable( } - /********************* interface Iterator ****************d*g**/ + /********************* interface IteratorAggregate ****************d*g**/ - public function rewind(): void + /** @return \Generator */ + public function getIterator(): \Generator { $this->execute(); - $this->keys = array_keys($this->data); - reset($this->keys); - } - - - /** @return T|false */ - public function current(): ActiveRow|false - { - return ($key = current($this->keys)) !== false - ? $this->data[$key] - : false; - } - - - public function key(): string|int - { - return current($this->keys); - } - - - public function next(): void - { - do { - next($this->keys); - } while (($key = current($this->keys)) !== false && !isset($this->data[$key])); - } - - - public function valid(): bool - { - return current($this->keys) !== false; + foreach ($this->data as $key => $value) { + if (isset($this->data[$key])) { // may be unset by offsetUnset + yield $key => $value; + } + } } diff --git a/src/Database/Table/SqlBuilder.php b/src/Database/Table/SqlBuilder.php index 4db2d3f09..29c0182b6 100644 --- a/src/Database/Table/SqlBuilder.php +++ b/src/Database/Table/SqlBuilder.php @@ -11,10 +11,10 @@ use Nette; use Nette\Database\Conventions; -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Nette\Database\Explorer; -use Nette\Database\IStructure; use Nette\Database\SqlLiteral; +use Nette\Database\Structure; /** @@ -46,8 +46,8 @@ class SqlBuilder protected array $reservedTableNames = []; protected array $aliases = []; protected string $currentAlias = ''; - private readonly Driver $driver; - private readonly IStructure $structure; + private readonly Engine $engine; + private readonly Structure $structure; private array $cacheTableList = []; private array $expandingJoins = []; @@ -55,11 +55,11 @@ class SqlBuilder public function __construct(string $tableName, Explorer $explorer) { $this->tableName = $tableName; - $this->driver = $explorer->getConnection()->getDriver(); + $this->engine = $explorer->getDatabaseEngine(); $this->conventions = $explorer->getConventions(); $this->structure = $explorer->getStructure(); $tableNameParts = explode('.', $tableName); - $this->delimitedTable = implode('.', array_map($this->driver->delimite(...), $tableNameParts)); + $this->delimitedTable = implode('.', array_map($this->engine->delimit(...), $tableNameParts)); $this->checkUniqueTableName(end($tableNameParts), $tableName); } @@ -78,14 +78,14 @@ public function buildInsertQuery(): string public function buildUpdateQuery(): string { - $query = "UPDATE {$this->delimitedTable} SET ?set" . $this->tryDelimite($this->buildConditions()); + $query = "UPDATE {$this->delimitedTable} SET ?set" . $this->tryDelimit($this->buildConditions()); if ($this->order !== []) { $query .= ' ORDER BY ' . implode(', ', $this->order); } if ($this->limit !== null || $this->offset) { - $this->driver->applyLimit($query, $this->limit, $this->offset); + $query = $this->engine->applyLimit($query, $this->limit, $this->offset); } return $query; @@ -94,9 +94,9 @@ public function buildUpdateQuery(): string public function buildDeleteQuery(): string { - $query = "DELETE FROM {$this->delimitedTable}" . $this->tryDelimite($this->buildConditions()); + $query = "DELETE FROM {$this->delimitedTable}" . $this->tryDelimit($this->buildConditions()); if ($this->limit !== null || $this->offset) { - $this->driver->applyLimit($query, $this->limit, $this->offset); + $query = $this->engine->applyLimit($query, $this->limit, $this->offset); } return $query; @@ -119,7 +119,7 @@ public function getSelectQueryHash(?array $columns = null): string $parts[] = $this->select; } elseif ($columns) { $parts[] = [$this->delimitedTable, $columns]; - } elseif ($this->group && !$this->driver->isSupported(Driver::SupportSelectUngroupedColumns)) { + } elseif ($this->group && !$this->engine->isSupported(Engine::SupportSelectUngroupedColumns)) { $parts[] = [$this->group]; } else { $parts[] = "{$this->delimitedTable}.*"; @@ -171,7 +171,7 @@ public function buildSelectQuery(?array $columns = null): string $querySelect = $this->buildSelect($cols); - } elseif ($this->group && !$this->driver->isSupported(Driver::SupportSelectUngroupedColumns)) { + } elseif ($this->group && !$this->engine->isSupported(Engine::SupportSelectUngroupedColumns)) { $querySelect = $this->buildSelect([$this->group]); $this->parseJoins($joins, $querySelect); @@ -183,9 +183,9 @@ public function buildSelectQuery(?array $columns = null): string $queryJoins = $this->buildQueryJoins($joins, $finalJoinConditions); $query = "{$querySelect} FROM {$this->delimitedTable}{$queryJoins}{$queryCondition}{$queryEnd}"; - $this->driver->applyLimit($query, $this->limit, $this->offset); + $query = $this->engine->applyLimit($query, $this->limit, $this->offset); - return $this->tryDelimite($query); + return $this->tryDelimit($query); } @@ -343,7 +343,7 @@ protected function addCondition( } } - if ($this->driver->isSupported(Driver::SupportSubselect)) { + if ($this->engine->isSupported(Engine::SupportSubselect)) { $arg = null; $subSelectPlaceholderCount = substr_count($clone->getSql(), '?'); $replace = $match[2][0] . '(' . $clone->getSql() . (!$subSelectPlaceholderCount && count($clone->getSqlBuilder()->getParameters()) === 1 ? ' ?' : '') . ')'; @@ -634,7 +634,7 @@ public function parseJoinsCb(array &$joins, array $match): string $parentAlias = preg_replace('#^(.*\.)?(.*)$#', '$2', $this->tableName); // join schema keyMatch and table keyMatch to schema.table keyMatch - if ($this->driver->isSupported(Driver::SupportSchema) && count($keyMatches) > 1) { + if ($this->engine->isSupported(Engine::SupportSchema) && count($keyMatches) > 1) { $tables = $this->getCachedTableList(); if ( !isset($tables[$keyMatches[0]['key']]) @@ -789,13 +789,13 @@ protected function buildQueryEnd(): string } - protected function tryDelimite(string $s): string + protected function tryDelimit(string $s): string { return preg_replace_callback( '#(?<=[^\w`"\[?:]|^)[a-z_][a-z0-9_]*(?=[^\w`"(\]]|$)#Di', fn(array $m): string => strtoupper($m[0]) === $m[0] ? $m[0] - : $this->driver->delimite($m[0]), + : $this->engine->delimit($m[0]), $s, ); } @@ -808,7 +808,7 @@ protected function addConditionComposition( array &$conditionsParameters, ): bool { - if ($this->driver->isSupported(Driver::SupportMultiColumnAsOrCond)) { + if ($this->engine->isSupported(Engine::SupportMultiColumnAsOrCondition)) { $conditionFragment = '(' . implode(' = ? AND ', $columns) . ' = ?) OR '; $condition = substr(str_repeat($conditionFragment, count($parameters)), 0, -4); return $this->addCondition($condition, [Nette\Utils\Arrays::flatten($parameters)], $conditions, $conditionsParameters); diff --git a/src/Database/TypeConverter.php b/src/Database/TypeConverter.php new file mode 100644 index 000000000..78d5edc2c --- /dev/null +++ b/src/Database/TypeConverter.php @@ -0,0 +1,119 @@ + self::Text, // PostgreSQL arrays + '(TINY|SMALL|SHORT|MEDIUM|BIG|LONG)(INT)?|INT(EGER|\d+| IDENTITY| UNSIGNED)?|(SMALL|BIG|)SERIAL\d*|COUNTER|YEAR|BYTE|LONGLONG|UNSIGNED BIG INT' => self::Integer, + '(NEW)?DEC(IMAL)?(\(.*)?|NUMERIC|(SMALL)?MONEY|CURRENCY|NUMBER' => self::Decimal, + 'REAL|DOUBLE( PRECISION)?|FLOAT\d*' => self::Float, + 'BOOL(EAN)?' => self::Boolean, + 'TIME' => self::Time, + 'DATE' => self::Date, + '(SMALL)?DATETIME(OFFSET)?\d*|TIME(STAMP.*)?' => self::DateTime, + 'BYTEA|(TINY|MEDIUM|LONG|)BLOB|(LONG )?(VAR)?BINARY|IMAGE' => self::Binary, + ]; + + public bool $convertBoolean = true; + public bool $convertDateTime = true; + public bool $convertDecimal = true; + public bool $newDateTime = true; + + + /** + * Heuristic column type detection. + */ + private function detectType(string $nativeType): int + { + static $cache; + if (!isset($cache[$nativeType])) { + $cache[$nativeType] = self::Text; + foreach (self::Patterns as $s => $val) { + if (preg_match("#^($s)$#i", $nativeType)) { + return $cache[$nativeType] = $val; + } + } + } + + return $cache[$nativeType]; + } + + + public function convertToPhp(mixed $value, array $meta): mixed + { + return match ($this->detectType($meta['nativeType'] ?? '')) { + self::Integer => $this->toInt($value), + self::Float => $this->toFloat($value), + self::Decimal => $this->convertDecimal + ? ($meta['scale'] === 0 ? $this->toInt($value) : $this->toFloat($value)) + : $value, + self::Boolean => $this->convertBoolean ? $this->toBool($value) : $value, + self::DateTime, self::Date => $this->convertDateTime ? $this->toDateTime($value) : $value, + self::Time => $this->convertDateTime ? $this->toTime($value) : $value, + self::Interval => $this->convertDateTime ? self::toInterval($value) : $value, + default => $value, + }; + } + + + public function toInt(int|float|string $value): int|float|string + { + return is_float($tmp = $value * 1) ? $value : $tmp; + } + + + public function toFloat(float|string $value): float + { + return (float) $value; + } + + + public function toBool(bool|int|string $value): bool + { + return (bool) $value; + } + + + public function toDateTime(string $value): \DateTimeInterface + { + return $this->newDateTime ? new DateTime($value) : new \Nette\Utils\DateTime($value); + } + + + public function toTime(string $value): \DateTimeInterface + { + return $this->toDateTime($value)->setDate(1, 1, 1); + } + + + public function toInterval(string $value): \DateInterval + { + preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)(\.\d+)?$#D', $value, $m); + $interval = new \DateInterval("PT$m[2]H$m[3]M$m[4]S"); + $interval->f = isset($m[5]) ? (float) $m[5] : 0.0; + $interval->invert = (int) (bool) $m[1]; + return $interval; + } +} diff --git a/src/compatibility-intf.php b/src/compatibility-intf.php deleted file mode 100644 index fe3ab8558..000000000 --- a/src/compatibility-intf.php +++ /dev/null @@ -1,28 +0,0 @@ -initialize(); - $connection = $container->getService('database.default'); - Assert::type(Nette\Database\Connection::class, $connection); - Assert::same('sqlite::memory:', $connection->getDsn()); - - $explorer = $container->getService('database.default.explorer'); + $explorer = $container->getService('database.default'); Assert::type(Nette\Database\Explorer::class, $explorer); - Assert::same($connection, $explorer->getConnection()); - Assert::same($container->getService('database.default.context'), $explorer); - - Assert::type(Nette\Database\Structure::class, $explorer->getStructure()); - Assert::type(Nette\Database\Conventions\DiscoveredConventions::class, $explorer->getConventions()); + Assert::type(Nette\Caching\Cache::class, $explorer->getCache()); // aliases - Assert::same($connection, $container->getService('nette.database.default')); + Assert::same($explorer, $container->getService('database.default.explorer')); + Assert::same($explorer, $container->getService('nette.database.default')); Assert::same($explorer, $container->getService('nette.database.default.context')); }); diff --git a/tests/Database.DI/DatabaseExtension.multiple.phpt b/tests/Database.DI/DatabaseExtension.multiple.phpt index 463349587..e808b5337 100644 --- a/tests/Database.DI/DatabaseExtension.multiple.phpt +++ b/tests/Database.DI/DatabaseExtension.multiple.phpt @@ -23,16 +23,12 @@ test('', function () { user: name password: secret debugger: no - options: - lazy: yes second: dsn: "sqlite::memory:" user: name password: secret debugger: no - options: - lazy: yes services: cache: Nette\Caching\Storages\DevNullStorage @@ -45,23 +41,13 @@ test('', function () { $container = new Container1; $container->initialize(); - $connection = $container->getService('database.first'); - Assert::type(Nette\Database\Connection::class, $connection); - Assert::same($connection, $container->getByType(Nette\Database\Connection::class)); - Assert::same('sqlite::memory:', $connection->getDsn()); - - $explorer = $container->getService('database.first.explorer'); + $explorer = $container->getService('database.first'); Assert::type(Nette\Database\Explorer::class, $explorer); Assert::same($explorer, $container->getByType(Nette\Database\Explorer::class)); - Assert::same($connection, $explorer->getConnection()); - Assert::same($container->getService('database.first.context'), $explorer); - - Assert::type(Nette\Database\Structure::class, $explorer->getStructure()); - Assert::same($explorer->getStructure(), $container->getByType(Nette\Database\IStructure::class)); - Assert::type(Nette\Database\Conventions\DiscoveredConventions::class, $explorer->getConventions()); - Assert::same($explorer->getConventions(), $container->getByType(Nette\Database\Conventions::class)); + Assert::type(Nette\Caching\Cache::class, $explorer->getCache()); // aliases - Assert::same($connection, $container->getService('nette.database.first')); + Assert::same($explorer, $container->getService('database.first.explorer')); + Assert::same($explorer, $container->getService('nette.database.first')); Assert::same($explorer, $container->getService('nette.database.first.context')); }); diff --git a/tests/Database.Tracy/ConnectionPanel.phpt b/tests/Database.Tracy/ConnectionPanel.phpt index 4dba1b30c..42374a537 100644 --- a/tests/Database.Tracy/ConnectionPanel.phpt +++ b/tests/Database.Tracy/ConnectionPanel.phpt @@ -15,7 +15,7 @@ require __DIR__ . '/../bootstrap.php'; test('Tracy Bar', function () { $connection = new Connection('sqlite::memory:'); - $panel = ConnectionPanel::initialize($connection, addBarPanel: true, name: 'foo'); + $panel = ConnectionPanel::initialize($connection, name: 'foo'); $connection->beginTransaction(); $connection->query('SELECT 1'); @@ -47,7 +47,7 @@ test('Bluescreen Panel', function () { test('deprecated initialization', function () { $connection = new Connection('sqlite::memory:'); - $panel = Nette\Database\Helpers::initializeTracy($connection, addBarPanel: true, name: 'foo'); + $panel = @Nette\Database\Helpers::initializeTracy($connection, addBarPanel: true, name: 'foo'); // deprecated $connection->beginTransaction(); $connection->query('SELECT 1'); diff --git a/tests/Database.Tracy/panel.html b/tests/Database.Tracy/panel.html index 1ec032563..3722349a9 100644 --- a/tests/Database.Tracy/panel.html +++ b/tests/Database.Tracy/panel.html @@ -1,26 +1,26 @@ %A% -

Queries: 4, time: %a% ms, foo

+

Queries: 4, time: %a% ms, foo

%A% - + + %a%Explorer.php:%d% - + @@ -28,7 +28,7 @@

Queries: 4, time: %a% ms, foo

ERROR + %a%Explorer.php:%d%
%A%
::beginTransaction
- %a%Connection.php:%d%
BEGIN TRANSACTION
+ %a%Explorer.php:%d%
%A%
SELECT 1
%A%
- %a%Connection.php:%d%
0
%A%
::commit
- %a%Connection.php:%d%
COMMIT
+ %a%Explorer.php:%d%
SELECT
- %a%SqliteDriver.php:%d%
diff --git a/tests/Database/Connection.exceptions.mysql.phpt b/tests/Database/Connection.exceptions.mysql.phpt index 1d60d5adc..8d8346638 100644 --- a/tests/Database/Connection.exceptions.mysql.phpt +++ b/tests/Database/Connection.exceptions.mysql.phpt @@ -11,20 +11,18 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('Exception thrown for invalid database credentials', function () { $options = Tester\Environment::loadData(); $e = Assert::exception( - fn() => new Nette\Database\Connection($options['dsn'], 'unknown', 'unknown'), + fn() => (new Nette\Database\Explorer($options['dsn'], 'unknown', 'unknown'))->connect(), Nette\Database\ConnectionException::class, '%a% Access denied for user %a%', + 1045, ); - - Assert::same(1045, $e->getDriverCode()); Assert::contains($e->getSqlState(), ['HY000', '28000']); - Assert::same($e->getCode(), $e->getSqlState()); }); @@ -35,7 +33,7 @@ test('Exception thrown when calling rollback with no active transaction', functi 'There is no active transaction', 0, ); - Assert::same(null, $e->getDriverCode()); + Assert::null($e->getSqlState()); }); @@ -44,11 +42,9 @@ test('Exception thrown for syntax error in SQL query', function () use ($connect fn() => $connection->query('SELECT'), Nette\Database\DriverException::class, '%a% Syntax error %a%', - '42000', + 1064, ); - - Assert::same(1064, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('42000', $e->getSqlState()); }); @@ -57,11 +53,9 @@ test('Exception thrown for unique constraint violation', function () use ($conne fn() => $connection->query('INSERT INTO author (id, name, web, born) VALUES (11, "", "", NULL)'), Nette\Database\UniqueConstraintViolationException::class, '%a% Integrity constraint violation: %a%', - '23000', + 1062, ); - - Assert::same(1062, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('23000', $e->getSqlState()); }); @@ -70,11 +64,9 @@ test('Exception thrown for not null constraint violation', function () use ($con fn() => $connection->query('INSERT INTO author (name, web, born) VALUES (NULL, "", NULL)'), Nette\Database\NotNullConstraintViolationException::class, '%a% Integrity constraint violation: %a%', - '23000', + 1048, ); - - Assert::same(1048, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('23000', $e->getSqlState()); }); @@ -83,9 +75,7 @@ test('Exception thrown for foreign key constraint violation', function () use ($ fn() => $connection->query('INSERT INTO book (author_id, translator_id, title) VALUES (999, 12, "")'), Nette\Database\ForeignKeyConstraintViolationException::class, '%a% a foreign key constraint fails %a%', - '23000', + 1452, ); - - Assert::same(1452, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('23000', $e->getSqlState()); }); diff --git a/tests/Database/Connection.exceptions.phpt b/tests/Database/Connection.exceptions.phpt deleted file mode 100644 index 8efbc2871..000000000 --- a/tests/Database/Connection.exceptions.phpt +++ /dev/null @@ -1,22 +0,0 @@ - new Nette\Database\Connection('unknown'), - Nette\Database\ConnectionException::class, - '%a%valid data source %a%', - 0, -); - -Assert::same(null, $e->getDriverCode()); -Assert::same(null, $e->getSqlState()); diff --git a/tests/Database/Connection.exceptions.postgre.phpt b/tests/Database/Connection.exceptions.postgre.phpt index 717245d38..8fe0f5a05 100644 --- a/tests/Database/Connection.exceptions.postgre.phpt +++ b/tests/Database/Connection.exceptions.postgre.phpt @@ -11,21 +11,19 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('Exception thrown for invalid database credentials', function () { $options = Tester\Environment::loadData(); $e = Assert::exception( - fn() => new Nette\Database\Connection($options['dsn'], 'unknown', 'unknown'), + fn() => (new Nette\Database\Explorer($options['dsn'], 'unknown', 'unknown'))->connect(), Nette\Database\ConnectionException::class, null, - '08006', + 7, ); - - Assert::same(7, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('08006', $e->getSqlState()); }); @@ -36,8 +34,7 @@ test('Exception thrown when calling rollback with no active transaction', functi 'There is no active transaction', 0, ); - - Assert::same(null, $e->getDriverCode()); + Assert::null($e->getSqlState()); }); @@ -46,11 +43,9 @@ test('Exception thrown for syntax error in SQL query', function () use ($connect fn() => $connection->query('SELECT INTO'), Nette\Database\DriverException::class, '%a% syntax error %A%', - '42601', + 7, ); - - Assert::same(7, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('42601', $e->getSqlState()); }); @@ -59,11 +54,9 @@ test('Exception thrown for unique constraint violation', function () use ($conne fn() => $connection->query("INSERT INTO author (id, name, web, born) VALUES (11, '', '', NULL)"), Nette\Database\UniqueConstraintViolationException::class, '%a% Unique violation: %A%', - '23505', + 7, ); - - Assert::same(7, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('23505', $e->getSqlState()); }); @@ -72,11 +65,9 @@ test('Exception thrown for not null constraint violation', function () use ($con fn() => $connection->query("INSERT INTO author (name, web, born) VALUES (NULL, '', NULL)"), Nette\Database\NotNullConstraintViolationException::class, '%a% Not null violation: %A%', - '23502', + 7, ); - - Assert::same(7, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('23502', $e->getSqlState()); }); @@ -85,9 +76,7 @@ test('Exception thrown for foreign key constraint violation', function () use ($ fn() => $connection->query("INSERT INTO book (author_id, translator_id, title) VALUES (999, 12, '')"), Nette\Database\ForeignKeyConstraintViolationException::class, '%a% Foreign key violation: %A%', - '23503', + 7, ); - - Assert::same(7, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('23503', $e->getSqlState()); }); diff --git a/tests/Database/Connection.exceptions.sqlite.phpt b/tests/Database/Connection.exceptions.sqlite.phpt index dbd66d5cb..12d41bbc5 100644 --- a/tests/Database/Connection.exceptions.sqlite.phpt +++ b/tests/Database/Connection.exceptions.sqlite.phpt @@ -11,20 +11,18 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('Exception thrown for unable to open database file', function () { $e = Assert::exception( - fn() => new Nette\Database\Connection('sqlite:.'), + fn() => (new Nette\Database\Explorer('sqlite:.'))->connect(), Nette\Database\ConnectionException::class, 'SQLSTATE[HY000] [14] unable to open database file', - 'HY000', + 14, ); - - Assert::same(14, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('HY000', $e->getSqlState()); }); @@ -35,8 +33,7 @@ test('Exception thrown when calling rollback with no active transaction', functi 'There is no active transaction', 0, ); - - Assert::same(null, $e->getDriverCode()); + Assert::null($e->getSqlState()); }); @@ -45,11 +42,9 @@ test('Exception thrown for error in SQL query', function () use ($connection) { fn() => $connection->query('SELECT'), Nette\Database\DriverException::class, '%a% error%a%', - 'HY000', + 1, ); - - Assert::same(1, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('HY000', $e->getSqlState()); }); @@ -58,11 +53,9 @@ test('Exception thrown for unique constraint violation', function () use ($conne fn() => $connection->query('INSERT INTO author (id, name, web, born) VALUES (11, "", "", NULL)'), Nette\Database\UniqueConstraintViolationException::class, '%a% Integrity constraint violation: %a%', - '23000', + 19, ); - - Assert::same(19, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('23000', $e->getSqlState()); }); @@ -71,11 +64,9 @@ test('Exception thrown for not null constraint violation', function () use ($con fn() => $connection->query('INSERT INTO author (name, web, born) VALUES (NULL, "", NULL)'), Nette\Database\NotNullConstraintViolationException::class, '%a% Integrity constraint violation: %a%', - '23000', + 19, ); - - Assert::same(19, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + Assert::same('23000', $e->getSqlState()); }); @@ -83,8 +74,6 @@ test('Exception thrown for foreign key constraint violation', function () use ($ $e = Assert::exception(function () use ($connection) { $connection->query('PRAGMA foreign_keys=true'); $connection->query('INSERT INTO book (author_id, translator_id, title) VALUES (999, 12, "")'); - }, Nette\Database\ForeignKeyConstraintViolationException::class, '%a% Integrity constraint violation: %a%', '23000'); - - Assert::same(19, $e->getDriverCode()); - Assert::same($e->getCode(), $e->getSqlState()); + }, Nette\Database\ForeignKeyConstraintViolationException::class, '%a% Integrity constraint violation: %a%', 19); + Assert::same('23000', $e->getSqlState()); }); diff --git a/tests/Database/Connection.fetch.phpt b/tests/Database/Connection.fetch.phpt index 745d76c80..0d1bffaf6 100644 --- a/tests/Database/Connection.fetch.phpt +++ b/tests/Database/Connection.fetch.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); @@ -39,8 +39,8 @@ test('fetchField', function () use ($connection) { }); -test('fetchFields', function () use ($connection) { - Assert::same([11, 'Jakub Vrana'], $connection->fetchFields('SELECT id, name FROM author ORDER BY id')); +test('fetchList', function () use ($connection) { + Assert::same([11, 'Jakub Vrana'], $connection->fetchList('SELECT id, name FROM author ORDER BY id')); }); diff --git a/tests/Database/Connection.getInsertId().mysql.phpt b/tests/Database/Connection.getInsertId().mysql.phpt index 020ef596c..ffd7a71ff 100644 --- a/tests/Database/Connection.getInsertId().mysql.phpt +++ b/tests/Database/Connection.getInsertId().mysql.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); $connection->query(' CREATE TEMPORARY TABLE noprimarykey ( @@ -19,11 +19,11 @@ $connection->query(' ) ENGINE=InnoDB '); -$connection->query('INSERT INTO noprimarykey (col) VALUES (NULL)'); -Assert::same('0', $connection->getInsertId()); - $connection->query('INSERT INTO noprimarykey (col) VALUES (3)'); -Assert::same('0', $connection->getInsertId()); +Assert::exception( + fn() => $connection->getInsertId(), + Nette\Database\DriverException::class, +); $connection->query(' @@ -34,10 +34,10 @@ $connection->query(' '); $connection->query('INSERT INTO primarykey (prim) VALUES (5)'); -Assert::same('0', $connection->getInsertId()); - -$connection->query('INSERT INTO primarykey (prim) VALUES (6)'); -Assert::same('0', $connection->getInsertId()); +Assert::exception( + fn() => $connection->getInsertId(), + Nette\Database\DriverException::class, +); $connection->query(' @@ -49,13 +49,13 @@ $connection->query(' '); $connection->query('INSERT INTO autoprimarykey (col) VALUES (NULL)'); -Assert::same('1', $connection->getInsertId()); +Assert::same(1, $connection->getInsertId()); $connection->query('INSERT INTO autoprimarykey (col) VALUES (NULL)'); -Assert::same('2', $connection->getInsertId()); +Assert::same(2, $connection->getInsertId()); $connection->query('INSERT INTO autoprimarykey (prim, col) VALUES (10, NULL)'); -Assert::same('10', $connection->getInsertId()); +Assert::same(10, $connection->getInsertId()); $connection->query(' @@ -67,10 +67,10 @@ $connection->query(' '); $connection->query('INSERT INTO multiautoprimarykey (prim2) VALUES (3)'); -Assert::same('1', $connection->getInsertId()); +Assert::same(1, $connection->getInsertId()); $connection->query('INSERT INTO multiautoprimarykey (prim2) VALUES (3)'); -Assert::same('2', $connection->getInsertId()); +Assert::same(2, $connection->getInsertId()); $connection->query('INSERT INTO multiautoprimarykey (prim1, prim2) VALUES (10, 3)'); -Assert::same('10', $connection->getInsertId()); +Assert::same(10, $connection->getInsertId()); diff --git a/tests/Database/Connection.getInsertId().postgre.phpt b/tests/Database/Connection.getInsertId().postgre.phpt index 5c0a65c1c..41e2f10d5 100644 --- a/tests/Database/Connection.getInsertId().postgre.phpt +++ b/tests/Database/Connection.getInsertId().postgre.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); $connection->query(' CREATE TEMPORARY TABLE "primarykey" ( @@ -36,13 +36,13 @@ $connection->query(' '); $connection->query('INSERT INTO autoprimarykey (col) VALUES (NULL)'); -Assert::same('1', $connection->getInsertId('autoprimarykey_prim_seq')); +Assert::same(1, $connection->getInsertId('autoprimarykey_prim_seq')); $connection->query('INSERT INTO autoprimarykey (col) VALUES (NULL)'); -Assert::same('2', $connection->getInsertId('autoprimarykey_prim_seq')); +Assert::same(2, $connection->getInsertId('autoprimarykey_prim_seq')); $connection->query('INSERT INTO autoprimarykey (prim, col) VALUES (10, NULL)'); -Assert::same('2', $connection->getInsertId('autoprimarykey_prim_seq')); +Assert::same(2, $connection->getInsertId('autoprimarykey_prim_seq')); $connection->query(' @@ -54,10 +54,10 @@ $connection->query(' '); $connection->query('INSERT INTO multiautoprimarykey (prim2) VALUES (3)'); -Assert::same('1', $connection->getInsertId('multiautoprimarykey_prim1_seq')); +Assert::same(1, $connection->getInsertId('multiautoprimarykey_prim1_seq')); $connection->query('INSERT INTO multiautoprimarykey (prim2) VALUES (3)'); -Assert::same('2', $connection->getInsertId('multiautoprimarykey_prim1_seq')); +Assert::same(2, $connection->getInsertId('multiautoprimarykey_prim1_seq')); $connection->query('INSERT INTO multiautoprimarykey (prim1, prim2) VALUES (10, 3)'); -Assert::same('2', $connection->getInsertId('multiautoprimarykey_prim1_seq')); +Assert::same(2, $connection->getInsertId('multiautoprimarykey_prim1_seq')); diff --git a/tests/Database/Connection.getInsertId().sqlite.phpt b/tests/Database/Connection.getInsertId().sqlite.phpt index 26f57ac46..131431fca 100644 --- a/tests/Database/Connection.getInsertId().sqlite.phpt +++ b/tests/Database/Connection.getInsertId().sqlite.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); $connection->query(' CREATE TABLE [noprimarykey] ( @@ -20,10 +20,10 @@ $connection->query(' '); $connection->query('INSERT INTO noprimarykey (col) VALUES (NULL)'); -Assert::same('1', $connection->getInsertId()); +Assert::same(1, $connection->getInsertId()); $connection->query('INSERT INTO noprimarykey (col) VALUES (3)'); -Assert::same('2', $connection->getInsertId()); +Assert::same(2, $connection->getInsertId()); $connection->query(' @@ -33,10 +33,10 @@ $connection->query(' '); $connection->query('INSERT INTO primarykey (prim) VALUES (5)'); -Assert::same('5', $connection->getInsertId()); +Assert::same(5, $connection->getInsertId()); $connection->query('INSERT INTO primarykey (prim) VALUES (6)'); -Assert::same('6', $connection->getInsertId()); +Assert::same(6, $connection->getInsertId()); $connection->query(' @@ -47,10 +47,10 @@ $connection->query(' '); $connection->query('INSERT INTO autoprimarykey (col) VALUES (NULL)'); -Assert::same('1', $connection->getInsertId()); +Assert::same(1, $connection->getInsertId()); $connection->query('INSERT INTO autoprimarykey (col) VALUES (NULL)'); -Assert::same('2', $connection->getInsertId()); +Assert::same(2, $connection->getInsertId()); $connection->query('INSERT INTO autoprimarykey (prim, col) VALUES (10, NULL)'); -Assert::same('10', $connection->getInsertId()); +Assert::same(10, $connection->getInsertId()); diff --git a/tests/Database/Connection.getInsertId().sqlsrv.phpt b/tests/Database/Connection.getInsertId().sqlsrv.phpt index e971245c7..627fa45d0 100644 --- a/tests/Database/Connection.getInsertId().sqlsrv.phpt +++ b/tests/Database/Connection.getInsertId().sqlsrv.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); $connection->query("IF OBJECT_ID('noprimarykey', 'U') IS NOT NULL DROP TABLE noprimarykey"); $connection->query(' @@ -21,10 +21,10 @@ $connection->query(' '); $connection->query('INSERT INTO noprimarykey (col) VALUES (NULL)'); -Assert::same('', $connection->getInsertId()); - -$connection->query('INSERT INTO noprimarykey (col) VALUES (NULL)'); -Assert::same('', $connection->getInsertId()); +Assert::exception( + fn() => $connection->getInsertId(), + Nette\Database\DriverException::class, +); $connection->query("IF OBJECT_ID('primarykey', 'U') IS NOT NULL DROP TABLE primarykey"); @@ -36,10 +36,10 @@ $connection->query(' '); $connection->query('INSERT INTO primarykey (prim) VALUES (5)'); -Assert::same('', $connection->getInsertId()); - -$connection->query('INSERT INTO primarykey (prim) VALUES (6)'); -Assert::same('', $connection->getInsertId()); +Assert::exception( + fn() => $connection->getInsertId(), + Nette\Database\DriverException::class, +); $connection->query("IF OBJECT_ID('autoprimarykey', 'U') IS NOT NULL DROP TABLE autoprimarykey"); @@ -52,13 +52,13 @@ $connection->query(' '); $connection->query('INSERT INTO autoprimarykey (col) VALUES (NULL)'); -Assert::same('1', $connection->getInsertId()); +Assert::same(1, $connection->getInsertId()); $connection->query('INSERT INTO autoprimarykey (col) VALUES (NULL)'); -Assert::same('2', $connection->getInsertId()); +Assert::same(2, $connection->getInsertId()); $connection->query('SET IDENTITY_INSERT autoprimarykey ON; INSERT INTO autoprimarykey (prim, col) VALUES (10, NULL)'); -Assert::same('10', $connection->getInsertId()); +Assert::same(10, $connection->getInsertId()); $connection->query("IF OBJECT_ID('multiautoprimarykey', 'U') IS NOT NULL DROP TABLE multiautoprimarykey"); @@ -71,10 +71,10 @@ $connection->query(' '); $connection->query('INSERT INTO multiautoprimarykey (prim2) VALUES (3)'); -Assert::same('1', $connection->getInsertId()); +Assert::same(1, $connection->getInsertId()); $connection->query('INSERT INTO multiautoprimarykey (prim2) VALUES (3)'); -Assert::same('2', $connection->getInsertId()); +Assert::same(2, $connection->getInsertId()); $connection->query('SET IDENTITY_INSERT multiautoprimarykey ON; INSERT INTO multiautoprimarykey (prim1, prim2) VALUES (10, 3)'); -Assert::same('10', $connection->getInsertId()); +Assert::same(10, $connection->getInsertId()); diff --git a/tests/Database/Connection.preprocess.phpt b/tests/Database/Connection.preprocess.phpt index 5f8cd5547..1a59e3f80 100644 --- a/tests/Database/Connection.preprocess.phpt +++ b/tests/Database/Connection.preprocess.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Assert::same(['SELECT name FROM author', []], $connection->preprocess('SELECT name FROM author')); diff --git a/tests/Database/Connection.query.phpt b/tests/Database/Connection.query.phpt index 1a1d60ce0..7e4633b61 100644 --- a/tests/Database/Connection.query.phpt +++ b/tests/Database/Connection.query.phpt @@ -11,28 +11,28 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('', function () use ($connection) { $res = $connection->query('SELECT id FROM author WHERE id = ?', 11); - Assert::type(Nette\Database\ResultSet::class, $res); - Assert::same('SELECT id FROM author WHERE id = ?', $res->getQueryString()); - Assert::same([11], $res->getParameters()); - Assert::same('SELECT id FROM author WHERE id = ?', $connection->getLastQueryString()); + Assert::type(Nette\Database\Result::class, $res); + Assert::same('SELECT id FROM author WHERE id = ?', $res->getQuery()->getSql()); + Assert::same([11], $res->getQuery()->getParameters()); + Assert::same('SELECT id FROM author WHERE id = ?', $connection->getLastQuery()->getSql()); }); test('', function () use ($connection) { $res = $connection->query('SELECT id FROM author WHERE id = ? OR id = ?', 11, 12); - Assert::same('SELECT id FROM author WHERE id = ? OR id = ?', $res->getQueryString()); - Assert::same([11, 12], $res->getParameters()); + Assert::same('SELECT id FROM author WHERE id = ? OR id = ?', $res->getQuery()->getSql()); + Assert::same([11, 12], $res->getQuery()->getParameters()); }); test('', function () use ($connection) { - $res = $connection->queryArgs('SELECT id FROM author WHERE id = ? OR id = ?', [11, 12]); - Assert::same('SELECT id FROM author WHERE id = ? OR id = ?', $res->getQueryString()); - Assert::same([11, 12], $res->getParameters()); + $res = @$connection->queryArgs('SELECT id FROM author WHERE id = ? OR id = ?', [11, 12]); // is deprecated + Assert::same('SELECT id FROM author WHERE id = ? OR id = ?', $res->getQuery()->getSql()); + Assert::same([11, 12], $res->getQuery()->getParameters()); }); diff --git a/tests/Database/Connection.transaction.phpt b/tests/Database/Connection.transaction.phpt index 91d6f6fdc..26a3fc9bc 100644 --- a/tests/Database/Connection.transaction.phpt +++ b/tests/Database/Connection.transaction.phpt @@ -7,12 +7,12 @@ declare(strict_types=1); -use Nette\Database\Connection; +use Nette\Database\Explorer; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); @@ -27,7 +27,7 @@ test('', function () use ($connection) { test('', function () use ($connection) { Assert::exception( - fn() => $connection->transaction(function (Connection $connection) { + fn() => $connection->transaction(function (Explorer $connection) { $connection->query('DELETE FROM book'); throw new Exception('my exception'); }), @@ -52,13 +52,13 @@ test('nested transaction() call fail', function () use ($connection) { $base = (int) $connection->query('SELECT COUNT(*) AS cnt FROM author')->fetchField(); Assert::exception( - fn() => $connection->transaction(function (Connection $connection) { + fn() => $connection->transaction(function (Explorer $connection) { $connection->query('INSERT INTO author', [ 'name' => 'A', 'web' => '', ]); - $connection->transaction(function (Connection $connection2) { + $connection->transaction(function (Explorer $connection2) { $connection2->query('INSERT INTO author', [ 'name' => 'B', 'web' => '', @@ -77,7 +77,7 @@ test('nested transaction() call fail', function () use ($connection) { test('nested transaction() call success', function () use ($connection) { $base = (int) $connection->query('SELECT COUNT(*) AS cnt FROM author')->fetchField(); - $connection->transaction(function (Connection $connection) { + $connection->transaction(function (Explorer $connection) { $connection->query('INSERT INTO author', [ 'name' => 'A', 'web' => '', @@ -97,18 +97,18 @@ test('beginTransaction(), commit() & rollBack() calls are forbidden in transacti Assert::exception( fn() => $connection->transaction(fn() => $connection->beginTransaction()), LogicException::class, - Connection::class . '::beginTransaction() call is forbidden inside a transaction() callback', + Explorer::class . '::beginTransaction() call is forbidden inside a transaction() callback', ); Assert::exception( fn() => $connection->transaction(fn() => $connection->commit()), LogicException::class, - Connection::class . '::commit() call is forbidden inside a transaction() callback', + Explorer::class . '::commit() call is forbidden inside a transaction() callback', ); Assert::exception( fn() => $connection->transaction(fn() => $connection->rollBack()), LogicException::class, - Connection::class . '::rollBack() call is forbidden inside a transaction() callback', + Explorer::class . '::rollBack() call is forbidden inside a transaction() callback', ); }); diff --git a/tests/Database/Conventions/DiscoveredConventions.getBelongsToReference().phpt b/tests/Database/Conventions/DiscoveredConventions.getBelongsToReference().phpt index 7234910f3..5b3ec9430 100644 --- a/tests/Database/Conventions/DiscoveredConventions.getBelongsToReference().phpt +++ b/tests/Database/Conventions/DiscoveredConventions.getBelongsToReference().phpt @@ -13,7 +13,7 @@ require __DIR__ . '/../../bootstrap.php'; test('basic test', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getBelongsToReference')->with('books')->andReturn([ 'author_id' => 'authors', 'translator_id' => 'authors', @@ -26,7 +26,7 @@ test('basic test', function () { }); test('basic test', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getBelongsToReference')->with('public.books')->andReturn([ 'author_id' => 'public.authors', 'translator_id' => 'public.authors', @@ -39,7 +39,7 @@ test('basic test', function () { }); test('tests order of table columns with foreign keys', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getBelongsToReference')->with('books')->andReturn([ 'translator_id' => 'authors', 'author_id' => 'authors', @@ -52,7 +52,7 @@ test('tests order of table columns with foreign keys', function () { test('tests case insensivity', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getBelongsToReference')->with('books')->andReturn([ 'author_id' => 'authors', 'translator_id' => 'authors', @@ -65,7 +65,7 @@ test('tests case insensivity', function () { test('tests case insensivity and prefixes', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getBelongsToReference')->with('nBooks')->andReturn([ 'authorId' => 'nAuthors', 'translatorId' => 'nAuthors', @@ -79,7 +79,7 @@ test('tests case insensivity and prefixes', function () { test('tests rebuilt', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('isRebuilt')->andReturn(false); $structure->shouldReceive('rebuild'); $structure->shouldReceive('getBelongsToReference')->andReturn([])->once(); @@ -96,7 +96,7 @@ test('tests rebuilt', function () { test('tests already rebuilt structure', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('isRebuilt')->andReturn(true); $structure->shouldReceive('getBelongsToReference')->with('books')->andReturn([])->once(); diff --git a/tests/Database/Conventions/DiscoveredConventions.getHasManyReference().phpt b/tests/Database/Conventions/DiscoveredConventions.getHasManyReference().phpt index a7dc0255d..ed14a18ab 100644 --- a/tests/Database/Conventions/DiscoveredConventions.getHasManyReference().phpt +++ b/tests/Database/Conventions/DiscoveredConventions.getHasManyReference().phpt @@ -13,7 +13,7 @@ require __DIR__ . '/../../bootstrap.php'; test('basic test singular', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getHasManyReference')->with('author')->andReturn([ 'book' => ['author_id', 'translator_id'], 'book_topics' => ['author_id'], @@ -43,7 +43,7 @@ test('basic test singular', function () { test('basic test singular with schema', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getHasManyReference')->with('public.author')->andReturn([ 'public.book' => ['author_id', 'translator_id'], 'public.book_topics' => ['author_id'], @@ -88,7 +88,7 @@ test('basic test singular with schema', function () { test('basic test plural', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getHasManyReference')->with('authors')->andReturn([ 'books' => ['author_id', 'translator_id'], ])->once(); @@ -112,7 +112,7 @@ test('basic test plural', function () { test('tests column match with source table', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getHasManyReference')->with('author')->andReturn([ 'book' => ['author_id', 'tran_id'], ])->once(); @@ -139,7 +139,7 @@ test('tests column match with source table', function () { test('tests case insensivity and prefixes', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getHasManyReference')->with('nAuthors')->andReturn([ 'nBooks' => ['authorId', 'translatorId'], ])->once(); @@ -151,7 +151,7 @@ test('tests case insensivity and prefixes', function () { test('tests rebuilt', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('isRebuilt')->andReturn(false); $structure->shouldReceive('rebuild'); $structure->shouldReceive('getHasManyReference')->with('author')->andReturn([])->once(); @@ -165,7 +165,7 @@ test('tests rebuilt', function () { test('tests already rebuilt structure', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('isRebuilt')->andReturn(true); $structure->shouldReceive('getHasManyReference')->with('author')->andReturn([])->once(); diff --git a/tests/Database/Conventions/DiscoveredConventions.getPrimary().phpt b/tests/Database/Conventions/DiscoveredConventions.getPrimary().phpt index 196c4e0e6..f68b38f5c 100644 --- a/tests/Database/Conventions/DiscoveredConventions.getPrimary().phpt +++ b/tests/Database/Conventions/DiscoveredConventions.getPrimary().phpt @@ -13,7 +13,7 @@ require __DIR__ . '/../../bootstrap.php'; test('', function () { - $structure = Mockery::mock(Nette\Database\IStructure::class); + $structure = Mockery::mock(Nette\Database\Structure::class); $structure->shouldReceive('getPrimaryKey')->with('books_x_tags')->andReturn(['book_id', 'tag_id']); $conventions = new DiscoveredConventions($structure); diff --git a/tests/Database/Drivers/MsSqlDriver.applyLimit.phpt b/tests/Database/Drivers/MsSqlDriver.applyLimit.phpt deleted file mode 100644 index 636142214..000000000 --- a/tests/Database/Drivers/MsSqlDriver.applyLimit.phpt +++ /dev/null @@ -1,60 +0,0 @@ -applyLimit($query, 10, 20); -}, Nette\NotSupportedException::class, 'Offset is not supported by this database.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, 0, 20); -}, Nette\NotSupportedException::class, 'Offset is not supported by this database.'); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, 0); -Assert::same('SELECT TOP 10 1 FROM t', $query); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, 20); -}, Nette\NotSupportedException::class, 'Offset is not supported by this database.'); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same('SELECT TOP 10 1 FROM t', $query); - -$query = ' select distinct 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same(' select distinct TOP 10 1 FROM t', $query); - -$query = 'UPDATE t SET'; -$driver->applyLimit($query, 10, null); -Assert::same('UPDATE TOP 10 t SET', $query); - -$query = 'DELETE FROM t SET'; -$driver->applyLimit($query, 10, null); -Assert::same('DELETE TOP 10 FROM t SET', $query); - -Assert::exception(function () use ($driver) { - $query = 'SET FROM t'; - $driver->applyLimit($query, 10, null); -}, Nette\InvalidArgumentException::class, 'SQL query must begin with SELECT, UPDATE or DELETE command.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, -1, null); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, -1); -}, Nette\NotSupportedException::class, 'Offset is not supported by this database.'); diff --git a/tests/Database/Drivers/MySqlDriver.applyLimit.phpt b/tests/Database/Drivers/MySqlDriver.applyLimit.phpt deleted file mode 100644 index 8434fe2f5..000000000 --- a/tests/Database/Drivers/MySqlDriver.applyLimit.phpt +++ /dev/null @@ -1,44 +0,0 @@ -getConnection(); -$driver = $connection->getDriver(); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, 20); -Assert::same('SELECT 1 FROM t LIMIT 10 OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 0, 20); -Assert::same('SELECT 1 FROM t LIMIT 0 OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, 0); -Assert::same('SELECT 1 FROM t LIMIT 10', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, null, 20); -Assert::same('SELECT 1 FROM t LIMIT 18446744073709551615 OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same('SELECT 1 FROM t LIMIT 10', $query); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, -1, null); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, -1); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); diff --git a/tests/Database/Drivers/MySqlDriver.formatLike.phpt b/tests/Database/Drivers/MySqlDriver.formatLike.phpt deleted file mode 100644 index 45d7fa032..000000000 --- a/tests/Database/Drivers/MySqlDriver.formatLike.phpt +++ /dev/null @@ -1,32 +0,0 @@ -getConnection(); -$driver = $connection->getDriver(); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A_B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA_BB' LIKE", $connection::literal($driver->formatLike('A_B', 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A%B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA%BB' LIKE", $connection::literal($driver->formatLike('A%B', 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike("A'B", 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA''BB' LIKE", $connection::literal($driver->formatLike("A'B", 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A"B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA\"BB' LIKE", $connection::literal($driver->formatLike('A"B', 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A\B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA\\\\BB' LIKE", $connection::literal($driver->formatLike('A\B', 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A\%B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA\\\\%BB' LIKE", $connection::literal($driver->formatLike('A\%B', 0)))->fetchField()); diff --git a/tests/Database/Drivers/OciDriver.applyLimit.phpt b/tests/Database/Drivers/OciDriver.applyLimit.phpt deleted file mode 100644 index c446108f5..000000000 --- a/tests/Database/Drivers/OciDriver.applyLimit.phpt +++ /dev/null @@ -1,40 +0,0 @@ -applyLimit($query, 10, 20); -Assert::same('SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (SELECT 1 FROM t) t WHERE ROWNUM <= 30) WHERE "__rnum" > 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 0, 20); -Assert::same('SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (SELECT 1 FROM t) t WHERE ROWNUM <= 20) WHERE "__rnum" > 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, 0); -Assert::same('SELECT * FROM (SELECT 1 FROM t) WHERE ROWNUM <= 10', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, null, 20); -Assert::same('SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (SELECT 1 FROM t) t ) WHERE "__rnum" > 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same('SELECT * FROM (SELECT 1 FROM t) WHERE ROWNUM <= 10', $query); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, -1, null); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, -1); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); diff --git a/tests/Database/Drivers/OdbcDriver.applyLimit.phpt b/tests/Database/Drivers/OdbcDriver.applyLimit.phpt deleted file mode 100644 index b40e3f03e..000000000 --- a/tests/Database/Drivers/OdbcDriver.applyLimit.phpt +++ /dev/null @@ -1,60 +0,0 @@ -applyLimit($query, 10, 20); -}, Nette\NotSupportedException::class, 'Offset is not supported by this database.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, 0, 20); -}, Nette\NotSupportedException::class, 'Offset is not supported by this database.'); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, 0); -Assert::same('SELECT TOP 10 1 FROM t', $query); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, 20); -}, Nette\NotSupportedException::class, 'Offset is not supported by this database.'); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same('SELECT TOP 10 1 FROM t', $query); - -$query = ' select distinct 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same(' select distinct TOP 10 1 FROM t', $query); - -$query = 'UPDATE t SET'; -$driver->applyLimit($query, 10, null); -Assert::same('UPDATE TOP 10 t SET', $query); - -$query = 'DELETE FROM t SET'; -$driver->applyLimit($query, 10, null); -Assert::same('DELETE TOP 10 FROM t SET', $query); - -Assert::exception(function () use ($driver) { - $query = 'SET FROM t'; - $driver->applyLimit($query, 10, null); -}, Nette\InvalidArgumentException::class, 'SQL query must begin with SELECT, UPDATE or DELETE command.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, -1, null); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, -1); -}, Nette\NotSupportedException::class, 'Offset is not supported by this database.'); diff --git a/tests/Database/Drivers/PgSqlDriver.applyLimit.phpt b/tests/Database/Drivers/PgSqlDriver.applyLimit.phpt deleted file mode 100644 index 6516e93d6..000000000 --- a/tests/Database/Drivers/PgSqlDriver.applyLimit.phpt +++ /dev/null @@ -1,40 +0,0 @@ -applyLimit($query, 10, 20); -Assert::same('SELECT 1 FROM t LIMIT 10 OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 0, 20); -Assert::same('SELECT 1 FROM t LIMIT 0 OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, 0); -Assert::same('SELECT 1 FROM t LIMIT 10', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, null, 20); -Assert::same('SELECT 1 FROM t OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same('SELECT 1 FROM t LIMIT 10', $query); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, -1, null); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, -1); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); diff --git a/tests/Database/Drivers/PgSqlDriver.formatLike.phpt b/tests/Database/Drivers/PgSqlDriver.formatLike.phpt deleted file mode 100644 index 2e85ec7bb..000000000 --- a/tests/Database/Drivers/PgSqlDriver.formatLike.phpt +++ /dev/null @@ -1,42 +0,0 @@ -getConnection(); - -$tests = function ($connection) { - $driver = $connection->getDriver(); - - Assert::false($connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A_B', 0)))->fetchField()); - Assert::true($connection->query("SELECT 'AA_BB' LIKE", $connection::literal($driver->formatLike('A_B', 0)))->fetchField()); - - Assert::false($connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A%B', 0)))->fetchField()); - Assert::true($connection->query("SELECT 'AA%BB' LIKE", $connection::literal($driver->formatLike('A%B', 0)))->fetchField()); - - Assert::false($connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike("A'B", 0)))->fetchField()); - Assert::true($connection->query("SELECT 'AA''BB' LIKE", $connection::literal($driver->formatLike("A'B", 0)))->fetchField()); - - Assert::false($connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A"B', 0)))->fetchField()); - Assert::true($connection->query("SELECT 'AA\"BB' LIKE", $connection::literal($driver->formatLike('A"B', 0)))->fetchField()); -}; - -$driver = $connection->getDriver(); -$connection->query('SET escape_string_warning TO off'); // do not log warnings - -$connection->query('SET standard_conforming_strings TO on'); -$tests($connection); -Assert::false($connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A\\B', 0)))->fetchField()); -Assert::true($connection->query("SELECT 'AA\\BB' LIKE", $connection::literal($driver->formatLike('A\\B', 0)))->fetchField()); - -$connection->query('SET standard_conforming_strings TO off'); -$tests($connection); -Assert::false($connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A\\B', 0)))->fetchField()); -Assert::true($connection->query("SELECT 'AA\\\\BB' LIKE", $connection::literal($driver->formatLike('A\\B', 0)))->fetchField()); diff --git a/tests/Database/Drivers/SqliteDriver.applyLimit.phpt b/tests/Database/Drivers/SqliteDriver.applyLimit.phpt deleted file mode 100644 index 662cb5d76..000000000 --- a/tests/Database/Drivers/SqliteDriver.applyLimit.phpt +++ /dev/null @@ -1,40 +0,0 @@ -applyLimit($query, 10, 20); -Assert::same('SELECT 1 FROM t LIMIT 10 OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 0, 20); -Assert::same('SELECT 1 FROM t LIMIT 0 OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, 0); -Assert::same('SELECT 1 FROM t LIMIT 10', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, null, 20); -Assert::same('SELECT 1 FROM t LIMIT -1 OFFSET 20', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same('SELECT 1 FROM t LIMIT 10', $query); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, -1, null); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, -1); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); diff --git a/tests/Database/Drivers/SqliteDriver.formatLike.phpt b/tests/Database/Drivers/SqliteDriver.formatLike.phpt deleted file mode 100644 index 2b2443957..000000000 --- a/tests/Database/Drivers/SqliteDriver.formatLike.phpt +++ /dev/null @@ -1,32 +0,0 @@ -getConnection(); -$driver = $connection->getDriver(); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A_B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA_BB' LIKE", $connection::literal($driver->formatLike('A_B', 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A%B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA%BB' LIKE", $connection::literal($driver->formatLike('A%B', 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike("A'B", 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA''BB' LIKE", $connection::literal($driver->formatLike("A'B", 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A"B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA\"BB' LIKE", $connection::literal($driver->formatLike('A"B', 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A\B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA\\BB' LIKE", $connection::literal($driver->formatLike('A\B', 0)))->fetchField()); - -Assert::same(0, $connection->query("SELECT 'AAxBB' LIKE", $connection::literal($driver->formatLike('A\%B', 0)))->fetchField()); -Assert::same(1, $connection->query("SELECT 'AA\\%BB' LIKE", $connection::literal($driver->formatLike('A\%B', 0)))->fetchField()); diff --git a/tests/Database/Drivers/SqlsrvDriver.applyLimit.phpt b/tests/Database/Drivers/SqlsrvDriver.applyLimit.phpt deleted file mode 100644 index 3a06219af..000000000 --- a/tests/Database/Drivers/SqlsrvDriver.applyLimit.phpt +++ /dev/null @@ -1,42 +0,0 @@ -applyLimit($query, 10, 20); -Assert::same('SELECT 1 FROM t OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 0, 20); -Assert::same('SELECT 1 FROM t OFFSET 20 ROWS FETCH NEXT 0 ROWS ONLY', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, 0); -Assert::same('SELECT 1 FROM t OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, null, 20); -Assert::same('SELECT 1 FROM t OFFSET 20 ROWS FETCH NEXT 0 ROWS ONLY', $query); - -$query = 'SELECT 1 FROM t'; -$driver->applyLimit($query, 10, null); -Assert::same('SELECT 1 FROM t OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', $query); - - - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, -1, null); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); - -Assert::exception(function () use ($driver) { - $query = 'SELECT 1 FROM t'; - $driver->applyLimit($query, null, -1); -}, Nette\InvalidArgumentException::class, 'Negative offset or limit.'); diff --git a/tests/Database/Drivers/SqlsrvDriver.formatLike.phpt b/tests/Database/Drivers/SqlsrvDriver.formatLike.phpt deleted file mode 100644 index cd5ccc3f1..000000000 --- a/tests/Database/Drivers/SqlsrvDriver.formatLike.phpt +++ /dev/null @@ -1,35 +0,0 @@ -getConnection(); -$driver = $connection->getDriver(); - -Assert::same(0, $connection->query("SELECT CASE WHEN 'AAxBB' LIKE", $connection::literal($driver->formatLike('A_B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); -Assert::same(1, $connection->query("SELECT CASE WHEN 'AA_BB' LIKE", $connection::literal($driver->formatLike('A_B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); - -Assert::same(0, $connection->query("SELECT CASE WHEN 'AAxBB' LIKE", $connection::literal($driver->formatLike('A%B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); -Assert::same(1, $connection->query("SELECT CASE WHEN 'AA%BB' LIKE", $connection::literal($driver->formatLike('A%B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); - -Assert::same(0, $connection->query("SELECT CASE WHEN 'AAxBB' LIKE", $connection::literal($driver->formatLike("A'B", 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); -Assert::same(1, $connection->query("SELECT CASE WHEN 'AA''BB' LIKE", $connection::literal($driver->formatLike("A'B", 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); - -Assert::same(0, $connection->query("SELECT CASE WHEN 'AAxBB' LIKE", $connection::literal($driver->formatLike('A"B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); -Assert::same(1, $connection->query("SELECT CASE WHEN 'AA\"BB' LIKE", $connection::literal($driver->formatLike('A"B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); - -Assert::same(0, $connection->query("SELECT CASE WHEN 'AAxBB' LIKE", $connection::literal($driver->formatLike('A\B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); -Assert::same(1, $connection->query("SELECT CASE WHEN 'AA\\BB' LIKE", $connection::literal($driver->formatLike('A\B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); - -Assert::same(0, $connection->query("SELECT CASE WHEN 'AAxBB' LIKE", $connection::literal($driver->formatLike('A\%B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); -Assert::same(1, $connection->query("SELECT CASE WHEN 'AA\\%BB' LIKE", $connection::literal($driver->formatLike('A\%B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); - -Assert::same(0, $connection->query("SELECT CASE WHEN 'AAxBB' LIKE", $connection::literal($driver->formatLike('A[a-z]B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); -Assert::same(1, $connection->query("SELECT CASE WHEN 'AA[a-z]BB' LIKE", $connection::literal($driver->formatLike('A[a-z]B', 0)), 'THEN 1 ELSE 0 END AS col')->fetchField()); diff --git a/tests/Database/Reflection.postgre.10.phpt b/tests/Database/Engine.postgre.10.phpt similarity index 76% rename from tests/Database/Reflection.postgre.10.phpt rename to tests/Database/Engine.postgre.10.phpt index 2b6191da0..c6e64379e 100644 --- a/tests/Database/Reflection.postgre.10.phpt +++ b/tests/Database/Engine.postgre.10.phpt @@ -12,7 +12,7 @@ use Tester\Environment; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); $ver = $connection->query('SHOW server_version')->fetchField(); if (version_compare($ver, '10') < 0) { @@ -24,13 +24,13 @@ function shortInfo(array $columns): array { return array_map(fn(array $col): array => [ 'name' => $col['name'], - 'autoincrement' => $col['autoincrement'], + 'autoIncrement' => $col['autoIncrement'], 'sequence' => $col['vendor']['sequence'], ], $columns); } -test('SERIAL and IDENTITY imply autoincrement on primary keys', function () use ($connection) { +test('SERIAL and IDENTITY imply autoIncrement on primary keys', function () use ($connection) { Nette\Database\Helpers::loadFromFile($connection, Tester\FileMock::create(' DROP SCHEMA IF EXISTS "reflection_10" CASCADE; CREATE SCHEMA "reflection_10"; @@ -45,53 +45,53 @@ test('SERIAL and IDENTITY imply autoincrement on primary keys', function () use CREATE TABLE "reflection_10"."identity_by_default_pk" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY); ')); - $driver = $connection->getDriver(); + $engine = $connection->getDatabaseEngine(); $connection->query('SET search_path TO reflection_10'); $columns = [ - 'serial' => shortInfo($driver->getColumns('serial')), - 'serial_pk' => shortInfo($driver->getColumns('serial_pk')), - 'identity_always' => shortInfo($driver->getColumns('identity_always')), - 'identity_always_pk' => shortInfo($driver->getColumns('identity_always_pk')), - 'identity_by_default' => shortInfo($driver->getColumns('identity_by_default')), - 'identity_by_default_pk' => shortInfo($driver->getColumns('identity_by_default_pk')), + 'serial' => shortInfo($engine->getColumns('serial')), + 'serial_pk' => shortInfo($engine->getColumns('serial_pk')), + 'identity_always' => shortInfo($engine->getColumns('identity_always')), + 'identity_always_pk' => shortInfo($engine->getColumns('identity_always_pk')), + 'identity_by_default' => shortInfo($engine->getColumns('identity_by_default')), + 'identity_by_default_pk' => shortInfo($engine->getColumns('identity_by_default_pk')), ]; Assert::same([ 'serial' => [[ 'name' => 'id', - 'autoincrement' => false, + 'autoIncrement' => false, 'sequence' => 'serial_id_seq', ]], 'serial_pk' => [[ 'name' => 'id', - 'autoincrement' => true, + 'autoIncrement' => true, 'sequence' => 'serial_pk_id_seq', ]], 'identity_always' => [[ 'name' => 'id', - 'autoincrement' => false, + 'autoIncrement' => false, 'sequence' => 'identity_always_id_seq', ]], 'identity_always_pk' => [[ 'name' => 'id', - 'autoincrement' => true, + 'autoIncrement' => true, 'sequence' => 'identity_always_pk_id_seq', ]], 'identity_by_default' => [[ 'name' => 'id', - 'autoincrement' => false, + 'autoIncrement' => false, 'sequence' => 'identity_by_default_id_seq', ]], 'identity_by_default_pk' => [[ 'name' => 'id', - 'autoincrement' => true, + 'autoIncrement' => true, 'sequence' => 'identity_by_default_pk_id_seq', ]], ], $columns); @@ -111,18 +111,18 @@ test('Materialized view columns', function () use ($connection) { CREATE MATERIALIZED VIEW "reflection_10"."source_mt" AS SELECT "name", "id" FROM "reflection_10"."source"; ')); - $driver = $connection->getDriver(); + $engine = $connection->getDatabaseEngine(); $connection->query('SET search_path TO reflection_10'); Assert::same([ ['name' => 'source', 'view' => false, 'fullName' => 'reflection_10.source'], ['name' => 'source_mt', 'view' => true, 'fullName' => 'reflection_10.source_mt'], - ], $driver->getTables()); + ], $engine->getTables()); Assert::same( ['name', 'id'], - array_column($driver->getColumns('source_mt'), 'name'), + array_column($engine->getColumns('source_mt'), 'name'), ); }); @@ -140,22 +140,22 @@ test('Partitioned table', function () use ($connection) { CREATE TABLE "reflection_10"."part_1" PARTITION OF "reflection_10"."parted" FOR VALUES FROM (1) TO (10); ')); - $driver = $connection->getDriver(); + $engine = $connection->getDatabaseEngine(); $connection->query('SET search_path TO reflection_10'); Assert::same([ ['name' => 'part_1', 'view' => false, 'fullName' => 'reflection_10.part_1'], ['name' => 'parted', 'view' => false, 'fullName' => 'reflection_10.parted'], - ], $driver->getTables()); + ], $engine->getTables()); - Assert::same(['id', 'value'], array_column($driver->getColumns('parted'), 'name')); - Assert::same(['id', 'value'], array_column($driver->getColumns('part_1'), 'name')); + Assert::same(['id', 'value'], array_column($engine->getColumns('parted'), 'name')); + Assert::same(['id', 'value'], array_column($engine->getColumns('part_1'), 'name')); Assert::same([[ 'name' => 'parted_pkey', 'unique' => true, 'primary' => true, 'columns' => ['id'], - ]], $driver->getIndexes('parted')); + ]], $engine->getIndexes('parted')); }); diff --git a/tests/Database/Reflection.postgre.phpt b/tests/Database/Engine.postgre.phpt similarity index 66% rename from tests/Database/Reflection.postgre.phpt rename to tests/Database/Engine.postgre.phpt index 53393629a..89fc5dd90 100644 --- a/tests/Database/Reflection.postgre.phpt +++ b/tests/Database/Engine.postgre.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); function names($columns): array @@ -43,38 +43,38 @@ test('Tables in schema', function () use ($connection) { ALTER TABLE "two"."slave" ADD CONSTRAINT "two_slave_fk" FOREIGN KEY ("two_id") REFERENCES "two"."master"("two_id"); ')); - $driver = $connection->getDriver(); + $engine = $connection->getDatabaseEngine(); // Reflection for tables with the same name but different schema $connection->query('SET search_path TO one, two'); - Assert::same(['master', 'slave'], names($driver->getTables())); - Assert::same(['one_id'], names($driver->getColumns('master'))); - Assert::same(['one_master_pkey'], names($driver->getIndexes('master'))); - Assert::same(['one_slave_fk'], names($driver->getForeignKeys('slave'))); + Assert::same(['master', 'slave'], names($engine->getTables())); + Assert::same(['one_id'], names($engine->getColumns('master'))); + Assert::same(['one_master_pkey'], names($engine->getIndexes('master'))); + Assert::same(['one_slave_fk'], names($engine->getForeignKeys('slave'))); $connection->query('SET search_path TO two, one'); - Assert::same(['master', 'slave'], names($driver->getTables())); - Assert::same(['two_id'], names($driver->getColumns('master'))); - Assert::same(['two_master_pkey'], names($driver->getIndexes('master'))); - Assert::same(['two_slave_fk'], names($driver->getForeignKeys('slave'))); + Assert::same(['master', 'slave'], names($engine->getTables())); + Assert::same(['two_id'], names($engine->getColumns('master'))); + Assert::same(['two_master_pkey'], names($engine->getIndexes('master'))); + Assert::same(['two_slave_fk'], names($engine->getForeignKeys('slave'))); // Reflection for FQN - Assert::same(['one_id'], names($driver->getColumns('one.master'))); - Assert::same(['one_master_pkey'], names($driver->getIndexes('one.master'))); - $foreign = $driver->getForeignKeys('one.slave'); + Assert::same(['one_id'], names($engine->getColumns('one.master'))); + Assert::same(['one_master_pkey'], names($engine->getIndexes('one.master'))); + $foreign = $engine->getForeignKeys('one.slave'); Assert::same([ 'name' => 'one_slave_fk', - 'local' => 'one_id', + 'local' => ['one_id'], 'table' => 'one.master', - 'foreign' => 'one_id', + 'foreign' => ['one_id'], ], (array) $foreign[0]); // Limit foreign keys for current schemas only $connection->query('ALTER TABLE "one"."slave" ADD CONSTRAINT "one_two_fk" FOREIGN KEY ("one_id") REFERENCES "two"."master"("two_id")'); $connection->query('SET search_path TO one'); - Assert::same(['one_slave_fk'], names($driver->getForeignKeys('slave'))); + Assert::same(['one_slave_fk'], names($engine->getForeignKeys('slave'))); $connection->query('SET search_path TO one, two'); - Assert::same(['one_slave_fk', 'one_two_fk'], names($driver->getForeignKeys('slave'))); + Assert::same(['one_slave_fk', 'one_two_fk'], names($engine->getForeignKeys('slave'))); }); diff --git a/tests/Database/Reflection.driver.phpt b/tests/Database/Engine.reflection.phpt similarity index 79% rename from tests/Database/Reflection.driver.phpt rename to tests/Database/Engine.reflection.phpt index 04c676bf8..43d0449ed 100644 --- a/tests/Database/Reflection.driver.phpt +++ b/tests/Database/Engine.reflection.phpt @@ -7,23 +7,22 @@ declare(strict_types=1); -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/files/{$driverName}-nette_test1.sql"); -$driver = $connection->getDriver(); -$tables = $driver->getTables(); +$engine = $explorer->getDatabaseEngine(); +$tables = $engine->getTables(); $tables = array_filter($tables, fn($t) => in_array($t['name'], ['author', 'book', 'book_tag', 'tag'], true)); usort($tables, fn($a, $b) => strcmp($a['name'], $b['name'])); -if ($driver->isSupported(Driver::SupportSchema)) { +if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( [ ['name' => 'author', 'view' => false, 'fullName' => 'public.author'], @@ -43,7 +42,7 @@ if ($driver->isSupported(Driver::SupportSchema)) { } -$columns = $driver->getColumns('author'); +$columns = $engine->getColumns('author'); array_walk($columns, function (&$item) { Assert::type('array', $item['vendor']); unset($item['vendor']); @@ -53,64 +52,68 @@ $expectedColumns = [ [ 'name' => 'id', 'table' => 'author', - 'nativetype' => 'INT', + 'nativeType' => 'INT', 'size' => 11, + 'scale' => null, 'nullable' => false, 'default' => null, - 'autoincrement' => true, + 'autoIncrement' => true, 'primary' => true, ], [ 'name' => 'name', 'table' => 'author', - 'nativetype' => 'VARCHAR', + 'nativeType' => 'VARCHAR', 'size' => 30, + 'scale' => null, 'nullable' => false, 'default' => null, - 'autoincrement' => false, + 'autoIncrement' => false, 'primary' => false, ], [ 'name' => 'web', 'table' => 'author', - 'nativetype' => 'VARCHAR', + 'nativeType' => 'VARCHAR', 'size' => 100, + 'scale' => null, 'nullable' => false, 'default' => null, - 'autoincrement' => false, + 'autoIncrement' => false, 'primary' => false, ], [ 'name' => 'born', 'table' => 'author', - 'nativetype' => 'DATE', + 'nativeType' => 'DATE', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, - 'autoincrement' => false, + 'autoIncrement' => false, 'primary' => false, ], ]; switch ($driverName) { case 'mysql': - $version = $connection->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); + $version = $explorer->getServerVersion(); if (version_compare($version, '8.0', '>=')) { $expectedColumns[0]['size'] = null; } break; case 'pgsql': - $expectedColumns[0]['nativetype'] = 'INT4'; + $expectedColumns[0]['nativeType'] = 'INT4'; $expectedColumns[0]['default'] = "nextval('author_id_seq'::regclass)"; $expectedColumns[0]['size'] = 4; $expectedColumns[3]['size'] = 4; break; case 'sqlite': - $expectedColumns[0]['nativetype'] = 'INTEGER'; + $expectedColumns[0]['nativeType'] = 'INTEGER'; $expectedColumns[0]['size'] = null; - $expectedColumns[1]['nativetype'] = 'TEXT'; + $expectedColumns[1]['nativeType'] = 'TEXT'; $expectedColumns[1]['size'] = null; - $expectedColumns[2]['nativetype'] = 'TEXT'; + $expectedColumns[2]['nativeType'] = 'TEXT'; $expectedColumns[2]['size'] = null; break; case 'sqlsrv': @@ -124,7 +127,7 @@ switch ($driverName) { Assert::same($expectedColumns, $columns); -$indexes = $driver->getIndexes('book_tag'); +$indexes = $engine->getIndexes('book_tag'); switch ($driverName) { case 'pgsql': Assert::same([ diff --git a/tests/Database/Engines/MSSQLEngine.applyLimit.phpt b/tests/Database/Engines/MSSQLEngine.applyLimit.phpt new file mode 100644 index 000000000..35cfb9159 --- /dev/null +++ b/tests/Database/Engines/MSSQLEngine.applyLimit.phpt @@ -0,0 +1,71 @@ + $engine->applyLimit('SELECT 1 FROM t', 10, 20), + Nette\NotSupportedException::class, + 'Offset is not supported by this database.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', 0, 20), + Nette\NotSupportedException::class, + 'Offset is not supported by this database.', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, 0), + 'SELECT TOP 10 1 FROM t', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, 20), + Nette\NotSupportedException::class, + 'Offset is not supported by this database.', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, null), + 'SELECT TOP 10 1 FROM t', +); + +Assert::same( + $engine->applyLimit(' select distinct 1 FROM t', 10, null), + ' select distinct TOP 10 1 FROM t', +); + +Assert::same( + $engine->applyLimit('UPDATE t SET', 10, null), + 'UPDATE TOP 10 t SET', +); + +Assert::same( + $engine->applyLimit('DELETE FROM t SET', 10, null), + 'DELETE TOP 10 FROM t SET', +); + +Assert::exception( + fn() => $engine->applyLimit('SET FROM t', 10, null), + Nette\InvalidArgumentException::class, + 'SQL query must begin with SELECT, UPDATE or DELETE command.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', -1, null), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, -1), + Nette\NotSupportedException::class, + 'Offset is not supported by this database.', +); diff --git a/tests/Database/Engines/MySQLEngine.applyLimit.phpt b/tests/Database/Engines/MySQLEngine.applyLimit.phpt new file mode 100644 index 000000000..59f67b995 --- /dev/null +++ b/tests/Database/Engines/MySQLEngine.applyLimit.phpt @@ -0,0 +1,47 @@ +applyLimit('SELECT 1 FROM t', 10, 20), + 'SELECT 1 FROM t LIMIT 10 OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 0, 20), + 'SELECT 1 FROM t LIMIT 0 OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, 0), + 'SELECT 1 FROM t LIMIT 10', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', null, 20), + 'SELECT 1 FROM t LIMIT 18446744073709551615 OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, null), + 'SELECT 1 FROM t LIMIT 10', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', -1, null), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, -1), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); diff --git a/tests/Database/Engines/ODBCEngine.applyLimit.phpt b/tests/Database/Engines/ODBCEngine.applyLimit.phpt new file mode 100644 index 000000000..ea38594c2 --- /dev/null +++ b/tests/Database/Engines/ODBCEngine.applyLimit.phpt @@ -0,0 +1,71 @@ + $engine->applyLimit('SELECT 1 FROM t', 10, 20), + Nette\NotSupportedException::class, + 'Offset is not supported by this database.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', 0, 20), + Nette\NotSupportedException::class, + 'Offset is not supported by this database.', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, 0), + 'SELECT TOP 10 1 FROM t', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, 20), + Nette\NotSupportedException::class, + 'Offset is not supported by this database.', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, null), + 'SELECT TOP 10 1 FROM t', +); + +Assert::same( + $engine->applyLimit(' select distinct 1 FROM t', 10, null), + ' select distinct TOP 10 1 FROM t', +); + +Assert::same( + $engine->applyLimit('UPDATE t SET', 10, null), + 'UPDATE TOP 10 t SET', +); + +Assert::same( + $engine->applyLimit('DELETE FROM t SET', 10, null), + 'DELETE TOP 10 FROM t SET', +); + +Assert::exception( + fn() => $engine->applyLimit('SET FROM t', 10, null), + Nette\InvalidArgumentException::class, + 'SQL query must begin with SELECT, UPDATE or DELETE command.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', -1, null), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, -1), + Nette\NotSupportedException::class, + 'Offset is not supported by this database.', +); diff --git a/tests/Database/Engines/OracleEngine.applyLimit.phpt b/tests/Database/Engines/OracleEngine.applyLimit.phpt new file mode 100644 index 000000000..a2ed1d618 --- /dev/null +++ b/tests/Database/Engines/OracleEngine.applyLimit.phpt @@ -0,0 +1,47 @@ +applyLimit('SELECT 1 FROM t', 10, 20), + 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (SELECT 1 FROM t) t WHERE ROWNUM <= 30) WHERE "__rnum" > 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 0, 20), + 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (SELECT 1 FROM t) t WHERE ROWNUM <= 20) WHERE "__rnum" > 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, 0), + 'SELECT * FROM (SELECT 1 FROM t) WHERE ROWNUM <= 10', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', null, 20), + 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (SELECT 1 FROM t) t ) WHERE "__rnum" > 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, null), + 'SELECT * FROM (SELECT 1 FROM t) WHERE ROWNUM <= 10', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', -1, null), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, -1), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); diff --git a/tests/Database/Engines/PostgreSQLEngine.applyLimit.phpt b/tests/Database/Engines/PostgreSQLEngine.applyLimit.phpt new file mode 100644 index 000000000..b77321e76 --- /dev/null +++ b/tests/Database/Engines/PostgreSQLEngine.applyLimit.phpt @@ -0,0 +1,47 @@ +applyLimit('SELECT 1 FROM t', 10, 20), + 'SELECT 1 FROM t LIMIT 10 OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 0, 20), + 'SELECT 1 FROM t LIMIT 0 OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, 0), + 'SELECT 1 FROM t LIMIT 10', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', null, 20), + 'SELECT 1 FROM t OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, null), + 'SELECT 1 FROM t LIMIT 10', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', -1, null), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, -1), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); diff --git a/tests/Database/Engines/SQLServerEngine.applyLimit.phpt b/tests/Database/Engines/SQLServerEngine.applyLimit.phpt new file mode 100644 index 000000000..0bdc50957 --- /dev/null +++ b/tests/Database/Engines/SQLServerEngine.applyLimit.phpt @@ -0,0 +1,47 @@ +applyLimit('SELECT 1 FROM t', 10, 20), + 'SELECT 1 FROM t OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 0, 20), + 'SELECT 1 FROM t OFFSET 20 ROWS FETCH NEXT 0 ROWS ONLY', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, 0), + 'SELECT 1 FROM t OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', null, 20), + 'SELECT 1 FROM t OFFSET 20 ROWS FETCH NEXT 0 ROWS ONLY', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, null), + 'SELECT 1 FROM t OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', -1, null), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, -1), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); diff --git a/tests/Database/Engines/SQLiteEngine.applyLimit.phpt b/tests/Database/Engines/SQLiteEngine.applyLimit.phpt new file mode 100644 index 000000000..4258393e7 --- /dev/null +++ b/tests/Database/Engines/SQLiteEngine.applyLimit.phpt @@ -0,0 +1,47 @@ +applyLimit('SELECT 1 FROM t', 10, 20), + 'SELECT 1 FROM t LIMIT 10 OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 0, 20), + 'SELECT 1 FROM t LIMIT 0 OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, 0), + 'SELECT 1 FROM t LIMIT 10', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', null, 20), + 'SELECT 1 FROM t LIMIT -1 OFFSET 20', +); + +Assert::same( + $engine->applyLimit('SELECT 1 FROM t', 10, null), + 'SELECT 1 FROM t LIMIT 10', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', -1, null), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); + +Assert::exception( + fn() => $engine->applyLimit('SELECT 1 FROM t', null, -1), + Nette\InvalidArgumentException::class, + 'Negative offset or limit.', +); diff --git a/tests/Database/Explorer.fetch.phpt b/tests/Database/Explorer.fetch.phpt index bc1024032..0a6fe9a8b 100644 --- a/tests/Database/Explorer.fetch.phpt +++ b/tests/Database/Explorer.fetch.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('fetch', function () use ($explorer) { @@ -41,8 +40,8 @@ test('fetchField', function () use ($explorer) { }); -test('fetchFields', function () use ($explorer) { - Assert::same([11, 'Jakub Vrana'], $explorer->fetchFields('SELECT id, name FROM author ORDER BY id')); +test('fetchList', function () use ($explorer) { + Assert::same([11, 'Jakub Vrana'], $explorer->fetchList('SELECT id, name FROM author ORDER BY id')); }); diff --git a/tests/Database/Explorer.query.phpt b/tests/Database/Explorer.query.phpt index 254a38c7d..e36c76148 100644 --- a/tests/Database/Explorer.query.phpt +++ b/tests/Database/Explorer.query.phpt @@ -12,28 +12,27 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { $res = $explorer->query('SELECT id FROM author WHERE id = ?', 11); - Assert::type(Nette\Database\ResultSet::class, $res); - Assert::same('SELECT id FROM author WHERE id = ?', $res->getQueryString()); - Assert::same([11], $res->getParameters()); + Assert::type(Nette\Database\Result::class, $res); + Assert::same('SELECT id FROM author WHERE id = ?', $res->getQuery()->getSql()); + Assert::same([11], $res->getQuery()->getParameters()); }); test('', function () use ($explorer) { $res = $explorer->query('SELECT id FROM author WHERE id = ? OR id = ?', 11, 12); - Assert::same('SELECT id FROM author WHERE id = ? OR id = ?', $res->getQueryString()); - Assert::same([11, 12], $res->getParameters()); + Assert::same('SELECT id FROM author WHERE id = ? OR id = ?', $res->getQuery()->getSql()); + Assert::same([11, 12], $res->getQuery()->getParameters()); }); test('', function () use ($explorer) { - $res = $explorer->queryArgs('SELECT id FROM author WHERE id = ? OR id = ?', [11, 12]); - Assert::same('SELECT id FROM author WHERE id = ? OR id = ?', $res->getQueryString()); - Assert::same([11, 12], $res->getParameters()); + $res = @$explorer->queryArgs('SELECT id FROM author WHERE id = ? OR id = ?', [11, 12]); // is deprecated + Assert::same('SELECT id FROM author WHERE id = ? OR id = ?', $res->getQuery()->getSql()); + Assert::same([11, 12], $res->getQuery()->getParameters()); }); diff --git a/tests/Database/Explorer.transaction.phpt b/tests/Database/Explorer.transaction.phpt index adbf31b1d..7dddcc567 100644 --- a/tests/Database/Explorer.transaction.phpt +++ b/tests/Database/Explorer.transaction.phpt @@ -13,9 +13,8 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/ActiveRow.__toString().phpt b/tests/Database/Explorer/ActiveRow.__toString().phpt index f3a29d9d7..ce25195b6 100644 --- a/tests/Database/Explorer/ActiveRow.__toString().phpt +++ b/tests/Database/Explorer/ActiveRow.__toString().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Explorer.aggregation.phpt b/tests/Database/Explorer/Explorer.aggregation.phpt index e04fea142..79d31bdee 100644 --- a/tests/Database/Explorer/Explorer.aggregation.phpt +++ b/tests/Database/Explorer/Explorer.aggregation.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Explorer.backjoin.phpt b/tests/Database/Explorer/Explorer.backjoin.phpt index a466b3404..7fdcf7f61 100644 --- a/tests/Database/Explorer/Explorer.backjoin.phpt +++ b/tests/Database/Explorer/Explorer.backjoin.phpt @@ -7,16 +7,15 @@ declare(strict_types=1); -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); -$driver = $connection->getDriver(); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +$engine = $explorer->getDatabaseEngine(); test('', function () use ($explorer) { @@ -39,10 +38,10 @@ test('', function () use ($explorer) { }); -test('', function () use ($explorer, $driver) { +test('', function () use ($explorer, $engine) { $authorsSelection = $explorer->table('author')->where(':book.translator_id IS NOT NULL')->wherePrimary(12); - if ($driver->isSupported(Driver::SupportSchema)) { + if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( reformat('SELECT [author].* FROM [author] LEFT JOIN [public].[book] [book] ON [author].[id] = [book].[author_id] WHERE ([book].[translator_id] IS NOT NULL) AND ([author].[id] = ?)'), $authorsSelection->getSql(), diff --git a/tests/Database/Explorer/Explorer.basic.camelCase.phpt b/tests/Database/Explorer/Explorer.basic.camelCase.phpt index 6fe3432fc..4f3a072b6 100644 --- a/tests/Database/Explorer/Explorer.basic.camelCase.phpt +++ b/tests/Database/Explorer/Explorer.basic.camelCase.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test2.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test2.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Explorer.basic.phpt b/tests/Database/Explorer/Explorer.basic.phpt index 4a1d833be..c109645fc 100644 --- a/tests/Database/Explorer/Explorer.basic.phpt +++ b/tests/Database/Explorer/Explorer.basic.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Explorer.cache.observer.phpt b/tests/Database/Explorer/Explorer.cache.observer.phpt index 1c52c4296..931a926a8 100644 --- a/tests/Database/Explorer/Explorer.cache.observer.phpt +++ b/tests/Database/Explorer/Explorer.cache.observer.phpt @@ -7,27 +7,30 @@ declare(strict_types=1); -use Nette\Database\ResultSet; +use Nette\Database\Result; use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); -$cacheStorage = Mockery::mock(Nette\Caching\Istorage::class); -$cacheStorage->shouldReceive('read')->withAnyArgs()->once()->andReturn(['id' => true]); -$cacheStorage->shouldReceive('read')->withAnyArgs()->times(4)->andReturn(['id' => true, 'author_id' => true]); -$cacheStorage->shouldReceive('write')->with(Mockery::any(), ['id' => true, 'author_id' => true, 'title' => true], []); - -$explorer = new Nette\Database\Explorer($connection, $explorer->getStructure(), $explorer->getConventions(), $cacheStorage); +$cache = Mockery::mock(Nette\Caching\Cache::class); +$cache->shouldReceive('load')->withAnyArgs()->once()->andReturn([]); +$cache->shouldReceive('load')->withAnyArgs()->once()->andReturn(['id' => true]); +$cache->shouldReceive('load')->withAnyArgs()->times(4)->andReturn(['id' => true, 'author_id' => true]); +$cache->shouldReceive('save')->with('structure', Mockery::any()); +$cache->shouldReceive('save')->with(Mockery::any(), ['id' => true, 'author_id' => true, 'title' => true]); +$explorer->setCache($cache); $queries = 0; -$connection->onQuery[] = function ($dao, ResultSet $result) use (&$queries) { - if (!preg_match('#SHOW|CONSTRAINT_NAME|pg_catalog|sys\.|SET|PRAGMA|FROM sqlite_#i', $result->getQueryString())) { +$explorer->onQuery[] = function ($explorer, $result) use (&$queries) { + if ( + $result instanceof Result + && !preg_match('#SHOW|CONSTRAINT_NAME|pg_catalog|sys\.|SET|PRAGMA|FROM sqlite_#i', $result->getQuery()->getSql()) + ) { $queries++; } }; diff --git a/tests/Database/Explorer/Explorer.cache.observer2.phpt b/tests/Database/Explorer/Explorer.cache.observer2.phpt index 5eff0cf66..0e7ce2ea1 100644 --- a/tests/Database/Explorer/Explorer.cache.observer2.phpt +++ b/tests/Database/Explorer/Explorer.cache.observer2.phpt @@ -7,31 +7,31 @@ declare(strict_types=1); +use Nette\Caching\Cache; use Nette\Caching\Storages\MemoryStorage; use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); -class CacheMock extends MemoryStorage +class CacheMock extends Cache { public int $writes = 0; - public function write(string $key, $data, array $dependencies): void + public function save(mixed $key, mixed $data, ?array $dependencies = null): mixed { $this->writes++; - parent::write($key, $data, $dependencies); + return parent::save($key, $data, $dependencies); } } -$cacheStorage = new CacheMock; -$explorer = new Nette\Database\Explorer($connection, $explorer->getStructure(), $explorer->getConventions(), $cacheStorage); +$cache = new CacheMock(new MemoryStorage); +$explorer->setCache($cache); for ($i = 0; $i < 2; ++$i) { $authors = $explorer->table('author'); @@ -52,4 +52,4 @@ for ($i = 0; $i < 2; ++$i) { } Assert::same(reformat('SELECT [id], [name] FROM [author]'), $sql); -Assert::same(2, $cacheStorage->writes); +Assert::same(3, $cache->writes); // Structure + 2x Selection diff --git a/tests/Database/Explorer/Explorer.cache.phpt b/tests/Database/Explorer/Explorer.cache.phpt index 20e94bad9..9ec981e47 100644 --- a/tests/Database/Explorer/Explorer.cache.phpt +++ b/tests/Database/Explorer/Explorer.cache.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('Testing Selection caching', function () use ($explorer) { @@ -198,7 +197,7 @@ test('Test saving the union of needed cols, the second call is not subset', func test('Test multiple use of same selection', function () use ($explorer) { $sql = []; - $explorer->getConnection()->onQuery[] = function ($_, $result) use (&$sql) { + $explorer->onQuery[] = function ($_, $result) use (&$sql) { $sql[] = $result->getQueryString(); }; diff --git a/tests/Database/Explorer/Explorer.cache.rows.phpt b/tests/Database/Explorer/Explorer.cache.rows.phpt index 78761af63..19ba71027 100644 --- a/tests/Database/Explorer/Explorer.cache.rows.phpt +++ b/tests/Database/Explorer/Explorer.cache.rows.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); $selections = []; diff --git a/tests/Database/Explorer/Explorer.cache2.phpt b/tests/Database/Explorer/Explorer.cache2.phpt index c174b7779..5f6202526 100644 --- a/tests/Database/Explorer/Explorer.cache2.phpt +++ b/tests/Database/Explorer/Explorer.cache2.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); $res = []; diff --git a/tests/Database/Explorer/Explorer.columnRefetch.phpt b/tests/Database/Explorer/Explorer.columnRefetch.phpt index 4ff46a398..e49d7917e 100644 --- a/tests/Database/Explorer/Explorer.columnRefetch.phpt +++ b/tests/Database/Explorer/Explorer.columnRefetch.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); $books = $explorer->table('book')->order('id DESC')->limit(2); diff --git a/tests/Database/Explorer/Explorer.discoveredReflection.phpt b/tests/Database/Explorer/Explorer.discoveredReflection.phpt index eed582dab..f427a8e5c 100644 --- a/tests/Database/Explorer/Explorer.discoveredReflection.phpt +++ b/tests/Database/Explorer/Explorer.discoveredReflection.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { @@ -90,10 +89,10 @@ test('', function () use ($explorer) { }); -test('', function () use ($connection, $explorer, $driverName) { +test('', function () use ($explorer, $driverName) { if ( $driverName === 'mysql' && - ($lowerCase = $connection->query('SHOW VARIABLES LIKE "lower_case_table_names"')->fetch()) && + ($lowerCase = $explorer->query('SHOW VARIABLES LIKE "lower_case_table_names"')->fetch()) && $lowerCase->Value != 0 ) { // tests case-insensitive reflection diff --git a/tests/Database/Explorer/Explorer.join-condition.phpt b/tests/Database/Explorer/Explorer.join-condition.phpt index 34af228b9..9b22801ac 100644 --- a/tests/Database/Explorer/Explorer.join-condition.phpt +++ b/tests/Database/Explorer/Explorer.join-condition.phpt @@ -7,18 +7,18 @@ declare(strict_types=1); -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); -$driver = $connection->getDriver(); -test('', function () use ($explorer, $driver) { - $schema = $driver->isSupported(Driver::SupportSchema) +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +$engine = $explorer->getDatabaseEngine(); + +test('', function () use ($explorer, $engine) { + $schema = $engine->isSupported(Engine::SupportSchema) ? '[public].' : ''; $sql = $explorer->table('book')->joinWhere('translator', 'translator.name', 'Geek')->select('book.*')->getSql(); @@ -29,7 +29,7 @@ test('', function () use ($explorer, $driver) { ), $sql); }); -test('', function () use ($explorer, $driver) { +test('', function () use ($explorer, $engine) { $sql = $explorer->table('tag') ->select('tag.name, COUNT(:book_tag.book.id) AS count_of_next_volume_written_by_younger_author') ->joinWhere(':book_tag.book.author', ':book_tag.book.author.born < next_volume_author.born') @@ -37,7 +37,7 @@ test('', function () use ($explorer, $driver) { ->where('tag.name', 'PHP') ->group('tag.name') ->getSql(); - if ($driver->isSupported(Driver::SupportSchema)) { + if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( reformat( 'SELECT [tag].[name], COUNT([book].[id]) AS [count_of_next_volume_written_by_younger_author] FROM [tag] ' . diff --git a/tests/Database/Explorer/Explorer.join.phpt b/tests/Database/Explorer/Explorer.join.phpt index 897975fb7..39f63fa64 100644 --- a/tests/Database/Explorer/Explorer.join.phpt +++ b/tests/Database/Explorer/Explorer.join.phpt @@ -7,16 +7,15 @@ declare(strict_types=1); -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); -$driver = $connection->getDriver(); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +$engine = $explorer->getDatabaseEngine(); test('', function () use ($explorer) { @@ -34,10 +33,10 @@ test('', function () use ($explorer) { }); -test('', function () use ($explorer, $driver) { +test('', function () use ($explorer, $engine) { $joinSql = $explorer->table('book_tag')->where('book_id', 1)->select('tag.*')->getSql(); - if ($driver->isSupported(Driver::SupportSchema)) { + if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( reformat('SELECT [tag].* FROM [book_tag] LEFT JOIN [public].[tag] [tag] ON [book_tag].[tag_id] = [tag].[id] WHERE ([book_id] = ?)'), $joinSql, @@ -51,10 +50,10 @@ test('', function () use ($explorer, $driver) { }); -test('', function () use ($explorer, $driver) { +test('', function () use ($explorer, $engine) { $joinSql = $explorer->table('book_tag')->where('book_id', 1)->select('Tag.id')->getSql(); - if ($driver->isSupported(Driver::SupportSchema)) { + if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( reformat('SELECT [Tag].[id] FROM [book_tag] LEFT JOIN [public].[tag] [Tag] ON [book_tag].[tag_id] = [Tag].[id] WHERE ([book_id] = ?)'), $joinSql, diff --git a/tests/Database/Explorer/Explorer.limit.phpt b/tests/Database/Explorer/Explorer.limit.phpt index ccf1bec6a..95b733982 100644 --- a/tests/Database/Explorer/Explorer.limit.phpt +++ b/tests/Database/Explorer/Explorer.limit.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); Assert::same( diff --git a/tests/Database/Explorer/Explorer.limit.sqlsrv.phpt b/tests/Database/Explorer/Explorer.limit.sqlsrv.phpt index e5218ff98..a803010e6 100644 --- a/tests/Database/Explorer/Explorer.limit.sqlsrv.phpt +++ b/tests/Database/Explorer/Explorer.limit.sqlsrv.phpt @@ -12,11 +12,10 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); -$version2008 = $connection->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION) < 11; +$version2008 = $explorer->getServerVersion() < 11; Assert::same( $version2008 diff --git a/tests/Database/Explorer/Explorer.multi-primary-key.phpt b/tests/Database/Explorer/Explorer.multi-primary-key.phpt index d914c028a..decc738f8 100644 --- a/tests/Database/Explorer/Explorer.multi-primary-key.phpt +++ b/tests/Database/Explorer/Explorer.multi-primary-key.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Explorer.placeholders.phpt b/tests/Database/Explorer/Explorer.placeholders.phpt index 52ce83415..858c6dd55 100644 --- a/tests/Database/Explorer/Explorer.placeholders.phpt +++ b/tests/Database/Explorer/Explorer.placeholders.phpt @@ -13,9 +13,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('Leave literals lower-cased, also not-delimiting them is tested.', function () use ($explorer, $driverName) { diff --git a/tests/Database/Explorer/Explorer.ref().phpt b/tests/Database/Explorer/Explorer.ref().phpt index a8a017087..e2cfdd36a 100644 --- a/tests/Database/Explorer/Explorer.ref().phpt +++ b/tests/Database/Explorer/Explorer.ref().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); Assert::same('Jakub Vrana', $explorer->table('book')->get(1)->ref('author')->name); @@ -36,10 +35,10 @@ test('', function () use ($explorer) { Assert::null($explorer->table('book')->get(2)->ref('author', 'translator_id')); }); -test('', function () use ($explorer, $connection) { +test('', function () use ($explorer) { $counter = 0; - $connection->onQuery[] = function ($connection, $result) use (&$counter) { + $explorer->onQuery[] = function ($explorer, $result) use (&$counter) { $counter++; }; diff --git a/tests/Database/Explorer/Explorer.related().caching.phpt b/tests/Database/Explorer/Explorer.related().caching.phpt index d6995923d..f1322ca3b 100644 --- a/tests/Database/Explorer/Explorer.related().caching.phpt +++ b/tests/Database/Explorer/Explorer.related().caching.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Explorer.related().phpt b/tests/Database/Explorer/Explorer.related().phpt index 84455a402..daf9c3462 100644 --- a/tests/Database/Explorer/Explorer.related().phpt +++ b/tests/Database/Explorer/Explorer.related().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Explorer.self-reference.phpt b/tests/Database/Explorer/Explorer.self-reference.phpt index 345043024..6d13a2710 100644 --- a/tests/Database/Explorer/Explorer.self-reference.phpt +++ b/tests/Database/Explorer/Explorer.self-reference.phpt @@ -12,15 +12,14 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); $explorer->query('UPDATE book SET next_volume = 3 WHERE id IN (2,4)'); -test('', function () use ($connection, $explorer) { +test('', function () use ($explorer) { $book = $explorer->table('book')->get(4); Assert::same('Nette', $book->volume->title); Assert::same('Nette', $book->ref('book', 'next_volume')->title); diff --git a/tests/Database/Explorer/Explorer.subquery.phpt b/tests/Database/Explorer/Explorer.subquery.phpt index 314188f0d..744a5ecc6 100644 --- a/tests/Database/Explorer/Explorer.subquery.phpt +++ b/tests/Database/Explorer/Explorer.subquery.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Explorer.update().phpt b/tests/Database/Explorer/Explorer.update().phpt index 11ffd1a81..0c966779f 100644 --- a/tests/Database/Explorer/Explorer.update().phpt +++ b/tests/Database/Explorer/Explorer.update().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); $author = $explorer->table('author')->get(12); // SELECT * FROM `author` WHERE (`id` = ?) @@ -79,7 +78,7 @@ $tag2 = $explorer->table('tag')->insert([ 'name' => 'PS4 Game', ]); // INSERT INTO `tag` (`name`) VALUES ('PS4 Game') -// SQL Server throw PDOException because does not allow to update identity column +// SQL Server throw exception because does not allow to update identity column if ($driverName !== 'sqlsrv') { $tag2->update([ 'id' => 1, diff --git a/tests/Database/Explorer/GroupedSelection.insert().phpt b/tests/Database/Explorer/GroupedSelection.insert().phpt index 60c239637..b24d7b307 100644 --- a/tests/Database/Explorer/GroupedSelection.insert().phpt +++ b/tests/Database/Explorer/GroupedSelection.insert().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.delete().phpt b/tests/Database/Explorer/Selection.delete().phpt index f5292bb95..f3ba7f959 100644 --- a/tests/Database/Explorer/Selection.delete().phpt +++ b/tests/Database/Explorer/Selection.delete().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.fetch().phpt b/tests/Database/Explorer/Selection.fetch().phpt index ec71f3887..594debe55 100644 --- a/tests/Database/Explorer/Selection.fetch().phpt +++ b/tests/Database/Explorer/Selection.fetch().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.fetchAssoc().phpt b/tests/Database/Explorer/Selection.fetchAssoc().phpt index 6280b9cb6..63e66b7b9 100644 --- a/tests/Database/Explorer/Selection.fetchAssoc().phpt +++ b/tests/Database/Explorer/Selection.fetchAssoc().phpt @@ -13,9 +13,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.fetchField().phpt b/tests/Database/Explorer/Selection.fetchField().phpt index 5eb29cc3b..ff35ca756 100644 --- a/tests/Database/Explorer/Selection.fetchField().phpt +++ b/tests/Database/Explorer/Selection.fetchField().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.fetchPairs().phpt b/tests/Database/Explorer/Selection.fetchPairs().phpt index 89fc97028..832ed2e3f 100644 --- a/tests/Database/Explorer/Selection.fetchPairs().phpt +++ b/tests/Database/Explorer/Selection.fetchPairs().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.get().phpt b/tests/Database/Explorer/Selection.get().phpt index 138afd97e..b153cc479 100644 --- a/tests/Database/Explorer/Selection.get().phpt +++ b/tests/Database/Explorer/Selection.get().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.group().phpt b/tests/Database/Explorer/Selection.group().phpt index ec51d7da7..68cb0ef2b 100644 --- a/tests/Database/Explorer/Selection.group().phpt +++ b/tests/Database/Explorer/Selection.group().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.insert().multi.phpt b/tests/Database/Explorer/Selection.insert().multi.phpt index 99dbcdcb7..0a032e008 100644 --- a/tests/Database/Explorer/Selection.insert().multi.phpt +++ b/tests/Database/Explorer/Selection.insert().multi.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.insert().phpt b/tests/Database/Explorer/Selection.insert().phpt index f96e6f88c..4f5599278 100644 --- a/tests/Database/Explorer/Selection.insert().phpt +++ b/tests/Database/Explorer/Selection.insert().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); $book = $explorer->table('author')->insert([ @@ -41,7 +40,7 @@ $book2 = $books->insert([ Assert::same('eddard stark', $book2->author->name); // SELECT * FROM `author` WHERE (`author`.`id` IN (11, 15)) -// SQL Server throw PDOException because does not allow insert explicit value for IDENTITY column. +// SQL Server throw exception because does not allow insert explicit value for IDENTITY column. // This exception is about primary key violation. if ($driverName !== 'sqlsrv') { Assert::exception( @@ -50,7 +49,7 @@ if ($driverName !== 'sqlsrv') { 'name' => 'Jon Snow', 'web' => 'http://example.com', ]), - PDOException::class, + Nette\Database\UniqueConstraintViolationException::class, ); } diff --git a/tests/Database/Explorer/Selection.insert().primaryKeys.phpt b/tests/Database/Explorer/Selection.insert().primaryKeys.phpt index 885a89e37..0562d6af7 100644 --- a/tests/Database/Explorer/Selection.insert().primaryKeys.phpt +++ b/tests/Database/Explorer/Selection.insert().primaryKeys.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test4.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test4.sql"); test('Insert into table with simple primary index (autoincrement)', function () use ($explorer) { $simplePkAutoincrementResult = $explorer->table('simple_pk_autoincrement')->insert([ diff --git a/tests/Database/Explorer/Selection.order().phpt b/tests/Database/Explorer/Selection.order().phpt index f264baa84..5633bbfb7 100644 --- a/tests/Database/Explorer/Selection.order().phpt +++ b/tests/Database/Explorer/Selection.order().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/Selection.page().phpt b/tests/Database/Explorer/Selection.page().phpt index ea05cad0a..b4cce1ccb 100644 --- a/tests/Database/Explorer/Selection.page().phpt +++ b/tests/Database/Explorer/Selection.page().phpt @@ -12,13 +12,12 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -if ($driverName === 'sqlsrv' && $connection->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION) < 11) { +if ($driverName === 'sqlsrv' && $explorer->getServerVersion() < 11) { Tester\Environment::skip('Offset is supported since SQL Server 2012'); } -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); //public function page($page, $itemsPerPage, &$numOfPages = null) diff --git a/tests/Database/Explorer/Selection.whereOr().phpt b/tests/Database/Explorer/Selection.whereOr().phpt index 9cc8b1058..2279db58a 100644 --- a/tests/Database/Explorer/Selection.whereOr().phpt +++ b/tests/Database/Explorer/Selection.whereOr().phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('without question mark', function () use ($explorer) { $count = $explorer->table('book')->whereOr([ diff --git a/tests/Database/Explorer/SqlBuilder.addAlias().phpt b/tests/Database/Explorer/SqlBuilder.addAlias().phpt index f3fbd4a06..adb9a41d3 100644 --- a/tests/Database/Explorer/SqlBuilder.addAlias().phpt +++ b/tests/Database/Explorer/SqlBuilder.addAlias().phpt @@ -7,16 +7,15 @@ declare(strict_types=1); -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Nette\Database\Table\SqlBuilder; use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); class SqlBuilderMock extends SqlBuilder { @@ -32,11 +31,11 @@ class SqlBuilderMock extends SqlBuilder } } -$driver = $connection->getDriver(); +$engine = $explorer->getDatabaseEngine(); -test('test duplicated table names throw exception', function () use ($explorer, $driver) { - $authorTable = ($driver->isSupported(Driver::SupportSchema) ? 'public.' : '') . 'author'; +test('test duplicated table names throw exception', function () use ($explorer, $engine) { + $authorTable = ($engine->isSupported(Engine::SupportSchema) ? 'public.' : '') . 'author'; $sqlBuilder = new SqlBuilderMock($authorTable, $explorer); $sqlBuilder->addAlias(':book(translator)', 'book1'); $sqlBuilder->addAlias(':book:book_tag', 'book2'); @@ -73,7 +72,7 @@ test('test duplicated table names throw exception', function () use ($explorer, }); -test('test same table chain with another alias', function () use ($explorer, $driver) { +test('test same table chain with another alias', function () use ($explorer, $engine) { $sqlBuilder = new SqlBuilderMock('author', $explorer); $sqlBuilder->addAlias(':book(translator)', 'translated_book'); $sqlBuilder->addAlias(':book(translator)', 'translated_book2'); @@ -90,8 +89,8 @@ test('test same table chain with another alias', function () use ($explorer, $dr }); -test('test nested alias', function () use ($explorer, $driver) { - $sqlBuilder = $driver->isSupported(Driver::SupportSchema) +test('test nested alias', function () use ($explorer, $engine) { + $sqlBuilder = $engine->isSupported(Engine::SupportSchema) ? new SqlBuilderMock('public.author', $explorer) : new SqlBuilderMock('author', $explorer); $sqlBuilder->addAlias(':book(translator)', 'translated_book'); @@ -100,7 +99,7 @@ test('test nested alias', function () use ($explorer, $driver) { $joins = []; $sqlBuilder->parseJoins($joins, $query); $join = $sqlBuilder->buildQueryJoins($joins); - if ($driver->isSupported(Driver::SupportSchema)) { + if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( 'LEFT JOIN book translated_book ON author.id = translated_book.translator_id ' . 'LEFT JOIN public.book next ON translated_book.next_volume = next.id', diff --git a/tests/Database/Explorer/SqlBuilder.addWhere().phpt b/tests/Database/Explorer/SqlBuilder.addWhere().phpt index 12e81c04b..92b43343e 100644 --- a/tests/Database/Explorer/SqlBuilder.addWhere().phpt +++ b/tests/Database/Explorer/SqlBuilder.addWhere().phpt @@ -7,7 +7,7 @@ declare(strict_types=1); -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Nette\Database\SqlLiteral; use Nette\Database\Table\SqlBuilder; use Tester\Assert; @@ -15,9 +15,8 @@ use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); test('test paramateres with null', function () use ($explorer) { @@ -82,7 +81,7 @@ test('test more ActiveRow as a parameter', function () use ($explorer) { test('test Selection with parameters as a parameter', function () use ($explorer) { $sqlBuilder = new SqlBuilder('book', $explorer); $sqlBuilder->addWhere('id', $explorer->table('book')->having('COUNT(:book_tag.tag_id) >', 1)); - $schemaSupported = $explorer->getConnection()->getDriver()->isSupported(Driver::SupportSchema); + $schemaSupported = $explorer->getDatabaseEngine()->isSupported(Engine::SupportSchema); Assert::same(reformat([ 'mysql' => 'SELECT * FROM `book` WHERE (`id` IN (?))', 'SELECT * FROM [book] WHERE ([id] IN (SELECT [id] FROM [book] LEFT JOIN ' . ($schemaSupported ? '[public].[book_tag] ' : '') . '[book_tag] ON [book].[id] = [book_tag].[book_id] HAVING COUNT([book_tag].[tag_id]) > ?))', @@ -243,7 +242,7 @@ Assert::exception(function () use ($explorer) { }, Nette\InvalidArgumentException::class, 'Column operator does not accept array argument.'); -test('', function () use ($driverName, $explorer, $connection) { +test('', function () use ($driverName, $explorer) { $structure = $explorer->getStructure(); switch ($driverName) { case 'mysql': diff --git a/tests/Database/Explorer/SqlBuilder.parseJoinConditions().phpt b/tests/Database/Explorer/SqlBuilder.parseJoinConditions().phpt index cb0d83ff0..b1c6f9f5f 100644 --- a/tests/Database/Explorer/SqlBuilder.parseJoinConditions().phpt +++ b/tests/Database/Explorer/SqlBuilder.parseJoinConditions().phpt @@ -7,16 +7,15 @@ declare(strict_types=1); -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Nette\Database\Table\SqlBuilder; use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); class SqlBuilderMock extends SqlBuilder { @@ -44,7 +43,7 @@ class SqlBuilderMock extends SqlBuilder } } -$driver = $connection->getDriver(); +$engine = $explorer->getDatabaseEngine(); test('test circular reference', function () use ($explorer) { $sqlBuilder = new SqlBuilderMock('author', $explorer); @@ -75,7 +74,7 @@ test('test circular reference', function () use ($explorer) { ); }); -test('', function () use ($explorer, $driver) { +test('', function () use ($explorer, $engine) { $sqlBuilder = new SqlBuilderMock('author', $explorer); $sqlBuilder->addJoinCondition(':book(translator)', ':book(translator).id > ?', 2); $sqlBuilder->addJoinCondition(':book(translator):book_tag_alt', ':book(translator):book_tag_alt.state ?', 'private'); @@ -83,7 +82,7 @@ test('', function () use ($explorer, $driver) { $leftJoinConditions = $sqlBuilder->parseJoinConditions($joins, $sqlBuilder->buildJoinConditions()); $join = $sqlBuilder->buildQueryJoins($joins, $leftJoinConditions); - if ($driver->isSupported(Driver::SupportSchema)) { + if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( 'LEFT JOIN book ON author.id = book.translator_id AND (book.id > ?) ' . 'LEFT JOIN public.book_tag_alt book_tag_alt ON book.id = book_tag_alt.book_id AND (book_tag_alt.state = ?)', diff --git a/tests/Database/Explorer/SqlBuilder.parseJoins().phpt b/tests/Database/Explorer/SqlBuilder.parseJoins().phpt index 847f32ec4..3fabed91b 100644 --- a/tests/Database/Explorer/SqlBuilder.parseJoins().phpt +++ b/tests/Database/Explorer/SqlBuilder.parseJoins().phpt @@ -8,16 +8,15 @@ declare(strict_types=1); use Nette\Database\Conventions\DiscoveredConventions; -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Nette\Database\Table\SqlBuilder; use Tester\Assert; require __DIR__ . '/../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test2.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test2.sql"); class SqlBuilderMock extends SqlBuilder @@ -37,7 +36,7 @@ class SqlBuilderMock extends SqlBuilder $structure = $explorer->getStructure(); $conventions = new DiscoveredConventions($structure); $sqlBuilder = new SqlBuilderMock('nUsers', $explorer); -$driver = $connection->getDriver(); +$engine = $explorer->getDatabaseEngine(); $joins = []; @@ -46,9 +45,9 @@ $sqlBuilder->parseJoins($joins, $query); $join = $sqlBuilder->buildQueryJoins($joins); Assert::same('WHERE priorit.id IS NULL', $query); -$tables = $connection->getDriver()->getTables(); +$tables = $explorer->getDatabaseEngine()->getTables(); if (!in_array($tables[0]['name'], ['npriorities', 'ntopics', 'nusers', 'nusers_ntopics', 'nusers_ntopics_alt'], true)) { - if ($driver->isSupported(Driver::SupportSchema)) { + if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( 'LEFT JOIN public.nUsers_nTopics nusers_ntopics ON nUsers.nUserId = nusers_ntopics.nUserId ' . 'LEFT JOIN public.nTopics topic ON nusers_ntopics.nTopicId = topic.nTopicId ' . @@ -75,7 +74,7 @@ if (!in_array($tables[0]['name'], ['npriorities', 'ntopics', 'nusers', 'nusers_n } -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); $structure->rebuild(); $sqlBuilder = new SqlBuilderMock('author', $explorer); @@ -91,7 +90,7 @@ Assert::same( ); -$sqlBuilder = $driver->isSupported(Driver::SupportSchema) +$sqlBuilder = $engine->isSupported(Engine::SupportSchema) ? new SqlBuilderMock('public.book', $explorer) : new SqlBuilderMock('book', $explorer); @@ -101,7 +100,7 @@ $sqlBuilder->parseJoins($joins, $query); $join = $sqlBuilder->buildQueryJoins($joins); Assert::same('WHERE book_ref.translator_id IS NULL AND book_ref_ref.translator_id IS NULL', $query); -if ($driver->isSupported(Driver::SupportSchema)) { +if ($engine->isSupported(Engine::SupportSchema)) { Assert::same( 'LEFT JOIN public.book book_ref ON book.id = book_ref.next_volume ' . 'LEFT JOIN public.book book_ref_ref ON book_ref.id = book_ref_ref.next_volume', diff --git a/tests/Database/Explorer/SqlBuilder.tryDelimit().phpt b/tests/Database/Explorer/SqlBuilder.tryDelimit().phpt new file mode 100644 index 000000000..a2930edf2 --- /dev/null +++ b/tests/Database/Explorer/SqlBuilder.tryDelimit().phpt @@ -0,0 +1,29 @@ +getMethod('tryDelimit'); +$tryDelimit->setAccessible(true); + +Assert::same(reformat('[hello]'), $tryDelimit->invoke($sqlBuilder, 'hello')); +Assert::same(reformat(' [hello] '), $tryDelimit->invoke($sqlBuilder, ' hello ')); +Assert::same(reformat('HELLO'), $tryDelimit->invoke($sqlBuilder, 'HELLO')); +Assert::same(reformat('[HellO]'), $tryDelimit->invoke($sqlBuilder, 'HellO')); +Assert::same(reformat('[hello].[world]'), $tryDelimit->invoke($sqlBuilder, 'hello.world')); +Assert::same(reformat('[hello] [world]'), $tryDelimit->invoke($sqlBuilder, 'hello world')); +Assert::same(reformat('HELLO([world])'), $tryDelimit->invoke($sqlBuilder, 'HELLO(world)')); +Assert::same(reformat('hello([world])'), $tryDelimit->invoke($sqlBuilder, 'hello(world)')); +Assert::same('[hello]', $tryDelimit->invoke($sqlBuilder, '[hello]')); +Assert::same(reformat('::int'), $tryDelimit->invoke($sqlBuilder, '::int')); diff --git a/tests/Database/Explorer/SqlBuilder.tryDelimite().phpt b/tests/Database/Explorer/SqlBuilder.tryDelimite().phpt deleted file mode 100644 index 5aa8aaf31..000000000 --- a/tests/Database/Explorer/SqlBuilder.tryDelimite().phpt +++ /dev/null @@ -1,29 +0,0 @@ -getMethod('tryDelimite'); -$tryDelimite->setAccessible(true); - -Assert::same(reformat('[hello]'), $tryDelimite->invoke($sqlBuilder, 'hello')); -Assert::same(reformat(' [hello] '), $tryDelimite->invoke($sqlBuilder, ' hello ')); -Assert::same(reformat('HELLO'), $tryDelimite->invoke($sqlBuilder, 'HELLO')); -Assert::same(reformat('[HellO]'), $tryDelimite->invoke($sqlBuilder, 'HellO')); -Assert::same(reformat('[hello].[world]'), $tryDelimite->invoke($sqlBuilder, 'hello.world')); -Assert::same(reformat('[hello] [world]'), $tryDelimite->invoke($sqlBuilder, 'hello world')); -Assert::same(reformat('HELLO([world])'), $tryDelimite->invoke($sqlBuilder, 'HELLO(world)')); -Assert::same(reformat('hello([world])'), $tryDelimite->invoke($sqlBuilder, 'hello(world)')); -Assert::same('[hello]', $tryDelimite->invoke($sqlBuilder, '[hello]')); -Assert::same(reformat('::int'), $tryDelimite->invoke($sqlBuilder, '::int')); diff --git a/tests/Database/Explorer/bugs/ActiveRow.__isset().phpt b/tests/Database/Explorer/bugs/ActiveRow.__isset().phpt index bac634f6a..a26087efd 100644 --- a/tests/Database/Explorer/bugs/ActiveRow.__isset().phpt +++ b/tests/Database/Explorer/bugs/ActiveRow.__isset().phpt @@ -11,9 +11,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/bugs/Selection.emptyResultSet.phpt b/tests/Database/Explorer/bugs/Selection.emptyResultSet.phpt index e0b42ed08..ff3974cee 100644 --- a/tests/Database/Explorer/bugs/Selection.emptyResultSet.phpt +++ b/tests/Database/Explorer/bugs/Selection.emptyResultSet.phpt @@ -11,9 +11,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/bugs/Selection.getReferencingTable.pkTypes.phpt b/tests/Database/Explorer/bugs/Selection.getReferencingTable.pkTypes.phpt index cc4621b7b..fe38a5be9 100644 --- a/tests/Database/Explorer/bugs/Selection.getReferencingTable.pkTypes.phpt +++ b/tests/Database/Explorer/bugs/Selection.getReferencingTable.pkTypes.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test5.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test5.sql"); test('referencing table with integer primary key', function () use ($explorer) { diff --git a/tests/Database/Explorer/bugs/bug1356.phpt b/tests/Database/Explorer/bugs/bug1356.phpt index 479b5ae32..9e26a2eda 100644 --- a/tests/Database/Explorer/bugs/bug1356.phpt +++ b/tests/Database/Explorer/bugs/bug1356.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); $books = $explorer->table('book')->limit(1); @@ -31,7 +30,7 @@ foreach ($books as $book) { } Assert::same(reformat([ - 'sqlsrv' => $connection->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION) < 11 + 'sqlsrv' => $explorer->getServerVersion() < 11 ? 'SELECT TOP 1 * FROM [book] ORDER BY [book].[id]' : 'SELECT * FROM [book] ORDER BY [book].[id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY', 'SELECT * FROM [book] ORDER BY [book].[id] LIMIT 1', diff --git a/tests/Database/Explorer/bugs/bug170.phpt b/tests/Database/Explorer/bugs/bug170.phpt index 53c020d17..098400e78 100644 --- a/tests/Database/Explorer/bugs/bug170.phpt +++ b/tests/Database/Explorer/bugs/bug170.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-bug170.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-bug170.sql"); Assert::noError(function () use ($explorer) { // this bug is about picking the right foreign key to specified table regardless FKs definition order diff --git a/tests/Database/Explorer/bugs/bug187.phpt b/tests/Database/Explorer/bugs/bug187.phpt index a5210b55a..e1a44fd41 100644 --- a/tests/Database/Explorer/bugs/bug187.phpt +++ b/tests/Database/Explorer/bugs/bug187.phpt @@ -13,9 +13,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-bug187.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-bug187.sql"); foreach ([true, false] as $published) { $where = $published diff --git a/tests/Database/Explorer/bugs/bug216.phpt b/tests/Database/Explorer/bugs/bug216.phpt index 23db5de1e..8f085c919 100644 --- a/tests/Database/Explorer/bugs/bug216.phpt +++ b/tests/Database/Explorer/bugs/bug216.phpt @@ -12,9 +12,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); $book = $explorer->table('author')->insert([ 'name' => $explorer->literal('LOWER(?)', 'Eddard Stark'), diff --git a/tests/Database/Explorer/bugs/deleteCacheBug.phpt b/tests/Database/Explorer/bugs/deleteCacheBug.phpt index fd1912a48..227f9c4a2 100644 --- a/tests/Database/Explorer/bugs/deleteCacheBug.phpt +++ b/tests/Database/Explorer/bugs/deleteCacheBug.phpt @@ -11,9 +11,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { for ($i = 0; $i < 2; $i++) { diff --git a/tests/Database/Explorer/bugs/query.count.phpt b/tests/Database/Explorer/bugs/query.count.phpt index 52dd15ce5..d26c4339b 100644 --- a/tests/Database/Explorer/bugs/query.count.phpt +++ b/tests/Database/Explorer/bugs/query.count.phpt @@ -11,20 +11,19 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); // add additional tags (not relevant to other tests) $explorer->query("INSERT INTO book_tag_alt (book_id, tag_id, state) VALUES (1, 24, 'private');"); $explorer->query("INSERT INTO book_tag_alt (book_id, tag_id, state) VALUES (2, 24, 'private');"); $explorer->query("INSERT INTO book_tag_alt (book_id, tag_id, state) VALUES (2, 22, 'private');"); -test('', function () use ($connection, $explorer) { +test('', function () use ($explorer) { $explorer->table('author')->get(11); // have to build cache first $count = 0; - $connection->onQuery[] = function () use (&$count) { + $explorer->onQuery[] = function () use (&$count) { $count++; }; diff --git a/tests/Database/Explorer/bugs/staticReflection.undeclaredColumn.phpt b/tests/Database/Explorer/bugs/staticReflection.undeclaredColumn.phpt index 4780a937c..2f3ca74c2 100644 --- a/tests/Database/Explorer/bugs/staticReflection.undeclaredColumn.phpt +++ b/tests/Database/Explorer/bugs/staticReflection.undeclaredColumn.phpt @@ -12,13 +12,10 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -$conventions = new Nette\Database\Conventions\StaticConventions; -$cacheStorage = new Nette\Caching\Storages\MemoryStorage; -$explorer = new Nette\Database\Explorer($explorer->getConnection(), $explorer->getStructure(), $conventions, $cacheStorage); +$explorer->setConventions(new Nette\Database\Conventions\StaticConventions); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); test('', function () use ($explorer) { diff --git a/tests/Database/Explorer/bugs/view.bug.phpt b/tests/Database/Explorer/bugs/view.bug.phpt index 0a555757b..32cd165b4 100644 --- a/tests/Database/Explorer/bugs/view.bug.phpt +++ b/tests/Database/Explorer/bugs/view.bug.phpt @@ -11,9 +11,8 @@ use Tester\Assert; require __DIR__ . '/../../../bootstrap.php'; $explorer = connectToDB(); -$connection = $explorer->getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($explorer, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); $explorer->query('CREATE VIEW books_view AS SELECT * FROM book'); @@ -22,9 +21,9 @@ test('', function () use ($explorer) { Assert::same(1, $selection->count()); }); -test('', function () use ($connection) { - $driver = $connection->getDriver(); - $columns = $driver->getColumns('books_view'); +test('', function () use ($explorer) { + $engine = $explorer->getDatabaseEngine(); + $columns = $engine->getColumns('books_view'); $columnsNames = array_map(fn($item) => $item['name'], $columns); Assert::same(['id', 'author_id', 'translator_id', 'title', 'next_volume'], $columnsNames); }); diff --git a/tests/Database/Helpers.dumpSql.phpt b/tests/Database/Helpers.dumpSql.phpt index 907a870b5..360961b2d 100644 --- a/tests/Database/Helpers.dumpSql.phpt +++ b/tests/Database/Helpers.dumpSql.phpt @@ -7,58 +7,62 @@ declare(strict_types=1); +use Nette\Database\SqlLiteral; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('int check', function () use ($connection) { Assert::same( "
SELECT id \nFROM author \nWHERE id = 10 OR id = 11
\n", - Nette\Database\Helpers::dumpSql('SELECT id FROM author WHERE id = ? OR id = ?', [10, 11], $connection), + Nette\Database\Helpers::dumpSql(new SqlLiteral('SELECT id FROM author WHERE id = ? OR id = ?', [10, 11]), $connection), ); }); test('bool check', function () use ($connection) { Assert::same( "
SELECT id \nFROM author \nWHERE deleted = 0
\n", - Nette\Database\Helpers::dumpSql('SELECT id FROM author WHERE deleted = ?', [false], $connection), + Nette\Database\Helpers::dumpSql(new SqlLiteral('SELECT id FROM author WHERE deleted = ?', [false]), $connection), ); }); test('string check', function () use ($connection) { Assert::same( "
SELECT id \nFROM author \nWHERE name = 'Alexej Chruščev'
\n", - Nette\Database\Helpers::dumpSql('SELECT id FROM author WHERE name = ?', ['Alexej Chruščev'], $connection), + Nette\Database\Helpers::dumpSql(new SqlLiteral('SELECT id FROM author WHERE name = ?', ['Alexej Chruščev']), $connection), ); }); test('string check with \'', function () use ($connection) { Assert::same( "
SELECT id \nFROM author \nWHERE name = 'Alexej Ch\\'ruščev'
\n", - Nette\Database\Helpers::dumpSql('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"], $connection), + Nette\Database\Helpers::dumpSql(new SqlLiteral('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"]), $connection), ); }); test('string check without connection', function () { Assert::same( "
SELECT id \nFROM author \nWHERE name = 'Alexej Ch'ruščev'
\n", - Nette\Database\Helpers::dumpSql('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"]), + Nette\Database\Helpers::dumpSql(new SqlLiteral('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"])), ); }); test('string compare with $connection vs without', function () use ($connection) { - Assert::notSame(Nette\Database\Helpers::dumpSql('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"], $connection), Nette\Database\Helpers::dumpSql('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"])); + Assert::notSame( + Nette\Database\Helpers::dumpSql(new SqlLiteral('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"]), $connection), + Nette\Database\Helpers::dumpSql(new SqlLiteral('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"])), + ); }); test('string check with \'', function () use ($connection) { Nette\Database\Helpers::$maxLength = 10; Assert::same( "
SELECT id \nFROM author \nWHERE name = 'Alexej Ch…'
\n", - Nette\Database\Helpers::dumpSql('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"], $connection), + Nette\Database\Helpers::dumpSql(new SqlLiteral('SELECT id FROM author WHERE name = ?', ["Alexej Ch'ruščev"]), $connection), ); }); diff --git a/tests/Database/Helpers.loadFromFile.phpt b/tests/Database/Helpers.loadFromFile.phpt index 1f6c616cc..d0cc8ba43 100644 --- a/tests/Database/Helpers.loadFromFile.phpt +++ b/tests/Database/Helpers.loadFromFile.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-delimiter.sql'); $arr = $connection->query('SELECT name, id FROM author ORDER BY id')->fetchAll(); diff --git a/tests/Database/Helpers.parseColumnType.phpt b/tests/Database/Helpers.parseColumnType.phpt index fe665d92e..a8bca9003 100644 --- a/tests/Database/Helpers.parseColumnType.phpt +++ b/tests/Database/Helpers.parseColumnType.phpt @@ -15,16 +15,16 @@ require __DIR__ . '/../bootstrap.php'; // Test basic type $result = Helpers::parseColumnType('UNSIGNED INT'); -Assert::same(['type' => 'UNSIGNED INT', 'length' => null, 'scale' => null, 'parameters' => null], $result); +Assert::same(['type' => 'UNSIGNED INT', 'size' => null, 'scale' => null, 'parameters' => null], $result); // Test type with length $result = Helpers::parseColumnType('VARCHAR(255)'); -Assert::same(['type' => 'VARCHAR', 'length' => 255, 'scale' => null, 'parameters' => null], $result); +Assert::same(['type' => 'VARCHAR', 'size' => 255, 'scale' => null, 'parameters' => null], $result); // Test type with precision and scale $result = Helpers::parseColumnType('DECIMAL(10,2)'); -Assert::same(['type' => 'DECIMAL', 'length' => 10, 'scale' => 2, 'parameters' => null], $result); +Assert::same(['type' => 'DECIMAL', 'size' => 10, 'scale' => 2, 'parameters' => null], $result); // Test type with additional parameters $result = Helpers::parseColumnType("ENUM('value1','value2')"); -Assert::same(['type' => 'ENUM', 'length' => null, 'scale' => null, 'parameters' => "'value1','value2'"], $result); +Assert::same(['type' => 'ENUM', 'size' => null, 'scale' => null, 'parameters' => "'value1','value2'"], $result); diff --git a/tests/Database/Reflection.columns.mysql.phpt b/tests/Database/Reflection.columns.mysql.phpt index 14d913162..07ab45e8b 100644 --- a/tests/Database/Reflection.columns.mysql.phpt +++ b/tests/Database/Reflection.columns.mysql.phpt @@ -11,11 +11,11 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-nette_test3.sql'); -$version80 = version_compare($connection->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0', '>='); +$version80 = version_compare($connection->getServerVersion(), '8.0', '>='); $reflection = $connection->getReflection(); $columns = $reflection->getTable('types')->columns; @@ -25,6 +25,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => $version80 ? 'INT UNSIGNED' : 'INT', 'size' => $version80 ? null : 11, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -35,6 +36,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INT', 'size' => $version80 ? null : 11, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -45,6 +47,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'SMALLINT', 'size' => $version80 ? null : 6, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -55,6 +58,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TINYINT', 'size' => $version80 ? null : 4, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -65,6 +69,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'MEDIUMINT', 'size' => $version80 ? null : 9, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -75,6 +80,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BIGINT', 'size' => $version80 ? null : 20, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -85,6 +91,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TINYINT', 'size' => 1, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -95,6 +102,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BIT', 'size' => 1, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -105,6 +113,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DECIMAL', 'size' => 10, + 'scale' => 0, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -115,6 +124,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DECIMAL', 'size' => 10, + 'scale' => 2, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -125,6 +135,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'FLOAT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -135,6 +146,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DOUBLE', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -145,6 +157,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DATE', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -155,6 +168,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TIME', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -165,6 +179,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DATETIME', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -175,6 +190,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TIMESTAMP', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -185,6 +201,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'YEAR', 'size' => $version80 ? null : 4, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -195,6 +212,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'CHAR', 'size' => 1, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -205,6 +223,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'VARCHAR', 'size' => 30, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -215,6 +234,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BINARY', 'size' => 1, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -225,6 +245,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'VARBINARY', 'size' => 30, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -235,6 +256,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BLOB', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -245,6 +267,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TINYBLOB', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -255,6 +278,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'MEDIUMBLOB', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -265,6 +289,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'LONGBLOB', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -275,6 +300,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TEXT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -285,6 +311,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TINYTEXT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -295,6 +322,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'MEDIUMTEXT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -305,6 +333,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'LONGTEXT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -315,6 +344,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'ENUM', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -325,6 +355,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'SET', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -339,6 +370,7 @@ Assert::same( 'table' => $c->table->name, 'nativeType' => $c->nativeType, 'size' => $c->size, + 'scale' => $c->scale, 'nullable' => $c->nullable, 'default' => $c->default, 'autoIncrement' => $c->autoIncrement, diff --git a/tests/Database/Reflection.columns.postgre.phpt b/tests/Database/Reflection.columns.postgre.phpt index b404de843..81c219838 100644 --- a/tests/Database/Reflection.columns.postgre.phpt +++ b/tests/Database/Reflection.columns.postgre.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/pgsql-nette_test3.sql'); @@ -24,6 +24,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INT2', 'size' => 2, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -34,6 +35,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INT4', 'size' => 4, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -44,6 +46,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INT8', 'size' => 8, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -54,6 +57,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NUMERIC', 'size' => 3, + 'scale' => 2, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -64,6 +68,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'FLOAT4', 'size' => 4, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -74,6 +79,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'FLOAT8', 'size' => 8, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -84,6 +90,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'MONEY', 'size' => 8, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -94,6 +101,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BOOL', 'size' => 1, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -104,6 +112,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DATE', 'size' => 4, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -114,6 +123,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TIME', 'size' => 8, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -124,6 +134,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TIMESTAMP', 'size' => 8, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -134,6 +145,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TIMESTAMPTZ', 'size' => 8, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -144,6 +156,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INTERVAL', 'size' => 16, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -154,6 +167,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BPCHAR', 'size' => 30, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -164,6 +178,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'VARCHAR', 'size' => 30, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -174,6 +189,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TEXT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -184,6 +200,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TSQUERY', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -194,6 +211,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TSVECTOR', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -204,6 +222,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'UUID', 'size' => 16, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -214,6 +233,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'XML', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -224,6 +244,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'CIDR', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -234,6 +255,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INET', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -244,6 +266,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'MACADDR', 'size' => 6, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -254,6 +277,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BIT', 'size' => -3, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -264,6 +288,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'VARBIT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -274,6 +299,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BYTEA', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -284,6 +310,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BOX', 'size' => 32, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -294,6 +321,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'CIRCLE', 'size' => 24, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -304,6 +332,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'LSEG', 'size' => 32, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -314,6 +343,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'PATH', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -324,6 +354,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'POINT', 'size' => 16, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -334,6 +365,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'POLYGON', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -348,6 +380,7 @@ Assert::same( 'table' => $c->table->name, 'nativeType' => $c->nativeType, 'size' => $c->size, + 'scale' => $c->scale, 'nullable' => $c->nullable, 'default' => $c->default, 'autoIncrement' => $c->autoIncrement, diff --git a/tests/Database/Reflection.columns.sqlite.phpt b/tests/Database/Reflection.columns.sqlite.phpt index 4f32b02a5..c2b7e8855 100644 --- a/tests/Database/Reflection.columns.sqlite.phpt +++ b/tests/Database/Reflection.columns.sqlite.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlite-nette_test3.sql'); @@ -24,6 +24,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -34,6 +35,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INTEGER', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -44,6 +46,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TINYINT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -54,6 +57,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'SMALLINT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -64,6 +68,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'MEDIUMINT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -74,6 +79,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BIGINT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -84,6 +90,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'UNSIGNED BIG INT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -94,6 +101,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INT2', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -104,6 +112,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INT8', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -114,6 +123,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'CHARACTER', 'size' => 20, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -124,6 +134,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'VARCHAR', 'size' => 255, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -134,6 +145,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'VARYING CHARACTER', 'size' => 255, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -144,6 +156,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NCHAR', 'size' => 55, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -154,6 +167,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NATIVE CHARACTER', 'size' => 70, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -164,6 +178,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NVARCHAR', 'size' => 100, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -174,6 +189,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TEXT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -184,6 +200,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'CLOB', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -194,6 +211,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BLOB', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -204,6 +222,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'REAL', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -214,6 +233,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DOUBLE', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -224,6 +244,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DOUBLE PRECISION', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -234,6 +255,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'FLOAT', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -244,6 +266,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NUMERIC', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -254,6 +277,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DECIMAL', 'size' => 10, + 'scale' => 5, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -264,6 +288,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BOOLEAN', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -274,6 +299,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DATE', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -284,6 +310,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DATETIME', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -298,6 +325,7 @@ Assert::same( 'table' => $c->table->name, 'nativeType' => $c->nativeType, 'size' => $c->size, + 'scale' => $c->scale, 'nullable' => $c->nullable, 'default' => $c->default, 'autoIncrement' => $c->autoIncrement, diff --git a/tests/Database/Reflection.columns.sqlsrv.phpt b/tests/Database/Reflection.columns.sqlsrv.phpt index 57109e4aa..1d7ce8952 100644 --- a/tests/Database/Reflection.columns.sqlsrv.phpt +++ b/tests/Database/Reflection.columns.sqlsrv.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-nette_test3.sql'); @@ -24,6 +24,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BIGINT', 'size' => 19, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -34,6 +35,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BINARY', 'size' => 3, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -44,6 +46,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'BIT', 'size' => 1, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -54,6 +57,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'CHAR', 'size' => 5, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -64,6 +68,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DATE', 'size' => 10, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -74,6 +79,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DATETIME', 'size' => 23, + 'scale' => 3, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -84,6 +90,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DATETIME2', 'size' => 27, + 'scale' => 7, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -94,6 +101,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'DECIMAL', 'size' => 18, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -104,6 +112,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'FLOAT', 'size' => 53, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -114,6 +123,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'GEOGRAPHY', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -124,6 +134,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'GEOMETRY', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -134,6 +145,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'HIERARCHYID', 'size' => 892, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -144,6 +156,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'INT', 'size' => 10, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -154,6 +167,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'MONEY', 'size' => 19, + 'scale' => 4, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -164,6 +178,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NCHAR', 'size' => 2, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -174,6 +189,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NTEXT', 'size' => 16, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -184,6 +200,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NUMERIC', 'size' => 10, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -194,6 +211,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NUMERIC', 'size' => 10, + 'scale' => 2, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -204,6 +222,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'NVARCHAR', 'size' => 2, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -214,6 +233,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'REAL', 'size' => 24, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -224,6 +244,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'SMALLDATETIME', 'size' => 16, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -234,6 +255,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'SMALLINT', 'size' => 5, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -244,6 +266,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'SMALLMONEY', 'size' => 10, + 'scale' => 4, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -254,6 +277,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TEXT', 'size' => 16, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -264,6 +288,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TIME', 'size' => 16, + 'scale' => 7, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -274,6 +299,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'TINYINT', 'size' => 3, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -284,6 +310,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'UNIQUEIDENTIFIER', 'size' => 16, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -294,6 +321,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'VARBINARY', 'size' => 1, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -304,6 +332,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'VARCHAR', 'size' => 1, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -314,6 +343,7 @@ $expectedColumns = [ 'table' => 'types', 'nativeType' => 'XML', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -328,6 +358,7 @@ Assert::same( 'table' => $c->table->name, 'nativeType' => $c->nativeType, 'size' => $c->size, + 'scale' => $c->scale, 'nullable' => $c->nullable, 'default' => $c->default, 'autoIncrement' => $c->autoIncrement, diff --git a/tests/Database/Reflection.phpt b/tests/Database/Reflection.phpt index ebbc1f9c6..9afcd4116 100644 --- a/tests/Database/Reflection.phpt +++ b/tests/Database/Reflection.phpt @@ -7,17 +7,17 @@ declare(strict_types=1); -use Nette\Database\Driver; +use Nette\Database\Drivers\Engine; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); $reflection = $connection->getReflection(); -$schemaSupported = $connection->getDriver()->isSupported(Driver::SupportSchema); +$schemaSupported = $connection->getDatabaseEngine()->isSupported(Engine::SupportSchema); // table names $tableNames = array_keys($reflection->tables); @@ -75,6 +75,7 @@ $expectedColumns = [ 'table' => 'author', 'nativeType' => 'INT', 'size' => 11, + 'scale' => null, 'nullable' => false, 'default' => null, 'autoIncrement' => true, @@ -85,6 +86,7 @@ $expectedColumns = [ 'table' => 'author', 'nativeType' => 'VARCHAR', 'size' => 30, + 'scale' => null, 'nullable' => false, 'default' => null, 'autoIncrement' => false, @@ -95,6 +97,7 @@ $expectedColumns = [ 'table' => 'author', 'nativeType' => 'VARCHAR', 'size' => 100, + 'scale' => null, 'nullable' => false, 'default' => null, 'autoIncrement' => false, @@ -105,6 +108,7 @@ $expectedColumns = [ 'table' => 'author', 'nativeType' => 'DATE', 'size' => null, + 'scale' => null, 'nullable' => true, 'default' => null, 'autoIncrement' => false, @@ -114,7 +118,7 @@ $expectedColumns = [ switch ($driverName) { case 'mysql': - $version = $connection->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); + $version = $connection->getServerVersion(); if (version_compare($version, '8.0', '>=')) { $expectedColumns['id']['size'] = null; } @@ -149,6 +153,7 @@ Assert::same( 'table' => $c->table->name, 'nativeType' => $c->nativeType, 'size' => $c->size, + 'scale' => $c->scale, 'nullable' => $c->nullable, 'default' => $c->default, 'autoIncrement' => $c->autoIncrement, diff --git a/tests/Database/ResultSet.fetch().phpt b/tests/Database/Result.fetch().phpt similarity index 86% rename from tests/Database/ResultSet.fetch().phpt rename to tests/Database/Result.fetch().phpt index 376bcb085..a62214044 100644 --- a/tests/Database/ResultSet.fetch().phpt +++ b/tests/Database/Result.fetch().phpt @@ -1,7 +1,7 @@ getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('', function () use ($connection, $driverName) { $res = $connection->query('SELECT name, name FROM author'); $message = match ($driverName) { - 'mysql' => "Found duplicate columns in database result set: 'name' (from author).", + 'mysql' => "Found duplicate columns in database result set: 'name'.", 'pgsql' => "Found duplicate columns in database result set: 'name'%a%", - 'sqlite' => "Found duplicate columns in database result set: 'name' (from author).", + 'sqlite' => "Found duplicate columns in database result set: 'name'.", 'sqlsrv' => "Found duplicate columns in database result set: 'name'.", default => Assert::fail("Unsupported driver $driverName"), }; @@ -38,7 +38,7 @@ test('', function () use ($connection, $driverName) { test('tests closeCursor()', function () use ($connection, $driverName) { if ($driverName === 'mysql') { $connection->query('CREATE DEFINER = CURRENT_USER PROCEDURE `testProc`(IN param int(10) unsigned) BEGIN SELECT * FROM book WHERE id != param; END;;'); - $connection->getPdo()->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); + $connection->getConnection()->getNativeConnection()->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); $res = $connection->query('CALL testProc(1)'); foreach ($res as $row) { @@ -54,9 +54,9 @@ test('tests closeCursor()', function () use ($connection, $driverName) { test('', function () use ($connection, $driverName) { $res = $connection->query('SELECT book.id, author.id, author.name, translator.name FROM book JOIN author ON (author.id = book.author_id) JOIN author translator ON (translator.id = book.translator_id)'); $message = match ($driverName) { - 'mysql' => "Found duplicate columns in database result set: 'id' (from book, author), 'name' (from author, translator).", + 'mysql' => "Found duplicate columns in database result set: 'id', 'name'.", 'pgsql' => "Found duplicate columns in database result set: 'id'%a% 'name'%a%", - 'sqlite' => "Found duplicate columns in database result set: 'id' (from book, author), 'name' (from author).", + 'sqlite' => "Found duplicate columns in database result set: 'id', 'name'.", 'sqlsrv' => "Found duplicate columns in database result set: 'id', 'name'.", default => Assert::fail("Unsupported driver $driverName"), }; diff --git a/tests/Database/ResultSet.fetchAll().phpt b/tests/Database/Result.fetchAll().phpt similarity index 87% rename from tests/Database/ResultSet.fetchAll().phpt rename to tests/Database/Result.fetchAll().phpt index 8d7ade31d..0609c94cf 100644 --- a/tests/Database/ResultSet.fetchAll().phpt +++ b/tests/Database/Result.fetchAll().phpt @@ -1,7 +1,7 @@ getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); @@ -24,7 +24,7 @@ match ($driverName) { }; Assert::same(1, $res->getColumnCount()); -Assert::same('SELECT id FROM book ORDER BY id', $res->getQueryString()); +Assert::same('SELECT id FROM book ORDER BY id', $res->getQuery()->getSql()); Assert::equal([ Nette\Database\Row::from(['id' => 1]), diff --git a/tests/Database/ResultSet.fetchAssoc().phpt b/tests/Database/Result.fetchAssoc().phpt similarity index 93% rename from tests/Database/ResultSet.fetchAssoc().phpt rename to tests/Database/Result.fetchAssoc().phpt index 01bd04391..2b33f8287 100644 --- a/tests/Database/ResultSet.fetchAssoc().phpt +++ b/tests/Database/Result.fetchAssoc().phpt @@ -1,7 +1,7 @@ getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); diff --git a/tests/Database/ResultSet.fetchField().phpt b/tests/Database/Result.fetchField().phpt similarity index 85% rename from tests/Database/ResultSet.fetchField().phpt rename to tests/Database/Result.fetchField().phpt index 065c3820a..f54d3b6cb 100644 --- a/tests/Database/ResultSet.fetchField().phpt +++ b/tests/Database/Result.fetchField().phpt @@ -1,7 +1,7 @@ getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); diff --git a/tests/Database/ResultSet.fetchFields().phpt b/tests/Database/Result.fetchList().phpt similarity index 71% rename from tests/Database/ResultSet.fetchFields().phpt rename to tests/Database/Result.fetchList().phpt index 92457b389..3885b942b 100644 --- a/tests/Database/ResultSet.fetchFields().phpt +++ b/tests/Database/Result.fetchList().phpt @@ -1,7 +1,7 @@ getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); test('', function () use ($connection) { $res = $connection->query('SELECT name, id FROM author ORDER BY id'); - Assert::same(['Jakub Vrana', 11], $res->fetchFields()); + Assert::same(['Jakub Vrana', 11], $res->fetchList()); }); test('', function () use ($connection) { $res = $connection->query('SELECT id FROM author WHERE id = ?', 666); - Assert::null($res->fetchFields()); + Assert::null($res->fetchList()); }); diff --git a/tests/Database/ResultSet.fetchPairs().phpt b/tests/Database/Result.fetchPairs().phpt similarity index 97% rename from tests/Database/ResultSet.fetchPairs().phpt rename to tests/Database/Result.fetchPairs().phpt index a0fc0ca8f..6927dfb8a 100644 --- a/tests/Database/ResultSet.fetchPairs().phpt +++ b/tests/Database/Result.fetchPairs().phpt @@ -1,7 +1,7 @@ getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); diff --git a/tests/Database/ResultSet.customNormalizer.phpt b/tests/Database/ResultSet.customNormalizer.phpt deleted file mode 100644 index 4da4189ae..000000000 --- a/tests/Database/ResultSet.customNormalizer.phpt +++ /dev/null @@ -1,53 +0,0 @@ -getConnection(); -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql"); - -$connection->query('UPDATE author SET born=?', new DateTime('2022-01-23')); - - -test('disabled normalization', function () use ($connection) { - $driverName = $GLOBALS['driverName']; - - $connection->setRowNormalizer(null); - $res = $connection->query('SELECT * FROM author'); - $asInt = $driverName === 'pgsql' || ($driverName !== 'sqlsrv' && PHP_VERSION_ID >= 80100); - Assert::same([ - 'id' => $asInt ? 11 : '11', - 'name' => 'Jakub Vrana', - 'web' => 'http://www.vrana.cz/', - 'born' => $driverName === 'sqlite' ? ($asInt ? 1_642_892_400 : '1642892400') : '2022-01-23', - ], (array) $res->fetch()); -}); - - -test('custom normalization', function () use ($connection) { - $driverName = $GLOBALS['driverName']; - - $connection->setRowNormalizer(function (array $row, Nette\Database\ResultSet $resultSet) { - foreach ($row as $key => $value) { - unset($row[$key]); - $row['_' . $key . '_'] = (string) $value; - } - - return $row; - }); - - $res = $connection->query('SELECT * FROM author'); - Assert::same([ - '_id_' => '11', - '_name_' => 'Jakub Vrana', - '_web_' => 'http://www.vrana.cz/', - '_born_' => $driverName === 'sqlite' ? '1642892400' : '2022-01-23', - ], (array) $res->fetch()); -}); diff --git a/tests/Database/ResultSet.normalizeRow.mysql.phpt b/tests/Database/ResultSet.normalizeRow.mysql.phpt index f78e86154..e078dfbf6 100644 --- a/tests/Database/ResultSet.normalizeRow.mysql.phpt +++ b/tests/Database/ResultSet.normalizeRow.mysql.phpt @@ -12,7 +12,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-nette_test3.sql'); @@ -151,7 +151,7 @@ Assert::same( ); -$connection->getPdo()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); +$connection->getConnection()->getNativeConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $res = $connection->query('SELECT `int`, `decimal`, `decimal2`, `float`, `double` FROM types'); Assert::same([ 'int' => 1, diff --git a/tests/Database/ResultSet.normalizeRow.postgre.phpt b/tests/Database/ResultSet.normalizeRow.postgre.phpt index fd24a2315..eac4f0e84 100644 --- a/tests/Database/ResultSet.normalizeRow.postgre.phpt +++ b/tests/Database/ResultSet.normalizeRow.postgre.phpt @@ -12,7 +12,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/pgsql-nette_test3.sql'); diff --git a/tests/Database/ResultSet.normalizeRow.sqlite.phpt b/tests/Database/ResultSet.normalizeRow.sqlite.phpt index 347cf977b..5e92fcd3a 100644 --- a/tests/Database/ResultSet.normalizeRow.sqlite.phpt +++ b/tests/Database/ResultSet.normalizeRow.sqlite.phpt @@ -12,7 +12,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlite-nette_test3.sql'); diff --git a/tests/Database/ResultSet.normalizeRow.sqlsrv.phpt b/tests/Database/ResultSet.normalizeRow.sqlsrv.phpt index 09ea94fe7..4ff795da7 100644 --- a/tests/Database/ResultSet.normalizeRow.sqlsrv.phpt +++ b/tests/Database/ResultSet.normalizeRow.sqlsrv.phpt @@ -12,7 +12,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-nette_test3.sql'); @@ -21,12 +21,12 @@ $res = $connection->query('SELECT * FROM types'); Assert::equal([ 'bigint' => 1, 'binary_3' => "\x00\x00\xFF", - 'bit' => '1', + 'bit' => true, 'char_5' => 'a ', 'date' => new DateTime('2012-10-13 00:00:00'), 'datetime' => new DateTime('2012-10-13 10:10:10'), 'datetime2' => new DateTime('2012-10-13 10:10:10'), - 'decimal' => 1.0, + 'decimal' => 1, 'float' => 1.1, 'geography' => "\xe6\x10\x00\x00\x01\x14\x87\x16\xd9\xce\xf7\xd3G@\xd7\xa3p=\n\x97^\xc0\x87\x16\xd9\xce\xf7\xd3G@\xcb\xa1E\xb6\xf3\x95^\xc0", 'geometry' => "\x00\x00\x00\x00\x01\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00Y@\x00\x00\x00\x00\x00\x00Y@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x80f@\x00\x00\x00\x00\x00\x80f@\x00\x00\x00\x00\x00\x80f@\x01\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x02", @@ -35,7 +35,7 @@ Assert::equal([ 'money' => 1111.1, 'nchar' => 'a', 'ntext' => 'a', - 'numeric_10_0' => 1.0, + 'numeric_10_0' => 1, 'numeric_10_2' => 1.1, 'nvarchar' => 'a', 'real' => 1.1, @@ -54,12 +54,12 @@ Assert::equal([ Assert::equal([ 'bigint' => 0, 'binary_3' => "\x00\x00\x00", - 'bit' => '0', + 'bit' => false, 'char_5' => ' ', 'date' => new DateTime('0001-01-01 00:00:00'), 'datetime' => new DateTime('1753-01-01 00:00:00'), 'datetime2' => new DateTime('0001-01-01 00:00:00'), - 'decimal' => 0.0, + 'decimal' => 0, 'float' => 0.5, 'geography' => null, 'geometry' => null, @@ -68,7 +68,7 @@ Assert::equal([ 'money' => 0.0, 'nchar' => ' ', 'ntext' => '', - 'numeric_10_0' => 0.0, + 'numeric_10_0' => 0, 'numeric_10_2' => 0.5, 'nvarchar' => '', 'real' => 0.0, diff --git a/tests/Database/Row.phpt b/tests/Database/Row.phpt index 9dfc65170..04631da86 100644 --- a/tests/Database/Row.phpt +++ b/tests/Database/Row.phpt @@ -11,10 +11,10 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); test('numeric field', function () use ($connection) { - $row = $connection->fetch("SELECT 123 AS {$connection->getDriver()->delimite('123')}, NULL as nullcol"); + $row = $connection->fetch("SELECT 123 AS {$connection->getDatabaseEngine()->delimit('123')}, NULL as nullcol"); Assert::same(123, $row->{123}); Assert::same(123, $row->{'123'}); Assert::true(isset($row->{123})); diff --git a/tests/Database/SqlPreprocessor.enum.phpt b/tests/Database/SqlPreprocessor.enum.phpt index 91c787cff..0dc4cc250 100644 --- a/tests/Database/SqlPreprocessor.enum.phpt +++ b/tests/Database/SqlPreprocessor.enum.phpt @@ -11,7 +11,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); enum EnumInt: int { diff --git a/tests/Database/SqlPreprocessor.phpt b/tests/Database/SqlPreprocessor.phpt index f51d5ef02..ee8daccc2 100644 --- a/tests/Database/SqlPreprocessor.phpt +++ b/tests/Database/SqlPreprocessor.phpt @@ -12,7 +12,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$connection = connectToDB()->getConnection(); +$connection = connectToDB(); $preprocessor = new Nette\Database\SqlPreprocessor($connection); test('basic', function () use ($preprocessor) { diff --git a/tests/Database/Structure.phpt b/tests/Database/Structure.phpt index 434908402..61afde090 100644 --- a/tests/Database/Structure.phpt +++ b/tests/Database/Structure.phpt @@ -6,6 +6,7 @@ declare(strict_types=1); +use Mockery\MockInterface; use Nette\Database\Structure; use Tester\Assert; use Tester\TestCase; @@ -29,61 +30,56 @@ class StructureMock extends Structure */ class StructureTestCase extends TestCase { - private Nette\Database\Connection $connection; - private Nette\Database\Driver $driver; - private Nette\Caching\Storage $storage; - private Structure $structure; + private Nette\Database\Drivers\Engine|MockInterface $engine; + private Nette\Caching\Cache|MockInterface $cache; + private Structure|MockInterface $structure; protected function setUp() { parent::setUp(); - $this->driver = Mockery::mock(Nette\Database\Driver::class); - $this->connection = Mockery::mock(Nette\Database\Connection::class); - $this->storage = Mockery::mock(Nette\Caching\Storage::class); + $this->engine = Mockery::mock(Nette\Database\Drivers\Engine::class); + $this->cache = Mockery::mock(Nette\Caching\Cache::class); - $this->connection->shouldReceive('getDsn')->once()->andReturn(''); - $this->connection->shouldReceive('getDriver')->once()->andReturn($this->driver); - $this->driver->shouldReceive('getTables')->once()->andReturn([ + $this->engine->shouldReceive('getTables')->once()->andReturn([ ['name' => 'authors', 'view' => false], ['name' => 'Books', 'view' => false], ['name' => 'tags', 'view' => false], ['name' => 'books_x_tags', 'view' => false], ['name' => 'books_view', 'view' => true], ]); - $this->driver->shouldReceive('getColumns')->with('authors')->once()->andReturn([ - ['name' => 'id', 'primary' => true, 'autoincrement' => true, 'vendor' => ['sequence' => '"public"."authors_id_seq"']], - ['name' => 'name', 'primary' => false, 'autoincrement' => false, 'vendor' => []], + $this->engine->shouldReceive('getColumns')->with('authors')->once()->andReturn([ + ['name' => 'id', 'primary' => true, 'autoIncrement' => true, 'vendor' => ['sequence' => '"public"."authors_id_seq"']], + ['name' => 'name', 'primary' => false, 'autoIncrement' => false, 'vendor' => []], ]); - $this->driver->shouldReceive('getColumns')->with('Books')->once()->andReturn([ - ['name' => 'id', 'primary' => true, 'autoincrement' => true, 'vendor' => ['sequence' => '"public"."Books_id_seq"']], - ['name' => 'title', 'primary' => false, 'autoincrement' => false, 'vendor' => []], + $this->engine->shouldReceive('getColumns')->with('Books')->once()->andReturn([ + ['name' => 'id', 'primary' => true, 'autoIncrement' => true, 'vendor' => ['sequence' => '"public"."Books_id_seq"']], + ['name' => 'title', 'primary' => false, 'autoIncrement' => false, 'vendor' => []], ]); - $this->driver->shouldReceive('getColumns')->with('tags')->once()->andReturn([ - ['name' => 'id', 'primary' => true, 'autoincrement' => false, 'vendor' => []], - ['name' => 'name', 'primary' => false, 'autoincrement' => false, 'vendor' => []], + $this->engine->shouldReceive('getColumns')->with('tags')->once()->andReturn([ + ['name' => 'id', 'primary' => true, 'autoIncrement' => false, 'vendor' => []], + ['name' => 'name', 'primary' => false, 'autoIncrement' => false, 'vendor' => []], ]); - $this->driver->shouldReceive('getColumns')->with('books_x_tags')->once()->andReturn([ - ['name' => 'book_id', 'primary' => true, 'autoincrement' => false, 'vendor' => []], - ['name' => 'tag_id', 'primary' => true, 'autoincrement' => false, 'vendor' => []], + $this->engine->shouldReceive('getColumns')->with('books_x_tags')->once()->andReturn([ + ['name' => 'book_id', 'primary' => true, 'autoIncrement' => false, 'vendor' => []], + ['name' => 'tag_id', 'primary' => true, 'autoIncrement' => false, 'vendor' => []], ]); - $this->driver->shouldReceive('getColumns')->with('books_view')->once()->andReturn([ - ['name' => 'id', 'primary' => false, 'autoincrement' => false, 'vendor' => []], - ['name' => 'title', 'primary' => false, 'autoincrement' => false, 'vendor' => []], + $this->engine->shouldReceive('getColumns')->with('books_view')->once()->andReturn([ + ['name' => 'id', 'primary' => false, 'autoIncrement' => false, 'vendor' => []], + ['name' => 'title', 'primary' => false, 'autoIncrement' => false, 'vendor' => []], ]); - $this->connection->shouldReceive('getDriver')->times(4)->andReturn($this->driver); - $this->driver->shouldReceive('getForeignKeys')->with('authors')->once()->andReturn([]); - $this->driver->shouldReceive('getForeignKeys')->with('Books')->once()->andReturn([ - ['local' => 'author_id', 'table' => 'authors', 'foreign' => 'id', 'name' => 'authors_fk1'], - ['local' => 'translator_id', 'table' => 'authors', 'foreign' => 'id', 'name' => 'authors_fk2'], + $this->engine->shouldReceive('getForeignKeys')->with('authors')->once()->andReturn([]); + $this->engine->shouldReceive('getForeignKeys')->with('Books')->once()->andReturn([ + ['local' => ['author_id'], 'table' => 'authors', 'foreign' => ['id'], 'name' => 'authors_fk1'], + ['local' => ['translator_id'], 'table' => 'authors', 'foreign' => ['id'], 'name' => 'authors_fk2'], ]); - $this->driver->shouldReceive('getForeignKeys')->with('tags')->once()->andReturn([]); - $this->driver->shouldReceive('getForeignKeys')->with('books_x_tags')->once()->andReturn([ - ['local' => 'book_id', 'table' => 'Books', 'foreign' => 'id', 'name' => 'books_x_tags_fk1'], - ['local' => 'tag_id', 'table' => 'tags', 'foreign' => 'id', 'name' => 'books_x_tags_fk2'], + $this->engine->shouldReceive('getForeignKeys')->with('tags')->once()->andReturn([]); + $this->engine->shouldReceive('getForeignKeys')->with('books_x_tags')->once()->andReturn([ + ['local' => ['book_id'], 'table' => 'Books', 'foreign' => ['id'], 'name' => 'books_x_tags_fk1'], + ['local' => ['tag_id'], 'table' => 'tags', 'foreign' => ['id'], 'name' => 'books_x_tags_fk2'], ]); - $this->structure = new StructureMock($this->connection, $this->storage); + $this->structure = new StructureMock($this->engine, $this->cache); } @@ -102,8 +98,8 @@ class StructureTestCase extends TestCase public function testGetColumns() { $columns = [ - ['name' => 'id', 'primary' => true, 'autoincrement' => false, 'vendor' => []], - ['name' => 'name', 'primary' => false, 'autoincrement' => false, 'vendor' => []], + ['name' => 'id', 'primary' => true, 'autoIncrement' => false, 'vendor' => []], + ['name' => 'name', 'primary' => false, 'autoIncrement' => false, 'vendor' => []], ]; Assert::same($columns, $this->structure->getColumns('tags')); @@ -132,9 +128,8 @@ class StructureTestCase extends TestCase public function testGetPrimaryKeySequence() { - $this->connection->shouldReceive('getDriver')->times(4)->andReturn($this->driver); - $this->driver->shouldReceive('isSupported')->with('sequence')->once()->andReturn(false); - $this->driver->shouldReceive('isSupported')->with('sequence')->times(3)->andReturn(true); + $this->engine->shouldReceive('isSupported')->with('sequence')->once()->andReturn(false); + $this->engine->shouldReceive('isSupported')->with('sequence')->times(3)->andReturn(true); Assert::null($this->structure->getPrimaryKeySequence('Authors')); Assert::same('"public"."authors_id_seq"', $this->structure->getPrimaryKeySequence('Authors')); diff --git a/tests/Database/Structure.schemas.phpt b/tests/Database/Structure.schemas.phpt index 0a145a56c..e92d333b5 100644 --- a/tests/Database/Structure.schemas.phpt +++ b/tests/Database/Structure.schemas.phpt @@ -6,6 +6,7 @@ declare(strict_types=1); +use Mockery\MockInterface; use Nette\Database\Structure; use Tester\Assert; use Tester\TestCase; @@ -29,42 +30,37 @@ class StructureMock extends Structure */ class StructureSchemasTestCase extends TestCase { - private Nette\Database\Connection $connection; - private Nette\Database\Driver $driver; - private Nette\Caching\Storage $storage; - private Structure $structure; + private Nette\Database\Drivers\Engine|MockInterface $engine; + private Nette\Caching\Cache|MockInterface $cache; + private Structure|MockInterface $structure; protected function setUp() { parent::setUp(); - $this->driver = Mockery::mock(Nette\Database\Driver::class); - $this->connection = Mockery::mock(Nette\Database\Connection::class); - $this->storage = Mockery::mock(Nette\Caching\Storage::class); + $this->engine = Mockery::mock(Nette\Database\Drivers\Engine::class); + $this->cache = Mockery::mock(Nette\Caching\Cache::class); - $this->connection->shouldReceive('getDsn')->once()->andReturn(''); - $this->connection->shouldReceive('getDriver')->once()->andReturn($this->driver); - $this->driver->shouldReceive('getTables')->once()->andReturn([ + $this->engine->shouldReceive('getTables')->once()->andReturn([ ['name' => 'authors', 'view' => false, 'fullName' => 'authors.authors'], ['name' => 'books', 'view' => false, 'fullName' => 'books.books'], ]); - $this->driver->shouldReceive('getColumns')->with('authors.authors')->once()->andReturn([ + $this->engine->shouldReceive('getColumns')->with('authors.authors')->once()->andReturn([ ['name' => 'id', 'primary' => true, 'vendor' => ['sequence' => '"authors"."authors_id_seq"']], ['name' => 'name', 'primary' => false, 'vendor' => []], ]); - $this->driver->shouldReceive('getColumns')->with('books.books')->once()->andReturn([ + $this->engine->shouldReceive('getColumns')->with('books.books')->once()->andReturn([ ['name' => 'id', 'primary' => true, 'vendor' => ['sequence' => '"books"."books_id_seq"']], ['name' => 'title', 'primary' => false, 'vendor' => []], ]); - $this->connection->shouldReceive('getDriver')->times(2)->andReturn($this->driver); - $this->driver->shouldReceive('getForeignKeys')->with('authors.authors')->once()->andReturn([]); - $this->driver->shouldReceive('getForeignKeys')->with('books.books')->once()->andReturn([ - ['local' => 'author_id', 'table' => 'authors.authors', 'foreign' => 'id', 'name' => 'authors_authors_fk1'], - ['local' => 'translator_id', 'table' => 'authors.authors', 'foreign' => 'id', 'name' => 'authors_authors_fk2'], + $this->engine->shouldReceive('getForeignKeys')->with('authors.authors')->once()->andReturn([]); + $this->engine->shouldReceive('getForeignKeys')->with('books.books')->once()->andReturn([ + ['local' => ['author_id'], 'table' => 'authors.authors', 'foreign' => ['id'], 'name' => 'authors_authors_fk1'], + ['local' => ['translator_id'], 'table' => 'authors.authors', 'foreign' => ['id'], 'name' => 'authors_authors_fk2'], ]); - $this->structure = new StructureMock($this->connection, $this->storage); + $this->structure = new StructureMock($this->engine, $this->cache); } diff --git a/tests/Database/connection.disconnect.phpt b/tests/Database/connection.disconnect.phpt new file mode 100644 index 000000000..b2b06ed8e --- /dev/null +++ b/tests/Database/connection.disconnect.phpt @@ -0,0 +1,55 @@ + null, 'password' => null]; + $connections = 1; + + $connection = new Nette\Database\Explorer($options['dsn'], $options['username'], $options['password']); + try { + $connection->connect(); + } catch (Nette\Database\DriverException $e) { + Tester\Environment::skip("Connection to '$options[dsn]' failed. Reason: " . $e->getMessage()); + } + + $connection->onConnect[] = function () use (&$connections) { + $connections++; + }; + + // first connection + $native = $connection->getConnection()->getNativeConnection(); + $driver = $connection->getConnection(); + Assert::same(1, $connections); + + // still first connection + $connection->connect(); + Assert::same($native, $connection->getConnection()->getNativeConnection()); + Assert::same($driver, $connection->getConnection()); + Assert::same(1, $connections); + + // second connection + $connection->reconnect(); + $native2 = $connection->getConnection()->getNativeConnection(); + $driver2 = $connection->getConnection(); + + Assert::notSame($native, $native2); + Assert::notSame($driver, $driver2); + Assert::same(2, $connections); + + // third connection + $connection->disconnect(); + Assert::notSame($native2, $connection->getConnection()->getNativeConnection()); + Assert::notSame($driver2, $connection->getConnection()); + Assert::same(3, $connections); +}); diff --git a/tests/Database/connection.option.lazy.phpt b/tests/Database/connection.option.lazy.phpt deleted file mode 100644 index 1561dc39f..000000000 --- a/tests/Database/connection.option.lazy.phpt +++ /dev/null @@ -1,86 +0,0 @@ - new Nette\Database\Connection('dsn', 'user', 'password'), - Nette\Database\DriverException::class, - '%a%valid data source %a%', - ); -}); - - -test('lazy', function () { - $connection = new Nette\Database\Connection('dsn', 'user', 'password', ['lazy' => true]); - $explorer = new Nette\Database\Explorer($connection, new Structure($connection, new DevNullStorage)); - Assert::exception( - fn() => $explorer->query('SELECT ?', 10), - Nette\Database\DriverException::class, - '%a%valid data source %a%', - ); -}); - - -test('', function () { - $connection = new Nette\Database\Connection('dsn', 'user', 'password', ['lazy' => true]); - Assert::exception( - fn() => $connection->quote('x'), - Nette\Database\DriverException::class, - '%a%valid data source %a%', - ); -}); - - -test('connect & disconnect', function () { - $options = Tester\Environment::loadData() + ['username' => null, 'password' => null]; - $connections = 1; - - try { - $connection = new Nette\Database\Connection($options['dsn'], $options['username'], $options['password']); - } catch (PDOException $e) { - Tester\Environment::skip("Connection to '$options[dsn]' failed. Reason: " . $e->getMessage()); - } - - $connection->onConnect[] = function () use (&$connections) { - $connections++; - }; - - // first connection - $pdo = $connection->getPdo(); - $driver = $connection->getDriver(); - Assert::same(1, $connections); - - // still first connection - $connection->connect(); - Assert::same($pdo, $connection->getPdo()); - Assert::same($driver, $connection->getDriver()); - Assert::same(1, $connections); - - // second connection - $connection->reconnect(); - $pdo2 = $connection->getPdo(); - $driver2 = $connection->getDriver(); - - Assert::notSame($pdo, $pdo2); - Assert::notSame($driver, $driver2); - Assert::same(2, $connections); - - // third connection - $connection->disconnect(); - Assert::notSame($pdo2, $connection->getPdo()); - Assert::notSame($driver2, $connection->getDriver()); - Assert::same(3, $connections); -}); diff --git a/tests/Database/connection.options.mysql.phpt b/tests/Database/connection.options.mysql.phpt index f95c202c7..9e20c290d 100644 --- a/tests/Database/connection.options.mysql.phpt +++ b/tests/Database/connection.options.mysql.phpt @@ -13,13 +13,13 @@ require __DIR__ . '/../bootstrap.php'; test('default charset', function () { - $connection = connectToDB(['charset' => null])->getConnection(); + $connection = connectToDB(['charset' => null]); $row = $connection->fetch("SHOW VARIABLES LIKE 'character_set_client'"); Assert::same('utf8mb4', $row->Value); }); test('custom charset', function () { - $connection = connectToDB(['charset' => 'latin2'])->getConnection(); + $connection = connectToDB(['charset' => 'latin2']); $row = $connection->fetch("SHOW VARIABLES LIKE 'character_set_client'"); Assert::same('latin2', $row->Value); }); @@ -27,28 +27,28 @@ test('custom charset', function () { test('custom sqlmode', function () { $desiredMode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'; - $connection = connectToDB(['sqlmode' => $desiredMode])->getConnection(); + $connection = connectToDB(['sqlmode' => $desiredMode]); $field = $connection->fetchField('SELECT @@sql_mode'); Assert::same($desiredMode, $field); }); test('default convertBoolean', function () { - $connection = connectToDB(['convertBoolean' => null])->getConnection(); + $connection = connectToDB(['convertBoolean' => null]); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-nette_test3.sql'); $row = $connection->fetch('SELECT * FROM types'); - Assert::same(1, $row->bool); + Assert::same(true, $row->bool); }); test('convertBoolean = true', function () { - $connection = connectToDB(['convertBoolean' => true])->getConnection(); + $connection = connectToDB(['convertBoolean' => true]); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-nette_test3.sql'); $row = $connection->fetch('SELECT * FROM types'); Assert::same(true, $row->bool); }); test('convertBoolean = false', function () { - $connection = connectToDB(['convertBoolean' => false])->getConnection(); + $connection = connectToDB(['convertBoolean' => false]); Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-nette_test3.sql'); $row = $connection->fetch('SELECT * FROM types'); Assert::same(1, $row->bool); @@ -56,19 +56,72 @@ test('convertBoolean = false', function () { test('default newDateTime', function () { - $connection = connectToDB(['newDateTime' => null])->getConnection(); + $connection = connectToDB(['newDateTime' => null]); $field = $connection->fetchField('SELECT NOW()'); - Assert::type(Nette\Utils\DateTime::class, $field); + Assert::type(Nette\Database\DateTime::class, $field); }); test('newDateTime = false', function () { - $connection = connectToDB(['newDateTime' => false])->getConnection(); + $connection = connectToDB(['newDateTime' => false]); $field = $connection->fetchField('SELECT NOW()'); Assert::type(Nette\Utils\DateTime::class, $field); }); test('newDateTime = true', function () { - $connection = connectToDB(['newDateTime' => true])->getConnection(); + $connection = connectToDB(['newDateTime' => true]); + $field = $connection->fetchField('SELECT NOW()'); + Assert::type(Nette\Database\DateTime::class, $field); +}); + + +test('default convertDateTime', function () { + $connection = connectToDB(['convertDateTime' => null]); + $field = $connection->fetchField('SELECT NOW()'); + Assert::type(Nette\Database\DateTime::class, $field); +}); + +test('convertDateTime = false', function () { + $connection = connectToDB(['convertDateTime' => false]); + $field = $connection->fetchField('SELECT NOW()'); + Assert::type('string', $field); +}); + +test('convertDateTime = true', function () { + $connection = connectToDB(['convertDateTime' => true]); $field = $connection->fetchField('SELECT NOW()'); Assert::type(Nette\Database\DateTime::class, $field); }); + + +test('default convertDecimal', function () { + $connection = connectToDB(['convertDecimal' => null]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::same(1, $row->decimal); + Assert::same(1.1, $row->decimal2); + + $fields = $connection->fetchFields('SELECT 10, 10.5'); + Assert::same([10, 10.5], $fields); +}); + +test('convertDecimal = false', function () { + $connection = connectToDB(['convertDecimal' => false]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::same('1', $row->decimal); + Assert::same('1.10', $row->decimal2); + + $fields = $connection->fetchFields('SELECT 10, 10.5'); + Assert::same([10, '10.5'], $fields); +}); + +test('convertDecimal = true', function () { + $connection = connectToDB(['convertDecimal' => true]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/mysql-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::same(1, $row->decimal); + Assert::same(1.1, $row->decimal2); + + $fields = $connection->fetchFields('SELECT 10, 10.5'); + Assert::same([10, 10.5], $fields); +}); diff --git a/tests/Database/connection.options.sqlite.phpt b/tests/Database/connection.options.sqlite.phpt index 63b8536d4..352803206 100644 --- a/tests/Database/connection.options.sqlite.phpt +++ b/tests/Database/connection.options.sqlite.phpt @@ -13,13 +13,37 @@ require __DIR__ . '/../bootstrap.php'; test('formatDateTime', function () { - $connection = connectToDB(['formatDateTime' => 'U'])->getConnection(); - $driver = $connection->getDriver(); - Assert::same('254358000', $driver->formatDateTime(new DateTime('1978-01-23 00:00:00'))); + $connection = connectToDB(['formatDateTime' => 'U']); + $engine = $connection->getDatabaseEngine(); + Assert::same('254358000', $engine->formatDateTime(new DateTime('1978-01-23 00:00:00'))); }); test('formatDateTime', function () { - $connection = connectToDB(['formatDateTime' => 'Y-m-d'])->getConnection(); - $driver = $connection->getDriver(); - Assert::same('1978-01-23', $driver->formatDateTime(new DateTime('1978-01-23 00:00:00'))); + $connection = connectToDB(['formatDateTime' => 'Y-m-d']); + $engine = $connection->getDatabaseEngine(); + Assert::same('1978-01-23', $engine->formatDateTime(new DateTime('1978-01-23 00:00:00'))); +}); + + +test('default convertDateTime', function () { + $connection = connectToDB(['convertDateTime' => null]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlite-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::type(Nette\Database\DateTime::class, $row->date); + Assert::type(Nette\Database\DateTime::class, $row->datetime); +}); + +test('convertDateTime = false', function () { + $connection = connectToDB(['convertDateTime' => false]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlite-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::type('int', $row->date); + Assert::type('int', $row->datetime); +}); + +test('convertDateTime = true', function () { + $connection = connectToDB(['convertDateTime' => true]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlite-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::type(Nette\Database\DateTime::class, $row->date); }); diff --git a/tests/Database/connection.options.sqlsrv.phpt b/tests/Database/connection.options.sqlsrv.phpt new file mode 100644 index 000000000..39369c69a --- /dev/null +++ b/tests/Database/connection.options.sqlsrv.phpt @@ -0,0 +1,62 @@ + null]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::same(1, $row->decimal); + Assert::same(1, $row->numeric_10_0); + Assert::same(1.1, $row->numeric_10_2); +}); + +test('convertDecimal = true', function () { + $connection = connectToDB(['convertDecimal' => true]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::same(1, $row->decimal); + Assert::same(1, $row->numeric_10_0); + Assert::same(1.1, $row->numeric_10_2); +}); + +test('convertDecimal = false', function () { + $connection = connectToDB(['convertDecimal' => false]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::same('1', $row->decimal); + Assert::same('1', $row->numeric_10_0); + Assert::same('1.10', $row->numeric_10_2); +}); + + +test('default convertBoolean', function () { + $connection = connectToDB(['convertBoolean' => null]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::equal(true, $row->bit); +}); + +test('convertBoolean = true', function () { + $connection = connectToDB(['convertBoolean' => true]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::equal(true, $row->bit); +}); + +test('convertBoolean = false', function () { + $connection = connectToDB(['convertBoolean' => false]); + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-nette_test3.sql'); + $row = $connection->fetch('SELECT * FROM types'); + Assert::equal(1, $row->bit); +}); diff --git a/tests/Database/files/sqlsrv-loadFromFile.sql b/tests/Database/files/sqlsrv-loadFromFile.sql new file mode 100644 index 000000000..d4336c2a0 --- /dev/null +++ b/tests/Database/files/sqlsrv-loadFromFile.sql @@ -0,0 +1,13 @@ +IF OBJECT_ID('products', 'U') IS NOT NULL DROP TABLE products; + +CREATE TABLE products ( + product_id int NOT NULL IDENTITY(11,1), + title varchar(50) NOT NULL, + PRIMARY KEY(product_id) +); + +SET IDENTITY_INSERT products ON; +INSERT INTO products (product_id, title) VALUES (1, 'Chair'); +INSERT INTO products (product_id, title) VALUES (2, 'Table'); +INSERT INTO products (product_id, title) VALUES (3, 'Computer'); +SET IDENTITY_INSERT products OFF; diff --git a/tests/Database/sqlsrv-loadFromFile.phpt b/tests/Database/sqlsrv-loadFromFile.phpt new file mode 100644 index 000000000..2289d6fd5 --- /dev/null +++ b/tests/Database/sqlsrv-loadFromFile.phpt @@ -0,0 +1,18 @@ + Nette\Database\Helpers::loadFromFile($connection, __DIR__ . '/files/sqlsrv-loadFromFile.sql') +); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f112e33ce..73bb24e86 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -16,6 +16,10 @@ Tester\Environment::setupFunctions(); date_default_timezone_set('Europe/Prague'); +if (PHP_VERSION_ID >= 80400) { + error_reporting(E_ALL & ~E_DEPRECATED); +} + function getTempDir(): string { @@ -34,18 +38,16 @@ function connectToDB(array $options = []): Nette\Database\Explorer Tester\Environment::lock($args['dsn'], getTempDir()); } + $explorer = new Nette\Database\Explorer($args['dsn'], $args['username'], $args['password'], $args['options']); + try { - $connection = new Nette\Database\Connection($args['dsn'], $args['username'], $args['password'], $args['options']); - } catch (PDOException $e) { + $explorer->connect(); + } catch (Nette\Database\ConnectionException $e) { Tester\Environment::skip("Connection to '$args[dsn]' failed. Reason: " . $e->getMessage()); } - $driverName = $connection->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME); - $cacheMemoryStorage = new Nette\Caching\Storages\MemoryStorage; - - $structure = new Nette\Database\Structure($connection, $cacheMemoryStorage); - $conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); - $explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $cacheMemoryStorage); + $driverName = $explorer->getConnection()->getNativeConnection()->getAttribute(PDO::ATTR_DRIVER_NAME); + $explorer->setCache(new Nette\Caching\Cache(new Nette\Caching\Storages\MemoryStorage)); echo "Driver: $driverName\n"; $GLOBALS['driverName'] = $driverName; diff --git a/tests/databases.github.ini b/tests/databases.github.ini index d00f44271..1c722a84c 100644 --- a/tests/databases.github.ini +++ b/tests/databases.github.ini @@ -2,34 +2,26 @@ dsn = "mysql:host=127.0.0.1;port=3306;dbname=nette_test" username = root password = root -options[convertBoolean] = yes -options[newDateTime] = yes [mysql 8.0] dsn = "mysql:host=127.0.0.1;port=3307;dbname=nette_test" username = root password = root -options[convertBoolean] = yes -options[newDateTime] = yes [postgresql 9.6] dsn = "pgsql:host=127.0.0.1;port=5432;dbname=nette_test" username = postgres password = postgres -options[newDateTime] = yes [postgresql 13] dsn = "pgsql:host=127.0.0.1;port=5433;dbname=nette_test" username = postgres password = postgres -options[newDateTime] = yes [sqlsrv] dsn = "sqlsrv:Server=localhost,1433;Database=nette_test" username = SA password = "YourStrong!Passw0rd" -options[newDateTime] = yes [sqlite] dsn = "sqlite::memory:" -options[newDateTime] = yes diff --git a/tests/databases.sqlite.ini b/tests/databases.sqlite.ini index 2e076f7e8..2d89c58ce 100644 --- a/tests/databases.sqlite.ini +++ b/tests/databases.sqlite.ini @@ -1,3 +1,2 @@ [sqlite] dsn = "sqlite::memory:" -options[newDateTime] = yes