From 9a2b0dd0e158e7f7507c49b26e2d69cadb61ace0 Mon Sep 17 00:00:00 2001 From: Ihor Tymoshenko Date: Wed, 13 Sep 2023 21:10:12 +0300 Subject: [PATCH] Added Doctrine DBAL 3 support (#54) --- .github/workflows/tests.yml | 4 +- .gitignore | 13 +- README.md | 2 +- composer.json | 8 +- src/ClickHouseConnection.php | 119 ++-- ...n.php => ClickHouseExceptionConverter.php} | 14 +- src/ClickHouseKeywords.php | 23 +- src/ClickHousePlatform.php | 581 +++++++++--------- src/ClickHouseResult.php | 109 ++++ src/ClickHouseSchemaManager.php | 47 +- src/ClickHouseStatement.php | 390 +++--------- src/Connection.php | 121 ++-- src/Driver.php | 58 +- src/Driver/Exception/Exception.php | 32 + src/Types/ArrayDateTimeType.php | 38 +- src/Types/ArrayDateType.php | 38 +- src/Types/ArrayFloat32Type.php | 7 +- src/Types/ArrayFloat64Type.php | 7 +- src/Types/ArrayInt16Type.php | 7 +- src/Types/ArrayInt32Type.php | 7 +- src/Types/ArrayInt64Type.php | 7 +- src/Types/ArrayInt8Type.php | 7 +- ...StringType.php => ArrayStringableType.php} | 28 +- src/Types/ArrayType.php | 53 +- src/Types/ArrayUInt16Type.php | 7 +- src/Types/ArrayUInt32Type.php | 7 +- src/Types/ArrayUInt64Type.php | 7 +- src/Types/ArrayUInt8Type.php | 7 +- src/Types/BigIntType.php | 11 +- src/Types/BitNumericalClickHouseType.php | 2 +- src/Types/ClickHouseType.php | 2 +- ...eType.php => StringableClickHouseType.php} | 2 +- tests/ArraysTest.php | 129 ++-- tests/ConnectionTest.php | 125 ++-- tests/CreateConnectionTest.php | 36 +- tests/CreateSchemaTest.php | 320 ++++++---- tests/DbalTypeTest.php | 209 ++++--- tests/DriverTest.php | 41 +- tests/InsertTest.php | 74 ++- tests/SelectTest.php | 190 +++--- 40 files changed, 1426 insertions(+), 1463 deletions(-) rename src/{ClickHouseException.php => ClickHouseExceptionConverter.php} (55%) create mode 100644 src/ClickHouseResult.php create mode 100644 src/Driver/Exception/Exception.php rename src/Types/{ArrayStringType.php => ArrayStringableType.php} (65%) rename src/Types/{StringClickHouseType.php => StringableClickHouseType.php} (90%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a94f84..10801fc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - php-versions: ['7.1', '7.4', '8.0', '8.1'] + php-versions: ['8.0', '8.1'] fail-fast: false services: @@ -45,4 +45,4 @@ jobs: run: composer install --prefer-dist - name: Run tests - run: ./vendor/bin/phpunit --configuration ./phpunit.xml.dist --coverage-text \ No newline at end of file + run: ./vendor/bin/phpunit --configuration ./phpunit.xml.dist --coverage-text diff --git a/.gitignore b/.gitignore index 27e4c53..11e3515 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ -.idea/ -composer.lock -vendor/ -phpunit.xml -.phpunit.result.cache -Vagrantfile -puphpet/ -.vagrant/ \ No newline at end of file +/.idea/ +/vendor/ +/.phpunit.result.cache +/composer.lock +/phpunit.xml diff --git a/README.md b/README.md index cb0e482..57d00b4 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ doctrine: array(uint64): FOD\DBALClickHouse\Types\ArrayUInt64Type array(float32): FOD\DBALClickHouse\Types\ArrayFloat32Type array(float64): FOD\DBALClickHouse\Types\ArrayFloat64Type - array(string): FOD\DBALClickHouse\Types\ArrayStringType + array(string): FOD\DBALClickHouse\Types\ArrayStringableType array(datetime): FOD\DBALClickHouse\Types\ArrayDateTimeType array(date): FOD\DBALClickHouse\Types\ArrayDateType ``` diff --git a/composer.json b/composer.json index 3328fc5..1497a3d 100644 --- a/composer.json +++ b/composer.json @@ -25,15 +25,15 @@ } ], "require": { - "php": "^7.1 || ^8.0", - "ext-pdo": "*", + "php": "^8.0", "ext-pcre": "*", - "doctrine/dbal": "^2.7", + "ext-mbstring": "*", + "doctrine/dbal": "^3.0", "smi2/phpclickhouse": "^1.0" }, "require-dev": { "doctrine/coding-standard": "^4.0 || ^9.0", - "phpunit/phpunit": "^7.0 || ^9.0" + "phpunit/phpunit": "^9.5" }, "autoload": { "psr-4": { diff --git a/src/ClickHouseConnection.php b/src/ClickHouseConnection.php index 563437a..d138600 100644 --- a/src/ClickHouseConnection.php +++ b/src/ClickHouseConnection.php @@ -14,85 +14,76 @@ namespace FOD\DBALClickHouse; -use ClickHouseDB\Client as Smi2CHClient; -use ClickHouseDB\Exception\TransportException; +use ClickHouseDB\Client; +use ClickHouseDB\Exception\ClickHouseException; use Doctrine\DBAL\Driver\Connection; -use Doctrine\DBAL\Driver\PingableConnection; +use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; +use Doctrine\DBAL\Driver\Statement; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; + use function array_merge; -use function func_get_args; -/** - * ClickHouse implementation for the Connection interface. - */ -class ClickHouseConnection implements Connection, PingableConnection, ServerInfoAwareConnection +class ClickHouseConnection implements Connection, ServerInfoAwareConnection { - /** @var Smi2CHClient */ - protected $smi2CHClient; + protected Client $client; - /** @var AbstractPlatform */ - protected $platform; + protected AbstractPlatform $platform; public function __construct( array $params, - string $username, + string $user, string $password, AbstractPlatform $platform ) { - $this->smi2CHClient = new Smi2CHClient([ - 'host' => $params['host'] ?? 'localhost', - 'port' => $params['port'] ?? 8123, - 'username' => $username, - 'password' => $password, - ], array_merge([ - 'database' => $params['dbname'] ?? 'default', - ], $params['driverOptions'] ?? [])); + $this->client = new Client( + [ + 'host' => $params['host'] ?? 'localhost', + 'port' => $params['port'] ?? 8123, + 'username' => $user, + 'password' => $password, + ], + array_merge(['database' => $params['dbname'] ?? 'default'], $params['driverOptions'] ?? []) + ); $this->platform = $platform; } /** * {@inheritDoc} */ - public function prepare($prepareString) : ClickHouseStatement + public function prepare(string $sql): Statement { - return new ClickHouseStatement($this->smi2CHClient, $prepareString, $this->platform); + return new ClickHouseStatement($this->client, $sql, $this->platform); } /** * {@inheritDoc} */ - public function query() : ClickHouseStatement + public function query(string $sql): Result { - $args = func_get_args(); - $stmt = $this->prepare($args[0]); - $stmt->execute(); - - return $stmt; + return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ - public function quote($input, $type = ParameterType::STRING) + public function quote($value, $type = ParameterType::STRING) { - if ($type === ParameterType::INTEGER) { - return $input; + if ($type === ParameterType::STRING) { + return $this->platform->quoteStringLiteral($value); } - return $this->platform->quoteStringLiteral($input); + return $value; } /** * {@inheritDoc} */ - public function exec($statement) : int + public function exec(string $sql): int { - $stmt = $this->prepare($statement); - $stmt->execute(); - - return $stmt->rowCount(); + return $this->prepare($sql)->execute()->rowCount(); } /** @@ -100,74 +91,42 @@ public function exec($statement) : int */ public function lastInsertId($name = null) { - throw ClickHouseException::notSupported('Unable to get last insert id in ClickHouse'); - } - - /** - * {@inheritDoc} - */ - public function beginTransaction() : bool - { - throw ClickHouseException::notSupported('Transactions are not allowed in ClickHouse'); - } - - /** - * {@inheritDoc} - */ - public function commit() : bool - { - throw ClickHouseException::notSupported('Transactions are not allowed in ClickHouse'); - } - - /** - * {@inheritDoc} - */ - public function rollBack() : bool - { - throw ClickHouseException::notSupported('Transactions are not allowed in ClickHouse'); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function errorCode() : ?string + public function beginTransaction(): bool { - throw ClickHouseException::notSupported('You need to implement ClickHouseConnection::errorCode()'); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function errorInfo() : array + public function commit(): bool { - throw ClickHouseException::notSupported('You need to implement ClickHouseConnection::errorInfo()'); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function ping() : bool + public function rollBack(): bool { - return $this->smi2CHClient->ping(); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getServerVersion() : string + public function getServerVersion(): string { try { - return $this->smi2CHClient->getServerVersion(); - } catch (TransportException $e) { + return $this->client->getServerVersion(); + } catch (ClickHouseException) { return ''; } } - - /** - * {@inheritDoc} - */ - public function requiresQueryForServerVersion() : bool - { - return true; - } } diff --git a/src/ClickHouseException.php b/src/ClickHouseExceptionConverter.php similarity index 55% rename from src/ClickHouseException.php rename to src/ClickHouseExceptionConverter.php index 076f2c0..4542e27 100644 --- a/src/ClickHouseException.php +++ b/src/ClickHouseExceptionConverter.php @@ -14,15 +14,15 @@ namespace FOD\DBALClickHouse; -use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Driver\API\ExceptionConverter; +use Doctrine\DBAL\Driver\Exception; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Query; -/** - * Specific Exception for ClickHouse - */ -class ClickHouseException extends Exception +class ClickHouseExceptionConverter implements ExceptionConverter { - public static function notSupported($method) : ClickHouseException + public function convert(Exception $exception, ?Query $query): DriverException { - return new self(sprintf("Operation '%s' is not supported by platform.", $method)); + return new DriverException($exception, $query); } } diff --git a/src/ClickHouseKeywords.php b/src/ClickHouseKeywords.php index b375b7c..724398a 100644 --- a/src/ClickHouseKeywords.php +++ b/src/ClickHouseKeywords.php @@ -16,25 +16,14 @@ use Doctrine\DBAL\Platforms\Keywords\KeywordList; -/** - * ClickHouse Keywordlist - */ class ClickHouseKeywords extends KeywordList { /** * {@inheritdoc} */ - public function getName() : string - { - return 'ClickHouse'; - } - - /** - * {@inheritdoc} - */ - protected function getKeywords() : array + protected function getKeywords(): array { - //TODO actualize it! + // @todo actualize it! return [ 'ADD', 'ALL', @@ -266,4 +255,12 @@ protected function getKeywords() : array 'ANY', ]; } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'ClickHouse'; + } } diff --git a/src/ClickHousePlatform.php b/src/ClickHousePlatform.php index ce3d020..bee7d19 100644 --- a/src/ClickHousePlatform.php +++ b/src/ClickHousePlatform.php @@ -14,12 +14,14 @@ namespace FOD\DBALClickHouse; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\TrimMode; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\UniqueConstraint; use Doctrine\DBAL\Types\BigIntType; use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\DateTimeType; @@ -34,8 +36,9 @@ use FOD\DBALClickHouse\Types\BitNumericalClickHouseType; use FOD\DBALClickHouse\Types\DatableClickHouseType; use FOD\DBALClickHouse\Types\NumericalClickHouseType; -use FOD\DBALClickHouse\Types\StringClickHouseType; +use FOD\DBALClickHouse\Types\StringableClickHouseType; use FOD\DBALClickHouse\Types\UnsignedNumericalClickHouseType; + use function addslashes; use function array_filter; use function array_key_exists; @@ -48,13 +51,10 @@ use function get_class; use function implode; use function in_array; +use function mb_stripos; use function sprintf; -use function stripos; use function trim; -/** - * Provides the behavior, features and SQL dialect of the ClickHouse database platform. - */ class ClickHousePlatform extends AbstractPlatform { protected const TIME_MINUTE = 60; @@ -65,128 +65,128 @@ class ClickHousePlatform extends AbstractPlatform /** * {@inheritDoc} */ - public function getBooleanTypeDeclarationSQL(array $columnDef) : string + public function getBooleanTypeDeclarationSQL(array $column): string { return $this->prepareDeclarationSQL( UnsignedNumericalClickHouseType::UNSIGNED_CHAR . NumericalClickHouseType::TYPE_INT . BitNumericalClickHouseType::EIGHT_BIT, - $columnDef + $column ); } /** * {@inheritDoc} */ - public function getIntegerTypeDeclarationSQL(array $columnDef) : string + public function getIntegerTypeDeclarationSQL(array $column): string { return $this->prepareDeclarationSQL( - $this->_getCommonIntegerTypeDeclarationSQL($columnDef) . + $this->_getCommonIntegerTypeDeclarationSQL($column) . NumericalClickHouseType::TYPE_INT . BitNumericalClickHouseType::THIRTY_TWO_BIT, - $columnDef + $column ); } /** * {@inheritDoc} */ - public function getBigIntTypeDeclarationSQL(array $columnDef) : string + public function getBigIntTypeDeclarationSQL(array $column): string { - return $this->prepareDeclarationSQL(StringClickHouseType::TYPE_STRING, $columnDef); + return $this->prepareDeclarationSQL(StringableClickHouseType::TYPE_STRING, $column); } /** * {@inheritDoc} */ - public function getSmallIntTypeDeclarationSQL(array $columnDef) : string + public function getSmallIntTypeDeclarationSQL(array $column): string { return $this->prepareDeclarationSQL( - $this->_getCommonIntegerTypeDeclarationSQL($columnDef) . + $this->_getCommonIntegerTypeDeclarationSQL($column) . NumericalClickHouseType::TYPE_INT . BitNumericalClickHouseType::SIXTEEN_BIT, - $columnDef + $column ); } /** * {@inheritDoc} */ - protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string + protected function _getCommonIntegerTypeDeclarationSQL(array $column): string { - if (! empty($columnDef['autoincrement'])) { - throw ClickHouseException::notSupported('Clickhouse do not support AUTO_INCREMENT fields'); + if (!empty($column['autoincrement'])) { + throw Exception::notSupported('Clickhouse does not support AUTO_INCREMENT fields'); } - return empty($columnDef['unsigned']) ? '' : UnsignedNumericalClickHouseType::UNSIGNED_CHAR; + return empty($column['unsigned']) ? '' : UnsignedNumericalClickHouseType::UNSIGNED_CHAR; } /** * {@inheritDoc} */ - protected function initializeDoctrineTypeMappings() : void + protected function initializeDoctrineTypeMappings(): void { $this->doctrineTypeMapping = [ - 'int8' => 'smallint', - 'int16' => 'integer', - 'int32' => 'integer', - 'int64' => 'bigint', - 'uint8' => 'smallint', - 'uint16' => 'integer', - 'uint32' => 'integer', - 'uint64' => 'bigint', + 'int8' => 'smallint', + 'int16' => 'integer', + 'int32' => 'integer', + 'int64' => 'bigint', + 'uint8' => 'smallint', + 'uint16' => 'integer', + 'uint32' => 'integer', + 'uint64' => 'bigint', 'float32' => 'decimal', 'float64' => 'float', - 'string' => 'string', + 'string' => 'string', 'fixedstring' => 'string', - 'date' => 'date', - 'datetime' => 'datetime', - - 'array(int8)' => 'array', - 'array(int16)' => 'array', - 'array(int32)' => 'array', - 'array(int64)' => 'array', - 'array(uint8)' => 'array', - 'array(uint16)' => 'array', - 'array(uint32)' => 'array', - 'array(uint64)' => 'array', + 'date' => 'date', + 'datetime' => 'datetime', + + 'array(int8)' => 'array', + 'array(int16)' => 'array', + 'array(int32)' => 'array', + 'array(int64)' => 'array', + 'array(uint8)' => 'array', + 'array(uint16)' => 'array', + 'array(uint32)' => 'array', + 'array(uint64)' => 'array', 'array(float32)' => 'array', 'array(float64)' => 'array', - 'array(string)' => 'array', - 'array(date)' => 'array', + 'array(string)' => 'array', + 'array(date)' => 'array', 'array(datetime)' => 'array', - 'enum8' => 'string', + 'enum8' => 'string', 'enum16' => 'string', - 'nullable(int8)' => 'smallint', - 'nullable(int16)' => 'integer', - 'nullable(int32)' => 'integer', - 'nullable(int64)' => 'bigint', - 'nullable(uint8)' => 'smallint', - 'nullable(uint16)' => 'integer', - 'nullable(uint32)' => 'integer', - 'nullable(uint64)' => 'bigint', + 'nullable(int8)' => 'smallint', + 'nullable(int16)' => 'integer', + 'nullable(int32)' => 'integer', + 'nullable(int64)' => 'bigint', + 'nullable(uint8)' => 'smallint', + 'nullable(uint16)' => 'integer', + 'nullable(uint32)' => 'integer', + 'nullable(uint64)' => 'bigint', 'nullable(float32)' => 'decimal', 'nullable(float64)' => 'float', - 'nullable(string)' => 'string', + 'nullable(string)' => 'string', 'nullable(fixedstring)' => 'string', - 'nullable(date)' => 'date', - 'nullable(datetime)' => 'datetime', - - 'array(nullable(int8))' => 'array', - 'array(nullable(int16))' => 'array', - 'array(nullable(int32))' => 'array', - 'array(nullable(int64))' => 'array', - 'array(nullable(uint8))' => 'array', - 'array(nullable(uint16))' => 'array', - 'array(nullable(uint32))' => 'array', - 'array(nullable(uint64))' => 'array', + 'nullable(date)' => 'date', + 'nullable(datetime)' => 'datetime', + + 'array(nullable(int8))' => 'array', + 'array(nullable(int16))' => 'array', + 'array(nullable(int32))' => 'array', + 'array(nullable(int64))' => 'array', + 'array(nullable(uint8))' => 'array', + 'array(nullable(uint16))' => 'array', + 'array(nullable(uint32))' => 'array', + 'array(nullable(uint64))' => 'array', 'array(nullable(float32))' => 'array', 'array(nullable(float64))' => 'array', - 'array(nullable(string))' => 'array', - 'array(nullable(date))' => 'array', + 'array(nullable(string))' => 'array', + 'array(nullable(date))' => 'array', 'array(nullable(datetime))' => 'array', ]; } @@ -194,74 +194,74 @@ protected function initializeDoctrineTypeMappings() : void /** * {@inheritDoc} */ - protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) : string + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed): string { return $fixed - ? (StringClickHouseType::TYPE_FIXED_STRING . '(' . $length . ')') - : StringClickHouseType::TYPE_STRING; + ? (StringableClickHouseType::TYPE_FIXED_STRING . '(' . $length . ')') + : StringableClickHouseType::TYPE_STRING; } /** * {@inheritDoc} */ - public function getVarcharTypeDeclarationSQL(array $field) : string + public function getVarcharTypeDeclarationSQL(array $column): string { - if (! isset($field['length'])) { - $field['length'] = $this->getVarcharDefaultLength(); + if (!isset($column['length'])) { + $column['length'] = $this->getVarcharDefaultLength(); } - $fixed = $field['fixed'] ?? false; + $fixed = $column['fixed'] ?? false; $maxLength = $fixed ? $this->getCharMaxLength() : $this->getVarcharMaxLength(); - if ($field['length'] > $maxLength) { - return $this->getClobTypeDeclarationSQL($field); + if ($column['length'] > $maxLength) { + return $this->getClobTypeDeclarationSQL($column); } return $this->prepareDeclarationSQL( - $this->getVarcharTypeDeclarationSQLSnippet($field['length'], $fixed), - $field + $this->getVarcharTypeDeclarationSQLSnippet($column['length'], $fixed), + $column ); } /** * {@inheritDoc} */ - protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) : string + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed): string { - return StringClickHouseType::TYPE_STRING; + return StringableClickHouseType::TYPE_STRING; } /** * {@inheritDoc} */ - public function getClobTypeDeclarationSQL(array $field) : string + public function getClobTypeDeclarationSQL(array $column): string { - return $this->prepareDeclarationSQL(StringClickHouseType::TYPE_STRING, $field); + return $this->prepareDeclarationSQL(StringableClickHouseType::TYPE_STRING, $column); } /** * {@inheritDoc} */ - public function getBlobTypeDeclarationSQL(array $field) : string + public function getBlobTypeDeclarationSQL(array $column): string { - return $this->prepareDeclarationSQL(StringClickHouseType::TYPE_STRING, $field); + return $this->prepareDeclarationSQL(StringableClickHouseType::TYPE_STRING, $column); } /** * {@inheritDoc} */ - public function getName() : string + public function getName(): string { - return 'clickhouse'; + return 'ClickHouse'; } /** * {@inheritDoc} */ - public function getIdentifierQuoteCharacter() : string + public function getIdentifierQuoteCharacter(): string { return '`'; } @@ -269,7 +269,7 @@ public function getIdentifierQuoteCharacter() : string /** * {@inheritDoc} */ - public function getVarcharDefaultLength() : int + public function getVarcharDefaultLength(): int { return 512; } @@ -277,7 +277,7 @@ public function getVarcharDefaultLength() : int /** * {@inheritDoc} */ - public function getCountExpression($column) : string + public function getCountExpression($column): string { return 'COUNT()'; } @@ -287,7 +287,7 @@ public function getCountExpression($column) : string /** * {@inheritDoc} */ - public function getMd5Expression($column) : string + public function getMd5Expression($column): string { return 'MD5(CAST(' . $column . ' AS String))'; } @@ -295,7 +295,7 @@ public function getMd5Expression($column) : string /** * {@inheritDoc} */ - public function getLengthExpression($column) : string + public function getLengthExpression($column): string { return 'lengthUTF8(CAST(' . $column . ' AS String))'; } @@ -303,7 +303,7 @@ public function getLengthExpression($column) : string /** * {@inheritDoc} */ - public function getSqrtExpression($column) : string + public function getSqrtExpression($column): string { return 'sqrt(' . $column . ')'; } @@ -311,7 +311,7 @@ public function getSqrtExpression($column) : string /** * {@inheritDoc} */ - public function getRoundExpression($column, $decimals = 0) : string + public function getRoundExpression($column, $decimals = 0): string { return 'round(' . $column . ', ' . $decimals . ')'; } @@ -319,7 +319,7 @@ public function getRoundExpression($column, $decimals = 0) : string /** * {@inheritDoc} */ - public function getModExpression($expression1, $expression2) : string + public function getModExpression($expression1, $expression2): string { return 'modulo(' . $expression1 . ', ' . $expression2 . ')'; } @@ -327,17 +327,14 @@ public function getModExpression($expression1, $expression2) : string /** * {@inheritDoc} */ - public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = false) : string + public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false): string { - if (! $char) { - switch ($pos) { - case TrimMode::LEADING: - return $this->getLtrimExpression($str); - case TrimMode::TRAILING: - return $this->getRtrimExpression($str); - default: - return sprintf("replaceRegexpAll(%s, '(^\\\s+|\\\s+$)', '')", $str); - } + if (!$char) { + return match ($mode) { + TrimMode::LEADING => $this->getLtrimExpression($str), + TrimMode::TRAILING => $this->getRtrimExpression($str), + default => sprintf("replaceRegexpAll(%s, '(^\\\s+|\\\s+$)', '')", $str), + }; } return sprintf("replaceRegexpAll(%s, '(^%s+|%s+$)', '')", $str, $char, $char); @@ -346,7 +343,7 @@ public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = fa /** * {@inheritDoc} */ - public function getRtrimExpression($str) : string + public function getRtrimExpression($str): string { return sprintf("replaceRegexpAll(%s, '(\\\s+$)', '')", $str); } @@ -354,7 +351,7 @@ public function getRtrimExpression($str) : string /** * {@inheritDoc} */ - public function getLtrimExpression($str) : string + public function getLtrimExpression($str): string { return sprintf("replaceRegexpAll(%s, '(^\\\s+)', '')", $str); } @@ -362,7 +359,7 @@ public function getLtrimExpression($str) : string /** * {@inheritDoc} */ - public function getUpperExpression($str) : string + public function getUpperExpression($str): string { return 'upperUTF8(' . $str . ')'; } @@ -370,7 +367,7 @@ public function getUpperExpression($str) : string /** * {@inheritDoc} */ - public function getLowerExpression($str) : string + public function getLowerExpression($str): string { return 'lowerUTF8(' . $str . ')'; } @@ -378,7 +375,7 @@ public function getLowerExpression($str) : string /** * {@inheritDoc} */ - public function getLocateExpression($str, $substr, $startPos = false) : string + public function getLocateExpression($str, $substr, $startPos = false): string { return 'positionUTF8(' . $str . ', ' . $substr . ')'; } @@ -386,7 +383,7 @@ public function getLocateExpression($str, $substr, $startPos = false) : string /** * {@inheritDoc} */ - public function getNowExpression() : string + public function getNowExpression(): string { return 'now()'; } @@ -394,19 +391,19 @@ public function getNowExpression() : string /** * {@inheritDoc} */ - public function getSubstringExpression($value, $from, $length = null) : string + public function getSubstringExpression($string, $start, $length = null): string { if ($length === null) { throw new \InvalidArgumentException("'length' argument must be a constant"); } - return 'substringUTF8(' . $value . ', ' . $from . ', ' . $length . ')'; + return 'substringUTF8(' . $string . ', ' . $start . ', ' . $length . ')'; } /** * {@inheritDoc} */ - public function getConcatExpression() : string + public function getConcatExpression(): string { return 'concat(' . implode(', ', func_get_args()) . ')'; } @@ -414,7 +411,7 @@ public function getConcatExpression() : string /** * {@inheritDoc} */ - public function getIsNullExpression($expression) : string + public function getIsNullExpression($expression): string { return 'isNull(' . $expression . ')'; } @@ -422,7 +419,7 @@ public function getIsNullExpression($expression) : string /** * {@inheritDoc} */ - public function getIsNotNullExpression($expression) : string + public function getIsNotNullExpression($expression): string { return 'isNotNull(' . $expression . ')'; } @@ -430,7 +427,7 @@ public function getIsNotNullExpression($expression) : string /** * {@inheritDoc} */ - public function getAcosExpression($value) : string + public function getAcosExpression($value): string { return 'acos(' . $value . ')'; } @@ -438,7 +435,7 @@ public function getAcosExpression($value) : string /** * {@inheritDoc} */ - public function getSinExpression($value) : string + public function getSinExpression($value): string { return 'sin(' . $value . ')'; } @@ -446,7 +443,7 @@ public function getSinExpression($value) : string /** * {@inheritDoc} */ - public function getPiExpression() : string + public function getPiExpression(): string { return 'pi()'; } @@ -454,7 +451,7 @@ public function getPiExpression() : string /** * {@inheritDoc} */ - public function getCosExpression($value) : string + public function getCosExpression($value): string { return 'cos(' . $value . ')'; } @@ -462,7 +459,7 @@ public function getCosExpression($value) : string /** * {@inheritDoc} */ - public function getDateDiffExpression($date1, $date2) : string + public function getDateDiffExpression($date1, $date2): string { return 'CAST(' . $date1 . ' AS Date) - CAST(' . $date2 . ' AS Date)'; } @@ -470,7 +467,7 @@ public function getDateDiffExpression($date1, $date2) : string /** * {@inheritDoc} */ - public function getDateAddSecondsExpression($date, $seconds) : string + public function getDateAddSecondsExpression($date, $seconds): string { return $date . ' + ' . $seconds; } @@ -478,7 +475,7 @@ public function getDateAddSecondsExpression($date, $seconds) : string /** * {@inheritDoc} */ - public function getDateSubSecondsExpression($date, $seconds) : string + public function getDateSubSecondsExpression($date, $seconds): string { return $date . ' - ' . $seconds; } @@ -486,7 +483,7 @@ public function getDateSubSecondsExpression($date, $seconds) : string /** * {@inheritDoc} */ - public function getDateAddMinutesExpression($date, $minutes) : string + public function getDateAddMinutesExpression($date, $minutes): string { return $date . ' + ' . $minutes * self::TIME_MINUTE; } @@ -494,7 +491,7 @@ public function getDateAddMinutesExpression($date, $minutes) : string /** * {@inheritDoc} */ - public function getDateSubMinutesExpression($date, $minutes) : string + public function getDateSubMinutesExpression($date, $minutes): string { return $date . ' - ' . $minutes * self::TIME_MINUTE; } @@ -502,7 +499,7 @@ public function getDateSubMinutesExpression($date, $minutes) : string /** * {@inheritDoc} */ - public function getDateAddHourExpression($date, $hours) : string + public function getDateAddHourExpression($date, $hours): string { return $date . ' + ' . $hours * self::TIME_HOUR; } @@ -510,7 +507,7 @@ public function getDateAddHourExpression($date, $hours) : string /** * {@inheritDoc} */ - public function getDateSubHourExpression($date, $hours) : string + public function getDateSubHourExpression($date, $hours): string { return $date . ' - ' . $hours * self::TIME_HOUR; } @@ -518,7 +515,7 @@ public function getDateSubHourExpression($date, $hours) : string /** * {@inheritDoc} */ - public function getDateAddDaysExpression($date, $days) : string + public function getDateAddDaysExpression($date, $days): string { return $date . ' + ' . $days * self::TIME_DAY; } @@ -526,7 +523,7 @@ public function getDateAddDaysExpression($date, $days) : string /** * {@inheritDoc} */ - public function getDateSubDaysExpression($date, $days) : string + public function getDateSubDaysExpression($date, $days): string { return $date . ' - ' . $days * self::TIME_DAY; } @@ -534,7 +531,7 @@ public function getDateSubDaysExpression($date, $days) : string /** * {@inheritDoc} */ - public function getDateAddWeeksExpression($date, $weeks) : string + public function getDateAddWeeksExpression($date, $weeks): string { return $date . ' + ' . $weeks * self::TIME_WEEK; } @@ -542,7 +539,7 @@ public function getDateAddWeeksExpression($date, $weeks) : string /** * {@inheritDoc} */ - public function getDateSubWeeksExpression($date, $weeks) : string + public function getDateSubWeeksExpression($date, $weeks): string { return $date . ' - ' . $weeks * self::TIME_WEEK; } @@ -550,7 +547,7 @@ public function getDateSubWeeksExpression($date, $weeks) : string /** * {@inheritDoc} */ - public function getBitAndComparisonExpression($value1, $value2) : string + public function getBitAndComparisonExpression($value1, $value2): string { return 'bitAnd(' . $value1 . ', ' . $value2 . ')'; } @@ -558,7 +555,7 @@ public function getBitAndComparisonExpression($value1, $value2) : string /** * {@inheritDoc} */ - public function getBitOrComparisonExpression($value1, $value2) : string + public function getBitOrComparisonExpression($value1, $value2): string { return 'bitOr(' . $value1 . ', ' . $value2 . ')'; } @@ -566,81 +563,81 @@ public function getBitOrComparisonExpression($value1, $value2) : string /** * {@inheritDoc} */ - public function getForUpdateSQL() : string + public function getForUpdateSQL(): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function appendLockHint($fromClause, $lockMode) : string + public function appendLockHint($fromClause, $lockMode): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getReadLockSQL() : string + public function getReadLockSQL(): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getWriteLockSQL() : string + public function getWriteLockSQL(): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getDropIndexSQL($index, $table = null) : string + public function getDropIndexSQL($index, $table = null): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getDropConstraintSQL($constraint, $table) : string + public function getDropConstraintSQL($constraint, $table): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getDropForeignKeySQL($foreignKey, $table) : string + public function getDropForeignKeySQL($foreignKey, $table): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getCommentOnColumnSQL($tableName, $columnName, $comment) : string + public function getCommentOnColumnSQL($tableName, $columnName, $comment): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - protected function _getCreateTableSQL($tableName, array $columns, array $options = []) : array + protected function _getCreateTableSQL($name, array $columns, array $options = []): array { - $engine = ! empty($options['engine']) ? $options['engine'] : 'ReplacingMergeTree'; + $engine = !empty($options['engine']) ? $options['engine'] : 'ReplacingMergeTree'; $engineOptions = ''; - if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { - throw ClickHouseException::notSupported('uniqueConstraints'); + if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) { + throw Exception::notSupported('uniqueConstraints'); } - if (isset($options['indexes']) && ! empty($options['indexes'])) { - throw ClickHouseException::notSupported('uniqueConstraints'); + if (isset($options['indexes']) && !empty($options['indexes'])) { + throw Exception::notSupported('uniqueConstraints'); } /** @@ -658,34 +655,36 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options ], true )) { - $indexGranularity = ! empty($options['indexGranularity']) ? $options['indexGranularity'] : 8192; + $indexGranularity = !empty($options['indexGranularity']) ? $options['indexGranularity'] : 8192; $samplingExpression = ''; /** * eventDateColumn section */ $dateColumnParams = [ - 'type' => Type::getType('date'), + 'type' => Type::getType('date'), 'default' => 'today()', ]; - if (! empty($options['eventDateProviderColumn'])) { + + if (!empty($options['eventDateProviderColumn'])) { $options['eventDateProviderColumn'] = trim($options['eventDateProviderColumn']); - if (! isset($columns[$options['eventDateProviderColumn']])) { + + if (!isset($columns[$options['eventDateProviderColumn']])) { throw new \Exception( - 'Table `' . $tableName . '` has not column with name: `' . $options['eventDateProviderColumn'] + 'Table `' . $name . '` has not column with name: `' . $options['eventDateProviderColumn'] ); } - if (! ($columns[$options['eventDateProviderColumn']]['type'] instanceof DateType) && - ! ($columns[$options['eventDateProviderColumn']]['type'] instanceof DateTimeType) && - ! ($columns[$options['eventDateProviderColumn']]['type'] instanceof TextType) && - ! ($columns[$options['eventDateProviderColumn']]['type'] instanceof IntegerType) && - ! ($columns[$options['eventDateProviderColumn']]['type'] instanceof SmallIntType) && - ! ($columns[$options['eventDateProviderColumn']]['type'] instanceof BigIntType) && - ! ($columns[$options['eventDateProviderColumn']]['type'] instanceof FloatType) && - ! ($columns[$options['eventDateProviderColumn']]['type'] instanceof DecimalType) && + if (!($columns[$options['eventDateProviderColumn']]['type'] instanceof DateType) && + !($columns[$options['eventDateProviderColumn']]['type'] instanceof DateTimeType) && + !($columns[$options['eventDateProviderColumn']]['type'] instanceof TextType) && + !($columns[$options['eventDateProviderColumn']]['type'] instanceof IntegerType) && + !($columns[$options['eventDateProviderColumn']]['type'] instanceof SmallIntType) && + !($columns[$options['eventDateProviderColumn']]['type'] instanceof BigIntType) && + !($columns[$options['eventDateProviderColumn']]['type'] instanceof FloatType) && + !($columns[$options['eventDateProviderColumn']]['type'] instanceof DecimalType) && ( - ! ($columns[$options['eventDateProviderColumn']]['type'] instanceof StringType) || + !($columns[$options['eventDateProviderColumn']]['type'] instanceof StringType) || $columns[$options['eventDateProviderColumn']]['fixed'] ) ) { @@ -703,14 +702,13 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options ('toDate(toDateTime(' . $options['eventDateProviderColumn'] . '))') : ('toDate(' . $options['eventDateProviderColumn'] . ')'); } + if (empty($options['eventDateColumn'])) { - $dateColumns = array_filter($columns, function ($column) { - return $column['type'] instanceof DateType; - }); + $dateColumns = array_filter($columns, fn (array $column): bool => $column['type'] instanceof DateType); if ($dateColumns) { throw new \Exception( - 'Table `' . $tableName . '` has DateType columns: `' . implode( + 'Table `' . $name . '` has DateType columns: `' . implode( '`, `', array_keys($dateColumns) ) . @@ -721,9 +719,9 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options $eventDateColumnName = 'EventDate'; } elseif (isset($columns[$options['eventDateColumn']])) { - if (! ($columns[$options['eventDateColumn']]['type'] instanceof DateType)) { + if (!($columns[$options['eventDateColumn']]['type'] instanceof DateType)) { throw new \Exception( - 'In table `' . $tableName . '` you have set field `' . + 'In table `' . $name . '` you have set field `' . $options['eventDateColumn'] . '` (' . get_class($columns[$options['eventDateColumn']]['type']) . ') as `eventDateColumn`, but it is not instance of DateType' @@ -731,10 +729,12 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options } $eventDateColumnName = $options['eventDateColumn']; + unset($columns[$options['eventDateColumn']]); } else { $eventDateColumnName = $options['eventDateColumn']; } + $dateColumnParams['name'] = $eventDateColumnName; // insert into very beginning $columns = [$eventDateColumnName => $dateColumnParams] + $columns; @@ -747,7 +747,8 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options } $primaryIndex = array_values($options['primary']); - if (! empty($options['samplingExpression'])) { + + if (!empty($options['samplingExpression'])) { $samplingExpression = ', ' . $options['samplingExpression']; $primaryIndex[] = $options['samplingExpression']; } @@ -766,19 +767,19 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options /** * any specific MergeTree* table parameters */ - if ($engine === 'ReplacingMergeTree' && ! empty($options['versionColumn'])) { - if (! isset($columns[$options['versionColumn']])) { + if ($engine === 'ReplacingMergeTree' && !empty($options['versionColumn'])) { + if (!isset($columns[$options['versionColumn']])) { throw new \Exception( 'If you specify `versionColumn` for ReplacingMergeTree table -- you must add this column manually (any of UInt*, Date or DateTime types)' ); } - if (! $columns[$options['versionColumn']]['type'] instanceof IntegerType && - ! $columns[$options['versionColumn']]['type'] instanceof BigIntType && - ! $columns[$options['versionColumn']]['type'] instanceof SmallIntType && - ! $columns[$options['versionColumn']]['type'] instanceof DateType && - ! $columns[$options['versionColumn']]['type'] instanceof DateTimeType + if (!$columns[$options['versionColumn']]['type'] instanceof IntegerType && + !$columns[$options['versionColumn']]['type'] instanceof BigIntType && + !$columns[$options['versionColumn']]['type'] instanceof SmallIntType && + !$columns[$options['versionColumn']]['type'] instanceof DateType && + !$columns[$options['versionColumn']]['type'] instanceof DateTimeType ) { throw new \Exception( 'For ReplacingMergeTree tables `versionColumn` must be any of UInt* family, @@ -795,7 +796,7 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options $sql[] = sprintf( 'CREATE TABLE %s (%s) ENGINE = %s%s', - $tableName, + $name, $this->getColumnDeclarationListSQL($columns), $engine, $engineOptions @@ -807,20 +808,21 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options /** * {@inheritDoc} */ - public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) : string + public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getAlterTableSQL(TableDiff $diff) : array + public function getAlterTableSQL(TableDiff $diff): array { $columnSql = []; $queryParts = []; - if ($diff->newName !== false || ! empty($diff->renamedColumns)) { - throw ClickHouseException::notSupported('RENAME COLUMN'); + + if ($diff->newName !== false || !empty($diff->renamedColumns)) { + throw Exception::notSupported('RENAME COLUMN'); } foreach ($diff->addedColumns as $column) { @@ -857,19 +859,16 @@ public function getAlterTableSQL(TableDiff $diff) : array } $queryParts[] = 'MODIFY COLUMN ' . $this->getColumnDeclarationSQL( - $column->getQuotedName($this), - $columnArray - ); + $column->getQuotedName($this), + $columnArray + ); } $sql = []; $tableSql = []; - if (! $this->onSchemaAlterTable($diff, $tableSql) && (count($queryParts) > 0)) { - $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode( - ', ', - $queryParts - ); + if (!$this->onSchemaAlterTable($diff, $tableSql) && (count($queryParts) > 0)) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(', ', $queryParts); } return array_merge($sql, $tableSql, $columnSql); @@ -878,36 +877,28 @@ public function getAlterTableSQL(TableDiff $diff) : array /** * {@inheritDoc} */ - protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array - { - throw ClickHouseException::notSupported(__METHOD__); - } - - /** - * {@inheritDoc} - */ - protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) : array + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) : array + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff) : array + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName): array { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } - protected function prepareDeclarationSQL(string $declarationSQL, array $columnDef) : string + protected function prepareDeclarationSQL(string $declarationSQL, array $columnDef): string { if (array_key_exists('notnull', $columnDef) && $columnDef['notnull'] === false) { return 'Nullable(' . $declarationSQL . ')'; @@ -919,14 +910,14 @@ protected function prepareDeclarationSQL(string $declarationSQL, array $columnDe /** * {@inheritDoc} */ - public function getColumnDeclarationSQL($name, array $field) : string + public function getColumnDeclarationSQL($name, array $column): string { - if (isset($field['columnDefinition'])) { - $columnDef = $this->getCustomTypeDeclarationSQL($field); + if (isset($column['columnDefinition'])) { + $columnDef = $this->getCustomTypeDeclarationSQL($column); } else { - $default = $this->getDefaultValueDeclarationSQL($field); + $default = $this->getDefaultValueDeclarationSQL($column); - $columnDef = $field['type']->getSqlDeclaration($field, $this) . $default; + $columnDef = $column['type']->getSqlDeclaration($column, $this) . $default; } return $name . ' ' . $columnDef; @@ -935,80 +926,79 @@ public function getColumnDeclarationSQL($name, array $field) : string /** * {@inheritDoc} */ - public function getDecimalTypeDeclarationSQL(array $columnDef) : string + public function getDecimalTypeDeclarationSQL(array $column): string { - return $this->prepareDeclarationSQL(StringClickHouseType::TYPE_STRING, $columnDef); + return $this->prepareDeclarationSQL(StringableClickHouseType::TYPE_STRING, $column); } /** * {@inheritDoc} */ - public function getCheckDeclarationSQL(array $definition) : string + public function getCheckDeclarationSQL(array $definition): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getUniqueConstraintDeclarationSQL($name, Index $index) : string + public function getUniqueConstraintDeclarationSQL($name, UniqueConstraint $constraint): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getIndexDeclarationSQL($name, Index $index) : string + public function getIndexDeclarationSQL($name, Index $index): string { - // Index declaration in statements like CREATE TABLE is not supported. - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) : string + public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getForeignKeyReferentialActionSQL($action) : string + public function getForeignKeyReferentialActionSQL($action): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) : string + public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getUniqueFieldDeclarationSQL() : string + public function getUniqueFieldDeclarationSQL(): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getCurrentDateSQL() : string + public function getCurrentDateSQL(): string { return 'today()'; } @@ -1016,7 +1006,7 @@ public function getCurrentDateSQL() : string /** * {@inheritDoc} */ - public function getCurrentTimeSQL() : string + public function getCurrentTimeSQL(): string { return 'now()'; } @@ -1024,7 +1014,7 @@ public function getCurrentTimeSQL() : string /** * {@inheritDoc} */ - public function getCurrentTimestampSQL() : string + public function getCurrentTimestampSQL(): string { return 'toUnixTimestamp(now())'; } @@ -1032,7 +1022,7 @@ public function getCurrentTimestampSQL() : string /** * {@inheritDoc} */ - public function getListDatabasesSQL() : string + public function getListDatabasesSQL(): string { return 'SHOW DATABASES'; } @@ -1040,7 +1030,7 @@ public function getListDatabasesSQL() : string /** * {@inheritDoc} */ - public function getListTableColumnsSQL($table, $database = null) : string + public function getListTableColumnsSQL($table, $database = null): string { return sprintf( 'DESCRIBE TABLE %s', @@ -1051,7 +1041,7 @@ public function getListTableColumnsSQL($table, $database = null) : string /** * {@inheritDoc} */ - public function getListTablesSQL() : string + public function getListTablesSQL(): string { return "SELECT database, name FROM system.tables WHERE database != 'system' AND engine != 'View'"; } @@ -1059,7 +1049,7 @@ public function getListTablesSQL() : string /** * {@inheritDoc} */ - public function getListViewsSQL($database) : string + public function getListViewsSQL($database): string { return "SELECT name FROM system.tables WHERE database != 'system' AND engine = 'View'"; } @@ -1067,7 +1057,7 @@ public function getListViewsSQL($database) : string /** * {@inheritDoc} */ - public function getCreateViewSQL($name, $sql) : string + public function getCreateViewSQL($name, $sql): string { return 'CREATE VIEW ' . $this->quoteIdentifier($name) . ' AS ' . $sql; } @@ -1075,7 +1065,7 @@ public function getCreateViewSQL($name, $sql) : string /** * {@inheritDoc} */ - public function getDropViewSQL($name) : string + public function getDropViewSQL($name): string { return 'DROP TABLE ' . $this->quoteIdentifier($name); } @@ -1083,60 +1073,60 @@ public function getDropViewSQL($name) : string /** * {@inheritDoc} */ - public function getCreateDatabaseSQL($database) : string + public function getCreateDatabaseSQL($name): string { - return 'CREATE DATABASE ' . $database; + return 'CREATE DATABASE ' . $name; } /** * {@inheritDoc} */ - public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string + public function getDateTimeTypeDeclarationSQL(array $column): string { - return $this->prepareDeclarationSQL(DatableClickHouseType::TYPE_DATE_TIME, $fieldDeclaration); + return $this->prepareDeclarationSQL(DatableClickHouseType::TYPE_DATE_TIME, $column); } /** * {@inheritDoc} */ - public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) : string + public function getDateTimeTzTypeDeclarationSQL(array $column): string { - return $this->prepareDeclarationSQL(DatableClickHouseType::TYPE_DATE_TIME, $fieldDeclaration); + return $this->prepareDeclarationSQL(DatableClickHouseType::TYPE_DATE_TIME, $column); } /** * {@inheritDoc} */ - public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string + public function getTimeTypeDeclarationSQL(array $column): string { - return $this->prepareDeclarationSQL(StringClickHouseType::TYPE_STRING, $fieldDeclaration); + return $this->prepareDeclarationSQL(StringableClickHouseType::TYPE_STRING, $column); } /** * {@inheritDoc} */ - public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string + public function getDateTypeDeclarationSQL(array $column): string { - return $this->prepareDeclarationSQL(DatableClickHouseType::TYPE_DATE, $fieldDeclaration); + return $this->prepareDeclarationSQL(DatableClickHouseType::TYPE_DATE, $column); } /** * {@inheritDoc} */ - public function getFloatDeclarationSQL(array $fieldDeclaration) : string + public function getFloatDeclarationSQL(array $column): string { return $this->prepareDeclarationSQL( NumericalClickHouseType::TYPE_FLOAT . BitNumericalClickHouseType::SIXTY_FOUR_BIT, - $fieldDeclaration + $column ); } /** * {@inheritDoc} */ - public function getDefaultTransactionIsolationLevel() : int + public function getDefaultTransactionIsolationLevel(): int { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /* supports*() methods */ @@ -1144,7 +1134,7 @@ public function getDefaultTransactionIsolationLevel() : int /** * {@inheritDoc} */ - public function supportsTransactions() : bool + public function supportsTransactions(): bool { return false; } @@ -1152,7 +1142,7 @@ public function supportsTransactions() : bool /** * {@inheritDoc} */ - public function supportsSavepoints() : bool + public function supportsSavepoints(): bool { return false; } @@ -1160,7 +1150,7 @@ public function supportsSavepoints() : bool /** * {@inheritDoc} */ - public function supportsPrimaryConstraints() : bool + public function supportsPrimaryConstraints(): bool { return false; } @@ -1168,7 +1158,7 @@ public function supportsPrimaryConstraints() : bool /** * {@inheritDoc} */ - public function supportsForeignKeyConstraints() : bool + public function supportsForeignKeyConstraints(): bool { return false; } @@ -1176,7 +1166,7 @@ public function supportsForeignKeyConstraints() : bool /** * {@inheritDoc} */ - public function supportsGettingAffectedRows() : bool + public function supportsGettingAffectedRows(): bool { return false; } @@ -1184,13 +1174,14 @@ public function supportsGettingAffectedRows() : bool /** * {@inheritDoc} */ - protected function doModifyLimitQuery($query, $limit, $offset) : string + protected function doModifyLimitQuery($query, $limit, $offset): string { if ($limit === null) { return $query; } $query .= ' LIMIT '; + if ($offset !== null) { $query .= $offset . ', '; } @@ -1203,15 +1194,15 @@ protected function doModifyLimitQuery($query, $limit, $offset) : string /** * {@inheritDoc} */ - public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) : string + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function getTruncateTableSQL($tableName, $cascade = false) : string + public function getTruncateTableSQL($tableName, $cascade = false): string { /** * For MergeTree* engines may be done with next workaround: @@ -1219,37 +1210,37 @@ public function getTruncateTableSQL($tableName, $cascade = false) : string * SELECT partition FROM system.parts WHERE table= '$tableName'; * ALTER TABLE $tableName DROP PARTITION $partitionName */ - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function createSavePoint($savepoint) : string + public function createSavePoint($savepoint): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function releaseSavePoint($savepoint) : string + public function releaseSavePoint($savepoint): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - public function rollbackSavePoint($savepoint) : string + public function rollbackSavePoint($savepoint): string { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ - protected function getReservedKeywordsClass() : string + protected function getReservedKeywordsClass(): string { return ClickHouseKeywords::class; } @@ -1257,13 +1248,15 @@ protected function getReservedKeywordsClass() : string /** * {@inheritDoc} */ - public function getDefaultValueDeclarationSQL($field) : string + public function getDefaultValueDeclarationSQL($column): string { - if (! isset($field['default'])) { + if (!isset($column['default'])) { return ''; } - $defaultValue = $this->quoteStringLiteral($field['default']); - $fieldType = $field['type'] ?: null; + + $defaultValue = $this->quoteStringLiteral($column['default']); + $fieldType = $column['type'] ?: null; + if ($fieldType !== null) { if ($fieldType === DatableClickHouseType::TYPE_DATE || $fieldType instanceof DateType || @@ -1277,9 +1270,9 @@ public function getDefaultValueDeclarationSQL($field) : string && Type::getType('BigInt')->getBindingType() === ParameterType::INTEGER ) ) { - $defaultValue = $field['default']; + $defaultValue = $column['default']; } elseif ($fieldType === DatableClickHouseType::TYPE_DATE_TIME && - $field['default'] === $this->getCurrentTimestampSQL() + $column['default'] === $this->getCurrentTimestampSQL() ) { $defaultValue = $this->getCurrentTimestampSQL(); } @@ -1291,42 +1284,44 @@ public function getDefaultValueDeclarationSQL($field) : string /** * {@inheritDoc} */ - public function getDoctrineTypeMapping($dbType) : string + public function getDoctrineTypeMapping($dbType): string { - // FixedString - if (stripos($dbType, 'fixedstring') === 0) { + if (mb_stripos($dbType, 'fixedstring') === 0) { $dbType = 'fixedstring'; } - //Enum8 - if (stripos($dbType, 'enum8') === 0) { + if (mb_stripos($dbType, 'enum8') === 0) { $dbType = 'enum8'; } - //Enum16 - if (stripos($dbType, 'enum16') === 0) { + if (mb_stripos($dbType, 'enum16') === 0) { $dbType = 'enum16'; } + return parent::getDoctrineTypeMapping($dbType); } /** * {@inheritDoc} */ - public function quoteStringLiteral($str) : string + public function quoteStringLiteral($str): string { - $c = $this->getStringLiteralQuoteCharacter(); - - return $c . addslashes($str) . $c; + return parent::quoteStringLiteral(addslashes($str)); } /** * {@inheritDoc} */ - public function quoteSingleIdentifier($str) : string + public function quoteSingleIdentifier($str): string { - $c = $this->getIdentifierQuoteCharacter(); + return parent::quoteSingleIdentifier(addslashes($str)); + } - return $c . addslashes($str) . $c; + /** + * {@inheritDoc} + */ + public function getCurrentDatabaseExpression(): string + { + return 'DATABASE()'; } } diff --git a/src/ClickHouseResult.php b/src/ClickHouseResult.php new file mode 100644 index 0000000..40c7d7a --- /dev/null +++ b/src/ClickHouseResult.php @@ -0,0 +1,109 @@ +) + * + * (c) FriendsOfDoctrine . + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOD\DBALClickHouse; + +use Doctrine\DBAL\Driver\Result; + +class ClickHouseResult implements Result +{ + public function __construct(private ?\ArrayIterator $iterator) + { + } + + public function fetchNumeric() + { + $row = $this->iterator->current(); + + if ($row === null) { + return false; + } + + $this->iterator->next(); + + return array_values($row); + } + + public function fetchAssociative() + { + $row = $this->iterator->current(); + + if ($row === null) { + return false; + } + + $this->iterator->next(); + + return $row; + } + + public function fetchOne() + { + $row = $this->iterator->current(); + + if ($row === null) { + return false; + } + + $this->iterator->next(); + + return current($row); + } + + public function fetchAllNumeric(): array + { + return array_map('array_values', $this->iterator->getArrayCopy()); + } + + public function fetchAllAssociative(): array + { + return $this->iterator->getArrayCopy(); + } + + public function fetchFirstColumn(): array + { + $row = $this->iterator->current(); + + if ($row === null) { + return []; + } + + $this->iterator->next(); + + return array_column($this->iterator->getArrayCopy(), array_key_first($row)); + } + + public function rowCount(): int + { + return $this->iterator->count(); + } + + public function columnCount(): int + { + $row = $this->iterator->current(); + + if ($row === null) { + return 0; + } + + $this->iterator->next(); + + return count($row); + } + + public function free(): void + { + $this->iterator = null; + } +} diff --git a/src/ClickHouseSchemaManager.php b/src/ClickHouseSchemaManager.php index 3cd7850..29e1b48 100644 --- a/src/ClickHouseSchemaManager.php +++ b/src/ClickHouseSchemaManager.php @@ -19,7 +19,7 @@ use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\View; use Doctrine\DBAL\Types\Type; -use const CASE_LOWER; + use function array_change_key_case; use function array_filter; use function array_key_exists; @@ -28,17 +28,15 @@ use function current; use function explode; use function is_array; +use function mb_stripos; +use function mb_strtolower; use function preg_match; use function preg_replace; use function str_replace; -use function stripos; -use function strpos; -use function strtolower; use function trim; -/** - * Schema manager for the ClickHouse DBMS. - */ +use const CASE_LOWER; + class ClickHouseSchemaManager extends AbstractSchemaManager { /** @@ -52,7 +50,7 @@ protected function _getPortableTableDefinition($table) /** * {@inheritdoc} */ - protected function _getPortableViewDefinition($view) : View + protected function _getPortableViewDefinition($view): View { $statement = $this->_conn->fetchOne('SHOW CREATE TABLE ' . $view['name']); @@ -62,7 +60,7 @@ protected function _getPortableViewDefinition($view) : View /** * {@inheritdoc} */ - public function listTableIndexes($table) : array + public function listTableIndexes($table): array { $tableView = $this->_getPortableViewDefinition(['name' => $table]); @@ -75,9 +73,7 @@ public function listTableIndexes($table) : array if (is_array($matches) && array_key_exists(2, $matches)) { $indexColumns = array_filter( array_map('trim', explode(',', $matches[2])), - function (string $column) { - return strpos($column, '(') === false; - } + fn (string $column): bool => !str_contains($column, '(') ); return [ @@ -96,7 +92,7 @@ function (string $column) { /** * {@inheritdoc} */ - protected function _getPortableTableColumnDefinition($tableColumn) : Column + protected function _getPortableTableColumnDefinition($tableColumn): Column { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); @@ -110,7 +106,7 @@ protected function _getPortableTableColumnDefinition($tableColumn) : Column $notnull = false; } - if (stripos($columnType, 'fixedstring') === 0) { + if (mb_stripos($columnType, 'fixedstring') === 0) { // get length from FixedString definition $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $columnType); $dbType = 'fixedstring'; @@ -118,29 +114,30 @@ protected function _getPortableTableColumnDefinition($tableColumn) : Column } $unsigned = false; - if (stripos($columnType, 'uint') === 0) { + + if (mb_stripos($columnType, 'uint') === 0) { $unsigned = true; } - if (! isset($tableColumn['name'])) { + if (!isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $default = null; - //TODO process not only DEFAULT type, but ALIAS and MATERIALIZED too - if ($tableColumn['default_expression'] && strtolower($tableColumn['default_type']) === 'default') { + + // @todo process not only DEFAULT type, but ALIAS and MATERIALIZED too + if ($tableColumn['default_expression'] && mb_strtolower($tableColumn['default_type']) === 'default') { $default = $tableColumn['default_expression']; } $options = [ - 'length' => $length, - 'notnull' => $notnull, - 'default' => $default, - 'primary' => false, - 'fixed' => $fixed, - 'unsigned' => $unsigned, + 'length' => $length, + 'unsigned' => $unsigned, + 'fixed' => $fixed, + 'default' => $default, + 'notnull' => $notnull, 'autoincrement' => false, - 'comment' => null, + 'comment' => null, ]; return new Column( diff --git a/src/ClickHouseStatement.php b/src/ClickHouseStatement.php index d88634e..e10237f 100644 --- a/src/ClickHouseStatement.php +++ b/src/ClickHouseStatement.php @@ -15,91 +15,50 @@ namespace FOD\DBALClickHouse; use ClickHouseDB\Client; +use ClickHouseDB\Exception\ClickHouseException; +use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement; -use Doctrine\DBAL\FetchMode; +use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; -use function array_key_exists; -use function array_keys; +use FOD\DBALClickHouse\Driver\Exception\Exception; + use function array_map; use function array_replace; -use function array_shift; -use function array_values; -use function array_walk; -use function count; use function current; -use function explode; use function implode; -use function in_array; use function is_array; use function is_bool; use function is_float; use function is_int; -use function preg_replace; -use function stripos; -use function trim; +use function mb_stripos; -/** - * ClickHouse Statement - */ -class ClickHouseStatement implements \IteratorAggregate, Statement +class ClickHouseStatement implements Statement { - /** @var Client */ - protected $smi2CHClient; - - /** @var string */ - protected $statement; + protected Client $client; - /** @var AbstractPlatform */ - protected $platform; + protected string $statement; - /** @var mixed[] */ - protected $rows = []; - - /** - * Query parameters for prepared statement (key => value) - * @var mixed[] - */ - protected $values = []; - - /** - * Query parameters' types for prepared statement (key => value) - * @var mixed[] - */ - protected $types = []; + protected AbstractPlatform $platform; - /** @var \ArrayIterator|null */ - protected $iterator; + protected array $values = []; - /** @var int */ - private $fetchMode = FetchMode::MIXED; + protected array $types = []; public function __construct(Client $client, string $statement, AbstractPlatform $platform) { - $this->smi2CHClient = $client; - $this->statement = $statement; - $this->platform = $platform; - } - - /** - * {@inheritDoc} - */ - public function getIterator() : \ArrayIterator - { - if (! $this->iterator) { - $this->iterator = new \ArrayIterator($this->rows); - } - - return $this->iterator; + $this->client = $client; + $this->statement = $statement; + $this->platform = $platform; } /** * {@inheritDoc} */ - public function closeCursor() + public function bindValue($param, $value, $type = ParameterType::STRING): bool { - $this->rows = []; - $this->iterator = null; + $this->values[$param] = $value; + $this->types[$param] = $type; return true; } @@ -107,290 +66,115 @@ public function closeCursor() /** * {@inheritDoc} */ - public function columnCount() - { - return $this->rows - ? count(current($this->rows)) - : 0; - } - - /** - * {@inheritDoc} - */ - public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { - $this->fetchMode = $this->assumeFetchMode($fetchMode); + $this->values[$param] = $variable; + $this->types[$param] = $type; return true; } - protected function assumeFetchMode(?int $fetchMode = null) : int - { - $mode = $fetchMode ?: $this->fetchMode; - if (! in_array($mode, [ - FetchMode::ASSOCIATIVE, - FetchMode::NUMERIC, - FetchMode::STANDARD_OBJECT, - \PDO::FETCH_KEY_PAIR, - ], true)) { - $mode = FetchMode::MIXED; - } - - return $mode; - } - /** * {@inheritDoc} */ - public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0) + public function execute($params = null): Result { - $data = $this->getIterator()->current(); - - if ($data === null) { - return false; - } - - $this->getIterator()->next(); - - if ($this->assumeFetchMode($fetchMode) === FetchMode::NUMERIC) { - return array_values($data); + if (is_array($params)) { + $this->values = array_replace($this->values, $params); } - if ($this->assumeFetchMode($fetchMode) === FetchMode::MIXED) { - return array_values($data) + $data; - } + $statement = $this->statement; - if ($this->assumeFetchMode($fetchMode) === FetchMode::STANDARD_OBJECT) { - return (object) $data; - } + $firstPlaceholder = array_key_first($this->values); - if ($this->assumeFetchMode($fetchMode) === \PDO::FETCH_KEY_PAIR) { - if (count($data) < 2) { - throw new \Exception( - 'To fetch in \PDO::FETCH_KEY_PAIR mode, result set must contain at least 2 columns' - ); - } + $positionalPlaceholders = is_int($firstPlaceholder); + $positionalPlaceholdersIsList = $firstPlaceholder === 0; - return [array_shift($data) => array_shift($data)]; - } + if ($positionalPlaceholders) { + $pieces = explode('?', $statement); - return $data; - } + foreach ($pieces as $key => &$piece) { + $positionalPlaceholder = $positionalPlaceholdersIsList ? $key : $key + 1; - /** - * {@inheritDoc} - */ - public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) - { - if ($this->assumeFetchMode($fetchMode) === FetchMode::NUMERIC) { - return array_map( - 'array_values', - $this->rows - ); - } - - if ($this->assumeFetchMode($fetchMode) === FetchMode::MIXED) { - return array_map( - function ($row) { - return array_values($row) + $row; - }, - $this->rows - ); - } + if (array_key_exists($positionalPlaceholder, $this->values)) { + $piece .= $this->resolveType($positionalPlaceholder); + } + } - if ($this->assumeFetchMode($fetchMode) === FetchMode::STANDARD_OBJECT) { - return array_map( - function ($row) { - return (object) $row; - }, - $this->rows - ); + $statement = implode('', $pieces); + } else { + foreach (array_keys($this->values) as $key) { + $namedPlaceholder = ":$key"; + $namedPlaceholderOffset = mb_stripos($statement, $namedPlaceholder); + $namedPlaceholderLength = mb_strlen($namedPlaceholder); + + if ($namedPlaceholderOffset !== false) { + $statement = substr_replace( + $statement, + $this->resolveType($key), + $namedPlaceholderOffset, + $namedPlaceholderLength + ); + } + } } - if ($this->assumeFetchMode($fetchMode) === \PDO::FETCH_KEY_PAIR) { - return array_map( - function ($row) { - if (count($row) < 2) { - throw new \Exception( - 'To fetch in \PDO::FETCH_KEY_PAIR mode, result set must contain at least 2 columns' - ); - } - - return [array_shift($row) => array_shift($row)]; - }, - $this->rows + try { + return new ClickHouseResult( + new \ArrayIterator( + mb_stripos($statement, 'select') === 0 || + mb_stripos($statement, 'show') === 0 || + mb_stripos($statement, 'describe') === 0 + ? $this->client->select($statement)->rows() + : $this->client->write($statement)->rows() + ) ); + } catch (ClickHouseException $exception) { + throw new Exception(previous: $exception, sqlState: $exception->getMessage()); } - - return $this->rows; - } - - /** - * {@inheritDoc} - */ - public function fetchColumn($columnIndex = 0) - { - $elem = $this->fetch(FetchMode::NUMERIC); - if (is_array($elem)) { - return $elem[$columnIndex] ?? $elem[0]; - } - - return false; } /** - * {@inheritDoc} + * @throws DBALException */ - public function bindValue($param, $value, $type = null): bool + protected function resolveType(int|string $key): string { - $this->values[$param] = $value; - $this->types[$param] = $type; - - return true; - } - - /** - * {@inheritDoc} - */ - public function bindParam($column, &$variable, $type = null, $length = null): bool - { - $this->values[$column] = &$variable; - $this->types[$column] = $type; - - return true; - } + $value = $this->values[$key]; - public function errorCode() : int - { - throw new ClickHouseException('You need to implement ClickHouseStatement::' . __METHOD__ . '()'); - } - - public function errorInfo() : array - { - throw new ClickHouseException('You need to implement ClickHouseStatement::' . __METHOD__ . '()'); - } - - /** - * {@inheritDoc} - */ - public function execute($params = null) : bool - { - $hasZeroIndex = false; - if (is_array($params)) { - $this->values = array_replace($this->values, $params);//TODO array keys must be all strings or all integers? - $hasZeroIndex = array_key_exists(0, $params); + if ($value === null) { + return 'NULL'; } - $sql = $this->statement; - - if ($hasZeroIndex) { - $statementParts = explode('?', $sql); - array_walk($statementParts, function (&$part, $key) : void { - if (! array_key_exists($key, $this->values)) { - return; + if (is_array($value)) { + if (is_int(current($value)) || is_float(current($value))) { + foreach ($value as $item) { + if (!is_int($item) && !is_float($item)) { + throw new DBALException('Array values must all be int/float or string, mixes not allowed'); + } } - - $part .= $this->getTypedParam($key); - }); - $sql = implode('', $statementParts); - } else { - foreach (array_keys($this->values) as $key) { - $sql = preg_replace( - '/(' . (is_int($key) ? '\?' : ':' . $key) . ')/i', - $this->getTypedParam($key), - $sql, - 1 - ); + } else { + $value = array_map(function (?string $item): string { + return $item === null ? 'NULL' : $this->platform->quoteStringLiteral($item); + }, $value); } - } - - $this->processViaSMI2($sql); - return true; - } - - /** - * {@inheritDoc} - */ - public function rowCount() : int - { - return 1; // ClickHouse do not return amount of inserted rows, so we will return 1 - } - - public function getSql() : string - { - return $this->statement; - } - - /** - * Specific SMI2 ClickHouse lib statement execution - * If you want to use any other lib for working with CH -- just update this method - * - */ - protected function processViaSMI2(string $sql) : void - { - $sql = trim($sql); - - $this->rows = - stripos($sql, 'select') === 0 || - stripos($sql, 'show') === 0 || - stripos($sql, 'describe') === 0 ? - $this->smi2CHClient->select($sql)->rows() : - $this->smi2CHClient->write($sql)->rows(); - } - - /** - * @param string|int $key - * @throws ClickHouseException - */ - protected function getTypedParam($key) : string - { - if ($this->values[$key] === null) { - return 'NULL'; + return '[' . implode(', ', $value) . ']'; } $type = $this->types[$key] ?? null; - // if param type was not setted - trying to get db-type by php-var-type if ($type === null) { - if (is_bool($this->values[$key])) { - $type = ParameterType::BOOLEAN; - } elseif (is_int($this->values[$key]) || is_float($this->values[$key])) { + if (is_int($value) || is_float($value)) { $type = ParameterType::INTEGER; - } elseif (is_array($this->values[$key])) { - /* - * ClickHouse Arrays - */ - $values = $this->values[$key]; - if (is_int(current($values)) || is_float(current($values))) { - array_map( - function ($value) : void { - if (! is_int($value) && ! is_float($value)) { - throw new ClickHouseException( - 'Array values must all be int/float or string, mixes not allowed' - ); - } - }, - $values - ); - } else { - $values = array_map(function ($value) { - return $value === null ? 'NULL' : $this->platform->quoteStringLiteral($value); - }, $values); - } - - return '[' . implode(', ', $values) . ']'; + } elseif (is_bool($value)) { + $type = ParameterType::BOOLEAN; } } - if ($type === ParameterType::INTEGER) { - return (string) $this->values[$key]; - } - - if ($type === ParameterType::BOOLEAN) { - return (string) (int) (bool) $this->values[$key]; - } - - return $this->platform->quoteStringLiteral((string) $this->values[$key]); + return match ($type) { + ParameterType::INTEGER => (string) $value, + ParameterType::BOOLEAN => (string) (int) (bool) $value, + default => $this->platform->quoteStringLiteral((string) $value) + }; } } diff --git a/src/Connection.php b/src/Connection.php index 316bf62..01086cc 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -14,43 +14,42 @@ namespace FOD\DBALClickHouse; -use function strtoupper; -use function substr; +use Doctrine\DBAL\Exception; + +use function mb_strtoupper; +use function mb_substr; use function trim; -/** - * ClickHouse Connection - */ class Connection extends \Doctrine\DBAL\Connection { /** * {@inheritDoc} */ - public function executeUpdate($query, array $params = [], array $types = []) : int + public function delete($table, array $criteria, array $types = []): int { - // ClickHouse has no UPDATE or DELETE statements - $command = strtoupper(substr(trim($query), 0, 6)); - if ($command === 'UPDATE' || $command === 'DELETE') { - throw new ClickHouseException('UPDATE and DELETE are not allowed in ClickHouse'); - } - - return parent::executeUpdate($query, $params, $types); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function delete($tableExpression, array $identifier, array $types = []) : int + public function update($table, array $data, array $criteria, array $types = []): int { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function update($tableExpression, array $data, array $identifier, array $types = []) : int + public function executeStatement($sql, array $params = [], array $types = []): int|string { - throw ClickHouseException::notSupported(__METHOD__); + $command = mb_strtoupper(mb_substr(trim($sql), 0, 6)); + + if (in_array($command, ['DELETE', 'UPDATE'], true)) { + throw Exception::notSupported($command); + } + + return parent::executeStatement($sql, $params, $types); } /** @@ -58,114 +57,114 @@ public function update($tableExpression, array $data, array $identifier, array $ */ /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function setTransactionIsolation($level) : int + public function setTransactionIsolation($level): int { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function getTransactionIsolation() : int + public function getTransactionIsolation(): int { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function getTransactionNestingLevel() : int + public function getTransactionNestingLevel(): int { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function transactional(\Closure $func) : void + public function transactional(\Closure $func): void { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) : void + public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints): void { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function getNestTransactionsWithSavepoints() : bool + public function getNestTransactionsWithSavepoints(): bool { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function beginTransaction() : bool + public function beginTransaction(): bool { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function commit() : bool + public function commit(): bool { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function rollBack() : bool + public function rollBack(): bool { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function createSavepoint($savepoint) : void + public function createSavepoint($savepoint): void { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function releaseSavepoint($savepoint) : void + public function releaseSavepoint($savepoint): void { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function rollbackSavepoint($savepoint) : void + public function rollbackSavepoint($savepoint): void { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function setRollbackOnly() : void + public function setRollbackOnly(): void { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } /** - * @throws ClickHouseException + * {@inheritDoc} */ - public function isRollbackOnly() : bool + public function isRollbackOnly(): bool { - throw ClickHouseException::notSupported(__METHOD__); + throw Exception::notSupported(__METHOD__); } } diff --git a/src/Driver.php b/src/Driver.php index 46218d7..3662fe6 100644 --- a/src/Driver.php +++ b/src/Driver.php @@ -15,48 +15,46 @@ namespace FOD\DBALClickHouse; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver\API\ExceptionConverter; +use Doctrine\DBAL\Driver\Connection as DriverConnection; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\AbstractSchemaManager; -/** - * ClickHouse Driver - */ class Driver implements \Doctrine\DBAL\Driver { /** * {@inheritDoc} */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) : ClickHouseConnection + public function connect(array $params): DriverConnection { - if ($username === null) { - if (! isset($params['user'])) { - throw new ClickHouseException('Connection parameter `user` is required'); - } - - $username = $params['user']; + if (!isset($params['user'])) { + throw new Exception('Connection parameter `user` is required'); } - if ($password === null) { - if (! isset($params['password'])) { - throw new ClickHouseException('Connection parameter `password` is required'); - } + $user = $params['user']; - $password = $params['password']; + if (!isset($params['password'])) { + throw new Exception('Connection parameter `password` is required'); } - if (! isset($params['host'])) { - throw new ClickHouseException('Connection parameter `host` is required'); + $password = $params['password']; + + if (!isset($params['host'])) { + throw new Exception('Connection parameter `host` is required'); } - if (! isset($params['port'])) { - throw new ClickHouseException('Connection parameter `port` is required'); + if (!isset($params['port'])) { + throw new Exception('Connection parameter `port` is required'); } - return new ClickHouseConnection($params, (string) $username, (string) $password, $this->getDatabasePlatform()); + return new ClickHouseConnection($params, (string) $user, (string) $password, $this->getDatabasePlatform()); } /** * {@inheritDoc} */ - public function getDatabasePlatform() : ClickHousePlatform + public function getDatabasePlatform(): AbstractPlatform { return new ClickHousePlatform(); } @@ -64,26 +62,16 @@ public function getDatabasePlatform() : ClickHousePlatform /** * {@inheritDoc} */ - public function getSchemaManager(Connection $conn) : ClickHouseSchemaManager - { - return new ClickHouseSchemaManager($conn); - } - - /** - * {@inheritDoc} - */ - public function getName() : string + public function getSchemaManager(Connection $conn, AbstractPlatform $platform): AbstractSchemaManager { - return 'clickhouse'; + return new ClickHouseSchemaManager($conn, $platform); } /** * {@inheritDoc} */ - public function getDatabase(Connection $conn) : string + public function getExceptionConverter(): ExceptionConverter { - $params = $conn->getParams(); - - return $params['dbname'] ?? $conn->fetchOne('SELECT currentDatabase() as dbname'); + return new ClickHouseExceptionConverter(); } } diff --git a/src/Driver/Exception/Exception.php b/src/Driver/Exception/Exception.php new file mode 100644 index 0000000..4ea172a --- /dev/null +++ b/src/Driver/Exception/Exception.php @@ -0,0 +1,32 @@ +) + * + * (c) FriendsOfDoctrine . + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOD\DBALClickHouse\Driver\Exception; + +class Exception extends \Exception implements \Doctrine\DBAL\Driver\Exception +{ + public function __construct( + string $message = '', + int $code = 0, + ?\Throwable $previous = null, + private ?string $sqlState = null + ) { + parent::__construct($message, $code, $previous); + } + + public function getSQLState(): ?string + { + return $this->sqlState; + } +} diff --git a/src/Types/ArrayDateTimeType.php b/src/Types/ArrayDateTimeType.php index 390fa91..30096d8 100644 --- a/src/Types/ArrayDateTimeType.php +++ b/src/Types/ArrayDateTimeType.php @@ -16,16 +16,14 @@ use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; + use function array_filter; use function array_map; use function implode; -/** - * Array(DateTime) Type class - */ class ArrayDateTimeType extends ArrayType implements DatableClickHouseType { - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return DatableClickHouseType::TYPE_DATE_TIME; } @@ -33,10 +31,10 @@ public function getBaseClickHouseType() : string /** * {@inheritDoc} */ - public function convertToPHPValue($value, AbstractPlatform $platform) : array + public function convertToPHPValue($value, AbstractPlatform $platform): array { return array_map( - function ($stringDatetime) use ($platform) { + function ($stringDatetime) use ($platform): \DateTime { return \DateTime::createFromFormat($platform->getDateTimeFormatString(), $stringDatetime); }, (array) $value @@ -46,28 +44,28 @@ function ($stringDatetime) use ($platform) { /** * {@inheritDoc} */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) : string + public function convertToDatabaseValue($value, AbstractPlatform $platform): string { return '[' . implode( - ', ', - array_map( - function (\DateTime $datetime) use ($platform) { - return "'" . $datetime->format($platform->getDateTimeFormatString()) . "'"; - }, - array_filter( - (array) $value, - function ($datetime) { - return $datetime instanceof \DateTime; - } + ', ', + array_map( + function (\DateTime $datetime) use ($platform): string { + return "'" . $datetime->format($platform->getDateTimeFormatString()) . "'"; + }, + array_filter( + (array) $value, + function ($datetime): bool { + return $datetime instanceof \DateTime; + } + ) ) - ) - ) . ']'; + ) . ']'; } /** * {@inheritDoc} */ - public function getBindingType() : int + public function getBindingType(): int { return ParameterType::INTEGER; } diff --git a/src/Types/ArrayDateType.php b/src/Types/ArrayDateType.php index 69ad234..c9c909c 100644 --- a/src/Types/ArrayDateType.php +++ b/src/Types/ArrayDateType.php @@ -16,16 +16,14 @@ use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; + use function array_filter; use function array_map; use function implode; -/** - * Array(Date) Type class - */ class ArrayDateType extends ArrayType implements DatableClickHouseType { - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return DatableClickHouseType::TYPE_DATE; } @@ -33,10 +31,10 @@ public function getBaseClickHouseType() : string /** * {@inheritDoc} */ - public function convertToPHPValue($value, AbstractPlatform $platform) : array + public function convertToPHPValue($value, AbstractPlatform $platform): array { return array_map( - function ($stringDatetime) use ($platform) { + function ($stringDatetime) use ($platform): \DateTime { return \DateTime::createFromFormat($platform->getDateFormatString(), $stringDatetime); }, (array) $value @@ -46,28 +44,28 @@ function ($stringDatetime) use ($platform) { /** * {@inheritDoc} */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) : string + public function convertToDatabaseValue($value, AbstractPlatform $platform): string { return '[' . implode( - ', ', - array_map( - function (\DateTime $datetime) use ($platform) { - return "'" . $datetime->format($platform->getDateFormatString()) . "'"; - }, - array_filter( - (array) $value, - function ($datetime) { - return $datetime instanceof \DateTime; - } + ', ', + array_map( + function (\DateTime $datetime) use ($platform) { + return "'" . $datetime->format($platform->getDateFormatString()) . "'"; + }, + array_filter( + (array) $value, + function ($datetime): bool { + return $datetime instanceof \DateTime; + } + ) ) - ) - ) . ']'; + ) . ']'; } /** * {@inheritDoc} */ - public function getBindingType() : int + public function getBindingType(): int { return ParameterType::INTEGER; } diff --git a/src/Types/ArrayFloat32Type.php b/src/Types/ArrayFloat32Type.php index 77eac6a..5b5b7b8 100644 --- a/src/Types/ArrayFloat32Type.php +++ b/src/Types/ArrayFloat32Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(Float32) Type - */ class ArrayFloat32Type extends ArrayType implements BitNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::THIRTY_TWO_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_FLOAT; } diff --git a/src/Types/ArrayFloat64Type.php b/src/Types/ArrayFloat64Type.php index d2924f9..eb8bf7c 100644 --- a/src/Types/ArrayFloat64Type.php +++ b/src/Types/ArrayFloat64Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(Float64) Type - */ class ArrayFloat64Type extends ArrayType implements BitNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::SIXTY_FOUR_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_FLOAT; } diff --git a/src/Types/ArrayInt16Type.php b/src/Types/ArrayInt16Type.php index 7b6faf1..deaadc3 100644 --- a/src/Types/ArrayInt16Type.php +++ b/src/Types/ArrayInt16Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(Int16) Type - */ class ArrayInt16Type extends ArrayType implements BitNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::SIXTEEN_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_INT; } diff --git a/src/Types/ArrayInt32Type.php b/src/Types/ArrayInt32Type.php index 5df7fed..bcc45b8 100644 --- a/src/Types/ArrayInt32Type.php +++ b/src/Types/ArrayInt32Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(Int32) Type - */ class ArrayInt32Type extends ArrayType implements BitNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::THIRTY_TWO_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_INT; } diff --git a/src/Types/ArrayInt64Type.php b/src/Types/ArrayInt64Type.php index 591708d..2efea2e 100644 --- a/src/Types/ArrayInt64Type.php +++ b/src/Types/ArrayInt64Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(Int64) Type - */ class ArrayInt64Type extends ArrayType implements BitNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::SIXTY_FOUR_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_INT; } diff --git a/src/Types/ArrayInt8Type.php b/src/Types/ArrayInt8Type.php index f3db1f9..f6035ae 100644 --- a/src/Types/ArrayInt8Type.php +++ b/src/Types/ArrayInt8Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(Int8) Type - */ class ArrayInt8Type extends ArrayType implements BitNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::EIGHT_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_INT; } diff --git a/src/Types/ArrayStringType.php b/src/Types/ArrayStringableType.php similarity index 65% rename from src/Types/ArrayStringType.php rename to src/Types/ArrayStringableType.php index b11ff28..8902366 100644 --- a/src/Types/ArrayStringType.php +++ b/src/Types/ArrayStringableType.php @@ -16,39 +16,37 @@ use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; + use function array_map; use function implode; -/** - * Array(String) Type class - */ -class ArrayStringType extends ArrayType implements StringClickHouseType +class ArrayStringableType extends ArrayType implements StringableClickHouseType { - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { - return StringClickHouseType::TYPE_STRING; + return StringableClickHouseType::TYPE_STRING; } /** * {@inheritDoc} */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) : string + public function convertToDatabaseValue($value, AbstractPlatform $platform): string { return '[' . implode( - ', ', - array_map( - function (string $value) use ($platform) { + ', ', + array_map( + function (string $value) use ($platform): string { return $platform->quoteStringLiteral($value); - }, - (array) $value - ) - ) . ']'; + }, + (array) $value + ) + ) . ']'; } /** * {@inheritDoc} */ - public function getBindingType() : int + public function getBindingType(): int { return ParameterType::INTEGER; } diff --git a/src/Types/ArrayType.php b/src/Types/ArrayType.php index a7a03dc..2281413 100644 --- a/src/Types/ArrayType.php +++ b/src/Types/ArrayType.php @@ -14,40 +14,32 @@ namespace FOD\DBALClickHouse\Types; -use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; + use function array_key_exists; +use function mb_strtolower; use function sprintf; -use function strtolower; -/** - * Array(*) Types basic class - */ abstract class ArrayType extends Type implements ClickHouseType { protected const ARRAY_TYPES = [ - 'array(int8)' => ArrayInt8Type::class, - 'array(int16)' => ArrayInt16Type::class, - 'array(int32)' => ArrayInt32Type::class, - 'array(int64)' => ArrayInt64Type::class, - 'array(uint8)' => ArrayUInt8Type::class, - 'array(uint16)' => ArrayUInt16Type::class, - 'array(uint32)' => ArrayUInt32Type::class, - 'array(uint64)' => ArrayUInt64Type::class, - 'array(float32)' => ArrayFloat32Type::class, - 'array(float64)' => ArrayFloat64Type::class, - 'array(string)' => ArrayStringType::class, + 'array(int8)' => ArrayInt8Type::class, + 'array(int16)' => ArrayInt16Type::class, + 'array(int32)' => ArrayInt32Type::class, + 'array(int64)' => ArrayInt64Type::class, + 'array(uint8)' => ArrayUInt8Type::class, + 'array(uint16)' => ArrayUInt16Type::class, + 'array(uint32)' => ArrayUInt32Type::class, + 'array(uint64)' => ArrayUInt64Type::class, + 'array(float32)' => ArrayFloat32Type::class, + 'array(float64)' => ArrayFloat64Type::class, + 'array(string)' => ArrayStringableType::class, 'array(datetime)' => ArrayDateTimeType::class, - 'array(date)' => ArrayDateType::class, + 'array(date)' => ArrayDateType::class, ]; - /** - * Register Array types to the type map. - * - * @throws DBALException - */ - public static function registerArrayTypes(AbstractPlatform $platform) : void + public static function registerArrayTypes(AbstractPlatform $platform): void { foreach (self::ARRAY_TYPES as $typeName => $className) { if (self::hasType($typeName)) { @@ -55,6 +47,7 @@ public static function registerArrayTypes(AbstractPlatform $platform) : void } self::addType($typeName, $className); + foreach (Type::getType($typeName)->getMappedDatabaseTypes($platform) as $dbType) { $platform->registerDoctrineTypeMapping($dbType, $typeName); } @@ -64,7 +57,7 @@ public static function registerArrayTypes(AbstractPlatform $platform) : void /** * {@inheritDoc} */ - public function getMappedDatabaseTypes(AbstractPlatform $platform) : array + public function getMappedDatabaseTypes(AbstractPlatform $platform): array { return [$this->getName()]; } @@ -72,23 +65,23 @@ public function getMappedDatabaseTypes(AbstractPlatform $platform) : array /** * {@inheritDoc} */ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) : string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return $this->getDeclaration($fieldDeclaration); + return $this->getDeclaration($column); } /** * {@inheritDoc} */ - public function getName() : string + public function getName(): string { - return strtolower($this->getDeclaration()); + return mb_strtolower($this->getDeclaration()); } /** - * @param mixed[] $fieldDeclaration + * @param array $fieldDeclaration */ - protected function getDeclaration(array $fieldDeclaration = []) : string + protected function getDeclaration(array $fieldDeclaration = []): string { return sprintf( array_key_exists( diff --git a/src/Types/ArrayUInt16Type.php b/src/Types/ArrayUInt16Type.php index 89e00bf..906b4b9 100644 --- a/src/Types/ArrayUInt16Type.php +++ b/src/Types/ArrayUInt16Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(UInt16) Type - */ class ArrayUInt16Type extends ArrayType implements BitNumericalClickHouseType, UnsignedNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::SIXTEEN_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_INT; } diff --git a/src/Types/ArrayUInt32Type.php b/src/Types/ArrayUInt32Type.php index d2a609e..0b2dd35 100644 --- a/src/Types/ArrayUInt32Type.php +++ b/src/Types/ArrayUInt32Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(UInt32) Type - */ class ArrayUInt32Type extends ArrayType implements BitNumericalClickHouseType, UnsignedNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::THIRTY_TWO_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_INT; } diff --git a/src/Types/ArrayUInt64Type.php b/src/Types/ArrayUInt64Type.php index 7fff264..57ee1f8 100644 --- a/src/Types/ArrayUInt64Type.php +++ b/src/Types/ArrayUInt64Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(UInt64) Type - */ class ArrayUInt64Type extends ArrayType implements BitNumericalClickHouseType, UnsignedNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::SIXTY_FOUR_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_INT; } diff --git a/src/Types/ArrayUInt8Type.php b/src/Types/ArrayUInt8Type.php index 7692369..4468b98 100644 --- a/src/Types/ArrayUInt8Type.php +++ b/src/Types/ArrayUInt8Type.php @@ -14,17 +14,14 @@ namespace FOD\DBALClickHouse\Types; -/** - * Array(UInt8) Type - */ class ArrayUInt8Type extends ArrayType implements BitNumericalClickHouseType, UnsignedNumericalClickHouseType { - public function getBits() : int + public function getBits(): int { return BitNumericalClickHouseType::EIGHT_BIT; } - public function getBaseClickHouseType() : string + public function getBaseClickHouseType(): string { return NumericalClickHouseType::TYPE_INT; } diff --git a/src/Types/BigIntType.php b/src/Types/BigIntType.php index 1bdd44a..964e2e0 100644 --- a/src/Types/BigIntType.php +++ b/src/Types/BigIntType.php @@ -17,15 +17,12 @@ use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; -/** - * BigInt Type - */ class BigIntType extends \Doctrine\DBAL\Types\BigIntType { /** * {@inheritdoc} */ - public function getBindingType() : int + public function getBindingType(): int { return ParameterType::INTEGER; } @@ -33,7 +30,7 @@ public function getBindingType() : int /** * {@inheritdoc} */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) : int + public function convertToDatabaseValue($value, AbstractPlatform $platform): int { return (int) $value; } @@ -41,8 +38,8 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform) : int /** * {@inheritdoc} */ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) : string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return (empty($fieldDeclaration['unsigned']) ? '' : 'U') . 'Int64'; + return (empty($column['unsigned']) ? '' : 'U') . 'Int64'; } } diff --git a/src/Types/BitNumericalClickHouseType.php b/src/Types/BitNumericalClickHouseType.php index 8a1f8dc..49a7445 100644 --- a/src/Types/BitNumericalClickHouseType.php +++ b/src/Types/BitNumericalClickHouseType.php @@ -21,5 +21,5 @@ interface BitNumericalClickHouseType extends NumericalClickHouseType public const THIRTY_TWO_BIT = 32; public const SIXTY_FOUR_BIT = 64; - public function getBits() : int; + public function getBits(): int; } diff --git a/src/Types/ClickHouseType.php b/src/Types/ClickHouseType.php index ef56712..4077a5e 100644 --- a/src/Types/ClickHouseType.php +++ b/src/Types/ClickHouseType.php @@ -16,5 +16,5 @@ interface ClickHouseType { - public function getBaseClickHouseType() : string; + public function getBaseClickHouseType(): string; } diff --git a/src/Types/StringClickHouseType.php b/src/Types/StringableClickHouseType.php similarity index 90% rename from src/Types/StringClickHouseType.php rename to src/Types/StringableClickHouseType.php index 2705923..d39f1ff 100644 --- a/src/Types/StringClickHouseType.php +++ b/src/Types/StringableClickHouseType.php @@ -14,7 +14,7 @@ namespace FOD\DBALClickHouse\Types; -interface StringClickHouseType extends ClickHouseType +interface StringableClickHouseType extends ClickHouseType { public const TYPE_STRING = 'String'; public const TYPE_FIXED_STRING = 'FixedString'; diff --git a/tests/ArraysTest.php b/tests/ArraysTest.php index c99d1cb..e7df599 100644 --- a/tests/ArraysTest.php +++ b/tests/ArraysTest.php @@ -1,4 +1,7 @@ ) @@ -22,128 +25,180 @@ */ class ArraysTest extends TestCase { - /** @var Connection */ - protected $connection; + private Connection $connection; - public function setUp() : void + public function setUp(): void { $this->connection = CreateConnectionTest::createConnection(); + ArrayType::registerArrayTypes($this->connection->getDatabasePlatform()); } - public function tearDown() : void + public function tearDown(): void { - $this->connection->exec('DROP TABLE test_array_table'); + $this->connection->executeStatement('DROP TABLE test_array_table'); } - public function testArrayInt8() + public function testArrayInt8(): void { $this->createTempTable('array(int8)'); $this->connection->insert('test_array_table', ['arr' => [1, 2, 3, 4, 5, 6, 7, 8]]); - $this->assertEquals(['arr' => [1, 2, 3, 4, 5, 6, 7, 8]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [1, 2, 3, 4, 5, 6, 7, 8]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayInt16() + public function testArrayInt16(): void { $this->createTempTable('array(int16)'); $this->connection->insert('test_array_table', ['arr' => [100, 2000, 30000]]); - $this->assertEquals(['arr' => [100, 2000, 30000]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [100, 2000, 30000]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayInt32() + public function testArrayInt32(): void { $this->createTempTable('array(int32)'); $this->connection->insert('test_array_table', ['arr' => [1000000, 2000000000]]); - $this->assertEquals(['arr' => [1000000, 2000000000]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [1000000, 2000000000]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayInt64() + public function testArrayInt64(): void { $this->createTempTable('array(int64)'); $this->connection->insert('test_array_table', ['arr' => [200000000000, 3000000000000000000]]); - $this->assertEquals(['arr' => [200000000000, 3000000000000000000]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [200000000000, 3000000000000000000]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayUInt8() + public function testArrayUInt8(): void { $this->createTempTable('array(uint8)'); $this->connection->insert('test_array_table', ['arr' => [1, 2, 3, 4, 5, 6, 7, 8]]); - $this->assertEquals(['arr' => [1, 2, 3, 4, 5, 6, 7, 8]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [1, 2, 3, 4, 5, 6, 7, 8]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayUInt16() + public function testArrayUInt16(): void { $this->createTempTable('array(uint16)'); $this->connection->insert('test_array_table', ['arr' => [100, 2000, 30000]]); - $this->assertEquals(['arr' => [100, 2000, 30000]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [100, 2000, 30000]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayUInt32() + public function testArrayUInt32(): void { $this->createTempTable('array(uint32)'); $this->connection->insert('test_array_table', ['arr' => [1000000, 2000000000]]); - $this->assertEquals(['arr' => [1000000, 2000000000]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [1000000, 2000000000]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayUInt64() + public function testArrayUInt64(): void { $this->createTempTable('array(uint64)'); $this->connection->insert('test_array_table', ['arr' => [200000000000, 3000000000000000000]]); - $this->assertEquals(['arr' => [200000000000, 3000000000000000000]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [200000000000, 3000000000000000000]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayFloat32() + public function testArrayFloat32(): void { $this->createTempTable('array(float32)'); $this->connection->insert('test_array_table', ['arr' => [1.5, 10.5]]); - $this->assertEquals(['arr' => [1.5, 10.5]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [1.5, 10.5]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayFloat64() + public function testArrayFloat64(): void { $this->createTempTable('array(float64)'); $this->connection->insert('test_array_table', ['arr' => [100.512, 10000.5814]]); - $this->assertEquals(['arr' => [100.512, 10000.5814]], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => [100.512, 10000.5814]], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayString() + public function testArrayString(): void { $this->createTempTable('array(string)'); $this->connection->insert('test_array_table', ['arr' => ['foo', 'bar']]); - $this->assertEquals(['arr' => ['foo', 'bar']], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => ['foo', 'bar']], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayDatetime() + public function testArrayDatetime(): void { $dateTimeArray = [(new \DateTime('2000-01-01'))->format('Y-m-d H:i:s'), (new \DateTime('2017-05-05'))->format('Y-m-d H:i:s')]; $this->createTempTable('array(datetime)'); $this->connection->insert('test_array_table', ['arr' => $dateTimeArray]); - $this->assertEquals(['arr' => $dateTimeArray], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => $dateTimeArray], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - public function testArrayDate() + public function testArrayDate(): void { $datesArray = [(new \DateTime('2000-01-01'))->format('Y-m-d'), (new \DateTime('2017-05-05'))->format('Y-m-d')]; $this->createTempTable('array(date)'); $this->connection->insert('test_array_table', ['arr' => $datesArray]); - $this->assertEquals(['arr' => $datesArray], current($this->connection->fetchAll('SELECT arr FROM test_array_table'))); + + $this->assertEquals( + ['arr' => $datesArray], + current($this->connection->fetchAllAssociative('SELECT arr FROM test_array_table')) + ); } - protected function createTempTable($arrayType) + private function createTempTable(string $arrayType): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; + if ($toSchema->hasTable('test_array_table') || $fromSchema->hasTable('test_array_table')) { - $this->connection->exec('DROP TABLE test_array_table'); + $this->connection->executeStatement('DROP TABLE test_array_table'); } - $newTable = $toSchema->createTable('test_array_table'); + $newTable = $toSchema->createTable('test_array_table'); $newTable->addColumn('arr', $arrayType); $newTable->addOption('engine', 'Memory'); foreach ($fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } } } - diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 6dd6aa9..54d012c 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -1,4 +1,7 @@ ) @@ -12,7 +15,7 @@ namespace FOD\DBALClickHouse\Tests; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; -use FOD\DBALClickHouse\ClickHouseException; +use Doctrine\DBAL\Exception; use FOD\DBALClickHouse\Connection; use PHPUnit\Framework\TestCase; @@ -23,141 +26,157 @@ */ class ConnectionTest extends TestCase { - /** @var Connection */ - protected $connection; + private Connection $connection; - public function setUp() : void + public function setUp(): void { $this->connection = CreateConnectionTest::createConnection(); } - public function testExecuteUpdateDelete() + public function testDelete(): void { - $this->expectException(ClickHouseException::class); - $this->connection->executeUpdate('DELETE from test WHERE 1'); + $this->expectException(Exception::class); + + $this->connection->delete('test', ['id' => 1]); } - public function testExecuteUpdateUpdate() + public function testUpdate(): void { - $this->expectException(ClickHouseException::class); - $this->connection->executeUpdate('UPDATE test SET name = :name WHERE id = :id', [':name' => 'test', ':id' => 1]); + $this->expectException(Exception::class); + + $this->connection->update('test', ['name' => 'test'], ['id' => 1]); } - public function testDelete() + public function testExecuteStatementDelete(): void { - $this->expectException(ClickHouseException::class); - $this->connection->delete('test', ['id' => 1]); + $this->expectException(Exception::class); + + $this->connection->executeStatement('DELETE FROM test WHERE id = :id', ['id' => 1]); } - public function testUpdate() + public function testExecuteStatementUpdate(): void { - $this->expectException(ClickHouseException::class); - $this->connection->update('test', ['name' => 'test'], ['id' => 1]); + $this->expectException(Exception::class); + + $this->connection->executeStatement('UPDATE test SET name = :name WHERE id = :id', ['name' => 'test', 'id' => 1]); } - public function testSetTransactionIsolation() + public function testSetTransactionIsolation(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->setTransactionIsolation(1); } - public function testGetTransactionIsolation() + public function testGetTransactionIsolation(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->getTransactionIsolation(); } - public function testGetTransactionNestingLevel() + public function testGetTransactionNestingLevel(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->getTransactionNestingLevel(); } - public function testTransactional() + public function testTransactional(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->transactional(function () { }); } - public function testSetNestTransactionsWithSavepoints() + public function testSetNestTransactionsWithSavepoints(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->setNestTransactionsWithSavepoints(true); } - public function testGetNestTransactionsWithSavepoints() + public function testGetNestTransactionsWithSavepoints(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->getNestTransactionsWithSavepoints(); } - public function testBeginTransaction() + public function testBeginTransaction(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->beginTransaction(); } - public function testCommit() + public function testCommit(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->commit(); } - public function testRollBack() + public function testRollBack(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->rollBack(); } - public function testCreateSavepoint() + public function testCreateSavepoint(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->createSavepoint('1'); } - public function testReleaseSavepoint() + public function testReleaseSavepoint(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->releaseSavepoint('1'); } - public function testRollbackSavepoint() + public function testRollbackSavepoint(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->rollbackSavepoint('1'); } - public function testSetRollbackOnly() + public function testSetRollbackOnly(): void { - $this->expectException(ClickHouseException::class); + $this->expectException(Exception::class); + $this->connection->setRollbackOnly(); } - public function testIsRollbackOnly() + public function testIsRollbackOnly(): void { - $this->expectException(ClickHouseException::class); - $this->connection->isRollbackOnly(); - } + $this->expectException(Exception::class); - public function testPing() - { - $this->assertTrue($this->connection->ping()); + $this->connection->isRollbackOnly(); } - public function testGetServerVersion() + public function testGetServerVersion(): void { $conn = $this->connection->getWrappedConnection(); + if ($conn instanceof ServerInfoAwareConnection) { - $pattern = '/(^[0-9]+.[0-9]+.[0-9]+(.[0-9]$|$))/mi'; + $pattern = '/^\d+\.\d+\.\d+\.\d+$/'; + if (method_exists($this, 'assertMatchesRegularExpression')) { $this->assertMatchesRegularExpression($pattern, $conn->getServerVersion()); } else { $this->assertRegExp($pattern, $conn->getServerVersion()); } } else { - $this->fail(sprintf('`%s` does not implement the `%s` interface', \get_class($conn), - ServerInfoAwareConnection::class)); + $this->fail( + sprintf('`%s` does not implement the `%s` interface', \get_class($conn), + ServerInfoAwareConnection::class) + ); } } } diff --git a/tests/CreateConnectionTest.php b/tests/CreateConnectionTest.php index bcc026c..9f5ee5d 100644 --- a/tests/CreateConnectionTest.php +++ b/tests/CreateConnectionTest.php @@ -1,4 +1,7 @@ ) @@ -11,7 +14,9 @@ namespace FOD\DBALClickHouse\Tests; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; use PHPUnit\Framework\TestCase; @@ -22,41 +27,38 @@ */ class CreateConnectionTest extends TestCase { - public function testCreateConnectionWithRightParams() + public function testCreateConnectionWithRightParams(): void { $this->assertInstanceOf(Connection::class, self::createConnection()); } - public function testCreateConnectionWithBadParams() + public function testCreateConnectionWithBadParams(): void { $this->expectException(Exception::class); $this->assertInstanceOf(Connection::class, self::createConnection([])); } - /** - * @param null|array $params - * @return Connection - */ - public static function createConnection($params = null) + public static function createConnection(array $params = null): Connection { - if (null === $params) { + if ($params === null) { $params = [ - 'host' => phpunit_ch_host, - 'port' => phpunit_ch_port, - 'user' => phpunit_ch_user, - 'password' => phpunit_ch_password, - 'dbname' => phpunit_ch_dbname, - 'driverClass' => phpunit_ch_driver_class, - 'wrapperClass' => phpunit_ch_wrapper_class, + 'host' => phpunit_ch_host, + 'port' => phpunit_ch_port, + 'user' => phpunit_ch_user, + 'password' => phpunit_ch_password, + 'dbname' => phpunit_ch_dbname, + 'driverClass' => phpunit_ch_driver_class, + 'wrapperClass' => phpunit_ch_wrapper_class, 'driverOptions' => [ 'extremes' => false, 'readonly' => true, 'max_execution_time' => 30, 'enable_http_compression' => 0, - 'https' => false + 'https' => false, ], ]; } - return \Doctrine\DBAL\DriverManager::getConnection($params, new \Doctrine\DBAL\Configuration()); + + return DriverManager::getConnection($params, new Configuration()); } } diff --git a/tests/CreateSchemaTest.php b/tests/CreateSchemaTest.php index ebc358b..55cfaa0 100644 --- a/tests/CreateSchemaTest.php +++ b/tests/CreateSchemaTest.php @@ -1,4 +1,7 @@ ) @@ -11,9 +14,10 @@ namespace FOD\DBALClickHouse\Tests; -use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Schema\Index; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use FOD\DBALClickHouse\ClickHouseSchemaManager; use FOD\DBALClickHouse\Connection; use FOD\DBALClickHouse\Types\ArrayType; @@ -26,72 +30,76 @@ */ class CreateSchemaTest extends TestCase { - /** @var Connection */ - protected $connection; + private Connection $connection; - public function setUp() : void + public function setUp(): void { $this->connection = CreateConnectionTest::createConnection(); } - public function testGetSchemaManager() + public function testGetSchemaManager(): void { - $this->assertInstanceOf(ClickHouseSchemaManager::class, $this->connection->getSchemaManager()); + $this->assertInstanceOf(ClickHouseSchemaManager::class, $this->connection->createSchemaManager()); } - public function testCreateNewTableSQL() + public function testCreateNewTableSQL(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); $newTable->addColumn('id', 'integer', ['unsigned' => true]); $newTable->addColumn('payload', 'string'); - $newTable->addColumn('oneVal', Type::FLOAT); - $newTable->addColumn('twoVal', Type::DECIMAL); - $newTable->addColumn('flag', Type::BOOLEAN); - $newTable->addColumn('mask', Type::SMALLINT); + $newTable->addColumn('oneVal', Types::FLOAT); + $newTable->addColumn('twoVal', Types::DECIMAL); + $newTable->addColumn('flag', Types::BOOLEAN); + $newTable->addColumn('mask', Types::SMALLINT); $newTable->addColumn('hash', 'string', ['length' => 32, 'fixed' => true]); $newTable->setPrimaryKey(['id']); $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); - $this->assertEquals("CREATE TABLE test_table (EventDate Date DEFAULT today(), id UInt32, payload String, oneVal Float64, twoVal String, flag UInt8, mask Int16, hash FixedString(32)) ENGINE = ReplacingMergeTree(EventDate, (id), 8192)", - implode(';', $migrationSQLs)); + + $this->assertEquals( + "CREATE TABLE test_table (EventDate Date DEFAULT today(), id UInt32, payload String, oneVal Float64, twoVal String, flag UInt8, mask Int16, hash FixedString(32)) ENGINE = ReplacingMergeTree(EventDate, (id), 8192)", + implode(';', $migrationSQLs) + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); + + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testCreateDropTable() + public function testCreateDropTable(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); $newTable->addColumn('id', 'integer', ['unsigned' => true]); $newTable->addColumn('payload', 'string'); - $newTable->addColumn('oneVal', Type::FLOAT); - $newTable->addColumn('twoVal', Type::DECIMAL); - $newTable->addColumn('flag', Type::BOOLEAN); - $newTable->addColumn('mask', Type::SMALLINT); + $newTable->addColumn('oneVal', Types::FLOAT); + $newTable->addColumn('twoVal', Types::DECIMAL); + $newTable->addColumn('flag', Types::BOOLEAN); + $newTable->addColumn('mask', Types::SMALLINT); $newTable->addColumn('hash', 'string', ['length' => 32, 'fixed' => true]); $newTable->setPrimaryKey(['id']); foreach ($fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); - $this->expectException(DBALException::class); - $this->connection->exec('DROP TABLE test_table'); + $this->connection->executeStatement('DROP TABLE test_table'); + $this->expectException(Exception::class); + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testIndexGranularityOption() + public function testIndexGranularityOption(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); @@ -103,17 +111,22 @@ public function testIndexGranularityOption() $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); $generatedSQL = implode(';', $migrationSQLs); - $this->assertEquals("CREATE TABLE test_table (EventDate Date DEFAULT today(), id UInt32, payload String) ENGINE = ReplacingMergeTree(EventDate, (id), 4096)", - $generatedSQL); + + $this->assertEquals( + "CREATE TABLE test_table (EventDate Date DEFAULT today(), id UInt32, payload String) ENGINE = ReplacingMergeTree(EventDate, (id), 4096)", + $generatedSQL + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); + + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testEngineMergeOption() + public function testEngineMergeOption(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); @@ -125,17 +138,22 @@ public function testEngineMergeOption() $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); $generatedSQL = implode(';', $migrationSQLs); - $this->assertEquals("CREATE TABLE test_table (EventDate Date DEFAULT today(), id UInt32, payload String) ENGINE = MergeTree(EventDate, (id), 8192)", - $generatedSQL); + + $this->assertEquals( + "CREATE TABLE test_table (EventDate Date DEFAULT today(), id UInt32, payload String) ENGINE = MergeTree(EventDate, (id), 8192)", + $generatedSQL + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); + + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testEngineMemoryOption() + public function testEngineMemoryOption(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); @@ -147,16 +165,19 @@ public function testEngineMemoryOption() $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); $generatedSQL = implode(';', $migrationSQLs); + $this->assertEquals("CREATE TABLE test_table (id UInt32, payload String) ENGINE = Memory", $generatedSQL); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); + + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testEventDateColumnOption() + public function testEventDateColumnOption(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); @@ -169,103 +190,125 @@ public function testEventDateColumnOption() $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); $generatedSQL = implode(';', $migrationSQLs); - $this->assertEquals("CREATE TABLE test_table (event_date Date DEFAULT today(), id UInt32, payload String) ENGINE = ReplacingMergeTree(event_date, (id), 8192)", - $generatedSQL); + + $this->assertEquals( + "CREATE TABLE test_table (event_date Date DEFAULT today(), id UInt32, payload String) ENGINE = ReplacingMergeTree(event_date, (id), 8192)", + $generatedSQL + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); + + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testEventDateColumnBadOption() + public function testEventDateColumnBadOption(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); $newTable->addColumn('id', 'integer', ['unsigned' => true]); $newTable->addColumn('payload', 'string'); - $newTable->addColumn('event_date', Type::DATETIME, ['default' => 'toDate(now())']); + $newTable->addColumn('event_date', Types::DATETIME_MUTABLE, ['default' => 'toDate(now())']); $newTable->addOption('eventDateColumn', 'event_date'); $newTable->setPrimaryKey(['id']); $this->expectException(\Exception::class); $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); $generatedSQL = implode(';', $migrationSQLs); - $this->assertEquals("CREATE TABLE test_table (event_date Date DEFAULT today(), id UInt32, payload String) ENGINE = ReplacingMergeTree(event_date, (id), 8192)", - $generatedSQL); + + $this->assertEquals( + "CREATE TABLE test_table (event_date Date DEFAULT today(), id UInt32, payload String) ENGINE = ReplacingMergeTree(event_date, (id), 8192)", + $generatedSQL + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); + + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testEventDateProviderColumnOption() + public function testEventDateProviderColumnOption(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); $newTable->addColumn('id', 'integer', ['unsigned' => true]); $newTable->addColumn('payload', 'string'); - $newTable->addColumn('updated_at', Type::DATETIME); + $newTable->addColumn('updated_at', Types::DATETIME_MUTABLE); $newTable->addOption('eventDateProviderColumn', 'updated_at'); $newTable->setPrimaryKey(['id']); $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); $generatedSQL = implode(';', $migrationSQLs); - $this->assertEquals("CREATE TABLE test_table (EventDate Date DEFAULT toDate(updated_at), id UInt32, payload String, updated_at DateTime) ENGINE = ReplacingMergeTree(EventDate, (id), 8192)", - $generatedSQL); + + $this->assertEquals( + "CREATE TABLE test_table (EventDate Date DEFAULT toDate(updated_at), id UInt32, payload String, updated_at DateTime) ENGINE = ReplacingMergeTree(EventDate, (id), 8192)", + $generatedSQL + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); + + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testEventDateProviderColumnBadOption() + public function testEventDateProviderColumnBadOption(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table'); $newTable->addColumn('id', 'integer', ['unsigned' => true]); $newTable->addColumn('payload', 'string'); - $newTable->addColumn('flag', Type::BOOLEAN); + $newTable->addColumn('flag', Types::BOOLEAN); $newTable->addOption('eventDateProviderColumn', 'flag'); $newTable->setPrimaryKey(['id']); $this->expectException(\Exception::class); $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); $generatedSQL = implode(';', $migrationSQLs); - $this->assertEquals("CREATE TABLE test_table (EventDate Date DEFAULT toDate(updated_at), id UInt32, payload String, updated_at DateTime) ENGINE = ReplacingMergeTree(EventDate, (id), 8192)", - $generatedSQL); + + $this->assertEquals( + "CREATE TABLE test_table (EventDate Date DEFAULT toDate(updated_at), id UInt32, payload String, updated_at DateTime) ENGINE = ReplacingMergeTree(EventDate, (id), 8192)", + $generatedSQL + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec('DROP TABLE test_table'); + + $this->connection->executeStatement('DROP TABLE test_table'); } - public function testListTableIndexes() + public function testListTableIndexes(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_indexes_table'); $newTable->addColumn('id', 'integer', ['unsigned' => true]); $newTable->addColumn('payload', 'string'); - $newTable->addColumn('event_date', Type::DATE); + $newTable->addColumn('event_date', Types::DATE_MUTABLE); $newTable->addOption('eventDateColumn', 'event_date'); $newTable->setPrimaryKey(['id', 'event_date']); + $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $indexes = $this->connection->getSchemaManager()->listTableIndexes('test_indexes_table'); + $indexes = $this->connection->createSchemaManager()->listTableIndexes('test_indexes_table'); $this->assertEquals(1, \count($indexes)); @@ -276,31 +319,37 @@ public function testListTableIndexes() $this->assertTrue($index->isPrimary()); } - $this->connection->exec('DROP TABLE test_indexes_table'); + $this->connection->executeStatement('DROP TABLE test_indexes_table'); } - public function testTableWithSamplingExpression() + public function testTableWithSamplingExpression(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_sampling_table'); $newTable->addColumn('id', 'integer', ['unsigned' => true]); $newTable->addColumn('payload', 'string'); - $newTable->addColumn('event_date', Type::DATE); + $newTable->addColumn('event_date', Types::DATE_MUTABLE); $newTable->addOption('eventDateColumn', 'event_date'); $newTable->addOption('samplingExpression', 'intHash32(id)'); $newTable->setPrimaryKey(['id', 'event_date']); + $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); + $generatedSQL = implode(';', $migrationSQLs); - $this->assertEquals("CREATE TABLE test_sampling_table (event_date Date DEFAULT today(), id UInt32, payload String) ENGINE = ReplacingMergeTree(event_date, intHash32(id), (id, event_date, intHash32(id)), 8192)", - $generatedSQL); + + $this->assertEquals( + "CREATE TABLE test_sampling_table (event_date Date DEFAULT today(), id UInt32, payload String) ENGINE = ReplacingMergeTree(event_date, intHash32(id), (id, event_date, intHash32(id)), 8192)", + $generatedSQL + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $indexes = $this->connection->getSchemaManager()->listTableIndexes('test_sampling_table'); + $indexes = $this->connection->createSchemaManager()->listTableIndexes('test_sampling_table'); $this->assertEquals(1, \count($indexes)); @@ -311,18 +360,18 @@ public function testTableWithSamplingExpression() $this->assertTrue($index->isPrimary()); } - $this->connection->exec('DROP TABLE test_sampling_table'); + $this->connection->executeStatement('DROP TABLE test_sampling_table'); } - public function testNullableColumns() + public function testNullableColumns(): void { - $fromSchema = $this->connection->getSchemaManager()->createSchema(); - ArrayType::registerArrayTypes($this->connection->getDatabasePlatform()); - + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_table_nullable'); + ArrayType::registerArrayTypes($this->connection->getDatabasePlatform()); + $newTable->addColumn('id', 'integer', ['unsigned' => true, 'notnull' => false]); $newTable->addColumn('payload', 'string', ['notnull' => false]); $newTable->addColumn('price', 'float', ['notnull' => false]); @@ -332,56 +381,79 @@ public function testNullableColumns() $newTable->addOption('engine', 'Memory'); $migrationSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); + $generatedSQL = implode(';', $migrationSQLs); - $this->assertEquals("CREATE TABLE test_table_nullable (id UInt32, payload Nullable(String), price Nullable(Float64), transactions Array(Nullable(DateTime)), status Nullable(UInt8)) ENGINE = Memory", - $generatedSQL); + + $this->assertEquals( + "CREATE TABLE test_table_nullable (id UInt32, payload Nullable(String), price Nullable(Float64), transactions Array(Nullable(DateTime)), status Nullable(UInt8)) ENGINE = Memory", + $generatedSQL + ); + foreach ($migrationSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->insert('test_table_nullable', + + $this->connection->insert( + 'test_table_nullable', [ - 'id' => 1, - 'payload' => 's1', - 'price' => 1.5, + 'id' => 1, + 'payload' => 's1', + 'price' => 1.5, 'transactions' => [date('Y-m-d H:i:s'), null], - 'status' => null - ]); - $this->connection->insert('test_table_nullable', + 'status' => null, + ], + ['status' => ParameterType::NULL] + ); + $this->connection->insert( + 'test_table_nullable', [ - 'id' => 2, - 'payload' => 's2', - 'price' => 120, + 'id' => 2, + 'payload' => 's2', + 'price' => 120, 'transactions' => [null, null], - 'status' => false - ]); - $this->connection->insert('test_table_nullable', + 'status' => false, + ], + ['status' => ParameterType::BOOLEAN] + ); + $this->connection->insert( + 'test_table_nullable', [ - 'id' => 3, - 'payload' => null, - 'price' => 1000, + 'id' => 3, + 'payload' => null, + 'price' => 1000, 'transactions' => [date('Y-m-d H:i:s')], - 'status' => true - ]); - $this->connection->insert('test_table_nullable', + 'status' => true, + ], + ['status' => ParameterType::BOOLEAN] + ); + $this->connection->insert( + 'test_table_nullable', [ - 'id' => 4, - 'payload' => 's4', - 'price' => null, + 'id' => 4, + 'payload' => 's4', + 'price' => null, 'transactions' => [date('Y-m-d H:i:s'), date('Y-m-d H:i:s')], - 'status' => null - ]); - $this->connection->insert('test_table_nullable', + 'status' => null, + ], + ['status' => ParameterType::NULL] + ); + $this->connection->insert( + 'test_table_nullable', [ - 'id' => 5, - 'payload' => 's5', - 'price' => 100, + 'id' => 5, + 'payload' => 's5', + 'price' => 100, 'transactions' => [date('Y-m-d H:i:s')], - 'status' => true - ]); + 'status' => true, + ], + ['status' => ParameterType::BOOLEAN] + ); - $this->assertEquals(2, - (int)$this->connection->fetchColumn("SELECT count() from test_table_nullable WHERE {$this->connection->getDatabasePlatform()->getIsNullExpression('status')}")); + $this->assertEquals( + 2, + (int) $this->connection->fetchOne("SELECT count() from test_table_nullable WHERE {$this->connection->getDatabasePlatform()->getIsNullExpression('status')}") + ); - $this->connection->exec('DROP TABLE test_table_nullable'); + $this->connection->executeStatement('DROP TABLE test_table_nullable'); } } diff --git a/tests/DbalTypeTest.php b/tests/DbalTypeTest.php index 17d9fee..0c2945c 100644 --- a/tests/DbalTypeTest.php +++ b/tests/DbalTypeTest.php @@ -1,4 +1,7 @@ ) @@ -12,6 +15,7 @@ namespace FOD\DBALClickHouse\Tests; use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use FOD\DBALClickHouse\Connection; use PHPUnit\Framework\TestCase; @@ -22,188 +26,211 @@ */ class DbalTypeTest extends TestCase { - /** @var Connection */ - protected $connection; + private Connection$connection; - protected $schemaSQLs = []; + private array $schemaSQLs = []; - public function setUp() : void + public function setUp(): void { $this->connection = CreateConnectionTest::createConnection(); - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_dbal_type_table'); - $newTable->addColumn('typeArray', Type::TARRAY); - $newTable->addColumn('typeSimpleArray', Type::SIMPLE_ARRAY); - $newTable->addColumn('typeJsonArray', Type::JSON_ARRAY); - $newTable->addColumn('typeBigInt', Type::BIGINT); - $newTable->addColumn('typeBoolean', Type::BOOLEAN); - $newTable->addColumn('typeDateTime', Type::DATETIME); - $newTable->addColumn('typeDateTimeTZ', Type::DATETIMETZ); - $newTable->addColumn('typeDate', Type::DATE); - $newTable->addColumn('typeTime', Type::TIME); - $newTable->addColumn('typeDecimal', Type::DECIMAL); - $newTable->addColumn('typeInteger', Type::INTEGER); - $newTable->addColumn('typeObject', Type::OBJECT); - $newTable->addColumn('typeSmallInt', Type::SMALLINT); - $newTable->addColumn('typeString', Type::STRING); - $newTable->addColumn('typeText', Type::TEXT); - $newTable->addColumn('typeBinary', Type::BINARY); - $newTable->addColumn('typeBlob', Type::BLOB); - $newTable->addColumn('typeFloat', Type::FLOAT); - $newTable->addColumn('typeGUID', Type::GUID); + $newTable->addColumn('typeArray', Types::ARRAY); + $newTable->addColumn('typeSimpleArray', Types::SIMPLE_ARRAY); + $newTable->addColumn('typeJsonArray', Types::JSON); + $newTable->addColumn('typeBigInt', Types::BIGINT); + $newTable->addColumn('typeBoolean', Types::BOOLEAN); + $newTable->addColumn('typeDateTime', Types::DATETIME_MUTABLE); + $newTable->addColumn('typeDateTimeTZ', Types::DATETIMETZ_MUTABLE); + $newTable->addColumn('typeDate', Types::DATE_MUTABLE); + $newTable->addColumn('typeTime', Types::TIME_MUTABLE); + $newTable->addColumn('typeDecimal', Types::DECIMAL); + $newTable->addColumn('typeInteger', Types::INTEGER); + $newTable->addColumn('typeObject', Types::OBJECT); + $newTable->addColumn('typeSmallInt', Types::SMALLINT); + $newTable->addColumn('typeString', Types::STRING); + $newTable->addColumn('typeText', Types::TEXT); + $newTable->addColumn('typeBinary', Types::BINARY); + $newTable->addColumn('typeBlob', Types::BLOB); + $newTable->addColumn('typeFloat', Types::FLOAT); + $newTable->addColumn('typeGUID', Types::GUID); $newTable->addOption('engine', 'Memory'); $this->schemaSQLs = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); foreach ($this->schemaSQLs as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } } - public function tearDown() : void + public function tearDown(): void { - $this->connection->exec('DROP TABLE test_dbal_type_table'); + $this->connection->executeStatement('DROP TABLE test_dbal_type_table'); } - public function testCreateSchema() + public function testCreateSchema(): void { $this->assertEquals('CREATE TABLE test_dbal_type_table (typeArray String, typeSimpleArray String, typeJsonArray String, typeBigInt String, typeBoolean UInt8, typeDateTime DateTime, typeDateTimeTZ DateTime, typeDate Date, typeTime String, typeDecimal String, typeInteger Int32, typeObject String, typeSmallInt Int16, typeString String, typeText String, typeBinary String, typeBlob String, typeFloat Float64, typeGUID FixedString(36)) ENGINE = Memory', implode(';', $this->schemaSQLs)); } - public function testTypeArray() + public function testTypeArray(): void { - $this->connection->insert('test_dbal_type_table', ['typeArray' => ['v1' => 123, 'v2' => 234]], ['typeArray' => Type::TARRAY]); - $this->assertEquals(serialize(['v1' => 123, 'v2' => 234]), $this->connection->fetchColumn('SELECT typeArray FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeArray' => ['v1' => 123, 'v2' => 234]], ['typeArray' => Types::ARRAY]); + + $this->assertEquals(serialize(['v1' => 123, 'v2' => 234]), $this->connection->fetchOne('SELECT typeArray FROM test_dbal_type_table')); } - public function testTypeSimpleArray() + public function testTypeSimpleArray(): void { - $this->connection->insert('test_dbal_type_table', ['typeSimpleArray' => [123, 234]], ['typeSimpleArray' => Type::SIMPLE_ARRAY]); - $this->assertEquals(implode(',', [123, 234]), $this->connection->fetchColumn('SELECT typeSimpleArray FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeSimpleArray' => [123, 234]], ['typeSimpleArray' => Types::SIMPLE_ARRAY]); + + $this->assertEquals(implode(',', [123, 234]), $this->connection->fetchOne('SELECT typeSimpleArray FROM test_dbal_type_table')); } - public function testTypeJsonArray() + public function testTypeJsonArray(): void { - $this->connection->insert('test_dbal_type_table', ['typeJsonArray' => [123, 'foo' => 'bar']], ['typeJsonArray' => Type::JSON_ARRAY]); - $this->assertEquals(json_encode([123, 'foo' => 'bar']), $this->connection->fetchColumn('SELECT typeJsonArray FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeJsonArray' => [123, 'foo' => 'bar']], ['typeJsonArray' => Types::JSON]); + + $this->assertEquals(json_encode([123, 'foo' => 'bar']), $this->connection->fetchOne('SELECT typeJsonArray FROM test_dbal_type_table')); } - public function testTypeBigInt() + public function testTypeBigInt(): void { - $this->connection->insert('test_dbal_type_table', ['typeBigInt' => 123123123123], ['typeBigInt' => Type::BIGINT]); - $this->assertEquals('123123123123', $this->connection->fetchColumn('SELECT typeBigInt FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeBigInt' => 123123123123], ['typeBigInt' => Types::BIGINT]); + + $this->assertEquals('123123123123', $this->connection->fetchOne('SELECT typeBigInt FROM test_dbal_type_table')); } - public function testTypeBigIntReload() + public function testTypeBigIntReload(): void { - Type::overrideType(Type::BIGINT, 'FOD\DBALClickHouse\Types\BigIntType'); + Type::overrideType(Types::BIGINT, 'FOD\DBALClickHouse\Types\BigIntType'); + $this->connection = CreateConnectionTest::createConnection(); - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_dbal_type_bigint_table'); - $newTable->addColumn('typeBigInt', Type::BIGINT); + $newTable->addColumn('typeBigInt', Types::BIGINT); $newTable->addOption('engine', 'Memory'); foreach ($fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); + $this->assertEquals('CREATE TABLE test_dbal_type_bigint_table (typeBigInt Int64) ENGINE = Memory', $sql); } - $this->connection->insert('test_dbal_type_bigint_table', ['typeBigInt' => 123123123123], ['typeBigInt' => Type::BIGINT]); - $this->assertEquals(123123123123, $this->connection->fetchColumn('SELECT typeBigInt FROM test_dbal_type_bigint_table')); - $this->connection->exec('DROP TABLE test_dbal_type_bigint_table'); + $this->connection->insert('test_dbal_type_bigint_table', ['typeBigInt' => 123123123123], ['typeBigInt' => Types::BIGINT]); + + $this->assertEquals(123123123123, $this->connection->fetchOne('SELECT typeBigInt FROM test_dbal_type_bigint_table')); + + $this->connection->executeStatement('DROP TABLE test_dbal_type_bigint_table'); } - public function testTypeBoolean() + public function testTypeBoolean(): void { - $this->connection->insert('test_dbal_type_table', ['typeBoolean' => true], ['typeBoolean' => Type::BOOLEAN]); - $this->assertEquals(1, $this->connection->fetchColumn('SELECT typeBoolean FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeBoolean' => true], ['typeBoolean' => Types::BOOLEAN]); + + $this->assertEquals(1, $this->connection->fetchOne('SELECT typeBoolean FROM test_dbal_type_table')); } - public function testTypeDatetime() + public function testTypeDatetime(): void { - $this->connection->insert('test_dbal_type_table', ['typeDateTime' => new \DateTime('2000-05-05')], ['typeDateTime' => Type::DATETIME]); - $this->assertEquals('2000-05-05 00:00:00', $this->connection->fetchColumn('SELECT typeDateTime FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeDateTime' => new \DateTime('2000-05-05')], ['typeDateTime' => Types::DATETIME_MUTABLE]); + + $this->assertEquals('2000-05-05 00:00:00', $this->connection->fetchOne('SELECT typeDateTime FROM test_dbal_type_table')); } - public function testTypeDatetimeTZ() + public function testTypeDatetimeTZ(): void { - $this->connection->insert('test_dbal_type_table', ['typeDateTimeTZ' => new \DateTime('2000-05-05')], ['typeDateTimeTZ' => Type::DATETIMETZ]); - $this->assertEquals('2000-05-05 00:00:00', $this->connection->fetchColumn('SELECT typeDateTimeTZ FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeDateTimeTZ' => new \DateTime('2000-05-05')], ['typeDateTimeTZ' => Types::DATETIMETZ_MUTABLE]); + + $this->assertEquals('2000-05-05 00:00:00', $this->connection->fetchOne('SELECT typeDateTimeTZ FROM test_dbal_type_table')); } - public function testTypeDate() + public function testTypeDate(): void { - $this->connection->insert('test_dbal_type_table', ['typeDate' => new \DateTime('2000-05-05')], ['typeDate' => Type::DATE]); - $this->assertEquals('2000-05-05', $this->connection->fetchColumn('SELECT typeDate FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeDate' => new \DateTime('2000-05-05')], ['typeDate' => Types::DATE_MUTABLE]); + + $this->assertEquals('2000-05-05', $this->connection->fetchOne('SELECT typeDate FROM test_dbal_type_table')); } - public function testTypeTime() + public function testTypeTime(): void { - $this->connection->insert('test_dbal_type_table', ['typeTime' => new \DateTime('13:41:18')], ['typeTime' => Type::TIME]); - $this->assertEquals('13:41:18', $this->connection->fetchColumn('SELECT typeTime FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeTime' => new \DateTime('13:41:18')], ['typeTime' => Types::TIME_MUTABLE]); + + $this->assertEquals('13:41:18', $this->connection->fetchOne('SELECT typeTime FROM test_dbal_type_table')); } - public function testTypeDecimalFail() + public function testTypeDecimalFail(): void { - $this->connection->insert('test_dbal_type_table', ['typeDecimal' => 142.15], ['typeDecimal' => Type::DECIMAL]); - $this->assertEquals('142.15', $this->connection->fetchColumn('SELECT typeDecimal FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeDecimal' => 142.15], ['typeDecimal' => Types::DECIMAL]); + + $this->assertEquals('142.15', $this->connection->fetchOne('SELECT typeDecimal FROM test_dbal_type_table')); } - public function testTypeInteger() + public function testTypeInteger(): void { - $this->connection->insert('test_dbal_type_table', ['typeInteger' => 142], ['typeInteger' => Type::INTEGER]); - $this->assertEquals(142, $this->connection->fetchColumn('SELECT typeInteger FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeInteger' => 142], ['typeInteger' => Types::INTEGER]); + + $this->assertEquals(142, $this->connection->fetchOne('SELECT typeInteger FROM test_dbal_type_table')); } - public function testTypeObject() + public function testTypeObject(): void { - $this->connection->insert('test_dbal_type_table', ['typeObject' => (object)['foo' => 'bar']], ['typeObject' => Type::OBJECT]); - $this->assertEquals(serialize((object)['foo' => 'bar']), $this->connection->fetchColumn('SELECT typeObject FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeObject' => (object)['foo' => 'bar']], ['typeObject' => Types::OBJECT]); + + $this->assertEquals(serialize((object)['foo' => 'bar']), $this->connection->fetchOne('SELECT typeObject FROM test_dbal_type_table')); } - public function testTypeSmallInt() + public function testTypeSmallInt(): void { - $this->connection->insert('test_dbal_type_table', ['typeSmallInt' => 14], ['typeSmallInt' => Type::SMALLINT]); - $this->assertEquals(14, $this->connection->fetchColumn('SELECT typeSmallInt FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeSmallInt' => 14], ['typeSmallInt' => Types::SMALLINT]); + + $this->assertEquals(14, $this->connection->fetchOne('SELECT typeSmallInt FROM test_dbal_type_table')); } - public function testTypeString() + public function testTypeString(): void { - $this->connection->insert('test_dbal_type_table', ['typeString' => 'foo bar baz'], ['typeString' => Type::STRING]); - $this->assertEquals('foo bar baz', $this->connection->fetchColumn('SELECT typeString FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeString' => 'foo bar baz'], ['typeString' => Types::STRING]); + + $this->assertEquals('foo bar baz', $this->connection->fetchOne('SELECT typeString FROM test_dbal_type_table')); } - public function testTypeText() + public function testTypeText(): void { - $this->connection->insert('test_dbal_type_table', ['typeText' => 'foo bar baz'], ['typeText' => Type::TEXT]); - $this->assertEquals('foo bar baz', $this->connection->fetchColumn('SELECT typeText FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeText' => 'foo bar baz'], ['typeText' => Types::TEXT]); + + $this->assertEquals('foo bar baz', $this->connection->fetchOne('SELECT typeText FROM test_dbal_type_table')); } - public function testTypeBinary() + public function testTypeBinary(): void { - $this->connection->insert('test_dbal_type_table', ['typeBinary' => 1], ['typeBinary' => Type::BINARY]); - $this->assertEquals(1, $this->connection->fetchColumn('SELECT typeBinary FROM test_dbal_type_table')); + $this->connection->insert('test_dbal_type_table', ['typeBinary' => 1], ['typeBinary' => Types::BINARY]); + + $this->assertEquals(1, $this->connection->fetchOne('SELECT typeBinary FROM test_dbal_type_table')); } - public function testTypeBlob() + public function testTypeBlob(): void { - $val = md5(time()); - $this->connection->insert('test_dbal_type_table', ['typeBlob' => $val], ['typeBlob' => Type::BLOB]); - $this->assertEquals($val, $this->connection->fetchColumn('SELECT typeBlob FROM test_dbal_type_table')); + $val = md5((string) time()); + + $this->connection->insert('test_dbal_type_table', ['typeBlob' => $val], ['typeBlob' => Types::BLOB]); + + $this->assertEquals($val, $this->connection->fetchOne('SELECT typeBlob FROM test_dbal_type_table')); } - public function testTypeGUID() + public function testTypeGUID(): void { $val = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'; - $this->connection->insert('test_dbal_type_table', ['typeGUID' => $val], ['typeGUID' => Type::GUID]); - $this->assertEquals($val, $this->connection->fetchColumn('SELECT typeGUID FROM test_dbal_type_table')); + + $this->connection->insert('test_dbal_type_table', ['typeGUID' => $val], ['typeGUID' => Types::GUID]); + + $this->assertEquals($val, $this->connection->fetchOne('SELECT typeGUID FROM test_dbal_type_table')); } } diff --git a/tests/DriverTest.php b/tests/DriverTest.php index 521221b..363291a 100644 --- a/tests/DriverTest.php +++ b/tests/DriverTest.php @@ -1,4 +1,7 @@ ) @@ -24,40 +27,34 @@ */ class DriverTest extends TestCase { - /** @var Connection */ - protected $connection; + private Connection $connection; - public function setUp() : void + public function setUp(): void { $this->connection = CreateConnectionTest::createConnection(); } - public function testConnect() + public function testConnect(): void { - $this->assertInstanceOf(ClickHouseConnection::class, $this->connection->getDriver()->connect( - $this->connection->getParams(), - $this->connection->getUsername(), - $this->connection->getPassword() - )); + $this->assertInstanceOf( + ClickHouseConnection::class, + $this->connection->getDriver()->connect($this->connection->getParams()) + ); } - public function testGetDatabasePlatform() + public function testGetDatabasePlatform(): void { $this->assertInstanceOf(ClickHousePlatform::class, $this->connection->getDriver()->getDatabasePlatform()); } - public function testGetSchemaManager() - { - $this->assertInstanceOf(ClickHouseSchemaManager::class, $this->connection->getDriver()->getSchemaManager($this->connection)); - } - - public function testGetName() - { - $this->assertEquals('clickhouse', $this->connection->getDriver()->getName()); - } - - public function testGetDatabase() + public function testGetSchemaManager(): void { - $this->assertEquals(phpunit_ch_dbname, $this->connection->getDriver()->getDatabase($this->connection)); + $this->assertInstanceOf( + ClickHouseSchemaManager::class, + $this->connection->getDriver()->getSchemaManager( + $this->connection, + $this->connection->getDatabasePlatform() + ) + ); } } diff --git a/tests/InsertTest.php b/tests/InsertTest.php index 7184e0d..9fb5a67 100644 --- a/tests/InsertTest.php +++ b/tests/InsertTest.php @@ -1,4 +1,7 @@ ) @@ -11,6 +14,7 @@ namespace FOD\DBALClickHouse\Tests; +use Doctrine\DBAL\ParameterType; use FOD\DBALClickHouse\Connection; use PHPUnit\Framework\TestCase; @@ -21,14 +25,13 @@ */ class InsertTest extends TestCase { - /** @var Connection */ - protected $connection; + private Connection $connection; public function setUp() : void { $this->connection = CreateConnectionTest::createConnection(); - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_insert_table'); @@ -39,59 +42,78 @@ public function setUp() : void $newTable->setPrimaryKey(['id']); foreach ($fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } } - public function tearDown() : void + public function tearDown(): void { - $this->connection->exec('DROP TABLE test_insert_table'); + $this->connection->executeStatement('DROP TABLE test_insert_table'); } - public function testExecInsert() + public function testExecInsert(): void { - $this->connection->exec("INSERT INTO test_insert_table(id, payload) VALUES (1, 'v1'), (2, 'v2')"); - $this->assertEquals([['payload' => 'v1'], ['payload' => 'v2']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (1, 2) ORDER BY id")); + $this->connection->executeStatement("INSERT INTO test_insert_table(id, payload) VALUES (1, 'v1'), (2, 'v2')"); + + $this->assertEquals( + [['payload' => 'v1'], ['payload' => 'v2']], + $this->connection->fetchAllAssociative("SELECT payload from test_insert_table WHERE id IN (1, 2) ORDER BY id") + ); } - public function testFunctionInsert() + public function testFunctionInsert(): void { $this->connection->insert('test_insert_table', ['id' => 3, 'payload' => 'v3']); - $this->connection->insert('test_insert_table', ['id' => 4, 'payload' => 'v4'], ['id' => \PDO::PARAM_INT, 'payload' => \PDO::PARAM_STR]); - $this->assertEquals([['payload' => 'v3'], ['payload' => 'v4']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (3, 4) ORDER BY id")); + $this->connection->insert('test_insert_table', ['id' => 4, 'payload' => 'v4'], ['id' => ParameterType::INTEGER, 'payload' => ParameterType::STRING]); + + $this->assertEquals( + [['payload' => 'v3'], ['payload' => 'v4']], + $this->connection->fetchAllAssociative("SELECT payload from test_insert_table WHERE id IN (3, 4) ORDER BY id") + ); } - public function testInsertViaQueryBuilder() + public function testInsertViaQueryBuilder(): void { - $qb = $this->connection->createQueryBuilder(); + $queryBuilder = $this->connection->createQueryBuilder(); - $qb + $queryBuilder ->insert('test_insert_table') ->setValue('id', ':id') ->setValue('payload', ':payload') - ->setParameter('id', 5, \PDO::PARAM_INT) + ->setParameter('id', 5, ParameterType::INTEGER) ->setParameter('payload', 'v5') - ->execute(); + ->executeStatement(); - $qb + $queryBuilder ->setParameter('id', 6) ->setParameter('payload', 'v6') - ->execute(); + ->executeStatement(); - $this->assertEquals([['payload' => 'v5'], ['payload' => 'v6']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (5, 6) ORDER BY id")); + $this->assertEquals( + [['payload' => 'v5'], ['payload' => 'v6']], + $this->connection->fetchAllAssociative("SELECT payload from test_insert_table WHERE id IN (5, 6) ORDER BY id") + ); } - public function testStatementInsertWithoutKeyName() + public function testStatementInsertWithoutKeyName(): void { $statement = $this->connection->prepare('INSERT INTO test_insert_table(id, payload) VALUES (?, ?), (?, ?)'); - $statement->execute([7, 'v?7', 8, 'v8']); - $this->assertEquals([['payload' => 'v?7'], ['payload' => 'v8']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (7, 8) ORDER BY id")); + $statement->executeStatement([7, 'v?7', 8, 'v8']); + + $this->assertEquals( + [['payload' => 'v?7'], ['payload' => 'v8']], + $this->connection->fetchAllAssociative("SELECT payload from test_insert_table WHERE id IN (7, 8) ORDER BY id") + ); } - public function testStatementInsertWithKeyName() + public function testStatementInsertWithKeyName(): void { $statement = $this->connection->prepare('INSERT INTO test_insert_table(id, payload) VALUES (:v0, :v1), (:v2, :v3)'); - $statement->execute(['v0' => 9, 'v1' => 'v?9', 'v2' => 10, 'v3' => 'v10']); - $this->assertEquals([['payload' => 'v?9'], ['payload' => 'v10']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (9, 10) ORDER BY id")); + $statement->executeStatement(['v0' => 9, 'v1' => 'v?9', 'v2' => 10, 'v3' => 'v10']); + + $this->assertEquals( + [['payload' => 'v?9'], ['payload' => 'v10']], + $this->connection->fetchAllAssociative("SELECT payload from test_insert_table WHERE id IN (9, 10) ORDER BY id") + ); } } diff --git a/tests/SelectTest.php b/tests/SelectTest.php index 45c4ffb..80da456 100644 --- a/tests/SelectTest.php +++ b/tests/SelectTest.php @@ -1,4 +1,7 @@ ) @@ -11,7 +14,7 @@ namespace FOD\DBALClickHouse\Tests; -use Doctrine\DBAL\FetchMode; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\TrimMode; use FOD\DBALClickHouse\Connection; use PHPUnit\Framework\TestCase; @@ -23,14 +26,13 @@ */ class SelectTest extends TestCase { - /** @var Connection */ - protected $connection; + private Connection $connection; - public function setUp() : void + public function setUp(): void { $this->connection = CreateConnectionTest::createConnection(); - $fromSchema = $this->connection->getSchemaManager()->createSchema(); + $fromSchema = $this->connection->createSchemaManager()->introspectSchema(); $toSchema = clone $fromSchema; $newTable = $toSchema->createTable('test_select_table'); @@ -42,62 +44,53 @@ public function setUp() : void $newTable->setPrimaryKey(['id']); foreach ($fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); + $this->connection->executeStatement($sql); } - $this->connection->exec("INSERT INTO test_select_table(id, payload, hits) VALUES (1, 'v1', 101), (2, 'v2', 202), (3, 'v3', 303), (4, 'v4', 404), (5, 'v4', 505), (6, ' t1 ', 606), (7, 'aat2aaa', 707)"); + $this->connection->executeStatement("INSERT INTO test_select_table(id, payload, hits) VALUES (1, 'v1', 101), (2, 'v2', 202), (3, 'v3', 303), (4, 'v4', 404), (5, 'v4', 505), (6, ' t1 ', 606), (7, 'aat2aaa', 707)"); } - public function tearDown() : void + public function tearDown(): void { - $this->connection->exec('DROP TABLE test_select_table'); + $this->connection->executeStatement('DROP TABLE test_select_table'); } - public function testFetchBothSelect() + public function testFetchBothSelect(): void { $results = []; - $stmt = $this->connection->query('SELECT * FROM test_select_table WHERE id = 3'); - while ($result = $stmt->fetch()) { - $results[] = $result; + + $result = $this->connection->executeQuery('SELECT * FROM test_select_table WHERE id = 3'); + + while ($row = $result->fetchAssociative()) { + $results[] = $row; } + $this->assertEquals([['id' => 3, 'payload' => 'v3', 'hits' => 303]], $results); } - public function testFetchAssocSelect() + public function testFetchAssocSelect(): void { $results = []; - $stmt = $this->connection->query('SELECT id, hits FROM test_select_table WHERE id IN (3, 4)'); - while ($result = $stmt->fetch(FetchMode::ASSOCIATIVE)) { - $results[] = $result; + + $result = $this->connection->executeQuery('SELECT id, hits FROM test_select_table WHERE id IN (3, 4)'); + + while ($row = $result->fetchAssociative()) { + $results[] = $row; } - $this->assertEquals([['id' => 3, 'hits' => 303], ['id' => 4, 'hits' => 404]], $results); - } - public function testFetchNumSelect() - { - $stmt = $this->connection->query('SELECT MAX(hits) as maxHits FROM test_select_table'); - $result = $stmt->fetch(FetchMode::ASSOCIATIVE); - $this->assertEquals(['maxHits' => 707], $result); + $this->assertEquals([['id' => 3, 'hits' => 303], ['id' => 4, 'hits' => 404]], $results); } - public function testFetchObjSelect() + public function testFetchNumSelect():void { - $stmt = $this->connection->query('SELECT MAX(hits) as maxHits FROM test_select_table'); - $result = $stmt->fetch(FetchMode::STANDARD_OBJECT); - $this->assertEquals((object)['maxHits' => 707], $result); - } + $result = $this->connection->executeQuery('SELECT MAX(hits) as maxHits FROM test_select_table'); - public function testFetchKeyPairSelect() - { - $stmt = $this->connection->query("SELECT id, hits FROM test_select_table WHERE id = 2"); - $result = $stmt->fetch(\PDO::FETCH_KEY_PAIR); - $this->assertEquals([2 => 202], $result); + $this->assertEquals(['maxHits' => 707], $result->fetchAssociative()); } - public function testFetchAllBothSelect() + public function testFetchAllBothSelect(): void { - $stmt = $this->connection->query("SELECT * FROM test_select_table WHERE id IN (1, 3)"); - $result = $stmt->fetchAll(); + $result = $this->connection->executeQuery("SELECT * FROM test_select_table WHERE id IN (1, 3)"); $this->assertEquals([ [ @@ -110,85 +103,55 @@ public function testFetchAllBothSelect() 'payload' => 'v3', 'hits' => 303, ] - ], $result); - } - - public function testFetchAllNumSelect() - { - $stmt = $this->connection->query("SELECT AVG(hits) FROM test_select_table"); - $result = $stmt->fetchAll(FetchMode::NUMERIC); - - $this->assertEquals([[404]], $result); + ], $result->fetchAllAssociative()); } - public function testFetchAllObjSelect() + public function testFetchAllNumSelect(): void { - $stmt = $this->connection->query("SELECT * FROM test_select_table WHERE id IN (2, 4)"); - $result = $stmt->fetchAll(FetchMode::STANDARD_OBJECT); + $result = $this->connection->executeQuery("SELECT AVG(hits) FROM test_select_table"); - $this->assertEquals([ - (object)[ - 'id' => 2, - 'payload' => 'v2', - 'hits' => 202, - ], - (object)[ - 'id' => 4, - 'payload' => 'v4', - 'hits' => 404, - ] - ], $result); + $this->assertEquals([[404]], $result->fetchAllNumeric()); } - public function testFetchAllKeyPairSelect() + public function testFetchColumnValidOffsetSelect(): void { - $stmt = $this->connection->query("SELECT payload, hits FROM test_select_table WHERE id IN (2, 4) ORDER BY id"); - $result = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); + $results = []; - $this->assertEquals([ - [ - 'v2' => 202, - ], - [ - 'v4' => 404, - ] - ], $result); - } + $result = $this->connection->executeQuery("SELECT payload, hits FROM test_select_table WHERE id > 1 ORDER BY id LIMIT 3"); - public function testFetchColumnValidOffsetSelect() - { - $stmt = $this->connection->query("SELECT payload, hits FROM test_select_table WHERE id > 1 ORDER BY id LIMIT 3"); - $results = []; - while ($result = $stmt->fetchColumn(1)) { - $results[] = $result; + while ($row = $result->fetchNumeric()) { + $results[] = $row[1]; } $this->assertEquals([202, 303, 404], $results); } - public function testFetchColumnInvalidOffsetSelect() + public function testFetchColumnInvalidOffsetSelect(): void { - $stmt = $this->connection->query("SELECT payload, hits FROM test_select_table WHERE id > 1 ORDER BY id"); $results = []; - while ($result = $stmt->fetchColumn(2)) { - $results[] = $result; + + $result = $this->connection->executeQuery("SELECT payload, hits FROM test_select_table WHERE id > 1 ORDER BY id"); + + while ($row = $result->fetchOne()) { + $results[] = $row; } + $this->assertEquals(['v2', 'v3', 'v4', 'v4', ' t1 ', 'aat2aaa'], $results); } - public function testQueryBuilderSelect() + public function testQueryBuilderSelect(): void { - $qb = $this->connection->createQueryBuilder(); - $result = $qb + $queryBuilder = $this->connection->createQueryBuilder(); + + $result = $queryBuilder ->select('payload, uniq(hits) as uniques') ->from('test_select_table') ->where('id > :id') - ->setParameter('id', 2, \PDO::PARAM_INT) + ->setParameter('id', 2, ParameterType::INTEGER) ->groupBy('payload') ->orderBy('payload') ->setMaxResults(2) - ->execute() - ->fetchAll(); + ->fetchAllAssociative(); $this->assertEquals([ [ @@ -202,12 +165,13 @@ public function testQueryBuilderSelect() ], $result); } - public function testDynamicParametersSelect() + public function testDynamicParametersSelect(): void { - $stmt = $this->connection->prepare('SELECT payload, AVG(hits) AS avg_hits FROM test_select_table WHERE id > :id GROUP BY payload ORDER BY payload'); - - $stmt->bindValue('id', 3, 'integer'); - $stmt->execute(); + $result = $this->connection->executeQuery( + 'SELECT payload, AVG(hits) AS avg_hits FROM test_select_table WHERE id > :id GROUP BY payload ORDER BY payload', + ['id' => 3], + ['id' => ParameterType::INTEGER] + ); $this->assertEquals([ [ @@ -222,67 +186,61 @@ public function testDynamicParametersSelect() 'payload' => 'v4', 'avg_hits' => 454.5, ], - ], $stmt->fetchAll()); + ], $result->fetchAllAssociative()); } - public function testColumnCount() + public function testColumnCount(): void { - $stmt = $this->connection->prepare('SELECT * FROM test_select_table'); - $stmt->execute(); + $result = $this->connection->executeQuery('SELECT * FROM test_select_table'); - $this->assertEquals(3, $stmt->columnCount()); + $this->assertEquals(3, $result->columnCount()); } - public function testTrim() + public function testTrim(): void { - $stmt = $this->connection->prepare( + $result = $this->connection->executeQuery( sprintf( 'SELECT %s FROM test_select_table WHERE id = 6', $this->connection->getDatabasePlatform()->getTrimExpression('payload') ) ); - $stmt->execute(); - $this->assertEquals('t1', $stmt->fetchColumn()); + $this->assertEquals('t1', $result->fetchOne()); } - public function testTrimLeft() + public function testTrimLeft(): void { - $stmt = $this->connection->prepare( + $result = $this->connection->executeQuery( sprintf( 'SELECT %s FROM test_select_table WHERE id = 6', $this->connection->getDatabasePlatform()->getTrimExpression('payload', TrimMode::LEADING) ) ); - $stmt->execute(); - $this->assertEquals('t1 ', $stmt->fetchColumn()); + $this->assertEquals('t1 ', $result->fetchOne()); } - public function testTrimRight() + public function testTrimRight(): void { - $stmt = $this->connection->prepare( + $result = $this->connection->executeQuery( sprintf( 'SELECT %s FROM test_select_table WHERE id = 6', $this->connection->getDatabasePlatform()->getTrimExpression('payload', TrimMode::TRAILING) ) ); - $stmt->execute(); - $this->assertEquals(' t1', $stmt->fetchColumn()); + $this->assertEquals(' t1', $result->fetchOne()); } - public function testTrimChar() + public function testTrimChar(): void { - $stmt = $this->connection->prepare( + $result = $this->connection->executeQuery( sprintf( 'SELECT %s FROM test_select_table WHERE id = 7', $this->connection->getDatabasePlatform()->getTrimExpression('payload', TrimMode::UNSPECIFIED, 'a') ) ); - $stmt->execute(); - $this->assertEquals('t2', $stmt->fetchColumn()); + $this->assertEquals('t2', $result->fetchOne()); } } -