From 923b5362f253945676f1e7bb930da7fb738de138 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Wed, 17 Jul 2019 12:28:39 +0300 Subject: [PATCH 01/38] move register extensions to connection --- src/PostgresConnection.php | 40 +++++++++++++++++++++++++++++++ src/UmbrellioPostgresProvider.php | 38 +++-------------------------- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/PostgresConnection.php b/src/PostgresConnection.php index 60d1697..d282375 100644 --- a/src/PostgresConnection.php +++ b/src/PostgresConnection.php @@ -6,6 +6,8 @@ use Illuminate\Database\PostgresConnection as BasePostgresConnection; use Illuminate\Support\Traits\Macroable; +use Umbrellio\Postgres\Extensions\AbstractExtension; +use Umbrellio\Postgres\Extensions\Exceptions\ExtensionInvalidException; use Umbrellio\Postgres\Schema\Builder; use Umbrellio\Postgres\Schema\Grammars\PostgresGrammar; @@ -13,6 +15,38 @@ class PostgresConnection extends BasePostgresConnection { use Macroable; + private static $extensions = []; + + /** + * @param AbstractExtension|string $extension + * @codeCoverageIgnore + */ + final public static function registerExtension(string $extension): void + { + if (!is_subclass_of($extension, AbstractExtension::class)) { + throw new ExtensionInvalidException(sprintf( + 'Class %s must be implemented from %s', + $extension, + AbstractExtension::class + )); + } + self::$extensions[$extension::getName()] = $extension; + } + + /** + * @codeCoverageIgnore + */ + final private function registerExtensions(): void + { + collect(self::$extensions)->each(function ($extension, $key) { + /** @var AbstractExtension $extension */ + $extension::register(); + foreach ($extension::getTypes() as $type => $typeClass) { + $this->getSchemaBuilder()->registerCustomDoctrineType($typeClass, $type, $type); + } + }); + } + public function getSchemaBuilder() { if ($this->schemaGrammar === null) { @@ -25,4 +59,10 @@ protected function getDefaultSchemaGrammar() { return $this->withTablePrefix(new PostgresGrammar()); } + + public function useDefaultPostProcessor() + { + parent::useDefaultPostProcessor(); + $this->registerExtensions(); + } } diff --git a/src/UmbrellioPostgresProvider.php b/src/UmbrellioPostgresProvider.php index fcfc6e5..942edf0 100644 --- a/src/UmbrellioPostgresProvider.php +++ b/src/UmbrellioPostgresProvider.php @@ -4,6 +4,7 @@ namespace Umbrellio\Postgres; +use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\DatabaseManager; use Illuminate\Database\DatabaseServiceProvider; use Illuminate\Support\Facades\DB; @@ -13,30 +14,9 @@ class UmbrellioPostgresProvider extends DatabaseServiceProvider { - private static $extensions = []; - /** - * @param AbstractExtension|string $extension * @codeCoverageIgnore */ - final public static function registerExtension(string $extension): void - { - if (!is_subclass_of($extension, AbstractExtension::class)) { - throw new ExtensionInvalidException(sprintf( - 'Class %s must be implemented from %s', - $extension, - AbstractExtension::class - )); - } - self::$extensions[$extension::getName()] = $extension; - } - - public function boot() - { - parent::boot(); - self::registerExtensions(); - } - protected function registerConnectionServices(): void { $this->app->singleton('db.factory', function ($app) { @@ -46,21 +26,9 @@ protected function registerConnectionServices(): void $this->app->singleton('db', function ($app) { return new DatabaseManager($app, $app['db.factory']); }); - } - /** - * @codeCoverageIgnore - */ - final private static function registerExtensions(): void - { - /** @var PostgresConnection $connection */ - $connection = DB::connection(); - collect(self::$extensions)->each(function ($extension, $key) use ($connection) { - /** @var AbstractExtension $extension */ - $extension::register(); - foreach ($extension::getTypes() as $type => $typeClass) { - $connection->getSchemaBuilder()->registerCustomDoctrineType($typeClass, $type, $type); - } + $this->app->bind('db.connection', function ($app) { + return $app['db']->connection(); }); } } From 4e4497181b6e1068dacaeb8b1e8ed031d68c5124 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Wed, 17 Jul 2019 12:30:33 +0300 Subject: [PATCH 02/38] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2929c9..0712d0f 100644 --- a/README.md +++ b/README.md @@ -138,13 +138,13 @@ class SomeExtension extends AbstractExtension ```php use Illuminate\Support\ServiceProvider; -use Umbrellio\Postgres\UmbrellioPostgresProvider; +use Umbrellio\Postgres\PostgresConnection; class SomeServiceProvider extends ServiceProvider { public function register(): void { - UmbrellioPostgresProvider::registerExtension(SomeExtension::class); + PostgresConnection::registerExtension(SomeExtension::class); } } ``` From aa55e16e59ccdb036bc2625f3efd77a4e1ee495f Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Wed, 17 Jul 2019 12:32:16 +0300 Subject: [PATCH 03/38] linter fixes --- src/PostgresConnection.php | 36 +++++++++++++++---------------- src/UmbrellioPostgresProvider.php | 4 ---- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/PostgresConnection.php b/src/PostgresConnection.php index d282375..d1818d1 100644 --- a/src/PostgresConnection.php +++ b/src/PostgresConnection.php @@ -33,20 +33,6 @@ final public static function registerExtension(string $extension): void self::$extensions[$extension::getName()] = $extension; } - /** - * @codeCoverageIgnore - */ - final private function registerExtensions(): void - { - collect(self::$extensions)->each(function ($extension, $key) { - /** @var AbstractExtension $extension */ - $extension::register(); - foreach ($extension::getTypes() as $type => $typeClass) { - $this->getSchemaBuilder()->registerCustomDoctrineType($typeClass, $type, $type); - } - }); - } - public function getSchemaBuilder() { if ($this->schemaGrammar === null) { @@ -55,14 +41,28 @@ public function getSchemaBuilder() return new Builder($this); } + public function useDefaultPostProcessor() + { + parent::useDefaultPostProcessor(); + $this->registerExtensions(); + } + protected function getDefaultSchemaGrammar() { return $this->withTablePrefix(new PostgresGrammar()); } - - public function useDefaultPostProcessor() + + /** + * @codeCoverageIgnore + */ + final private function registerExtensions(): void { - parent::useDefaultPostProcessor(); - $this->registerExtensions(); + collect(self::$extensions)->each(function ($extension, $key) { + /** @var AbstractExtension $extension */ + $extension::register(); + foreach ($extension::getTypes() as $type => $typeClass) { + $this->getSchemaBuilder()->registerCustomDoctrineType($typeClass, $type, $type); + } + }); } } diff --git a/src/UmbrellioPostgresProvider.php b/src/UmbrellioPostgresProvider.php index 942edf0..2da00c4 100644 --- a/src/UmbrellioPostgresProvider.php +++ b/src/UmbrellioPostgresProvider.php @@ -4,13 +4,9 @@ namespace Umbrellio\Postgres; -use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\DatabaseManager; use Illuminate\Database\DatabaseServiceProvider; -use Illuminate\Support\Facades\DB; use Umbrellio\Postgres\Connectors\ConnectionFactory; -use Umbrellio\Postgres\Extensions\AbstractExtension; -use Umbrellio\Postgres\Extensions\Exceptions\ExtensionInvalidException; class UmbrellioPostgresProvider extends DatabaseServiceProvider { From 910b4059aeba7fbbd4e4bd7ed698896f3ae7e681 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Wed, 17 Jul 2019 12:39:46 +0300 Subject: [PATCH 04/38] tests fixes --- tests/Unit/Schema/Grammars/GrammarTest.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/Unit/Schema/Grammars/GrammarTest.php b/tests/Unit/Schema/Grammars/GrammarTest.php index 8ba667e..808628b 100644 --- a/tests/Unit/Schema/Grammars/GrammarTest.php +++ b/tests/Unit/Schema/Grammars/GrammarTest.php @@ -17,7 +17,7 @@ public function addingGinIndex() { $blueprint = new Blueprint('test'); $blueprint->gin('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql($this->getConnectionMock(), $this->getGrammar()); $this->assertCount(1, $statements); $this->assertStringContainsString('CREATE INDEX', $statements[0]); $this->assertStringContainsString('GIN("foo")', $statements[0]); @@ -28,16 +28,13 @@ public function addingGistIndex() { $blueprint = new Blueprint('test'); $blueprint->gist('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql($this->getConnectionMock(), $this->getGrammar()); $this->assertCount(1, $statements); $this->assertStringContainsString('CREATE INDEX', $statements[0]); $this->assertStringContainsString('GIST("foo")', $statements[0]); } - /** - * @return PostgresConnection - */ - protected function getConnection() + protected function getConnectionMock() { return Mockery::mock(PostgresConnection::class); } From bc267a8609a26fdd441fc628e512d5aee1585e06 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Thu, 18 Jul 2019 22:41:12 +0300 Subject: [PATCH 05/38] doctrine-refactor --- .gitignore | 1 + .travis.yml | 106 +- composer.json | 5 +- composer.lock | 235 ++- ecs.yml | 1 + phpunit.travis.xml | 39 + src/.meta.php | 7 + src/Connectors/ConnectionFactory.php | 5 +- .../Schema/Drivers/AbstractDoctrineDriver.php | 13 + src/PostgresConnection.php | 6 + src/Schema/Drivers/DoctrineDriver.php | 50 + .../Drivers/Platforms/SQL100Platform.php | 30 + .../Drivers/Platforms/SQL91Platform.php | 45 + .../Drivers/Platforms/SQL92Platform.php | 68 + .../Drivers/Platforms/SQL94Platform.php | 41 + src/Schema/Drivers/Platforms/SQLPlatform.php | 11 + .../Traits/AlterTableSQLDeclarations.php | 261 +++ src/Schema/SQLSchemaManager.php | 13 + tests/Functional/DBAL/BlobTest.php | 164 ++ tests/Functional/DBAL/ConnectionTest.php | 327 ++++ tests/Functional/DBAL/DataAccessTest.php | 972 ++++++++++ .../DBAL/Driver/AbstractDriverTest.php | 69 + .../DBAL/Driver/PDOConnectionTest.php | 71 + .../DBAL/Driver/PDOPgSql/DriverTest.php | 79 + .../DBAL/Driver/PDOPgsqlConnectionTest.php | 61 + tests/Functional/DBAL/ExceptionTest.php | 426 +++++ .../DBAL/LikeWildcardsEscapingTest.php | 28 + tests/Functional/DBAL/LoggingTest.php | 55 + .../DBAL/MasterSlaveConnectionTest.php | 241 +++ .../Functional/DBAL/ModifyLimitQueryTest.php | 185 ++ tests/Functional/DBAL/NamedParametersTest.php | 224 +++ tests/Functional/DBAL/PDOStatementTest.php | 54 + .../DBAL/Platform/DateExpressionTest.php | 55 + .../DBAL/Platform/DefaultExpressionTest.php | 60 + .../Functional/DBAL/Platform/QuotingTest.php | 32 + tests/Functional/DBAL/PortabilityTest.php | 177 ++ tests/Functional/DBAL/ResultCacheTest.php | 251 +++ .../Functional/DBAL/Schema/ComparatorTest.php | 55 + .../DBAL/Schema/Db2SchemaManagerTest.php | 34 + .../DBAL/Schema/DefaultValueTest.php | 155 ++ .../DBAL/Schema/DrizzleSchemaManagerTest.php | 48 + .../DBAL/Schema/MySqlSchemaManagerTest.php | 567 ++++++ .../DBAL/Schema/OracleSchemaManagerTest.php | 288 +++ .../Schema/PostgreSqlSchemaManagerTest.php | 536 ++++++ .../Schema/SQLAnywhereSchemaManagerTest.php | 64 + .../Schema/SQLServerSchemaManagerTest.php | 347 ++++ .../SchemaManagerFunctionalTestCase.php | 1597 +++++++++++++++++ .../DBAL/Schema/SqliteSchemaManagerTest.php | 281 +++ tests/Functional/DBAL/StatementTest.php | 322 ++++ tests/Functional/DBAL/TableGeneratorTest.php | 62 + tests/Functional/DBAL/TemporaryTableTest.php | 108 ++ tests/Functional/DBAL/Ticket/DBAL168Test.php | 30 + tests/Functional/DBAL/Ticket/DBAL202Test.php | 51 + tests/Functional/DBAL/Ticket/DBAL421Test.php | 56 + tests/Functional/DBAL/Ticket/DBAL461Test.php | 41 + tests/Functional/DBAL/Ticket/DBAL510Test.php | 40 + tests/Functional/DBAL/Ticket/DBAL630Test.php | 172 ++ tests/Functional/DBAL/Ticket/DBAL752Test.php | 65 + tests/Functional/DBAL/TransactionTest.php | 39 + tests/Functional/DBAL/TypeConversionTest.php | 251 +++ tests/Functional/DBAL/Types/BinaryTest.php | 95 + tests/Functional/DBAL/WriteTest.php | 359 ++++ tests/Functional/DbalFunctionalTestCase.php | 113 ++ tests/Functional/FunctionalTestCase.php | 31 - tests/Functional/Schema/ChangeTest.php | 76 + .../Functional/{ => Schema}/HasIndexTest.php | 6 +- tests/Functional/{ => Schema}/SchemaTest.php | 6 +- .../{ => Schema}/UniqueIndexTest.php | 6 +- tests/Functional/TestUtil.php | 181 ++ tests/FunctionalTestCase.php | 36 + tests/TestCase.php | 14 + tests/Unit/DBAL/ConfigurationTest.php | 53 + tests/Unit/DBAL/ConnectionTest.php | 971 ++++++++++ tests/Unit/DBAL/DBALExceptionTest.php | 88 + tests/Unit/DBAL/Driver/AbstractDriverTest.php | 239 +++ .../Driver/AbstractPostgreSQLDriverTest.php | 125 ++ .../Unit/DBAL/Driver/PDOPgSql/DriverTest.php | 120 ++ tests/Unit/DBAL/DriverManagerTest.php | 542 ++++++ .../Platforms/AbstractPlatformTestCase.php | 1542 ++++++++++++++++ .../AbstractPostgreSqlPlatformTestCase.php | 1068 +++++++++++ .../Platforms/PostgreSQL100PlatformTest.php | 34 + .../Platforms/PostgreSQL91PlatformTest.php | 41 + .../Platforms/PostgreSQL92PlatformTest.php | 72 + .../Platforms/PostgreSQL94PlatformTest.php | 32 + .../DBAL/Platforms/PostgreSqlPlatformTest.php | 19 + .../ReservedKeywordsValidatorTest.php | 43 + tests/Unit/DBAL/SQLParserUtilsTest.php | 498 +++++ tests/Unit/DBAL/Schema/ColumnDiffTest.php | 30 + tests/Unit/DBAL/Schema/ColumnTest.php | 146 ++ tests/Unit/DBAL/Schema/ComparatorTest.php | 1329 ++++++++++++++ .../DBAL/Schema/ForeignKeyConstraintTest.php | 59 + tests/Unit/DBAL/Schema/IndexTest.php | 191 ++ tests/Unit/DBAL/Schema/SchemaDiffTest.php | 124 ++ tests/Unit/DBAL/Schema/SchemaTest.php | 465 +++++ tests/Unit/DBAL/Schema/SequenceTest.php | 47 + tests/Unit/DBAL/Schema/TableDiffTest.php | 65 + tests/Unit/DBAL/Schema/TableTest.php | 888 +++++++++ tests/Unit/DBAL/StatementTest.php | 153 ++ tests/Unit/DBAL/UtilTest.php | 81 + tests/Unit/Types/CommentedType.php | 34 + tests/Unit/Types/MySqlPointType.php | 34 + tests/travis/install-postgres-10.sh | 13 + tests/travis/install-postgres-11.sh | 12 + tests/travis/pgsql.travis.xml | 49 + 104 files changed, 19689 insertions(+), 118 deletions(-) create mode 100644 phpunit.travis.xml create mode 100644 src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php create mode 100644 src/Schema/Drivers/DoctrineDriver.php create mode 100644 src/Schema/Drivers/Platforms/SQL100Platform.php create mode 100644 src/Schema/Drivers/Platforms/SQL91Platform.php create mode 100644 src/Schema/Drivers/Platforms/SQL92Platform.php create mode 100644 src/Schema/Drivers/Platforms/SQL94Platform.php create mode 100644 src/Schema/Drivers/Platforms/SQLPlatform.php create mode 100644 src/Schema/Drivers/Traits/AlterTableSQLDeclarations.php create mode 100644 src/Schema/SQLSchemaManager.php create mode 100644 tests/Functional/DBAL/BlobTest.php create mode 100644 tests/Functional/DBAL/ConnectionTest.php create mode 100644 tests/Functional/DBAL/DataAccessTest.php create mode 100644 tests/Functional/DBAL/Driver/AbstractDriverTest.php create mode 100644 tests/Functional/DBAL/Driver/PDOConnectionTest.php create mode 100644 tests/Functional/DBAL/Driver/PDOPgSql/DriverTest.php create mode 100644 tests/Functional/DBAL/Driver/PDOPgsqlConnectionTest.php create mode 100644 tests/Functional/DBAL/ExceptionTest.php create mode 100644 tests/Functional/DBAL/LikeWildcardsEscapingTest.php create mode 100644 tests/Functional/DBAL/LoggingTest.php create mode 100644 tests/Functional/DBAL/MasterSlaveConnectionTest.php create mode 100644 tests/Functional/DBAL/ModifyLimitQueryTest.php create mode 100644 tests/Functional/DBAL/NamedParametersTest.php create mode 100644 tests/Functional/DBAL/PDOStatementTest.php create mode 100644 tests/Functional/DBAL/Platform/DateExpressionTest.php create mode 100644 tests/Functional/DBAL/Platform/DefaultExpressionTest.php create mode 100644 tests/Functional/DBAL/Platform/QuotingTest.php create mode 100644 tests/Functional/DBAL/PortabilityTest.php create mode 100644 tests/Functional/DBAL/ResultCacheTest.php create mode 100644 tests/Functional/DBAL/Schema/ComparatorTest.php create mode 100644 tests/Functional/DBAL/Schema/Db2SchemaManagerTest.php create mode 100644 tests/Functional/DBAL/Schema/DefaultValueTest.php create mode 100644 tests/Functional/DBAL/Schema/DrizzleSchemaManagerTest.php create mode 100644 tests/Functional/DBAL/Schema/MySqlSchemaManagerTest.php create mode 100644 tests/Functional/DBAL/Schema/OracleSchemaManagerTest.php create mode 100644 tests/Functional/DBAL/Schema/PostgreSqlSchemaManagerTest.php create mode 100644 tests/Functional/DBAL/Schema/SQLAnywhereSchemaManagerTest.php create mode 100644 tests/Functional/DBAL/Schema/SQLServerSchemaManagerTest.php create mode 100644 tests/Functional/DBAL/Schema/SchemaManagerFunctionalTestCase.php create mode 100644 tests/Functional/DBAL/Schema/SqliteSchemaManagerTest.php create mode 100644 tests/Functional/DBAL/StatementTest.php create mode 100644 tests/Functional/DBAL/TableGeneratorTest.php create mode 100644 tests/Functional/DBAL/TemporaryTableTest.php create mode 100644 tests/Functional/DBAL/Ticket/DBAL168Test.php create mode 100644 tests/Functional/DBAL/Ticket/DBAL202Test.php create mode 100644 tests/Functional/DBAL/Ticket/DBAL421Test.php create mode 100644 tests/Functional/DBAL/Ticket/DBAL461Test.php create mode 100644 tests/Functional/DBAL/Ticket/DBAL510Test.php create mode 100644 tests/Functional/DBAL/Ticket/DBAL630Test.php create mode 100644 tests/Functional/DBAL/Ticket/DBAL752Test.php create mode 100644 tests/Functional/DBAL/TransactionTest.php create mode 100644 tests/Functional/DBAL/TypeConversionTest.php create mode 100644 tests/Functional/DBAL/Types/BinaryTest.php create mode 100644 tests/Functional/DBAL/WriteTest.php create mode 100644 tests/Functional/DbalFunctionalTestCase.php delete mode 100644 tests/Functional/FunctionalTestCase.php create mode 100644 tests/Functional/Schema/ChangeTest.php rename tests/Functional/{ => Schema}/HasIndexTest.php (85%) rename tests/Functional/{ => Schema}/SchemaTest.php (90%) rename tests/Functional/{ => Schema}/UniqueIndexTest.php (96%) create mode 100644 tests/Functional/TestUtil.php create mode 100644 tests/FunctionalTestCase.php create mode 100644 tests/Unit/DBAL/ConfigurationTest.php create mode 100644 tests/Unit/DBAL/ConnectionTest.php create mode 100644 tests/Unit/DBAL/DBALExceptionTest.php create mode 100644 tests/Unit/DBAL/Driver/AbstractDriverTest.php create mode 100644 tests/Unit/DBAL/Driver/AbstractPostgreSQLDriverTest.php create mode 100644 tests/Unit/DBAL/Driver/PDOPgSql/DriverTest.php create mode 100644 tests/Unit/DBAL/DriverManagerTest.php create mode 100644 tests/Unit/DBAL/Platforms/AbstractPlatformTestCase.php create mode 100644 tests/Unit/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php create mode 100644 tests/Unit/DBAL/Platforms/PostgreSQL100PlatformTest.php create mode 100644 tests/Unit/DBAL/Platforms/PostgreSQL91PlatformTest.php create mode 100644 tests/Unit/DBAL/Platforms/PostgreSQL92PlatformTest.php create mode 100644 tests/Unit/DBAL/Platforms/PostgreSQL94PlatformTest.php create mode 100644 tests/Unit/DBAL/Platforms/PostgreSqlPlatformTest.php create mode 100644 tests/Unit/DBAL/Platforms/ReservedKeywordsValidatorTest.php create mode 100644 tests/Unit/DBAL/SQLParserUtilsTest.php create mode 100644 tests/Unit/DBAL/Schema/ColumnDiffTest.php create mode 100644 tests/Unit/DBAL/Schema/ColumnTest.php create mode 100644 tests/Unit/DBAL/Schema/ComparatorTest.php create mode 100644 tests/Unit/DBAL/Schema/ForeignKeyConstraintTest.php create mode 100644 tests/Unit/DBAL/Schema/IndexTest.php create mode 100644 tests/Unit/DBAL/Schema/SchemaDiffTest.php create mode 100644 tests/Unit/DBAL/Schema/SchemaTest.php create mode 100644 tests/Unit/DBAL/Schema/SequenceTest.php create mode 100644 tests/Unit/DBAL/Schema/TableDiffTest.php create mode 100644 tests/Unit/DBAL/Schema/TableTest.php create mode 100644 tests/Unit/DBAL/StatementTest.php create mode 100644 tests/Unit/DBAL/UtilTest.php create mode 100644 tests/Unit/Types/CommentedType.php create mode 100644 tests/Unit/Types/MySqlPointType.php create mode 100755 tests/travis/install-postgres-10.sh create mode 100755 tests/travis/install-postgres-11.sh create mode 100644 tests/travis/pgsql.travis.xml diff --git a/.gitignore b/.gitignore index 5ed27c6..332dcde 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ phpunit.xml .phpunit.result.cache /build +phpunit.xml diff --git a/.travis.yml b/.travis.yml index 883da78..b95591e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,109 @@ language: php +sudo: false +dist: trusty -php: - - 7.2 +cache: + directories: + - vendor + - $HOME/.composer/cache -services: - - postgresql - -matrix: - fast_finish: true +before_install: + - phpenv config-rm xdebug.ini || true + - | + if [ "x$COVERAGE" == "xyes" ]; then + pecl install pcov-1.0.0 + fi install: - - composer install + - rm composer.lock + - travis_retry composer -n update --prefer-dist before_script: - - psql -c 'create database testing;' -U postgres + - cp phpunit.travis.xml phpunit.xml script: - vendor/bin/ecs check --config=ecs.yml . - - phpdbg -qrr vendor/bin/phpunit --coverage-clover build/logs/clover.xml + - | + if [ "x$COVERAGE" == "xyes" ]; then + php -d pcov.directory='.' vendor/bin/phpunit --coverage-clover build/logs/clover.xml + else + ./vendor/bin/phpunit + fi after_success: - travis_retry vendor/bin/php-coveralls -v + +jobs: + allow_failures: + - php: "7.4snapshot" + + include: + - stage: Test + php: "7.2" + env: DB=pgsql POSTGRESQL_VERSION=11.0 + sudo: required + services: + - docker + before_script: + - bash ./tests/travis/install-postgres-11.sh + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.2 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.2" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.3" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.4 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.4" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.5 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.5" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.6 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.6" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes + sudo: required + services: + - postgresql + addons: + postgresql: "9.6" + before_script: + - bash ./tests/travis/install-postgres-10.sh + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=11.0 COVERAGE=yes + sudo: required + services: + - docker + before_script: + - bash ./tests/travis/install-postgres-11.sh + - stage: Test + php: "7.4snapshot" + env: DB=pgsql POSTGRESQL_VERSION=11.0 + sudo: required + services: + - docker + before_script: + - bash ./tests/travis/install-postgres-11.sh diff --git a/composer.json b/composer.json index dce9b12..4dc3726 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "umbrellio/laravel-pg-extensions", + "description": "", "type": "library", "minimum-stability": "stable", "authors": [ @@ -10,12 +11,14 @@ ], "require": { "php": "^7.2", + "doctrine/dbal": "^2.9", "laravel/framework": "^5.8" }, "require-dev": { "umbrellio/code-style-php": "^1.0", "orchestra/testbench": "^3.5", - "php-coveralls/php-coveralls": "^2.1" + "php-coveralls/php-coveralls": "^2.1", + "symfony/phpunit-bridge": "^3.4.5|^4.0.5|^5.0" }, "scripts": { "lint": [ diff --git a/composer.lock b/composer.lock index f8b1d94..602dabe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "806be43cfecda6994cd0335061c2ad39", + "content-hash": "74d899a67a8901f76c05815efc6ef67c", "packages": [ { "name": "doctrine/cache", @@ -83,31 +83,31 @@ }, { "name": "doctrine/dbal", - "version": "v2.9.2", + "version": "v2.10.4", "source": { "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9" + "url": "https://github.com/pvsaintpe/dbal.git", + "reference": "59aedf521cca50af294bdbbc14337ea3a0293d86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", - "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", + "url": "https://api.github.com/repos/pvsaintpe/dbal/zipball/59aedf521cca50af294bdbbc14337ea3a0293d86", + "reference": "59aedf521cca50af294bdbbc14337ea3a0293d86", "shasum": "" }, "require": { "doctrine/cache": "^1.0", "doctrine/event-manager": "^1.0", "ext-pdo": "*", - "php": "^7.1" + "php": "^7.2" }, "require-dev": { - "doctrine/coding-standard": "^5.0", - "jetbrains/phpstorm-stubs": "^2018.1.2", - "phpstan/phpstan": "^0.10.1", - "phpunit/phpunit": "^7.4", - "symfony/console": "^2.0.5|^3.0|^4.0", - "symfony/phpunit-bridge": "^3.4.5|^4.0.5" + "doctrine/coding-standard": "^6.0", + "jetbrains/phpstorm-stubs": "^2019.1", + "phpstan/phpstan": "^0.11.3", + "phpunit/phpunit": "^8.2.1", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "symfony/phpunit-bridge": "^3.4.5|^4.0.5|^5.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -118,7 +118,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", + "dev-master": "2.10.x-dev", "dev-develop": "3.0.x-dev" } }, @@ -127,11 +127,19 @@ "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\": "tests/Doctrine/Tests" + } + }, "license": [ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -140,10 +148,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -154,14 +158,28 @@ "keywords": [ "abstraction", "database", + "db2", "dbal", + "mariadb", + "mssql", "mysql", - "persistence", + "oci8", + "oracle", + "pdo", "pgsql", - "php", - "queryobject" - ], - "time": "2018-12-31T03:27:51+00:00" + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlanywhere", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "source": "https://github.com/pvsaintpe/dbal/tree/bug/default-expression" + }, + "time": "2019-07-11T16:39:36+00:00" }, { "name": "doctrine/event-manager", @@ -523,16 +541,16 @@ }, { "name": "laravel/framework", - "version": "v5.8.27", + "version": "v5.8.29", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "f1dccffb96f614895393e27e4667105a05407af5" + "reference": "489ae2218c7eb138caac780de584d8df9fe8160b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/f1dccffb96f614895393e27e4667105a05407af5", - "reference": "f1dccffb96f614895393e27e4667105a05407af5", + "url": "https://api.github.com/repos/laravel/framework/zipball/489ae2218c7eb138caac780de584d8df9fe8160b", + "reference": "489ae2218c7eb138caac780de584d8df9fe8160b", "shasum": "" }, "require": { @@ -666,7 +684,7 @@ "framework", "laravel" ], - "time": "2019-07-02T13:43:47+00:00" + "time": "2019-07-16T14:05:28+00:00" }, { "name": "league/flysystem", @@ -832,16 +850,16 @@ }, { "name": "nesbot/carbon", - "version": "2.20.0", + "version": "2.21.2", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "bc671b896c276795fad8426b0aa24e8ade0f2498" + "reference": "a8bfb6631e3bc982427406d13ad817fc45d47b61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bc671b896c276795fad8426b0aa24e8ade0f2498", - "reference": "bc671b896c276795fad8426b0aa24e8ade0f2498", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/a8bfb6631e3bc982427406d13ad817fc45d47b61", + "reference": "a8bfb6631e3bc982427406d13ad817fc45d47b61", "shasum": "" }, "require": { @@ -857,6 +875,9 @@ "phpunit/phpunit": "^7.5 || ^8.0", "squizlabs/php_codesniffer": "^3.4" }, + "bin": [ + "bin/carbon" + ], "type": "library", "extra": { "laravel": { @@ -879,6 +900,10 @@ "name": "Brian Nesbitt", "email": "brian@nesbot.com", "homepage": "http://nesbot.com" + }, + { + "name": "kylekatarnls", + "homepage": "http://github.com/kylekatarnls" } ], "description": "A simple API extension for DateTime.", @@ -888,20 +913,20 @@ "datetime", "time" ], - "time": "2019-06-25T10:00:57+00:00" + "time": "2019-07-17T07:49:05+00:00" }, { "name": "opis/closure", - "version": "3.3.0", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "f846725591203098246276b2e7b9e8b7814c4965" + "reference": "92927e26d7fc3f271efe1f55bdbb073fbb2f0722" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/f846725591203098246276b2e7b9e8b7814c4965", - "reference": "f846725591203098246276b2e7b9e8b7814c4965", + "url": "https://api.github.com/repos/opis/closure/zipball/92927e26d7fc3f271efe1f55bdbb073fbb2f0722", + "reference": "92927e26d7fc3f271efe1f55bdbb073fbb2f0722", "shasum": "" }, "require": { @@ -949,7 +974,7 @@ "serialization", "serialize" ], - "time": "2019-05-31T20:04:32+00:00" + "time": "2019-07-09T21:58:11+00:00" }, { "name": "paragonie/random_compat", @@ -3723,34 +3748,35 @@ }, { "name": "ocramius/package-versions", - "version": "1.4.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb" + "reference": "1d32342b8c1eb27353c8887c366147b4c2da673c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", - "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/1d32342b8c1eb27353c8887c366147b4c2da673c", + "reference": "1d32342b8c1eb27353c8887c366147b4c2da673c", "shasum": "" }, "require": { "composer-plugin-api": "^1.0.0", - "php": "^7.1.0" + "php": "^7.3.0" }, "require-dev": { - "composer/composer": "^1.6.3", - "doctrine/coding-standard": "^5.0.1", + "composer/composer": "^1.8.6", + "doctrine/coding-standard": "^6.0.0", "ext-zip": "*", - "infection/infection": "^0.7.1", - "phpunit/phpunit": "^7.0.0" + "infection/infection": "^0.13.4", + "phpunit/phpunit": "^8.2.5", + "vimeo/psalm": "^3.4.9" }, "type": "composer-plugin", "extra": { "class": "PackageVersions\\Installer", "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { @@ -3769,7 +3795,7 @@ } ], "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "time": "2019-02-21T12:16:21+00:00" + "time": "2019-07-17T15:49:50+00:00" }, { "name": "orchestra/testbench", @@ -4386,16 +4412,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "7.0.5", + "version": "7.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aed67b57d459dcab93e84a5c9703d3deb5025dff" + "reference": "d471d0d2b529a67c6a722dd446c4ec90881ac315" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aed67b57d459dcab93e84a5c9703d3deb5025dff", - "reference": "aed67b57d459dcab93e84a5c9703d3deb5025dff", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d471d0d2b529a67c6a722dd446c4ec90881ac315", + "reference": "d471d0d2b529a67c6a722dd446c4ec90881ac315", "shasum": "" }, "require": { @@ -4404,17 +4430,17 @@ "php": "^7.2", "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0.1", + "phpunit/php-token-stream": "^3.0.2", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.1", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.6.1" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { @@ -4445,7 +4471,7 @@ "testing", "xunit" ], - "time": "2019-06-06T12:28:18+00:00" + "time": "2019-07-08T05:29:42+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4589,16 +4615,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + "reference": "c4a66b97f040e3e20b3aa2a243230a1c3a9f7c8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c4a66b97f040e3e20b3aa2a243230a1c3a9f7c8c", + "reference": "c4a66b97f040e3e20b3aa2a243230a1c3a9f7c8c", "shasum": "" }, "require": { @@ -4634,20 +4660,20 @@ "keywords": [ "tokenizer" ], - "time": "2018-10-30T05:52:18+00:00" + "time": "2019-07-08T05:24:54+00:00" }, { "name": "phpunit/phpunit", - "version": "8.2.3", + "version": "8.2.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f67ca36860ebca7224d4573f107f79bd8ed0ba03" + "reference": "c1b8534b3730f20f58600124129197bf1183dc92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f67ca36860ebca7224d4573f107f79bd8ed0ba03", - "reference": "f67ca36860ebca7224d4573f107f79bd8ed0ba03", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1b8534b3730f20f58600124129197bf1183dc92", + "reference": "c1b8534b3730f20f58600124129197bf1183dc92", "shasum": "" }, "require": { @@ -4674,7 +4700,7 @@ "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", "sebastian/resource-operations": "^2.0.1", - "sebastian/type": "^1.1.0", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, "require-dev": { @@ -4717,7 +4743,7 @@ "testing", "xunit" ], - "time": "2019-06-19T12:03:56+00:00" + "time": "2019-07-15T06:26:24+00:00" }, { "name": "psr/cache", @@ -5985,6 +6011,71 @@ ], "time": "2019-06-13T11:01:17+00:00" }, + { + "name": "symfony/phpunit-bridge", + "version": "v4.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/phpunit-bridge.git", + "reference": "573b5c4a36a171b94cf031d8dc6cc568082190f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/573b5c4a36a171b94cf031d8dc6cc568082190f1", + "reference": "573b5c4a36a171b94cf031d8dc6cc568082190f1", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "suggest": { + "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" + }, + "bin": [ + "bin/simple-phpunit" + ], + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + }, + "thanks": { + "name": "phpunit/phpunit", + "url": "https://github.com/sebastianbergmann/phpunit" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Bridge\\PhpUnit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony PHPUnit Bridge", + "homepage": "https://symfony.com", + "time": "2019-06-26T12:14:14+00:00" + }, { "name": "symfony/polyfill-php70", "version": "v1.11.0", @@ -6550,7 +6641,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.1.3" + "php": "^7.2" }, "platform-dev": [] } diff --git a/ecs.yml b/ecs.yml index 768e555..7f5cf1a 100644 --- a/ecs.yml +++ b/ecs.yml @@ -10,6 +10,7 @@ parameters: cache_directory: .ecs_cache exclude_files: - vendor/* + - src/Schema/Drivers/Traits/* skip: Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff: diff --git a/phpunit.travis.xml b/phpunit.travis.xml new file mode 100644 index 0000000..3f62946 --- /dev/null +++ b/phpunit.travis.xml @@ -0,0 +1,39 @@ + + + + + ./src + + ./src/.meta.php + ./src/Commands + + + + + + + + + + + + + + + + + + + + ./tests + + + diff --git a/src/.meta.php b/src/.meta.php index 1fdb06a..6b40c8c 100644 --- a/src/.meta.php +++ b/src/.meta.php @@ -19,4 +19,11 @@ class Blueprint { } + + /** + * @method ColumnDefinition using($expression) + */ + class ColumnDefinition + { + } } diff --git a/src/Connectors/ConnectionFactory.php b/src/Connectors/ConnectionFactory.php index 0c78c2e..1eded11 100644 --- a/src/Connectors/ConnectionFactory.php +++ b/src/Connectors/ConnectionFactory.php @@ -11,6 +11,9 @@ class ConnectionFactory extends ConnectionFactoryBase { protected function createConnection($driver, $connection, $database, $prefix = '', array $config = []) { - return new PostgresConnection($connection, $database, $prefix, $config); + //dd($driver, $connection, $database, $prefix, $config); + $connection = new PostgresConnection($connection, $database, $prefix, $config); +// dd($connection->getName()); + return $connection; } } diff --git a/src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php b/src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php new file mode 100644 index 0000000..904f216 --- /dev/null +++ b/src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php @@ -0,0 +1,13 @@ +registerExtensions(); } + protected function getDoctrineDriver() + { + return new DoctrineDriver(); + } + protected function getDefaultSchemaGrammar() { return $this->withTablePrefix(new PostgresGrammar()); diff --git a/src/Schema/Drivers/DoctrineDriver.php b/src/Schema/Drivers/DoctrineDriver.php new file mode 100644 index 0000000..89d9ffd --- /dev/null +++ b/src/Schema/Drivers/DoctrineDriver.php @@ -0,0 +1,50 @@ +getDatabasePlatform(); + } + } +} diff --git a/src/Schema/Drivers/Platforms/SQL100Platform.php b/src/Schema/Drivers/Platforms/SQL100Platform.php new file mode 100644 index 0000000..0ca665d --- /dev/null +++ b/src/Schema/Drivers/Platforms/SQL100Platform.php @@ -0,0 +1,30 @@ +quoteStringLiteral($database) . " + AND sequence_schema NOT LIKE 'pg\_%' + AND sequence_schema != 'information_schema'"; + } +} diff --git a/src/Schema/Drivers/Platforms/SQL91Platform.php b/src/Schema/Drivers/Platforms/SQL91Platform.php new file mode 100644 index 0000000..0e56bde --- /dev/null +++ b/src/Schema/Drivers/Platforms/SQL91Platform.php @@ -0,0 +1,45 @@ +quoteSingleIdentifier($collation); + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $sql = parent::getListTableColumnsSQL($table, $database); + $parts = explode('AS complete_type,', $sql, 2); + + return $parts[0] . 'AS complete_type, (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,' . $parts[1]; + } +} diff --git a/src/Schema/Drivers/Platforms/SQL92Platform.php b/src/Schema/Drivers/Platforms/SQL92Platform.php new file mode 100644 index 0000000..0e46e6d --- /dev/null +++ b/src/Schema/Drivers/Platforms/SQL92Platform.php @@ -0,0 +1,68 @@ +doctrineTypeMapping['json'] = Types::JSON; + } + + /** + * {@inheritdoc} + */ + public function getCloseActiveDatabaseConnectionsSQL($database) + { + return sprintf( + 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = %s', + $this->quoteStringLiteral($database) + ); + } +} diff --git a/src/Schema/Drivers/Platforms/SQL94Platform.php b/src/Schema/Drivers/Platforms/SQL94Platform.php new file mode 100644 index 0000000..71f95b2 --- /dev/null +++ b/src/Schema/Drivers/Platforms/SQL94Platform.php @@ -0,0 +1,41 @@ +doctrineTypeMapping['jsonb'] = Types::JSON; + } +} diff --git a/src/Schema/Drivers/Platforms/SQLPlatform.php b/src/Schema/Drivers/Platforms/SQLPlatform.php new file mode 100644 index 0000000..50221dc --- /dev/null +++ b/src/Schema/Drivers/Platforms/SQLPlatform.php @@ -0,0 +1,11 @@ +getDefault() === null) { + return sprintf('ALTER TABLE %s ALTER %s DROP DEFAULT', $table, $columnName); + } + + return sprintf( + 'ALTER TABLE %s ALTER %s SET %s', + $table, + $columnName, + trim($this->fixDefaultValueDeclarationSQL($column)) + ); + } + + public function fixDefaultValueDeclarationSQL(Column $column): string + { + if ($column->getDefault() instanceof Expression) { + return ' DEFAULT ' . $column->getDefault(); + } + return $this->getDefaultValueDeclarationSQL($column->toArray()); + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = []; + $commentsSQL = []; + $columnSql = []; + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + + $comment = $this->getColumnComment($column); + + if ($comment === null || $comment === '') { + continue; + } + + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $comment + ); + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $query = 'DROP ' . $column->getQuotedName($this); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + foreach ($diff->changedColumns as $columnDiff) { + /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + if ($this->isUnchangedBinaryColumn($columnDiff)) { + continue; + } + + $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this); + $column = $columnDiff->column; + + if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed')) { + $type = $column->getType(); + + // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type + $columnDefinition = $column->toArray(); + $columnDefinition['autoincrement'] = false; + + // here was a server version check before, but DBAL API does not support this anymore. + $sql[] = $this->getAlterTableAlterTypeColumnSQL( + $diff->getName($this)->getQuotedName($this), + $oldColumnName, + $type->getSQLDeclaration($columnDefinition, $this), + $columnDefinition + ); + } + + if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) { + $sql[] = $this->getAlterTableAlterDefaultSQL( + $diff->getName($this)->getQuotedName($this), + $oldColumnName, + $column + ); + } + + if ($columnDiff->hasChanged('notnull')) { + $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL'; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + if ($columnDiff->hasChanged('autoincrement')) { + if ($column->getAutoincrement()) { + // add autoincrement + $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName); + + $sql[] = 'CREATE SEQUENCE ' . $seqName; + $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ') FROM ' . $diff->getName($this)->getQuotedName($this) . '))'; + $query = 'ALTER ' . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } else { + // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have + $query = 'ALTER ' . $oldColumnName . ' DROP DEFAULT'; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + } + + $newComment = $this->getColumnComment($column); + $oldComment = $this->getOldColumnComment($columnDiff); + + if ($columnDiff->hasChanged('comment') || ($columnDiff->fromColumn !== null && $oldComment !== $newComment)) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $newComment + ); + } + + if (! $columnDiff->hasChanged('length')) { + continue; + } + + $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $column->getType()->getSQLDeclaration($column->toArray(), $this); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . + ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); + } + + $tableSql = []; + + if (! $this->onSchemaAlterTable($diff, $tableSql)) { + $sql = array_merge($sql, $commentsSQL); + + $newName = $diff->getNewName(); + + if ($newName !== false) { + $sql[] = sprintf( + 'ALTER TABLE %s RENAME TO %s', + $diff->getName($this)->getQuotedName($this), + $newName->getQuotedName($this) + ); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + private function isUnchangedBinaryColumn(ColumnDiff $columnDiff): bool + { + $columnType = $columnDiff->column->getType(); + + if (!$columnType instanceof BinaryType && !$columnType instanceof BlobType) { + return false; + } + + $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null; + + if ($fromColumn) { + $fromColumnType = $fromColumn->getType(); + + if (!$fromColumnType instanceof BinaryType && !$fromColumnType instanceof BlobType) { + return false; + } + + return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0; + } + + if ($columnDiff->hasChanged('type')) { + return false; + } + + return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0; + } + + private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff): bool + { + if (!$columnDiff->fromColumn) { + return $columnDiff->hasChanged('type'); + } + + $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType()); + $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType()); + + // default should not be changed when switching between numeric types and the default comes from a sequence + return $columnDiff->hasChanged('type') + && !($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement()); + } + + private function isNumericType(Type $type): bool + { + return $type instanceof IntegerType || $type instanceof BigIntType; + } + + private function getOldColumnComment(ColumnDiff $columnDiff): ?string + { + return $columnDiff->fromColumn ? $this->getColumnComment($columnDiff->fromColumn) : null; + } +} diff --git a/src/Schema/SQLSchemaManager.php b/src/Schema/SQLSchemaManager.php new file mode 100644 index 0000000..3d62971 --- /dev/null +++ b/src/Schema/SQLSchemaManager.php @@ -0,0 +1,13 @@ +addColumn('id', 'integer'); + $table->addColumn('clobfield', 'text'); + $table->addColumn('blobfield', 'blob'); + $table->setPrimaryKey(['id']); + + $sm = $this->connection->getSchemaManager(); + $sm->dropAndCreateTable($table); + } + + public function testInsert() : void + { + $ret = $this->connection->insert('blob_table', [ + 'id' => 1, + 'clobfield' => 'test', + 'blobfield' => 'test', + ], [ + ParameterType::INTEGER, + ParameterType::STRING, + ParameterType::LARGE_OBJECT, + ]); + + self::assertEquals(1, $ret); + } + + public function testInsertProcessesStream() : void + { + // https://github.com/doctrine/dbal/issues/3290 + if ($this->connection->getDriver() instanceof OCI8Driver) { + $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters'); + } + + $longBlob = str_repeat('x', 4 * 8192); // send 4 chunks + $this->connection->insert('blob_table', [ + 'id' => 1, + 'clobfield' => 'ignored', + 'blobfield' => fopen('data://text/plain,' . $longBlob, 'r'), + ], [ + ParameterType::INTEGER, + ParameterType::STRING, + ParameterType::LARGE_OBJECT, + ]); + + $this->assertBlobContains($longBlob); + } + + public function testSelect() : void + { + $this->connection->insert('blob_table', [ + 'id' => 1, + 'clobfield' => 'test', + 'blobfield' => 'test', + ], [ + ParameterType::INTEGER, + ParameterType::STRING, + ParameterType::LARGE_OBJECT, + ]); + + $this->assertBlobContains('test'); + } + + public function testUpdate() : void + { + $this->connection->insert('blob_table', [ + 'id' => 1, + 'clobfield' => 'test', + 'blobfield' => 'test', + ], [ + ParameterType::INTEGER, + ParameterType::STRING, + ParameterType::LARGE_OBJECT, + ]); + + $this->connection->update('blob_table', ['blobfield' => 'test2'], ['id' => 1], [ + ParameterType::LARGE_OBJECT, + ParameterType::INTEGER, + ]); + + $this->assertBlobContains('test2'); + } + + public function testUpdateProcessesStream() : void + { + // https://github.com/doctrine/dbal/issues/3290 + if ($this->connection->getDriver() instanceof OCI8Driver) { + $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters'); + } + + $this->connection->insert('blob_table', [ + 'id' => 1, + 'clobfield' => 'ignored', + 'blobfield' => 'test', + ], [ + ParameterType::INTEGER, + ParameterType::STRING, + ParameterType::LARGE_OBJECT, + ]); + + $this->connection->update('blob_table', [ + 'id' => 1, + 'blobfield' => fopen('data://text/plain,test2', 'r'), + ], ['id' => 1], [ + ParameterType::INTEGER, + ParameterType::LARGE_OBJECT, + ]); + + $this->assertBlobContains('test2'); + } + + public function testBindParamProcessesStream() : void + { + if ($this->connection->getDriver() instanceof OCI8Driver) { + $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters'); + } + + $stmt = $this->connection->prepare("INSERT INTO blob_table(id, clobfield, blobfield) VALUES (1, 'ignored', ?)"); + + $stream = null; + $stmt->bindParam(1, $stream, ParameterType::LARGE_OBJECT); + + // Bind param does late binding (bind by reference), so create the stream only now: + $stream = fopen('data://text/plain,test', 'r'); + + $stmt->execute(); + + $this->assertBlobContains('test'); + } + + private function assertBlobContains(string $text) : void + { + $rows = $this->connection->query('SELECT blobfield FROM blob_table')->fetchAll(FetchMode::COLUMN); + + self::assertCount(1, $rows); + + $blobValue = Type::getType('blob')->convertToPHPValue($rows[0], $this->connection->getDatabasePlatform()); + + self::assertIsResource($blobValue); + self::assertEquals($text, stream_get_contents($blobValue)); + } +} diff --git a/tests/Functional/DBAL/ConnectionTest.php b/tests/Functional/DBAL/ConnectionTest.php new file mode 100644 index 0000000..21d1d0f --- /dev/null +++ b/tests/Functional/DBAL/ConnectionTest.php @@ -0,0 +1,327 @@ +resetSharedConn(); + parent::setUp(); + } + + protected function tearDown() : void + { + parent::tearDown(); + $this->resetSharedConn(); + } + + public function testGetWrappedConnection() : void + { + self::assertInstanceOf(DriverConnection::class, $this->connection->getWrappedConnection()); + } + + public function testCommitWithRollbackOnlyThrowsException() : void + { + $this->connection->beginTransaction(); + $this->connection->setRollbackOnly(); + + $this->expectException(ConnectionException::class); + $this->connection->commit(); + } + + public function testTransactionNestingBehavior() : void + { + try { + $this->connection->beginTransaction(); + self::assertEquals(1, $this->connection->getTransactionNestingLevel()); + + try { + $this->connection->beginTransaction(); + self::assertEquals(2, $this->connection->getTransactionNestingLevel()); + throw new Exception(); + $this->connection->commit(); // never reached + } catch (Throwable $e) { + $this->connection->rollBack(); + self::assertEquals(1, $this->connection->getTransactionNestingLevel()); + //no rethrow + } + self::assertTrue($this->connection->isRollbackOnly()); + + $this->connection->commit(); // should throw exception + $this->fail('Transaction commit after failed nested transaction should fail.'); + } catch (ConnectionException $e) { + self::assertEquals(1, $this->connection->getTransactionNestingLevel()); + $this->connection->rollBack(); + self::assertEquals(0, $this->connection->getTransactionNestingLevel()); + } + } + + public function testTransactionNestingBehaviorWithSavepoints() : void + { + if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) { + $this->markTestSkipped('This test requires the platform to support savepoints.'); + } + + $this->connection->setNestTransactionsWithSavepoints(true); + try { + $this->connection->beginTransaction(); + self::assertEquals(1, $this->connection->getTransactionNestingLevel()); + + try { + $this->connection->beginTransaction(); + self::assertEquals(2, $this->connection->getTransactionNestingLevel()); + $this->connection->beginTransaction(); + self::assertEquals(3, $this->connection->getTransactionNestingLevel()); + self::assertTrue($this->connection->commit()); + self::assertEquals(2, $this->connection->getTransactionNestingLevel()); + throw new Exception(); + $this->connection->commit(); // never reached + } catch (Throwable $e) { + $this->connection->rollBack(); + self::assertEquals(1, $this->connection->getTransactionNestingLevel()); + //no rethrow + } + self::assertFalse($this->connection->isRollbackOnly()); + try { + $this->connection->setNestTransactionsWithSavepoints(false); + $this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.'); + } catch (ConnectionException $e) { + self::assertTrue($this->connection->getNestTransactionsWithSavepoints()); + } + $this->connection->commit(); // should not throw exception + } catch (ConnectionException $e) { + $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.'); + $this->connection->rollBack(); + } + } + + public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction() : void + { + if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) { + $this->markTestSkipped('This test requires the platform to support savepoints.'); + } + + $this->connection->beginTransaction(); + $this->expectException(ConnectionException::class); + $this->connection->setNestTransactionsWithSavepoints(true); + } + + public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException() : void + { + if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { + $this->markTestSkipped('This test requires the platform not to support savepoints.'); + } + + $this->expectException(ConnectionException::class); + $this->expectExceptionMessage('Savepoints are not supported by this driver.'); + + $this->connection->setNestTransactionsWithSavepoints(true); + } + + public function testCreateSavepointsNotSupportedThrowsException() : void + { + if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { + $this->markTestSkipped('This test requires the platform not to support savepoints.'); + } + + $this->expectException(ConnectionException::class); + $this->expectExceptionMessage('Savepoints are not supported by this driver.'); + + $this->connection->createSavepoint('foo'); + } + + public function testReleaseSavepointsNotSupportedThrowsException() : void + { + if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { + $this->markTestSkipped('This test requires the platform not to support savepoints.'); + } + + $this->expectException(ConnectionException::class); + $this->expectExceptionMessage('Savepoints are not supported by this driver.'); + + $this->connection->releaseSavepoint('foo'); + } + + public function testRollbackSavepointsNotSupportedThrowsException() : void + { + if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { + $this->markTestSkipped('This test requires the platform not to support savepoints.'); + } + + $this->expectException(ConnectionException::class); + $this->expectExceptionMessage('Savepoints are not supported by this driver.'); + + $this->connection->rollbackSavepoint('foo'); + } + + public function testTransactionBehaviorWithRollback() : void + { + try { + $this->connection->beginTransaction(); + self::assertEquals(1, $this->connection->getTransactionNestingLevel()); + + throw new Exception(); + + $this->connection->commit(); // never reached + } catch (Throwable $e) { + self::assertEquals(1, $this->connection->getTransactionNestingLevel()); + $this->connection->rollBack(); + self::assertEquals(0, $this->connection->getTransactionNestingLevel()); + } + } + + public function testTransactionBehaviour() : void + { + try { + $this->connection->beginTransaction(); + self::assertEquals(1, $this->connection->getTransactionNestingLevel()); + $this->connection->commit(); + } catch (Throwable $e) { + $this->connection->rollBack(); + self::assertEquals(0, $this->connection->getTransactionNestingLevel()); + } + + self::assertEquals(0, $this->connection->getTransactionNestingLevel()); + } + + public function testTransactionalWithException() : void + { + try { + $this->connection->transactional(static function ($conn) : void { + /** @var Connection $conn */ + $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); + throw new RuntimeException('Ooops!'); + }); + $this->fail('Expected exception'); + } catch (RuntimeException $expected) { + self::assertEquals(0, $this->connection->getTransactionNestingLevel()); + } + } + + public function testTransactionalWithThrowable() : void + { + try { + $this->connection->transactional(static function ($conn) : void { + /** @var Connection $conn */ + $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); + throw new Error('Ooops!'); + }); + $this->fail('Expected exception'); + } catch (Error $expected) { + self::assertEquals(0, $this->connection->getTransactionNestingLevel()); + } + } + + public function testTransactional() : void + { + $res = $this->connection->transactional(static function ($conn) : void { + /** @var Connection $conn */ + $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); + }); + + self::assertNull($res); + } + + public function testTransactionalReturnValue() : void + { + $res = $this->connection->transactional(static function () { + return 42; + }); + + self::assertEquals(42, $res); + } + + /** + * Tests that the quote function accepts DBAL and PDO types. + */ + public function testQuote() : void + { + self::assertEquals( + $this->connection->quote('foo', Types::STRING), + $this->connection->quote('foo', ParameterType::STRING) + ); + } + + public function testPingDoesTriggersConnect() : void + { + self::assertTrue($this->connection->ping()); + self::assertTrue($this->connection->isConnected()); + } + + /** + * @group DBAL-1025 + */ + public function testConnectWithoutExplicitDatabaseName() : void + { + if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { + $this->markTestSkipped('Platform does not support connecting without database name.'); + } + + $params = $this->connection->getParams(); + unset($params['dbname']); + + $connection = DriverManager::getConnection( + $params, + $this->connection->getConfiguration(), + $this->connection->getEventManager() + ); + + self::assertTrue($connection->connect()); + + $connection->close(); + } + + /** + * @group DBAL-990 + */ + public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase() : void + { + if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { + $this->markTestSkipped('Platform does not support connecting without database name.'); + } + + $params = $this->connection->getParams(); + + $params['dbname'] = 'foo_bar'; + + $connection = DriverManager::getConnection( + $params, + $this->connection->getConfiguration(), + $this->connection->getEventManager() + ); + + self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform()); + self::assertFalse($connection->isConnected()); + self::assertSame($params, $connection->getParams()); + + $connection->close(); + } + + /** + * @requires extension pdo_sqlite + */ + public function testUserProvidedPDOConnection() : void + { + self::assertTrue( + DriverManager::getConnection([ + 'pdo' => new PDO('sqlite::memory:'), + ])->ping() + ); + } +} diff --git a/tests/Functional/DBAL/DataAccessTest.php b/tests/Functional/DBAL/DataAccessTest.php new file mode 100644 index 0000000..651aeda --- /dev/null +++ b/tests/Functional/DBAL/DataAccessTest.php @@ -0,0 +1,972 @@ +addColumn('test_int', 'integer'); + $table->addColumn('test_string', 'string'); + $table->addColumn('test_datetime', 'datetime', ['notnull' => false]); + $table->setPrimaryKey(['test_int']); + + $sm = $this->connection->getSchemaManager(); + $sm->createTable($table); + + $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo', 'test_datetime' => '2010-01-01 10:10:10']); + self::$generated = true; + } + + public function testPrepareWithBindValue() : void + { + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $stmt = $this->connection->prepare($sql); + self::assertInstanceOf(Statement::class, $stmt); + + $stmt->bindValue(1, 1); + $stmt->bindValue(2, 'foo'); + $stmt->execute(); + + $row = $stmt->fetch(FetchMode::ASSOCIATIVE); + $row = array_change_key_case($row, CASE_LOWER); + self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row); + } + + public function testPrepareWithBindParam() : void + { + $paramInt = 1; + $paramStr = 'foo'; + + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $stmt = $this->connection->prepare($sql); + self::assertInstanceOf(Statement::class, $stmt); + + $stmt->bindParam(1, $paramInt); + $stmt->bindParam(2, $paramStr); + $stmt->execute(); + + $row = $stmt->fetch(FetchMode::ASSOCIATIVE); + $row = array_change_key_case($row, CASE_LOWER); + self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row); + } + + public function testPrepareWithFetchAll() : void + { + $paramInt = 1; + $paramStr = 'foo'; + + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $stmt = $this->connection->prepare($sql); + self::assertInstanceOf(Statement::class, $stmt); + + $stmt->bindParam(1, $paramInt); + $stmt->bindParam(2, $paramStr); + $stmt->execute(); + + $rows = $stmt->fetchAll(FetchMode::ASSOCIATIVE); + $rows[0] = array_change_key_case($rows[0], CASE_LOWER); + self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]); + } + + /** + * @group DBAL-228 + */ + public function testPrepareWithFetchAllBoth() : void + { + $paramInt = 1; + $paramStr = 'foo'; + + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $stmt = $this->connection->prepare($sql); + self::assertInstanceOf(Statement::class, $stmt); + + $stmt->bindParam(1, $paramInt); + $stmt->bindParam(2, $paramStr); + $stmt->execute(); + + $rows = $stmt->fetchAll(FetchMode::MIXED); + $rows[0] = array_change_key_case($rows[0], CASE_LOWER); + self::assertEquals(['test_int' => 1, 'test_string' => 'foo', 0 => 1, 1 => 'foo'], $rows[0]); + } + + public function testPrepareWithFetchColumn() : void + { + $paramInt = 1; + $paramStr = 'foo'; + + $sql = 'SELECT test_int FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $stmt = $this->connection->prepare($sql); + self::assertInstanceOf(Statement::class, $stmt); + + $stmt->bindParam(1, $paramInt); + $stmt->bindParam(2, $paramStr); + $stmt->execute(); + + $column = $stmt->fetchColumn(); + self::assertEquals(1, $column); + } + + public function testPrepareWithIterator() : void + { + $paramInt = 1; + $paramStr = 'foo'; + + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $stmt = $this->connection->prepare($sql); + self::assertInstanceOf(Statement::class, $stmt); + + $stmt->bindParam(1, $paramInt); + $stmt->bindParam(2, $paramStr); + $stmt->execute(); + + $rows = []; + $stmt->setFetchMode(FetchMode::ASSOCIATIVE); + foreach ($stmt as $row) { + $rows[] = array_change_key_case($row, CASE_LOWER); + } + + self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]); + } + + public function testPrepareWithQuoted() : void + { + $table = 'fetch_table'; + $paramInt = 1; + $paramStr = 'foo'; + + $stmt = $this->connection->prepare(sprintf( + 'SELECT test_int, test_string FROM %s WHERE test_int = %s AND test_string = %s', + $this->connection->quoteIdentifier($table), + $this->connection->quote($paramInt), + $this->connection->quote($paramStr) + )); + self::assertInstanceOf(Statement::class, $stmt); + } + + public function testPrepareWithExecuteParams() : void + { + $paramInt = 1; + $paramStr = 'foo'; + + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $stmt = $this->connection->prepare($sql); + self::assertInstanceOf(Statement::class, $stmt); + $stmt->execute([$paramInt, $paramStr]); + + $row = $stmt->fetch(FetchMode::ASSOCIATIVE); + self::assertNotFalse($row); + $row = array_change_key_case($row, CASE_LOWER); + self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row); + } + + public function testFetchAll() : void + { + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $data = $this->connection->fetchAll($sql, [1, 'foo']); + + self::assertCount(1, $data); + + $row = $data[0]; + self::assertCount(2, $row); + + $row = array_change_key_case($row, CASE_LOWER); + self::assertEquals(1, $row['test_int']); + self::assertEquals('foo', $row['test_string']); + } + + /** + * @group DBAL-209 + */ + public function testFetchAllWithTypes() : void + { + $datetimeString = '2010-01-01 10:10:10'; + $datetime = new DateTime($datetimeString); + + $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; + $data = $this->connection->fetchAll( + $sql, + [1, $datetime], + [ParameterType::STRING, Types::DATETIME_MUTABLE] + ); + + self::assertCount(1, $data); + + $row = $data[0]; + self::assertCount(2, $row); + + $row = array_change_key_case($row, CASE_LOWER); + self::assertEquals(1, $row['test_int']); + self::assertStringStartsWith($datetimeString, $row['test_datetime']); + } + + /** + * @group DBAL-209 + */ + public function testFetchAllWithMissingTypes() : void + { + if ($this->connection->getDriver() instanceof MySQLiDriver || + $this->connection->getDriver() instanceof SQLSrvDriver) { + $this->markTestSkipped('mysqli and sqlsrv actually supports this'); + } + + $datetimeString = '2010-01-01 10:10:10'; + $datetime = new DateTime($datetimeString); + $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; + + $this->expectException(DBALException::class); + + $this->connection->fetchAll($sql, [1, $datetime]); + } + + public function testFetchBoth() : void + { + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $row = $this->connection->executeQuery($sql, [1, 'foo'])->fetch(FetchMode::MIXED); + + self::assertNotFalse($row); + + $row = array_change_key_case($row, CASE_LOWER); + + self::assertEquals(1, $row['test_int']); + self::assertEquals('foo', $row['test_string']); + self::assertEquals(1, $row[0]); + self::assertEquals('foo', $row[1]); + } + + public function testFetchNoResult() : void + { + self::assertFalse( + $this->connection->executeQuery('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])->fetch() + ); + } + + public function testFetchAssoc() : void + { + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $row = $this->connection->fetchAssoc($sql, [1, 'foo']); + + self::assertNotFalse($row); + + $row = array_change_key_case($row, CASE_LOWER); + + self::assertEquals(1, $row['test_int']); + self::assertEquals('foo', $row['test_string']); + } + + public function testFetchAssocWithTypes() : void + { + $datetimeString = '2010-01-01 10:10:10'; + $datetime = new DateTime($datetimeString); + + $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; + $row = $this->connection->fetchAssoc( + $sql, + [1, $datetime], + [ParameterType::STRING, Types::DATETIME_MUTABLE] + ); + + self::assertNotFalse($row); + + $row = array_change_key_case($row, CASE_LOWER); + + self::assertEquals(1, $row['test_int']); + self::assertStringStartsWith($datetimeString, $row['test_datetime']); + } + + public function testFetchAssocWithMissingTypes() : void + { + if ($this->connection->getDriver() instanceof MySQLiDriver || + $this->connection->getDriver() instanceof SQLSrvDriver) { + $this->markTestSkipped('mysqli and sqlsrv actually supports this'); + } + + $datetimeString = '2010-01-01 10:10:10'; + $datetime = new DateTime($datetimeString); + $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; + + $this->expectException(DBALException::class); + + $this->connection->fetchAssoc($sql, [1, $datetime]); + } + + public function testFetchArray() : void + { + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $row = $this->connection->fetchArray($sql, [1, 'foo']); + + self::assertEquals(1, $row[0]); + self::assertEquals('foo', $row[1]); + } + + public function testFetchArrayWithTypes() : void + { + $datetimeString = '2010-01-01 10:10:10'; + $datetime = new DateTime($datetimeString); + + $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; + $row = $this->connection->fetchArray( + $sql, + [1, $datetime], + [ParameterType::STRING, Types::DATETIME_MUTABLE] + ); + + self::assertNotFalse($row); + + $row = array_change_key_case($row, CASE_LOWER); + + self::assertEquals(1, $row[0]); + self::assertStringStartsWith($datetimeString, $row[1]); + } + + public function testFetchArrayWithMissingTypes() : void + { + if ($this->connection->getDriver() instanceof MySQLiDriver || + $this->connection->getDriver() instanceof SQLSrvDriver) { + $this->markTestSkipped('mysqli and sqlsrv actually supports this'); + } + + $datetimeString = '2010-01-01 10:10:10'; + $datetime = new DateTime($datetimeString); + $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; + + $this->expectException(DBALException::class); + + $this->connection->fetchArray($sql, [1, $datetime]); + } + + public function testFetchColumn() : void + { + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $testInt = $this->connection->fetchColumn($sql, [1, 'foo'], 0); + + self::assertEquals(1, $testInt); + + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $testString = $this->connection->fetchColumn($sql, [1, 'foo'], 1); + + self::assertEquals('foo', $testString); + } + + public function testFetchColumnWithTypes() : void + { + $datetimeString = '2010-01-01 10:10:10'; + $datetime = new DateTime($datetimeString); + + $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; + $column = $this->connection->fetchColumn( + $sql, + [1, $datetime], + 1, + [ParameterType::STRING, Types::DATETIME_MUTABLE] + ); + + self::assertNotFalse($column); + + self::assertStringStartsWith($datetimeString, $column); + } + + public function testFetchColumnWithMissingTypes() : void + { + if ($this->connection->getDriver() instanceof MySQLiDriver || + $this->connection->getDriver() instanceof SQLSrvDriver) { + $this->markTestSkipped('mysqli and sqlsrv actually supports this'); + } + + $datetimeString = '2010-01-01 10:10:10'; + $datetime = new DateTime($datetimeString); + $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; + + $this->expectException(DBALException::class); + + $this->connection->fetchColumn($sql, [1, $datetime], 1); + } + + /** + * @group DDC-697 + */ + public function testExecuteQueryBindDateTimeType() : void + { + $sql = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?'; + $stmt = $this->connection->executeQuery( + $sql, + [1 => new DateTime('2010-01-01 10:10:10')], + [1 => Types::DATETIME_MUTABLE] + ); + + self::assertEquals(1, $stmt->fetchColumn()); + } + + /** + * @group DDC-697 + */ + public function testExecuteUpdateBindDateTimeType() : void + { + $datetime = new DateTime('2010-02-02 20:20:20'); + + $sql = 'INSERT INTO fetch_table (test_int, test_string, test_datetime) VALUES (?, ?, ?)'; + $affectedRows = $this->connection->executeUpdate($sql, [ + 1 => 50, + 2 => 'foo', + 3 => $datetime, + ], [ + 1 => ParameterType::INTEGER, + 2 => ParameterType::STRING, + 3 => Types::DATETIME_MUTABLE, + ]); + + self::assertEquals(1, $affectedRows); + self::assertEquals(1, $this->connection->executeQuery( + 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?', + [1 => $datetime], + [1 => Types::DATETIME_MUTABLE] + )->fetchColumn()); + } + + /** + * @group DDC-697 + */ + public function testPrepareQueryBindValueDateTimeType() : void + { + $sql = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?'; + $stmt = $this->connection->prepare($sql); + $stmt->bindValue(1, new DateTime('2010-01-01 10:10:10'), Types::DATETIME_MUTABLE); + $stmt->execute(); + + self::assertEquals(1, $stmt->fetchColumn()); + } + + /** + * @group DBAL-78 + */ + public function testNativeArrayListSupport() : void + { + for ($i = 100; $i < 110; $i++) { + $this->connection->insert('fetch_table', ['test_int' => $i, 'test_string' => 'foo' . $i, 'test_datetime' => '2010-01-01 10:10:10']); + } + + $stmt = $this->connection->executeQuery( + 'SELECT test_int FROM fetch_table WHERE test_int IN (?)', + [[100, 101, 102, 103, 104]], + [Connection::PARAM_INT_ARRAY] + ); + + $data = $stmt->fetchAll(FetchMode::NUMERIC); + self::assertCount(5, $data); + self::assertEquals([[100], [101], [102], [103], [104]], $data); + + $stmt = $this->connection->executeQuery( + 'SELECT test_int FROM fetch_table WHERE test_string IN (?)', + [['foo100', 'foo101', 'foo102', 'foo103', 'foo104']], + [Connection::PARAM_STR_ARRAY] + ); + + $data = $stmt->fetchAll(FetchMode::NUMERIC); + self::assertCount(5, $data); + self::assertEquals([[100], [101], [102], [103], [104]], $data); + } + + /** + * @param string|false $char + * + * @dataProvider getTrimExpressionData + */ + public function testTrimExpression(string $value, int $position, $char, string $expectedResult) : void + { + $sql = 'SELECT ' . + $this->connection->getDatabasePlatform()->getTrimExpression($value, $position, $char) . ' AS trimmed ' . + 'FROM fetch_table'; + + $row = $this->connection->fetchAssoc($sql); + $row = array_change_key_case($row, CASE_LOWER); + + self::assertEquals($expectedResult, $row['trimmed']); + } + + /** + * @return array> + */ + public static function getTrimExpressionData() : iterable + { + return [ + ['test_string', TrimMode::UNSPECIFIED, false, 'foo'], + ['test_string', TrimMode::LEADING, false, 'foo'], + ['test_string', TrimMode::TRAILING, false, 'foo'], + ['test_string', TrimMode::BOTH, false, 'foo'], + ['test_string', TrimMode::UNSPECIFIED, "'f'", 'oo'], + ['test_string', TrimMode::UNSPECIFIED, "'o'", 'f'], + ['test_string', TrimMode::UNSPECIFIED, "'.'", 'foo'], + ['test_string', TrimMode::LEADING, "'f'", 'oo'], + ['test_string', TrimMode::LEADING, "'o'", 'foo'], + ['test_string', TrimMode::LEADING, "'.'", 'foo'], + ['test_string', TrimMode::TRAILING, "'f'", 'foo'], + ['test_string', TrimMode::TRAILING, "'o'", 'f'], + ['test_string', TrimMode::TRAILING, "'.'", 'foo'], + ['test_string', TrimMode::BOTH, "'f'", 'oo'], + ['test_string', TrimMode::BOTH, "'o'", 'f'], + ['test_string', TrimMode::BOTH, "'.'", 'foo'], + ["' foo '", TrimMode::UNSPECIFIED, false, 'foo'], + ["' foo '", TrimMode::LEADING, false, 'foo '], + ["' foo '", TrimMode::TRAILING, false, ' foo'], + ["' foo '", TrimMode::BOTH, false, 'foo'], + ["' foo '", TrimMode::UNSPECIFIED, "'f'", ' foo '], + ["' foo '", TrimMode::UNSPECIFIED, "'o'", ' foo '], + ["' foo '", TrimMode::UNSPECIFIED, "'.'", ' foo '], + ["' foo '", TrimMode::UNSPECIFIED, "' '", 'foo'], + ["' foo '", TrimMode::LEADING, "'f'", ' foo '], + ["' foo '", TrimMode::LEADING, "'o'", ' foo '], + ["' foo '", TrimMode::LEADING, "'.'", ' foo '], + ["' foo '", TrimMode::LEADING, "' '", 'foo '], + ["' foo '", TrimMode::TRAILING, "'f'", ' foo '], + ["' foo '", TrimMode::TRAILING, "'o'", ' foo '], + ["' foo '", TrimMode::TRAILING, "'.'", ' foo '], + ["' foo '", TrimMode::TRAILING, "' '", ' foo'], + ["' foo '", TrimMode::BOTH, "'f'", ' foo '], + ["' foo '", TrimMode::BOTH, "'o'", ' foo '], + ["' foo '", TrimMode::BOTH, "'.'", ' foo '], + ["' foo '", TrimMode::BOTH, "' '", 'foo'], + ]; + } + + /** + * @group DDC-1014 + */ + public function testDateArithmetics() : void + { + $p = $this->connection->getDatabasePlatform(); + $sql = 'SELECT '; + $sql .= $p->getDateAddSecondsExpression('test_datetime', 1) . ' AS add_seconds, '; + $sql .= $p->getDateSubSecondsExpression('test_datetime', 1) . ' AS sub_seconds, '; + $sql .= $p->getDateAddMinutesExpression('test_datetime', 5) . ' AS add_minutes, '; + $sql .= $p->getDateSubMinutesExpression('test_datetime', 5) . ' AS sub_minutes, '; + $sql .= $p->getDateAddHourExpression('test_datetime', 3) . ' AS add_hour, '; + $sql .= $p->getDateSubHourExpression('test_datetime', 3) . ' AS sub_hour, '; + $sql .= $p->getDateAddDaysExpression('test_datetime', 10) . ' AS add_days, '; + $sql .= $p->getDateSubDaysExpression('test_datetime', 10) . ' AS sub_days, '; + $sql .= $p->getDateAddWeeksExpression('test_datetime', 1) . ' AS add_weeks, '; + $sql .= $p->getDateSubWeeksExpression('test_datetime', 1) . ' AS sub_weeks, '; + $sql .= $p->getDateAddMonthExpression('test_datetime', 2) . ' AS add_month, '; + $sql .= $p->getDateSubMonthExpression('test_datetime', 2) . ' AS sub_month, '; + $sql .= $p->getDateAddQuartersExpression('test_datetime', 3) . ' AS add_quarters, '; + $sql .= $p->getDateSubQuartersExpression('test_datetime', 3) . ' AS sub_quarters, '; + $sql .= $p->getDateAddYearsExpression('test_datetime', 6) . ' AS add_years, '; + $sql .= $p->getDateSubYearsExpression('test_datetime', 6) . ' AS sub_years '; + $sql .= 'FROM fetch_table'; + + $row = $this->connection->fetchAssoc($sql); + $row = array_change_key_case($row, CASE_LOWER); + + self::assertEquals('2010-01-01 10:10:11', date('Y-m-d H:i:s', strtotime($row['add_seconds'])), 'Adding second should end up on 2010-01-01 10:10:11'); + self::assertEquals('2010-01-01 10:10:09', date('Y-m-d H:i:s', strtotime($row['sub_seconds'])), 'Subtracting second should end up on 2010-01-01 10:10:09'); + self::assertEquals('2010-01-01 10:15:10', date('Y-m-d H:i:s', strtotime($row['add_minutes'])), 'Adding minutes should end up on 2010-01-01 10:15:10'); + self::assertEquals('2010-01-01 10:05:10', date('Y-m-d H:i:s', strtotime($row['sub_minutes'])), 'Subtracting minutes should end up on 2010-01-01 10:05:10'); + self::assertEquals('2010-01-01 13:10', date('Y-m-d H:i', strtotime($row['add_hour'])), 'Adding date should end up on 2010-01-01 13:10'); + self::assertEquals('2010-01-01 07:10', date('Y-m-d H:i', strtotime($row['sub_hour'])), 'Subtracting date should end up on 2010-01-01 07:10'); + self::assertEquals('2010-01-11', date('Y-m-d', strtotime($row['add_days'])), 'Adding date should end up on 2010-01-11'); + self::assertEquals('2009-12-22', date('Y-m-d', strtotime($row['sub_days'])), 'Subtracting date should end up on 2009-12-22'); + self::assertEquals('2010-01-08', date('Y-m-d', strtotime($row['add_weeks'])), 'Adding week should end up on 2010-01-08'); + self::assertEquals('2009-12-25', date('Y-m-d', strtotime($row['sub_weeks'])), 'Subtracting week should end up on 2009-12-25'); + self::assertEquals('2010-03-01', date('Y-m-d', strtotime($row['add_month'])), 'Adding month should end up on 2010-03-01'); + self::assertEquals('2009-11-01', date('Y-m-d', strtotime($row['sub_month'])), 'Subtracting month should end up on 2009-11-01'); + self::assertEquals('2010-10-01', date('Y-m-d', strtotime($row['add_quarters'])), 'Adding quarters should end up on 2010-04-01'); + self::assertEquals('2009-04-01', date('Y-m-d', strtotime($row['sub_quarters'])), 'Subtracting quarters should end up on 2009-10-01'); + self::assertEquals('2016-01-01', date('Y-m-d', strtotime($row['add_years'])), 'Adding years should end up on 2016-01-01'); + self::assertEquals('2004-01-01', date('Y-m-d', strtotime($row['sub_years'])), 'Subtracting years should end up on 2004-01-01'); + } + + public function testSqliteDateArithmeticWithDynamicInterval() : void + { + $platform = $this->connection->getDatabasePlatform(); + + if (! $platform instanceof SqlitePlatform) { + $this->markTestSkipped('test is for sqlite only'); + } + + $table = new Table('fetch_table_date_math'); + $table->addColumn('test_date', 'date'); + $table->addColumn('test_days', 'integer'); + $table->setPrimaryKey(['test_date']); + + $sm = $this->connection->getSchemaManager(); + $sm->createTable($table); + + $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-01-01', 'test_days' => 10]); + $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-06-01', 'test_days' => 20]); + + $sql = 'SELECT COUNT(*) FROM fetch_table_date_math WHERE '; + $sql .= $platform->getDateSubDaysExpression('test_date', 'test_days') . " < '2010-05-12'"; + + $rowCount = $this->connection->fetchColumn($sql, [], 0); + + $this->assertEquals(1, $rowCount); + } + + public function testLocateExpression() : void + { + $platform = $this->connection->getDatabasePlatform(); + + $sql = 'SELECT '; + $sql .= $platform->getLocateExpression('test_string', "'oo'") . ' AS locate1, '; + $sql .= $platform->getLocateExpression('test_string', "'foo'") . ' AS locate2, '; + $sql .= $platform->getLocateExpression('test_string', "'bar'") . ' AS locate3, '; + $sql .= $platform->getLocateExpression('test_string', 'test_string') . ' AS locate4, '; + $sql .= $platform->getLocateExpression("'foo'", 'test_string') . ' AS locate5, '; + $sql .= $platform->getLocateExpression("'barfoobaz'", 'test_string') . ' AS locate6, '; + $sql .= $platform->getLocateExpression("'bar'", 'test_string') . ' AS locate7, '; + $sql .= $platform->getLocateExpression('test_string', "'oo'", 2) . ' AS locate8, '; + $sql .= $platform->getLocateExpression('test_string', "'oo'", 3) . ' AS locate9 '; + $sql .= 'FROM fetch_table'; + + $row = $this->connection->fetchAssoc($sql); + $row = array_change_key_case($row, CASE_LOWER); + + self::assertEquals(2, $row['locate1']); + self::assertEquals(1, $row['locate2']); + self::assertEquals(0, $row['locate3']); + self::assertEquals(1, $row['locate4']); + self::assertEquals(1, $row['locate5']); + self::assertEquals(4, $row['locate6']); + self::assertEquals(0, $row['locate7']); + self::assertEquals(2, $row['locate8']); + self::assertEquals(0, $row['locate9']); + } + + public function testQuoteSQLInjection() : void + { + $sql = 'SELECT * FROM fetch_table WHERE test_string = ' . $this->connection->quote("bar' OR '1'='1"); + $rows = $this->connection->fetchAll($sql); + + self::assertCount(0, $rows, 'no result should be returned, otherwise SQL injection is possible'); + } + + /** + * @group DDC-1213 + */ + public function testBitComparisonExpressionSupport() : void + { + $this->connection->exec('DELETE FROM fetch_table'); + $platform = $this->connection->getDatabasePlatform(); + $bitmap = []; + + for ($i = 2; $i < 9; $i += 2) { + $bitmap[$i] = [ + 'bit_or' => ($i | 2), + 'bit_and' => ($i & 2), + ]; + $this->connection->insert('fetch_table', [ + 'test_int' => $i, + 'test_string' => json_encode($bitmap[$i]), + 'test_datetime' => '2010-01-01 10:10:10', + ]); + } + + $sql[] = 'SELECT '; + $sql[] = 'test_int, '; + $sql[] = 'test_string, '; + $sql[] = $platform->getBitOrComparisonExpression('test_int', 2) . ' AS bit_or, '; + $sql[] = $platform->getBitAndComparisonExpression('test_int', 2) . ' AS bit_and '; + $sql[] = 'FROM fetch_table'; + + $stmt = $this->connection->executeQuery(implode(PHP_EOL, $sql)); + $data = $stmt->fetchAll(FetchMode::ASSOCIATIVE); + + self::assertCount(4, $data); + self::assertEquals(count($bitmap), count($data)); + foreach ($data as $row) { + $row = array_change_key_case($row, CASE_LOWER); + + self::assertArrayHasKey('test_int', $row); + + $id = $row['test_int']; + + self::assertArrayHasKey($id, $bitmap); + self::assertArrayHasKey($id, $bitmap); + + self::assertArrayHasKey('bit_or', $row); + self::assertArrayHasKey('bit_and', $row); + + self::assertEquals($row['bit_or'], $bitmap[$id]['bit_or']); + self::assertEquals($row['bit_and'], $bitmap[$id]['bit_and']); + } + } + + public function testSetDefaultFetchMode() : void + { + $stmt = $this->connection->query('SELECT * FROM fetch_table'); + $stmt->setFetchMode(FetchMode::NUMERIC); + + $row = array_keys($stmt->fetch()); + self::assertCount(0, array_filter($row, static function ($v) { + return ! is_numeric($v); + }), 'should be no non-numerical elements in the result.'); + } + + /** + * @group DBAL-1091 + */ + public function testFetchAllStyleObject() : void + { + $this->setupFixture(); + + $sql = 'SELECT test_int, test_string, test_datetime FROM fetch_table'; + $stmt = $this->connection->prepare($sql); + + $stmt->execute(); + + $results = $stmt->fetchAll(FetchMode::STANDARD_OBJECT); + + self::assertCount(1, $results); + self::assertInstanceOf('stdClass', $results[0]); + + self::assertEquals( + 1, + property_exists($results[0], 'test_int') ? $results[0]->test_int : $results[0]->TEST_INT + ); + self::assertEquals( + 'foo', + property_exists($results[0], 'test_string') ? $results[0]->test_string : $results[0]->TEST_STRING + ); + self::assertStringStartsWith( + '2010-01-01 10:10:10', + property_exists($results[0], 'test_datetime') ? $results[0]->test_datetime : $results[0]->TEST_DATETIME + ); + } + + /** + * @group DBAL-196 + */ + public function testFetchAllSupportFetchClass() : void + { + $this->beforeFetchClassTest(); + $this->setupFixture(); + + $sql = 'SELECT test_int, test_string, test_datetime FROM fetch_table'; + $stmt = $this->connection->prepare($sql); + $stmt->execute(); + + $results = $stmt->fetchAll( + FetchMode::CUSTOM_OBJECT, + MyFetchClass::class + ); + + self::assertCount(1, $results); + self::assertInstanceOf(MyFetchClass::class, $results[0]); + + self::assertEquals(1, $results[0]->test_int); + self::assertEquals('foo', $results[0]->test_string); + self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime); + } + + /** + * @group DBAL-241 + */ + public function testFetchAllStyleColumn() : void + { + $sql = 'DELETE FROM fetch_table'; + $this->connection->executeUpdate($sql); + + $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo']); + $this->connection->insert('fetch_table', ['test_int' => 10, 'test_string' => 'foo']); + + $sql = 'SELECT test_int FROM fetch_table'; + $rows = $this->connection->query($sql)->fetchAll(FetchMode::COLUMN); + + self::assertEquals([1, 10], $rows); + } + + /** + * @group DBAL-214 + */ + public function testSetFetchModeClassFetchAll() : void + { + $this->beforeFetchClassTest(); + $this->setupFixture(); + + $sql = 'SELECT * FROM fetch_table'; + $stmt = $this->connection->query($sql); + $stmt->setFetchMode(FetchMode::CUSTOM_OBJECT, MyFetchClass::class); + + $results = $stmt->fetchAll(); + + self::assertCount(1, $results); + self::assertInstanceOf(MyFetchClass::class, $results[0]); + + self::assertEquals(1, $results[0]->test_int); + self::assertEquals('foo', $results[0]->test_string); + self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime); + } + + /** + * @group DBAL-214 + */ + public function testSetFetchModeClassFetch() : void + { + $this->beforeFetchClassTest(); + $this->setupFixture(); + + $sql = 'SELECT * FROM fetch_table'; + $stmt = $this->connection->query($sql); + $stmt->setFetchMode(FetchMode::CUSTOM_OBJECT, MyFetchClass::class); + + $results = []; + while ($row = $stmt->fetch()) { + $results[] = $row; + } + + self::assertCount(1, $results); + self::assertInstanceOf(MyFetchClass::class, $results[0]); + + self::assertEquals(1, $results[0]->test_int); + self::assertEquals('foo', $results[0]->test_string); + self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime); + } + + /** + * @group DBAL-257 + */ + public function testEmptyFetchColumnReturnsFalse() : void + { + $this->connection->beginTransaction(); + $this->connection->exec('DELETE FROM fetch_table'); + self::assertFalse($this->connection->fetchColumn('SELECT test_int FROM fetch_table')); + self::assertFalse($this->connection->query('SELECT test_int FROM fetch_table')->fetchColumn()); + $this->connection->rollBack(); + } + + /** + * @group DBAL-339 + */ + public function testSetFetchModeOnDbalStatement() : void + { + $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; + $stmt = $this->connection->executeQuery($sql, [1, 'foo']); + $stmt->setFetchMode(FetchMode::NUMERIC); + + $row = $stmt->fetch(); + + self::assertArrayHasKey(0, $row); + self::assertArrayHasKey(1, $row); + self::assertFalse($stmt->fetch()); + } + + /** + * @group DBAL-435 + */ + public function testEmptyParameters() : void + { + $sql = 'SELECT * FROM fetch_table WHERE test_int IN (?)'; + $stmt = $this->connection->executeQuery($sql, [[]], [Connection::PARAM_INT_ARRAY]); + $rows = $stmt->fetchAll(); + + self::assertEquals([], $rows); + } + + /** + * @group DBAL-1028 + */ + public function testFetchColumnNullValue() : void + { + $this->connection->executeUpdate( + 'INSERT INTO fetch_table (test_int, test_string) VALUES (?, ?)', + [2, 'foo'] + ); + + self::assertNull( + $this->connection->fetchColumn('SELECT test_datetime FROM fetch_table WHERE test_int = ?', [2]) + ); + } + + /** + * @group DBAL-1028 + */ + public function testFetchColumnNoResult() : void + { + self::assertFalse( + $this->connection->fetchColumn('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1]) + ); + } + + private function setupFixture() : void + { + $this->connection->exec('DELETE FROM fetch_table'); + $this->connection->insert('fetch_table', [ + 'test_int' => 1, + 'test_string' => 'foo', + 'test_datetime' => '2010-01-01 10:10:10', + ]); + } + + private function beforeFetchClassTest() : void + { + $driver = $this->connection->getDriver(); + + if ($driver instanceof Oci8Driver) { + $this->markTestSkipped('Not supported by OCI8'); + } + + if ($driver instanceof MySQLiDriver) { + $this->markTestSkipped('Mysqli driver dont support this feature.'); + } + + if (! $driver instanceof PDOOracleDriver) { + return; + } + + /** @var PDOConnection $connection */ + $connection = $this->connection->getWrappedConnection(); + $connection->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + } +} + +class MyFetchClass +{ + /** @var int */ + public $test_int; + + /** @var string */ + public $test_string; + + /** @var string */ + public $test_datetime; +} diff --git a/tests/Functional/DBAL/Driver/AbstractDriverTest.php b/tests/Functional/DBAL/Driver/AbstractDriverTest.php new file mode 100644 index 0000000..030cebb --- /dev/null +++ b/tests/Functional/DBAL/Driver/AbstractDriverTest.php @@ -0,0 +1,69 @@ +driver = $this->createDriver(); + } + + /** + * @group DBAL-1215 + */ + public function testConnectsWithoutDatabaseNameParameter() : void + { + $params = $this->connection->getParams(); + unset($params['dbname']); + + $user = $params['user'] ?? null; + $password = $params['password'] ?? null; + + $connection = $this->driver->connect($params, $user, $password); + + self::assertInstanceOf(DriverConnection::class, $connection); + } + + /** + * @group DBAL-1215 + */ + public function testReturnsDatabaseNameWithoutDatabaseNameParameter() : void + { + $params = $this->connection->getParams(); + unset($params['dbname']); + + $connection = new Connection( + $params, + $this->connection->getDriver(), + $this->connection->getConfiguration(), + $this->connection->getEventManager() + ); + + self::assertSame( + static::getDatabaseNameForConnectionWithoutDatabaseNameParameter(), + $this->driver->getDatabase($connection) + ); + } + + abstract protected function createDriver() : Driver; + + protected static function getDatabaseNameForConnectionWithoutDatabaseNameParameter() : ?string + { + return null; + } +} diff --git a/tests/Functional/DBAL/Driver/PDOConnectionTest.php b/tests/Functional/DBAL/Driver/PDOConnectionTest.php new file mode 100644 index 0000000..9b784ea --- /dev/null +++ b/tests/Functional/DBAL/Driver/PDOConnectionTest.php @@ -0,0 +1,71 @@ +markTestSkipped('PDO is not installed.'); + } + + parent::setUp(); + + $this->driverConnection = $this->connection->getWrappedConnection(); + + if ($this->driverConnection instanceof PDOConnection) { + return; + } + + $this->markTestSkipped('PDO connection only test.'); + } + + protected function tearDown() : void + { + $this->resetSharedConn(); + + parent::tearDown(); + } + + public function testDoesNotRequireQueryForServerVersion() : void + { + self::assertFalse($this->driverConnection->requiresQueryForServerVersion()); + } + + public function testThrowsWrappedExceptionOnConstruct() : void + { + $this->expectException(PDOException::class); + + new PDOConnection('foo'); + } + + /** + * @group DBAL-1022 + */ + public function testThrowsWrappedExceptionOnExec() : void + { + $this->expectException(PDOException::class); + + $this->driverConnection->exec('foo'); + } + + public function testThrowsWrappedExceptionOnQuery() : void + { + $this->expectException(PDOException::class); + + $this->driverConnection->query('foo'); + } +} diff --git a/tests/Functional/DBAL/Driver/PDOPgSql/DriverTest.php b/tests/Functional/DBAL/Driver/PDOPgSql/DriverTest.php new file mode 100644 index 0000000..d3907b9 --- /dev/null +++ b/tests/Functional/DBAL/Driver/PDOPgSql/DriverTest.php @@ -0,0 +1,79 @@ +markTestSkipped('pdo_pgsql is not installed.'); + } + + parent::setUp(); + + if ($this->connection->getDriver() instanceof Driver) { + return; + } + + $this->markTestSkipped('pdo_pgsql only test.'); + } + + /** + * @group DBAL-1146 + */ + public function testConnectsWithApplicationNameParameter() : void + { + $parameters = $this->connection->getParams(); + $parameters['application_name'] = 'doctrine'; + + $user = $parameters['user'] ?? null; + $password = $parameters['password'] ?? null; + + $connection = $this->driver->connect($parameters, $user, $password); + + $hash = microtime(true); // required to identify the record in the results uniquely + $sql = sprintf('SELECT * FROM pg_stat_activity WHERE %d = %d', $hash, $hash); + $statement = $connection->query($sql); + $records = $statement->fetchAll(); + + foreach ($records as $record) { + // The query column is named "current_query" on PostgreSQL < 9.2 + $queryColumnName = array_key_exists('current_query', $record) ? 'current_query' : 'query'; + + if ($record[$queryColumnName] === $sql) { + self::assertSame('doctrine', $record['application_name']); + + return; + } + } + + $this->fail(sprintf('Query result does not contain a record where column "query" equals "%s".', $sql)); + } + + /** + * {@inheritdoc} + */ + protected function createDriver() : DriverInterface + { + return new Driver(); + } + + /** + * {@inheritdoc} + */ + protected static function getDatabaseNameForConnectionWithoutDatabaseNameParameter() : ?string + { + return 'postgres'; + } +} diff --git a/tests/Functional/DBAL/Driver/PDOPgsqlConnectionTest.php b/tests/Functional/DBAL/Driver/PDOPgsqlConnectionTest.php new file mode 100644 index 0000000..96888b9 --- /dev/null +++ b/tests/Functional/DBAL/Driver/PDOPgsqlConnectionTest.php @@ -0,0 +1,61 @@ +markTestSkipped('pdo_pgsql is not loaded.'); + } + + parent::setUp(); + + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return; + } + + $this->markTestSkipped('PDOPgsql only test.'); + } + + /** + * @group DBAL-1183 + * @group DBAL-1189 + * @dataProvider getValidCharsets + */ + public function testConnectsWithValidCharsetOption(string $charset) : void + { + $params = $this->connection->getParams(); + $params['charset'] = $charset; + + $connection = DriverManager::getConnection( + $params, + $this->connection->getConfiguration(), + $this->connection->getEventManager() + ); + + self::assertEquals( + $charset, + $connection->query('SHOW client_encoding') + ->fetch(FetchMode::COLUMN) + ); + } + + /** + * @return mixed[][] + */ + public static function getValidCharsets() : iterable + { + return [ + ['UTF8'], + ['LATIN1'], + ]; + } +} diff --git a/tests/Functional/DBAL/ExceptionTest.php b/tests/Functional/DBAL/ExceptionTest.php new file mode 100644 index 0000000..853b039 --- /dev/null +++ b/tests/Functional/DBAL/ExceptionTest.php @@ -0,0 +1,426 @@ +connection->getDriver() instanceof ExceptionConverterDriver) { + return; + } + + $this->markTestSkipped('Driver does not support special exception handling.'); + } + + public function testPrimaryConstraintViolationException() : void + { + $table = new Table('duplicatekey_table'); + $table->addColumn('id', 'integer', []); + $table->setPrimaryKey(['id']); + + $this->connection->getSchemaManager()->createTable($table); + + $this->connection->insert('duplicatekey_table', ['id' => 1]); + + $this->expectException(Exception\UniqueConstraintViolationException::class); + $this->connection->insert('duplicatekey_table', ['id' => 1]); + } + + public function testTableNotFoundException() : void + { + $sql = 'SELECT * FROM unknown_table'; + + $this->expectException(Exception\TableNotFoundException::class); + $this->connection->executeQuery($sql); + } + + public function testTableExistsException() : void + { + $schemaManager = $this->connection->getSchemaManager(); + $table = new Table('alreadyexist_table'); + $table->addColumn('id', 'integer', []); + $table->setPrimaryKey(['id']); + + $this->expectException(Exception\TableExistsException::class); + $schemaManager->createTable($table); + $schemaManager->createTable($table); + } + + public function testForeignKeyConstraintViolationExceptionOnInsert() : void + { + if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('Only fails on platforms with foreign key constraints.'); + } + + $this->setUpForeignKeyConstraintViolationExceptionTest(); + + try { + $this->connection->insert('constraint_error_table', ['id' => 1]); + $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]); + } catch (Throwable $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } + + $this->expectException(Exception\ForeignKeyConstraintViolationException::class); + + try { + $this->connection->insert('owning_table', ['id' => 2, 'constraint_id' => 2]); + } catch (Exception\ForeignKeyConstraintViolationException $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } catch (Throwable $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } + + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + } + + public function testForeignKeyConstraintViolationExceptionOnUpdate() : void + { + if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('Only fails on platforms with foreign key constraints.'); + } + + $this->setUpForeignKeyConstraintViolationExceptionTest(); + + try { + $this->connection->insert('constraint_error_table', ['id' => 1]); + $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]); + } catch (Throwable $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } + + $this->expectException(Exception\ForeignKeyConstraintViolationException::class); + + try { + $this->connection->update('constraint_error_table', ['id' => 2], ['id' => 1]); + } catch (Exception\ForeignKeyConstraintViolationException $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } catch (Throwable $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } + + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + } + + public function testForeignKeyConstraintViolationExceptionOnDelete() : void + { + if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('Only fails on platforms with foreign key constraints.'); + } + + $this->setUpForeignKeyConstraintViolationExceptionTest(); + + try { + $this->connection->insert('constraint_error_table', ['id' => 1]); + $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]); + } catch (Throwable $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } + + $this->expectException(Exception\ForeignKeyConstraintViolationException::class); + + try { + $this->connection->delete('constraint_error_table', ['id' => 1]); + } catch (Exception\ForeignKeyConstraintViolationException $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } catch (Throwable $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } + + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + } + + public function testForeignKeyConstraintViolationExceptionOnTruncate() : void + { + $platform = $this->connection->getDatabasePlatform(); + + if (! $platform->supportsForeignKeyConstraints()) { + $this->markTestSkipped('Only fails on platforms with foreign key constraints.'); + } + + $this->setUpForeignKeyConstraintViolationExceptionTest(); + + try { + $this->connection->insert('constraint_error_table', ['id' => 1]); + $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]); + } catch (Throwable $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } + + $this->expectException(Exception\ForeignKeyConstraintViolationException::class); + + try { + $this->connection->executeUpdate($platform->getTruncateTableSQL('constraint_error_table')); + } catch (Exception\ForeignKeyConstraintViolationException $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } catch (Throwable $exception) { + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + + throw $exception; + } + + $this->tearDownForeignKeyConstraintViolationExceptionTest(); + } + + public function testNotNullConstraintViolationException() : void + { + $schema = new Schema(); + + $table = $schema->createTable('notnull_table'); + $table->addColumn('id', 'integer', []); + $table->addColumn('value', 'integer', ['notnull' => true]); + $table->setPrimaryKey(['id']); + + foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { + $this->connection->exec($sql); + } + + $this->expectException(Exception\NotNullConstraintViolationException::class); + $this->connection->insert('notnull_table', ['id' => 1, 'value' => null]); + } + + public function testInvalidFieldNameException() : void + { + $schema = new Schema(); + + $table = $schema->createTable('bad_fieldname_table'); + $table->addColumn('id', 'integer', []); + + foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { + $this->connection->exec($sql); + } + + $this->expectException(Exception\InvalidFieldNameException::class); + $this->connection->insert('bad_fieldname_table', ['name' => 5]); + } + + public function testNonUniqueFieldNameException() : void + { + $schema = new Schema(); + + $table = $schema->createTable('ambiguous_list_table'); + $table->addColumn('id', 'integer'); + + $table2 = $schema->createTable('ambiguous_list_table_2'); + $table2->addColumn('id', 'integer'); + + foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { + $this->connection->exec($sql); + } + + $sql = 'SELECT id FROM ambiguous_list_table, ambiguous_list_table_2'; + $this->expectException(Exception\NonUniqueFieldNameException::class); + $this->connection->executeQuery($sql); + } + + public function testUniqueConstraintViolationException() : void + { + $schema = new Schema(); + + $table = $schema->createTable('unique_field_table'); + $table->addColumn('id', 'integer'); + $table->addUniqueIndex(['id']); + + foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { + $this->connection->exec($sql); + } + + $this->connection->insert('unique_field_table', ['id' => 5]); + $this->expectException(Exception\UniqueConstraintViolationException::class); + $this->connection->insert('unique_field_table', ['id' => 5]); + } + + public function testSyntaxErrorException() : void + { + $table = new Table('syntax_error_table'); + $table->addColumn('id', 'integer', []); + $table->setPrimaryKey(['id']); + + $this->connection->getSchemaManager()->createTable($table); + + $sql = 'SELECT id FRO syntax_error_table'; + $this->expectException(Exception\SyntaxErrorException::class); + $this->connection->executeQuery($sql); + } + + public function testConnectionExceptionSqLite() : void + { + if ($this->connection->getDatabasePlatform()->getName() !== 'sqlite') { + $this->markTestSkipped('Only fails this way on sqlite'); + } + + // mode 0 is considered read-only on Windows + $mode = PHP_OS === 'Linux' ? 0444 : 0000; + + $filename = sprintf('%s/%s', sys_get_temp_dir(), 'doctrine_failed_connection_' . $mode . '.db'); + + if (file_exists($filename)) { + $this->cleanupReadOnlyFile($filename); + } + + touch($filename); + chmod($filename, $mode); + + if ($this->isLinuxRoot()) { + exec(sprintf('chattr +i %s', $filename)); + } + + $params = [ + 'driver' => 'pdo_sqlite', + 'path' => $filename, + ]; + $conn = DriverManager::getConnection($params); + + $schema = new Schema(); + $table = $schema->createTable('no_connection'); + $table->addColumn('id', 'integer'); + + $this->expectException(Exception\ReadOnlyException::class); + $this->expectExceptionMessage(<<toSql($conn->getDatabasePlatform()) as $sql) { + $conn->exec($sql); + } + } finally { + $this->cleanupReadOnlyFile($filename); + } + } + + /** + * @param array $params + * + * @dataProvider getConnectionParams + */ + public function testConnectionException(array $params) : void + { + if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') { + $this->markTestSkipped('Only skipped if platform is not sqlite'); + } + + if ($this->connection->getDatabasePlatform()->getName() === 'drizzle') { + $this->markTestSkipped('Drizzle does not always support authentication'); + } + + if ($this->connection->getDatabasePlatform()->getName() === 'postgresql' && isset($params['password'])) { + $this->markTestSkipped('Does not work on Travis'); + } + + $defaultParams = $this->connection->getParams(); + $params = array_merge($defaultParams, $params); + + $conn = DriverManager::getConnection($params); + + $schema = new Schema(); + $table = $schema->createTable('no_connection'); + $table->addColumn('id', 'integer'); + + $this->expectException(Exception\ConnectionException::class); + + foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) { + $conn->exec($sql); + } + } + + /** + * @return array> + */ + public static function getConnectionParams() : iterable + { + return [ + [['user' => 'not_existing']], + [['password' => 'really_not']], + [['host' => 'localnope']], + ]; + } + + private function setUpForeignKeyConstraintViolationExceptionTest() : void + { + $schemaManager = $this->connection->getSchemaManager(); + + $table = new Table('constraint_error_table'); + $table->addColumn('id', 'integer', []); + $table->setPrimaryKey(['id']); + + $owningTable = new Table('owning_table'); + $owningTable->addColumn('id', 'integer', []); + $owningTable->addColumn('constraint_id', 'integer', []); + $owningTable->setPrimaryKey(['id']); + $owningTable->addForeignKeyConstraint($table, ['constraint_id'], ['id']); + + $schemaManager->createTable($table); + $schemaManager->createTable($owningTable); + } + + private function tearDownForeignKeyConstraintViolationExceptionTest() : void + { + $schemaManager = $this->connection->getSchemaManager(); + + $schemaManager->dropTable('owning_table'); + $schemaManager->dropTable('constraint_error_table'); + } + + private function isLinuxRoot() : bool + { + return PHP_OS === 'Linux' && posix_getpwuid(posix_geteuid())['name'] === 'root'; + } + + private function cleanupReadOnlyFile(string $filename) : void + { + if ($this->isLinuxRoot()) { + exec(sprintf('chattr -i %s', $filename)); + } + + chmod($filename, 0200); // make the file writable again, so it can be removed on Windows + unlink($filename); + } +} diff --git a/tests/Functional/DBAL/LikeWildcardsEscapingTest.php b/tests/Functional/DBAL/LikeWildcardsEscapingTest.php new file mode 100644 index 0000000..a709022 --- /dev/null +++ b/tests/Functional/DBAL/LikeWildcardsEscapingTest.php @@ -0,0 +1,28 @@ +connection->getDatabasePlatform(); + $stmt = $this->connection->prepare( + $databasePlatform->getDummySelectSQL( + sprintf( + "(CASE WHEN '%s' LIKE '%s' ESCAPE '%s' THEN 1 ELSE 0 END)", + $string, + $databasePlatform->escapeStringForLike($string, $escapeChar), + $escapeChar + ) + ) + ); + $stmt->execute(); + $this->assertTrue((bool) $stmt->fetchColumn()); + } +} diff --git a/tests/Functional/DBAL/LoggingTest.php b/tests/Functional/DBAL/LoggingTest.php new file mode 100644 index 0000000..ae6574c --- /dev/null +++ b/tests/Functional/DBAL/LoggingTest.php @@ -0,0 +1,55 @@ +connection->getDatabasePlatform()->getDummySelectSQL(); + + $logMock = $this->createMock(SQLLogger::class); + $logMock->expects($this->at(0)) + ->method('startQuery') + ->with($this->equalTo($sql), $this->equalTo([]), $this->equalTo([])); + $logMock->expects($this->at(1)) + ->method('stopQuery'); + $this->connection->getConfiguration()->setSQLLogger($logMock); + $this->connection->executeQuery($sql, []); + } + + public function testLogExecuteUpdate() : void + { + $this->markTestSkipped('Test breaks MySQL but works on all other platforms (Unbuffered Queries stuff).'); + + $sql = $this->connection->getDatabasePlatform()->getDummySelectSQL(); + + $logMock = $this->createMock(SQLLogger::class); + $logMock->expects($this->at(0)) + ->method('startQuery') + ->with($this->equalTo($sql), $this->equalTo([]), $this->equalTo([])); + $logMock->expects($this->at(1)) + ->method('stopQuery'); + $this->connection->getConfiguration()->setSQLLogger($logMock); + $this->connection->executeUpdate($sql, []); + } + + public function testLogPrepareExecute() : void + { + $sql = $this->connection->getDatabasePlatform()->getDummySelectSQL(); + + $logMock = $this->createMock(SQLLogger::class); + $logMock->expects($this->once()) + ->method('startQuery') + ->with($this->equalTo($sql), $this->equalTo([])); + $logMock->expects($this->at(1)) + ->method('stopQuery'); + $this->connection->getConfiguration()->setSQLLogger($logMock); + + $stmt = $this->connection->prepare($sql); + $stmt->execute(); + } +} diff --git a/tests/Functional/DBAL/MasterSlaveConnectionTest.php b/tests/Functional/DBAL/MasterSlaveConnectionTest.php new file mode 100644 index 0000000..917655e --- /dev/null +++ b/tests/Functional/DBAL/MasterSlaveConnectionTest.php @@ -0,0 +1,241 @@ +connection->getDatabasePlatform()->getName(); + + // This is a MySQL specific test, skip other vendors. + if ($platformName !== 'mysql') { + $this->markTestSkipped(sprintf('Test does not work on %s.', $platformName)); + } + + try { + $table = new Table('master_slave_table'); + $table->addColumn('test_int', 'integer'); + $table->setPrimaryKey(['test_int']); + + $sm = $this->connection->getSchemaManager(); + $sm->createTable($table); + } catch (Throwable $e) { + } + + $this->connection->executeUpdate('DELETE FROM master_slave_table'); + $this->connection->insert('master_slave_table', ['test_int' => 1]); + } + + private function createMasterSlaveConnection(bool $keepSlave = false) : MasterSlaveConnection + { + return DriverManager::getConnection($this->createMasterSlaveConnectionParams($keepSlave)); + } + + /** + * @return mixed[] + */ + private function createMasterSlaveConnectionParams(bool $keepSlave = false) : array + { + $params = $this->connection->getParams(); + $params['master'] = $params; + $params['slaves'] = [$params, $params]; + $params['keepSlave'] = $keepSlave; + $params['wrapperClass'] = MasterSlaveConnection::class; + + return $params; + } + + public function testInheritCharsetFromMaster() : void + { + $charsets = [ + 'utf8', + 'latin1', + ]; + + foreach ($charsets as $charset) { + $params = $this->createMasterSlaveConnectionParams(); + $params['master']['charset'] = $charset; + + foreach ($params['slaves'] as $index => $slaveParams) { + if (! isset($slaveParams['charset'])) { + continue; + } + + unset($params['slaves'][$index]['charset']); + } + + /** @var MasterSlaveConnection $conn */ + $conn = DriverManager::getConnection($params); + $conn->connect('slave'); + + self::assertFalse($conn->isConnectedToMaster()); + + $clientCharset = $conn->fetchColumn('select @@character_set_client as c'); + + self::assertSame( + $charset, + substr(strtolower($clientCharset), 0, strlen($charset)) + ); + } + } + + public function testMasterOnConnect() : void + { + $conn = $this->createMasterSlaveConnection(); + + self::assertFalse($conn->isConnectedToMaster()); + $conn->connect('slave'); + self::assertFalse($conn->isConnectedToMaster()); + $conn->connect('master'); + self::assertTrue($conn->isConnectedToMaster()); + } + + public function testNoMasterOnExecuteQuery() : void + { + $conn = $this->createMasterSlaveConnection(); + + $sql = 'SELECT count(*) as num FROM master_slave_table'; + $data = $conn->fetchAll($sql); + $data[0] = array_change_key_case($data[0], CASE_LOWER); + + self::assertEquals(1, $data[0]['num']); + self::assertFalse($conn->isConnectedToMaster()); + } + + public function testMasterOnWriteOperation() : void + { + $conn = $this->createMasterSlaveConnection(); + $conn->insert('master_slave_table', ['test_int' => 30]); + + self::assertTrue($conn->isConnectedToMaster()); + + $sql = 'SELECT count(*) as num FROM master_slave_table'; + $data = $conn->fetchAll($sql); + $data[0] = array_change_key_case($data[0], CASE_LOWER); + + self::assertEquals(2, $data[0]['num']); + self::assertTrue($conn->isConnectedToMaster()); + } + + /** + * @group DBAL-335 + */ + public function testKeepSlaveBeginTransactionStaysOnMaster() : void + { + $conn = $this->createMasterSlaveConnection($keepSlave = true); + $conn->connect('slave'); + + $conn->beginTransaction(); + $conn->insert('master_slave_table', ['test_int' => 30]); + $conn->commit(); + + self::assertTrue($conn->isConnectedToMaster()); + + $conn->connect(); + self::assertTrue($conn->isConnectedToMaster()); + + $conn->connect('slave'); + self::assertFalse($conn->isConnectedToMaster()); + } + + /** + * @group DBAL-335 + */ + public function testKeepSlaveInsertStaysOnMaster() : void + { + $conn = $this->createMasterSlaveConnection($keepSlave = true); + $conn->connect('slave'); + + $conn->insert('master_slave_table', ['test_int' => 30]); + + self::assertTrue($conn->isConnectedToMaster()); + + $conn->connect(); + self::assertTrue($conn->isConnectedToMaster()); + + $conn->connect('slave'); + self::assertFalse($conn->isConnectedToMaster()); + } + + public function testMasterSlaveConnectionCloseAndReconnect() : void + { + $conn = $this->createMasterSlaveConnection(); + $conn->connect('master'); + self::assertTrue($conn->isConnectedToMaster()); + + $conn->close(); + self::assertFalse($conn->isConnectedToMaster()); + + $conn->connect('master'); + self::assertTrue($conn->isConnectedToMaster()); + } + + public function testQueryOnMaster() : void + { + $conn = $this->createMasterSlaveConnection(); + + $query = 'SELECT count(*) as num FROM master_slave_table'; + + $statement = $conn->query($query); + + self::assertInstanceOf(Statement::class, $statement); + + //Query must be executed only on Master + self::assertTrue($conn->isConnectedToMaster()); + + $data = $statement->fetchAll(); + + //Default fetchmode is FetchMode::ASSOCIATIVE + self::assertArrayHasKey(0, $data); + self::assertArrayHasKey('num', $data[0]); + + //Could be set in other fetchmodes + self::assertArrayNotHasKey(0, $data[0]); + self::assertEquals(1, $data[0]['num']); + } + + public function testQueryOnSlave() : void + { + $conn = $this->createMasterSlaveConnection(); + $conn->connect('slave'); + + $query = 'SELECT count(*) as num FROM master_slave_table'; + + $statement = $conn->query($query); + + self::assertInstanceOf(Statement::class, $statement); + + //Query must be executed only on Master, even when we connect to the slave + self::assertTrue($conn->isConnectedToMaster()); + + $data = $statement->fetchAll(); + + //Default fetchmode is FetchMode::ASSOCIATIVE + self::assertArrayHasKey(0, $data); + self::assertArrayHasKey('num', $data[0]); + + //Could be set in other fetchmodes + self::assertArrayNotHasKey(0, $data[0]); + + self::assertEquals(1, $data[0]['num']); + } +} diff --git a/tests/Functional/DBAL/ModifyLimitQueryTest.php b/tests/Functional/DBAL/ModifyLimitQueryTest.php new file mode 100644 index 0000000..572ce54 --- /dev/null +++ b/tests/Functional/DBAL/ModifyLimitQueryTest.php @@ -0,0 +1,185 @@ +addColumn('test_int', 'integer'); + $table->setPrimaryKey(['test_int']); + + $table2 = new Table('modify_limit_table2'); + $table2->addColumn('id', 'integer', ['autoincrement' => true]); + $table2->addColumn('test_int', 'integer'); + $table2->setPrimaryKey(['id']); + + $sm = $this->connection->getSchemaManager(); + $sm->createTable($table); + $sm->createTable($table2); + self::$tableCreated = true; + } + $this->connection->exec($this->connection->getDatabasePlatform()->getTruncateTableSQL('modify_limit_table')); + $this->connection->exec($this->connection->getDatabasePlatform()->getTruncateTableSQL('modify_limit_table2')); + } + + public function testModifyLimitQuerySimpleQuery() : void + { + $this->connection->insert('modify_limit_table', ['test_int' => 1]); + $this->connection->insert('modify_limit_table', ['test_int' => 2]); + $this->connection->insert('modify_limit_table', ['test_int' => 3]); + $this->connection->insert('modify_limit_table', ['test_int' => 4]); + + $sql = 'SELECT * FROM modify_limit_table ORDER BY test_int ASC'; + + $this->assertLimitResult([1, 2, 3, 4], $sql, 10, 0); + $this->assertLimitResult([1, 2], $sql, 2, 0); + $this->assertLimitResult([3, 4], $sql, 2, 2); + $this->assertLimitResult([2, 3, 4], $sql, null, 1); + } + + public function testModifyLimitQueryJoinQuery() : void + { + $this->connection->insert('modify_limit_table', ['test_int' => 1]); + $this->connection->insert('modify_limit_table', ['test_int' => 2]); + + $this->connection->insert('modify_limit_table2', ['test_int' => 1]); + $this->connection->insert('modify_limit_table2', ['test_int' => 1]); + $this->connection->insert('modify_limit_table2', ['test_int' => 1]); + $this->connection->insert('modify_limit_table2', ['test_int' => 2]); + $this->connection->insert('modify_limit_table2', ['test_int' => 2]); + + $sql = 'SELECT modify_limit_table.test_int FROM modify_limit_table INNER JOIN modify_limit_table2 ON modify_limit_table.test_int = modify_limit_table2.test_int ORDER BY modify_limit_table.test_int DESC'; + + $this->assertLimitResult([2, 2, 1, 1, 1], $sql, 10, 0); + $this->assertLimitResult([1, 1, 1], $sql, 3, 2); + $this->assertLimitResult([2, 2], $sql, 2, 0); + } + + public function testModifyLimitQueryNonDeterministic() : void + { + $this->connection->insert('modify_limit_table', ['test_int' => 1]); + $this->connection->insert('modify_limit_table', ['test_int' => 2]); + $this->connection->insert('modify_limit_table', ['test_int' => 3]); + $this->connection->insert('modify_limit_table', ['test_int' => 4]); + + $sql = 'SELECT * FROM modify_limit_table'; + + $this->assertLimitResult([4, 3, 2, 1], $sql, 10, 0, false); + $this->assertLimitResult([4, 3], $sql, 2, 0, false); + $this->assertLimitResult([2, 1], $sql, 2, 2, false); + } + + public function testModifyLimitQueryGroupBy() : void + { + $this->connection->insert('modify_limit_table', ['test_int' => 1]); + $this->connection->insert('modify_limit_table', ['test_int' => 2]); + + $this->connection->insert('modify_limit_table2', ['test_int' => 1]); + $this->connection->insert('modify_limit_table2', ['test_int' => 1]); + $this->connection->insert('modify_limit_table2', ['test_int' => 1]); + $this->connection->insert('modify_limit_table2', ['test_int' => 2]); + $this->connection->insert('modify_limit_table2', ['test_int' => 2]); + + $sql = 'SELECT modify_limit_table.test_int FROM modify_limit_table ' . + 'INNER JOIN modify_limit_table2 ON modify_limit_table.test_int = modify_limit_table2.test_int ' . + 'GROUP BY modify_limit_table.test_int ' . + 'ORDER BY modify_limit_table.test_int ASC'; + $this->assertLimitResult([1, 2], $sql, 10, 0); + $this->assertLimitResult([1], $sql, 1, 0); + $this->assertLimitResult([2], $sql, 1, 1); + } + + public function testModifyLimitQuerySubSelect() : void + { + $this->connection->insert('modify_limit_table', ['test_int' => 1]); + $this->connection->insert('modify_limit_table', ['test_int' => 2]); + $this->connection->insert('modify_limit_table', ['test_int' => 3]); + $this->connection->insert('modify_limit_table', ['test_int' => 4]); + + $sql = 'SELECT modify_limit_table.*, (SELECT COUNT(*) FROM modify_limit_table) AS cnt FROM modify_limit_table ORDER BY test_int DESC'; + + $this->assertLimitResult([4, 3, 2, 1], $sql, 10, 0); + $this->assertLimitResult([4, 3], $sql, 2, 0); + $this->assertLimitResult([2, 1], $sql, 2, 2); + } + + public function testModifyLimitQueryFromSubSelect() : void + { + $this->connection->insert('modify_limit_table', ['test_int' => 1]); + $this->connection->insert('modify_limit_table', ['test_int' => 2]); + $this->connection->insert('modify_limit_table', ['test_int' => 3]); + $this->connection->insert('modify_limit_table', ['test_int' => 4]); + + $sql = 'SELECT * FROM (SELECT * FROM modify_limit_table) sub ORDER BY test_int DESC'; + + $this->assertLimitResult([4, 3, 2, 1], $sql, 10, 0); + $this->assertLimitResult([4, 3], $sql, 2, 0); + $this->assertLimitResult([2, 1], $sql, 2, 2); + } + + public function testModifyLimitQueryLineBreaks() : void + { + $this->connection->insert('modify_limit_table', ['test_int' => 1]); + $this->connection->insert('modify_limit_table', ['test_int' => 2]); + $this->connection->insert('modify_limit_table', ['test_int' => 3]); + + $sql = <<assertLimitResult([2], $sql, 1, 1); + } + + public function testModifyLimitQueryZeroOffsetNoLimit() : void + { + $this->connection->insert('modify_limit_table', ['test_int' => 1]); + $this->connection->insert('modify_limit_table', ['test_int' => 2]); + + $sql = 'SELECT test_int FROM modify_limit_table ORDER BY test_int ASC'; + + $this->assertLimitResult([1, 2], $sql, null, 0); + } + + /** + * @param array $expectedResults + */ + private function assertLimitResult(array $expectedResults, string $sql, ?int $limit, int $offset, bool $deterministic = true) : void + { + $p = $this->connection->getDatabasePlatform(); + $data = []; + foreach ($this->connection->fetchAll($p->modifyLimitQuery($sql, $limit, $offset)) as $row) { + $row = array_change_key_case($row, CASE_LOWER); + $data[] = $row['test_int']; + } + + /** + * Do not assert the order of results when results are non-deterministic + */ + if ($deterministic) { + self::assertEquals($expectedResults, $data); + } else { + self::assertCount(count($expectedResults), $data); + } + } +} diff --git a/tests/Functional/DBAL/NamedParametersTest.php b/tests/Functional/DBAL/NamedParametersTest.php new file mode 100644 index 0000000..d85f643 --- /dev/null +++ b/tests/Functional/DBAL/NamedParametersTest.php @@ -0,0 +1,224 @@ +> + */ + public static function ticketProvider() : iterable + { + return [ + [ + 'SELECT * FROM ddc1372_foobar f WHERE f.foo = :foo AND f.bar IN (:bar)', + [ + 'foo' => 1, + 'bar' => [1, 2, 3], + ], + [ + 'foo' => ParameterType::INTEGER, + 'bar' => Connection::PARAM_INT_ARRAY, + ], + [ + ['id' => 1, 'foo' => 1, 'bar' => 1], + ['id' => 2, 'foo' => 1, 'bar' => 2], + ['id' => 3, 'foo' => 1, 'bar' => 3], + ], + ], + + [ + 'SELECT * FROM ddc1372_foobar f WHERE f.foo = :foo AND f.bar IN (:bar)', + [ + 'foo' => 1, + 'bar' => [1, 2, 3], + ], + [ + 'bar' => Connection::PARAM_INT_ARRAY, + 'foo' => ParameterType::INTEGER, + ], + [ + ['id' => 1, 'foo' => 1, 'bar' => 1], + ['id' => 2, 'foo' => 1, 'bar' => 2], + ['id' => 3, 'foo' => 1, 'bar' => 3], + ], + ], + + [ + 'SELECT * FROM ddc1372_foobar f WHERE f.bar IN (:bar) AND f.foo = :foo', + [ + 'foo' => 1, + 'bar' => [1, 2, 3], + ], + [ + 'bar' => Connection::PARAM_INT_ARRAY, + 'foo' => ParameterType::INTEGER, + ], + [ + ['id' => 1, 'foo' => 1, 'bar' => 1], + ['id' => 2, 'foo' => 1, 'bar' => 2], + ['id' => 3, 'foo' => 1, 'bar' => 3], + ], + ], + + [ + 'SELECT * FROM ddc1372_foobar f WHERE f.bar IN (:bar) AND f.foo = :foo', + [ + 'foo' => 1, + 'bar' => ['1', '2', '3'], + ], + [ + 'bar' => Connection::PARAM_STR_ARRAY, + 'foo' => ParameterType::INTEGER, + ], + [ + ['id' => 1, 'foo' => 1, 'bar' => 1], + ['id' => 2, 'foo' => 1, 'bar' => 2], + ['id' => 3, 'foo' => 1, 'bar' => 3], + ], + ], + + [ + 'SELECT * FROM ddc1372_foobar f WHERE f.bar IN (:bar) AND f.foo IN (:foo)', + [ + 'foo' => ['1'], + 'bar' => [1, 2, 3, 4], + ], + [ + 'bar' => Connection::PARAM_STR_ARRAY, + 'foo' => Connection::PARAM_INT_ARRAY, + ], + [ + ['id' => 1, 'foo' => 1, 'bar' => 1], + ['id' => 2, 'foo' => 1, 'bar' => 2], + ['id' => 3, 'foo' => 1, 'bar' => 3], + ['id' => 4, 'foo' => 1, 'bar' => 4], + ], + ], + + [ + 'SELECT * FROM ddc1372_foobar f WHERE f.bar IN (:bar) AND f.foo IN (:foo)', + [ + 'foo' => 1, + 'bar' => 2, + ], + [ + 'bar' => ParameterType::INTEGER, + 'foo' => ParameterType::INTEGER, + ], + [ + ['id' => 2, 'foo' => 1, 'bar' => 2], + ], + ], + + [ + 'SELECT * FROM ddc1372_foobar f WHERE f.bar = :arg AND f.foo <> :arg', + ['arg' => '1'], + [ + 'arg' => ParameterType::STRING, + ], + [ + ['id' => 5, 'foo' => 2, 'bar' => 1], + ], + ], + + [ + 'SELECT * FROM ddc1372_foobar f WHERE f.bar NOT IN (:arg) AND f.foo IN (:arg)', + [ + 'arg' => [1, 2], + ], + [ + 'arg' => Connection::PARAM_INT_ARRAY, + ], + [ + ['id' => 3, 'foo' => 1, 'bar' => 3], + ['id' => 4, 'foo' => 1, 'bar' => 4], + ], + ], + ]; + } + + protected function setUp() : void + { + parent::setUp(); + + if ($this->connection->getSchemaManager()->tablesExist('ddc1372_foobar')) { + return; + } + + try { + $table = new Table('ddc1372_foobar'); + $table->addColumn('id', 'integer'); + $table->addColumn('foo', 'string'); + $table->addColumn('bar', 'string'); + $table->setPrimaryKey(['id']); + + $sm = $this->connection->getSchemaManager(); + $sm->createTable($table); + + $this->connection->insert('ddc1372_foobar', [ + 'id' => 1, + 'foo' => 1, + 'bar' => 1, + ]); + $this->connection->insert('ddc1372_foobar', [ + 'id' => 2, + 'foo' => 1, + 'bar' => 2, + ]); + $this->connection->insert('ddc1372_foobar', [ + 'id' => 3, + 'foo' => 1, + 'bar' => 3, + ]); + $this->connection->insert('ddc1372_foobar', [ + 'id' => 4, + 'foo' => 1, + 'bar' => 4, + ]); + $this->connection->insert('ddc1372_foobar', [ + 'id' => 5, + 'foo' => 2, + 'bar' => 1, + ]); + $this->connection->insert('ddc1372_foobar', [ + 'id' => 6, + 'foo' => 2, + 'bar' => 2, + ]); + } catch (Throwable $e) { + $this->fail($e->getMessage()); + } + } + + /** + * @param mixed[] $params + * @param int[] $types + * @param int[] $expected + * + * @dataProvider ticketProvider + */ + public function testTicket(string $query, array $params, array $types, array $expected) : void + { + $stmt = $this->connection->executeQuery($query, $params, $types); + $result = $stmt->fetchAll(FetchMode::ASSOCIATIVE); + + foreach ($result as $k => $v) { + $result[$k] = array_change_key_case($v, CASE_LOWER); + } + + self::assertEquals($result, $expected); + } +} diff --git a/tests/Functional/DBAL/PDOStatementTest.php b/tests/Functional/DBAL/PDOStatementTest.php new file mode 100644 index 0000000..9baaf4d --- /dev/null +++ b/tests/Functional/DBAL/PDOStatementTest.php @@ -0,0 +1,54 @@ +markTestSkipped('PDO is not installed'); + } + + parent::setUp(); + + if (! $this->connection->getWrappedConnection() instanceof PDOConnection) { + $this->markTestSkipped('PDO-only test'); + } + + $table = new Table('stmt_test'); + $table->addColumn('id', 'integer'); + $table->addColumn('name', 'string'); + $this->connection->getSchemaManager()->dropAndCreateTable($table); + } + + /** + * @group legacy + * @expectedDeprecation Using a PDO fetch mode or their combination (%d given) is deprecated and will cause an error in Doctrine 3.0 + */ + public function testPDOSpecificModeIsAccepted() : void + { + $this->connection->insert('stmt_test', [ + 'id' => 1, + 'name' => 'Alice', + ]); + $this->connection->insert('stmt_test', [ + 'id' => 2, + 'name' => 'Bob', + ]); + + $data = $this->connection->query('SELECT id, name FROM stmt_test ORDER BY id') + ->fetchAll(PDO::FETCH_KEY_PAIR); + + self::assertSame([ + 1 => 'Alice', + 2 => 'Bob', + ], $data); + } +} diff --git a/tests/Functional/DBAL/Platform/DateExpressionTest.php b/tests/Functional/DBAL/Platform/DateExpressionTest.php new file mode 100644 index 0000000..8427185 --- /dev/null +++ b/tests/Functional/DBAL/Platform/DateExpressionTest.php @@ -0,0 +1,55 @@ +addColumn('date1', 'datetime'); + $table->addColumn('date2', 'datetime'); + $this->connection->getSchemaManager()->dropAndCreateTable($table); + $this->connection->insert('date_expr_test', [ + 'date1' => $date1, + 'date2' => $date2, + ]); + + $platform = $this->connection->getDatabasePlatform(); + + $sql = sprintf('SELECT %s FROM date_expr_test', $platform->getDateDiffExpression('date1', 'date2')); + $diff = $this->connection->query($sql)->fetchColumn(); + + self::assertEquals($expected, $diff); + } + + /** + * @return string[][]|int[][] + */ + public static function differenceProvider() : iterable + { + $date1 = new DateTimeImmutable(); + $date2 = new DateTimeImmutable('2018-04-10 10:10:10'); + $expected = $date1->modify('midnight')->diff( + $date2->modify('midnight') + )->days; + + return [ + 'dynamic' => [ + $date1->format('Y-m-d H:i:s'), + $date2->format('Y-m-d H:i:s'), + $expected, + ], + 'same day' => ['2018-04-14 23:59:59', '2018-04-14 00:00:00', 0], + 'midnight' => ['2018-04-14 00:00:00', '2018-04-13 23:59:59', 1], + ]; + } +} diff --git a/tests/Functional/DBAL/Platform/DefaultExpressionTest.php b/tests/Functional/DBAL/Platform/DefaultExpressionTest.php new file mode 100644 index 0000000..f46fde8 --- /dev/null +++ b/tests/Functional/DBAL/Platform/DefaultExpressionTest.php @@ -0,0 +1,60 @@ +assertDefaultExpression(Types::DATE_MUTABLE, static function (AbstractPlatform $platform) : string { + return $platform->getCurrentDateSQL(); + }); + } + + public function testCurrentTime() : void + { + $this->assertDefaultExpression(Types::TIME_MUTABLE, static function (AbstractPlatform $platform) : string { + return $platform->getCurrentTimeSQL(); + }); + } + + public function testCurrentTimestamp() : void + { + $this->assertDefaultExpression(Types::DATETIME_MUTABLE, static function (AbstractPlatform $platform) : string { + return $platform->getCurrentTimestampSQL(); + }); + } + + private function assertDefaultExpression(string $type, callable $expression) : void + { + $platform = $this->connection->getDatabasePlatform(); + $defaultSql = $expression($platform, $this); + + $table = new Table('default_expr_test'); + $table->addColumn('actual_value', $type); + $table->addColumn('default_value', $type, ['default' => $defaultSql]); + $this->connection->getSchemaManager()->dropAndCreateTable($table); + + $this->connection->exec( + sprintf( + 'INSERT INTO default_expr_test (actual_value) VALUES (%s)', + $defaultSql + ) + ); + + [$actualValue, $defaultValue] = $this->connection->query( + 'SELECT default_value, actual_value FROM default_expr_test' + )->fetch(FetchMode::NUMERIC); + + self::assertEquals($actualValue, $defaultValue); + } +} diff --git a/tests/Functional/DBAL/Platform/QuotingTest.php b/tests/Functional/DBAL/Platform/QuotingTest.php new file mode 100644 index 0000000..d340775 --- /dev/null +++ b/tests/Functional/DBAL/Platform/QuotingTest.php @@ -0,0 +1,32 @@ +connection->getDatabasePlatform(); + $query = $platform->getDummySelectSQL( + $platform->quoteStringLiteral($string) + ); + + self::assertSame($string, $this->connection->fetchColumn($query)); + } + + /** + * @return mixed[][] + */ + public static function stringLiteralProvider() : iterable + { + return [ + 'backslash' => ['\\'], + 'single-quote' => ["'"], + ]; + } +} diff --git a/tests/Functional/DBAL/PortabilityTest.php b/tests/Functional/DBAL/PortabilityTest.php new file mode 100644 index 0000000..178846c --- /dev/null +++ b/tests/Functional/DBAL/PortabilityTest.php @@ -0,0 +1,177 @@ +portableConnection) { + $this->portableConnection->close(); + } + + parent::tearDown(); + } + + private function getPortableConnection( + int $portabilityMode = ConnectionPortability::PORTABILITY_ALL, + int $case = ColumnCase::LOWER + ) : Connection { + if (! $this->portableConnection) { + $params = $this->connection->getParams(); + + $params['wrapperClass'] = ConnectionPortability::class; + $params['portability'] = $portabilityMode; + $params['fetch_case'] = $case; + + $this->portableConnection = DriverManager::getConnection($params, $this->connection->getConfiguration(), $this->connection->getEventManager()); + + try { + $table = new Table('portability_table'); + $table->addColumn('Test_Int', 'integer'); + $table->addColumn('Test_String', 'string', ['fixed' => true, 'length' => 32]); + $table->addColumn('Test_Null', 'string', ['notnull' => false]); + $table->setPrimaryKey(['Test_Int']); + + $sm = $this->portableConnection->getSchemaManager(); + $sm->createTable($table); + + $this->portableConnection->insert('portability_table', ['Test_Int' => 1, 'Test_String' => 'foo', 'Test_Null' => '']); + $this->portableConnection->insert('portability_table', ['Test_Int' => 2, 'Test_String' => 'foo ', 'Test_Null' => null]); + } catch (Throwable $e) { + } + } + + return $this->portableConnection; + } + + public function testFullFetchMode() : void + { + $rows = $this->getPortableConnection()->fetchAll('SELECT * FROM portability_table'); + $this->assertFetchResultRows($rows); + + $stmt = $this->getPortableConnection()->query('SELECT * FROM portability_table'); + $stmt->setFetchMode(FetchMode::ASSOCIATIVE); + + foreach ($stmt as $row) { + $this->assertFetchResultRow($row); + } + + $stmt = $this->getPortableConnection()->query('SELECT * FROM portability_table'); + + while (($row = $stmt->fetch(FetchMode::ASSOCIATIVE))) { + $this->assertFetchResultRow($row); + } + + $stmt = $this->getPortableConnection()->prepare('SELECT * FROM portability_table'); + $stmt->execute(); + + while (($row = $stmt->fetch(FetchMode::ASSOCIATIVE))) { + $this->assertFetchResultRow($row); + } + } + + public function testConnFetchMode() : void + { + $conn = $this->getPortableConnection(); + $conn->setFetchMode(FetchMode::ASSOCIATIVE); + + $rows = $conn->fetchAll('SELECT * FROM portability_table'); + $this->assertFetchResultRows($rows); + + $stmt = $conn->query('SELECT * FROM portability_table'); + foreach ($stmt as $row) { + $this->assertFetchResultRow($row); + } + + $stmt = $conn->query('SELECT * FROM portability_table'); + while (($row = $stmt->fetch())) { + $this->assertFetchResultRow($row); + } + + $stmt = $conn->prepare('SELECT * FROM portability_table'); + $stmt->execute(); + while (($row = $stmt->fetch())) { + $this->assertFetchResultRow($row); + } + } + + /** + * @param array> $rows + */ + private function assertFetchResultRows(array $rows) : void + { + self::assertCount(2, $rows); + foreach ($rows as $row) { + $this->assertFetchResultRow($row); + } + } + + /** + * @param array $row + */ + public function assertFetchResultRow(array $row) : void + { + self::assertContains($row['test_int'], [1, 2], 'Primary key test_int should either be 1 or 2.'); + self::assertArrayHasKey('test_string', $row, 'Case should be lowered.'); + self::assertEquals(3, strlen($row['test_string']), 'test_string should be rtrimed to length of three for CHAR(32) column.'); + self::assertNull($row['test_null']); + self::assertArrayNotHasKey(0, $row, 'The row should not contain numerical keys.'); + } + + /** + * @param mixed[] $expected + * + * @dataProvider fetchAllColumnProvider + */ + public function testFetchAllColumn(string $field, array $expected) : void + { + $conn = $this->getPortableConnection(); + $stmt = $conn->query('SELECT ' . $field . ' FROM portability_table'); + + $column = $stmt->fetchAll(FetchMode::COLUMN); + self::assertEquals($expected, $column); + } + + /** + * @return iterable> + */ + public static function fetchAllColumnProvider() : iterable + { + return [ + 'int' => [ + 'Test_Int', + [1, 2], + ], + 'string' => [ + 'Test_String', + ['foo', 'foo'], + ], + ]; + } + + public function testFetchAllNullColumn() : void + { + $conn = $this->getPortableConnection(); + $stmt = $conn->query('SELECT Test_Null FROM portability_table'); + + $column = $stmt->fetchAll(FetchMode::COLUMN); + self::assertSame([null, null], $column); + } +} diff --git a/tests/Functional/DBAL/ResultCacheTest.php b/tests/Functional/DBAL/ResultCacheTest.php new file mode 100644 index 0000000..b3c61b8 --- /dev/null +++ b/tests/Functional/DBAL/ResultCacheTest.php @@ -0,0 +1,251 @@ +> */ + private $expectedResult = [['test_int' => 100, 'test_string' => 'foo'], ['test_int' => 200, 'test_string' => 'bar'], ['test_int' => 300, 'test_string' => 'baz']]; + + /** @var DebugStack */ + private $sqlLogger; + + protected function setUp() : void + { + parent::setUp(); + + $table = new Table('caching'); + $table->addColumn('test_int', 'integer'); + $table->addColumn('test_string', 'string', ['notnull' => false]); + $table->setPrimaryKey(['test_int']); + + $sm = $this->connection->getSchemaManager(); + $sm->createTable($table); + + foreach ($this->expectedResult as $row) { + $this->connection->insert('caching', $row); + } + + $config = $this->connection->getConfiguration(); + $config->setSQLLogger($this->sqlLogger = new DebugStack()); + + $cache = new ArrayCache(); + $config->setResultCacheImpl($cache); + } + + protected function tearDown() : void + { + $this->connection->getSchemaManager()->dropTable('caching'); + + parent::tearDown(); + } + + public function testCacheFetchAssoc() : void + { + $this->assertCacheNonCacheSelectSameFetchModeAreEqual( + $this->expectedResult, + FetchMode::ASSOCIATIVE + ); + } + + public function testFetchNum() : void + { + $expectedResult = []; + foreach ($this->expectedResult as $v) { + $expectedResult[] = array_values($v); + } + + $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::NUMERIC); + } + + public function testFetchBoth() : void + { + $expectedResult = []; + foreach ($this->expectedResult as $v) { + $expectedResult[] = array_merge($v, array_values($v)); + } + + $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::MIXED); + } + + public function testFetchColumn() : void + { + $expectedResult = []; + foreach ($this->expectedResult as $v) { + $expectedResult[] = array_shift($v); + } + + $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::COLUMN); + } + + public function testMixingFetch() : void + { + $numExpectedResult = []; + foreach ($this->expectedResult as $v) { + $numExpectedResult[] = array_values($v); + } + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + + $data = $this->hydrateStmt($stmt, FetchMode::ASSOCIATIVE); + + self::assertEquals($this->expectedResult, $data); + + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + + $data = $this->hydrateStmt($stmt, FetchMode::NUMERIC); + + self::assertEquals($numExpectedResult, $data); + } + + public function testIteratorFetch() : void + { + self::assertStandardAndIteratorFetchAreEqual(FetchMode::MIXED); + self::assertStandardAndIteratorFetchAreEqual(FetchMode::ASSOCIATIVE); + self::assertStandardAndIteratorFetchAreEqual(FetchMode::NUMERIC); + } + + private function assertStandardAndIteratorFetchAreEqual(int $fetchMode) : void + { + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + $data = $this->hydrateStmt($stmt, $fetchMode); + + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + $data_iterator = $this->hydrateStmtIterator($stmt, $fetchMode); + + self::assertEquals($data, $data_iterator); + } + + public function testDontCloseNoCache() : void + { + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + + $data = []; + + while ($row = $stmt->fetch(FetchMode::ASSOCIATIVE)) { + $data[] = $row; + } + + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + + $data = []; + + while ($row = $stmt->fetch(FetchMode::NUMERIC)) { + $data[] = $row; + } + + self::assertCount(2, $this->sqlLogger->queries); + } + + public function testDontFinishNoCache() : void + { + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + + $stmt->fetch(FetchMode::ASSOCIATIVE); + $stmt->closeCursor(); + + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + + $this->hydrateStmt($stmt, FetchMode::NUMERIC); + + self::assertCount(2, $this->sqlLogger->queries); + } + + public function testFetchAllAndFinishSavesCache() : void + { + $layerCache = new ArrayCache(); + $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'testcachekey', $layerCache)); + $stmt->fetchAll(); + $stmt->closeCursor(); + + self::assertCount(1, $layerCache->fetch('testcachekey')); + } + + /** + * @param array> $expectedResult + */ + private function assertCacheNonCacheSelectSameFetchModeAreEqual(array $expectedResult, int $fetchMode) : void + { + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + + self::assertEquals(2, $stmt->columnCount()); + $data = $this->hydrateStmt($stmt, $fetchMode); + self::assertEquals($expectedResult, $data); + + $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); + + self::assertEquals(2, $stmt->columnCount()); + $data = $this->hydrateStmt($stmt, $fetchMode); + self::assertEquals($expectedResult, $data); + self::assertCount(1, $this->sqlLogger->queries, 'just one dbal hit'); + } + + public function testEmptyResultCache() : void + { + $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey')); + $data = $this->hydrateStmt($stmt); + + $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey')); + $data = $this->hydrateStmt($stmt); + + self::assertCount(1, $this->sqlLogger->queries, 'just one dbal hit'); + } + + public function testChangeCacheImpl() : void + { + $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey')); + $data = $this->hydrateStmt($stmt); + + $secondCache = new ArrayCache(); + $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey', $secondCache)); + $data = $this->hydrateStmt($stmt); + + self::assertCount(2, $this->sqlLogger->queries, 'two hits'); + self::assertCount(1, $secondCache->fetch('emptycachekey')); + } + + /** + * @return array + */ + private function hydrateStmt(ResultStatement $stmt, int $fetchMode = FetchMode::ASSOCIATIVE) : array + { + $data = []; + while ($row = $stmt->fetch($fetchMode)) { + $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row; + } + $stmt->closeCursor(); + + return $data; + } + + /** + * @return array + */ + private function hydrateStmtIterator(ResultStatement $stmt, int $fetchMode = FetchMode::ASSOCIATIVE) : array + { + $data = []; + $stmt->setFetchMode($fetchMode); + foreach ($stmt as $row) { + $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row; + } + $stmt->closeCursor(); + + return $data; + } +} diff --git a/tests/Functional/DBAL/Schema/ComparatorTest.php b/tests/Functional/DBAL/Schema/ComparatorTest.php new file mode 100644 index 0000000..ad75d88 --- /dev/null +++ b/tests/Functional/DBAL/Schema/ComparatorTest.php @@ -0,0 +1,55 @@ +schemaManager = $this->connection->getSchemaManager(); + $this->comparator = new Comparator(); + } + + /** + * @param mixed $value + * + * @dataProvider defaultValueProvider + */ + public function testDefaultValueComparison(string $type, $value) : void + { + $table = new Table('default_value'); + $table->addColumn('test', $type, ['default' => $value]); + + $this->schemaManager->dropAndCreateTable($table); + + $onlineTable = $this->schemaManager->listTableDetails('default_value'); + + self::assertFalse($this->comparator->diffTable($table, $onlineTable)); + } + + /** + * @return mixed[][] + */ + public static function defaultValueProvider() : iterable + { + return [ + ['integer', 1], + ['boolean', false], + ]; + } +} diff --git a/tests/Functional/DBAL/Schema/Db2SchemaManagerTest.php b/tests/Functional/DBAL/Schema/Db2SchemaManagerTest.php new file mode 100644 index 0000000..d9b863f --- /dev/null +++ b/tests/Functional/DBAL/Schema/Db2SchemaManagerTest.php @@ -0,0 +1,34 @@ +addColumn('bool', 'boolean'); + $table->addColumn('bool_commented', 'boolean', ['comment' => "That's a comment"]); + + $this->schemaManager->createTable($table); + + $columns = $this->schemaManager->listTableColumns('boolean_column_test'); + + self::assertInstanceOf(BooleanType::class, $columns['bool']->getType()); + self::assertInstanceOf(BooleanType::class, $columns['bool_commented']->getType()); + + self::assertNull($columns['bool']->getComment()); + self::assertSame("That's a comment", $columns['bool_commented']->getComment()); + } + + public function testListTableWithBinary() : void + { + self::markTestSkipped('Binary data type is currently not supported on DB2 LUW'); + } +} diff --git a/tests/Functional/DBAL/Schema/DefaultValueTest.php b/tests/Functional/DBAL/Schema/DefaultValueTest.php new file mode 100644 index 0000000..7aaa743 --- /dev/null +++ b/tests/Functional/DBAL/Schema/DefaultValueTest.php @@ -0,0 +1,155 @@ +addColumn('id', 'integer'); + + foreach (self::columnProvider() as [$name, $default]) { + $table->addColumn($name, 'string', [ + 'default' => $default, + 'notnull' => false, + ]); + } + + $this->connection->getSchemaManager() + ->dropAndCreateTable($table); + + $this->connection->insert('default_value', ['id' => 1]); + } + + /** + * @param mixed $expectedDefault + * + * @dataProvider columnProvider + */ + public function testEscapedDefaultValueCanBeIntrospected(string $name, ?string $expectedDefault) : void + { + self::assertSame( + $expectedDefault, + $this->connection + ->getSchemaManager() + ->listTableDetails('default_value') + ->getColumn($name) + ->getDefault() + ); + } + + /** + * @param mixed $expectedDefault + * + * @dataProvider columnProvider + */ + public function testEscapedDefaultValueCanBeInserted(string $name, ?string $expectedDefault) : void + { + $value = $this->connection->fetchColumn( + sprintf('SELECT %s FROM default_value', $name) + ); + + self::assertSame($expectedDefault, $value); + } + + /** + * Returns potential escaped literals from all platforms combined. + * + * @see https://dev.mysql.com/doc/refman/5.7/en/string-literals.html + * @see http://www.sqlite.org/lang_expr.html + * @see https://www.postgresql.org/docs/9.6/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE + * + * @return mixed[][] + */ + public static function columnProvider() : iterable + { + return [ + 'Single quote' => [ + 'single_quote', + "foo'bar", + ], + 'Single quote, doubled' => [ + 'single_quote_doubled', + "foo''bar", + ], + 'Double quote' => [ + 'double_quote', + 'foo"bar', + ], + 'Double quote, doubled' => [ + 'double_quote_doubled', + 'foo""bar', + ], + 'Backspace' => [ + 'backspace', + "foo\x08bar", + ], + 'New line' => [ + 'new_line', + "foo\nbar", + ], + 'Carriage return' => [ + 'carriage_return', + "foo\rbar", + ], + 'Tab' => [ + 'tab', + "foo\tbar", + ], + 'Substitute' => [ + 'substitute', + "foo\x1abar", + ], + 'Backslash' => [ + 'backslash', + 'foo\\bar', + ], + 'Backslash, doubled' => [ + 'backslash_doubled', + 'foo\\\\bar', + ], + 'Percent' => [ + 'percent_sign', + 'foo%bar', + ], + 'Underscore' => [ + 'underscore', + 'foo_bar', + ], + 'NULL string' => [ + 'null_string', + 'NULL', + ], + 'NULL value' => [ + 'null_value', + null, + ], + 'SQL expression' => [ + 'sql_expression', + "'; DROP DATABASE doctrine --", + ], + 'No double conversion' => [ + 'no_double_conversion', + "\\'", + ], + ]; + } +} diff --git a/tests/Functional/DBAL/Schema/DrizzleSchemaManagerTest.php b/tests/Functional/DBAL/Schema/DrizzleSchemaManagerTest.php new file mode 100644 index 0000000..291d356 --- /dev/null +++ b/tests/Functional/DBAL/Schema/DrizzleSchemaManagerTest.php @@ -0,0 +1,48 @@ +addColumn('id', 'integer'); + $table->addColumn('column_varbinary', 'binary', []); + $table->addColumn('column_binary', 'binary', ['fixed' => true]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $table = $this->schemaManager->listTableDetails($tableName); + + self::assertInstanceOf(BinaryType::class, $table->getColumn('column_varbinary')->getType()); + self::assertFalse($table->getColumn('column_varbinary')->getFixed()); + + self::assertInstanceOf(BinaryType::class, $table->getColumn('column_binary')->getType()); + self::assertFalse($table->getColumn('column_binary')->getFixed()); + } + + public function testColumnCollation() : void + { + $table = new Table('test_collation'); + $table->addOption('collate', $collation = 'utf8_unicode_ci'); + $table->addColumn('id', 'integer'); + $table->addColumn('text', 'text'); + $table->addColumn('foo', 'text')->setPlatformOption('collation', 'utf8_swedish_ci'); + $table->addColumn('bar', 'text')->setPlatformOption('collation', 'utf8_general_ci'); + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('test_collation'); + + self::assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); + self::assertEquals('utf8_unicode_ci', $columns['text']->getPlatformOption('collation')); + self::assertEquals('utf8_swedish_ci', $columns['foo']->getPlatformOption('collation')); + self::assertEquals('utf8_general_ci', $columns['bar']->getPlatformOption('collation')); + } +} diff --git a/tests/Functional/DBAL/Schema/MySqlSchemaManagerTest.php b/tests/Functional/DBAL/Schema/MySqlSchemaManagerTest.php new file mode 100644 index 0000000..a4d6b3d --- /dev/null +++ b/tests/Functional/DBAL/Schema/MySqlSchemaManagerTest.php @@ -0,0 +1,567 @@ +resetSharedConn(); + + Type::addType('point', MySqlPointType::class); + } + + public function testSwitchPrimaryKeyColumns() : void + { + $tableOld = new Table('switch_primary_key_columns'); + $tableOld->addColumn('foo_id', 'integer'); + $tableOld->addColumn('bar_id', 'integer'); + + $this->schemaManager->createTable($tableOld); + $tableFetched = $this->schemaManager->listTableDetails('switch_primary_key_columns'); + $tableNew = clone $tableFetched; + $tableNew->setPrimaryKey(['bar_id', 'foo_id']); + + $comparator = new Comparator(); + $this->schemaManager->alterTable($comparator->diffTable($tableFetched, $tableNew)); + + $table = $this->schemaManager->listTableDetails('switch_primary_key_columns'); + $primaryKey = $table->getPrimaryKeyColumns(); + + self::assertCount(2, $primaryKey); + self::assertContains('bar_id', $primaryKey); + self::assertContains('foo_id', $primaryKey); + } + + public function testDiffTableBug() : void + { + $schema = new Schema(); + $table = $schema->createTable('diffbug_routing_translations'); + $table->addColumn('id', 'integer'); + $table->addColumn('route', 'string'); + $table->addColumn('locale', 'string'); + $table->addColumn('attribute', 'string'); + $table->addColumn('localized_value', 'string'); + $table->addColumn('original_value', 'string'); + $table->setPrimaryKey(['id']); + $table->addUniqueIndex(['route', 'locale', 'attribute']); + $table->addIndex(['localized_value']); // this is much more selective than the unique index + + $this->schemaManager->createTable($table); + $tableFetched = $this->schemaManager->listTableDetails('diffbug_routing_translations'); + + $comparator = new Comparator(); + $diff = $comparator->diffTable($tableFetched, $table); + + self::assertFalse($diff, 'no changes expected.'); + } + + public function testFulltextIndex() : void + { + $table = new Table('fulltext_index'); + $table->addColumn('text', 'text'); + $table->addIndex(['text'], 'f_index'); + $table->addOption('engine', 'MyISAM'); + + $index = $table->getIndex('f_index'); + $index->addFlag('fulltext'); + + $this->schemaManager->dropAndCreateTable($table); + + $indexes = $this->schemaManager->listTableIndexes('fulltext_index'); + self::assertArrayHasKey('f_index', $indexes); + self::assertTrue($indexes['f_index']->hasFlag('fulltext')); + } + + public function testSpatialIndex() : void + { + $table = new Table('spatial_index'); + $table->addColumn('point', 'point'); + $table->addIndex(['point'], 's_index'); + $table->addOption('engine', 'MyISAM'); + + $index = $table->getIndex('s_index'); + $index->addFlag('spatial'); + + $this->schemaManager->dropAndCreateTable($table); + + $indexes = $this->schemaManager->listTableIndexes('spatial_index'); + self::assertArrayHasKey('s_index', $indexes); + self::assertTrue($indexes['s_index']->hasFlag('spatial')); + } + + public function testIndexWithLength() : void + { + $table = new Table('index_length'); + $table->addColumn('text', 'string', ['length' => 255]); + $table->addIndex(['text'], 'text_index', [], ['lengths' => [128]]); + + $this->schemaManager->dropAndCreateTable($table); + + $indexes = $this->schemaManager->listTableIndexes('index_length'); + self::assertArrayHasKey('text_index', $indexes); + self::assertSame([128], $indexes['text_index']->getOption('lengths')); + } + + /** + * @group DBAL-400 + */ + public function testAlterTableAddPrimaryKey() : void + { + $table = new Table('alter_table_add_pk'); + $table->addColumn('id', 'integer'); + $table->addColumn('foo', 'integer'); + $table->addIndex(['id'], 'idx_id'); + + $this->schemaManager->createTable($table); + + $comparator = new Comparator(); + $diffTable = clone $table; + + $diffTable->dropIndex('idx_id'); + $diffTable->setPrimaryKey(['id']); + + $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); + + $table = $this->schemaManager->listTableDetails('alter_table_add_pk'); + + self::assertFalse($table->hasIndex('idx_id')); + self::assertTrue($table->hasPrimaryKey()); + } + + /** + * @group DBAL-464 + */ + public function testDropPrimaryKeyWithAutoincrementColumn() : void + { + $table = new Table('drop_primary_key'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('foo', 'integer'); + $table->setPrimaryKey(['id', 'foo']); + + $this->schemaManager->dropAndCreateTable($table); + + $diffTable = clone $table; + + $diffTable->dropPrimaryKey(); + + $comparator = new Comparator(); + + $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); + + $table = $this->schemaManager->listTableDetails('drop_primary_key'); + + self::assertFalse($table->hasPrimaryKey()); + self::assertFalse($table->getColumn('id')->getAutoincrement()); + } + + /** + * @group DBAL-789 + */ + public function testDoesNotPropagateDefaultValuesForUnsupportedColumnTypes() : void + { + if ($this->schemaManager->getDatabasePlatform() instanceof MariaDb1027Platform) { + $this->markTestSkipped( + 'MariaDb102Platform supports default values for BLOB and TEXT columns and will propagate values' + ); + } + + $table = new Table('text_blob_default_value'); + $table->addColumn('def_text', 'text', ['default' => 'def']); + $table->addColumn('def_text_null', 'text', ['notnull' => false, 'default' => 'def']); + $table->addColumn('def_blob', 'blob', ['default' => 'def']); + $table->addColumn('def_blob_null', 'blob', ['notnull' => false, 'default' => 'def']); + + $this->schemaManager->dropAndCreateTable($table); + + $onlineTable = $this->schemaManager->listTableDetails('text_blob_default_value'); + + self::assertNull($onlineTable->getColumn('def_text')->getDefault()); + self::assertNull($onlineTable->getColumn('def_text_null')->getDefault()); + self::assertFalse($onlineTable->getColumn('def_text_null')->getNotnull()); + self::assertNull($onlineTable->getColumn('def_blob')->getDefault()); + self::assertNull($onlineTable->getColumn('def_blob_null')->getDefault()); + self::assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull()); + + $comparator = new Comparator(); + + $this->schemaManager->alterTable($comparator->diffTable($table, $onlineTable)); + + $onlineTable = $this->schemaManager->listTableDetails('text_blob_default_value'); + + self::assertNull($onlineTable->getColumn('def_text')->getDefault()); + self::assertNull($onlineTable->getColumn('def_text_null')->getDefault()); + self::assertFalse($onlineTable->getColumn('def_text_null')->getNotnull()); + self::assertNull($onlineTable->getColumn('def_blob')->getDefault()); + self::assertNull($onlineTable->getColumn('def_blob_null')->getDefault()); + self::assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull()); + } + + public function testColumnCharset() : void + { + $table = new Table('test_column_charset'); + $table->addColumn('id', 'integer'); + $table->addColumn('no_charset', 'text'); + $table->addColumn('foo', 'text')->setPlatformOption('charset', 'ascii'); + $table->addColumn('bar', 'text')->setPlatformOption('charset', 'latin1'); + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('test_column_charset'); + + self::assertFalse($columns['id']->hasPlatformOption('charset')); + self::assertEquals('utf8', $columns['no_charset']->getPlatformOption('charset')); + self::assertEquals('ascii', $columns['foo']->getPlatformOption('charset')); + self::assertEquals('latin1', $columns['bar']->getPlatformOption('charset')); + } + + public function testAlterColumnCharset() : void + { + $tableName = 'test_alter_column_charset'; + + $table = new Table($tableName); + $table->addColumn('col_text', 'text')->setPlatformOption('charset', 'utf8'); + + $this->schemaManager->dropAndCreateTable($table); + + $diffTable = clone $table; + $diffTable->getColumn('col_text')->setPlatformOption('charset', 'ascii'); + + $comparator = new Comparator(); + + $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); + + $table = $this->schemaManager->listTableDetails($tableName); + + self::assertEquals('ascii', $table->getColumn('col_text')->getPlatformOption('charset')); + } + + public function testColumnCharsetChange() : void + { + $table = new Table('test_column_charset_change'); + $table->addColumn('col_string', 'string')->setLength(100)->setNotnull(true)->setPlatformOption('charset', 'utf8'); + + $diffTable = clone $table; + $diffTable->getColumn('col_string')->setPlatformOption('charset', 'ascii'); + + $fromSchema = new Schema([$table]); + $toSchema = new Schema([$diffTable]); + + $diff = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); + self::assertContains('ALTER TABLE test_column_charset_change CHANGE col_string col_string VARCHAR(100) CHARACTER SET ascii NOT NULL', $diff); + } + + public function testColumnCollation() : void + { + $table = new Table('test_collation'); + $table->addOption('collate', $collation = 'latin1_swedish_ci'); + $table->addOption('charset', 'latin1'); + $table->addColumn('id', 'integer'); + $table->addColumn('text', 'text'); + $table->addColumn('foo', 'text')->setPlatformOption('collation', 'latin1_swedish_ci'); + $table->addColumn('bar', 'text')->setPlatformOption('collation', 'utf8_general_ci'); + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('test_collation'); + + self::assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); + self::assertEquals('latin1_swedish_ci', $columns['text']->getPlatformOption('collation')); + self::assertEquals('latin1_swedish_ci', $columns['foo']->getPlatformOption('collation')); + self::assertEquals('utf8_general_ci', $columns['bar']->getPlatformOption('collation')); + } + + /** + * @group DBAL-843 + */ + public function testListLobTypeColumns() : void + { + $tableName = 'lob_type_columns'; + $table = new Table($tableName); + + $table->addColumn('col_tinytext', 'text', ['length' => MySqlPlatform::LENGTH_LIMIT_TINYTEXT]); + $table->addColumn('col_text', 'text', ['length' => MySqlPlatform::LENGTH_LIMIT_TEXT]); + $table->addColumn('col_mediumtext', 'text', ['length' => MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT]); + $table->addColumn('col_longtext', 'text'); + + $table->addColumn('col_tinyblob', 'text', ['length' => MySqlPlatform::LENGTH_LIMIT_TINYBLOB]); + $table->addColumn('col_blob', 'blob', ['length' => MySqlPlatform::LENGTH_LIMIT_BLOB]); + $table->addColumn('col_mediumblob', 'blob', ['length' => MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB]); + $table->addColumn('col_longblob', 'blob'); + + $this->schemaManager->dropAndCreateTable($table); + + $platform = $this->schemaManager->getDatabasePlatform(); + $offlineColumns = $table->getColumns(); + $onlineColumns = $this->schemaManager->listTableColumns($tableName); + + self::assertSame( + $platform->getClobTypeDeclarationSQL($offlineColumns['col_tinytext']->toArray()), + $platform->getClobTypeDeclarationSQL($onlineColumns['col_tinytext']->toArray()) + ); + self::assertSame( + $platform->getClobTypeDeclarationSQL($offlineColumns['col_text']->toArray()), + $platform->getClobTypeDeclarationSQL($onlineColumns['col_text']->toArray()) + ); + self::assertSame( + $platform->getClobTypeDeclarationSQL($offlineColumns['col_mediumtext']->toArray()), + $platform->getClobTypeDeclarationSQL($onlineColumns['col_mediumtext']->toArray()) + ); + self::assertSame( + $platform->getClobTypeDeclarationSQL($offlineColumns['col_longtext']->toArray()), + $platform->getClobTypeDeclarationSQL($onlineColumns['col_longtext']->toArray()) + ); + + self::assertSame( + $platform->getBlobTypeDeclarationSQL($offlineColumns['col_tinyblob']->toArray()), + $platform->getBlobTypeDeclarationSQL($onlineColumns['col_tinyblob']->toArray()) + ); + self::assertSame( + $platform->getBlobTypeDeclarationSQL($offlineColumns['col_blob']->toArray()), + $platform->getBlobTypeDeclarationSQL($onlineColumns['col_blob']->toArray()) + ); + self::assertSame( + $platform->getBlobTypeDeclarationSQL($offlineColumns['col_mediumblob']->toArray()), + $platform->getBlobTypeDeclarationSQL($onlineColumns['col_mediumblob']->toArray()) + ); + self::assertSame( + $platform->getBlobTypeDeclarationSQL($offlineColumns['col_longblob']->toArray()), + $platform->getBlobTypeDeclarationSQL($onlineColumns['col_longblob']->toArray()) + ); + } + + /** + * @group DBAL-423 + */ + public function testDiffListGuidTableColumn() : void + { + $offlineTable = new Table('list_guid_table_column'); + $offlineTable->addColumn('col_guid', 'guid'); + + $this->schemaManager->dropAndCreateTable($offlineTable); + + $onlineTable = $this->schemaManager->listTableDetails('list_guid_table_column'); + + $comparator = new Comparator(); + + self::assertFalse( + $comparator->diffTable($offlineTable, $onlineTable), + 'No differences should be detected with the offline vs online schema.' + ); + } + + /** + * @group DBAL-1082 + */ + public function testListDecimalTypeColumns() : void + { + $tableName = 'test_list_decimal_columns'; + $table = new Table($tableName); + + $table->addColumn('col', 'decimal'); + $table->addColumn('col_unsigned', 'decimal', ['unsigned' => true]); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertArrayHasKey('col', $columns); + self::assertArrayHasKey('col_unsigned', $columns); + self::assertFalse($columns['col']->getUnsigned()); + self::assertTrue($columns['col_unsigned']->getUnsigned()); + } + + /** + * @group DBAL-1082 + */ + public function testListFloatTypeColumns() : void + { + $tableName = 'test_list_float_columns'; + $table = new Table($tableName); + + $table->addColumn('col', 'float'); + $table->addColumn('col_unsigned', 'float', ['unsigned' => true]); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertArrayHasKey('col', $columns); + self::assertArrayHasKey('col_unsigned', $columns); + self::assertFalse($columns['col']->getUnsigned()); + self::assertTrue($columns['col_unsigned']->getUnsigned()); + } + + public function testJsonColumnType() : void + { + $table = new Table('test_mysql_json'); + $table->addColumn('col_json', 'json'); + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('test_mysql_json'); + + self::assertSame(Types::JSON, $columns['col_json']->getType()->getName()); + } + + public function testColumnDefaultCurrentTimestamp() : void + { + $platform = $this->schemaManager->getDatabasePlatform(); + + $table = new Table('test_column_defaults_current_timestamp'); + + $currentTimeStampSql = $platform->getCurrentTimestampSQL(); + + $table->addColumn('col_datetime', 'datetime', ['notnull' => true, 'default' => $currentTimeStampSql]); + $table->addColumn('col_datetime_nullable', 'datetime', ['default' => $currentTimeStampSql]); + + $this->schemaManager->dropAndCreateTable($table); + + $onlineTable = $this->schemaManager->listTableDetails('test_column_defaults_current_timestamp'); + self::assertSame($currentTimeStampSql, $onlineTable->getColumn('col_datetime')->getDefault()); + self::assertSame($currentTimeStampSql, $onlineTable->getColumn('col_datetime_nullable')->getDefault()); + + $comparator = new Comparator(); + + $diff = $comparator->diffTable($table, $onlineTable); + self::assertFalse($diff, 'Tables should be identical with column defaults.'); + } + + public function testColumnDefaultsAreValid() : void + { + $table = new Table('test_column_defaults_are_valid'); + + $currentTimeStampSql = $this->schemaManager->getDatabasePlatform()->getCurrentTimestampSQL(); + $table->addColumn('col_datetime', 'datetime', ['default' => $currentTimeStampSql]); + $table->addColumn('col_datetime_null', 'datetime', ['notnull' => false, 'default' => null]); + $table->addColumn('col_int', 'integer', ['default' => 1]); + $table->addColumn('col_neg_int', 'integer', ['default' => -1]); + $table->addColumn('col_string', 'string', ['default' => 'A']); + $table->addColumn('col_decimal', 'decimal', ['scale' => 3, 'precision' => 6, 'default' => -2.3]); + $table->addColumn('col_date', 'date', ['default' => '2012-12-12']); + + $this->schemaManager->dropAndCreateTable($table); + + $this->connection->executeUpdate( + 'INSERT INTO test_column_defaults_are_valid () VALUES()' + ); + + $row = $this->connection->fetchAssoc( + 'SELECT *, DATEDIFF(CURRENT_TIMESTAMP(), col_datetime) as diff_seconds FROM test_column_defaults_are_valid' + ); + + self::assertInstanceOf(DateTime::class, DateTime::createFromFormat('Y-m-d H:i:s', $row['col_datetime'])); + self::assertNull($row['col_datetime_null']); + self::assertSame('2012-12-12', $row['col_date']); + self::assertSame('A', $row['col_string']); + self::assertEquals(1, $row['col_int']); + self::assertEquals(-1, $row['col_neg_int']); + self::assertEquals('-2.300', $row['col_decimal']); + self::assertLessThan(5, $row['diff_seconds']); + } + + /** + * MariaDB 10.2+ does support CURRENT_TIME and CURRENT_DATE as + * column default values for time and date columns. + * (Not supported on Mysql as of 5.7.19) + * + * Note that MariaDB 10.2+, when storing default in information_schema, + * silently change CURRENT_TIMESTAMP as 'current_timestamp()', + * CURRENT_TIME as 'currtime()' and CURRENT_DATE as 'currdate()'. + * This test also ensure proper aliasing to not trigger a table diff. + */ + public function testColumnDefaultValuesCurrentTimeAndDate() : void + { + if (! $this->schemaManager->getDatabasePlatform() instanceof MariaDb1027Platform) { + $this->markTestSkipped('Only relevant for MariaDb102Platform.'); + } + + $platform = $this->schemaManager->getDatabasePlatform(); + + $table = new Table('test_column_defaults_current_time_and_date'); + + $currentTimestampSql = $platform->getCurrentTimestampSQL(); + $currentTimeSql = $platform->getCurrentTimeSQL(); + $currentDateSql = $platform->getCurrentDateSQL(); + + $table->addColumn('col_datetime', 'datetime', ['default' => $currentTimestampSql]); + $table->addColumn('col_date', 'date', ['default' => $currentDateSql]); + $table->addColumn('col_time', 'time', ['default' => $currentTimeSql]); + + $this->schemaManager->dropAndCreateTable($table); + + $onlineTable = $this->schemaManager->listTableDetails('test_column_defaults_current_time_and_date'); + + self::assertSame($currentTimestampSql, $onlineTable->getColumn('col_datetime')->getDefault()); + self::assertSame($currentDateSql, $onlineTable->getColumn('col_date')->getDefault()); + self::assertSame($currentTimeSql, $onlineTable->getColumn('col_time')->getDefault()); + + $comparator = new Comparator(); + + $diff = $comparator->diffTable($table, $onlineTable); + self::assertFalse($diff, 'Tables should be identical with column defauts time and date.'); + } + + public function testEnsureTableOptionsAreReflectedInMetadata() : void + { + $this->connection->query('DROP TABLE IF EXISTS test_table_metadata'); + + $sql = <<<'SQL' +CREATE TABLE test_table_metadata( + col1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY +) +COLLATE utf8_general_ci +ENGINE InnoDB +ROW_FORMAT COMPRESSED +COMMENT 'This is a test' +AUTO_INCREMENT=42 +PARTITION BY HASH (col1) +SQL; + + $this->connection->query($sql); + $onlineTable = $this->schemaManager->listTableDetails('test_table_metadata'); + + self::assertEquals('InnoDB', $onlineTable->getOption('engine')); + self::assertEquals('utf8_general_ci', $onlineTable->getOption('collation')); + self::assertEquals(42, $onlineTable->getOption('autoincrement')); + self::assertEquals('This is a test', $onlineTable->getOption('comment')); + self::assertEquals([ + 'row_format' => 'COMPRESSED', + 'partitioned' => true, + ], $onlineTable->getOption('create_options')); + } + + public function testEnsureTableWithoutOptionsAreReflectedInMetadata() : void + { + $this->connection->query('DROP TABLE IF EXISTS test_table_empty_metadata'); + + $this->connection->query('CREATE TABLE test_table_empty_metadata(col1 INT NOT NULL)'); + $onlineTable = $this->schemaManager->listTableDetails('test_table_empty_metadata'); + + self::assertNotEmpty($onlineTable->getOption('engine')); + // collation could be set to default or not set, information_schema indicate a possibly null value + self::assertFalse($onlineTable->hasOption('autoincrement')); + self::assertEquals('', $onlineTable->getOption('comment')); + self::assertEquals([], $onlineTable->getOption('create_options')); + } + + public function testParseNullCreateOptions() : void + { + $table = $this->schemaManager->listTableDetails('sys.processlist'); + + self::assertEquals([], $table->getOption('create_options')); + } +} diff --git a/tests/Functional/DBAL/Schema/OracleSchemaManagerTest.php b/tests/Functional/DBAL/Schema/OracleSchemaManagerTest.php new file mode 100644 index 0000000..6783f71 --- /dev/null +++ b/tests/Functional/DBAL/Schema/OracleSchemaManagerTest.php @@ -0,0 +1,288 @@ +exec('GRANT ALL PRIVILEGES TO ' . $GLOBALS['db_username']); + + self::$privilegesGranted = true; + } + + public function testRenameTable() : void + { + $this->schemaManager->tryMethod('DropTable', 'list_tables_test'); + $this->schemaManager->tryMethod('DropTable', 'list_tables_test_new_name'); + + $this->createTestTable('list_tables_test'); + $this->schemaManager->renameTable('list_tables_test', 'list_tables_test_new_name'); + + $tables = $this->schemaManager->listTables(); + + self::assertHasTable($tables, 'list_tables_test_new_name'); + } + + public function testListTableWithBinary() : void + { + $tableName = 'test_binary_table'; + + $table = new Table($tableName); + $table->addColumn('id', 'integer'); + $table->addColumn('column_varbinary', 'binary', []); + $table->addColumn('column_binary', 'binary', ['fixed' => true]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $table = $this->schemaManager->listTableDetails($tableName); + + self::assertInstanceOf(BinaryType::class, $table->getColumn('column_varbinary')->getType()); + self::assertFalse($table->getColumn('column_varbinary')->getFixed()); + + self::assertInstanceOf(BinaryType::class, $table->getColumn('column_binary')->getType()); + self::assertFalse($table->getColumn('column_binary')->getFixed()); + } + + /** + * @group DBAL-472 + * @group DBAL-1001 + */ + public function testAlterTableColumnNotNull() : void + { + $comparator = new Schema\Comparator(); + $tableName = 'list_table_column_notnull'; + $table = new Schema\Table($tableName); + + $table->addColumn('id', 'integer'); + $table->addColumn('foo', 'integer'); + $table->addColumn('bar', 'string'); + $table->setPrimaryKey(['id']); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertTrue($columns['id']->getNotnull()); + self::assertTrue($columns['foo']->getNotnull()); + self::assertTrue($columns['bar']->getNotnull()); + + $diffTable = clone $table; + $diffTable->changeColumn('foo', ['notnull' => false]); + $diffTable->changeColumn('bar', ['length' => 1024]); + + $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); + + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertTrue($columns['id']->getNotnull()); + self::assertFalse($columns['foo']->getNotnull()); + self::assertTrue($columns['bar']->getNotnull()); + } + + public function testListDatabases() : void + { + // We need the temp connection that has privileges to create a database. + $sm = TestUtil::getTempConnection()->getSchemaManager(); + + $sm->dropAndCreateDatabase('c##test_create_database'); + + $databases = $this->schemaManager->listDatabases(); + $databases = array_map('strtolower', $databases); + + self::assertContains('c##test_create_database', $databases); + } + + /** + * @group DBAL-831 + */ + public function testListTableDetailsWithDifferentIdentifierQuotingRequirements() : void + { + $primaryTableName = '"Primary_Table"'; + $offlinePrimaryTable = new Schema\Table($primaryTableName); + $offlinePrimaryTable->addColumn( + '"Id"', + 'integer', + ['autoincrement' => true, 'comment' => 'Explicit casing.'] + ); + $offlinePrimaryTable->addColumn('select', 'integer', ['comment' => 'Reserved keyword.']); + $offlinePrimaryTable->addColumn('foo', 'integer', ['comment' => 'Implicit uppercasing.']); + $offlinePrimaryTable->addColumn('BAR', 'integer'); + $offlinePrimaryTable->addColumn('"BAZ"', 'integer'); + $offlinePrimaryTable->addIndex(['select'], 'from'); + $offlinePrimaryTable->addIndex(['foo'], 'foo_index'); + $offlinePrimaryTable->addIndex(['BAR'], 'BAR_INDEX'); + $offlinePrimaryTable->addIndex(['"BAZ"'], 'BAZ_INDEX'); + $offlinePrimaryTable->setPrimaryKey(['"Id"']); + + $foreignTableName = 'foreign'; + $offlineForeignTable = new Schema\Table($foreignTableName); + $offlineForeignTable->addColumn('id', 'integer', ['autoincrement' => true]); + $offlineForeignTable->addColumn('"Fk"', 'integer'); + $offlineForeignTable->addIndex(['"Fk"'], '"Fk_index"'); + $offlineForeignTable->addForeignKeyConstraint( + $primaryTableName, + ['"Fk"'], + ['"Id"'], + [], + '"Primary_Table_Fk"' + ); + $offlineForeignTable->setPrimaryKey(['id']); + + $this->schemaManager->tryMethod('dropTable', $foreignTableName); + $this->schemaManager->tryMethod('dropTable', $primaryTableName); + + $this->schemaManager->createTable($offlinePrimaryTable); + $this->schemaManager->createTable($offlineForeignTable); + + $onlinePrimaryTable = $this->schemaManager->listTableDetails($primaryTableName); + $onlineForeignTable = $this->schemaManager->listTableDetails($foreignTableName); + + $platform = $this->schemaManager->getDatabasePlatform(); + + // Primary table assertions + self::assertSame($primaryTableName, $onlinePrimaryTable->getQuotedName($platform)); + + self::assertTrue($onlinePrimaryTable->hasColumn('"Id"')); + self::assertSame('"Id"', $onlinePrimaryTable->getColumn('"Id"')->getQuotedName($platform)); + self::assertTrue($onlinePrimaryTable->hasPrimaryKey()); + self::assertSame(['"Id"'], $onlinePrimaryTable->getPrimaryKey()->getQuotedColumns($platform)); + + self::assertTrue($onlinePrimaryTable->hasColumn('select')); + self::assertSame('"select"', $onlinePrimaryTable->getColumn('select')->getQuotedName($platform)); + + self::assertTrue($onlinePrimaryTable->hasColumn('foo')); + self::assertSame('FOO', $onlinePrimaryTable->getColumn('foo')->getQuotedName($platform)); + + self::assertTrue($onlinePrimaryTable->hasColumn('BAR')); + self::assertSame('BAR', $onlinePrimaryTable->getColumn('BAR')->getQuotedName($platform)); + + self::assertTrue($onlinePrimaryTable->hasColumn('"BAZ"')); + self::assertSame('BAZ', $onlinePrimaryTable->getColumn('"BAZ"')->getQuotedName($platform)); + + self::assertTrue($onlinePrimaryTable->hasIndex('from')); + self::assertTrue($onlinePrimaryTable->getIndex('from')->hasColumnAtPosition('"select"')); + self::assertSame(['"select"'], $onlinePrimaryTable->getIndex('from')->getQuotedColumns($platform)); + + self::assertTrue($onlinePrimaryTable->hasIndex('foo_index')); + self::assertTrue($onlinePrimaryTable->getIndex('foo_index')->hasColumnAtPosition('foo')); + self::assertSame(['FOO'], $onlinePrimaryTable->getIndex('foo_index')->getQuotedColumns($platform)); + + self::assertTrue($onlinePrimaryTable->hasIndex('BAR_INDEX')); + self::assertTrue($onlinePrimaryTable->getIndex('BAR_INDEX')->hasColumnAtPosition('BAR')); + self::assertSame(['BAR'], $onlinePrimaryTable->getIndex('BAR_INDEX')->getQuotedColumns($platform)); + + self::assertTrue($onlinePrimaryTable->hasIndex('BAZ_INDEX')); + self::assertTrue($onlinePrimaryTable->getIndex('BAZ_INDEX')->hasColumnAtPosition('"BAZ"')); + self::assertSame(['BAZ'], $onlinePrimaryTable->getIndex('BAZ_INDEX')->getQuotedColumns($platform)); + + // Foreign table assertions + self::assertTrue($onlineForeignTable->hasColumn('id')); + self::assertSame('ID', $onlineForeignTable->getColumn('id')->getQuotedName($platform)); + self::assertTrue($onlineForeignTable->hasPrimaryKey()); + self::assertSame(['ID'], $onlineForeignTable->getPrimaryKey()->getQuotedColumns($platform)); + + self::assertTrue($onlineForeignTable->hasColumn('"Fk"')); + self::assertSame('"Fk"', $onlineForeignTable->getColumn('"Fk"')->getQuotedName($platform)); + + self::assertTrue($onlineForeignTable->hasIndex('"Fk_index"')); + self::assertTrue($onlineForeignTable->getIndex('"Fk_index"')->hasColumnAtPosition('"Fk"')); + self::assertSame(['"Fk"'], $onlineForeignTable->getIndex('"Fk_index"')->getQuotedColumns($platform)); + + self::assertTrue($onlineForeignTable->hasForeignKey('"Primary_Table_Fk"')); + self::assertSame( + $primaryTableName, + $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignTableName($platform) + ); + self::assertSame( + ['"Fk"'], + $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedLocalColumns($platform) + ); + self::assertSame( + ['"Id"'], + $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignColumns($platform) + ); + } + + public function testListTableColumnsSameTableNamesInDifferentSchemas() : void + { + $table = $this->createListTableColumns(); + $this->schemaManager->dropAndCreateTable($table); + + $otherTable = new Table($table->getName()); + $otherTable->addColumn('id', Types::STRING); + TestUtil::getTempConnection()->getSchemaManager()->dropAndCreateTable($otherTable); + + $columns = $this->schemaManager->listTableColumns($table->getName(), $this->connection->getUsername()); + self::assertCount(7, $columns); + } + + /** + * @group DBAL-1234 + */ + public function testListTableIndexesPrimaryKeyConstraintNameDiffersFromIndexName() : void + { + $table = new Table('list_table_indexes_pk_id_test'); + $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); + $table->addColumn('id', 'integer', ['notnull' => true]); + $table->addUniqueIndex(['id'], 'id_unique_index'); + $this->schemaManager->dropAndCreateTable($table); + + // Adding a primary key on already indexed columns + // Oracle will reuse the unique index, which cause a constraint name differing from the index name + $this->schemaManager->createConstraint(new Schema\Index('id_pk_id_index', ['id'], true, true), 'list_table_indexes_pk_id_test'); + + $tableIndexes = $this->schemaManager->listTableIndexes('list_table_indexes_pk_id_test'); + + self::assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.'); + self::assertEquals(['id'], array_map('strtolower', $tableIndexes['primary']->getColumns())); + self::assertTrue($tableIndexes['primary']->isUnique()); + self::assertTrue($tableIndexes['primary']->isPrimary()); + } + + /** + * @group DBAL-2555 + */ + public function testListTableDateTypeColumns() : void + { + $table = new Table('tbl_date'); + $table->addColumn('col_date', 'date'); + $table->addColumn('col_datetime', 'datetime'); + $table->addColumn('col_datetimetz', 'datetimetz'); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('tbl_date'); + + self::assertSame('date', $columns['col_date']->getType()->getName()); + self::assertSame('datetime', $columns['col_datetime']->getType()->getName()); + self::assertSame('datetimetz', $columns['col_datetimetz']->getType()->getName()); + } + + public function testCreateAndListSequences() : void + { + self::markTestSkipped("Skipped for uppercase letters are contained in sequences' names. Fix the schema manager in 3.0."); + } +} diff --git a/tests/Functional/DBAL/Schema/PostgreSqlSchemaManagerTest.php b/tests/Functional/DBAL/Schema/PostgreSqlSchemaManagerTest.php new file mode 100644 index 0000000..0c579c3 --- /dev/null +++ b/tests/Functional/DBAL/Schema/PostgreSqlSchemaManagerTest.php @@ -0,0 +1,536 @@ +connection) { + return; + } + + $this->connection->getConfiguration()->setFilterSchemaAssetsExpression(null); + } + + /** + * @group DBAL-244 + */ + public function testGetSchemaNames() : void + { + $names = $this->schemaManager->getSchemaNames(); + + self::assertIsArray($names); + self::assertNotEmpty($names); + self::assertContains('public', $names, 'The public schema should be found.'); + } + + /** + * @group DBAL-21 + */ + public function testSupportDomainTypeFallback() : void + { + $createDomainTypeSQL = 'CREATE DOMAIN MyMoney AS DECIMAL(18,2)'; + $this->connection->exec($createDomainTypeSQL); + + $createTableSQL = 'CREATE TABLE domain_type_test (id INT PRIMARY KEY, value MyMoney)'; + $this->connection->exec($createTableSQL); + + $table = $this->connection->getSchemaManager()->listTableDetails('domain_type_test'); + self::assertInstanceOf(DecimalType::class, $table->getColumn('value')->getType()); + + Type::addType('MyMoney', MoneyType::class); + $this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('MyMoney', 'MyMoney'); + + $table = $this->connection->getSchemaManager()->listTableDetails('domain_type_test'); + self::assertInstanceOf(MoneyType::class, $table->getColumn('value')->getType()); + } + + /** + * @group DBAL-37 + */ + public function testDetectsAutoIncrement() : void + { + $autoincTable = new Table('autoinc_table'); + $column = $autoincTable->addColumn('id', 'integer'); + $column->setAutoincrement(true); + $this->schemaManager->createTable($autoincTable); + $autoincTable = $this->schemaManager->listTableDetails('autoinc_table'); + + self::assertTrue($autoincTable->getColumn('id')->getAutoincrement()); + } + + /** + * @group DBAL-37 + */ + public function testAlterTableAutoIncrementAdd() : void + { + $tableFrom = new Table('autoinc_table_add'); + $column = $tableFrom->addColumn('id', 'integer'); + $this->schemaManager->createTable($tableFrom); + $tableFrom = $this->schemaManager->listTableDetails('autoinc_table_add'); + self::assertFalse($tableFrom->getColumn('id')->getAutoincrement()); + + $tableTo = new Table('autoinc_table_add'); + $column = $tableTo->addColumn('id', 'integer'); + $column->setAutoincrement(true); + + $c = new Comparator(); + $diff = $c->diffTable($tableFrom, $tableTo); + $sql = $this->connection->getDatabasePlatform()->getAlterTableSQL($diff); + self::assertEquals([ + 'CREATE SEQUENCE autoinc_table_add_id_seq', + "SELECT setval('autoinc_table_add_id_seq', (SELECT MAX(id) FROM autoinc_table_add))", + "ALTER TABLE autoinc_table_add ALTER id SET DEFAULT nextval('autoinc_table_add_id_seq')", + ], $sql); + + $this->schemaManager->alterTable($diff); + $tableFinal = $this->schemaManager->listTableDetails('autoinc_table_add'); + self::assertTrue($tableFinal->getColumn('id')->getAutoincrement()); + } + + /** + * @group DBAL-37 + */ + public function testAlterTableAutoIncrementDrop() : void + { + $tableFrom = new Table('autoinc_table_drop'); + $column = $tableFrom->addColumn('id', 'integer'); + $column->setAutoincrement(true); + $this->schemaManager->createTable($tableFrom); + $tableFrom = $this->schemaManager->listTableDetails('autoinc_table_drop'); + self::assertTrue($tableFrom->getColumn('id')->getAutoincrement()); + + $tableTo = new Table('autoinc_table_drop'); + $column = $tableTo->addColumn('id', 'integer'); + + $c = new Comparator(); + $diff = $c->diffTable($tableFrom, $tableTo); + self::assertInstanceOf(TableDiff::class, $diff, 'There should be a difference and not false being returned from the table comparison'); + self::assertEquals(['ALTER TABLE autoinc_table_drop ALTER id DROP DEFAULT'], $this->connection->getDatabasePlatform()->getAlterTableSQL($diff)); + + $this->schemaManager->alterTable($diff); + $tableFinal = $this->schemaManager->listTableDetails('autoinc_table_drop'); + self::assertFalse($tableFinal->getColumn('id')->getAutoincrement()); + } + + /** + * @group DBAL-75 + */ + public function testTableWithSchema() : void + { + $this->connection->exec('CREATE SCHEMA nested'); + + $nestedRelatedTable = new Table('nested.schemarelated'); + $column = $nestedRelatedTable->addColumn('id', 'integer'); + $column->setAutoincrement(true); + $nestedRelatedTable->setPrimaryKey(['id']); + + $nestedSchemaTable = new Table('nested.schematable'); + $column = $nestedSchemaTable->addColumn('id', 'integer'); + $column->setAutoincrement(true); + $nestedSchemaTable->setPrimaryKey(['id']); + $nestedSchemaTable->addUnnamedForeignKeyConstraint($nestedRelatedTable, ['id'], ['id']); + + $this->schemaManager->createTable($nestedRelatedTable); + $this->schemaManager->createTable($nestedSchemaTable); + + $tables = $this->schemaManager->listTableNames(); + self::assertContains('nested.schematable', $tables, 'The table should be detected with its non-public schema.'); + + $nestedSchemaTable = $this->schemaManager->listTableDetails('nested.schematable'); + self::assertTrue($nestedSchemaTable->hasColumn('id')); + self::assertEquals(['id'], $nestedSchemaTable->getPrimaryKey()->getColumns()); + + $relatedFks = $nestedSchemaTable->getForeignKeys(); + self::assertCount(1, $relatedFks); + $relatedFk = array_pop($relatedFks); + self::assertEquals('nested.schemarelated', $relatedFk->getForeignTableName()); + } + + /** + * @group DBAL-91 + * @group DBAL-88 + */ + public function testReturnQuotedAssets() : void + { + $sql = 'create table dbal91_something ( id integer CONSTRAINT id_something PRIMARY KEY NOT NULL ,"table" integer );'; + $this->connection->exec($sql); + + $sql = 'ALTER TABLE dbal91_something ADD CONSTRAINT something_input FOREIGN KEY( "table" ) REFERENCES dbal91_something ON UPDATE CASCADE;'; + $this->connection->exec($sql); + + $table = $this->schemaManager->listTableDetails('dbal91_something'); + + self::assertEquals( + [ + 'CREATE TABLE dbal91_something (id INT NOT NULL, "table" INT DEFAULT NULL, PRIMARY KEY(id))', + 'CREATE INDEX IDX_A9401304ECA7352B ON dbal91_something ("table")', + ], + $this->connection->getDatabasePlatform()->getCreateTableSQL($table) + ); + } + + /** + * @group DBAL-204 + */ + public function testFilterSchemaExpression() : void + { + $testTable = new Table('dbal204_test_prefix'); + $column = $testTable->addColumn('id', 'integer'); + $this->schemaManager->createTable($testTable); + $testTable = new Table('dbal204_without_prefix'); + $column = $testTable->addColumn('id', 'integer'); + $this->schemaManager->createTable($testTable); + + $this->connection->getConfiguration()->setFilterSchemaAssetsExpression('#^dbal204_#'); + $names = $this->schemaManager->listTableNames(); + self::assertCount(2, $names); + + $this->connection->getConfiguration()->setFilterSchemaAssetsExpression('#^dbal204_test#'); + $names = $this->schemaManager->listTableNames(); + self::assertCount(1, $names); + } + + public function testListForeignKeys() : void + { + if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('Does not support foreign key constraints.'); + } + + $fkOptions = ['SET NULL', 'SET DEFAULT', 'NO ACTION','CASCADE', 'RESTRICT']; + $foreignKeys = []; + $fkTable = $this->getTestTable('test_create_fk1'); + for ($i = 0; $i < count($fkOptions); $i++) { + $fkTable->addColumn('foreign_key_test' . $i, 'integer'); + $foreignKeys[] = new ForeignKeyConstraint( + ['foreign_key_test' . $i], + 'test_create_fk2', + ['id'], + 'foreign_key_test' . $i . '_fk', + ['onDelete' => $fkOptions[$i]] + ); + } + $this->schemaManager->dropAndCreateTable($fkTable); + $this->createTestTable('test_create_fk2'); + + foreach ($foreignKeys as $foreignKey) { + $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk1'); + } + $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk1'); + self::assertEquals(count($foreignKeys), count($fkeys), "Table 'test_create_fk1' has to have " . count($foreignKeys) . ' foreign keys.'); + for ($i = 0; $i < count($fkeys); $i++) { + self::assertEquals(['foreign_key_test' . $i], array_map('strtolower', $fkeys[$i]->getLocalColumns())); + self::assertEquals(['id'], array_map('strtolower', $fkeys[$i]->getForeignColumns())); + self::assertEquals('test_create_fk2', strtolower($fkeys[0]->getForeignTableName())); + if ($foreignKeys[$i]->getOption('onDelete') === 'NO ACTION') { + self::assertFalse($fkeys[$i]->hasOption('onDelete'), 'Unexpected option: ' . $fkeys[$i]->getOption('onDelete')); + } else { + self::assertEquals($foreignKeys[$i]->getOption('onDelete'), $fkeys[$i]->getOption('onDelete')); + } + } + } + + /** + * @group DBAL-511 + */ + public function testDefaultValueCharacterVarying() : void + { + $testTable = new Table('dbal511_default'); + $testTable->addColumn('id', 'integer'); + $testTable->addColumn('def', 'string', ['default' => 'foo']); + $testTable->setPrimaryKey(['id']); + + $this->schemaManager->createTable($testTable); + + $databaseTable = $this->schemaManager->listTableDetails($testTable->getName()); + + self::assertEquals('foo', $databaseTable->getColumn('def')->getDefault()); + } + + /** + * @group DDC-2843 + */ + public function testBooleanDefault() : void + { + $table = new Table('ddc2843_bools'); + $table->addColumn('id', 'integer'); + $table->addColumn('checked', 'boolean', ['default' => false]); + + $this->schemaManager->createTable($table); + + $databaseTable = $this->schemaManager->listTableDetails($table->getName()); + + $c = new Comparator(); + $diff = $c->diffTable($table, $databaseTable); + + self::assertFalse($diff); + } + + public function testListTableWithBinary() : void + { + $tableName = 'test_binary_table'; + + $table = new Table($tableName); + $table->addColumn('id', 'integer'); + $table->addColumn('column_varbinary', 'binary', []); + $table->addColumn('column_binary', 'binary', ['fixed' => true]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $table = $this->schemaManager->listTableDetails($tableName); + + self::assertInstanceOf(BlobType::class, $table->getColumn('column_varbinary')->getType()); + self::assertFalse($table->getColumn('column_varbinary')->getFixed()); + + self::assertInstanceOf(BlobType::class, $table->getColumn('column_binary')->getType()); + self::assertFalse($table->getColumn('column_binary')->getFixed()); + } + + public function testListQuotedTable() : void + { + $offlineTable = new Schema\Table('user'); + $offlineTable->addColumn('id', 'integer'); + $offlineTable->addColumn('username', 'string'); + $offlineTable->addColumn('fk', 'integer'); + $offlineTable->setPrimaryKey(['id']); + $offlineTable->addForeignKeyConstraint($offlineTable, ['fk'], ['id']); + + $this->schemaManager->dropAndCreateTable($offlineTable); + + $onlineTable = $this->schemaManager->listTableDetails('"user"'); + + $comparator = new Schema\Comparator(); + + self::assertFalse($comparator->diffTable($offlineTable, $onlineTable)); + } + + public function testListTablesExcludesViews() : void + { + $this->createTestTable('list_tables_excludes_views'); + + $name = 'list_tables_excludes_views_test_view'; + $sql = 'SELECT * from list_tables_excludes_views'; + + $view = new Schema\View($name, $sql); + + $this->schemaManager->dropAndCreateView($view); + + $tables = $this->schemaManager->listTables(); + + $foundTable = false; + foreach ($tables as $table) { + self::assertInstanceOf(Table::class, $table, 'No Table instance was found in tables array.'); + if (strtolower($table->getName()) !== 'list_tables_excludes_views_test_view') { + continue; + } + + $foundTable = true; + } + + self::assertFalse($foundTable, 'View "list_tables_excludes_views_test_view" must not be found in table list'); + } + + /** + * @group DBAL-1033 + */ + public function testPartialIndexes() : void + { + $offlineTable = new Schema\Table('person'); + $offlineTable->addColumn('id', 'integer'); + $offlineTable->addColumn('name', 'string'); + $offlineTable->addColumn('email', 'string'); + $offlineTable->addUniqueIndex(['id', 'name'], 'simple_partial_index', ['where' => '(id IS NULL)']); + + $this->schemaManager->dropAndCreateTable($offlineTable); + + $onlineTable = $this->schemaManager->listTableDetails('person'); + + $comparator = new Schema\Comparator(); + + self::assertFalse($comparator->diffTable($offlineTable, $onlineTable)); + self::assertTrue($onlineTable->hasIndex('simple_partial_index')); + self::assertTrue($onlineTable->getIndex('simple_partial_index')->hasOption('where')); + self::assertSame('(id IS NULL)', $onlineTable->getIndex('simple_partial_index')->getOption('where')); + } + + /** + * @dataProvider jsonbColumnTypeProvider + */ + public function testJsonbColumn(string $type) : void + { + if (! $this->schemaManager->getDatabasePlatform() instanceof PostgreSQL94Platform) { + $this->markTestSkipped('Requires PostgresSQL 9.4+'); + + return; + } + + $table = new Schema\Table('test_jsonb'); + $table->addColumn('foo', $type)->setPlatformOption('jsonb', true); + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('test_jsonb'); + + self::assertSame($type, $columns['foo']->getType()->getName()); + self::assertTrue(true, $columns['foo']->getPlatformOption('jsonb')); + } + + /** + * @return mixed[][] + */ + public function jsonbColumnTypeProvider() : array + { + return [ + [Types::JSON], + [Types::JSON_ARRAY], + ]; + } + + /** + * @group DBAL-2427 + */ + public function testListNegativeColumnDefaultValue() : void + { + $table = new Schema\Table('test_default_negative'); + $table->addColumn('col_smallint', 'smallint', ['default' => -1]); + $table->addColumn('col_integer', 'integer', ['default' => -1]); + $table->addColumn('col_bigint', 'bigint', ['default' => -1]); + $table->addColumn('col_float', 'float', ['default' => -1.1]); + $table->addColumn('col_decimal', 'decimal', ['default' => -1.1]); + $table->addColumn('col_string', 'string', ['default' => '(-1)']); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('test_default_negative'); + + self::assertEquals(-1, $columns['col_smallint']->getDefault()); + self::assertEquals(-1, $columns['col_integer']->getDefault()); + self::assertEquals(-1, $columns['col_bigint']->getDefault()); + self::assertEquals(-1.1, $columns['col_float']->getDefault()); + self::assertEquals(-1.1, $columns['col_decimal']->getDefault()); + self::assertEquals('(-1)', $columns['col_string']->getDefault()); + } + + /** + * @return mixed[][] + */ + public static function serialTypes() : iterable + { + return [ + ['integer'], + ['bigint'], + ]; + } + + /** + * @dataProvider serialTypes + * @group 2906 + */ + public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValue(string $type) : void + { + $tableName = 'test_serial_type_' . $type; + + $table = new Schema\Table($tableName); + $table->addColumn('id', $type, ['autoincrement' => true, 'notnull' => false]); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertNull($columns['id']->getDefault()); + } + + /** + * @dataProvider serialTypes + * @group 2906 + */ + public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValueEvenWhenDefaultIsSet(string $type) : void + { + $tableName = 'test_serial_type_with_default_' . $type; + + $table = new Schema\Table($tableName); + $table->addColumn('id', $type, ['autoincrement' => true, 'notnull' => false, 'default' => 1]); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertNull($columns['id']->getDefault()); + } + + /** + * @group 2916 + * @dataProvider autoIncrementTypeMigrations + */ + public function testAlterTableAutoIncrementIntToBigInt(string $from, string $to, string $expected) : void + { + $tableFrom = new Table('autoinc_type_modification'); + $column = $tableFrom->addColumn('id', $from); + $column->setAutoincrement(true); + $this->schemaManager->dropAndCreateTable($tableFrom); + $tableFrom = $this->schemaManager->listTableDetails('autoinc_type_modification'); + self::assertTrue($tableFrom->getColumn('id')->getAutoincrement()); + + $tableTo = new Table('autoinc_type_modification'); + $column = $tableTo->addColumn('id', $to); + $column->setAutoincrement(true); + + $c = new Comparator(); + $diff = $c->diffTable($tableFrom, $tableTo); + self::assertInstanceOf(TableDiff::class, $diff, 'There should be a difference and not false being returned from the table comparison'); + self::assertSame(['ALTER TABLE autoinc_type_modification ALTER id TYPE ' . $expected . ' USING id::' . $expected], $this->connection->getDatabasePlatform()->getAlterTableSQL($diff)); + + $this->schemaManager->alterTable($diff); + $tableFinal = $this->schemaManager->listTableDetails('autoinc_type_modification'); + self::assertTrue($tableFinal->getColumn('id')->getAutoincrement()); + } + + /** + * @return mixed[][] + */ + public static function autoIncrementTypeMigrations() : iterable + { + return [ + 'int->bigint' => ['integer', 'bigint', 'BIGINT'], + 'bigint->int' => ['bigint', 'integer', 'INT'], + ]; + } +} + +class MoneyType extends Type +{ + /** + * {@inheritDoc} + */ + public function getName() + { + return 'MyMoney'; + } + + /** + * {@inheritDoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return 'MyMoney'; + } +} diff --git a/tests/Functional/DBAL/Schema/SQLAnywhereSchemaManagerTest.php b/tests/Functional/DBAL/Schema/SQLAnywhereSchemaManagerTest.php new file mode 100644 index 0000000..9de342e --- /dev/null +++ b/tests/Functional/DBAL/Schema/SQLAnywhereSchemaManagerTest.php @@ -0,0 +1,64 @@ +createTestTable('view_test_table'); + + $name = 'doctrine_test_view'; + $sql = 'SELECT * from DBA.view_test_table'; + + $view = new View($name, $sql); + + $this->schemaManager->dropAndCreateView($view); + + $views = $this->schemaManager->listViews(); + + self::assertCount(1, $views, 'Database has to have one view.'); + self::assertInstanceOf(View::class, $views[$name]); + self::assertEquals($name, $views[$name]->getName()); + self::assertRegExp('/^SELECT \* from "?DBA"?\."?view_test_table"?$/', $views[$name]->getSql()); + } + + public function testDropAndCreateAdvancedIndex() : void + { + $table = $this->getTestTable('test_create_advanced_index'); + $this->schemaManager->dropAndCreateTable($table); + $this->schemaManager->dropAndCreateIndex( + new Index('test', ['test'], true, false, ['clustered', 'with_nulls_not_distinct', 'for_olap_workload']), + $table->getName() + ); + + $tableIndexes = $this->schemaManager->listTableIndexes('test_create_advanced_index'); + self::assertIsArray($tableIndexes); + self::assertEquals('test', $tableIndexes['test']->getName()); + self::assertEquals(['test'], $tableIndexes['test']->getColumns()); + self::assertTrue($tableIndexes['test']->isUnique()); + self::assertFalse($tableIndexes['test']->isPrimary()); + self::assertTrue($tableIndexes['test']->hasFlag('clustered')); + self::assertTrue($tableIndexes['test']->hasFlag('with_nulls_not_distinct')); + self::assertTrue($tableIndexes['test']->hasFlag('for_olap_workload')); + } + + public function testListTableColumnsWithFixedStringTypeColumn() : void + { + $table = new Table('list_table_columns_char'); + $table->addColumn('id', 'integer', ['notnull' => true]); + $table->addColumn('test', 'string', ['fixed' => true]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('list_table_columns_char'); + + self::assertArrayHasKey('test', $columns); + self::assertTrue($columns['test']->getFixed()); + } +} diff --git a/tests/Functional/DBAL/Schema/SQLServerSchemaManagerTest.php b/tests/Functional/DBAL/Schema/SQLServerSchemaManagerTest.php new file mode 100644 index 0000000..94c85cb --- /dev/null +++ b/tests/Functional/DBAL/Schema/SQLServerSchemaManagerTest.php @@ -0,0 +1,347 @@ +addColumn('id', 'integer'); + $table->addColumn('todrop', 'decimal', ['default' => 10.2]); + + $this->schemaManager->createTable($table); + + $diff = new TableDiff('sqlsrv_drop_column', [], [], [new Column('todrop', Type::getType('decimal'))]); + $this->schemaManager->alterTable($diff); + + $columns = $this->schemaManager->listTableColumns('sqlsrv_drop_column'); + self::assertCount(1, $columns); + } + + public function testColumnCollation() : void + { + $table = new Table($tableName = 'test_collation'); + $column = $table->addColumn($columnName = 'test', 'string'); + + $this->schemaManager->dropAndCreateTable($table); + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertTrue($columns[$columnName]->hasPlatformOption('collation')); // SQL Server should report a default collation on the column + + $column->setPlatformOption('collation', $collation = 'Icelandic_CS_AS'); + + $this->schemaManager->dropAndCreateTable($table); + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertEquals($collation, $columns[$columnName]->getPlatformOption('collation')); + } + + public function testDefaultConstraints() : void + { + $table = new Table('sqlsrv_default_constraints'); + $table->addColumn('no_default', 'string'); + $table->addColumn('df_integer', 'integer', ['default' => 666]); + $table->addColumn('df_string_1', 'string', ['default' => 'foobar']); + $table->addColumn('df_string_2', 'string', ['default' => 'Doctrine rocks!!!']); + $table->addColumn('df_string_3', 'string', ['default' => 'another default value']); + $table->addColumn('df_string_4', 'string', ['default' => 'column to rename']); + $table->addColumn('df_boolean', 'boolean', ['default' => true]); + + $this->schemaManager->createTable($table); + $columns = $this->schemaManager->listTableColumns('sqlsrv_default_constraints'); + + self::assertNull($columns['no_default']->getDefault()); + self::assertEquals(666, $columns['df_integer']->getDefault()); + self::assertEquals('foobar', $columns['df_string_1']->getDefault()); + self::assertEquals('Doctrine rocks!!!', $columns['df_string_2']->getDefault()); + self::assertEquals('another default value', $columns['df_string_3']->getDefault()); + self::assertEquals(1, $columns['df_boolean']->getDefault()); + + $diff = new TableDiff( + 'sqlsrv_default_constraints', + [], + [ + 'df_integer' => new ColumnDiff( + 'df_integer', + new Column('df_integer', Type::getType('integer'), ['default' => 0]), + ['default'], + new Column('df_integer', Type::getType('integer'), ['default' => 666]) + ), + 'df_string_2' => new ColumnDiff( + 'df_string_2', + new Column('df_string_2', Type::getType('string')), + ['default'], + new Column('df_string_2', Type::getType('string'), ['default' => 'Doctrine rocks!!!']) + ), + 'df_string_3' => new ColumnDiff( + 'df_string_3', + new Column('df_string_3', Type::getType('string'), ['length' => 50, 'default' => 'another default value']), + ['length'], + new Column('df_string_3', Type::getType('string'), ['length' => 50, 'default' => 'another default value']) + ), + 'df_boolean' => new ColumnDiff( + 'df_boolean', + new Column('df_boolean', Type::getType('boolean'), ['default' => false]), + ['default'], + new Column('df_boolean', Type::getType('boolean'), ['default' => true]) + ), + ], + [ + 'df_string_1' => new Column('df_string_1', Type::getType('string')), + ], + [], + [], + [], + $table + ); + $diff->newName = 'sqlsrv_default_constraints'; + $diff->renamedColumns['df_string_4'] = new Column( + 'df_string_renamed', + Type::getType('string'), + ['default' => 'column to rename'] + ); + + $this->schemaManager->alterTable($diff); + $columns = $this->schemaManager->listTableColumns('sqlsrv_default_constraints'); + + self::assertNull($columns['no_default']->getDefault()); + self::assertEquals(0, $columns['df_integer']->getDefault()); + self::assertNull($columns['df_string_2']->getDefault()); + self::assertEquals('another default value', $columns['df_string_3']->getDefault()); + self::assertEquals(0, $columns['df_boolean']->getDefault()); + self::assertEquals('column to rename', $columns['df_string_renamed']->getDefault()); + + /** + * Test that column default constraints can still be referenced after table rename + */ + $diff = new TableDiff( + 'sqlsrv_default_constraints', + [], + [ + 'df_integer' => new ColumnDiff( + 'df_integer', + new Column('df_integer', Type::getType('integer'), ['default' => 666]), + ['default'], + new Column('df_integer', Type::getType('integer'), ['default' => 0]) + ), + ], + [], + [], + [], + [], + $table + ); + + $this->schemaManager->alterTable($diff); + $columns = $this->schemaManager->listTableColumns('sqlsrv_default_constraints'); + + self::assertEquals(666, $columns['df_integer']->getDefault()); + } + + /** + * @group DBAL-543 + */ + public function testColumnComments() : void + { + $table = new Table('sqlsrv_column_comment'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('comment_null', 'integer', ['comment' => null]); + $table->addColumn('comment_false', 'integer', ['comment' => false]); + $table->addColumn('comment_empty_string', 'integer', ['comment' => '']); + $table->addColumn('comment_integer_0', 'integer', ['comment' => 0]); + $table->addColumn('comment_float_0', 'integer', ['comment' => 0.0]); + $table->addColumn('comment_string_0', 'integer', ['comment' => '0']); + $table->addColumn('comment', 'integer', ['comment' => 'Doctrine 0wnz you!']); + $table->addColumn('`comment_quoted`', 'integer', ['comment' => 'Doctrine 0wnz comments for explicitly quoted columns!']); + $table->addColumn('create', 'integer', ['comment' => 'Doctrine 0wnz comments for reserved keyword columns!']); + $table->addColumn('commented_type', 'object'); + $table->addColumn('commented_type_with_comment', 'array', ['comment' => 'Doctrine array type.']); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $columns = $this->schemaManager->listTableColumns('sqlsrv_column_comment'); + self::assertCount(12, $columns); + self::assertNull($columns['id']->getComment()); + self::assertNull($columns['comment_null']->getComment()); + self::assertNull($columns['comment_false']->getComment()); + self::assertNull($columns['comment_empty_string']->getComment()); + self::assertEquals('0', $columns['comment_integer_0']->getComment()); + self::assertEquals('0', $columns['comment_float_0']->getComment()); + self::assertEquals('0', $columns['comment_string_0']->getComment()); + self::assertEquals('Doctrine 0wnz you!', $columns['comment']->getComment()); + self::assertEquals('Doctrine 0wnz comments for explicitly quoted columns!', $columns['comment_quoted']->getComment()); + self::assertEquals('Doctrine 0wnz comments for reserved keyword columns!', $columns['[create]']->getComment()); + self::assertNull($columns['commented_type']->getComment()); + self::assertEquals('Doctrine array type.', $columns['commented_type_with_comment']->getComment()); + + $tableDiff = new TableDiff('sqlsrv_column_comment'); + $tableDiff->fromTable = $table; + $tableDiff->addedColumns['added_comment_none'] = new Column('added_comment_none', Type::getType('integer')); + $tableDiff->addedColumns['added_comment_null'] = new Column('added_comment_null', Type::getType('integer'), ['comment' => null]); + $tableDiff->addedColumns['added_comment_false'] = new Column('added_comment_false', Type::getType('integer'), ['comment' => false]); + $tableDiff->addedColumns['added_comment_empty_string'] = new Column('added_comment_empty_string', Type::getType('integer'), ['comment' => '']); + $tableDiff->addedColumns['added_comment_integer_0'] = new Column('added_comment_integer_0', Type::getType('integer'), ['comment' => 0]); + $tableDiff->addedColumns['added_comment_float_0'] = new Column('added_comment_float_0', Type::getType('integer'), ['comment' => 0.0]); + $tableDiff->addedColumns['added_comment_string_0'] = new Column('added_comment_string_0', Type::getType('integer'), ['comment' => '0']); + $tableDiff->addedColumns['added_comment'] = new Column('added_comment', Type::getType('integer'), ['comment' => 'Doctrine']); + $tableDiff->addedColumns['`added_comment_quoted`'] = new Column('`added_comment_quoted`', Type::getType('integer'), ['comment' => 'rulez']); + $tableDiff->addedColumns['select'] = new Column('select', Type::getType('integer'), ['comment' => '666']); + $tableDiff->addedColumns['added_commented_type'] = new Column('added_commented_type', Type::getType('object')); + $tableDiff->addedColumns['added_commented_type_with_comment'] = new Column('added_commented_type_with_comment', Type::getType('array'), ['comment' => '666']); + + $tableDiff->renamedColumns['comment_float_0'] = new Column('comment_double_0', Type::getType('decimal'), ['comment' => 'Double for real!']); + + // Add comment to non-commented column. + $tableDiff->changedColumns['id'] = new ColumnDiff( + 'id', + new Column('id', Type::getType('integer'), ['autoincrement' => true, 'comment' => 'primary']), + ['comment'], + new Column('id', Type::getType('integer'), ['autoincrement' => true]) + ); + + // Remove comment from null-commented column. + $tableDiff->changedColumns['comment_null'] = new ColumnDiff( + 'comment_null', + new Column('comment_null', Type::getType('string')), + ['type'], + new Column('comment_null', Type::getType('integer'), ['comment' => null]) + ); + + // Add comment to false-commented column. + $tableDiff->changedColumns['comment_false'] = new ColumnDiff( + 'comment_false', + new Column('comment_false', Type::getType('integer'), ['comment' => 'false']), + ['comment'], + new Column('comment_false', Type::getType('integer'), ['comment' => false]) + ); + + // Change type to custom type from empty string commented column. + $tableDiff->changedColumns['comment_empty_string'] = new ColumnDiff( + 'comment_empty_string', + new Column('comment_empty_string', Type::getType('object')), + ['type'], + new Column('comment_empty_string', Type::getType('integer'), ['comment' => '']) + ); + + // Change comment to false-comment from zero-string commented column. + $tableDiff->changedColumns['comment_string_0'] = new ColumnDiff( + 'comment_string_0', + new Column('comment_string_0', Type::getType('integer'), ['comment' => false]), + ['comment'], + new Column('comment_string_0', Type::getType('integer'), ['comment' => '0']) + ); + + // Remove comment from regular commented column. + $tableDiff->changedColumns['comment'] = new ColumnDiff( + 'comment', + new Column('comment', Type::getType('integer')), + ['comment'], + new Column('comment', Type::getType('integer'), ['comment' => 'Doctrine 0wnz you!']) + ); + + // Change comment and change type to custom type from regular commented column. + $tableDiff->changedColumns['`comment_quoted`'] = new ColumnDiff( + '`comment_quoted`', + new Column('`comment_quoted`', Type::getType('array'), ['comment' => 'Doctrine array.']), + ['comment', 'type'], + new Column('`comment_quoted`', Type::getType('integer'), ['comment' => 'Doctrine 0wnz you!']) + ); + + // Remove comment and change type to custom type from regular commented column. + $tableDiff->changedColumns['create'] = new ColumnDiff( + 'create', + new Column('create', Type::getType('object')), + ['comment', 'type'], + new Column('create', Type::getType('integer'), ['comment' => 'Doctrine 0wnz comments for reserved keyword columns!']) + ); + + // Add comment and change custom type to regular type from non-commented column. + $tableDiff->changedColumns['commented_type'] = new ColumnDiff( + 'commented_type', + new Column('commented_type', Type::getType('integer'), ['comment' => 'foo']), + ['comment', 'type'], + new Column('commented_type', Type::getType('object')) + ); + + // Remove comment from commented custom type column. + $tableDiff->changedColumns['commented_type_with_comment'] = new ColumnDiff( + 'commented_type_with_comment', + new Column('commented_type_with_comment', Type::getType('array')), + ['comment'], + new Column('commented_type_with_comment', Type::getType('array'), ['comment' => 'Doctrine array type.']) + ); + + $tableDiff->removedColumns['comment_integer_0'] = new Column('comment_integer_0', Type::getType('integer'), ['comment' => 0]); + + $this->schemaManager->alterTable($tableDiff); + + $columns = $this->schemaManager->listTableColumns('sqlsrv_column_comment'); + self::assertCount(23, $columns); + self::assertEquals('primary', $columns['id']->getComment()); + self::assertNull($columns['comment_null']->getComment()); + self::assertEquals('false', $columns['comment_false']->getComment()); + self::assertNull($columns['comment_empty_string']->getComment()); + self::assertEquals('0', $columns['comment_double_0']->getComment()); + self::assertNull($columns['comment_string_0']->getComment()); + self::assertNull($columns['comment']->getComment()); + self::assertEquals('Doctrine array.', $columns['comment_quoted']->getComment()); + self::assertNull($columns['[create]']->getComment()); + self::assertEquals('foo', $columns['commented_type']->getComment()); + self::assertNull($columns['commented_type_with_comment']->getComment()); + self::assertNull($columns['added_comment_none']->getComment()); + self::assertNull($columns['added_comment_null']->getComment()); + self::assertNull($columns['added_comment_false']->getComment()); + self::assertNull($columns['added_comment_empty_string']->getComment()); + self::assertEquals('0', $columns['added_comment_integer_0']->getComment()); + self::assertEquals('0', $columns['added_comment_float_0']->getComment()); + self::assertEquals('0', $columns['added_comment_string_0']->getComment()); + self::assertEquals('Doctrine', $columns['added_comment']->getComment()); + self::assertEquals('rulez', $columns['added_comment_quoted']->getComment()); + self::assertEquals('666', $columns['[select]']->getComment()); + self::assertNull($columns['added_commented_type']->getComment()); + self::assertEquals('666', $columns['added_commented_type_with_comment']->getComment()); + } + + public function testPkOrdering() : void + { + // SQL Server stores index column information in a system table with two + // columns that almost always have the same value: index_column_id and key_ordinal. + // The only situation when the two values doesn't match up is when a clustered index + // is declared that references columns in a different order from which they are + // declared in the table. In that case, key_ordinal != index_column_id. + // key_ordinal holds the index ordering. index_column_id is just a unique identifier + // for index columns within the given index. + $table = new Table('sqlsrv_pk_ordering'); + $table->addColumn('colA', 'integer', ['notnull' => true]); + $table->addColumn('colB', 'integer', ['notnull' => true]); + $table->setPrimaryKey(['colB', 'colA']); + $this->schemaManager->createTable($table); + + $indexes = $this->schemaManager->listTableIndexes('sqlsrv_pk_ordering'); + + self::assertCount(1, $indexes); + + $firstIndex = current($indexes); + $columns = $firstIndex->getColumns(); + self::assertCount(2, $columns); + self::assertEquals('colB', $columns[0]); + self::assertEquals('colA', $columns[1]); + } +} diff --git a/tests/Functional/DBAL/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/DBAL/Schema/SchemaManagerFunctionalTestCase.php new file mode 100644 index 0000000..2b2a74e --- /dev/null +++ b/tests/Functional/DBAL/Schema/SchemaManagerFunctionalTestCase.php @@ -0,0 +1,1597 @@ +getPlatformName(); + + if ($this->connection->getDatabasePlatform()->getName() !== $dbms) { + $this->markTestSkipped(static::class . ' requires the use of ' . $dbms); + } + + $this->schemaManager = $this->connection->getSchemaManager(); + } + + protected function tearDown() : void + { + parent::tearDown(); + + $this->schemaManager->tryMethod('dropTable', 'testschema.my_table_in_namespace'); + + //TODO: SchemaDiff does not drop removed namespaces? + try { + //sql server versions below 2016 do not support 'IF EXISTS' so we have to catch the exception here + $this->connection->exec('DROP SCHEMA testschema'); + } catch (DBALException $e) { + return; + } + } + + /** + * @group DBAL-1220 + */ + public function testDropsDatabaseWithActiveConnections() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsCreateDropDatabase()) { + $this->markTestSkipped('Cannot drop Database client side with this Driver.'); + } + + $this->schemaManager->dropAndCreateDatabase('test_drop_database'); + + $knownDatabases = $this->schemaManager->listDatabases(); + if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { + self::assertContains('TEST_DROP_DATABASE', $knownDatabases); + } else { + self::assertContains('test_drop_database', $knownDatabases); + } + + $params = $this->connection->getParams(); + if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { + $params['user'] = 'test_drop_database'; + } else { + $params['dbname'] = 'test_drop_database'; + } + + $user = $params['user'] ?? null; + $password = $params['password'] ?? null; + + $connection = $this->connection->getDriver()->connect($params, $user, $password); + + self::assertInstanceOf(Connection::class, $connection); + + $this->schemaManager->dropDatabase('test_drop_database'); + + self::assertNotContains('test_drop_database', $this->schemaManager->listDatabases()); + } + + /** + * @group DBAL-195 + */ + public function testDropAndCreateSequence() : void + { + $platform = $this->connection->getDatabasePlatform(); + + if (! $platform->supportsSequences()) { + $this->markTestSkipped( + sprintf('The "%s" platform does not support sequences.', $platform->getName()) + ); + } + + $name = 'dropcreate_sequences_test_seq'; + + $this->schemaManager->dropAndCreateSequence(new Sequence($name, 20, 10)); + + self::assertTrue($this->hasElementWithName($this->schemaManager->listSequences(), $name)); + } + + /** + * @param AbstractAsset[] $items + */ + private function hasElementWithName(array $items, string $name) : bool + { + $filteredList = array_filter( + $items, + static function (AbstractAsset $item) use ($name) : bool { + return $item->getShortestName($item->getNamespaceName()) === $name; + } + ); + + return count($filteredList) === 1; + } + + public function testListSequences() : void + { + $platform = $this->connection->getDatabasePlatform(); + + if (! $platform->supportsSequences()) { + $this->markTestSkipped( + sprintf('The "%s" platform does not support sequences.', $platform->getName()) + ); + } + + $sequence = new Sequence('list_sequences_test_seq', 20, 10); + $this->schemaManager->createSequence($sequence); + + $sequences = $this->schemaManager->listSequences(); + + self::assertIsArray($sequences, 'listSequences() should return an array.'); + + $foundSequence = null; + foreach ($sequences as $sequence) { + self::assertInstanceOf(Sequence::class, $sequence, 'Array elements of listSequences() should be Sequence instances.'); + if (strtolower($sequence->getName()) !== 'list_sequences_test_seq') { + continue; + } + + $foundSequence = $sequence; + } + + self::assertNotNull($foundSequence, "Sequence with name 'list_sequences_test_seq' was not found."); + self::assertSame(20, $foundSequence->getAllocationSize(), 'Allocation Size is expected to be 20.'); + self::assertSame(10, $foundSequence->getInitialValue(), 'Initial Value is expected to be 10.'); + } + + public function testListDatabases() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsCreateDropDatabase()) { + $this->markTestSkipped('Cannot drop Database client side with this Driver.'); + } + + $this->schemaManager->dropAndCreateDatabase('test_create_database'); + $databases = $this->schemaManager->listDatabases(); + + $databases = array_map('strtolower', $databases); + + self::assertContains('test_create_database', $databases); + } + + /** + * @group DBAL-1058 + */ + public function testListNamespaceNames() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) { + $this->markTestSkipped('Platform does not support schemas.'); + } + + // Currently dropping schemas is not supported, so we have to workaround here. + $namespaces = $this->schemaManager->listNamespaceNames(); + $namespaces = array_map('strtolower', $namespaces); + + if (! in_array('test_create_schema', $namespaces)) { + $this->connection->executeUpdate($this->schemaManager->getDatabasePlatform()->getCreateSchemaSQL('test_create_schema')); + + $namespaces = $this->schemaManager->listNamespaceNames(); + $namespaces = array_map('strtolower', $namespaces); + } + + self::assertContains('test_create_schema', $namespaces); + } + + public function testListTables() : void + { + $this->createTestTable('list_tables_test'); + $tables = $this->schemaManager->listTables(); + + self::assertIsArray($tables); + self::assertTrue(count($tables) > 0, "List Tables has to find at least one table named 'list_tables_test'."); + + $foundTable = false; + foreach ($tables as $table) { + self::assertInstanceOf(Table::class, $table); + if (strtolower($table->getName()) !== 'list_tables_test') { + continue; + } + + $foundTable = true; + + self::assertTrue($table->hasColumn('id')); + self::assertTrue($table->hasColumn('test')); + self::assertTrue($table->hasColumn('foreign_key_test')); + } + + self::assertTrue($foundTable, "The 'list_tables_test' table has to be found."); + } + + public function createListTableColumns() : Table + { + $table = new Table('list_table_columns'); + $table->addColumn('id', 'integer', ['notnull' => true]); + $table->addColumn('test', 'string', ['length' => 255, 'notnull' => false, 'default' => 'expected default']); + $table->addColumn('foo', 'text', ['notnull' => true]); + $table->addColumn('bar', 'decimal', ['precision' => 10, 'scale' => 4, 'notnull' => false]); + $table->addColumn('baz1', 'datetime'); + $table->addColumn('baz2', 'time'); + $table->addColumn('baz3', 'date'); + $table->setPrimaryKey(['id']); + + return $table; + } + + public function testListTableColumns() : void + { + $table = $this->createListTableColumns(); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('list_table_columns'); + $columnsKeys = array_keys($columns); + + self::assertArrayHasKey('id', $columns); + self::assertEquals(0, array_search('id', $columnsKeys)); + self::assertEquals('id', strtolower($columns['id']->getname())); + self::assertInstanceOf(IntegerType::class, $columns['id']->gettype()); + self::assertEquals(false, $columns['id']->getunsigned()); + self::assertEquals(true, $columns['id']->getnotnull()); + self::assertEquals(null, $columns['id']->getdefault()); + self::assertIsArray($columns['id']->getPlatformOptions()); + + self::assertArrayHasKey('test', $columns); + self::assertEquals(1, array_search('test', $columnsKeys)); + self::assertEquals('test', strtolower($columns['test']->getname())); + self::assertInstanceOf(StringType::class, $columns['test']->gettype()); + self::assertEquals(255, $columns['test']->getlength()); + self::assertEquals(false, $columns['test']->getfixed()); + self::assertEquals(false, $columns['test']->getnotnull()); + self::assertEquals('expected default', $columns['test']->getdefault()); + self::assertIsArray($columns['test']->getPlatformOptions()); + + self::assertEquals('foo', strtolower($columns['foo']->getname())); + self::assertEquals(2, array_search('foo', $columnsKeys)); + self::assertInstanceOf(TextType::class, $columns['foo']->gettype()); + self::assertEquals(false, $columns['foo']->getunsigned()); + self::assertEquals(false, $columns['foo']->getfixed()); + self::assertEquals(true, $columns['foo']->getnotnull()); + self::assertEquals(null, $columns['foo']->getdefault()); + self::assertIsArray($columns['foo']->getPlatformOptions()); + + self::assertEquals('bar', strtolower($columns['bar']->getname())); + self::assertEquals(3, array_search('bar', $columnsKeys)); + self::assertInstanceOf(DecimalType::class, $columns['bar']->gettype()); + self::assertEquals(null, $columns['bar']->getlength()); + self::assertEquals(10, $columns['bar']->getprecision()); + self::assertEquals(4, $columns['bar']->getscale()); + self::assertEquals(false, $columns['bar']->getunsigned()); + self::assertEquals(false, $columns['bar']->getfixed()); + self::assertEquals(false, $columns['bar']->getnotnull()); + self::assertEquals(null, $columns['bar']->getdefault()); + self::assertIsArray($columns['bar']->getPlatformOptions()); + + self::assertEquals('baz1', strtolower($columns['baz1']->getname())); + self::assertEquals(4, array_search('baz1', $columnsKeys)); + self::assertInstanceOf(DateTimeType::class, $columns['baz1']->gettype()); + self::assertEquals(true, $columns['baz1']->getnotnull()); + self::assertEquals(null, $columns['baz1']->getdefault()); + self::assertIsArray($columns['baz1']->getPlatformOptions()); + + self::assertEquals('baz2', strtolower($columns['baz2']->getname())); + self::assertEquals(5, array_search('baz2', $columnsKeys)); + self::assertContains($columns['baz2']->gettype()->getName(), ['time', 'date', 'datetime']); + self::assertEquals(true, $columns['baz2']->getnotnull()); + self::assertEquals(null, $columns['baz2']->getdefault()); + self::assertIsArray($columns['baz2']->getPlatformOptions()); + + self::assertEquals('baz3', strtolower($columns['baz3']->getname())); + self::assertEquals(6, array_search('baz3', $columnsKeys)); + self::assertContains($columns['baz3']->gettype()->getName(), ['time', 'date', 'datetime']); + self::assertEquals(true, $columns['baz3']->getnotnull()); + self::assertEquals(null, $columns['baz3']->getdefault()); + self::assertIsArray($columns['baz3']->getPlatformOptions()); + } + + /** + * @group DBAL-1078 + */ + public function testListTableColumnsWithFixedStringColumn() : void + { + $tableName = 'test_list_table_fixed_string'; + + $table = new Table($tableName); + $table->addColumn('column_char', 'string', ['fixed' => true, 'length' => 2]); + + $this->schemaManager->createTable($table); + + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertArrayHasKey('column_char', $columns); + self::assertInstanceOf(StringType::class, $columns['column_char']->getType()); + self::assertTrue($columns['column_char']->getFixed()); + self::assertSame(2, $columns['column_char']->getLength()); + } + + public function testListTableColumnsDispatchEvent() : void + { + $table = $this->createListTableColumns(); + + $this->schemaManager->dropAndCreateTable($table); + + $listenerMock = $this + ->getMockBuilder('ListTableColumnsDispatchEventListener') + ->setMethods(['onSchemaColumnDefinition']) + ->getMock(); + $listenerMock + ->expects($this->exactly(7)) + ->method('onSchemaColumnDefinition'); + + $oldEventManager = $this->schemaManager->getDatabasePlatform()->getEventManager(); + + $eventManager = new EventManager(); + $eventManager->addEventListener([Events::onSchemaColumnDefinition], $listenerMock); + + $this->schemaManager->getDatabasePlatform()->setEventManager($eventManager); + + $this->schemaManager->listTableColumns('list_table_columns'); + + $this->schemaManager->getDatabasePlatform()->setEventManager($oldEventManager); + } + + public function testListTableIndexesDispatchEvent() : void + { + $table = $this->getTestTable('list_table_indexes_test'); + $table->addUniqueIndex(['test'], 'test_index_name'); + $table->addIndex(['id', 'test'], 'test_composite_idx'); + + $this->schemaManager->dropAndCreateTable($table); + + $listenerMock = $this + ->getMockBuilder('ListTableIndexesDispatchEventListener') + ->setMethods(['onSchemaIndexDefinition']) + ->getMock(); + $listenerMock + ->expects($this->exactly(3)) + ->method('onSchemaIndexDefinition'); + + $oldEventManager = $this->schemaManager->getDatabasePlatform()->getEventManager(); + + $eventManager = new EventManager(); + $eventManager->addEventListener([Events::onSchemaIndexDefinition], $listenerMock); + + $this->schemaManager->getDatabasePlatform()->setEventManager($eventManager); + + $this->schemaManager->listTableIndexes('list_table_indexes_test'); + + $this->schemaManager->getDatabasePlatform()->setEventManager($oldEventManager); + } + + public function testDiffListTableColumns() : void + { + if ($this->schemaManager->getDatabasePlatform()->getName() === 'oracle') { + $this->markTestSkipped('Does not work with Oracle, since it cannot detect DateTime, Date and Time differenecs (at the moment).'); + } + + $offlineTable = $this->createListTableColumns(); + $this->schemaManager->dropAndCreateTable($offlineTable); + $onlineTable = $this->schemaManager->listTableDetails('list_table_columns'); + + $comparator = new Comparator(); + $diff = $comparator->diffTable($offlineTable, $onlineTable); + + self::assertFalse($diff, 'No differences should be detected with the offline vs online schema.'); + } + + public function testListTableIndexes() : void + { + $table = $this->getTestCompositeTable('list_table_indexes_test'); + $table->addUniqueIndex(['test'], 'test_index_name'); + $table->addIndex(['id', 'test'], 'test_composite_idx'); + + $this->schemaManager->dropAndCreateTable($table); + + $tableIndexes = $this->schemaManager->listTableIndexes('list_table_indexes_test'); + + self::assertEquals(3, count($tableIndexes)); + + self::assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.'); + self::assertEquals(['id', 'other_id'], array_map('strtolower', $tableIndexes['primary']->getColumns())); + self::assertTrue($tableIndexes['primary']->isUnique()); + self::assertTrue($tableIndexes['primary']->isPrimary()); + + self::assertEquals('test_index_name', strtolower($tableIndexes['test_index_name']->getName())); + self::assertEquals(['test'], array_map('strtolower', $tableIndexes['test_index_name']->getColumns())); + self::assertTrue($tableIndexes['test_index_name']->isUnique()); + self::assertFalse($tableIndexes['test_index_name']->isPrimary()); + + self::assertEquals('test_composite_idx', strtolower($tableIndexes['test_composite_idx']->getName())); + self::assertEquals(['id', 'test'], array_map('strtolower', $tableIndexes['test_composite_idx']->getColumns())); + self::assertFalse($tableIndexes['test_composite_idx']->isUnique()); + self::assertFalse($tableIndexes['test_composite_idx']->isPrimary()); + } + + public function testDropAndCreateIndex() : void + { + $table = $this->getTestTable('test_create_index'); + $table->addUniqueIndex(['test'], 'test'); + $this->schemaManager->dropAndCreateTable($table); + + $this->schemaManager->dropAndCreateIndex($table->getIndex('test'), $table); + $tableIndexes = $this->schemaManager->listTableIndexes('test_create_index'); + self::assertIsArray($tableIndexes); + + self::assertEquals('test', strtolower($tableIndexes['test']->getName())); + self::assertEquals(['test'], array_map('strtolower', $tableIndexes['test']->getColumns())); + self::assertTrue($tableIndexes['test']->isUnique()); + self::assertFalse($tableIndexes['test']->isPrimary()); + } + + public function testCreateTableWithForeignKeys() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('Platform does not support foreign keys.'); + } + + $tableB = $this->getTestTable('test_foreign'); + + $this->schemaManager->dropAndCreateTable($tableB); + + $tableA = $this->getTestTable('test_create_fk'); + $tableA->addForeignKeyConstraint('test_foreign', ['foreign_key_test'], ['id']); + + $this->schemaManager->dropAndCreateTable($tableA); + + $fkTable = $this->schemaManager->listTableDetails('test_create_fk'); + $fkConstraints = $fkTable->getForeignKeys(); + self::assertEquals(1, count($fkConstraints), "Table 'test_create_fk1' has to have one foreign key."); + + $fkConstraint = current($fkConstraints); + self::assertInstanceOf(ForeignKeyConstraint::class, $fkConstraint); + self::assertEquals('test_foreign', strtolower($fkConstraint->getForeignTableName())); + self::assertEquals(['foreign_key_test'], array_map('strtolower', $fkConstraint->getColumns())); + self::assertEquals(['id'], array_map('strtolower', $fkConstraint->getForeignColumns())); + + self::assertTrue($fkTable->columnsAreIndexed($fkConstraint->getColumns()), 'The columns of a foreign key constraint should always be indexed.'); + } + + public function testListForeignKeys() : void + { + if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('Does not support foreign key constraints.'); + } + + $this->createTestTable('test_create_fk1'); + $this->createTestTable('test_create_fk2'); + + $foreignKey = new ForeignKeyConstraint( + ['foreign_key_test'], + 'test_create_fk2', + ['id'], + 'foreign_key_test_fk', + ['onDelete' => 'CASCADE'] + ); + + $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk1'); + + $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk1'); + + self::assertEquals(1, count($fkeys), "Table 'test_create_fk1' has to have one foreign key."); + + self::assertInstanceOf(ForeignKeyConstraint::class, $fkeys[0]); + self::assertEquals(['foreign_key_test'], array_map('strtolower', $fkeys[0]->getLocalColumns())); + self::assertEquals(['id'], array_map('strtolower', $fkeys[0]->getForeignColumns())); + self::assertEquals('test_create_fk2', strtolower($fkeys[0]->getForeignTableName())); + + if (! $fkeys[0]->hasOption('onDelete')) { + return; + } + + self::assertEquals('CASCADE', $fkeys[0]->getOption('onDelete')); + } + + protected function getCreateExampleViewSql() : void + { + $this->markTestSkipped('No Create Example View SQL was defined for this SchemaManager'); + } + + public function testCreateSchema() : void + { + $this->createTestTable('test_table'); + + $schema = $this->schemaManager->createSchema(); + self::assertTrue($schema->hasTable('test_table')); + } + + public function testAlterTableScenario() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsAlterTable()) { + $this->markTestSkipped('Alter Table is not supported by this platform.'); + } + + $alterTable = $this->createTestTable('alter_table'); + $this->createTestTable('alter_table_foreign'); + + $table = $this->schemaManager->listTableDetails('alter_table'); + self::assertTrue($table->hasColumn('id')); + self::assertTrue($table->hasColumn('test')); + self::assertTrue($table->hasColumn('foreign_key_test')); + self::assertEquals(0, count($table->getForeignKeys())); + self::assertEquals(1, count($table->getIndexes())); + + $tableDiff = new TableDiff('alter_table'); + $tableDiff->fromTable = $alterTable; + $tableDiff->addedColumns['foo'] = new Column('foo', Type::getType('integer')); + $tableDiff->removedColumns['test'] = $table->getColumn('test'); + + $this->schemaManager->alterTable($tableDiff); + + $table = $this->schemaManager->listTableDetails('alter_table'); + self::assertFalse($table->hasColumn('test')); + self::assertTrue($table->hasColumn('foo')); + + $tableDiff = new TableDiff('alter_table'); + $tableDiff->fromTable = $table; + $tableDiff->addedIndexes[] = new Index('foo_idx', ['foo']); + + $this->schemaManager->alterTable($tableDiff); + + $table = $this->schemaManager->listTableDetails('alter_table'); + self::assertEquals(2, count($table->getIndexes())); + self::assertTrue($table->hasIndex('foo_idx')); + self::assertEquals(['foo'], array_map('strtolower', $table->getIndex('foo_idx')->getColumns())); + self::assertFalse($table->getIndex('foo_idx')->isPrimary()); + self::assertFalse($table->getIndex('foo_idx')->isUnique()); + + $tableDiff = new TableDiff('alter_table'); + $tableDiff->fromTable = $table; + $tableDiff->changedIndexes[] = new Index('foo_idx', ['foo', 'foreign_key_test']); + + $this->schemaManager->alterTable($tableDiff); + + $table = $this->schemaManager->listTableDetails('alter_table'); + self::assertEquals(2, count($table->getIndexes())); + self::assertTrue($table->hasIndex('foo_idx')); + self::assertEquals(['foo', 'foreign_key_test'], array_map('strtolower', $table->getIndex('foo_idx')->getColumns())); + + $tableDiff = new TableDiff('alter_table'); + $tableDiff->fromTable = $table; + $tableDiff->renamedIndexes['foo_idx'] = new Index('bar_idx', ['foo', 'foreign_key_test']); + + $this->schemaManager->alterTable($tableDiff); + + $table = $this->schemaManager->listTableDetails('alter_table'); + self::assertEquals(2, count($table->getIndexes())); + self::assertTrue($table->hasIndex('bar_idx')); + self::assertFalse($table->hasIndex('foo_idx')); + self::assertEquals(['foo', 'foreign_key_test'], array_map('strtolower', $table->getIndex('bar_idx')->getColumns())); + self::assertFalse($table->getIndex('bar_idx')->isPrimary()); + self::assertFalse($table->getIndex('bar_idx')->isUnique()); + + $tableDiff = new TableDiff('alter_table'); + $tableDiff->fromTable = $table; + $tableDiff->removedIndexes[] = new Index('bar_idx', ['foo', 'foreign_key_test']); + $fk = new ForeignKeyConstraint(['foreign_key_test'], 'alter_table_foreign', ['id']); + $tableDiff->addedForeignKeys[] = $fk; + + $this->schemaManager->alterTable($tableDiff); + $table = $this->schemaManager->listTableDetails('alter_table'); + + // dont check for index size here, some platforms automatically add indexes for foreign keys. + self::assertFalse($table->hasIndex('bar_idx')); + + if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { + return; + } + + $fks = $table->getForeignKeys(); + self::assertCount(1, $fks); + $foreignKey = current($fks); + self::assertEquals('alter_table_foreign', strtolower($foreignKey->getForeignTableName())); + self::assertEquals(['foreign_key_test'], array_map('strtolower', $foreignKey->getColumns())); + self::assertEquals(['id'], array_map('strtolower', $foreignKey->getForeignColumns())); + } + + public function testTableInNamespace() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) { + $this->markTestSkipped('Schema definition is not supported by this platform.'); + } + + //create schema + $diff = new SchemaDiff(); + $diff->newNamespaces[] = 'testschema'; + + foreach ($diff->toSql($this->schemaManager->getDatabasePlatform()) as $sql) { + $this->connection->exec($sql); + } + + //test if table is create in namespace + $this->createTestTable('testschema.my_table_in_namespace'); + self::assertContains('testschema.my_table_in_namespace', $this->schemaManager->listTableNames()); + + //tables without namespace should be created in default namespace + //default namespaces are ignored in table listings + $this->createTestTable('my_table_not_in_namespace'); + self::assertContains('my_table_not_in_namespace', $this->schemaManager->listTableNames()); + } + + public function testCreateAndListViews() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsViews()) { + $this->markTestSkipped('Views is not supported by this platform.'); + } + + $this->createTestTable('view_test_table'); + + $name = 'doctrine_test_view'; + $sql = 'SELECT * FROM view_test_table'; + + $view = new View($name, $sql); + + $this->schemaManager->dropAndCreateView($view); + + self::assertTrue($this->hasElementWithName($this->schemaManager->listViews(), $name)); + } + + public function testAutoincrementDetection() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsIdentityColumns()) { + $this->markTestSkipped('This test is only supported on platforms that have autoincrement'); + } + + $table = new Table('test_autoincrement'); + $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $inferredTable = $this->schemaManager->listTableDetails('test_autoincrement'); + self::assertTrue($inferredTable->hasColumn('id')); + self::assertTrue($inferredTable->getColumn('id')->getAutoincrement()); + } + + /** + * @group DBAL-792 + */ + public function testAutoincrementDetectionMulticolumns() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsIdentityColumns()) { + $this->markTestSkipped('This test is only supported on platforms that have autoincrement'); + } + + $table = new Table('test_not_autoincrement'); + $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); + $table->addColumn('id', 'integer'); + $table->addColumn('other_id', 'integer'); + $table->setPrimaryKey(['id', 'other_id']); + + $this->schemaManager->createTable($table); + + $inferredTable = $this->schemaManager->listTableDetails('test_not_autoincrement'); + self::assertTrue($inferredTable->hasColumn('id')); + self::assertFalse($inferredTable->getColumn('id')->getAutoincrement()); + } + + /** + * @group DDC-887 + */ + public function testUpdateSchemaWithForeignKeyRenaming() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('This test is only supported on platforms that have foreign keys.'); + } + + $table = new Table('test_fk_base'); + $table->addColumn('id', 'integer'); + $table->setPrimaryKey(['id']); + + $tableFK = new Table('test_fk_rename'); + $tableFK->setSchemaConfig($this->schemaManager->createSchemaConfig()); + $tableFK->addColumn('id', 'integer'); + $tableFK->addColumn('fk_id', 'integer'); + $tableFK->setPrimaryKey(['id']); + $tableFK->addIndex(['fk_id'], 'fk_idx'); + $tableFK->addForeignKeyConstraint('test_fk_base', ['fk_id'], ['id']); + + $this->schemaManager->createTable($table); + $this->schemaManager->createTable($tableFK); + + $tableFKNew = new Table('test_fk_rename'); + $tableFKNew->setSchemaConfig($this->schemaManager->createSchemaConfig()); + $tableFKNew->addColumn('id', 'integer'); + $tableFKNew->addColumn('rename_fk_id', 'integer'); + $tableFKNew->setPrimaryKey(['id']); + $tableFKNew->addIndex(['rename_fk_id'], 'fk_idx'); + $tableFKNew->addForeignKeyConstraint('test_fk_base', ['rename_fk_id'], ['id']); + + $c = new Comparator(); + $tableDiff = $c->diffTable($tableFK, $tableFKNew); + + $this->schemaManager->alterTable($tableDiff); + + $table = $this->schemaManager->listTableDetails('test_fk_rename'); + $foreignKeys = $table->getForeignKeys(); + + self::assertTrue($table->hasColumn('rename_fk_id')); + self::assertCount(1, $foreignKeys); + self::assertSame(['rename_fk_id'], array_map('strtolower', current($foreignKeys)->getColumns())); + } + + /** + * @group DBAL-1062 + */ + public function testRenameIndexUsedInForeignKeyConstraint() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('This test is only supported on platforms that have foreign keys.'); + } + + $primaryTable = new Table('test_rename_index_primary'); + $primaryTable->addColumn('id', 'integer'); + $primaryTable->setPrimaryKey(['id']); + + $foreignTable = new Table('test_rename_index_foreign'); + $foreignTable->addColumn('fk', 'integer'); + $foreignTable->addIndex(['fk'], 'rename_index_fk_idx'); + $foreignTable->addForeignKeyConstraint( + 'test_rename_index_primary', + ['fk'], + ['id'], + [], + 'fk_constraint' + ); + + $this->schemaManager->dropAndCreateTable($primaryTable); + $this->schemaManager->dropAndCreateTable($foreignTable); + + $foreignTable2 = clone $foreignTable; + $foreignTable2->renameIndex('rename_index_fk_idx', 'renamed_index_fk_idx'); + + $comparator = new Comparator(); + + $this->schemaManager->alterTable($comparator->diffTable($foreignTable, $foreignTable2)); + + $foreignTable = $this->schemaManager->listTableDetails('test_rename_index_foreign'); + + self::assertFalse($foreignTable->hasIndex('rename_index_fk_idx')); + self::assertTrue($foreignTable->hasIndex('renamed_index_fk_idx')); + self::assertTrue($foreignTable->hasForeignKey('fk_constraint')); + } + + /** + * @group DBAL-42 + */ + public function testGetColumnComment() : void + { + if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && + ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && + $this->connection->getDatabasePlatform()->getName() !== 'mssql') { + $this->markTestSkipped('Database does not support column comments.'); + } + + $table = new Table('column_comment_test'); + $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $columns = $this->schemaManager->listTableColumns('column_comment_test'); + self::assertEquals(1, count($columns)); + self::assertEquals('This is a comment', $columns['id']->getComment()); + + $tableDiff = new TableDiff('column_comment_test'); + $tableDiff->fromTable = $table; + $tableDiff->changedColumns['id'] = new ColumnDiff( + 'id', + new Column( + 'id', + Type::getType('integer') + ), + ['comment'], + new Column( + 'id', + Type::getType('integer'), + ['comment' => 'This is a comment'] + ) + ); + + $this->schemaManager->alterTable($tableDiff); + + $columns = $this->schemaManager->listTableColumns('column_comment_test'); + self::assertEquals(1, count($columns)); + self::assertEmpty($columns['id']->getComment()); + } + + /** + * @group DBAL-42 + */ + public function testAutomaticallyAppendCommentOnMarkedColumns() : void + { + if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && + ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && + $this->connection->getDatabasePlatform()->getName() !== 'mssql') { + $this->markTestSkipped('Database does not support column comments.'); + } + + $table = new Table('column_comment_test2'); + $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); + $table->addColumn('obj', 'object', ['comment' => 'This is a comment']); + $table->addColumn('arr', 'array', ['comment' => 'This is a comment']); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $columns = $this->schemaManager->listTableColumns('column_comment_test2'); + self::assertEquals(3, count($columns)); + self::assertEquals('This is a comment', $columns['id']->getComment()); + self::assertEquals('This is a comment', $columns['obj']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.'); + self::assertInstanceOf(ObjectType::class, $columns['obj']->getType(), 'The Doctrine2 should be detected from comment hint.'); + self::assertEquals('This is a comment', $columns['arr']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.'); + self::assertInstanceOf(ArrayType::class, $columns['arr']->getType(), 'The Doctrine2 should be detected from comment hint.'); + } + + /** + * @group DBAL-1228 + */ + public function testCommentHintOnDateIntervalTypeColumn() : void + { + if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && + ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && + $this->connection->getDatabasePlatform()->getName() !== 'mssql') { + $this->markTestSkipped('Database does not support column comments.'); + } + + $table = new Table('column_dateinterval_comment'); + $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); + $table->addColumn('date_interval', 'dateinterval', ['comment' => 'This is a comment']); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $columns = $this->schemaManager->listTableColumns('column_dateinterval_comment'); + self::assertEquals(2, count($columns)); + self::assertEquals('This is a comment', $columns['id']->getComment()); + self::assertEquals('This is a comment', $columns['date_interval']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.'); + self::assertInstanceOf(DateIntervalType::class, $columns['date_interval']->getType(), 'The Doctrine2 should be detected from comment hint.'); + } + + /** + * @group DBAL-825 + */ + public function testChangeColumnsTypeWithDefaultValue() : void + { + $tableName = 'column_def_change_type'; + $table = new Table($tableName); + + $table->addColumn('col_int', 'smallint', ['default' => 666]); + $table->addColumn('col_string', 'string', ['default' => 'foo']); + + $this->schemaManager->dropAndCreateTable($table); + + $tableDiff = new TableDiff($tableName); + $tableDiff->fromTable = $table; + $tableDiff->changedColumns['col_int'] = new ColumnDiff( + 'col_int', + new Column('col_int', Type::getType('integer'), ['default' => 666]), + ['type'], + new Column('col_int', Type::getType('smallint'), ['default' => 666]) + ); + + $tableDiff->changedColumns['col_string'] = new ColumnDiff( + 'col_string', + new Column('col_string', Type::getType('string'), ['default' => 'foo', 'fixed' => true]), + ['fixed'], + new Column('col_string', Type::getType('string'), ['default' => 'foo']) + ); + + $this->schemaManager->alterTable($tableDiff); + + $columns = $this->schemaManager->listTableColumns($tableName); + + self::assertInstanceOf(IntegerType::class, $columns['col_int']->getType()); + self::assertEquals(666, $columns['col_int']->getDefault()); + + self::assertInstanceOf(StringType::class, $columns['col_string']->getType()); + self::assertEquals('foo', $columns['col_string']->getDefault()); + } + + /** + * @group DBAL-197 + */ + public function testListTableWithBlob() : void + { + $table = new Table('test_blob_table'); + $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); + $table->addColumn('binarydata', 'blob', []); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $created = $this->schemaManager->listTableDetails('test_blob_table'); + + self::assertTrue($created->hasColumn('id')); + self::assertTrue($created->hasColumn('binarydata')); + self::assertTrue($created->hasPrimaryKey()); + } + + /** + * @param mixed[] $data + */ + protected function createTestTable(string $name = 'test_table', array $data = []) : Table + { + $options = $data['options'] ?? []; + + $table = $this->getTestTable($name, $options); + + $this->schemaManager->dropAndCreateTable($table); + + return $table; + } + + /** + * @param mixed[] $options + */ + protected function getTestTable(string $name, array $options = []) : Table + { + $table = new Table($name, [], [], [], false, $options); + $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); + $table->addColumn('id', 'integer', ['notnull' => true]); + $table->setPrimaryKey(['id']); + $table->addColumn('test', 'string', ['length' => 255]); + $table->addColumn('foreign_key_test', 'integer'); + + return $table; + } + + protected function getTestCompositeTable(string $name) : Table + { + $table = new Table($name, [], [], [], false, []); + $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); + $table->addColumn('id', 'integer', ['notnull' => true]); + $table->addColumn('other_id', 'integer', ['notnull' => true]); + $table->setPrimaryKey(['id', 'other_id']); + $table->addColumn('test', 'string', ['length' => 255]); + + return $table; + } + + /** + * @param Table[] $tables + */ + protected function assertHasTable(array $tables) : void + { + $foundTable = false; + foreach ($tables as $table) { + self::assertInstanceOf(Table::class, $table, 'No Table instance was found in tables array.'); + if (strtolower($table->getName()) !== 'list_tables_test_new_name') { + continue; + } + + $foundTable = true; + } + self::assertTrue($foundTable, 'Could not find new table'); + } + + public function testListForeignKeysComposite() : void + { + if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('Does not support foreign key constraints.'); + } + + $this->schemaManager->createTable($this->getTestTable('test_create_fk3')); + $this->schemaManager->createTable($this->getTestCompositeTable('test_create_fk4')); + + $foreignKey = new ForeignKeyConstraint( + ['id', 'foreign_key_test'], + 'test_create_fk4', + ['id', 'other_id'], + 'foreign_key_test_fk2' + ); + + $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk3'); + + $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk3'); + + self::assertEquals(1, count($fkeys), "Table 'test_create_fk3' has to have one foreign key."); + + self::assertInstanceOf(ForeignKeyConstraint::class, $fkeys[0]); + self::assertEquals(['id', 'foreign_key_test'], array_map('strtolower', $fkeys[0]->getLocalColumns())); + self::assertEquals(['id', 'other_id'], array_map('strtolower', $fkeys[0]->getForeignColumns())); + } + + /** + * @group DBAL-44 + */ + public function testColumnDefaultLifecycle() : void + { + $table = new Table('col_def_lifecycle'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('column1', 'string', ['default' => null]); + $table->addColumn('column2', 'string', ['default' => false]); + $table->addColumn('column3', 'string', ['default' => true]); + $table->addColumn('column4', 'string', ['default' => 0]); + $table->addColumn('column5', 'string', ['default' => '']); + $table->addColumn('column6', 'string', ['default' => 'def']); + $table->addColumn('column7', 'integer', ['default' => 0]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('col_def_lifecycle'); + + self::assertNull($columns['id']->getDefault()); + self::assertNull($columns['column1']->getDefault()); + self::assertSame('', $columns['column2']->getDefault()); + self::assertSame('1', $columns['column3']->getDefault()); + self::assertSame('0', $columns['column4']->getDefault()); + self::assertSame('', $columns['column5']->getDefault()); + self::assertSame('def', $columns['column6']->getDefault()); + self::assertSame('0', $columns['column7']->getDefault()); + + $diffTable = clone $table; + + $diffTable->changeColumn('column1', ['default' => false]); + $diffTable->changeColumn('column2', ['default' => null]); + $diffTable->changeColumn('column3', ['default' => false]); + $diffTable->changeColumn('column4', ['default' => null]); + $diffTable->changeColumn('column5', ['default' => false]); + $diffTable->changeColumn('column6', ['default' => 666]); + $diffTable->changeColumn('column7', ['default' => null]); + + $comparator = new Comparator(); + + $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); + + $columns = $this->schemaManager->listTableColumns('col_def_lifecycle'); + + self::assertSame('', $columns['column1']->getDefault()); + self::assertNull($columns['column2']->getDefault()); + self::assertSame('', $columns['column3']->getDefault()); + self::assertNull($columns['column4']->getDefault()); + self::assertSame('', $columns['column5']->getDefault()); + self::assertSame('666', $columns['column6']->getDefault()); + self::assertNull($columns['column7']->getDefault()); + } + + public function testListTableWithBinary() : void + { + $tableName = 'test_binary_table'; + + $table = new Table($tableName); + $table->addColumn('id', 'integer'); + $table->addColumn('column_varbinary', 'binary', []); + $table->addColumn('column_binary', 'binary', ['fixed' => true]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $table = $this->schemaManager->listTableDetails($tableName); + + self::assertInstanceOf(BinaryType::class, $table->getColumn('column_varbinary')->getType()); + self::assertFalse($table->getColumn('column_varbinary')->getFixed()); + + self::assertInstanceOf(BinaryType::class, $table->getColumn('column_binary')->getType()); + self::assertTrue($table->getColumn('column_binary')->getFixed()); + } + + public function testListTableDetailsWithFullQualifiedTableName() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) { + $this->markTestSkipped('Test only works on platforms that support schemas.'); + } + + $defaultSchemaName = $this->schemaManager->getDatabasePlatform()->getDefaultSchemaName(); + $primaryTableName = 'primary_table'; + $foreignTableName = 'foreign_table'; + + $table = new Table($foreignTableName); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->dropAndCreateTable($table); + + $table = new Table($primaryTableName); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('foo', 'integer'); + $table->addColumn('bar', 'string'); + $table->addForeignKeyConstraint($foreignTableName, ['foo'], ['id']); + $table->addIndex(['bar']); + $table->setPrimaryKey(['id']); + + $this->schemaManager->dropAndCreateTable($table); + + self::assertEquals( + $this->schemaManager->listTableColumns($primaryTableName), + $this->schemaManager->listTableColumns($defaultSchemaName . '.' . $primaryTableName) + ); + self::assertEquals( + $this->schemaManager->listTableIndexes($primaryTableName), + $this->schemaManager->listTableIndexes($defaultSchemaName . '.' . $primaryTableName) + ); + self::assertEquals( + $this->schemaManager->listTableForeignKeys($primaryTableName), + $this->schemaManager->listTableForeignKeys($defaultSchemaName . '.' . $primaryTableName) + ); + } + + public function testCommentStringsAreQuoted() : void + { + if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && + ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && + $this->connection->getDatabasePlatform()->getName() !== 'mssql') { + $this->markTestSkipped('Database does not support column comments.'); + } + + $table = new Table('my_table'); + $table->addColumn('id', 'integer', ['comment' => "It's a comment with a quote"]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $columns = $this->schemaManager->listTableColumns('my_table'); + self::assertEquals("It's a comment with a quote", $columns['id']->getComment()); + } + + public function testCommentNotDuplicated() : void + { + if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments()) { + $this->markTestSkipped('Database does not support column comments.'); + } + + $options = [ + 'type' => Type::getType('integer'), + 'default' => 0, + 'notnull' => true, + 'comment' => 'expected+column+comment', + ]; + $columnDefinition = substr($this->connection->getDatabasePlatform()->getColumnDeclarationSQL('id', $options), strlen('id') + 1); + + $table = new Table('my_table'); + $table->addColumn('id', 'integer', ['columnDefinition' => $columnDefinition, 'comment' => 'unexpected_column_comment']); + $sql = $this->connection->getDatabasePlatform()->getCreateTableSQL($table); + + self::assertStringContainsString('expected+column+comment', $sql[0]); + self::assertStringNotContainsString('unexpected_column_comment', $sql[0]); + } + + /** + * @group DBAL-1009 + * @dataProvider getAlterColumnComment + */ + public function testAlterColumnComment( + ?string $comment1, + ?string $expectedComment1, + ?string $comment2, + ?string $expectedComment2 + ) : void { + if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && + ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && + $this->connection->getDatabasePlatform()->getName() !== 'mssql') { + $this->markTestSkipped('Database does not support column comments.'); + } + + $offlineTable = new Table('alter_column_comment_test'); + $offlineTable->addColumn('comment1', 'integer', ['comment' => $comment1]); + $offlineTable->addColumn('comment2', 'integer', ['comment' => $comment2]); + $offlineTable->addColumn('no_comment1', 'integer'); + $offlineTable->addColumn('no_comment2', 'integer'); + $this->schemaManager->dropAndCreateTable($offlineTable); + + $onlineTable = $this->schemaManager->listTableDetails('alter_column_comment_test'); + + self::assertSame($expectedComment1, $onlineTable->getColumn('comment1')->getComment()); + self::assertSame($expectedComment2, $onlineTable->getColumn('comment2')->getComment()); + self::assertNull($onlineTable->getColumn('no_comment1')->getComment()); + self::assertNull($onlineTable->getColumn('no_comment2')->getComment()); + + $onlineTable->changeColumn('comment1', ['comment' => $comment2]); + $onlineTable->changeColumn('comment2', ['comment' => $comment1]); + $onlineTable->changeColumn('no_comment1', ['comment' => $comment1]); + $onlineTable->changeColumn('no_comment2', ['comment' => $comment2]); + + $comparator = new Comparator(); + + $tableDiff = $comparator->diffTable($offlineTable, $onlineTable); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + + $this->schemaManager->alterTable($tableDiff); + + $onlineTable = $this->schemaManager->listTableDetails('alter_column_comment_test'); + + self::assertSame($expectedComment2, $onlineTable->getColumn('comment1')->getComment()); + self::assertSame($expectedComment1, $onlineTable->getColumn('comment2')->getComment()); + self::assertSame($expectedComment1, $onlineTable->getColumn('no_comment1')->getComment()); + self::assertSame($expectedComment2, $onlineTable->getColumn('no_comment2')->getComment()); + } + + /** + * @return mixed[][] + */ + public static function getAlterColumnComment() : iterable + { + return [ + [null, null, ' ', ' '], + [null, null, '0', '0'], + [null, null, 'foo', 'foo'], + + ['', null, ' ', ' '], + ['', null, '0', '0'], + ['', null, 'foo', 'foo'], + + [' ', ' ', '0', '0'], + [' ', ' ', 'foo', 'foo'], + + ['0', '0', 'foo', 'foo'], + ]; + } + + /** + * @group DBAL-1095 + */ + public function testDoesNotListIndexesImplicitlyCreatedByForeignKeys() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped('This test is only supported on platforms that have foreign keys.'); + } + + $primaryTable = new Table('test_list_index_impl_primary'); + $primaryTable->addColumn('id', 'integer'); + $primaryTable->setPrimaryKey(['id']); + + $foreignTable = new Table('test_list_index_impl_foreign'); + $foreignTable->addColumn('fk1', 'integer'); + $foreignTable->addColumn('fk2', 'integer'); + $foreignTable->addIndex(['fk1'], 'explicit_fk1_idx'); + $foreignTable->addForeignKeyConstraint('test_list_index_impl_primary', ['fk1'], ['id']); + $foreignTable->addForeignKeyConstraint('test_list_index_impl_primary', ['fk2'], ['id']); + + $this->schemaManager->dropAndCreateTable($primaryTable); + $this->schemaManager->dropAndCreateTable($foreignTable); + + $indexes = $this->schemaManager->listTableIndexes('test_list_index_impl_foreign'); + + self::assertCount(2, $indexes); + self::assertArrayHasKey('explicit_fk1_idx', $indexes); + self::assertArrayHasKey('idx_3d6c147fdc58d6c', $indexes); + } + + /** + * @after + */ + public function removeJsonArrayTable() : void + { + if (! $this->schemaManager->tablesExist(['json_array_test'])) { + return; + } + + $this->schemaManager->dropTable('json_array_test'); + } + + /** + * @group 2782 + * @group 6654 + */ + public function testComparatorShouldReturnFalseWhenLegacyJsonArrayColumnHasComment() : void + { + $table = new Table('json_array_test'); + $table->addColumn('parameters', 'json_array'); + + $this->schemaManager->createTable($table); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); + + self::assertFalse($tableDiff); + } + + /** + * @group 2782 + * @group 6654 + */ + public function testComparatorShouldModifyOnlyTheCommentWhenUpdatingFromJsonArrayTypeOnLegacyPlatforms() : void + { + if ($this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { + $this->markTestSkipped('This test is only supported on platforms that do not have native JSON type.'); + } + + $table = new Table('json_array_test'); + $table->addColumn('parameters', 'json_array'); + + $this->schemaManager->createTable($table); + + $table = new Table('json_array_test'); + $table->addColumn('parameters', 'json'); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + + $changedColumn = $tableDiff->changedColumns['parameters'] ?? $tableDiff->changedColumns['PARAMETERS']; + + self::assertSame(['comment'], $changedColumn->changedProperties); + } + + /** + * @group 2782 + * @group 6654 + */ + public function testComparatorShouldAddCommentToLegacyJsonArrayTypeThatDoesNotHaveIt() : void + { + if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { + $this->markTestSkipped('This test is only supported on platforms that have native JSON type.'); + } + + $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON NOT NULL)'); + + $table = new Table('json_array_test'); + $table->addColumn('parameters', 'json_array'); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertSame(['comment'], $tableDiff->changedColumns['parameters']->changedProperties); + } + + /** + * @group 2782 + * @group 6654 + */ + public function testComparatorShouldReturnAllChangesWhenUsingLegacyJsonArrayType() : void + { + if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { + $this->markTestSkipped('This test is only supported on platforms that have native JSON type.'); + } + + $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON DEFAULT NULL)'); + + $table = new Table('json_array_test'); + $table->addColumn('parameters', 'json_array'); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertSame(['notnull', 'comment'], $tableDiff->changedColumns['parameters']->changedProperties); + } + + /** + * @group 2782 + * @group 6654 + */ + public function testComparatorShouldReturnAllChangesWhenUsingLegacyJsonArrayTypeEvenWhenPlatformHasJsonSupport() : void + { + if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { + $this->markTestSkipped('This test is only supported on platforms that have native JSON type.'); + } + + $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON DEFAULT NULL)'); + + $table = new Table('json_array_test'); + $table->addColumn('parameters', 'json_array'); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertSame(['notnull', 'comment'], $tableDiff->changedColumns['parameters']->changedProperties); + } + + /** + * @group 2782 + * @group 6654 + */ + public function testComparatorShouldNotAddCommentToJsonTypeSinceItIsTheDefaultNow() : void + { + if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { + $this->markTestSkipped('This test is only supported on platforms that have native JSON type.'); + } + + $this->connection->executeQuery('CREATE TABLE json_test (parameters JSON NOT NULL)'); + + $table = new Table('json_test'); + $table->addColumn('parameters', 'json'); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_test'), $table); + + self::assertFalse($tableDiff); + } + + /** + * @dataProvider commentsProvider + * @group 2596 + */ + public function testExtractDoctrineTypeFromComment(string $comment, string $expected, string $currentType) : void + { + $result = $this->schemaManager->extractDoctrineTypeFromComment($comment, $currentType); + + self::assertSame($expected, $result); + } + + /** + * @return string[][] + */ + public function commentsProvider() : array + { + $currentType = 'current type'; + + return [ + 'invalid custom type comments' => ['should.return.current.type', $currentType, $currentType], + 'valid doctrine type' => ['(DC2Type:guid)', 'guid', $currentType], + 'valid with dots' => ['(DC2Type:type.should.return)', 'type.should.return', $currentType], + 'valid with namespace' => ['(DC2Type:Namespace\Class)', 'Namespace\Class', $currentType], + 'valid with extra closing bracket' => ['(DC2Type:should.stop)).before)', 'should.stop', $currentType], + 'valid with extra opening brackets' => ['(DC2Type:should((.stop)).before)', 'should((.stop', $currentType], + ]; + } + + public function testCreateAndListSequences() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsSequences()) { + self::markTestSkipped('This test is only supported on platforms that support sequences.'); + } + + $sequence1Name = 'sequence_1'; + $sequence1AllocationSize = 1; + $sequence1InitialValue = 2; + $sequence2Name = 'sequence_2'; + $sequence2AllocationSize = 3; + $sequence2InitialValue = 4; + $sequence1 = new Sequence($sequence1Name, $sequence1AllocationSize, $sequence1InitialValue); + $sequence2 = new Sequence($sequence2Name, $sequence2AllocationSize, $sequence2InitialValue); + + $this->schemaManager->createSequence($sequence1); + $this->schemaManager->createSequence($sequence2); + + /** @var Sequence[] $actualSequences */ + $actualSequences = []; + foreach ($this->schemaManager->listSequences() as $sequence) { + $actualSequences[$sequence->getName()] = $sequence; + } + + $actualSequence1 = $actualSequences[$sequence1Name]; + $actualSequence2 = $actualSequences[$sequence2Name]; + + self::assertSame($sequence1Name, $actualSequence1->getName()); + self::assertEquals($sequence1AllocationSize, $actualSequence1->getAllocationSize()); + self::assertEquals($sequence1InitialValue, $actualSequence1->getInitialValue()); + + self::assertSame($sequence2Name, $actualSequence2->getName()); + self::assertEquals($sequence2AllocationSize, $actualSequence2->getAllocationSize()); + self::assertEquals($sequence2InitialValue, $actualSequence2->getInitialValue()); + } + + /** + * @group #3086 + */ + public function testComparisonWithAutoDetectedSequenceDefinition() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsSequences()) { + self::markTestSkipped('This test is only supported on platforms that support sequences.'); + } + + $sequenceName = 'sequence_auto_detect_test'; + $sequenceAllocationSize = 5; + $sequenceInitialValue = 10; + $sequence = new Sequence($sequenceName, $sequenceAllocationSize, $sequenceInitialValue); + + $this->schemaManager->dropAndCreateSequence($sequence); + + $createdSequence = array_values( + array_filter( + $this->schemaManager->listSequences(), + static function (Sequence $sequence) use ($sequenceName) : bool { + return strcasecmp($sequence->getName(), $sequenceName) === 0; + } + ) + )[0] ?? null; + + self::assertNotNull($createdSequence); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffSequence($createdSequence, $sequence); + + self::assertFalse($tableDiff); + } + + /** + * @group DBAL-2921 + */ + public function testPrimaryKeyAutoIncrement() : void + { + $table = new Table('test_pk_auto_increment'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('text', 'string'); + $table->setPrimaryKey(['id']); + $this->schemaManager->dropAndCreateTable($table); + + $this->connection->insert('test_pk_auto_increment', ['text' => '1']); + + $query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'1\''); + $query->execute(); + $lastUsedIdBeforeDelete = (int) $query->fetchColumn(); + + $this->connection->query('DELETE FROM test_pk_auto_increment'); + + $this->connection->insert('test_pk_auto_increment', ['text' => '2']); + + $query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'2\''); + $query->execute(); + $lastUsedIdAfterDelete = (int) $query->fetchColumn(); + + $this->assertGreaterThan($lastUsedIdBeforeDelete, $lastUsedIdAfterDelete); + } + + public function testGenerateAnIndexWithPartialColumnLength() : void + { + if (! $this->schemaManager->getDatabasePlatform()->supportsColumnLengthIndexes()) { + self::markTestSkipped('This test is only supported on platforms that support indexes with column length definitions.'); + } + + $table = new Table('test_partial_column_index'); + $table->addColumn('long_column', 'string', ['length' => 40]); + $table->addColumn('standard_column', 'integer'); + $table->addIndex(['long_column'], 'partial_long_column_idx', [], ['lengths' => [4]]); + $table->addIndex(['standard_column', 'long_column'], 'standard_and_partial_idx', [], ['lengths' => [null, 2]]); + + $expected = $table->getIndexes(); + + $this->schemaManager->dropAndCreateTable($table); + + $onlineTable = $this->schemaManager->listTableDetails('test_partial_column_index'); + self::assertEquals($expected, $onlineTable->getIndexes()); + } +} diff --git a/tests/Functional/DBAL/Schema/SqliteSchemaManagerTest.php b/tests/Functional/DBAL/Schema/SqliteSchemaManagerTest.php new file mode 100644 index 0000000..7b62751 --- /dev/null +++ b/tests/Functional/DBAL/Schema/SqliteSchemaManagerTest.php @@ -0,0 +1,281 @@ +expectException(DBALException::class); + + $this->schemaManager->listDatabases(); + } + + public function testCreateAndDropDatabase() : void + { + $path = dirname(__FILE__) . '/test_create_and_drop_sqlite_database.sqlite'; + + $this->schemaManager->createDatabase($path); + self::assertFileExists($path); + $this->schemaManager->dropDatabase($path); + self::assertFileNotExists($path); + } + + /** + * @group DBAL-1220 + */ + public function testDropsDatabaseWithActiveConnections() : void + { + $this->schemaManager->dropAndCreateDatabase('test_drop_database'); + + self::assertFileExists('test_drop_database'); + + $params = $this->connection->getParams(); + $params['dbname'] = 'test_drop_database'; + + $user = $params['user'] ?? null; + $password = $params['password'] ?? null; + + $connection = $this->connection->getDriver()->connect($params, $user, $password); + + self::assertInstanceOf(Connection::class, $connection); + + $this->schemaManager->dropDatabase('test_drop_database'); + + self::assertFileNotExists('test_drop_database'); + + unset($connection); + } + + public function testRenameTable() : void + { + $this->createTestTable('oldname'); + $this->schemaManager->renameTable('oldname', 'newname'); + + $tables = $this->schemaManager->listTableNames(); + self::assertContains('newname', $tables); + self::assertNotContains('oldname', $tables); + } + + public function createListTableColumns() : Table + { + $table = parent::createListTableColumns(); + $table->getColumn('id')->setAutoincrement(true); + + return $table; + } + + public function testListForeignKeysFromExistingDatabase() : void + { + $this->connection->exec(<< 'SET NULL', 'onDelete' => 'NO ACTION', 'deferrable' => false, 'deferred' => false] + ), + new Schema\ForeignKeyConstraint( + ['parent'], + 'user', + ['id'], + '1', + ['onUpdate' => 'NO ACTION', 'onDelete' => 'CASCADE', 'deferrable' => false, 'deferred' => false] + ), + new Schema\ForeignKeyConstraint( + ['page'], + 'page', + ['key'], + 'FK_1', + ['onUpdate' => 'NO ACTION', 'onDelete' => 'NO ACTION', 'deferrable' => true, 'deferred' => true] + ), + ]; + + self::assertEquals($expected, $this->schemaManager->listTableForeignKeys('user')); + } + + public function testColumnCollation() : void + { + $table = new Schema\Table('test_collation'); + $table->addColumn('id', 'integer'); + $table->addColumn('text', 'text'); + $table->addColumn('foo', 'text')->setPlatformOption('collation', 'BINARY'); + $table->addColumn('bar', 'text')->setPlatformOption('collation', 'NOCASE'); + $this->schemaManager->dropAndCreateTable($table); + + $columns = $this->schemaManager->listTableColumns('test_collation'); + + self::assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); + self::assertEquals('BINARY', $columns['text']->getPlatformOption('collation')); + self::assertEquals('BINARY', $columns['foo']->getPlatformOption('collation')); + self::assertEquals('NOCASE', $columns['bar']->getPlatformOption('collation')); + } + + public function testListTableWithBinary() : void + { + $tableName = 'test_binary_table'; + + $table = new Table($tableName); + $table->addColumn('id', 'integer'); + $table->addColumn('column_varbinary', 'binary', []); + $table->addColumn('column_binary', 'binary', ['fixed' => true]); + $table->setPrimaryKey(['id']); + + $this->schemaManager->createTable($table); + + $table = $this->schemaManager->listTableDetails($tableName); + + self::assertInstanceOf(BlobType::class, $table->getColumn('column_varbinary')->getType()); + self::assertFalse($table->getColumn('column_varbinary')->getFixed()); + + self::assertInstanceOf(BlobType::class, $table->getColumn('column_binary')->getType()); + self::assertFalse($table->getColumn('column_binary')->getFixed()); + } + + public function testNonDefaultPKOrder() : void + { + if (! extension_loaded('sqlite3')) { + $this->markTestSkipped('This test requires the SQLite3 extension.'); + } + + $version = SQLite3::version(); + if (version_compare($version['versionString'], '3.7.16', '<')) { + $this->markTestSkipped('This version of sqlite doesn\'t return the order of the Primary Key.'); + } + $this->connection->exec(<<schemaManager->listTableIndexes('non_default_pk_order'); + + self::assertCount(1, $tableIndexes); + + self::assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.'); + self::assertEquals(['other_id', 'id'], array_map('strtolower', $tableIndexes['primary']->getColumns())); + } + + /** + * @group DBAL-1779 + */ + public function testListTableColumnsWithWhitespacesInTypeDeclarations() : void + { + $sql = <<connection->exec($sql); + + $columns = $this->schemaManager->listTableColumns('dbal_1779'); + + self::assertCount(2, $columns); + + self::assertArrayHasKey('foo', $columns); + self::assertArrayHasKey('bar', $columns); + + self::assertSame(Type::getType(Types::STRING), $columns['foo']->getType()); + self::assertSame(Type::getType(Types::TEXT), $columns['bar']->getType()); + + self::assertSame(64, $columns['foo']->getLength()); + self::assertSame(100, $columns['bar']->getLength()); + } + + /** + * @dataProvider getDiffListIntegerAutoincrementTableColumnsData + * @group DBAL-924 + */ + public function testDiffListIntegerAutoincrementTableColumns(string $integerType, bool $unsigned, bool $expectedComparatorDiff) : void + { + $tableName = 'test_int_autoincrement_table'; + + $offlineTable = new Table($tableName); + $offlineTable->addColumn('id', $integerType, ['autoincrement' => true, 'unsigned' => $unsigned]); + $offlineTable->setPrimaryKey(['id']); + + $this->schemaManager->dropAndCreateTable($offlineTable); + + $onlineTable = $this->schemaManager->listTableDetails($tableName); + $comparator = new Schema\Comparator(); + $diff = $comparator->diffTable($offlineTable, $onlineTable); + + if ($expectedComparatorDiff) { + self::assertEmpty($this->schemaManager->getDatabasePlatform()->getAlterTableSQL($diff)); + } else { + self::assertFalse($diff); + } + } + + /** + * @return mixed[][] + */ + public static function getDiffListIntegerAutoincrementTableColumnsData() : iterable + { + return [ + ['smallint', false, true], + ['smallint', true, true], + ['integer', false, false], + ['integer', true, true], + ['bigint', false, true], + ['bigint', true, true], + ]; + } + + /** + * @group DBAL-2921 + */ + public function testPrimaryKeyNoAutoIncrement() : void + { + $table = new Schema\Table('test_pk_auto_increment'); + $table->addColumn('id', 'integer'); + $table->addColumn('text', 'text'); + $table->setPrimaryKey(['id']); + $this->schemaManager->dropAndCreateTable($table); + + $this->connection->insert('test_pk_auto_increment', ['text' => '1']); + + $this->connection->query('DELETE FROM test_pk_auto_increment'); + + $this->connection->insert('test_pk_auto_increment', ['text' => '2']); + + $query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = "2"'); + $query->execute(); + $lastUsedIdAfterDelete = (int) $query->fetchColumn(); + + // with an empty table, non autoincrement rowid is always 1 + $this->assertEquals(1, $lastUsedIdAfterDelete); + } +} diff --git a/tests/Functional/DBAL/StatementTest.php b/tests/Functional/DBAL/StatementTest.php new file mode 100644 index 0000000..593d244 --- /dev/null +++ b/tests/Functional/DBAL/StatementTest.php @@ -0,0 +1,322 @@ +addColumn('id', 'integer'); + $table->addColumn('name', 'text', ['notnull' => false]); + $this->connection->getSchemaManager()->dropAndCreateTable($table); + } + + public function testStatementIsReusableAfterClosingCursor() : void + { + if ($this->connection->getDriver() instanceof PDOOracleDriver) { + $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=77181'); + } + + $this->connection->insert('stmt_test', ['id' => 1]); + $this->connection->insert('stmt_test', ['id' => 2]); + + $stmt = $this->connection->prepare('SELECT id FROM stmt_test ORDER BY id'); + + $stmt->execute(); + + $id = $stmt->fetchColumn(); + self::assertEquals(1, $id); + + $stmt->closeCursor(); + + $stmt->execute(); + $id = $stmt->fetchColumn(); + self::assertEquals(1, $id); + $id = $stmt->fetchColumn(); + self::assertEquals(2, $id); + } + + public function testReuseStatementWithLongerResults() : void + { + if ($this->connection->getDriver() instanceof PDOOracleDriver) { + $this->markTestIncomplete('PDO_OCI doesn\'t support fetching blobs via PDOStatement::fetchAll()'); + } + + $sm = $this->connection->getSchemaManager(); + $table = new Table('stmt_longer_results'); + $table->addColumn('param', 'string'); + $table->addColumn('val', 'text'); + $sm->createTable($table); + + $row1 = [ + 'param' => 'param1', + 'val' => 'X', + ]; + $this->connection->insert('stmt_longer_results', $row1); + + $stmt = $this->connection->prepare('SELECT param, val FROM stmt_longer_results ORDER BY param'); + $stmt->execute(); + self::assertEquals([ + ['param1', 'X'], + ], $stmt->fetchAll(FetchMode::NUMERIC)); + + $row2 = [ + 'param' => 'param2', + 'val' => 'A bit longer value', + ]; + $this->connection->insert('stmt_longer_results', $row2); + + $stmt->execute(); + self::assertEquals([ + ['param1', 'X'], + ['param2', 'A bit longer value'], + ], $stmt->fetchAll(FetchMode::NUMERIC)); + } + + public function testFetchLongBlob() : void + { + if ($this->connection->getDriver() instanceof PDOOracleDriver) { + // inserting BLOBs as streams on Oracle requires Oracle-specific SQL syntax which is currently not supported + // see http://php.net/manual/en/pdo.lobs.php#example-1035 + $this->markTestSkipped('DBAL doesn\'t support storing LOBs represented as streams using PDO_OCI'); + } + + // make sure memory limit is large enough to not cause false positives, + // but is still not enough to store a LONGBLOB of the max possible size + $this->iniSet('memory_limit', '4G'); + + $sm = $this->connection->getSchemaManager(); + $table = new Table('stmt_long_blob'); + $table->addColumn('contents', 'blob', ['length' => 0xFFFFFFFF]); + $sm->createTable($table); + + $contents = base64_decode(<<connection->insert('stmt_long_blob', ['contents' => $contents], [ParameterType::LARGE_OBJECT]); + + $stmt = $this->connection->prepare('SELECT contents FROM stmt_long_blob'); + $stmt->execute(); + + $stream = Type::getType('blob') + ->convertToPHPValue( + $stmt->fetchColumn(), + $this->connection->getDatabasePlatform() + ); + + self::assertSame($contents, stream_get_contents($stream)); + } + + public function testIncompletelyFetchedStatementDoesNotBlockConnection() : void + { + $this->connection->insert('stmt_test', ['id' => 1]); + $this->connection->insert('stmt_test', ['id' => 2]); + + $stmt1 = $this->connection->prepare('SELECT id FROM stmt_test'); + $stmt1->execute(); + $stmt1->fetch(); + $stmt1->execute(); + // fetching only one record out of two + $stmt1->fetch(); + + $stmt2 = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); + $stmt2->execute([1]); + self::assertEquals(1, $stmt2->fetchColumn()); + } + + public function testReuseStatementAfterClosingCursor() : void + { + if ($this->connection->getDriver() instanceof PDOOracleDriver) { + $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=77181'); + } + + $this->connection->insert('stmt_test', ['id' => 1]); + $this->connection->insert('stmt_test', ['id' => 2]); + + $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); + + $stmt->execute([1]); + $id = $stmt->fetchColumn(); + self::assertEquals(1, $id); + + $stmt->closeCursor(); + + $stmt->execute([2]); + $id = $stmt->fetchColumn(); + self::assertEquals(2, $id); + } + + public function testReuseStatementWithParameterBoundByReference() : void + { + $this->connection->insert('stmt_test', ['id' => 1]); + $this->connection->insert('stmt_test', ['id' => 2]); + + $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); + $stmt->bindParam(1, $id); + + $id = 1; + $stmt->execute(); + self::assertEquals(1, $stmt->fetchColumn()); + + $id = 2; + $stmt->execute(); + self::assertEquals(2, $stmt->fetchColumn()); + } + + public function testReuseStatementWithReboundValue() : void + { + $this->connection->insert('stmt_test', ['id' => 1]); + $this->connection->insert('stmt_test', ['id' => 2]); + + $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); + + $stmt->bindValue(1, 1); + $stmt->execute(); + self::assertEquals(1, $stmt->fetchColumn()); + + $stmt->bindValue(1, 2); + $stmt->execute(); + self::assertEquals(2, $stmt->fetchColumn()); + } + + public function testReuseStatementWithReboundParam() : void + { + $this->connection->insert('stmt_test', ['id' => 1]); + $this->connection->insert('stmt_test', ['id' => 2]); + + $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); + + $x = 1; + $stmt->bindParam(1, $x); + $stmt->execute(); + self::assertEquals(1, $stmt->fetchColumn()); + + $y = 2; + $stmt->bindParam(1, $y); + $stmt->execute(); + self::assertEquals(2, $stmt->fetchColumn()); + } + + /** + * @param mixed $expected + * + * @dataProvider emptyFetchProvider + */ + public function testFetchFromNonExecutedStatement(callable $fetch, $expected) : void + { + $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); + + self::assertSame($expected, $fetch($stmt)); + } + + public function testCloseCursorOnNonExecutedStatement() : void + { + $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); + self::assertTrue($stmt->closeCursor()); + } + + /** + * @group DBAL-2637 + */ + public function testCloseCursorAfterCursorEnd() : void + { + $stmt = $this->connection->prepare('SELECT name FROM stmt_test'); + + $stmt->execute(); + $stmt->fetch(); + + self::assertTrue($stmt->closeCursor()); + } + + /** + * @param mixed $expected + * + * @dataProvider emptyFetchProvider + */ + public function testFetchFromNonExecutedStatementWithClosedCursor(callable $fetch, $expected) : void + { + $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); + $stmt->closeCursor(); + + self::assertSame($expected, $fetch($stmt)); + } + + /** + * @param mixed $expected + * + * @dataProvider emptyFetchProvider + */ + public function testFetchFromExecutedStatementWithClosedCursor(callable $fetch, $expected) : void + { + $this->connection->insert('stmt_test', ['id' => 1]); + + $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); + $stmt->execute(); + $stmt->closeCursor(); + + self::assertSame($expected, $fetch($stmt)); + } + + /** + * @return mixed[][] + */ + public static function emptyFetchProvider() : iterable + { + return [ + 'fetch' => [ + static function (Statement $stmt) { + return $stmt->fetch(); + }, + false, + ], + 'fetch-column' => [ + static function (Statement $stmt) { + return $stmt->fetchColumn(); + }, + false, + ], + 'fetch-all' => [ + static function (Statement $stmt) { + return $stmt->fetchAll(); + }, + [], + ], + ]; + } + + public function testFetchInColumnMode() : void + { + $platform = $this->connection->getDatabasePlatform(); + $query = $platform->getDummySelectSQL(); + $result = $this->connection->executeQuery($query)->fetch(FetchMode::COLUMN); + + self::assertEquals(1, $result); + } +} diff --git a/tests/Functional/DBAL/TableGeneratorTest.php b/tests/Functional/DBAL/TableGeneratorTest.php new file mode 100644 index 0000000..9fa636e --- /dev/null +++ b/tests/Functional/DBAL/TableGeneratorTest.php @@ -0,0 +1,62 @@ +connection->getDatabasePlatform(); + if ($platform->getName() === 'sqlite') { + $this->markTestSkipped('TableGenerator does not work with SQLite'); + } + + try { + $schema = new Schema(); + $visitor = new TableGeneratorSchemaVisitor(); + $schema->visit($visitor); + + foreach ($schema->toSql($platform) as $sql) { + $this->connection->exec($sql); + } + } catch (Throwable $e) { + } + $this->generator = new TableGenerator($this->connection); + } + + public function testNextVal() : void + { + $id1 = $this->generator->nextValue('tbl1'); + $id2 = $this->generator->nextValue('tbl1'); + $id3 = $this->generator->nextValue('tbl2'); + + self::assertGreaterThan(0, $id1, 'First id has to be larger than 0'); + self::assertEquals($id1 + 1, $id2, 'Second id is one larger than first one.'); + self::assertEquals($id1, $id3, 'First ids from different tables are equal.'); + } + + public function testNextValNotAffectedByOuterTransactions() : void + { + $this->connection->beginTransaction(); + $id1 = $this->generator->nextValue('tbl1'); + $this->connection->rollBack(); + $id2 = $this->generator->nextValue('tbl1'); + + self::assertGreaterThan(0, $id1, 'First id has to be larger than 0'); + self::assertEquals($id1 + 1, $id2, 'Second id is one larger than first one.'); + } +} diff --git a/tests/Functional/DBAL/TemporaryTableTest.php b/tests/Functional/DBAL/TemporaryTableTest.php new file mode 100644 index 0000000..a60fe0e --- /dev/null +++ b/tests/Functional/DBAL/TemporaryTableTest.php @@ -0,0 +1,108 @@ +connection->exec($this->connection->getDatabasePlatform()->getDropTableSQL('nontemporary')); + } catch (Throwable $e) { + } + } + + protected function tearDown() : void + { + if ($this->connection) { + try { + $tempTable = $this->connection->getDatabasePlatform()->getTemporaryTableName('my_temporary'); + $this->connection->exec($this->connection->getDatabasePlatform()->getDropTemporaryTableSQL($tempTable)); + } catch (Throwable $e) { + } + } + + parent::tearDown(); + } + + /** + * @group DDC-1337 + */ + public function testDropTemporaryTableNotAutoCommitTransaction() : void + { + if ($this->connection->getDatabasePlatform()->getName() === 'sqlanywhere' || + $this->connection->getDatabasePlatform()->getName() === 'oracle') { + $this->markTestSkipped('Test does not work on Oracle and SQL Anywhere.'); + } + + $platform = $this->connection->getDatabasePlatform(); + $columnDefinitions = ['id' => ['type' => Type::getType('integer'), 'notnull' => true]]; + $tempTable = $platform->getTemporaryTableName('my_temporary'); + + $createTempTableSQL = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' + . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; + $this->connection->executeUpdate($createTempTableSQL); + + $table = new Table('nontemporary'); + $table->addColumn('id', 'integer'); + $table->setPrimaryKey(['id']); + + $this->connection->getSchemaManager()->createTable($table); + + $this->connection->beginTransaction(); + $this->connection->insert('nontemporary', ['id' => 1]); + $this->connection->exec($platform->getDropTemporaryTableSQL($tempTable)); + $this->connection->insert('nontemporary', ['id' => 2]); + + $this->connection->rollBack(); + + $rows = $this->connection->fetchAll('SELECT * FROM nontemporary'); + self::assertEquals([], $rows, 'In an event of an error this result has one row, because of an implicit commit.'); + } + + /** + * @group DDC-1337 + */ + public function testCreateTemporaryTableNotAutoCommitTransaction() : void + { + if ($this->connection->getDatabasePlatform()->getName() === 'sqlanywhere' || + $this->connection->getDatabasePlatform()->getName() === 'oracle') { + $this->markTestSkipped('Test does not work on Oracle and SQL Anywhere.'); + } + + $platform = $this->connection->getDatabasePlatform(); + $columnDefinitions = ['id' => ['type' => Type::getType('integer'), 'notnull' => true]]; + $tempTable = $platform->getTemporaryTableName('my_temporary'); + + $createTempTableSQL = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' + . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; + + $table = new Table('nontemporary'); + $table->addColumn('id', 'integer'); + $table->setPrimaryKey(['id']); + + $this->connection->getSchemaManager()->createTable($table); + + $this->connection->beginTransaction(); + $this->connection->insert('nontemporary', ['id' => 1]); + + $this->connection->exec($createTempTableSQL); + $this->connection->insert('nontemporary', ['id' => 2]); + + $this->connection->rollBack(); + + try { + $this->connection->exec($platform->getDropTemporaryTableSQL($tempTable)); + } catch (Throwable $e) { + } + + $rows = $this->connection->fetchAll('SELECT * FROM nontemporary'); + self::assertEquals([], $rows, 'In an event of an error this result has one row, because of an implicit commit.'); + } +} diff --git a/tests/Functional/DBAL/Ticket/DBAL168Test.php b/tests/Functional/DBAL/Ticket/DBAL168Test.php new file mode 100644 index 0000000..91dc785 --- /dev/null +++ b/tests/Functional/DBAL/Ticket/DBAL168Test.php @@ -0,0 +1,30 @@ +connection->getDatabasePlatform()->getName() !== 'postgresql') { + $this->markTestSkipped('PostgreSQL only test'); + } + + $table = new Table('domains'); + $table->addColumn('id', 'integer'); + $table->addColumn('parent_id', 'integer'); + $table->setPrimaryKey(['id']); + $table->addForeignKeyConstraint('domains', ['parent_id'], ['id']); + + $this->connection->getSchemaManager()->createTable($table); + $table = $this->connection->getSchemaManager()->listTableDetails('domains'); + + self::assertEquals('domains', $table->getName()); + } +} diff --git a/tests/Functional/DBAL/Ticket/DBAL202Test.php b/tests/Functional/DBAL/Ticket/DBAL202Test.php new file mode 100644 index 0000000..9cf5200 --- /dev/null +++ b/tests/Functional/DBAL/Ticket/DBAL202Test.php @@ -0,0 +1,51 @@ +connection->getDatabasePlatform()->getName() !== 'oracle') { + $this->markTestSkipped('OCI8 only test'); + } + + if ($this->connection->getSchemaManager()->tablesExist('DBAL202')) { + $this->connection->exec('DELETE FROM DBAL202'); + } else { + $table = new Table('DBAL202'); + $table->addColumn('id', 'integer'); + $table->setPrimaryKey(['id']); + + $this->connection->getSchemaManager()->createTable($table); + } + } + + public function testStatementRollback() : void + { + $stmt = $this->connection->prepare('INSERT INTO DBAL202 VALUES (8)'); + $this->connection->beginTransaction(); + $stmt->execute(); + $this->connection->rollBack(); + + self::assertEquals(0, $this->connection->query('SELECT COUNT(1) FROM DBAL202')->fetchColumn()); + } + + public function testStatementCommit() : void + { + $stmt = $this->connection->prepare('INSERT INTO DBAL202 VALUES (8)'); + $this->connection->beginTransaction(); + $stmt->execute(); + $this->connection->commit(); + + self::assertEquals(1, $this->connection->query('SELECT COUNT(1) FROM DBAL202')->fetchColumn()); + } +} diff --git a/tests/Functional/DBAL/Ticket/DBAL421Test.php b/tests/Functional/DBAL/Ticket/DBAL421Test.php new file mode 100644 index 0000000..2a8d50e --- /dev/null +++ b/tests/Functional/DBAL/Ticket/DBAL421Test.php @@ -0,0 +1,56 @@ +connection->getDatabasePlatform()->getName(); + if (in_array($platform, ['mysql', 'sqlite'])) { + return; + } + + $this->markTestSkipped('Currently restricted to MySQL and SQLite.'); + } + + public function testGuidShouldMatchPattern() : void + { + $guid = $this->connection->query($this->getSelectGuidSql())->fetchColumn(); + $pattern = '/[0-9A-F]{8}\-[0-9A-F]{4}\-[0-9A-F]{4}\-[8-9A-B][0-9A-F]{3}\-[0-9A-F]{12}/i'; + self::assertEquals(1, preg_match($pattern, $guid), 'GUID does not match pattern'); + } + + /** + * This test does (of course) not proof that all generated GUIDs are + * random, it should however provide some basic confidence. + */ + public function testGuidShouldBeRandom() : void + { + $statement = $this->connection->prepare($this->getSelectGuidSql()); + $guids = []; + + for ($i = 0; $i < 99; $i++) { + $statement->execute(); + $guid = $statement->fetchColumn(); + self::assertNotContains($guid, $guids, 'Duplicate GUID detected'); + $guids[] = $guid; + } + + $statement->closeCursor(); + } + + private function getSelectGuidSql() : string + { + return 'SELECT ' . $this->connection->getDatabasePlatform()->getGuidExpression(); + } +} diff --git a/tests/Functional/DBAL/Ticket/DBAL461Test.php b/tests/Functional/DBAL/Ticket/DBAL461Test.php new file mode 100644 index 0000000..165af4d --- /dev/null +++ b/tests/Functional/DBAL/Ticket/DBAL461Test.php @@ -0,0 +1,41 @@ +createMock(Connection::class); + $platform = $this->getMockForAbstractClass(AbstractPlatform::class); + $platform->registerDoctrineTypeMapping('numeric', 'decimal'); + + $schemaManager = new SQLServerSchemaManager($conn, $platform); + + $reflectionMethod = new ReflectionMethod($schemaManager, '_getPortableTableColumnDefinition'); + $reflectionMethod->setAccessible(true); + $column = $reflectionMethod->invoke($schemaManager, [ + 'type' => 'numeric(18,0)', + 'length' => null, + 'default' => null, + 'notnull' => false, + 'scale' => 18, + 'precision' => 0, + 'autoincrement' => false, + 'collation' => 'foo', + 'comment' => null, + ]); + + $this->assertInstanceOf(DecimalType::class, $column->getType()); + } +} diff --git a/tests/Functional/DBAL/Ticket/DBAL510Test.php b/tests/Functional/DBAL/Ticket/DBAL510Test.php new file mode 100644 index 0000000..74e297f --- /dev/null +++ b/tests/Functional/DBAL/Ticket/DBAL510Test.php @@ -0,0 +1,40 @@ +connection->getDatabasePlatform()->getName() === 'postgresql') { + return; + } + + $this->markTestSkipped('PostgreSQL Only test'); + } + + public function testSearchPathSchemaChanges() : void + { + $table = new Table('dbal510tbl'); + $table->addColumn('id', 'integer'); + $table->setPrimaryKey(['id']); + + $this->connection->getSchemaManager()->createTable($table); + + $onlineTable = $this->connection->getSchemaManager()->listTableDetails('dbal510tbl'); + + $comparator = new Comparator(); + $diff = $comparator->diffTable($onlineTable, $table); + + self::assertFalse($diff); + } +} diff --git a/tests/Functional/DBAL/Ticket/DBAL630Test.php b/tests/Functional/DBAL/Ticket/DBAL630Test.php new file mode 100644 index 0000000..635687c --- /dev/null +++ b/tests/Functional/DBAL/Ticket/DBAL630Test.php @@ -0,0 +1,172 @@ +connection->getDatabasePlatform()->getName(); + + if (! in_array($platform, ['postgresql'])) { + $this->markTestSkipped('Currently restricted to PostgreSQL'); + } + + try { + $this->connection->exec('CREATE TABLE dbal630 (id SERIAL, bool_col BOOLEAN NOT NULL);'); + $this->connection->exec('CREATE TABLE dbal630_allow_nulls (id SERIAL, bool_col BOOLEAN);'); + } catch (DBALException $e) { + } + $this->running = true; + } + + protected function tearDown() : void + { + if ($this->running) { + $this->connection->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + } + + parent::tearDown(); + } + + public function testBooleanConversionSqlLiteral() : void + { + $this->connection->executeUpdate('INSERT INTO dbal630 (bool_col) VALUES(false)'); + $id = $this->connection->lastInsertId('dbal630_id_seq'); + self::assertNotEmpty($id); + + $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', [$id]); + + self::assertFalse($row['bool_col']); + } + + public function testBooleanConversionBoolParamRealPrepares() : void + { + $this->connection->executeUpdate( + 'INSERT INTO dbal630 (bool_col) VALUES(?)', + ['false'], + [ParameterType::BOOLEAN] + ); + $id = $this->connection->lastInsertId('dbal630_id_seq'); + self::assertNotEmpty($id); + + $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', [$id]); + + self::assertFalse($row['bool_col']); + } + + public function testBooleanConversionBoolParamEmulatedPrepares() : void + { + $this->connection->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + + $platform = $this->connection->getDatabasePlatform(); + + $stmt = $this->connection->prepare('INSERT INTO dbal630 (bool_col) VALUES(?)'); + $stmt->bindValue(1, $platform->convertBooleansToDatabaseValue('false'), ParameterType::BOOLEAN); + $stmt->execute(); + + $id = $this->connection->lastInsertId('dbal630_id_seq'); + + self::assertNotEmpty($id); + + $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', [$id]); + + self::assertFalse($row['bool_col']); + } + + /** + * @dataProvider booleanTypeConversionWithoutPdoTypeProvider + */ + public function testBooleanConversionNullParamEmulatedPrepares( + ?bool $statementValue, + ?bool $databaseConvertedValue + ) : void { + $this->connection->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + + $platform = $this->connection->getDatabasePlatform(); + + $stmt = $this->connection->prepare('INSERT INTO dbal630_allow_nulls (bool_col) VALUES(?)'); + $stmt->bindValue(1, $platform->convertBooleansToDatabaseValue($statementValue)); + $stmt->execute(); + + $id = $this->connection->lastInsertId('dbal630_allow_nulls_id_seq'); + + self::assertNotEmpty($id); + + $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630_allow_nulls WHERE id = ?', [$id]); + + self::assertSame($databaseConvertedValue, $row['bool_col']); + } + + /** + * @dataProvider booleanTypeConversionUsingBooleanTypeProvider + */ + public function testBooleanConversionNullParamEmulatedPreparesWithBooleanTypeInBindValue( + ?bool $statementValue, + bool $databaseConvertedValue + ) : void { + $this->connection->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + + $platform = $this->connection->getDatabasePlatform(); + + $stmt = $this->connection->prepare('INSERT INTO dbal630_allow_nulls (bool_col) VALUES(?)'); + $stmt->bindValue( + 1, + $platform->convertBooleansToDatabaseValue($statementValue), + ParameterType::BOOLEAN + ); + $stmt->execute(); + + $id = $this->connection->lastInsertId('dbal630_allow_nulls_id_seq'); + + self::assertNotEmpty($id); + + $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630_allow_nulls WHERE id = ?', [$id]); + + self::assertSame($databaseConvertedValue, $row['bool_col']); + } + + /** + * Boolean conversion mapping provider + * + * @return mixed[][] + */ + public static function booleanTypeConversionUsingBooleanTypeProvider() : iterable + { + return [ + // statement value, database converted value result + [true, true], + [false, false], + [null, false], + ]; + } + + /** + * Boolean conversion mapping provider + * + * @return mixed[][] + */ + public static function booleanTypeConversionWithoutPdoTypeProvider() : iterable + { + return [ + // statement value, database converted value result + [true, true], + [false, false], + [null, null], + ]; + } +} diff --git a/tests/Functional/DBAL/Ticket/DBAL752Test.php b/tests/Functional/DBAL/Ticket/DBAL752Test.php new file mode 100644 index 0000000..8f0b16c --- /dev/null +++ b/tests/Functional/DBAL/Ticket/DBAL752Test.php @@ -0,0 +1,65 @@ +connection->getDatabasePlatform()->getName(); + + if (in_array($platform, ['sqlite'])) { + return; + } + + $this->markTestSkipped('Related to SQLite only'); + } + + public function testUnsignedIntegerDetection() : void + { + $this->connection->exec(<<connection->getSchemaManager(); + + $fetchedTable = $schemaManager->listTableDetails('dbal752_unsigneds'); + + self::assertEquals('smallint', $fetchedTable->getColumn('small')->getType()->getName()); + self::assertEquals('smallint', $fetchedTable->getColumn('small_unsigned')->getType()->getName()); + self::assertEquals('integer', $fetchedTable->getColumn('medium')->getType()->getName()); + self::assertEquals('integer', $fetchedTable->getColumn('medium_unsigned')->getType()->getName()); + self::assertEquals('integer', $fetchedTable->getColumn('integer')->getType()->getName()); + self::assertEquals('integer', $fetchedTable->getColumn('integer_unsigned')->getType()->getName()); + self::assertEquals('bigint', $fetchedTable->getColumn('big')->getType()->getName()); + self::assertEquals('bigint', $fetchedTable->getColumn('big_unsigned')->getType()->getName()); + + self::assertTrue($fetchedTable->getColumn('small_unsigned')->getUnsigned()); + self::assertTrue($fetchedTable->getColumn('medium_unsigned')->getUnsigned()); + self::assertTrue($fetchedTable->getColumn('integer_unsigned')->getUnsigned()); + self::assertTrue($fetchedTable->getColumn('big_unsigned')->getUnsigned()); + + self::assertFalse($fetchedTable->getColumn('small')->getUnsigned()); + self::assertFalse($fetchedTable->getColumn('medium')->getUnsigned()); + self::assertFalse($fetchedTable->getColumn('integer')->getUnsigned()); + self::assertFalse($fetchedTable->getColumn('big')->getUnsigned()); + } +} diff --git a/tests/Functional/DBAL/TransactionTest.php b/tests/Functional/DBAL/TransactionTest.php new file mode 100644 index 0000000..7ec7375 --- /dev/null +++ b/tests/Functional/DBAL/TransactionTest.php @@ -0,0 +1,39 @@ +connection->getDatabasePlatform() instanceof MySqlPlatform) { + return; + } + + $this->markTestSkipped('Restricted to MySQL.'); + } + + protected function tearDown() : void + { + $this->resetSharedConn(); + + parent::tearDown(); + } + + public function testCommitFalse() : void + { + $this->connection->query('SET SESSION wait_timeout=1'); + + $this->assertTrue($this->connection->beginTransaction()); + + sleep(2); // during the sleep mysql will close the connection + + $this->assertFalse(@$this->connection->commit()); // we will ignore `MySQL server has gone away` warnings + } +} diff --git a/tests/Functional/DBAL/TypeConversionTest.php b/tests/Functional/DBAL/TypeConversionTest.php new file mode 100644 index 0000000..0352bd8 --- /dev/null +++ b/tests/Functional/DBAL/TypeConversionTest.php @@ -0,0 +1,251 @@ +addColumn('id', 'integer', ['notnull' => false]); + $table->addColumn('test_string', 'string', ['notnull' => false]); + $table->addColumn('test_boolean', 'boolean', ['notnull' => false]); + $table->addColumn('test_bigint', 'bigint', ['notnull' => false]); + $table->addColumn('test_smallint', 'bigint', ['notnull' => false]); + $table->addColumn('test_datetime', 'datetime', ['notnull' => false]); + $table->addColumn('test_datetimetz', 'datetimetz', ['notnull' => false]); + $table->addColumn('test_date', 'date', ['notnull' => false]); + $table->addColumn('test_time', 'time', ['notnull' => false]); + $table->addColumn('test_text', 'text', ['notnull' => false]); + $table->addColumn('test_array', 'array', ['notnull' => false]); + $table->addColumn('test_json_array', 'json_array', ['notnull' => false]); + $table->addColumn('test_object', 'object', ['notnull' => false]); + $table->addColumn('test_float', 'float', ['notnull' => false]); + $table->addColumn('test_decimal', 'decimal', ['notnull' => false, 'scale' => 2, 'precision' => 10]); + $table->setPrimaryKey(['id']); + + $this->connection + ->getSchemaManager() + ->dropAndCreateTable($table); + } + + /** + * @param mixed $originalValue + * + * @dataProvider booleanProvider + */ + public function testIdempotentConversionToBoolean(string $type, $originalValue) : void + { + $dbValue = $this->processValue($type, $originalValue); + + self::assertIsBool($dbValue); + self::assertEquals($originalValue, $dbValue); + } + + /** + * @return mixed[][] + */ + public static function booleanProvider() : iterable + { + return [ + 'true' => ['boolean', true], + 'false' => ['boolean', false], + ]; + } + + /** + * @param mixed $originalValue + * + * @dataProvider integerProvider + */ + public function testIdempotentConversionToInteger(string $type, $originalValue) : void + { + $dbValue = $this->processValue($type, $originalValue); + + self::assertIsInt($dbValue); + self::assertEquals($originalValue, $dbValue); + } + + /** + * @return mixed[][] + */ + public static function integerProvider() : iterable + { + return [ + 'smallint' => ['smallint', 123], + ]; + } + + /** + * @param mixed $originalValue + * + * @dataProvider floatProvider + */ + public function testIdempotentConversionToFloat(string $type, $originalValue) : void + { + $dbValue = $this->processValue($type, $originalValue); + + self::assertIsFloat($dbValue); + self::assertEquals($originalValue, $dbValue); + } + + /** + * @return mixed[][] + */ + public static function floatProvider() : iterable + { + return [ + 'float' => ['float', 1.5], + ]; + } + + /** + * @param mixed $originalValue + * + * @dataProvider toStringProvider + */ + public function testIdempotentConversionToString(string $type, $originalValue) : void + { + if ($type === 'text' && $this->connection->getDriver() instanceof PDOOracleDriver) { + // inserting BLOBs as streams on Oracle requires Oracle-specific SQL syntax which is currently not supported + // see http://php.net/manual/en/pdo.lobs.php#example-1035 + $this->markTestSkipped('DBAL doesn\'t support storing LOBs represented as streams using PDO_OCI'); + } + + $dbValue = $this->processValue($type, $originalValue); + + self::assertIsString($dbValue); + self::assertEquals($originalValue, $dbValue); + } + + /** + * @return mixed[][] + */ + public static function toStringProvider() : iterable + { + return [ + 'string' => ['string', 'ABCDEFGabcdefg'], + 'bigint' => ['bigint', 12345678], + 'text' => ['text', str_repeat('foo ', 1000)], + 'decimal' => ['decimal', 1.55], + ]; + } + + /** + * @param mixed $originalValue + * + * @dataProvider toArrayProvider + */ + public function testIdempotentConversionToArray(string $type, $originalValue) : void + { + $dbValue = $this->processValue($type, $originalValue); + + self::assertIsArray($dbValue); + self::assertEquals($originalValue, $dbValue); + } + + /** + * @return mixed[][] + */ + public static function toArrayProvider() : iterable + { + return [ + 'array' => ['array', ['foo' => 'bar']], + 'json_array' => ['json_array', ['foo' => 'bar']], + ]; + } + + /** + * @param mixed $originalValue + * + * @dataProvider toObjectProvider + */ + public function testIdempotentConversionToObject(string $type, $originalValue) : void + { + $dbValue = $this->processValue($type, $originalValue); + + self::assertIsObject($dbValue); + self::assertEquals($originalValue, $dbValue); + } + + /** + * @return mixed[][] + */ + public static function toObjectProvider() : iterable + { + $obj = new stdClass(); + $obj->foo = 'bar'; + $obj->bar = 'baz'; + + return [ + 'object' => ['object', $obj], + ]; + } + + /** + * @dataProvider toDateTimeProvider + */ + public function testIdempotentConversionToDateTime(string $type, DateTime $originalValue) : void + { + $dbValue = $this->processValue($type, $originalValue); + + self::assertInstanceOf(DateTime::class, $dbValue); + + if ($type === 'datetimetz') { + return; + } + + self::assertEquals($originalValue, $dbValue); + self::assertEquals( + $originalValue->getTimezone(), + $dbValue->getTimezone() + ); + } + + /** + * @return mixed[][] + */ + public static function toDateTimeProvider() : iterable + { + return [ + 'datetime' => ['datetime', new DateTime('2010-04-05 10:10:10')], + 'datetimetz' => ['datetimetz', new DateTime('2010-04-05 10:10:10')], + 'date' => ['date', new DateTime('2010-04-05')], + 'time' => ['time', new DateTime('1970-01-01 10:10:10')], + ]; + } + + /** + * @param mixed $originalValue + * + * @return mixed + */ + private function processValue(string $type, $originalValue) + { + $columnName = 'test_' . $type; + $typeInstance = Type::getType($type); + $insertionValue = $typeInstance->convertToDatabaseValue($originalValue, $this->connection->getDatabasePlatform()); + + $this->connection->insert('type_conversion', ['id' => ++self::$typeCounter, $columnName => $insertionValue]); + + $sql = 'SELECT ' . $columnName . ' FROM type_conversion WHERE id = ' . self::$typeCounter; + + return $typeInstance->convertToPHPValue( + $this->connection->fetchColumn($sql), + $this->connection->getDatabasePlatform() + ); + } +} diff --git a/tests/Functional/DBAL/Types/BinaryTest.php b/tests/Functional/DBAL/Types/BinaryTest.php new file mode 100644 index 0000000..783d4ec --- /dev/null +++ b/tests/Functional/DBAL/Types/BinaryTest.php @@ -0,0 +1,95 @@ +connection->getDriver() instanceof PDOOracleDriver) { + $this->markTestSkipped('PDO_OCI doesn\'t support binding binary values'); + } + + $table = new Table('binary_table'); + $table->addColumn('id', 'binary', [ + 'length' => 16, + 'fixed' => true, + ]); + $table->addColumn('val', 'binary', ['length' => 64]); + $table->setPrimaryKey(['id']); + + $sm = $this->connection->getSchemaManager(); + $sm->dropAndCreateTable($table); + } + + public function testInsertAndSelect() : void + { + $id1 = random_bytes(16); + $id2 = random_bytes(16); + + $value1 = random_bytes(64); + $value2 = random_bytes(64); + + /** @see https://bugs.php.net/bug.php?id=76322 */ + if ($this->connection->getDriver() instanceof DB2Driver) { + $value1 = str_replace("\x00", "\xFF", $value1); + $value2 = str_replace("\x00", "\xFF", $value2); + } + + $this->insert($id1, $value1); + $this->insert($id2, $value2); + + $this->assertSame($value1, $this->select($id1)); + $this->assertSame($value2, $this->select($id2)); + } + + private function insert(string $id, string $value) : void + { + $result = $this->connection->insert('binary_table', [ + 'id' => $id, + 'val' => $value, + ], [ + ParameterType::BINARY, + ParameterType::BINARY, + ]); + + self::assertSame(1, $result); + } + + /** + * @return mixed + */ + private function select(string $id) + { + $value = $this->connection->fetchColumn( + 'SELECT val FROM binary_table WHERE id = ?', + [$id], + 0, + [ParameterType::BINARY] + ); + + // Currently, `BinaryType` mistakenly converts string values fetched from the DB to a stream. + // It should be the opposite. Streams should be used to represent large objects, not binary + // strings. The confusion comes from the PostgreSQL's type system where binary strings and + // large objects are represented by the same BYTEA type + if (is_resource($value)) { + $value = stream_get_contents($value); + } + + return $value; + } +} diff --git a/tests/Functional/DBAL/WriteTest.php b/tests/Functional/DBAL/WriteTest.php new file mode 100644 index 0000000..6589a72 --- /dev/null +++ b/tests/Functional/DBAL/WriteTest.php @@ -0,0 +1,359 @@ +addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('test_int', 'integer'); + $table->addColumn('test_string', 'string', ['notnull' => false]); + $table->setPrimaryKey(['id']); + + $this->connection->getSchemaManager()->createTable($table); + } catch (Throwable $e) { + } + $this->connection->executeUpdate('DELETE FROM write_table'); + } + + /** + * @group DBAL-80 + */ + public function testExecuteUpdateFirstTypeIsNull() : void + { + $sql = 'INSERT INTO write_table (test_string, test_int) VALUES (?, ?)'; + $this->connection->executeUpdate($sql, ['text', 1111], [null, ParameterType::INTEGER]); + + $sql = 'SELECT * FROM write_table WHERE test_string = ? AND test_int = ?'; + self::assertTrue((bool) $this->connection->fetchColumn($sql, ['text', 1111])); + } + + public function testExecuteUpdate() : void + { + $sql = 'INSERT INTO write_table (test_int) VALUES ( ' . $this->connection->quote(1) . ')'; + $affected = $this->connection->executeUpdate($sql); + + self::assertEquals(1, $affected, 'executeUpdate() should return the number of affected rows!'); + } + + public function testExecuteUpdateWithTypes() : void + { + $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; + $affected = $this->connection->executeUpdate( + $sql, + [1, 'foo'], + [ParameterType::INTEGER, ParameterType::STRING] + ); + + self::assertEquals(1, $affected, 'executeUpdate() should return the number of affected rows!'); + } + + public function testPrepareRowCountReturnsAffectedRows() : void + { + $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; + $stmt = $this->connection->prepare($sql); + + $stmt->bindValue(1, 1); + $stmt->bindValue(2, 'foo'); + $stmt->execute(); + + self::assertEquals(1, $stmt->rowCount()); + } + + public function testPrepareWithPdoTypes() : void + { + $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; + $stmt = $this->connection->prepare($sql); + + $stmt->bindValue(1, 1, ParameterType::INTEGER); + $stmt->bindValue(2, 'foo', ParameterType::STRING); + $stmt->execute(); + + self::assertEquals(1, $stmt->rowCount()); + } + + public function testPrepareWithDbalTypes() : void + { + $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; + $stmt = $this->connection->prepare($sql); + + $stmt->bindValue(1, 1, Type::getType('integer')); + $stmt->bindValue(2, 'foo', Type::getType('string')); + $stmt->execute(); + + self::assertEquals(1, $stmt->rowCount()); + } + + public function testPrepareWithDbalTypeNames() : void + { + $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; + $stmt = $this->connection->prepare($sql); + + $stmt->bindValue(1, 1, 'integer'); + $stmt->bindValue(2, 'foo', 'string'); + $stmt->execute(); + + self::assertEquals(1, $stmt->rowCount()); + } + + public function insertRows() : void + { + self::assertEquals(1, $this->connection->insert('write_table', ['test_int' => 1, 'test_string' => 'foo'])); + self::assertEquals(1, $this->connection->insert('write_table', ['test_int' => 2, 'test_string' => 'bar'])); + } + + public function testInsert() : void + { + $this->insertRows(); + } + + public function testDelete() : void + { + $this->insertRows(); + + self::assertEquals(1, $this->connection->delete('write_table', ['test_int' => 2])); + self::assertCount(1, $this->connection->fetchAll('SELECT * FROM write_table')); + + self::assertEquals(1, $this->connection->delete('write_table', ['test_int' => 1])); + self::assertCount(0, $this->connection->fetchAll('SELECT * FROM write_table')); + } + + public function testUpdate() : void + { + $this->insertRows(); + + self::assertEquals(1, $this->connection->update('write_table', ['test_string' => 'bar'], ['test_string' => 'foo'])); + self::assertEquals(2, $this->connection->update('write_table', ['test_string' => 'baz'], ['test_string' => 'bar'])); + self::assertEquals(0, $this->connection->update('write_table', ['test_string' => 'baz'], ['test_string' => 'bar'])); + } + + public function testLastInsertId() : void + { + if (! $this->connection->getDatabasePlatform()->prefersIdentityColumns()) { + $this->markTestSkipped('Test only works on platforms with identity columns.'); + } + + self::assertEquals(1, $this->connection->insert('write_table', ['test_int' => 2, 'test_string' => 'bar'])); + $num = $this->lastInsertId(); + + self::assertNotNull($num, 'LastInsertId() should not be null.'); + self::assertGreaterThan(0, $num, 'LastInsertId() should be non-negative number.'); + } + + public function testLastInsertIdSequence() : void + { + if (! $this->connection->getDatabasePlatform()->supportsSequences()) { + $this->markTestSkipped('Test only works on platforms with sequences.'); + } + + $sequence = new Sequence('write_table_id_seq'); + try { + $this->connection->getSchemaManager()->createSequence($sequence); + } catch (Throwable $e) { + } + + $sequences = $this->connection->getSchemaManager()->listSequences(); + self::assertCount(1, array_filter($sequences, static function ($sequence) { + return strtolower($sequence->getName()) === 'write_table_id_seq'; + })); + + $stmt = $this->connection->query($this->connection->getDatabasePlatform()->getSequenceNextValSQL('write_table_id_seq')); + $nextSequenceVal = $stmt->fetchColumn(); + + $lastInsertId = $this->lastInsertId('write_table_id_seq'); + + self::assertGreaterThan(0, $lastInsertId); + self::assertEquals($nextSequenceVal, $lastInsertId); + } + + public function testLastInsertIdNoSequenceGiven() : void + { + if (! $this->connection->getDatabasePlatform()->supportsSequences() || $this->connection->getDatabasePlatform()->supportsIdentityColumns()) { + $this->markTestSkipped("Test only works consistently on platforms that support sequences and don't support identity columns."); + } + + self::assertFalse($this->lastInsertId()); + } + + /** + * @group DBAL-445 + */ + public function testInsertWithKeyValueTypes() : void + { + $testString = new DateTime('2013-04-14 10:10:10'); + + $this->connection->insert( + 'write_table', + ['test_int' => '30', 'test_string' => $testString], + ['test_string' => 'datetime', 'test_int' => 'integer'] + ); + + $data = $this->connection->fetchColumn('SELECT test_string FROM write_table WHERE test_int = 30'); + + self::assertEquals($testString->format($this->connection->getDatabasePlatform()->getDateTimeFormatString()), $data); + } + + /** + * @group DBAL-445 + */ + public function testUpdateWithKeyValueTypes() : void + { + $testString = new DateTime('2013-04-14 10:10:10'); + + $this->connection->insert( + 'write_table', + ['test_int' => '30', 'test_string' => $testString], + ['test_string' => 'datetime', 'test_int' => 'integer'] + ); + + $testString = new DateTime('2013-04-15 10:10:10'); + + $this->connection->update( + 'write_table', + ['test_string' => $testString], + ['test_int' => '30'], + ['test_string' => 'datetime', 'test_int' => 'integer'] + ); + + $data = $this->connection->fetchColumn('SELECT test_string FROM write_table WHERE test_int = 30'); + + self::assertEquals($testString->format($this->connection->getDatabasePlatform()->getDateTimeFormatString()), $data); + } + + /** + * @group DBAL-445 + */ + public function testDeleteWithKeyValueTypes() : void + { + $val = new DateTime('2013-04-14 10:10:10'); + $this->connection->insert( + 'write_table', + ['test_int' => '30', 'test_string' => $val], + ['test_string' => 'datetime', 'test_int' => 'integer'] + ); + + $this->connection->delete('write_table', ['test_int' => 30, 'test_string' => $val], ['test_string' => 'datetime', 'test_int' => 'integer']); + + $data = $this->connection->fetchColumn('SELECT test_string FROM write_table WHERE test_int = 30'); + + self::assertFalse($data); + } + + public function testEmptyIdentityInsert() : void + { + $platform = $this->connection->getDatabasePlatform(); + + if (! ($platform->supportsIdentityColumns() || $platform->usesSequenceEmulatedIdentityColumns())) { + $this->markTestSkipped( + 'Test only works on platforms with identity columns or sequence emulated identity columns.' + ); + } + + $table = new Table('test_empty_identity'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['id']); + + try { + $this->connection->getSchemaManager()->dropTable($table->getQuotedName($platform)); + } catch (Throwable $e) { + } + + foreach ($platform->getCreateTableSQL($table) as $sql) { + $this->connection->exec($sql); + } + + $seqName = $platform->usesSequenceEmulatedIdentityColumns() + ? $platform->getIdentitySequenceName('test_empty_identity', 'id') + : null; + + $sql = $platform->getEmptyIdentityInsertSQL('test_empty_identity', 'id'); + + $this->connection->exec($sql); + + $firstId = $this->lastInsertId($seqName); + + $this->connection->exec($sql); + + $secondId = $this->lastInsertId($seqName); + + self::assertGreaterThan($firstId, $secondId); + } + + /** + * @group DBAL-2688 + */ + public function testUpdateWhereIsNull() : void + { + $this->connection->insert( + 'write_table', + ['test_int' => '30', 'test_string' => null], + ['test_string' => 'string', 'test_int' => 'integer'] + ); + + $data = $this->connection->fetchAll('SELECT * FROM write_table WHERE test_int = 30'); + + self::assertCount(1, $data); + + $this->connection->update('write_table', ['test_int' => 10], ['test_string' => null], ['test_string' => 'string', 'test_int' => 'integer']); + + $data = $this->connection->fetchAll('SELECT * FROM write_table WHERE test_int = 30'); + + self::assertCount(0, $data); + } + + public function testDeleteWhereIsNull() : void + { + $this->connection->insert( + 'write_table', + ['test_int' => '30', 'test_string' => null], + ['test_string' => 'string', 'test_int' => 'integer'] + ); + + $data = $this->connection->fetchAll('SELECT * FROM write_table WHERE test_int = 30'); + + self::assertCount(1, $data); + + $this->connection->delete('write_table', ['test_string' => null], ['test_string' => 'string']); + + $data = $this->connection->fetchAll('SELECT * FROM write_table WHERE test_int = 30'); + + self::assertCount(0, $data); + } + + /** + * Returns the ID of the last inserted row or skips the test if the currently used driver + * doesn't support this feature + * + * @return string|false + * + * @throws DriverException + */ + private function lastInsertId(?string $name = null) + { + try { + return $this->connection->lastInsertId($name); + } catch (DriverException $e) { + if ($e->getCode() === 'IM001') { + $this->markTestSkipped($e->getMessage()); + } + + throw $e; + } + } +} diff --git a/tests/Functional/DbalFunctionalTestCase.php b/tests/Functional/DbalFunctionalTestCase.php new file mode 100644 index 0000000..e81c82c --- /dev/null +++ b/tests/Functional/DbalFunctionalTestCase.php @@ -0,0 +1,113 @@ +close(); + self::$sharedConnection = null; + } + + protected function setUp() : void + { + parent::setUp(); + + if (! isset(self::$sharedConnection)) { + self::$sharedConnection = TestUtil::getConnection(); + } + $this->connection = self::$sharedConnection; + + $this->sqlLoggerStack = new DebugStack(); + $this->connection->getConfiguration()->setSQLLogger($this->sqlLoggerStack); + } + + protected function tearDown() : void + { + while ($this->connection->isTransactionActive()) { + $this->connection->rollBack(); + } + } + + protected function onNotSuccessfulTest(Throwable $t) : void + { + if ($t instanceof AssertionFailedError) { + throw $t; + } + + if (isset($this->sqlLoggerStack->queries) && count($this->sqlLoggerStack->queries)) { + $queries = ''; + $i = count($this->sqlLoggerStack->queries); + foreach (array_reverse($this->sqlLoggerStack->queries) as $query) { + $params = array_map(static function ($p) { + if (is_object($p)) { + return get_class($p); + } + + if (is_scalar($p)) { + return "'" . $p . "'"; + } + + return var_export($p, true); + }, $query['params'] ?: []); + $queries .= $i . ". SQL: '" . $query['sql'] . "' Params: " . implode(', ', $params) . PHP_EOL; + $i--; + } + + $trace = $t->getTrace(); + $traceMsg = ''; + foreach ($trace as $part) { + if (! isset($part['file'])) { + continue; + } + + if (strpos($part['file'], 'PHPUnit/') !== false) { + // Beginning with PHPUnit files we don't print the trace anymore. + break; + } + + $traceMsg .= $part['file'] . ':' . $part['line'] . PHP_EOL; + } + + $message = '[' . get_class($t) . '] ' . $t->getMessage() . PHP_EOL . PHP_EOL . 'With queries:' . PHP_EOL . $queries . PHP_EOL . 'Trace:' . PHP_EOL . $traceMsg; + + throw new Exception($message, (int) $t->getCode(), $t); + } + throw $t; + } +} diff --git a/tests/Functional/FunctionalTestCase.php b/tests/Functional/FunctionalTestCase.php deleted file mode 100644 index af522b6..0000000 --- a/tests/Functional/FunctionalTestCase.php +++ /dev/null @@ -1,31 +0,0 @@ -set('database.default', 'testing'); - $app['config']->set('database.connections.testing', [ - 'driver' => 'pgsql', - 'host' => env('TEST_DB_HOST', 'localhost'), - 'port' => env('TEST_DB_PORT', 5432), - 'database' => env('TEST_DB', 'testing'), - 'username' => env('TEST_DB_USER', 'postgres'), - 'password' => env('TEST_DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'schema' => 'public', - ]); - } -} diff --git a/tests/Functional/Schema/ChangeTest.php b/tests/Functional/Schema/ChangeTest.php new file mode 100644 index 0000000..848a714 --- /dev/null +++ b/tests/Functional/Schema/ChangeTest.php @@ -0,0 +1,76 @@ +integer('id')->default(1); +// $table->string('name')->nullable()->default(new Expression('NULL')); +// $table->binary('binaries'); +// $table->string('email')->unique(); +// $table->tinyInteger('phone')->unsigned(); +// $table->boolean('enabled')->default(0)->comment('Enabled'); +// }); +// +// $this->assertTrue(Schema::hasTable('test_table')); +// $this->assertSame( +// ['id', 'name', 'binaries', 'email', 'phone', 'enabled'], +// Schema::getColumnListing('test_table') +// ); +// +// Schema::table('test_table', function (Blueprint $table) { +// $table->integer('id')->primary()->change(); +// $table->string('email')->nullable()->default(new Expression('NULL'))->change(); +// $table->string('binaries')->nullable()->change(); +// $table->string('test1'); +// $table->string('test2')->comment('test_comment'); +// $table->string('dump1')->nullable(); +// $table->dropColumn(['dump1']); +// }); +// +// $this->assertSame( +// ['id', 'name', 'binaries', 'email', 'phone', 'enabled', 'test1', 'test2'], +// Schema::getColumnListing('test_table') +// ); +// +// Schema::table('test_table', function (Blueprint $table) { +// $table->integer('id')->autoIncrement()->change(); +// $table->string('email')->nullable()->default('player@example.om')->change(); +// $table->renameColumn('binaries', 'code'); +// $table->dropColumn(['phone', 'enabled']); +// $table->binary('test1')->nullable()->using('test1::bytea')->change(); +// $table->string('test2')->comment('new_comment')->change(); +// }); +// +// $this->assertSame(['id', 'name', 'code', 'email', 'test1', 'test2'], Schema::getColumnListing('test_table')); +// +// Schema::table('test_table', function (Blueprint $table) { +// $table->dropPrimary(['id']); +// $table->string('name', 100)->change(); +// $table->integer('id')->change(); +// $table->rename('some_table'); +// }); +// +// $this->assertTrue(Schema::hasTable('some_table')); +// +// Schema::table('some_table', function (Blueprint $table) { +// $table->drop(); +// }); +// +// $this->assertFalse(Schema::hasTable('some_table')); +// } +} diff --git a/tests/Functional/HasIndexTest.php b/tests/Functional/Schema/HasIndexTest.php similarity index 85% rename from tests/Functional/HasIndexTest.php rename to tests/Functional/Schema/HasIndexTest.php index 811ef37..4e4851d 100644 --- a/tests/Functional/HasIndexTest.php +++ b/tests/Functional/Schema/HasIndexTest.php @@ -2,14 +2,18 @@ declare(strict_types=1); -namespace Umbrellio\Postgres\Tests\Functional; +namespace Umbrellio\Postgres\Tests\Functional\Schema; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; +use Umbrellio\Postgres\Tests\FunctionalTestCase; class HasIndexTest extends FunctionalTestCase { + use RefreshDatabase; + /** @test */ public function createIndexIfNotExists(): void { diff --git a/tests/Functional/SchemaTest.php b/tests/Functional/Schema/SchemaTest.php similarity index 90% rename from tests/Functional/SchemaTest.php rename to tests/Functional/Schema/SchemaTest.php index 3e9f825..334e1de 100644 --- a/tests/Functional/SchemaTest.php +++ b/tests/Functional/Schema/SchemaTest.php @@ -2,13 +2,17 @@ declare(strict_types=1); -namespace Umbrellio\Postgres\Tests\Functional; +namespace Umbrellio\Postgres\Tests\Functional\Schema; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; +use Umbrellio\Postgres\Tests\FunctionalTestCase; class SchemaTest extends FunctionalTestCase { + use RefreshDatabase; + /** @test */ public function create(): void { diff --git a/tests/Functional/UniqueIndexTest.php b/tests/Functional/Schema/UniqueIndexTest.php similarity index 96% rename from tests/Functional/UniqueIndexTest.php rename to tests/Functional/Schema/UniqueIndexTest.php index 8de9265..b70220a 100644 --- a/tests/Functional/UniqueIndexTest.php +++ b/tests/Functional/Schema/UniqueIndexTest.php @@ -2,15 +2,19 @@ declare(strict_types=1); -namespace Umbrellio\Postgres\Tests\Functional; +namespace Umbrellio\Postgres\Tests\Functional\Schema; use Generator; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; +use Umbrellio\Postgres\Tests\FunctionalTestCase; class UniqueIndexTest extends FunctionalTestCase { + use RefreshDatabase; + /** * @test * @dataProvider provideIndexes diff --git a/tests/Functional/TestUtil.php b/tests/Functional/TestUtil.php new file mode 100644 index 0000000..a921ef2 --- /dev/null +++ b/tests/Functional/TestUtil.php @@ -0,0 +1,181 @@ +real database connection using the following parameters + * of the $GLOBALS array: + * + * 'db_type' : The name of the Doctrine DBAL database driver to use. + * 'db_username' : The username to use for connecting. + * 'db_password' : The password to use for connecting. + * 'db_host' : The hostname of the database to connect to. + * 'db_server' : The server name of the database to connect to + * (optional, some vendors allow multiple server instances with different names on the same host). + * 'db_name' : The name of the database to connect to. + * 'db_port' : The port of the database to connect to. + * + * Usually these variables of the $GLOBALS array are filled by PHPUnit based + * on an XML configuration file. If no such parameters exist, an SQLite + * in-memory database is used. + * + * IMPORTANT: Each invocation of this method returns a NEW database connection. + * + * @return Connection The database connection instance. + */ + public static function getConnection() : Connection + { + if (self::hasRequiredConnectionParams() && ! self::$initialized) { + self::initializeDatabase(); + self::$initialized = true; + } + + $conn = static::getDoctrineConnection(); + + self::addDbEventSubscribers($conn); + + return $conn; + } + + private static function hasRequiredConnectionParams() : bool + { + return isset( + $GLOBALS['db_type'], + $GLOBALS['db_username'], + $GLOBALS['db_password'], + $GLOBALS['db_host'], + $GLOBALS['db_name'], + $GLOBALS['db_port'] + ) + && isset( + $GLOBALS['tmpdb_type'], + $GLOBALS['tmpdb_username'], + $GLOBALS['tmpdb_password'], + $GLOBALS['tmpdb_host'], + $GLOBALS['tmpdb_port'] + ); + } + + private static function initializeDatabase() : void + { + $realConn = static::createDoctrineConnection(self::getParamsForMainConnection()); + $tmpConn = static::createDoctrineConnection(self::getParamsForTemporaryConnection()); + + + $platform = $tmpConn->getDatabasePlatform(); + + if ($platform->supportsCreateDropDatabase()) { + $dbname = $realConn->getDatabase(); + $realConn->close(); + + $tmpConn->getSchemaManager()->dropAndCreateDatabase($dbname); + + $tmpConn->close(); + } else { + //UPDATE pg_database SET datallowconn = true WHERE datname = 'my_database'; + $sm = $realConn->getSchemaManager(); + + $schema = $sm->createSchema(); + $stmts = $schema->toDropSql($realConn->getDatabasePlatform()); + + foreach ($stmts as $stmt) { + $realConn->exec($stmt); + } + } + } + + private static function addDbEventSubscribers(Connection $conn) : void + { + if (! isset($GLOBALS['db_event_subscribers'])) { + return; + } + + $evm = $conn->getEventManager(); + foreach (explode(',', $GLOBALS['db_event_subscribers']) as $subscriberClass) { + $subscriberInstance = new $subscriberClass(); + $evm->addEventSubscriber($subscriberInstance); + } + } + + public static function getTempConnection() : Connection + { + return static::getDoctrineConnection('temporary'); + } + + private static function getDoctrineConnection(string $name = null): Connection + { + /** @var PostgresConnection $connection */ + $connection = DB::connection($name); + return $connection->getDoctrineConnection(); + } + + private static function createDoctrineConnection($params): Connection + { + return DriverManager::getConnection($params); + } + + public static function getParamsForMainConnection() : array + { + $connectionParams = [ + 'driver' => $GLOBALS['db_type'], + 'user' => $GLOBALS['db_username'], + 'password' => $GLOBALS['db_password'], + 'host' => $GLOBALS['db_host'], + 'dbname' => $GLOBALS['db_name'], + 'port' => $GLOBALS['db_port'], + ]; + + if (isset($GLOBALS['db_server'])) { + $connectionParams['server'] = $GLOBALS['db_server']; + } + + if (isset($GLOBALS['db_unix_socket'])) { + $connectionParams['unix_socket'] = $GLOBALS['db_unix_socket']; + } + + return $connectionParams; + } + + /** + * @return mixed[] + */ + public static function getParamsForTemporaryConnection() : array + { + $connectionParams = [ + 'driver' => $GLOBALS['tmpdb_type'], + 'user' => $GLOBALS['tmpdb_username'], + 'password' => $GLOBALS['tmpdb_password'], + 'host' => $GLOBALS['tmpdb_host'], + 'dbname' => null, + 'port' => $GLOBALS['tmpdb_port'], + ]; + + if (isset($GLOBALS['tmpdb_name'])) { + $connectionParams['dbname'] = $GLOBALS['tmpdb_name']; + } + + if (isset($GLOBALS['tmpdb_server'])) { + $connectionParams['server'] = $GLOBALS['tmpdb_server']; + } + + if (isset($GLOBALS['tmpdb_unix_socket'])) { + $connectionParams['unix_socket'] = $GLOBALS['tmpdb_unix_socket']; + } + + return $connectionParams; + } +} diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php new file mode 100644 index 0000000..66d74b1 --- /dev/null +++ b/tests/FunctionalTestCase.php @@ -0,0 +1,36 @@ +set('database.default', 'main'); + + $this->setConnectionConfig($app, 'main', TestUtil::getParamsForMainConnection()); + $this->setConnectionConfig($app, 'temporary', TestUtil::getParamsForTemporaryConnection()); + } + + private function setConnectionConfig($app, $name, $params) + { + $app['config']->set('database.connections.' . $name, [ + 'driver' => 'pgsql', + 'host' => $params['host'], + 'port' => (int) $params['port'], + 'database' => $params['dbname'], + 'username' => $params['user'], + 'password' => $params['password'], + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + ]); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 2467719..dece52d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,6 +4,8 @@ namespace Umbrellio\Postgres\Tests; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Facade; use Orchestra\Testbench\TestCase as BaseTestCase; use Umbrellio\Postgres\UmbrellioPostgresProvider; @@ -13,4 +15,16 @@ protected function getPackageProviders($app) { return [UmbrellioPostgresProvider::class]; } + + protected function setUp(): void + { + if (!$this->app) { + putenv('APP_ENV=testing'); + $this->app = $this->createApplication(); + } + + parent::setUp(); + + Facade::clearResolvedInstances(); + } } diff --git a/tests/Unit/DBAL/ConfigurationTest.php b/tests/Unit/DBAL/ConfigurationTest.php new file mode 100644 index 0000000..b037c44 --- /dev/null +++ b/tests/Unit/DBAL/ConfigurationTest.php @@ -0,0 +1,53 @@ +config = new Configuration(); + } + + /** + * Tests that the default auto-commit mode for connections can be retrieved from the configuration container. + * + * @group DBAL-81 + */ + public function testReturnsDefaultConnectionAutoCommitMode() : void + { + self::assertTrue($this->config->getAutoCommit()); + } + + /** + * Tests that the default auto-commit mode for connections can be set in the configuration container. + * + * @group DBAL-81 + */ + public function testSetsDefaultConnectionAutoCommitMode() : void + { + $this->config->setAutoCommit(false); + + self::assertFalse($this->config->getAutoCommit()); + + $this->config->setAutoCommit(0); + + self::assertFalse($this->config->getAutoCommit()); + } +} diff --git a/tests/Unit/DBAL/ConnectionTest.php b/tests/Unit/DBAL/ConnectionTest.php new file mode 100644 index 0000000..d5d56fe --- /dev/null +++ b/tests/Unit/DBAL/ConnectionTest.php @@ -0,0 +1,971 @@ + 'pdo_mysql', + 'host' => 'localhost', + 'user' => 'root', + 'password' => 'password', + 'port' => '1234', + ]; + + protected function setUp() : void + { + $this->connection = DriverManager::getConnection($this->params); + } + + /** + * @return Connection|MockObject + */ + private function getExecuteUpdateMockConnection() + { + $driverMock = $this->createMock(Driver::class); + + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + + $platform = $this->getMockForAbstractClass(AbstractPlatform::class); + + return $this->getMockBuilder(Connection::class) + ->setMethods(['executeUpdate']) + ->setConstructorArgs([['platform' => $platform], $driverMock]) + ->getMock(); + } + + public function testIsConnected() : void + { + self::assertFalse($this->connection->isConnected()); + } + + public function testNoTransactionActiveByDefault() : void + { + self::assertFalse($this->connection->isTransactionActive()); + } + + public function testCommitWithNoActiveTransactionThrowsException() : void + { + $this->expectException(ConnectionException::class); + $this->connection->commit(); + } + + public function testRollbackWithNoActiveTransactionThrowsException() : void + { + $this->expectException(ConnectionException::class); + $this->connection->rollBack(); + } + + public function testSetRollbackOnlyNoActiveTransactionThrowsException() : void + { + $this->expectException(ConnectionException::class); + $this->connection->setRollbackOnly(); + } + + public function testIsRollbackOnlyNoActiveTransactionThrowsException() : void + { + $this->expectException(ConnectionException::class); + $this->connection->isRollbackOnly(); + } + + public function testGetConfiguration() : void + { + $config = $this->connection->getConfiguration(); + + self::assertInstanceOf(Configuration::class, $config); + } + + public function testGetHost() : void + { + self::assertEquals('localhost', $this->connection->getHost()); + } + + public function testGetPort() : void + { + self::assertEquals('1234', $this->connection->getPort()); + } + + public function testGetUsername() : void + { + self::assertEquals('root', $this->connection->getUsername()); + } + + public function testGetPassword() : void + { + self::assertEquals('password', $this->connection->getPassword()); + } + + public function testGetDriver() : void + { + self::assertInstanceOf(\Doctrine\DBAL\Driver\PDOMySql\Driver::class, $this->connection->getDriver()); + } + + public function testGetEventManager() : void + { + self::assertInstanceOf(EventManager::class, $this->connection->getEventManager()); + } + + public function testConnectDispatchEvent() : void + { + $listenerMock = $this->getMockBuilder('ConnectDispatchEventListener') + ->setMethods(['postConnect']) + ->getMock(); + $listenerMock->expects($this->once())->method('postConnect'); + + $eventManager = new EventManager(); + $eventManager->addEventListener([Events::postConnect], $listenerMock); + + $driverMock = $this->createMock(Driver::class); + $driverMock->expects($this->at(0)) + ->method('connect'); + + $conn = new Connection([], $driverMock, new Configuration(), $eventManager); + $conn->connect(); + } + + public function testEventManagerPassedToPlatform() : void + { + $eventManager = new EventManager(); + + /** @var AbstractPlatform|MockObject $driver */ + $platform = $this->createMock(AbstractPlatform::class); + $platform->expects($this->once()) + ->method('setEventManager') + ->with($eventManager); + + /** @var Driver|MockObject $driver */ + $driver = $this->createMock(Driver::class); + $driver->expects($this->any()) + ->method('getDatabasePlatform') + ->willReturn($platform); + + $connection = new Connection($this->params, $driver, null, $eventManager); + $connection->getDatabasePlatform(); + } + + /** + * @requires extension pdo_sqlite + * @dataProvider getQueryMethods + */ + public function testDriverExceptionIsWrapped(string $method) : void + { + $this->expectException(DBALException::class); + $this->expectExceptionMessage("An exception occurred while executing 'MUUHAAAAHAAAA':\n\nSQLSTATE[HY000]: General error: 1 near \"MUUHAAAAHAAAA\""); + + $connection = DriverManager::getConnection([ + 'driver' => 'pdo_sqlite', + 'memory' => true, + ]); + + $connection->$method('MUUHAAAAHAAAA'); + } + + /** + * @return array> + */ + public static function getQueryMethods() : iterable + { + return [ + ['exec'], + ['query'], + ['executeQuery'], + ['executeUpdate'], + ['prepare'], + ]; + } + + /** + * Pretty dumb test, however we want to check that the EchoSQLLogger correctly implements the interface. + * + * @group DBAL-11 + */ + public function testEchoSQLLogger() : void + { + $logger = new EchoSQLLogger(); + $this->connection->getConfiguration()->setSQLLogger($logger); + self::assertSame($logger, $this->connection->getConfiguration()->getSQLLogger()); + } + + /** + * Pretty dumb test, however we want to check that the DebugStack correctly implements the interface. + * + * @group DBAL-11 + */ + public function testDebugSQLStack() : void + { + $logger = new DebugStack(); + $this->connection->getConfiguration()->setSQLLogger($logger); + self::assertSame($logger, $this->connection->getConfiguration()->getSQLLogger()); + } + + /** + * @group DBAL-81 + */ + public function testIsAutoCommit() : void + { + self::assertTrue($this->connection->isAutoCommit()); + } + + /** + * @group DBAL-81 + */ + public function testSetAutoCommit() : void + { + $this->connection->setAutoCommit(false); + self::assertFalse($this->connection->isAutoCommit()); + $this->connection->setAutoCommit(0); + self::assertFalse($this->connection->isAutoCommit()); + } + + /** + * @group DBAL-81 + */ + public function testConnectStartsTransactionInNoAutoCommitMode() : void + { + $driverMock = $this->createMock(Driver::class); + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + $conn = new Connection([], $driverMock); + + $conn->setAutoCommit(false); + + self::assertFalse($conn->isTransactionActive()); + + $conn->connect(); + + self::assertTrue($conn->isTransactionActive()); + } + + /** + * @group DBAL-81 + */ + public function testCommitStartsTransactionInNoAutoCommitMode() : void + { + $driverMock = $this->createMock(Driver::class); + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + $conn = new Connection([], $driverMock); + + $conn->setAutoCommit(false); + $conn->connect(); + $conn->commit(); + + self::assertTrue($conn->isTransactionActive()); + } + + /** + * @dataProvider resultProvider + */ + public function testCommitReturn(bool $expectedResult) : void + { + $driverConnection = $this->createMock(DriverConnection::class); + $driverConnection->expects($this->once()) + ->method('commit')->willReturn($expectedResult); + + $driverMock = $this->createMock(Driver::class); + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue($driverConnection)); + + $conn = new Connection([], $driverMock); + + $conn->connect(); + $conn->beginTransaction(); + + self::assertSame($expectedResult, $conn->commit()); + } + + /** + * @return bool[][] + */ + public function resultProvider() : array + { + return [[true], [false]]; + } + + /** + * @group DBAL-81 + */ + public function testRollBackStartsTransactionInNoAutoCommitMode() : void + { + $driverMock = $this->createMock(Driver::class); + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + $conn = new Connection([], $driverMock); + + $conn->setAutoCommit(false); + $conn->connect(); + $conn->rollBack(); + + self::assertTrue($conn->isTransactionActive()); + } + + /** + * @group DBAL-81 + */ + public function testSwitchingAutoCommitModeCommitsAllCurrentTransactions() : void + { + $driverMock = $this->createMock(Driver::class); + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + $conn = new Connection([], $driverMock); + + $conn->connect(); + $conn->beginTransaction(); + $conn->beginTransaction(); + $conn->setAutoCommit(false); + + self::assertSame(1, $conn->getTransactionNestingLevel()); + + $conn->beginTransaction(); + $conn->beginTransaction(); + $conn->setAutoCommit(true); + + self::assertFalse($conn->isTransactionActive()); + } + + public function testEmptyInsert() : void + { + $conn = $this->getExecuteUpdateMockConnection(); + + $conn->expects($this->once()) + ->method('executeUpdate') + ->with('INSERT INTO footable () VALUES ()'); + + $conn->insert('footable', []); + } + + /** + * @group DBAL-2511 + */ + public function testUpdateWithDifferentColumnsInDataAndIdentifiers() : void + { + $conn = $this->getExecuteUpdateMockConnection(); + + $conn->expects($this->once()) + ->method('executeUpdate') + ->with( + 'UPDATE TestTable SET text = ?, is_edited = ? WHERE id = ? AND name = ?', + [ + 'some text', + true, + 1, + 'foo', + ], + [ + 'string', + 'boolean', + 'integer', + 'string', + ] + ); + + $conn->update( + 'TestTable', + [ + 'text' => 'some text', + 'is_edited' => true, + ], + [ + 'id' => 1, + 'name' => 'foo', + ], + [ + 'text' => 'string', + 'is_edited' => 'boolean', + 'id' => 'integer', + 'name' => 'string', + ] + ); + } + + /** + * @group DBAL-2511 + */ + public function testUpdateWithSameColumnInDataAndIdentifiers() : void + { + $conn = $this->getExecuteUpdateMockConnection(); + + $conn->expects($this->once()) + ->method('executeUpdate') + ->with( + 'UPDATE TestTable SET text = ?, is_edited = ? WHERE id = ? AND is_edited = ?', + [ + 'some text', + true, + 1, + false, + ], + [ + 'string', + 'boolean', + 'integer', + 'boolean', + ] + ); + + $conn->update( + 'TestTable', + [ + 'text' => 'some text', + 'is_edited' => true, + ], + [ + 'id' => 1, + 'is_edited' => false, + ], + [ + 'text' => 'string', + 'is_edited' => 'boolean', + 'id' => 'integer', + ] + ); + } + + /** + * @group DBAL-2688 + */ + public function testUpdateWithIsNull() : void + { + $conn = $this->getExecuteUpdateMockConnection(); + + $conn->expects($this->once()) + ->method('executeUpdate') + ->with( + 'UPDATE TestTable SET text = ?, is_edited = ? WHERE id IS NULL AND name = ?', + [ + 'some text', + null, + 'foo', + ], + [ + 'string', + 'boolean', + 'string', + ] + ); + + $conn->update( + 'TestTable', + [ + 'text' => 'some text', + 'is_edited' => null, + ], + [ + 'id' => null, + 'name' => 'foo', + ], + [ + 'text' => 'string', + 'is_edited' => 'boolean', + 'id' => 'integer', + 'name' => 'string', + ] + ); + } + + /** + * @group DBAL-2688 + */ + public function testDeleteWithIsNull() : void + { + $conn = $this->getExecuteUpdateMockConnection(); + + $conn->expects($this->once()) + ->method('executeUpdate') + ->with( + 'DELETE FROM TestTable WHERE id IS NULL AND name = ?', + ['foo'], + ['string'] + ); + + $conn->delete( + 'TestTable', + [ + 'id' => null, + 'name' => 'foo', + ], + [ + 'id' => 'integer', + 'name' => 'string', + ] + ); + } + + public function testFetchAssoc() : void + { + $statement = 'SELECT * FROM foo WHERE bar = ?'; + $params = [666]; + $types = [ParameterType::INTEGER]; + $result = []; + + $driverMock = $this->createMock(Driver::class); + + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + + $driverStatementMock = $this->createMock(Statement::class); + + $driverStatementMock->expects($this->once()) + ->method('fetch') + ->with(FetchMode::ASSOCIATIVE) + ->will($this->returnValue($result)); + + /** @var Connection|MockObject $conn */ + $conn = $this->getMockBuilder(Connection::class) + ->setMethods(['executeQuery']) + ->setConstructorArgs([[], $driverMock]) + ->getMock(); + + $conn->expects($this->once()) + ->method('executeQuery') + ->with($statement, $params, $types) + ->will($this->returnValue($driverStatementMock)); + + self::assertSame($result, $conn->fetchAssoc($statement, $params, $types)); + } + + public function testFetchArray() : void + { + $statement = 'SELECT * FROM foo WHERE bar = ?'; + $params = [666]; + $types = [ParameterType::INTEGER]; + $result = []; + + $driverMock = $this->createMock(Driver::class); + + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + + $driverStatementMock = $this->createMock(Statement::class); + + $driverStatementMock->expects($this->once()) + ->method('fetch') + ->with(FetchMode::NUMERIC) + ->will($this->returnValue($result)); + + /** @var Connection|MockObject $conn */ + $conn = $this->getMockBuilder(Connection::class) + ->setMethods(['executeQuery']) + ->setConstructorArgs([[], $driverMock]) + ->getMock(); + + $conn->expects($this->once()) + ->method('executeQuery') + ->with($statement, $params, $types) + ->will($this->returnValue($driverStatementMock)); + + self::assertSame($result, $conn->fetchArray($statement, $params, $types)); + } + + public function testFetchColumn() : void + { + $statement = 'SELECT * FROM foo WHERE bar = ?'; + $params = [666]; + $types = [ParameterType::INTEGER]; + $column = 0; + $result = []; + + $driverMock = $this->createMock(Driver::class); + + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + + $driverStatementMock = $this->createMock(Statement::class); + + $driverStatementMock->expects($this->once()) + ->method('fetchColumn') + ->with($column) + ->will($this->returnValue($result)); + + /** @var Connection|MockObject $conn */ + $conn = $this->getMockBuilder(Connection::class) + ->setMethods(['executeQuery']) + ->setConstructorArgs([[], $driverMock]) + ->getMock(); + + $conn->expects($this->once()) + ->method('executeQuery') + ->with($statement, $params, $types) + ->will($this->returnValue($driverStatementMock)); + + self::assertSame($result, $conn->fetchColumn($statement, $params, $column, $types)); + } + + public function testFetchAll() : void + { + $statement = 'SELECT * FROM foo WHERE bar = ?'; + $params = [666]; + $types = [ParameterType::INTEGER]; + $result = []; + + $driverMock = $this->createMock(Driver::class); + + $driverMock->expects($this->any()) + ->method('connect') + ->will($this->returnValue( + $this->createMock(DriverConnection::class) + )); + + $driverStatementMock = $this->createMock(Statement::class); + + $driverStatementMock->expects($this->once()) + ->method('fetchAll') + ->will($this->returnValue($result)); + + /** @var Connection|MockObject $conn */ + $conn = $this->getMockBuilder(Connection::class) + ->setMethods(['executeQuery']) + ->setConstructorArgs([[], $driverMock]) + ->getMock(); + + $conn->expects($this->once()) + ->method('executeQuery') + ->with($statement, $params, $types) + ->will($this->returnValue($driverStatementMock)); + + self::assertSame($result, $conn->fetchAll($statement, $params, $types)); + } + + public function testConnectionDoesNotMaintainTwoReferencesToExternalPDO() : void + { + $params['pdo'] = new stdClass(); + + $driverMock = $this->createMock(Driver::class); + + $conn = new Connection($params, $driverMock); + + self::assertArrayNotHasKey('pdo', $conn->getParams(), 'Connection is maintaining additional reference to the PDO connection'); + } + + public function testPassingExternalPDOMeansConnectionIsConnected() : void + { + $params['pdo'] = new stdClass(); + + $driverMock = $this->createMock(Driver::class); + + $conn = new Connection($params, $driverMock); + + self::assertTrue($conn->isConnected(), 'Connection is not connected after passing external PDO'); + } + + public function testCallingDeleteWithNoDeletionCriteriaResultsInInvalidArgumentException() : void + { + /** @var Driver $driver */ + $driver = $this->createMock(Driver::class); + $pdoMock = $this->createMock(\Doctrine\DBAL\Driver\Connection::class); + + // should never execute queries with invalid arguments + $pdoMock->expects($this->never())->method('exec'); + $pdoMock->expects($this->never())->method('prepare'); + + $conn = new Connection(['pdo' => $pdoMock], $driver); + + $this->expectException(InvalidArgumentException::class); + $conn->delete('kittens', []); + } + + /** + * @return array> + */ + public static function dataCallConnectOnce() : iterable + { + return [ + ['delete', ['tbl', ['id' => 12345]]], + ['insert', ['tbl', ['data' => 'foo']]], + ['update', ['tbl', ['data' => 'bar'], ['id' => 12345]]], + ['prepare', ['select * from dual']], + ['executeUpdate', ['insert into tbl (id) values (?)'], [123]], + ]; + } + + /** + * @param array $params + * + * @dataProvider dataCallConnectOnce + */ + public function testCallConnectOnce(string $method, array $params) : void + { + $driverMock = $this->createMock(Driver::class); + $pdoMock = $this->createMock(Connection::class); + $platformMock = $this->createMock(AbstractPlatform::class); + $stmtMock = $this->createMock(Statement::class); + + $pdoMock->expects($this->any()) + ->method('prepare') + ->will($this->returnValue($stmtMock)); + + $conn = $this->getMockBuilder(Connection::class) + ->setConstructorArgs([['pdo' => $pdoMock, 'platform' => $platformMock], $driverMock]) + ->setMethods(['connect']) + ->getMock(); + + $conn->expects($this->once())->method('connect'); + + call_user_func_array([$conn, $method], $params); + } + + /** + * @group DBAL-1127 + */ + public function testPlatformDetectionIsTriggerOnlyOnceOnRetrievingPlatform() : void + { + /** @var Driver|VersionAwarePlatformDriver|MockObject $driverMock */ + $driverMock = $this->createMock([Driver::class, VersionAwarePlatformDriver::class]); + + /** @var ServerInfoAwareConnection|MockObject $driverConnectionMock */ + $driverConnectionMock = $this->createMock(ServerInfoAwareConnection::class); + + /** @var AbstractPlatform|MockObject $platformMock */ + $platformMock = $this->getMockForAbstractClass(AbstractPlatform::class); + + $connection = new Connection([], $driverMock); + + $driverMock->expects($this->once()) + ->method('connect') + ->will($this->returnValue($driverConnectionMock)); + + $driverConnectionMock->expects($this->once()) + ->method('requiresQueryForServerVersion') + ->will($this->returnValue(false)); + + $driverConnectionMock->expects($this->once()) + ->method('getServerVersion') + ->will($this->returnValue('6.6.6')); + + $driverMock->expects($this->once()) + ->method('createDatabasePlatformForVersion') + ->with('6.6.6') + ->will($this->returnValue($platformMock)); + + self::assertSame($platformMock, $connection->getDatabasePlatform()); + } + + public function testConnectionParamsArePassedToTheQueryCacheProfileInExecuteCacheQuery() : void + { + $resultCacheDriverMock = $this->createMock(Cache::class); + + $resultCacheDriverMock + ->expects($this->atLeastOnce()) + ->method('fetch') + ->with('cacheKey') + ->will($this->returnValue(['realKey' => []])); + + $query = 'SELECT * FROM foo WHERE bar = ?'; + $params = [666]; + $types = [ParameterType::INTEGER]; + + /** @var QueryCacheProfile|MockObject $queryCacheProfileMock */ + $queryCacheProfileMock = $this->createMock(QueryCacheProfile::class); + + $queryCacheProfileMock + ->expects($this->any()) + ->method('getResultCacheDriver') + ->will($this->returnValue($resultCacheDriverMock)); + + // This is our main expectation + $queryCacheProfileMock + ->expects($this->once()) + ->method('generateCacheKeys') + ->with($query, $params, $types, $this->params) + ->will($this->returnValue(['cacheKey', 'realKey'])); + + /** @var Driver $driver */ + $driver = $this->createMock(Driver::class); + + self::assertInstanceOf( + ArrayStatement::class, + (new Connection($this->params, $driver))->executeCacheQuery($query, $params, $types, $queryCacheProfileMock) + ); + } + + /** + * @group #2821 + */ + public function testShouldNotPassPlatformInParamsToTheQueryCacheProfileInExecuteCacheQuery() : void + { + $resultCacheDriverMock = $this->createMock(Cache::class); + + $resultCacheDriverMock + ->expects($this->atLeastOnce()) + ->method('fetch') + ->with('cacheKey') + ->will($this->returnValue(['realKey' => []])); + + /** @var QueryCacheProfile|MockObject $queryCacheProfileMock */ + $queryCacheProfileMock = $this->createMock(QueryCacheProfile::class); + + $queryCacheProfileMock + ->expects($this->any()) + ->method('getResultCacheDriver') + ->will($this->returnValue($resultCacheDriverMock)); + + $query = 'SELECT 1'; + + $connectionParams = $this->params; + + $queryCacheProfileMock + ->expects($this->once()) + ->method('generateCacheKeys') + ->with($query, [], [], $connectionParams) + ->will($this->returnValue(['cacheKey', 'realKey'])); + + $connectionParams['platform'] = $this->createMock(AbstractPlatform::class); + + /** @var Driver $driver */ + $driver = $this->createMock(Driver::class); + + (new Connection($connectionParams, $driver))->executeCacheQuery($query, [], [], $queryCacheProfileMock); + } + + /** + * @group #2821 + */ + public function testThrowsExceptionWhenInValidPlatformSpecified() : void + { + $connectionParams = $this->params; + $connectionParams['platform'] = new stdClass(); + + /** @var Driver $driver */ + $driver = $this->createMock(Driver::class); + + $this->expectException(DBALException::class); + + new Connection($connectionParams, $driver); + } + + /** + * @group DBAL-990 + */ + public function testRethrowsOriginalExceptionOnDeterminingPlatformWhenConnectingToNonExistentDatabase() : void + { + /** @var Driver|VersionAwarePlatformDriver|MockObject $driverMock */ + $driverMock = $this->createMock([Driver::class, VersionAwarePlatformDriver::class]); + + $connection = new Connection(['dbname' => 'foo'], $driverMock); + $originalException = new Exception('Original exception'); + $fallbackException = new Exception('Fallback exception'); + + $driverMock->expects($this->at(0)) + ->method('connect') + ->willThrowException($originalException); + + $driverMock->expects($this->at(1)) + ->method('connect') + ->willThrowException($fallbackException); + + $this->expectExceptionMessage($originalException->getMessage()); + + $connection->getDatabasePlatform(); + } + + /** + * @group #3194 + */ + public function testExecuteCacheQueryStripsPlatformFromConnectionParamsBeforeGeneratingCacheKeys() : void + { + /** @var Driver|MockObject $driver */ + $driver = $this->createMock(Driver::class); + + /** @var AbstractPlatform|MockObject $platform */ + $platform = $this->createMock(AbstractPlatform::class); + + /** @var QueryCacheProfile|MockObject $queryCacheProfile */ + $queryCacheProfile = $this->createMock(QueryCacheProfile::class); + + /** @var Cache|MockObject $resultCacheDriver */ + $resultCacheDriver = $this->createMock(Cache::class); + + $queryCacheProfile + ->expects($this->any()) + ->method('getResultCacheDriver') + ->will($this->returnValue($resultCacheDriver)); + + $resultCacheDriver + ->expects($this->atLeastOnce()) + ->method('fetch') + ->with('cacheKey') + ->will($this->returnValue(['realKey' => []])); + + $query = 'SELECT 1'; + + $params = [ + 'dbname' => 'foo', + 'platform' => $platform, + ]; + + $paramsWithoutPlatform = $params; + unset($paramsWithoutPlatform['platform']); + + $queryCacheProfile + ->expects($this->once()) + ->method('generateCacheKeys') + ->with($query, [], [], $paramsWithoutPlatform) + ->will($this->returnValue(['cacheKey', 'realKey'])); + + $connection = new Connection($params, $driver); + + self::assertSame($params, $connection->getParams()); + + $connection->executeCacheQuery($query, [], [], $queryCacheProfile); + } +} diff --git a/tests/Unit/DBAL/DBALExceptionTest.php b/tests/Unit/DBAL/DBALExceptionTest.php new file mode 100644 index 0000000..4d60c79 --- /dev/null +++ b/tests/Unit/DBAL/DBALExceptionTest.php @@ -0,0 +1,88 @@ +createMock(Driver::class); + $e = DBALException::driverExceptionDuringQuery($driver, new Exception(), '', ['ABC', chr(128)]); + self::assertStringContainsString('with params ["ABC", "\x80"]', $e->getMessage()); + } + + public function testDriverExceptionDuringQueryAcceptsResource() : void + { + /** @var Driver $driver */ + $driver = $this->createMock(Driver::class); + $e = DBALException::driverExceptionDuringQuery($driver, new Exception(), 'INSERT INTO file (`content`) VALUES (?)', [1 => fopen(__FILE__, 'r')]); + self::assertStringContainsString('Resource', $e->getMessage()); + } + + public function testAvoidOverWrappingOnDriverException() : void + { + /** @var Driver $driver */ + $driver = $this->createMock(Driver::class); + + /** @var InnerDriverException $inner */ + $inner = $this->createMock(InnerDriverException::class); + + $ex = new DriverException('', $inner); + $e = DBALException::driverExceptionDuringQuery($driver, $ex, ''); + self::assertSame($ex, $e); + } + + public function testDriverRequiredWithUrl() : void + { + $url = 'mysql://localhost'; + $exception = DBALException::driverRequired($url); + + self::assertInstanceOf(DBALException::class, $exception); + self::assertSame( + sprintf( + "The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " . + 'is given to DriverManager::getConnection(). Given URL: %s', + $url + ), + $exception->getMessage() + ); + } + + /** + * @group #2821 + */ + public function testInvalidPlatformTypeObject() : void + { + $exception = DBALException::invalidPlatformType(new stdClass()); + + self::assertSame( + "Option 'platform' must be a subtype of 'Doctrine\DBAL\Platforms\AbstractPlatform', instance of 'stdClass' given", + $exception->getMessage() + ); + } + + /** + * @group #2821 + */ + public function testInvalidPlatformTypeScalar() : void + { + $exception = DBALException::invalidPlatformType('some string'); + + self::assertSame( + "Option 'platform' must be an object and subtype of 'Doctrine\DBAL\Platforms\AbstractPlatform'. Got 'string'", + $exception->getMessage() + ); + } +} diff --git a/tests/Unit/DBAL/Driver/AbstractDriverTest.php b/tests/Unit/DBAL/Driver/AbstractDriverTest.php new file mode 100644 index 0000000..14e7d0c --- /dev/null +++ b/tests/Unit/DBAL/Driver/AbstractDriverTest.php @@ -0,0 +1,239 @@ +driver = $this->createDriver(); + } + + /** + * @param int|string $errorCode + * + * @dataProvider exceptionConversionProvider + */ + public function testConvertsException($errorCode, ?string $sqlState, ?string $message, string $expectedClass) : void + { + if (! $this->driver instanceof ExceptionConverterDriver) { + $this->markTestSkipped('This test is only intended for exception converter drivers.'); + } + + /** @var DriverExceptionInterface|MockObject $driverException */ + $driverException = $this->getMockBuilder(DriverExceptionInterface::class) + ->setConstructorArgs([$message]) + ->getMock(); + $driverException->method('getErrorCode') + ->willReturn($errorCode); + $driverException->method('getSQLState') + ->willReturn($sqlState); + + $dbalMessage = 'DBAL exception message'; + $dbalException = $this->driver->convertException($dbalMessage, $driverException); + + self::assertInstanceOf($expectedClass, $dbalException); + + self::assertSame($driverException->getErrorCode(), $dbalException->getErrorCode()); + self::assertSame($driverException->getSQLState(), $dbalException->getSQLState()); + self::assertSame($driverException, $dbalException->getPrevious()); + self::assertSame($dbalMessage, $dbalException->getMessage()); + } + + public function testCreatesDatabasePlatformForVersion() : void + { + if (! $this->driver instanceof VersionAwarePlatformDriver) { + $this->markTestSkipped('This test is only intended for version aware platform drivers.'); + } + + $data = $this->getDatabasePlatformsForVersions(); + + self::assertNotEmpty( + $data, + sprintf( + 'No test data found for test %s. You have to return test data from %s.', + static::class . '::' . __FUNCTION__, + static::class . '::getDatabasePlatformsForVersions' + ) + ); + + foreach ($data as $item) { + $generatedVersion = get_class($this->driver->createDatabasePlatformForVersion($item[0])); + + self::assertSame( + $item[1], + $generatedVersion, + sprintf( + 'Expected platform for version "%s" should be "%s", "%s" given', + $item[0], + $item[1], + $generatedVersion + ) + ); + } + } + + public function testThrowsExceptionOnCreatingDatabasePlatformsForInvalidVersion() : void + { + if (! $this->driver instanceof VersionAwarePlatformDriver) { + $this->markTestSkipped('This test is only intended for version aware platform drivers.'); + } + + $this->expectException(DBALException::class); + $this->driver->createDatabasePlatformForVersion('foo'); + } + + public function testReturnsDatabaseName() : void + { + $params = [ + 'user' => 'foo', + 'password' => 'bar', + 'dbname' => 'baz', + ]; + + $connection = $this->getConnectionMock(); + + $connection->expects($this->once()) + ->method('getParams') + ->will($this->returnValue($params)); + + self::assertSame($params['dbname'], $this->driver->getDatabase($connection)); + } + + public function testReturnsDatabasePlatform() : void + { + self::assertEquals($this->createPlatform(), $this->driver->getDatabasePlatform()); + } + + public function testReturnsSchemaManager() : void + { + $connection = $this->getConnectionMock(); + $schemaManager = $this->driver->getSchemaManager($connection); + + self::assertEquals($this->createSchemaManager($connection), $schemaManager); + + $re = new ReflectionProperty($schemaManager, '_conn'); + $re->setAccessible(true); + + self::assertSame($connection, $re->getValue($schemaManager)); + } + + /** + * Factory method for creating the driver instance under test. + */ + abstract protected function createDriver() : Driver; + + /** + * Factory method for creating the the platform instance return by the driver under test. + * + * The platform instance returned by this method must be the same as returned by + * the driver's getDatabasePlatform() method. + */ + abstract protected function createPlatform() : AbstractPlatform; + + /** + * Factory method for creating the the schema manager instance return by the driver under test. + * + * The schema manager instance returned by this method must be the same as returned by + * the driver's getSchemaManager() method. + * + * @param Connection $connection The underlying connection to use. + */ + abstract protected function createSchemaManager(Connection $connection) : AbstractSchemaManager; + + protected function getConnectionMock() : Connection + { + return $this->getMockBuilder(Connection::class) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return array> + */ + protected function getDatabasePlatformsForVersions() : array + { + return []; + } + + /** + * @return mixed[][] + */ + public static function exceptionConversionProvider() : iterable + { + foreach (static::getExceptionConversionData() as $expectedClass => $items) { + foreach ($items as $item) { + yield array_merge($item, [$expectedClass]); + } + } + + yield ['foo', 'bar', 'baz', self::EXCEPTION_DRIVER]; + } + + /** + * @return array + */ + protected static function getExceptionConversionData() : array + { + return []; + } +} diff --git a/tests/Unit/DBAL/Driver/AbstractPostgreSQLDriverTest.php b/tests/Unit/DBAL/Driver/AbstractPostgreSQLDriverTest.php new file mode 100644 index 0000000..b6c96e4 --- /dev/null +++ b/tests/Unit/DBAL/Driver/AbstractPostgreSQLDriverTest.php @@ -0,0 +1,125 @@ + 'foo', + 'password' => 'bar', + ]; + + $statement = $this->createMock(ResultStatement::class); + + $statement->expects($this->once()) + ->method('fetchColumn') + ->will($this->returnValue($database)); + + $connection = $this->getConnectionMock(); + + $connection->expects($this->once()) + ->method('getParams') + ->will($this->returnValue($params)); + + $connection->expects($this->once()) + ->method('query') + ->will($this->returnValue($statement)); + + self::assertSame($database, $this->driver->getDatabase($connection)); + } + + protected function createDriver() : Driver + { + return $this->getMockForAbstractClass(AbstractPostgreSQLDriver::class); + } + + protected function createPlatform() : AbstractPlatform + { + return new PostgreSqlPlatform(); + } + + protected function createSchemaManager(Connection $connection) : AbstractSchemaManager + { + return new PostgreSqlSchemaManager($connection); + } + + /** + * {@inheritDoc} + */ + protected function getDatabasePlatformsForVersions() : array + { + return [ + ['9.0.9', PostgreSqlPlatform::class], + ['9.1', PostgreSQL91Platform::class], + ['9.1.0', PostgreSQL91Platform::class], + ['9.1.1', PostgreSQL91Platform::class], + ['9.1.9', PostgreSQL91Platform::class], + ['9.2', PostgreSQL92Platform::class], + ['9.2.0', PostgreSQL92Platform::class], + ['9.2.1', PostgreSQL92Platform::class], + ['9.3.6', PostgreSQL92Platform::class], + ['9.4', PostgreSQL94Platform::class], + ['9.4.0', PostgreSQL94Platform::class], + ['9.4.1', PostgreSQL94Platform::class], + ['10', PostgreSQL100Platform::class], + ]; + } + + /** + * {@inheritDoc} + */ + protected static function getExceptionConversionData() : array + { + return [ + self::EXCEPTION_CONNECTION => [ + [null, '7', 'SQLSTATE[08006]'], + ], + self::EXCEPTION_FOREIGN_KEY_CONSTRAINT_VIOLATION => [ + [null, '23503', null], + ], + self::EXCEPTION_INVALID_FIELD_NAME => [ + [null, '42703', null], + ], + self::EXCEPTION_NON_UNIQUE_FIELD_NAME => [ + [null, '42702', null], + ], + self::EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION => [ + [null, '23502', null], + ], + self::EXCEPTION_SYNTAX_ERROR => [ + [null, '42601', null], + ], + self::EXCEPTION_TABLE_EXISTS => [ + [null, '42P07', null], + ], + self::EXCEPTION_TABLE_NOT_FOUND => [ + [null, '42P01', null], + ], + self::EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION => [ + [null, '23505', null], + ], + self::EXCEPTION_DEADLOCK => [ + [null, '40001', null], + [null, '40P01', null], + ], + ]; + } +} diff --git a/tests/Unit/DBAL/Driver/PDOPgSql/DriverTest.php b/tests/Unit/DBAL/Driver/PDOPgSql/DriverTest.php new file mode 100644 index 0000000..cf00d7a --- /dev/null +++ b/tests/Unit/DBAL/Driver/PDOPgSql/DriverTest.php @@ -0,0 +1,120 @@ +driver->getName()); + } + + /** + * @group DBAL-920 + */ + public function testConnectionDisablesPreparesOnPhp56() : void + { + $this->skipWhenNotUsingPhp56AndPdoPgsql(); + + $connection = $this->createDriver()->connect( + [ + 'host' => $GLOBALS['db_host'], + 'port' => $GLOBALS['db_port'], + ], + $GLOBALS['db_username'], + $GLOBALS['db_password'] + ); + + self::assertInstanceOf(PDOConnection::class, $connection); + + try { + self::assertTrue($connection->getAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES)); + } catch (PDOException $ignored) { + /** @link https://bugs.php.net/bug.php?id=68371 */ + $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=68371'); + } + } + + /** + * @group DBAL-920 + */ + public function testConnectionDoesNotDisablePreparesOnPhp56WhenAttributeDefined() : void + { + $this->skipWhenNotUsingPhp56AndPdoPgsql(); + + $connection = $this->createDriver()->connect( + [ + 'host' => $GLOBALS['db_host'], + 'port' => $GLOBALS['db_port'], + ], + $GLOBALS['db_username'], + $GLOBALS['db_password'], + [PDO::PGSQL_ATTR_DISABLE_PREPARES => false] + ); + + self::assertInstanceOf(PDOConnection::class, $connection); + + try { + self::assertNotSame(true, $connection->getAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES)); + } catch (PDOException $ignored) { + /** @link https://bugs.php.net/bug.php?id=68371 */ + $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=68371'); + } + } + + /** + * @group DBAL-920 + */ + public function testConnectionDisablePreparesOnPhp56WhenDisablePreparesIsExplicitlyDefined() : void + { + $this->skipWhenNotUsingPhp56AndPdoPgsql(); + + $connection = $this->createDriver()->connect( + [ + 'host' => $GLOBALS['db_host'], + 'port' => $GLOBALS['db_port'], + ], + $GLOBALS['db_username'], + $GLOBALS['db_password'], + [PDO::PGSQL_ATTR_DISABLE_PREPARES => true] + ); + + self::assertInstanceOf(PDOConnection::class, $connection); + + try { + self::assertTrue($connection->getAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES)); + } catch (PDOException $ignored) { + /** @link https://bugs.php.net/bug.php?id=68371 */ + $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=68371'); + } + } + + /** + * {@inheritDoc} + */ + protected function createDriver() : DriverInterface + { + return new Driver(); + } + + private function skipWhenNotUsingPhp56AndPdoPgsql() : void + { + if (! defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) { + $this->markTestSkipped('Test requires PHP 5.6+'); + } + + if (isset($GLOBALS['db_type']) && $GLOBALS['db_type'] === 'pdo_pgsql') { + return; + } + + $this->markTestSkipped('Test enabled only when using pdo_pgsql specific phpunit.xml'); + } +} diff --git a/tests/Unit/DBAL/DriverManagerTest.php b/tests/Unit/DBAL/DriverManagerTest.php new file mode 100644 index 0000000..4b72cff --- /dev/null +++ b/tests/Unit/DBAL/DriverManagerTest.php @@ -0,0 +1,542 @@ +expectException(DBALException::class); + DriverManager::getConnection(['pdo' => 'test']); + } + + /** + * @requires extension pdo_sqlite + */ + public function testValidPdoInstance() : void + { + $conn = DriverManager::getConnection([ + 'pdo' => new PDO('sqlite::memory:'), + ]); + + self::assertEquals('sqlite', $conn->getDatabasePlatform()->getName()); + } + + /** + * @group DBAL-32 + * @requires extension pdo_sqlite + */ + public function testPdoInstanceSetErrorMode() : void + { + $pdo = new PDO('sqlite::memory:'); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + $options = ['pdo' => $pdo]; + + DriverManager::getConnection($options); + self::assertEquals(PDO::ERRMODE_EXCEPTION, $pdo->getAttribute(PDO::ATTR_ERRMODE)); + } + + public function testCheckParams() : void + { + $this->expectException(DBALException::class); + + DriverManager::getConnection([]); + } + + public function testInvalidDriver() : void + { + $this->expectException(DBALException::class); + + DriverManager::getConnection(['driver' => 'invalid_driver']); + } + + /** + * @requires extension pdo_sqlite + */ + public function testCustomPlatform() : void + { + $platform = $this->createMock(AbstractPlatform::class); + $options = [ + 'pdo' => new PDO('sqlite::memory:'), + 'platform' => $platform, + ]; + + $conn = DriverManager::getConnection($options); + self::assertSame($platform, $conn->getDatabasePlatform()); + } + + /** + * @requires extension pdo_sqlite + */ + public function testCustomWrapper() : void + { + $wrapper = $this->createMock(Connection::class); + $wrapperClass = get_class($wrapper); + + $options = [ + 'pdo' => new PDO('sqlite::memory:'), + 'wrapperClass' => $wrapperClass, + ]; + + $conn = DriverManager::getConnection($options); + self::assertInstanceOf($wrapperClass, $conn); + } + + /** + * @requires extension pdo_sqlite + */ + public function testInvalidWrapperClass() : void + { + $this->expectException(DBALException::class); + + $options = [ + 'pdo' => new PDO('sqlite::memory:'), + 'wrapperClass' => stdClass::class, + ]; + + DriverManager::getConnection($options); + } + + public function testInvalidDriverClass() : void + { + $this->expectException(DBALException::class); + + $options = ['driverClass' => stdClass::class]; + + DriverManager::getConnection($options); + } + + public function testValidDriverClass() : void + { + $options = ['driverClass' => PDOMySQLDriver::class]; + + $conn = DriverManager::getConnection($options); + self::assertInstanceOf(PDOMySQLDriver::class, $conn->getDriver()); + } + + public function testDatabaseUrlMasterSlave() : void + { + $options = [ + 'driver' => 'pdo_mysql', + 'master' => ['url' => 'mysql://foo:bar@localhost:11211/baz'], + 'slaves' => [ + 'slave1' => ['url' => 'mysql://foo:bar@localhost:11211/baz_slave'], + ], + 'wrapperClass' => MasterSlaveConnection::class, + ]; + + $conn = DriverManager::getConnection($options); + + $params = $conn->getParams(); + self::assertInstanceOf(PDOMySQLDriver::class, $conn->getDriver()); + + $expected = [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'port' => 11211, + ]; + + foreach ($expected as $key => $value) { + self::assertEquals($value, $params['master'][$key]); + self::assertEquals($value, $params['slaves']['slave1'][$key]); + } + + self::assertEquals('baz', $params['master']['dbname']); + self::assertEquals('baz_slave', $params['slaves']['slave1']['dbname']); + } + + public function testDatabaseUrlShard() : void + { + $options = [ + 'driver' => 'pdo_mysql', + 'shardChoser' => MultiTenantShardChoser::class, + 'global' => ['url' => 'mysql://foo:bar@localhost:11211/baz'], + 'shards' => [ + [ + 'id' => 1, + 'url' => 'mysql://foo:bar@localhost:11211/baz_slave', + ], + ], + 'wrapperClass' => PoolingShardConnection::class, + ]; + + $conn = DriverManager::getConnection($options); + + $params = $conn->getParams(); + self::assertInstanceOf(PDOMySQLDriver::class, $conn->getDriver()); + + $expected = [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'port' => 11211, + ]; + + foreach ($expected as $key => $value) { + self::assertEquals($value, $params['global'][$key]); + self::assertEquals($value, $params['shards'][0][$key]); + } + + self::assertEquals('baz', $params['global']['dbname']); + self::assertEquals('baz_slave', $params['shards'][0]['dbname']); + } + + /** + * @param mixed $url + * @param mixed $expected + * + * @dataProvider databaseUrls + */ + public function testDatabaseUrl($url, $expected) : void + { + $options = is_array($url) ? $url : ['url' => $url]; + + if (isset($options['pdo'])) { + if (! extension_loaded('pdo')) { + $this->markTestSkipped('PDO is not installed'); + } + + $options['pdo'] = $this->createMock(PDO::class); + } + + $options = is_array($url) ? $url : ['url' => $url]; + + if ($expected === false) { + $this->expectException(DBALException::class); + } + + $conn = DriverManager::getConnection($options); + + $params = $conn->getParams(); + foreach ($expected as $key => $value) { + if (in_array($key, ['pdo', 'driver', 'driverClass'], true)) { + self::assertInstanceOf($value, $conn->getDriver()); + } else { + self::assertEquals($value, $params[$key]); + } + } + } + + /** + * @return array> + */ + public function databaseUrls() : iterable + { + $driver = $this->createMock(Driver::class); + $driverClass = get_class($driver); + + return [ + 'simple URL' => [ + 'mysql://foo:bar@localhost/baz', + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'simple URL with port' => [ + 'mysql://foo:bar@localhost:11211/baz', + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'port' => 11211, + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'sqlite relative URL with host' => [ + 'sqlite://localhost/foo/dbname.sqlite', + [ + 'path' => 'foo/dbname.sqlite', + 'driver' => PDOSqliteDriver::class, + ], + ], + 'sqlite absolute URL with host' => [ + 'sqlite://localhost//tmp/dbname.sqlite', + [ + 'path' => '/tmp/dbname.sqlite', + 'driver' => PDOSqliteDriver::class, + ], + ], + 'sqlite relative URL without host' => [ + 'sqlite:///foo/dbname.sqlite', + [ + 'path' => 'foo/dbname.sqlite', + 'driver' => PDOSqliteDriver::class, + ], + ], + 'sqlite absolute URL without host' => [ + 'sqlite:////tmp/dbname.sqlite', + [ + 'path' => '/tmp/dbname.sqlite', + 'driver' => PDOSqliteDriver::class, + ], + ], + 'sqlite memory' => [ + 'sqlite:///:memory:', + [ + 'memory' => true, + 'driver' => PDOSqliteDriver::class, + ], + ], + 'sqlite memory with host' => [ + 'sqlite://localhost/:memory:', + [ + 'memory' => true, + 'driver' => PDOSqliteDriver::class, + ], + ], + 'params parsed from URL override individual params' => [ + [ + 'url' => 'mysql://foo:bar@localhost/baz', + 'password' => 'lulz', + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'params not parsed from URL but individual params are preserved' => [ + [ + 'url' => 'mysql://foo:bar@localhost/baz', + 'port' => 1234, + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'port' => 1234, + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'query params from URL are used as extra params' => [ + 'url' => 'mysql://foo:bar@localhost/dbname?charset=UTF-8', + ['charset' => 'UTF-8'], + ], + 'simple URL with fallthrough scheme not defined in map' => [ + 'sqlsrv://foo:bar@localhost/baz', + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => SQLSrvDriver::class, + ], + ], + 'simple URL with fallthrough scheme containing underscores fails' => [ + 'drizzle_pdo_mysql://foo:bar@localhost/baz', + false, + ], + 'simple URL with fallthrough scheme containing dashes works' => [ + 'drizzle-pdo-mysql://foo:bar@localhost/baz', + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => DrizzlePDOMySqlDriver::class, + ], + ], + 'simple URL with percent encoding' => [ + 'mysql://foo%3A:bar%2F@localhost/baz+baz%40', + [ + 'user' => 'foo:', + 'password' => 'bar/', + 'host' => 'localhost', + 'dbname' => 'baz+baz@', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'simple URL with percent sign in password' => [ + 'mysql://foo:bar%25bar@localhost/baz', + [ + 'user' => 'foo', + 'password' => 'bar%bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + + // DBAL-1234 + 'URL without scheme and without any driver information' => [ + ['url' => '//foo:bar@localhost/baz'], + false, + ], + 'URL without scheme but default PDO driver' => [ + [ + 'url' => '//foo:bar@localhost/baz', + 'pdo' => true, + ], + false, + ], + 'URL without scheme but default driver' => [ + [ + 'url' => '//foo:bar@localhost/baz', + 'driver' => 'pdo_mysql', + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'URL without scheme but custom driver' => [ + [ + 'url' => '//foo:bar@localhost/baz', + 'driverClass' => $driverClass, + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driverClass' => $driverClass, + ], + ], + 'URL without scheme but default PDO driver and default driver' => [ + [ + 'url' => '//foo:bar@localhost/baz', + 'pdo' => true, + 'driver' => 'pdo_mysql', + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'URL without scheme but driver and custom driver' => [ + [ + 'url' => '//foo:bar@localhost/baz', + 'driver' => 'pdo_mysql', + 'driverClass' => $driverClass, + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driverClass' => $driverClass, + ], + ], + 'URL with default PDO driver' => [ + [ + 'url' => 'mysql://foo:bar@localhost/baz', + 'pdo' => true, + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'URL with default driver' => [ + [ + 'url' => 'mysql://foo:bar@localhost/baz', + 'driver' => 'sqlite', + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'URL with default custom driver' => [ + [ + 'url' => 'mysql://foo:bar@localhost/baz', + 'driverClass' => $driverClass, + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'URL with default PDO driver and default driver' => [ + [ + 'url' => 'mysql://foo:bar@localhost/baz', + 'pdo' => true, + 'driver' => 'sqlite', + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'URL with default driver and default custom driver' => [ + [ + 'url' => 'mysql://foo:bar@localhost/baz', + 'driver' => 'sqlite', + 'driverClass' => $driverClass, + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + 'URL with default PDO driver and default driver and default custom driver' => [ + [ + 'url' => 'mysql://foo:bar@localhost/baz', + 'pdo' => true, + 'driver' => 'sqlite', + 'driverClass' => $driverClass, + ], + [ + 'user' => 'foo', + 'password' => 'bar', + 'host' => 'localhost', + 'dbname' => 'baz', + 'driver' => PDOMySQLDriver::class, + ], + ], + ]; + } +} diff --git a/tests/Unit/DBAL/Platforms/AbstractPlatformTestCase.php b/tests/Unit/DBAL/Platforms/AbstractPlatformTestCase.php new file mode 100644 index 0000000..eca3c53 --- /dev/null +++ b/tests/Unit/DBAL/Platforms/AbstractPlatformTestCase.php @@ -0,0 +1,1542 @@ +platform = $this->createPlatform(); + } + + /** + * @group DDC-1360 + */ + public function testQuoteIdentifier() : void + { + if ($this->platform->getName() === 'mssql') { + $this->markTestSkipped('Not working this way on mssql.'); + } + + $c = $this->platform->getIdentifierQuoteCharacter(); + self::assertEquals($c . 'test' . $c, $this->platform->quoteIdentifier('test')); + self::assertEquals($c . 'test' . $c . '.' . $c . 'test' . $c, $this->platform->quoteIdentifier('test.test')); + self::assertEquals(str_repeat($c, 4), $this->platform->quoteIdentifier($c)); + } + + /** + * @group DDC-1360 + */ + public function testQuoteSingleIdentifier() : void + { + if ($this->platform->getName() === 'mssql') { + $this->markTestSkipped('Not working this way on mssql.'); + } + + $c = $this->platform->getIdentifierQuoteCharacter(); + self::assertEquals($c . 'test' . $c, $this->platform->quoteSingleIdentifier('test')); + self::assertEquals($c . 'test.test' . $c, $this->platform->quoteSingleIdentifier('test.test')); + self::assertEquals(str_repeat($c, 4), $this->platform->quoteSingleIdentifier($c)); + } + + /** + * @group DBAL-1029 + * @dataProvider getReturnsForeignKeyReferentialActionSQL + */ + public function testReturnsForeignKeyReferentialActionSQL(string $action, string $expectedSQL) : void + { + self::assertSame($expectedSQL, $this->platform->getForeignKeyReferentialActionSQL($action)); + } + + /** + * @return mixed[][] + */ + public static function getReturnsForeignKeyReferentialActionSQL() : iterable + { + return [ + ['CASCADE', 'CASCADE'], + ['SET NULL', 'SET NULL'], + ['NO ACTION', 'NO ACTION'], + ['RESTRICT', 'RESTRICT'], + ['SET DEFAULT', 'SET DEFAULT'], + ['CaScAdE', 'CASCADE'], + ]; + } + + public function testGetInvalidForeignKeyReferentialActionSQL() : void + { + $this->expectException('InvalidArgumentException'); + $this->platform->getForeignKeyReferentialActionSQL('unknown'); + } + + public function testGetUnknownDoctrineMappingType() : void + { + $this->expectException(DBALException::class); + $this->platform->getDoctrineTypeMapping('foobar'); + } + + public function testRegisterDoctrineMappingType() : void + { + $this->platform->registerDoctrineTypeMapping('foo', 'integer'); + self::assertEquals('integer', $this->platform->getDoctrineTypeMapping('foo')); + } + + public function testRegisterUnknownDoctrineMappingType() : void + { + $this->expectException(DBALException::class); + $this->platform->registerDoctrineTypeMapping('foo', 'bar'); + } + + /** + * @group DBAL-2594 + */ + public function testRegistersCommentedDoctrineMappingTypeImplicitly() : void + { + if (! Type::hasType('my_commented')) { + Type::addType('my_commented', CommentedType::class); + } + + $type = Type::getType('my_commented'); + $this->platform->registerDoctrineTypeMapping('foo', 'my_commented'); + + self::assertTrue($this->platform->isCommentedDoctrineType($type)); + } + + /** + * @group DBAL-939 + * @dataProvider getIsCommentedDoctrineType + */ + public function testIsCommentedDoctrineType(Type $type, bool $commented) : void + { + self::assertSame($commented, $this->platform->isCommentedDoctrineType($type)); + } + + /** + * @return mixed[] + */ + public function getIsCommentedDoctrineType() : iterable + { + $this->setUp(); + + $data = []; + + foreach (Type::getTypesMap() as $typeName => $className) { + $type = Type::getType($typeName); + + $data[$typeName] = [ + $type, + $type->requiresSQLCommentHint($this->platform), + ]; + } + + return $data; + } + + public function testCreateWithNoColumns() : void + { + $table = new Table('test'); + + $this->expectException(DBALException::class); + $sql = $this->platform->getCreateTableSQL($table); + } + + public function testGeneratesTableCreationSql() : void + { + $table = new Table('test'); + $table->addColumn('id', 'integer', ['notnull' => true, 'autoincrement' => true]); + $table->addColumn('test', 'string', ['notnull' => false, 'length' => 255]); + $table->setPrimaryKey(['id']); + + $sql = $this->platform->getCreateTableSQL($table); + self::assertEquals($this->getGenerateTableSql(), $sql[0]); + } + + abstract public function getGenerateTableSql() : string; + + public function testGenerateTableWithMultiColumnUniqueIndex() : void + { + $table = new Table('test'); + $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]); + $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]); + $table->addUniqueIndex(['foo', 'bar']); + + $sql = $this->platform->getCreateTableSQL($table); + self::assertEquals($this->getGenerateTableWithMultiColumnUniqueIndexSql(), $sql); + } + + /** + * @return string[] + */ + abstract public function getGenerateTableWithMultiColumnUniqueIndexSql() : array; + + public function testGeneratesIndexCreationSql() : void + { + $indexDef = new Index('my_idx', ['user_name', 'last_login']); + + self::assertEquals( + $this->getGenerateIndexSql(), + $this->platform->getCreateIndexSQL($indexDef, 'mytable') + ); + } + + abstract public function getGenerateIndexSql() : string; + + public function testGeneratesUniqueIndexCreationSql() : void + { + $indexDef = new Index('index_name', ['test', 'test2'], true); + + $sql = $this->platform->getCreateIndexSQL($indexDef, 'test'); + self::assertEquals($this->getGenerateUniqueIndexSql(), $sql); + } + + abstract public function getGenerateUniqueIndexSql() : string; + + public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes() : void + { + $where = 'test IS NULL AND test2 IS NOT NULL'; + $indexDef = new Index('name', ['test', 'test2'], false, false, [], ['where' => $where]); + $uniqueIndex = new Index('name', ['test', 'test2'], true, false, [], ['where' => $where]); + + $expected = ' WHERE ' . $where; + + $actuals = []; + + if ($this->supportsInlineIndexDeclaration()) { + $actuals[] = $this->platform->getIndexDeclarationSQL('name', $indexDef); + } + + $actuals[] = $this->platform->getUniqueConstraintDeclarationSQL('name', $uniqueIndex); + $actuals[] = $this->platform->getCreateIndexSQL($indexDef, 'table'); + + foreach ($actuals as $actual) { + if ($this->platform->supportsPartialIndexes()) { + self::assertStringEndsWith($expected, $actual, 'WHERE clause should be present'); + } else { + self::assertStringEndsNotWith($expected, $actual, 'WHERE clause should NOT be present'); + } + } + } + + public function testGeneratesForeignKeyCreationSql() : void + { + $fk = new ForeignKeyConstraint(['fk_name_id'], 'other_table', ['id'], ''); + + $sql = $this->platform->getCreateForeignKeySQL($fk, 'test'); + self::assertEquals($sql, $this->getGenerateForeignKeySql()); + } + + abstract public function getGenerateForeignKeySql() : string; + + public function testGeneratesConstraintCreationSql() : void + { + $idx = new Index('constraint_name', ['test'], true, false); + $sql = $this->platform->getCreateConstraintSQL($idx, 'test'); + self::assertEquals($this->getGenerateConstraintUniqueIndexSql(), $sql); + + $pk = new Index('constraint_name', ['test'], true, true); + $sql = $this->platform->getCreateConstraintSQL($pk, 'test'); + self::assertEquals($this->getGenerateConstraintPrimaryIndexSql(), $sql); + + $fk = new ForeignKeyConstraint(['fk_name'], 'foreign', ['id'], 'constraint_fk'); + $sql = $this->platform->getCreateConstraintSQL($fk, 'test'); + self::assertEquals($this->getGenerateConstraintForeignKeySql($fk), $sql); + } + + public function testGeneratesForeignKeySqlOnlyWhenSupportingForeignKeys() : void + { + $fk = new ForeignKeyConstraint(['fk_name'], 'foreign', ['id'], 'constraint_fk'); + + if ($this->platform->supportsForeignKeyConstraints()) { + self::assertIsString($this->platform->getCreateForeignKeySQL($fk, 'test')); + } else { + $this->expectException(DBALException::class); + $this->platform->getCreateForeignKeySQL($fk, 'test'); + } + } + + protected function getBitAndComparisonExpressionSql(string $value1, string $value2) : string + { + return '(' . $value1 . ' & ' . $value2 . ')'; + } + + /** + * @group DDC-1213 + */ + public function testGeneratesBitAndComparisonExpressionSql() : void + { + $sql = $this->platform->getBitAndComparisonExpression(2, 4); + self::assertEquals($this->getBitAndComparisonExpressionSql(2, 4), $sql); + } + + protected function getBitOrComparisonExpressionSql(string $value1, string $value2) : string + { + return '(' . $value1 . ' | ' . $value2 . ')'; + } + + /** + * @group DDC-1213 + */ + public function testGeneratesBitOrComparisonExpressionSql() : void + { + $sql = $this->platform->getBitOrComparisonExpression(2, 4); + self::assertEquals($this->getBitOrComparisonExpressionSql(2, 4), $sql); + } + + public function getGenerateConstraintUniqueIndexSql() : string + { + return 'ALTER TABLE test ADD CONSTRAINT constraint_name UNIQUE (test)'; + } + + public function getGenerateConstraintPrimaryIndexSql() : string + { + return 'ALTER TABLE test ADD CONSTRAINT constraint_name PRIMARY KEY (test)'; + } + + public function getGenerateConstraintForeignKeySql(ForeignKeyConstraint $fk) : string + { + $quotedForeignTable = $fk->getQuotedForeignTableName($this->platform); + + return sprintf( + 'ALTER TABLE test ADD CONSTRAINT constraint_fk FOREIGN KEY (fk_name) REFERENCES %s (id)', + $quotedForeignTable + ); + } + + /** + * @return string[] + */ + abstract public function getGenerateAlterTableSql() : array; + + public function testGeneratesTableAlterationSql() : void + { + $expectedSql = $this->getGenerateAlterTableSql(); + + $table = new Table('mytable'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('foo', 'integer'); + $table->addColumn('bar', 'string'); + $table->addColumn('bloo', 'boolean'); + $table->setPrimaryKey(['id']); + + $tableDiff = new TableDiff('mytable'); + $tableDiff->fromTable = $table; + $tableDiff->newName = 'userlist'; + $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType('integer'), ['notnull' => false]); + $tableDiff->removedColumns['foo'] = new Column('foo', Type::getType('integer')); + $tableDiff->changedColumns['bar'] = new ColumnDiff( + 'bar', + new Column( + 'baz', + Type::getType('string'), + ['default' => 'def'] + ), + ['type', 'notnull', 'default'] + ); + $tableDiff->changedColumns['bloo'] = new ColumnDiff( + 'bloo', + new Column( + 'bloo', + Type::getType('boolean'), + ['default' => false] + ), + ['type', 'notnull', 'default'] + ); + + $sql = $this->platform->getAlterTableSQL($tableDiff); + + self::assertEquals($expectedSql, $sql); + } + + public function testGetCustomColumnDeclarationSql() : void + { + $field = ['columnDefinition' => 'MEDIUMINT(6) UNSIGNED']; + self::assertEquals('foo MEDIUMINT(6) UNSIGNED', $this->platform->getColumnDeclarationSQL('foo', $field)); + } + + public function testGetCreateTableSqlDispatchEvent() : void + { + $listenerMock = $this->getMockBuilder('GetCreateTableSqlDispatchEvenListener') + ->setMethods(['onSchemaCreateTable', 'onSchemaCreateTableColumn']) + ->getMock(); + $listenerMock + ->expects($this->once()) + ->method('onSchemaCreateTable'); + $listenerMock + ->expects($this->exactly(2)) + ->method('onSchemaCreateTableColumn'); + + $eventManager = new EventManager(); + $eventManager->addEventListener([Events::onSchemaCreateTable, Events::onSchemaCreateTableColumn], $listenerMock); + + $this->platform->setEventManager($eventManager); + + $table = new Table('test'); + $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]); + $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]); + + $this->platform->getCreateTableSQL($table); + } + + public function testGetDropTableSqlDispatchEvent() : void + { + $listenerMock = $this->getMockBuilder('GetDropTableSqlDispatchEventListener') + ->setMethods(['onSchemaDropTable']) + ->getMock(); + $listenerMock + ->expects($this->once()) + ->method('onSchemaDropTable'); + + $eventManager = new EventManager(); + $eventManager->addEventListener([Events::onSchemaDropTable], $listenerMock); + + $this->platform->setEventManager($eventManager); + + $this->platform->getDropTableSQL('TABLE'); + } + + public function testGetAlterTableSqlDispatchEvent() : void + { + $events = [ + 'onSchemaAlterTable', + 'onSchemaAlterTableAddColumn', + 'onSchemaAlterTableRemoveColumn', + 'onSchemaAlterTableChangeColumn', + 'onSchemaAlterTableRenameColumn', + ]; + + $listenerMock = $this->getMockBuilder('GetAlterTableSqlDispatchEvenListener') + ->setMethods($events) + ->getMock(); + $listenerMock + ->expects($this->once()) + ->method('onSchemaAlterTable'); + $listenerMock + ->expects($this->once()) + ->method('onSchemaAlterTableAddColumn'); + $listenerMock + ->expects($this->once()) + ->method('onSchemaAlterTableRemoveColumn'); + $listenerMock + ->expects($this->once()) + ->method('onSchemaAlterTableChangeColumn'); + $listenerMock + ->expects($this->once()) + ->method('onSchemaAlterTableRenameColumn'); + + $eventManager = new EventManager(); + $events = [ + Events::onSchemaAlterTable, + Events::onSchemaAlterTableAddColumn, + Events::onSchemaAlterTableRemoveColumn, + Events::onSchemaAlterTableChangeColumn, + Events::onSchemaAlterTableRenameColumn, + ]; + $eventManager->addEventListener($events, $listenerMock); + + $this->platform->setEventManager($eventManager); + + $table = new Table('mytable'); + $table->addColumn('removed', 'integer'); + $table->addColumn('changed', 'integer'); + $table->addColumn('renamed', 'integer'); + + $tableDiff = new TableDiff('mytable'); + $tableDiff->fromTable = $table; + $tableDiff->addedColumns['added'] = new Column('added', Type::getType('integer'), []); + $tableDiff->removedColumns['removed'] = new Column('removed', Type::getType('integer'), []); + $tableDiff->changedColumns['changed'] = new ColumnDiff( + 'changed', + new Column( + 'changed2', + Type::getType('string'), + [] + ), + [] + ); + $tableDiff->renamedColumns['renamed'] = new Column('renamed2', Type::getType('integer'), []); + + $this->platform->getAlterTableSQL($tableDiff); + } + + /** + * @group DBAL-42 + */ + public function testCreateTableColumnComments() : void + { + $table = new Table('test'); + $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); + $table->setPrimaryKey(['id']); + + self::assertEquals($this->getCreateTableColumnCommentsSQL(), $this->platform->getCreateTableSQL($table)); + } + + /** + * @group DBAL-42 + */ + public function testAlterTableColumnComments() : void + { + $tableDiff = new TableDiff('mytable'); + $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType('integer'), ['comment' => 'A comment']); + $tableDiff->changedColumns['foo'] = new ColumnDiff( + 'foo', + new Column( + 'foo', + Type::getType('string') + ), + ['comment'] + ); + $tableDiff->changedColumns['bar'] = new ColumnDiff( + 'bar', + new Column( + 'baz', + Type::getType('string'), + ['comment' => 'B comment'] + ), + ['comment'] + ); + + self::assertEquals($this->getAlterTableColumnCommentsSQL(), $this->platform->getAlterTableSQL($tableDiff)); + } + + public function testCreateTableColumnTypeComments() : void + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->addColumn('data', 'array'); + $table->setPrimaryKey(['id']); + + self::assertEquals($this->getCreateTableColumnTypeCommentsSQL(), $this->platform->getCreateTableSQL($table)); + } + + /** + * @return string[] + */ + public function getCreateTableColumnCommentsSQL() : array + { + $this->markTestSkipped('Platform does not support Column comments.'); + } + + /** + * @return string[] + */ + public function getAlterTableColumnCommentsSQL() : array + { + $this->markTestSkipped('Platform does not support Column comments.'); + } + + /** + * @return string[] + */ + public function getCreateTableColumnTypeCommentsSQL() : array + { + $this->markTestSkipped('Platform does not support Column comments.'); + } + + public function testGetDefaultValueDeclarationSQL() : void + { + // non-timestamp value will get single quotes + $field = [ + 'type' => Type::getType('string'), + 'default' => 'non_timestamp', + ]; + + self::assertEquals(" DEFAULT 'non_timestamp'", $this->platform->getDefaultValueDeclarationSQL($field)); + } + + /** + * @group 2859 + */ + public function testGetDefaultValueDeclarationSQLDateTime() : void + { + // timestamps on datetime types should not be quoted + foreach (['datetime', 'datetimetz', 'datetime_immutable', 'datetimetz_immutable'] as $type) { + $field = [ + 'type' => Type::getType($type), + 'default' => $this->platform->getCurrentTimestampSQL(), + ]; + + self::assertSame( + ' DEFAULT ' . $this->platform->getCurrentTimestampSQL(), + $this->platform->getDefaultValueDeclarationSQL($field) + ); + } + } + + public function testGetDefaultValueDeclarationSQLForIntegerTypes() : void + { + foreach (['bigint', 'integer', 'smallint'] as $type) { + $field = [ + 'type' => Type::getType($type), + 'default' => 1, + ]; + + self::assertEquals( + ' DEFAULT 1', + $this->platform->getDefaultValueDeclarationSQL($field) + ); + } + } + + /** + * @group 2859 + */ + public function testGetDefaultValueDeclarationSQLForDateType() : void + { + $currentDateSql = $this->platform->getCurrentDateSQL(); + foreach (['date', 'date_immutable'] as $type) { + $field = [ + 'type' => Type::getType($type), + 'default' => $currentDateSql, + ]; + + self::assertSame( + ' DEFAULT ' . $currentDateSql, + $this->platform->getDefaultValueDeclarationSQL($field) + ); + } + } + + /** + * @group DBAL-45 + */ + public function testKeywordList() : void + { + $keywordList = $this->platform->getReservedKeywordsList(); + self::assertInstanceOf(KeywordList::class, $keywordList); + + self::assertTrue($keywordList->isKeyword('table')); + } + + /** + * @group DBAL-374 + */ + public function testQuotedColumnInPrimaryKeyPropagation() : void + { + $table = new Table('`quoted`'); + $table->addColumn('create', 'string'); + $table->setPrimaryKey(['create']); + + $sql = $this->platform->getCreateTableSQL($table); + self::assertEquals($this->getQuotedColumnInPrimaryKeySQL(), $sql); + } + + /** + * @return string[] + */ + abstract protected function getQuotedColumnInPrimaryKeySQL() : array; + + /** + * @return string[] + */ + abstract protected function getQuotedColumnInIndexSQL() : array; + + /** + * @return string[] + */ + abstract protected function getQuotedNameInIndexSQL() : array; + + /** + * @return string[] + */ + abstract protected function getQuotedColumnInForeignKeySQL() : array; + + /** + * @group DBAL-374 + */ + public function testQuotedColumnInIndexPropagation() : void + { + $table = new Table('`quoted`'); + $table->addColumn('create', 'string'); + $table->addIndex(['create']); + + $sql = $this->platform->getCreateTableSQL($table); + self::assertEquals($this->getQuotedColumnInIndexSQL(), $sql); + } + + public function testQuotedNameInIndexSQL() : void + { + $table = new Table('test'); + $table->addColumn('column1', 'string'); + $table->addIndex(['column1'], '`key`'); + + $sql = $this->platform->getCreateTableSQL($table); + self::assertEquals($this->getQuotedNameInIndexSQL(), $sql); + } + + /** + * @group DBAL-374 + */ + public function testQuotedColumnInForeignKeyPropagation() : void + { + $table = new Table('`quoted`'); + $table->addColumn('create', 'string'); + $table->addColumn('foo', 'string'); + $table->addColumn('`bar`', 'string'); + + // Foreign table with reserved keyword as name (needs quotation). + $foreignTable = new Table('foreign'); + $foreignTable->addColumn('create', 'string'); // Foreign column with reserved keyword as name (needs quotation). + $foreignTable->addColumn('bar', 'string'); // Foreign column with non-reserved keyword as name (does not need quotation). + $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite). + + $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_RESERVED_KEYWORD'); + + // Foreign table with non-reserved keyword as name (does not need quotation). + $foreignTable = new Table('foo'); + $foreignTable->addColumn('create', 'string'); // Foreign column with reserved keyword as name (needs quotation). + $foreignTable->addColumn('bar', 'string'); // Foreign column with non-reserved keyword as name (does not need quotation). + $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite). + + $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_NON_RESERVED_KEYWORD'); + + // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite). + $foreignTable = new Table('`foo-bar`'); + $foreignTable->addColumn('create', 'string'); // Foreign column with reserved keyword as name (needs quotation). + $foreignTable->addColumn('bar', 'string'); // Foreign column with non-reserved keyword as name (does not need quotation). + $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite). + + $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_INTENDED_QUOTATION'); + + $sql = $this->platform->getCreateTableSQL($table, AbstractPlatform::CREATE_FOREIGNKEYS); + self::assertEquals($this->getQuotedColumnInForeignKeySQL(), $sql); + } + + /** + * @group DBAL-1051 + */ + public function testQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : void + { + $index = new Index('select', ['foo'], true); + + self::assertSame( + $this->getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(), + $this->platform->getUniqueConstraintDeclarationSQL('select', $index) + ); + } + + abstract protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string; + + /** + * @group DBAL-2270 + */ + public function testQuotesReservedKeywordInTruncateTableSQL() : void + { + self::assertSame( + $this->getQuotesReservedKeywordInTruncateTableSQL(), + $this->platform->getTruncateTableSQL('select') + ); + } + + abstract protected function getQuotesReservedKeywordInTruncateTableSQL() : string; + + /** + * @group DBAL-1051 + */ + public function testQuotesReservedKeywordInIndexDeclarationSQL() : void + { + $index = new Index('select', ['foo']); + + if (! $this->supportsInlineIndexDeclaration()) { + $this->expectException(DBALException::class); + } + + self::assertSame( + $this->getQuotesReservedKeywordInIndexDeclarationSQL(), + $this->platform->getIndexDeclarationSQL('select', $index) + ); + } + + abstract protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string; + + protected function supportsInlineIndexDeclaration() : bool + { + return true; + } + + public function testSupportsCommentOnStatement() : void + { + self::assertSame($this->supportsCommentOnStatement(), $this->platform->supportsCommentOnStatement()); + } + + protected function supportsCommentOnStatement() : bool + { + return false; + } + + public function testGetCreateSchemaSQL() : void + { + $this->expectException(DBALException::class); + + $this->platform->getCreateSchemaSQL('schema'); + } + + /** + * @group DBAL-585 + */ + public function testAlterTableChangeQuotedColumn() : void + { + $tableDiff = new TableDiff('mytable'); + $tableDiff->fromTable = new Table('mytable'); + $tableDiff->changedColumns['foo'] = new ColumnDiff( + 'select', + new Column( + 'select', + Type::getType('string') + ), + ['type'] + ); + + self::assertStringContainsString( + $this->platform->quoteIdentifier('select'), + implode(';', $this->platform->getAlterTableSQL($tableDiff)) + ); + } + + /** + * @group DBAL-563 + */ + public function testUsesSequenceEmulatedIdentityColumns() : void + { + self::assertFalse($this->platform->usesSequenceEmulatedIdentityColumns()); + } + + /** + * @group DBAL-563 + */ + public function testReturnsIdentitySequenceName() : void + { + $this->expectException(DBALException::class); + + $this->platform->getIdentitySequenceName('mytable', 'mycolumn'); + } + + public function testReturnsBinaryDefaultLength() : void + { + self::assertSame($this->getBinaryDefaultLength(), $this->platform->getBinaryDefaultLength()); + } + + protected function getBinaryDefaultLength() : int + { + return 255; + } + + public function testReturnsBinaryMaxLength() : void + { + self::assertSame($this->getBinaryMaxLength(), $this->platform->getBinaryMaxLength()); + } + + protected function getBinaryMaxLength() : int + { + return 4000; + } + + public function testReturnsBinaryTypeDeclarationSQL() : void + { + $this->expectException(DBALException::class); + + $this->platform->getBinaryTypeDeclarationSQL([]); + } + + public function testReturnsBinaryTypeLongerThanMaxDeclarationSQL() : void + { + $this->markTestSkipped('Not applicable to the platform'); + } + + /** + * @group DBAL-553 + */ + public function hasNativeJsonType() : void + { + self::assertFalse($this->platform->hasNativeJsonType()); + } + + /** + * @group DBAL-553 + */ + public function testReturnsJsonTypeDeclarationSQL() : void + { + $column = [ + 'length' => 666, + 'notnull' => true, + 'type' => Type::getType('json_array'), + ]; + + self::assertSame( + $this->platform->getClobTypeDeclarationSQL($column), + $this->platform->getJsonTypeDeclarationSQL($column) + ); + } + + /** + * @group DBAL-234 + */ + public function testAlterTableRenameIndex() : void + { + $tableDiff = new TableDiff('mytable'); + $tableDiff->fromTable = new Table('mytable'); + $tableDiff->fromTable->addColumn('id', 'integer'); + $tableDiff->fromTable->setPrimaryKey(['id']); + $tableDiff->renamedIndexes = [ + 'idx_foo' => new Index('idx_bar', ['id']), + ]; + + self::assertSame( + $this->getAlterTableRenameIndexSQL(), + $this->platform->getAlterTableSQL($tableDiff) + ); + } + + /** + * @return string[] + * + * @group DBAL-234 + */ + protected function getAlterTableRenameIndexSQL() : array + { + return [ + 'DROP INDEX idx_foo', + 'CREATE INDEX idx_bar ON mytable (id)', + ]; + } + + /** + * @group DBAL-234 + */ + public function testQuotesAlterTableRenameIndex() : void + { + $tableDiff = new TableDiff('table'); + $tableDiff->fromTable = new Table('table'); + $tableDiff->fromTable->addColumn('id', 'integer'); + $tableDiff->fromTable->setPrimaryKey(['id']); + $tableDiff->renamedIndexes = [ + 'create' => new Index('select', ['id']), + '`foo`' => new Index('`bar`', ['id']), + ]; + + self::assertSame( + $this->getQuotedAlterTableRenameIndexSQL(), + $this->platform->getAlterTableSQL($tableDiff) + ); + } + + /** + * @return string[] + * + * @group DBAL-234 + */ + protected function getQuotedAlterTableRenameIndexSQL() : array + { + return [ + 'DROP INDEX "create"', + 'CREATE INDEX "select" ON "table" (id)', + 'DROP INDEX "foo"', + 'CREATE INDEX "bar" ON "table" (id)', + ]; + } + + /** + * @group DBAL-835 + */ + public function testQuotesAlterTableRenameColumn() : void + { + $fromTable = new Table('mytable'); + + $fromTable->addColumn('unquoted1', 'integer', ['comment' => 'Unquoted 1']); + $fromTable->addColumn('unquoted2', 'integer', ['comment' => 'Unquoted 2']); + $fromTable->addColumn('unquoted3', 'integer', ['comment' => 'Unquoted 3']); + + $fromTable->addColumn('create', 'integer', ['comment' => 'Reserved keyword 1']); + $fromTable->addColumn('table', 'integer', ['comment' => 'Reserved keyword 2']); + $fromTable->addColumn('select', 'integer', ['comment' => 'Reserved keyword 3']); + + $fromTable->addColumn('`quoted1`', 'integer', ['comment' => 'Quoted 1']); + $fromTable->addColumn('`quoted2`', 'integer', ['comment' => 'Quoted 2']); + $fromTable->addColumn('`quoted3`', 'integer', ['comment' => 'Quoted 3']); + + $toTable = new Table('mytable'); + + $toTable->addColumn('unquoted', 'integer', ['comment' => 'Unquoted 1']); // unquoted -> unquoted + $toTable->addColumn('where', 'integer', ['comment' => 'Unquoted 2']); // unquoted -> reserved keyword + $toTable->addColumn('`foo`', 'integer', ['comment' => 'Unquoted 3']); // unquoted -> quoted + + $toTable->addColumn('reserved_keyword', 'integer', ['comment' => 'Reserved keyword 1']); // reserved keyword -> unquoted + $toTable->addColumn('from', 'integer', ['comment' => 'Reserved keyword 2']); // reserved keyword -> reserved keyword + $toTable->addColumn('`bar`', 'integer', ['comment' => 'Reserved keyword 3']); // reserved keyword -> quoted + + $toTable->addColumn('quoted', 'integer', ['comment' => 'Quoted 1']); // quoted -> unquoted + $toTable->addColumn('and', 'integer', ['comment' => 'Quoted 2']); // quoted -> reserved keyword + $toTable->addColumn('`baz`', 'integer', ['comment' => 'Quoted 3']); // quoted -> quoted + + $comparator = new Comparator(); + + self::assertEquals( + $this->getQuotedAlterTableRenameColumnSQL(), + $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable)) + ); + } + + /** + * Returns SQL statements for {@link testQuotesAlterTableRenameColumn}. + * + * @return string[] + * + * @group DBAL-835 + */ + abstract protected function getQuotedAlterTableRenameColumnSQL() : array; + + /** + * @group DBAL-835 + */ + public function testQuotesAlterTableChangeColumnLength() : void + { + $fromTable = new Table('mytable'); + + $fromTable->addColumn('unquoted1', 'string', ['comment' => 'Unquoted 1', 'length' => 10]); + $fromTable->addColumn('unquoted2', 'string', ['comment' => 'Unquoted 2', 'length' => 10]); + $fromTable->addColumn('unquoted3', 'string', ['comment' => 'Unquoted 3', 'length' => 10]); + + $fromTable->addColumn('create', 'string', ['comment' => 'Reserved keyword 1', 'length' => 10]); + $fromTable->addColumn('table', 'string', ['comment' => 'Reserved keyword 2', 'length' => 10]); + $fromTable->addColumn('select', 'string', ['comment' => 'Reserved keyword 3', 'length' => 10]); + + $toTable = new Table('mytable'); + + $toTable->addColumn('unquoted1', 'string', ['comment' => 'Unquoted 1', 'length' => 255]); + $toTable->addColumn('unquoted2', 'string', ['comment' => 'Unquoted 2', 'length' => 255]); + $toTable->addColumn('unquoted3', 'string', ['comment' => 'Unquoted 3', 'length' => 255]); + + $toTable->addColumn('create', 'string', ['comment' => 'Reserved keyword 1', 'length' => 255]); + $toTable->addColumn('table', 'string', ['comment' => 'Reserved keyword 2', 'length' => 255]); + $toTable->addColumn('select', 'string', ['comment' => 'Reserved keyword 3', 'length' => 255]); + + $comparator = new Comparator(); + + self::assertEquals( + $this->getQuotedAlterTableChangeColumnLengthSQL(), + $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable)) + ); + } + + /** + * Returns SQL statements for {@link testQuotesAlterTableChangeColumnLength}. + * + * @return string[] + * + * @group DBAL-835 + */ + abstract protected function getQuotedAlterTableChangeColumnLengthSQL() : array; + + /** + * @group DBAL-807 + */ + public function testAlterTableRenameIndexInSchema() : void + { + $tableDiff = new TableDiff('myschema.mytable'); + $tableDiff->fromTable = new Table('myschema.mytable'); + $tableDiff->fromTable->addColumn('id', 'integer'); + $tableDiff->fromTable->setPrimaryKey(['id']); + $tableDiff->renamedIndexes = [ + 'idx_foo' => new Index('idx_bar', ['id']), + ]; + + self::assertSame( + $this->getAlterTableRenameIndexInSchemaSQL(), + $this->platform->getAlterTableSQL($tableDiff) + ); + } + + /** + * @return string[] + * + * @group DBAL-807 + */ + protected function getAlterTableRenameIndexInSchemaSQL() : array + { + return [ + 'DROP INDEX idx_foo', + 'CREATE INDEX idx_bar ON myschema.mytable (id)', + ]; + } + + /** + * @group DBAL-807 + */ + public function testQuotesAlterTableRenameIndexInSchema() : void + { + $tableDiff = new TableDiff('`schema`.table'); + $tableDiff->fromTable = new Table('`schema`.table'); + $tableDiff->fromTable->addColumn('id', 'integer'); + $tableDiff->fromTable->setPrimaryKey(['id']); + $tableDiff->renamedIndexes = [ + 'create' => new Index('select', ['id']), + '`foo`' => new Index('`bar`', ['id']), + ]; + + self::assertSame( + $this->getQuotedAlterTableRenameIndexInSchemaSQL(), + $this->platform->getAlterTableSQL($tableDiff) + ); + } + + /** + * @return string[] + * + * @group DBAL-234 + */ + protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array + { + return [ + 'DROP INDEX "schema"."create"', + 'CREATE INDEX "select" ON "schema"."table" (id)', + 'DROP INDEX "schema"."foo"', + 'CREATE INDEX "bar" ON "schema"."table" (id)', + ]; + } + + /** + * @group DBAL-1237 + */ + public function testQuotesDropForeignKeySQL() : void + { + if (! $this->platform->supportsForeignKeyConstraints()) { + $this->markTestSkipped( + sprintf('%s does not support foreign key constraints.', get_class($this->platform)) + ); + } + + $tableName = 'table'; + $table = new Table($tableName); + $foreignKeyName = 'select'; + $foreignKey = new ForeignKeyConstraint([], 'foo', [], 'select'); + $expectedSql = $this->getQuotesDropForeignKeySQL(); + + self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKeyName, $tableName)); + self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKey, $table)); + } + + protected function getQuotesDropForeignKeySQL() : string + { + return 'ALTER TABLE "table" DROP FOREIGN KEY "select"'; + } + + /** + * @group DBAL-1237 + */ + public function testQuotesDropConstraintSQL() : void + { + $tableName = 'table'; + $table = new Table($tableName); + $constraintName = 'select'; + $constraint = new ForeignKeyConstraint([], 'foo', [], 'select'); + $expectedSql = $this->getQuotesDropConstraintSQL(); + + self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraintName, $tableName)); + self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraint, $table)); + } + + protected function getQuotesDropConstraintSQL() : string + { + return 'ALTER TABLE "table" DROP CONSTRAINT "select"'; + } + + protected function getStringLiteralQuoteCharacter() : string + { + return "'"; + } + + public function testGetStringLiteralQuoteCharacter() : void + { + self::assertSame($this->getStringLiteralQuoteCharacter(), $this->platform->getStringLiteralQuoteCharacter()); + } + + protected function getQuotedCommentOnColumnSQLWithoutQuoteCharacter() : string + { + return "COMMENT ON COLUMN mytable.id IS 'This is a comment'"; + } + + public function testGetCommentOnColumnSQLWithoutQuoteCharacter() : void + { + self::assertEquals( + $this->getQuotedCommentOnColumnSQLWithoutQuoteCharacter(), + $this->platform->getCommentOnColumnSQL('mytable', 'id', 'This is a comment') + ); + } + + protected function getQuotedCommentOnColumnSQLWithQuoteCharacter() : string + { + return "COMMENT ON COLUMN mytable.id IS 'It''s a quote !'"; + } + + public function testGetCommentOnColumnSQLWithQuoteCharacter() : void + { + $c = $this->getStringLiteralQuoteCharacter(); + + self::assertEquals( + $this->getQuotedCommentOnColumnSQLWithQuoteCharacter(), + $this->platform->getCommentOnColumnSQL('mytable', 'id', 'It' . $c . 's a quote !') + ); + } + + /** + * @see testGetCommentOnColumnSQL + * + * @return string[] + */ + abstract protected function getCommentOnColumnSQL() : array; + + /** + * @group DBAL-1004 + */ + public function testGetCommentOnColumnSQL() : void + { + self::assertSame( + $this->getCommentOnColumnSQL(), + [ + $this->platform->getCommentOnColumnSQL('foo', 'bar', 'comment'), // regular identifiers + $this->platform->getCommentOnColumnSQL('`Foo`', '`BAR`', 'comment'), // explicitly quoted identifiers + $this->platform->getCommentOnColumnSQL('select', 'from', 'comment'), // reserved keyword identifiers + ] + ); + } + + /** + * @group DBAL-1176 + * @dataProvider getGeneratesInlineColumnCommentSQL + */ + public function testGeneratesInlineColumnCommentSQL(?string $comment, string $expectedSql) : void + { + if (! $this->platform->supportsInlineColumnComments()) { + $this->markTestSkipped(sprintf('%s does not support inline column comments.', get_class($this->platform))); + } + + self::assertSame($expectedSql, $this->platform->getInlineColumnCommentSQL($comment)); + } + + /** + * @return mixed[][] + */ + public static function getGeneratesInlineColumnCommentSQL() : iterable + { + return [ + 'regular comment' => ['Regular comment', static::getInlineColumnRegularCommentSQL()], + 'comment requiring escaping' => [ + sprintf( + 'Using inline comment delimiter %s works', + static::getInlineColumnCommentDelimiter() + ), + static::getInlineColumnCommentRequiringEscapingSQL(), + ], + 'empty comment' => ['', static::getInlineColumnEmptyCommentSQL()], + ]; + } + + protected static function getInlineColumnCommentDelimiter() : string + { + return "'"; + } + + protected static function getInlineColumnRegularCommentSQL() : string + { + return "COMMENT 'Regular comment'"; + } + + protected static function getInlineColumnCommentRequiringEscapingSQL() : string + { + return "COMMENT 'Using inline comment delimiter '' works'"; + } + + protected static function getInlineColumnEmptyCommentSQL() : string + { + return "COMMENT ''"; + } + + protected function getQuotedStringLiteralWithoutQuoteCharacter() : string + { + return "'No quote'"; + } + + protected function getQuotedStringLiteralWithQuoteCharacter() : string + { + return "'It''s a quote'"; + } + + protected function getQuotedStringLiteralQuoteCharacter() : string + { + return "''''"; + } + + /** + * @group DBAL-1176 + */ + public function testThrowsExceptionOnGeneratingInlineColumnCommentSQLIfUnsupported() : void + { + if ($this->platform->supportsInlineColumnComments()) { + $this->markTestSkipped(sprintf('%s supports inline column comments.', get_class($this->platform))); + } + + $this->expectException(DBALException::class); + $this->expectExceptionMessage("Operation 'Doctrine\\DBAL\\Platforms\\AbstractPlatform::getInlineColumnCommentSQL' is not supported by platform."); + $this->expectExceptionCode(0); + + $this->platform->getInlineColumnCommentSQL('unsupported'); + } + + public function testQuoteStringLiteral() : void + { + $c = $this->getStringLiteralQuoteCharacter(); + + self::assertEquals( + $this->getQuotedStringLiteralWithoutQuoteCharacter(), + $this->platform->quoteStringLiteral('No quote') + ); + self::assertEquals( + $this->getQuotedStringLiteralWithQuoteCharacter(), + $this->platform->quoteStringLiteral('It' . $c . 's a quote') + ); + self::assertEquals( + $this->getQuotedStringLiteralQuoteCharacter(), + $this->platform->quoteStringLiteral($c) + ); + } + + /** + * @group DBAL-423 + */ + public function testReturnsGuidTypeDeclarationSQL() : void + { + $this->expectException(DBALException::class); + + $this->platform->getGuidTypeDeclarationSQL([]); + } + + /** + * @group DBAL-1010 + */ + public function testGeneratesAlterTableRenameColumnSQL() : void + { + $table = new Table('foo'); + $table->addColumn( + 'bar', + 'integer', + ['notnull' => true, 'default' => 666, 'comment' => 'rename test'] + ); + + $tableDiff = new TableDiff('foo'); + $tableDiff->fromTable = $table; + $tableDiff->renamedColumns['bar'] = new Column( + 'baz', + Type::getType('integer'), + ['notnull' => true, 'default' => 666, 'comment' => 'rename test'] + ); + + self::assertSame($this->getAlterTableRenameColumnSQL(), $this->platform->getAlterTableSQL($tableDiff)); + } + + /** + * @return string[] + */ + abstract public function getAlterTableRenameColumnSQL() : array; + + /** + * @group DBAL-1016 + */ + public function testQuotesTableIdentifiersInAlterTableSQL() : void + { + $table = new Table('"foo"'); + $table->addColumn('id', 'integer'); + $table->addColumn('fk', 'integer'); + $table->addColumn('fk2', 'integer'); + $table->addColumn('fk3', 'integer'); + $table->addColumn('bar', 'integer'); + $table->addColumn('baz', 'integer'); + $table->addForeignKeyConstraint('fk_table', ['fk'], ['id'], [], 'fk1'); + $table->addForeignKeyConstraint('fk_table', ['fk2'], ['id'], [], 'fk2'); + + $tableDiff = new TableDiff('"foo"'); + $tableDiff->fromTable = $table; + $tableDiff->newName = 'table'; + $tableDiff->addedColumns['bloo'] = new Column('bloo', Type::getType('integer')); + $tableDiff->changedColumns['bar'] = new ColumnDiff( + 'bar', + new Column('bar', Type::getType('integer'), ['notnull' => false]), + ['notnull'], + $table->getColumn('bar') + ); + $tableDiff->renamedColumns['id'] = new Column('war', Type::getType('integer')); + $tableDiff->removedColumns['baz'] = new Column('baz', Type::getType('integer')); + $tableDiff->addedForeignKeys[] = new ForeignKeyConstraint(['fk3'], 'fk_table', ['id'], 'fk_add'); + $tableDiff->changedForeignKeys[] = new ForeignKeyConstraint(['fk2'], 'fk_table2', ['id'], 'fk2'); + $tableDiff->removedForeignKeys[] = new ForeignKeyConstraint(['fk'], 'fk_table', ['id'], 'fk1'); + + self::assertSame( + $this->getQuotesTableIdentifiersInAlterTableSQL(), + $this->platform->getAlterTableSQL($tableDiff) + ); + } + + /** + * @return string[] + */ + abstract protected function getQuotesTableIdentifiersInAlterTableSQL() : array; + + /** + * @group DBAL-1090 + */ + public function testAlterStringToFixedString() : void + { + $table = new Table('mytable'); + $table->addColumn('name', 'string', ['length' => 2]); + + $tableDiff = new TableDiff('mytable'); + $tableDiff->fromTable = $table; + + $tableDiff->changedColumns['name'] = new ColumnDiff( + 'name', + new Column( + 'name', + Type::getType('string'), + ['fixed' => true, 'length' => 2] + ), + ['fixed'] + ); + + $sql = $this->platform->getAlterTableSQL($tableDiff); + + $expectedSql = $this->getAlterStringToFixedStringSQL(); + + self::assertEquals($expectedSql, $sql); + } + + /** + * @return string[] + */ + abstract protected function getAlterStringToFixedStringSQL() : array; + + /** + * @group DBAL-1062 + */ + public function testGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : void + { + $foreignTable = new Table('foreign_table'); + $foreignTable->addColumn('id', 'integer'); + $foreignTable->setPrimaryKey(['id']); + + $primaryTable = new Table('mytable'); + $primaryTable->addColumn('foo', 'integer'); + $primaryTable->addColumn('bar', 'integer'); + $primaryTable->addColumn('baz', 'integer'); + $primaryTable->addIndex(['foo'], 'idx_foo'); + $primaryTable->addIndex(['bar'], 'idx_bar'); + $primaryTable->addForeignKeyConstraint($foreignTable, ['foo'], ['id'], [], 'fk_foo'); + $primaryTable->addForeignKeyConstraint($foreignTable, ['bar'], ['id'], [], 'fk_bar'); + + $tableDiff = new TableDiff('mytable'); + $tableDiff->fromTable = $primaryTable; + $tableDiff->renamedIndexes['idx_foo'] = new Index('idx_foo_renamed', ['foo']); + + self::assertSame( + $this->getGeneratesAlterTableRenameIndexUsedByForeignKeySQL(), + $this->platform->getAlterTableSQL($tableDiff) + ); + } + + /** + * @return string[] + */ + abstract protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array; + + /** + * @param mixed[] $column + * + * @group DBAL-1082 + * @dataProvider getGeneratesDecimalTypeDeclarationSQL + */ + public function testGeneratesDecimalTypeDeclarationSQL(array $column, string $expectedSql) : void + { + self::assertSame($expectedSql, $this->platform->getDecimalTypeDeclarationSQL($column)); + } + + /** + * @return mixed[][] + */ + public static function getGeneratesDecimalTypeDeclarationSQL() : iterable + { + return [ + [[], 'NUMERIC(10, 0)'], + [['unsigned' => true], 'NUMERIC(10, 0)'], + [['unsigned' => false], 'NUMERIC(10, 0)'], + [['precision' => 5], 'NUMERIC(5, 0)'], + [['scale' => 5], 'NUMERIC(10, 5)'], + [['precision' => 8, 'scale' => 2], 'NUMERIC(8, 2)'], + ]; + } + + /** + * @param mixed[] $column + * + * @group DBAL-1082 + * @dataProvider getGeneratesFloatDeclarationSQL + */ + public function testGeneratesFloatDeclarationSQL(array $column, string $expectedSql) : void + { + self::assertSame($expectedSql, $this->platform->getFloatDeclarationSQL($column)); + } + + /** + * @return mixed[][] + */ + public static function getGeneratesFloatDeclarationSQL() : iterable + { + return [ + [[], 'DOUBLE PRECISION'], + [['unsigned' => true], 'DOUBLE PRECISION'], + [['unsigned' => false], 'DOUBLE PRECISION'], + [['precision' => 5], 'DOUBLE PRECISION'], + [['scale' => 5], 'DOUBLE PRECISION'], + [['precision' => 8, 'scale' => 2], 'DOUBLE PRECISION'], + ]; + } + + public function testItEscapesStringsForLike() : void + { + self::assertSame( + '\_25\% off\_ your next purchase \\\\o/', + $this->platform->escapeStringForLike('_25% off_ your next purchase \o/', '\\') + ); + } + + public function testZeroOffsetWithoutLimitIsIgnored() : void + { + $query = 'SELECT * FROM user'; + + self::assertSame( + $query, + $this->platform->modifyLimitQuery($query, null, 0) + ); + } +} diff --git a/tests/Unit/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php b/tests/Unit/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php new file mode 100644 index 0000000..228ffc8 --- /dev/null +++ b/tests/Unit/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php @@ -0,0 +1,1068 @@ + 'CASCADE'] + ); + self::assertEquals( + 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE', + $this->platform->getForeignKeyDeclarationSQL($foreignKey) + ); + + $foreignKey = new ForeignKeyConstraint( + ['foreign_id'], + 'my_table', + ['id'], + 'my_fk', + ['match' => 'full'] + ); + self::assertEquals( + 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) MATCH full NOT DEFERRABLE INITIALLY IMMEDIATE', + $this->platform->getForeignKeyDeclarationSQL($foreignKey) + ); + + $foreignKey = new ForeignKeyConstraint( + ['foreign_id'], + 'my_table', + ['id'], + 'my_fk', + ['deferrable' => true] + ); + self::assertEquals( + 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) DEFERRABLE INITIALLY IMMEDIATE', + $this->platform->getForeignKeyDeclarationSQL($foreignKey) + ); + + $foreignKey = new ForeignKeyConstraint( + ['foreign_id'], + 'my_table', + ['id'], + 'my_fk', + ['deferred' => true] + ); + self::assertEquals( + 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) NOT DEFERRABLE INITIALLY DEFERRED', + $this->platform->getForeignKeyDeclarationSQL($foreignKey) + ); + + $foreignKey = new ForeignKeyConstraint( + ['foreign_id'], + 'my_table', + ['id'], + 'my_fk', + ['feferred' => true] + ); + self::assertEquals( + 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) NOT DEFERRABLE INITIALLY DEFERRED', + $this->platform->getForeignKeyDeclarationSQL($foreignKey) + ); + + $foreignKey = new ForeignKeyConstraint( + ['foreign_id'], + 'my_table', + ['id'], + 'my_fk', + ['deferrable' => true, 'deferred' => true, 'match' => 'full'] + ); + self::assertEquals( + 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) MATCH full DEFERRABLE INITIALLY DEFERRED', + $this->platform->getForeignKeyDeclarationSQL($foreignKey) + ); + } + + public function testGeneratesSqlSnippets() : void + { + self::assertEquals('SIMILAR TO', $this->platform->getRegexpExpression(), 'Regular expression operator is not correct'); + self::assertEquals('"', $this->platform->getIdentifierQuoteCharacter(), 'Identifier quote character is not correct'); + self::assertEquals('column1 || column2 || column3', $this->platform->getConcatExpression('column1', 'column2', 'column3'), 'Concatenation expression is not correct'); + self::assertEquals('SUBSTRING(column FROM 5)', $this->platform->getSubstringExpression('column', 5), 'Substring expression without length is not correct'); + self::assertEquals('SUBSTRING(column FROM 1 FOR 5)', $this->platform->getSubstringExpression('column', 1, 5), 'Substring expression with length is not correct'); + } + + public function testGeneratesTransactionCommands() : void + { + self::assertEquals( + 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', + $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_UNCOMMITTED) + ); + self::assertEquals( + 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED', + $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_COMMITTED) + ); + self::assertEquals( + 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ', + $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::REPEATABLE_READ) + ); + self::assertEquals( + 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE', + $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::SERIALIZABLE) + ); + } + + public function testGeneratesDDLSnippets() : void + { + self::assertEquals('CREATE DATABASE foobar', $this->platform->getCreateDatabaseSQL('foobar')); + self::assertEquals('DROP DATABASE foobar', $this->platform->getDropDatabaseSQL('foobar')); + self::assertEquals('DROP TABLE foobar', $this->platform->getDropTableSQL('foobar')); + } + + public function testGenerateTableWithAutoincrement() : void + { + $table = new Table('autoinc_table'); + $column = $table->addColumn('id', 'integer'); + $column->setAutoincrement(true); + + self::assertEquals(['CREATE TABLE autoinc_table (id SERIAL NOT NULL)'], $this->platform->getCreateTableSQL($table)); + } + + /** + * @return mixed[][] + */ + public static function serialTypes() : iterable + { + return [ + ['integer', 'SERIAL'], + ['bigint', 'BIGSERIAL'], + ]; + } + + /** + * @dataProvider serialTypes + * @group 2906 + */ + public function testGenerateTableWithAutoincrementDoesNotSetDefault(string $type, string $definition) : void + { + $table = new Table('autoinc_table_notnull'); + $column = $table->addColumn('id', $type); + $column->setAutoIncrement(true); + $column->setNotNull(false); + + $sql = $this->platform->getCreateTableSQL($table); + + self::assertEquals([sprintf('CREATE TABLE autoinc_table_notnull (id %s)', $definition)], $sql); + } + + /** + * @dataProvider serialTypes + * @group 2906 + */ + public function testCreateTableWithAutoincrementAndNotNullAddsConstraint(string $type, string $definition) : void + { + $table = new Table('autoinc_table_notnull_enabled'); + $column = $table->addColumn('id', $type); + $column->setAutoIncrement(true); + $column->setNotNull(true); + + $sql = $this->platform->getCreateTableSQL($table); + + self::assertEquals([sprintf('CREATE TABLE autoinc_table_notnull_enabled (id %s NOT NULL)', $definition)], $sql); + } + + /** + * @dataProvider serialTypes + * @group 2906 + */ + public function testGetDefaultValueDeclarationSQLIgnoresTheDefaultKeyWhenTheFieldIsSerial(string $type) : void + { + $sql = $this->platform->getDefaultValueDeclarationSQL( + [ + 'autoincrement' => true, + 'type' => Type::getType($type), + 'default' => 1, + ] + ); + + self::assertSame('', $sql); + } + + public function testGeneratesTypeDeclarationForIntegers() : void + { + self::assertEquals( + 'INT', + $this->platform->getIntegerTypeDeclarationSQL([]) + ); + self::assertEquals( + 'SERIAL', + $this->platform->getIntegerTypeDeclarationSQL(['autoincrement' => true]) + ); + self::assertEquals( + 'SERIAL', + $this->platform->getIntegerTypeDeclarationSQL( + ['autoincrement' => true, 'primary' => true] + ) + ); + } + + public function testGeneratesTypeDeclarationForStrings() : void + { + self::assertEquals( + 'CHAR(10)', + $this->platform->getVarcharTypeDeclarationSQL( + ['length' => 10, 'fixed' => true] + ) + ); + self::assertEquals( + 'VARCHAR(50)', + $this->platform->getVarcharTypeDeclarationSQL(['length' => 50]), + 'Variable string declaration is not correct' + ); + self::assertEquals( + 'VARCHAR(255)', + $this->platform->getVarcharTypeDeclarationSQL([]), + 'Long string declaration is not correct' + ); + } + + public function getGenerateUniqueIndexSql() : string + { + return 'CREATE UNIQUE INDEX index_name ON test (test, test2)'; + } + + public function testGeneratesSequenceSqlCommands() : void + { + $sequence = new Sequence('myseq', 20, 1); + self::assertEquals( + 'CREATE SEQUENCE myseq INCREMENT BY 20 MINVALUE 1 START 1', + $this->platform->getCreateSequenceSQL($sequence) + ); + self::assertEquals( + 'DROP SEQUENCE myseq CASCADE', + $this->platform->getDropSequenceSQL('myseq') + ); + self::assertEquals( + "SELECT NEXTVAL('myseq')", + $this->platform->getSequenceNextValSQL('myseq') + ); + } + + public function testDoesNotPreferIdentityColumns() : void + { + self::assertFalse($this->platform->prefersIdentityColumns()); + } + + public function testPrefersSequences() : void + { + self::assertTrue($this->platform->prefersSequences()); + } + + public function testSupportsIdentityColumns() : void + { + self::assertTrue($this->platform->supportsIdentityColumns()); + } + + public function testSupportsSavePoints() : void + { + self::assertTrue($this->platform->supportsSavepoints()); + } + + public function testSupportsSequences() : void + { + self::assertTrue($this->platform->supportsSequences()); + } + + /** + * {@inheritdoc} + */ + protected function supportsCommentOnStatement() : bool + { + return true; + } + + public function testModifyLimitQuery() : void + { + $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10, 0); + self::assertEquals('SELECT * FROM user LIMIT 10', $sql); + } + + public function testModifyLimitQueryWithEmptyOffset() : void + { + $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10); + self::assertEquals('SELECT * FROM user LIMIT 10', $sql); + } + + /** + * {@inheritDoc} + */ + public function getCreateTableColumnCommentsSQL() : array + { + return [ + 'CREATE TABLE test (id INT NOT NULL, PRIMARY KEY(id))', + "COMMENT ON COLUMN test.id IS 'This is a comment'", + ]; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableColumnCommentsSQL() : array + { + return [ + 'ALTER TABLE mytable ADD quota INT NOT NULL', + "COMMENT ON COLUMN mytable.quota IS 'A comment'", + 'COMMENT ON COLUMN mytable.foo IS NULL', + "COMMENT ON COLUMN mytable.baz IS 'B comment'", + ]; + } + + /** + * {@inheritDoc} + */ + public function getCreateTableColumnTypeCommentsSQL() : array + { + return [ + 'CREATE TABLE test (id INT NOT NULL, data TEXT NOT NULL, PRIMARY KEY(id))', + "COMMENT ON COLUMN test.data IS '(DC2Type:array)'", + ]; + } + + /** + * {@inheritDoc} + */ + protected function getQuotedColumnInPrimaryKeySQL() : array + { + return ['CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, PRIMARY KEY("create"))']; + } + + /** + * {@inheritDoc} + */ + protected function getQuotedColumnInIndexSQL() : array + { + return [ + 'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL)', + 'CREATE INDEX IDX_22660D028FD6E0FB ON "quoted" ("create")', + ]; + } + + /** + * {@inheritDoc} + */ + protected function getQuotedNameInIndexSQL() : array + { + return [ + 'CREATE TABLE test (column1 VARCHAR(255) NOT NULL)', + 'CREATE INDEX "key" ON test (column1)', + ]; + } + + /** + * {@inheritDoc} + */ + protected function getQuotedColumnInForeignKeySQL() : array + { + return [ + 'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, foo VARCHAR(255) NOT NULL, "bar" VARCHAR(255) NOT NULL)', + 'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES "foreign" ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE', + 'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_NON_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES foo ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE', + 'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_INTENDED_QUOTATION FOREIGN KEY ("create", foo, "bar") REFERENCES "foo-bar" ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE', + ]; + } + + /** + * @param string|bool $databaseValue + * + * @group DBAL-457 + * @dataProvider pgBooleanProvider + */ + public function testConvertBooleanAsLiteralStrings( + $databaseValue, + string $preparedStatementValue, + ?int $integerValue, + ?bool $booleanValue + ) : void { + $platform = $this->createPlatform(); + + self::assertEquals($preparedStatementValue, $platform->convertBooleans($databaseValue)); + } + + /** + * @group DBAL-457 + */ + public function testConvertBooleanAsLiteralIntegers() : void + { + $platform = $this->createPlatform(); + $platform->setUseBooleanTrueFalseStrings(false); + + self::assertEquals(1, $platform->convertBooleans(true)); + self::assertEquals(1, $platform->convertBooleans('1')); + + self::assertEquals(0, $platform->convertBooleans(false)); + self::assertEquals(0, $platform->convertBooleans('0')); + } + + /** + * @param string|bool $databaseValue + * + * @group DBAL-630 + * @dataProvider pgBooleanProvider + */ + public function testConvertBooleanAsDatabaseValueStrings( + $databaseValue, + string $preparedStatementValue, + ?int $integerValue, + ?bool $booleanValue + ) : void { + $platform = $this->createPlatform(); + + self::assertSame($integerValue, $platform->convertBooleansToDatabaseValue($booleanValue)); + } + + /** + * @group DBAL-630 + */ + public function testConvertBooleanAsDatabaseValueIntegers() : void + { + $platform = $this->createPlatform(); + $platform->setUseBooleanTrueFalseStrings(false); + + self::assertSame(1, $platform->convertBooleansToDatabaseValue(true)); + self::assertSame(0, $platform->convertBooleansToDatabaseValue(false)); + } + + /** + * @param string|bool $databaseValue + * + * @dataProvider pgBooleanProvider + */ + public function testConvertFromBoolean($databaseValue, string $prepareStatementValue, ?int $integerValue, ?bool $booleanValue) : void + { + $platform = $this->createPlatform(); + + self::assertSame($booleanValue, $platform->convertFromBoolean($databaseValue)); + } + + public function testThrowsExceptionWithInvalidBooleanLiteral() : void + { + $platform = $this->createPlatform(); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage("Unrecognized boolean literal 'my-bool'"); + + $platform->convertBooleansToDatabaseValue('my-bool'); + } + + public function testGetCreateSchemaSQL() : void + { + $schemaName = 'schema'; + $sql = $this->platform->getCreateSchemaSQL($schemaName); + self::assertEquals('CREATE SCHEMA ' . $schemaName, $sql); + } + + public function testAlterDecimalPrecisionScale() : void + { + $table = new Table('mytable'); + $table->addColumn('dfoo1', 'decimal'); + $table->addColumn('dfoo2', 'decimal', ['precision' => 10, 'scale' => 6]); + $table->addColumn('dfoo3', 'decimal', ['precision' => 10, 'scale' => 6]); + $table->addColumn('dfoo4', 'decimal', ['precision' => 10, 'scale' => 6]); + + $tableDiff = new TableDiff('mytable'); + $tableDiff->fromTable = $table; + + $tableDiff->changedColumns['dloo1'] = new ColumnDiff( + 'dloo1', + new Column( + 'dloo1', + Type::getType('decimal'), + ['precision' => 16, 'scale' => 6] + ), + ['precision'] + ); + $tableDiff->changedColumns['dloo2'] = new ColumnDiff( + 'dloo2', + new Column( + 'dloo2', + Type::getType('decimal'), + ['precision' => 10, 'scale' => 4] + ), + ['scale'] + ); + $tableDiff->changedColumns['dloo3'] = new ColumnDiff( + 'dloo3', + new Column( + 'dloo3', + Type::getType('decimal'), + ['precision' => 10, 'scale' => 6] + ), + [] + ); + $tableDiff->changedColumns['dloo4'] = new ColumnDiff( + 'dloo4', + new Column( + 'dloo4', + Type::getType('decimal'), + ['precision' => 16, 'scale' => 8] + ), + ['precision', 'scale'] + ); + + $sql = $this->platform->getAlterTableSQL($tableDiff); + + $expectedSql = [ + 'ALTER TABLE mytable ALTER dloo1 TYPE NUMERIC(16, 6)', + 'ALTER TABLE mytable ALTER dloo2 TYPE NUMERIC(10, 4)', + 'ALTER TABLE mytable ALTER dloo4 TYPE NUMERIC(16, 8)', + ]; + + self::assertEquals($expectedSql, $sql); + } + + /** + * @group DBAL-365 + */ + public function testDroppingConstraintsBeforeColumns() : void + { + $newTable = new Table('mytable'); + $newTable->addColumn('id', 'integer'); + $newTable->setPrimaryKey(['id']); + + $oldTable = clone $newTable; + $oldTable->addColumn('parent_id', 'integer'); + $oldTable->addUnnamedForeignKeyConstraint('mytable', ['parent_id'], ['id']); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($oldTable, $newTable); + + $sql = $this->platform->getAlterTableSQL($tableDiff); + + $expectedSql = [ + 'ALTER TABLE mytable DROP CONSTRAINT FK_6B2BD609727ACA70', + 'DROP INDEX IDX_6B2BD609727ACA70', + 'ALTER TABLE mytable DROP parent_id', + ]; + + self::assertEquals($expectedSql, $sql); + } + + /** + * @group DBAL-563 + */ + public function testUsesSequenceEmulatedIdentityColumns() : void + { + self::assertTrue($this->platform->usesSequenceEmulatedIdentityColumns()); + } + + /** + * @group DBAL-563 + */ + public function testReturnsIdentitySequenceName() : void + { + self::assertSame('mytable_mycolumn_seq', $this->platform->getIdentitySequenceName('mytable', 'mycolumn')); + } + + /** + * @dataProvider dataCreateSequenceWithCache + * @group DBAL-139 + */ + public function testCreateSequenceWithCache(int $cacheSize, string $expectedSql) : void + { + $sequence = new Sequence('foo', 1, 1, $cacheSize); + self::assertStringContainsString($expectedSql, $this->platform->getCreateSequenceSQL($sequence)); + } + + /** + * @return mixed[][] + */ + public static function dataCreateSequenceWithCache() : iterable + { + return [ + [3, 'CACHE 3'], + ]; + } + + protected function getBinaryDefaultLength() : int + { + return 0; + } + + protected function getBinaryMaxLength() : int + { + return 0; + } + + public function testReturnsBinaryTypeDeclarationSQL() : void + { + self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL([])); + self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['length' => 0])); + self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['length' => 9999999])); + + self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true])); + self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 0])); + self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 9999999])); + } + + public function testDoesNotPropagateUnnecessaryTableAlterationOnBinaryType() : void + { + $table1 = new Table('mytable'); + $table1->addColumn('column_varbinary', 'binary'); + $table1->addColumn('column_binary', 'binary', ['fixed' => true]); + $table1->addColumn('column_blob', 'blob'); + + $table2 = new Table('mytable'); + $table2->addColumn('column_varbinary', 'binary', ['fixed' => true]); + $table2->addColumn('column_binary', 'binary'); + $table2->addColumn('column_blob', 'binary'); + + $comparator = new Comparator(); + + // VARBINARY -> BINARY + // BINARY -> VARBINARY + // BLOB -> VARBINARY + self::assertEmpty($this->platform->getAlterTableSQL($comparator->diffTable($table1, $table2))); + + $table2 = new Table('mytable'); + $table2->addColumn('column_varbinary', 'binary', ['length' => 42]); + $table2->addColumn('column_binary', 'blob'); + $table2->addColumn('column_blob', 'binary', ['length' => 11, 'fixed' => true]); + + // VARBINARY -> VARBINARY with changed length + // BINARY -> BLOB + // BLOB -> BINARY + self::assertEmpty($this->platform->getAlterTableSQL($comparator->diffTable($table1, $table2))); + + $table2 = new Table('mytable'); + $table2->addColumn('column_varbinary', 'blob'); + $table2->addColumn('column_binary', 'binary', ['length' => 42, 'fixed' => true]); + $table2->addColumn('column_blob', 'blob'); + + // VARBINARY -> BLOB + // BINARY -> BINARY with changed length + // BLOB -> BLOB + self::assertEmpty($this->platform->getAlterTableSQL($comparator->diffTable($table1, $table2))); + } + + /** + * {@inheritDoc} + * + * @group DBAL-234 + */ + protected function getAlterTableRenameIndexSQL() : array + { + return ['ALTER INDEX idx_foo RENAME TO idx_bar']; + } + + /** + * {@inheritDoc} + * + * @group DBAL-234 + */ + protected function getQuotedAlterTableRenameIndexSQL() : array + { + return [ + 'ALTER INDEX "create" RENAME TO "select"', + 'ALTER INDEX "foo" RENAME TO "bar"', + ]; + } + + /** + * PostgreSQL boolean strings provider + * + * @return mixed[][] + */ + public static function pgBooleanProvider() : iterable + { + return [ + // Database value, prepared statement value, boolean integer value, boolean value. + [true, 'true', 1, true], + ['t', 'true', 1, true], + ['true', 'true', 1, true], + ['y', 'true', 1, true], + ['yes', 'true', 1, true], + ['on', 'true', 1, true], + ['1', 'true', 1, true], + + [false, 'false', 0, false], + ['f', 'false', 0, false], + ['false', 'false', 0, false], + [ 'n', 'false', 0, false], + ['no', 'false', 0, false], + ['off', 'false', 0, false], + ['0', 'false', 0, false], + + [null, 'NULL', null, null], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getQuotedAlterTableRenameColumnSQL() : array + { + return [ + 'ALTER TABLE mytable RENAME COLUMN unquoted1 TO unquoted', + 'ALTER TABLE mytable RENAME COLUMN unquoted2 TO "where"', + 'ALTER TABLE mytable RENAME COLUMN unquoted3 TO "foo"', + 'ALTER TABLE mytable RENAME COLUMN "create" TO reserved_keyword', + 'ALTER TABLE mytable RENAME COLUMN "table" TO "from"', + 'ALTER TABLE mytable RENAME COLUMN "select" TO "bar"', + 'ALTER TABLE mytable RENAME COLUMN quoted1 TO quoted', + 'ALTER TABLE mytable RENAME COLUMN quoted2 TO "and"', + 'ALTER TABLE mytable RENAME COLUMN quoted3 TO "baz"', + ]; + } + + /** + * {@inheritdoc} + */ + protected function getQuotedAlterTableChangeColumnLengthSQL() : array + { + return [ + 'ALTER TABLE mytable ALTER unquoted1 TYPE VARCHAR(255)', + 'ALTER TABLE mytable ALTER unquoted2 TYPE VARCHAR(255)', + 'ALTER TABLE mytable ALTER unquoted3 TYPE VARCHAR(255)', + 'ALTER TABLE mytable ALTER "create" TYPE VARCHAR(255)', + 'ALTER TABLE mytable ALTER "table" TYPE VARCHAR(255)', + 'ALTER TABLE mytable ALTER "select" TYPE VARCHAR(255)', + ]; + } + + /** + * {@inheritDoc} + * + * @group DBAL-807 + */ + protected function getAlterTableRenameIndexInSchemaSQL() : array + { + return ['ALTER INDEX myschema.idx_foo RENAME TO idx_bar']; + } + + /** + * {@inheritDoc} + * + * @group DBAL-807 + */ + protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array + { + return [ + 'ALTER INDEX "schema"."create" RENAME TO "select"', + 'ALTER INDEX "schema"."foo" RENAME TO "bar"', + ]; + } + + protected function getQuotesDropForeignKeySQL() : string + { + return 'ALTER TABLE "table" DROP CONSTRAINT "select"'; + } + + public function testGetNullCommentOnColumnSQL() : void + { + self::assertEquals( + 'COMMENT ON COLUMN mytable.id IS NULL', + $this->platform->getCommentOnColumnSQL('mytable', 'id', null) + ); + } + + /** + * @group DBAL-423 + */ + public function testReturnsGuidTypeDeclarationSQL() : void + { + self::assertSame('UUID', $this->platform->getGuidTypeDeclarationSQL([])); + } + + /** + * {@inheritdoc} + */ + public function getAlterTableRenameColumnSQL() : array + { + return ['ALTER TABLE foo RENAME COLUMN bar TO baz']; + } + + /** + * {@inheritdoc} + */ + protected function getQuotesTableIdentifiersInAlterTableSQL() : array + { + return [ + 'ALTER TABLE "foo" DROP CONSTRAINT fk1', + 'ALTER TABLE "foo" DROP CONSTRAINT fk2', + 'ALTER TABLE "foo" ADD bloo INT NOT NULL', + 'ALTER TABLE "foo" DROP baz', + 'ALTER TABLE "foo" ALTER bar DROP NOT NULL', + 'ALTER TABLE "foo" RENAME COLUMN id TO war', + 'ALTER TABLE "foo" RENAME TO "table"', + 'ALTER TABLE "table" ADD CONSTRAINT fk_add FOREIGN KEY (fk3) REFERENCES fk_table (id) NOT DEFERRABLE ' . + 'INITIALLY IMMEDIATE', + 'ALTER TABLE "table" ADD CONSTRAINT fk2 FOREIGN KEY (fk2) REFERENCES fk_table2 (id) NOT DEFERRABLE ' . + 'INITIALLY IMMEDIATE', + ]; + } + + /** + * {@inheritdoc} + */ + protected function getCommentOnColumnSQL() : array + { + return [ + 'COMMENT ON COLUMN foo.bar IS \'comment\'', + 'COMMENT ON COLUMN "Foo"."BAR" IS \'comment\'', + 'COMMENT ON COLUMN "select"."from" IS \'comment\'', + ]; + } + + /** + * @group DBAL-1004 + */ + public function testAltersTableColumnCommentWithExplicitlyQuotedIdentifiers() : void + { + $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'))]); + $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'), ['comment' => 'baz'])]); + + $comparator = new Comparator(); + + $tableDiff = $comparator->diffTable($table1, $table2); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertSame( + ['COMMENT ON COLUMN "foo"."bar" IS \'baz\''], + $this->platform->getAlterTableSQL($tableDiff) + ); + } + + /** + * @group 3158 + */ + public function testAltersTableColumnCommentIfRequiredByType() : void + { + $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('datetime'))]); + $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('datetime_immutable'))]); + + $comparator = new Comparator(); + + $tableDiff = $comparator->diffTable($table1, $table2); + + $this->assertInstanceOf('Doctrine\DBAL\Schema\TableDiff', $tableDiff); + $this->assertSame( + [ + 'ALTER TABLE "foo" ALTER "bar" TYPE TIMESTAMP(0) WITHOUT TIME ZONE', + 'ALTER TABLE "foo" ALTER "bar" DROP DEFAULT', + 'COMMENT ON COLUMN "foo"."bar" IS \'(DC2Type:datetime_immutable)\'', + ], + $this->platform->getAlterTableSQL($tableDiff) + ); + } + + /** + * {@inheritdoc} + */ + protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string + { + return 'CONSTRAINT "select" UNIQUE (foo)'; + } + + /** + * {@inheritdoc} + */ + protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string + { + return 'INDEX "select" (foo)'; + } + + /** + * {@inheritdoc} + */ + protected function getQuotesReservedKeywordInTruncateTableSQL() : string + { + return 'TRUNCATE "select"'; + } + + /** + * {@inheritdoc} + */ + protected function getAlterStringToFixedStringSQL() : array + { + return ['ALTER TABLE mytable ALTER name TYPE CHAR(2)']; + } + + /** + * {@inheritdoc} + */ + protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array + { + return ['ALTER INDEX idx_foo RENAME TO idx_foo_renamed']; + } + + /** + * @group DBAL-1142 + */ + public function testInitializesTsvectorTypeMapping() : void + { + self::assertTrue($this->platform->hasDoctrineTypeMappingFor('tsvector')); + self::assertEquals('text', $this->platform->getDoctrineTypeMapping('tsvector')); + } + + /** + * @group DBAL-1220 + */ + public function testReturnsDisallowDatabaseConnectionsSQL() : void + { + self::assertSame( + "UPDATE pg_database SET datallowconn = 'false' WHERE datname = 'foo'", + $this->platform->getDisallowDatabaseConnectionsSQL('foo') + ); + } + + /** + * @group DBAL-1220 + */ + public function testReturnsCloseActiveDatabaseConnectionsSQL() : void + { + self::assertSame( + "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = 'foo'", + $this->platform->getCloseActiveDatabaseConnectionsSQL('foo') + ); + } + + /** + * @group DBAL-2436 + */ + public function testQuotesTableNameInListTableForeignKeysSQL() : void + { + self::assertStringContainsStringIgnoringCase( + "'Foo''Bar\\'", + $this->platform->getListTableForeignKeysSQL("Foo'Bar\\") + ); + } + + /** + * @group DBAL-2436 + */ + public function testQuotesSchemaNameInListTableForeignKeysSQL() : void + { + self::assertStringContainsStringIgnoringCase( + "'Foo''Bar\\'", + $this->platform->getListTableForeignKeysSQL("Foo'Bar\\.baz_table") + ); + } + + /** + * @group DBAL-2436 + */ + public function testQuotesTableNameInListTableConstraintsSQL() : void + { + self::assertStringContainsStringIgnoringCase( + "'Foo''Bar\\'", + $this->platform->getListTableConstraintsSQL("Foo'Bar\\") + ); + } + + /** + * @group DBAL-2436 + */ + public function testQuotesTableNameInListTableIndexesSQL() : void + { + self::assertStringContainsStringIgnoringCase( + "'Foo''Bar\\'", + $this->platform->getListTableIndexesSQL("Foo'Bar\\") + ); + } + + /** + * @group DBAL-2436 + */ + public function testQuotesSchemaNameInListTableIndexesSQL() : void + { + self::assertStringContainsStringIgnoringCase( + "'Foo''Bar\\'", + $this->platform->getListTableIndexesSQL("Foo'Bar\\.baz_table") + ); + } + + /** + * @group DBAL-2436 + */ + public function testQuotesTableNameInListTableColumnsSQL() : void + { + self::assertStringContainsStringIgnoringCase( + "'Foo''Bar\\'", + $this->platform->getListTableColumnsSQL("Foo'Bar\\") + ); + } + + /** + * @group DBAL-2436 + */ + public function testQuotesSchemaNameInListTableColumnsSQL() : void + { + self::assertStringContainsStringIgnoringCase( + "'Foo''Bar\\'", + $this->platform->getListTableColumnsSQL("Foo'Bar\\.baz_table") + ); + } + + /** + * @group DBAL-2436 + */ + public function testQuotesDatabaseNameInCloseActiveDatabaseConnectionsSQL() : void + { + self::assertStringContainsStringIgnoringCase( + "'Foo''Bar\\'", + $this->platform->getCloseActiveDatabaseConnectionsSQL("Foo'Bar\\") + ); + } +} diff --git a/tests/Unit/DBAL/Platforms/PostgreSQL100PlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSQL100PlatformTest.php new file mode 100644 index 0000000..79ebe59 --- /dev/null +++ b/tests/Unit/DBAL/Platforms/PostgreSQL100PlatformTest.php @@ -0,0 +1,34 @@ +platform->getListSequencesSQL('test_db') + ); + } +} diff --git a/tests/Unit/DBAL/Platforms/PostgreSQL91PlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSQL91PlatformTest.php new file mode 100644 index 0000000..deaecd6 --- /dev/null +++ b/tests/Unit/DBAL/Platforms/PostgreSQL91PlatformTest.php @@ -0,0 +1,41 @@ +platform->supportsColumnCollation()); + } + + public function testColumnCollationDeclarationSQL() : void + { + self::assertSame( + 'COLLATE "en_US.UTF-8"', + $this->platform->getColumnCollationDeclarationSQL('en_US.UTF-8') + ); + } + + public function testGetCreateTableSQLWithColumnCollation() : void + { + $table = new Table('foo'); + $table->addColumn('no_collation', 'string'); + $table->addColumn('column_collation', 'string')->setPlatformOption('collation', 'en_US.UTF-8'); + + self::assertSame( + ['CREATE TABLE foo (no_collation VARCHAR(255) NOT NULL, column_collation VARCHAR(255) NOT NULL COLLATE "en_US.UTF-8")'], + $this->platform->getCreateTableSQL($table), + 'Column "no_collation" will use the default collation from the table/database and "column_collation" overwrites the collation on this column' + ); + } +} diff --git a/tests/Unit/DBAL/Platforms/PostgreSQL92PlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSQL92PlatformTest.php new file mode 100644 index 0000000..e987053 --- /dev/null +++ b/tests/Unit/DBAL/Platforms/PostgreSQL92PlatformTest.php @@ -0,0 +1,72 @@ +platform->hasNativeJsonType()); + } + + /** + * @group DBAL-553 + */ + public function testReturnsJsonTypeDeclarationSQL() : void + { + self::assertSame('JSON', $this->platform->getJsonTypeDeclarationSQL([])); + } + + public function testReturnsSmallIntTypeDeclarationSQL() : void + { + self::assertSame( + 'SMALLSERIAL', + $this->platform->getSmallIntTypeDeclarationSQL(['autoincrement' => true]) + ); + + self::assertSame( + 'SMALLINT', + $this->platform->getSmallIntTypeDeclarationSQL(['autoincrement' => false]) + ); + + self::assertSame( + 'SMALLINT', + $this->platform->getSmallIntTypeDeclarationSQL([]) + ); + } + + /** + * @group DBAL-553 + */ + public function testInitializesJsonTypeMapping() : void + { + self::assertTrue($this->platform->hasDoctrineTypeMappingFor('json')); + self::assertEquals(Types::JSON, $this->platform->getDoctrineTypeMapping('json')); + } + + /** + * @group DBAL-1220 + */ + public function testReturnsCloseActiveDatabaseConnectionsSQL() : void + { + self::assertSame( + "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'foo'", + $this->platform->getCloseActiveDatabaseConnectionsSQL('foo') + ); + } +} diff --git a/tests/Unit/DBAL/Platforms/PostgreSQL94PlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSQL94PlatformTest.php new file mode 100644 index 0000000..1a843ce --- /dev/null +++ b/tests/Unit/DBAL/Platforms/PostgreSQL94PlatformTest.php @@ -0,0 +1,32 @@ +platform->getJsonTypeDeclarationSQL(['jsonb' => false])); + self::assertSame('JSONB', $this->platform->getJsonTypeDeclarationSQL(['jsonb' => true])); + } + + public function testInitializesJsonTypeMapping() : void + { + parent::testInitializesJsonTypeMapping(); + self::assertTrue($this->platform->hasDoctrineTypeMappingFor('jsonb')); + self::assertEquals(Types::JSON, $this->platform->getDoctrineTypeMapping('jsonb')); + } +} diff --git a/tests/Unit/DBAL/Platforms/PostgreSqlPlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSqlPlatformTest.php new file mode 100644 index 0000000..1111dda --- /dev/null +++ b/tests/Unit/DBAL/Platforms/PostgreSqlPlatformTest.php @@ -0,0 +1,19 @@ +platform->supportsPartialIndexes()); + } +} diff --git a/tests/Unit/DBAL/Platforms/ReservedKeywordsValidatorTest.php b/tests/Unit/DBAL/Platforms/ReservedKeywordsValidatorTest.php new file mode 100644 index 0000000..0976ce0 --- /dev/null +++ b/tests/Unit/DBAL/Platforms/ReservedKeywordsValidatorTest.php @@ -0,0 +1,43 @@ +validator = new ReservedKeywordsValidator([new MySQLKeywords()]); + } + + public function testReservedTableName() : void + { + $table = new Table('TABLE'); + $this->validator->acceptTable($table); + + self::assertEquals( + ['Table TABLE keyword violations: MySQL'], + $this->validator->getViolations() + ); + } + + public function testReservedColumnName() : void + { + $table = new Table('TABLE'); + $column = $table->addColumn('table', 'string'); + + $this->validator->acceptColumn($table, $column); + + self::assertEquals( + ['Table TABLE column table keyword violations: MySQL'], + $this->validator->getViolations() + ); + } +} diff --git a/tests/Unit/DBAL/SQLParserUtilsTest.php b/tests/Unit/DBAL/SQLParserUtilsTest.php new file mode 100644 index 0000000..8265bc3 --- /dev/null +++ b/tests/Unit/DBAL/SQLParserUtilsTest.php @@ -0,0 +1,498 @@ + 42, 0 => 30]], // explicit keys + + // named + ['SELECT :foo FROM :bar', false, [7 => 'foo', 17 => 'bar']], + ['SELECT * FROM Foo WHERE bar IN (:name1, :name2)', false, [32 => 'name1', 40 => 'name2']], + ['SELECT ":foo" FROM Foo WHERE bar IN (:name1, :name2)', false, [37 => 'name1', 45 => 'name2']], + ["SELECT ':foo' FROM Foo WHERE bar IN (:name1, :name2)", false, [37 => 'name1', 45 => 'name2']], + ['SELECT :foo_id', false, [7 => 'foo_id']], // Ticket DBAL-231 + ['SELECT @rank := 1', false, []], // Ticket DBAL-398 + ['SELECT @rank := 1 AS rank, :foo AS foo FROM :bar', false, [27 => 'foo', 44 => 'bar']], // Ticket DBAL-398 + ['SELECT * FROM Foo WHERE bar > :start_date AND baz > :start_date', false, [30 => 'start_date', 52 => 'start_date']], // Ticket GH-113 + ['SELECT foo::date as date FROM Foo WHERE bar > :start_date AND baz > :start_date', false, [46 => 'start_date', 68 => 'start_date']], // Ticket GH-259 + ['SELECT `d.ns:col_name` FROM my_table d WHERE `d.date` >= :param1', false, [57 => 'param1']], // Ticket DBAL-552 + ['SELECT [d.ns:col_name] FROM my_table d WHERE [d.date] >= :param1', false, [57 => 'param1']], // Ticket DBAL-552 + ['SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, ARRAY[:foo])', false, [56 => 'foo']], // Ticket GH-2295 + ['SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, array[:foo])', false, [56 => 'foo']], + ['SELECT table.field1, ARRAY[\'3\'] FROM schema.table table WHERE table.f1 = :foo AND ARRAY[\'3\']', false, [73 => 'foo']], + ['SELECT table.field1, ARRAY[\'3\']::integer[] FROM schema.table table WHERE table.f1 = :foo AND ARRAY[\'3\']::integer[]', false, [84 => 'foo']], + ['SELECT table.field1, ARRAY[:foo] FROM schema.table table WHERE table.f1 = :bar AND ARRAY[\'3\']', false, [27 => 'foo', 74 => 'bar']], + ['SELECT table.field1, ARRAY[:foo]::integer[] FROM schema.table table WHERE table.f1 = :bar AND ARRAY[\'3\']::integer[]', false, [27 => 'foo', 85 => 'bar']], + [ + <<<'SQLDATA' +SELECT * FROM foo WHERE +bar = ':not_a_param1 ''":not_a_param2"''' +OR bar=:a_param1 +OR bar=:a_param2||':not_a_param3' +OR bar=':not_a_param4 '':not_a_param5'' :not_a_param6' +OR bar='' +OR bar=:a_param3 +SQLDATA + , + false, + [ + 74 => 'a_param1', + 91 => 'a_param2', + 190 => 'a_param3', + ], + ], + ["SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE '\\\\') AND (data.description LIKE :condition_1 ESCAPE '\\\\') ORDER BY id ASC", false, [121 => 'condition_0', 174 => 'condition_1']], + ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE "\\\\") AND (data.description LIKE :condition_1 ESCAPE "\\\\") ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']], + ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE "\\\\") AND (data.description LIKE :condition_1 ESCAPE \'\\\\\') ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']], + ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE `\\\\`) AND (data.description LIKE :condition_1 ESCAPE `\\\\`) ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']], + ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE \'\\\\\') AND (data.description LIKE :condition_1 ESCAPE `\\\\`) ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']], + + ]; + } + + /** + * @param int[] $expectedParamPos + * + * @dataProvider dataGetPlaceholderPositions + */ + public function testGetPlaceholderPositions(string $query, bool $isPositional, array $expectedParamPos) : void + { + $actualParamPos = SQLParserUtils::getPlaceholderPositions($query, $isPositional); + self::assertEquals($expectedParamPos, $actualParamPos); + } + + /** + * @return mixed[][] + */ + public static function dataExpandListParameters() : iterable + { + return [ + // Positional: Very simple with one needle + [ + 'SELECT * FROM Foo WHERE foo IN (?)', + [[1, 2, 3]], + [Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?)', + [1, 2, 3], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ], + // Positional: One non-list before d one after list-needle + [ + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?)', + ['string', [1, 2, 3]], + [ParameterType::STRING, Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)', + ['string', 1, 2, 3], + [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ], + // Positional: One non-list after list-needle + [ + 'SELECT * FROM Foo WHERE bar IN (?) AND baz = ?', + [[1, 2, 3], 'foo'], + [Connection::PARAM_INT_ARRAY, ParameterType::STRING], + 'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?', + [1, 2, 3, 'foo'], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], + ], + // Positional: One non-list before and one after list-needle + [ + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?) AND baz = ?', + [1, [1, 2, 3], 4], + [ParameterType::INTEGER, Connection::PARAM_INT_ARRAY, ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?', + [1, 1, 2, 3, 4], + [ + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ], + ], + // Positional: Two lists + [ + 'SELECT * FROM Foo WHERE foo IN (?, ?)', + [[1, 2, 3], [4, 5]], + [Connection::PARAM_INT_ARRAY, Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)', + [1, 2, 3, 4, 5], + [ + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ], + ], + // Positional: Empty "integer" array DDC-1978 + [ + 'SELECT * FROM Foo WHERE foo IN (?)', + [[]], + [Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (NULL)', + [], + [], + ], + // Positional: Empty "str" array DDC-1978 + [ + 'SELECT * FROM Foo WHERE foo IN (?)', + [[]], + [Connection::PARAM_STR_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (NULL)', + [], + [], + ], + // Positional: explicit keys for params and types + [ + 'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?', + [1 => 'bar', 2 => 'baz', 0 => 1], + [2 => ParameterType::STRING, 1 => ParameterType::STRING], + 'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?', + [1 => 'bar', 0 => 1, 2 => 'baz'], + [1 => ParameterType::STRING, 2 => ParameterType::STRING], + ], + // Positional: explicit keys for array params and array types + [ + 'SELECT * FROM Foo WHERE foo IN (?) AND bar IN (?) AND baz = ?', + [1 => ['bar1', 'bar2'], 2 => true, 0 => [1, 2, 3]], + [2 => ParameterType::BOOLEAN, 1 => Connection::PARAM_STR_ARRAY, 0 => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND bar IN (?, ?) AND baz = ?', + [1, 2, 3, 'bar1', 'bar2', true], + [ + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::STRING, + ParameterType::STRING, + ParameterType::BOOLEAN, + ], + ], + // Positional starts from 1: One non-list before and one after list-needle + [ + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?) AND baz = ? AND foo IN (?)', + [1 => 1, 2 => [1, 2, 3], 3 => 4, 4 => [5, 6]], + [ + 1 => ParameterType::INTEGER, + 2 => Connection::PARAM_INT_ARRAY, + 3 => ParameterType::INTEGER, + 4 => Connection::PARAM_INT_ARRAY, + ], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ? AND foo IN (?, ?)', + [1, 1, 2, 3, 4, 5, 6], + [ + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ], + ], + // Named parameters : Very simple with param int + [ + 'SELECT * FROM Foo WHERE foo = :foo', + ['foo' => 1], + ['foo' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ?', + [1], + [ParameterType::INTEGER], + ], + + // Named parameters : Very simple with param int and string + [ + 'SELECT * FROM Foo WHERE foo = :foo AND bar = :bar', + ['bar' => 'Some String','foo' => 1], + ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::STRING], + 'SELECT * FROM Foo WHERE foo = ? AND bar = ?', + [1,'Some String'], + [ParameterType::INTEGER, ParameterType::STRING], + ], + // Named parameters : Very simple with one needle + [ + 'SELECT * FROM Foo WHERE foo IN (:foo)', + ['foo' => [1, 2, 3]], + ['foo' => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?)', + [1, 2, 3], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ], + // Named parameters: One non-list before d one after list-needle + [ + 'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar)', + ['foo' => 'string', 'bar' => [1, 2, 3]], + ['foo' => ParameterType::STRING, 'bar' => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)', + ['string', 1, 2, 3], + [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ], + // Named parameters: One non-list after list-needle + [ + 'SELECT * FROM Foo WHERE bar IN (:bar) AND baz = :baz', + ['bar' => [1, 2, 3], 'baz' => 'foo'], + ['bar' => Connection::PARAM_INT_ARRAY, 'baz' => ParameterType::STRING], + 'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?', + [1, 2, 3, 'foo'], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], + ], + // Named parameters: One non-list before and one after list-needle + [ + 'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar) AND baz = :baz', + ['bar' => [1, 2, 3],'foo' => 1, 'baz' => 4], + ['bar' => Connection::PARAM_INT_ARRAY, 'foo' => ParameterType::INTEGER, 'baz' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?', + [1, 1, 2, 3, 4], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ], + // Named parameters: Two lists + [ + 'SELECT * FROM Foo WHERE foo IN (:a, :b)', + ['b' => [4, 5],'a' => [1, 2, 3]], + ['a' => Connection::PARAM_INT_ARRAY, 'b' => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)', + [1, 2, 3, 4, 5], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ], + // Named parameters : With the same name arg type string + [ + 'SELECT * FROM Foo WHERE foo <> :arg AND bar = :arg', + ['arg' => 'Some String'], + ['arg' => ParameterType::STRING], + 'SELECT * FROM Foo WHERE foo <> ? AND bar = ?', + ['Some String','Some String'], + [ParameterType::STRING,ParameterType::STRING], + ], + // Named parameters : With the same name arg + [ + 'SELECT * FROM Foo WHERE foo IN (:arg) AND NOT bar IN (:arg)', + ['arg' => [1, 2, 3]], + ['arg' => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND NOT bar IN (?, ?, ?)', + [1, 2, 3, 1, 2, 3], + [ParameterType::INTEGER,ParameterType::INTEGER, ParameterType::INTEGER,ParameterType::INTEGER,ParameterType::INTEGER, ParameterType::INTEGER], + ], + + // Named parameters : Same name, other name in between DBAL-299 + [ + 'SELECT * FROM Foo WHERE (:foo = 2) AND (:bar = 3) AND (:foo = 2)', + ['foo' => 2,'bar' => 3], + ['foo' => ParameterType::INTEGER,'bar' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE (? = 2) AND (? = 3) AND (? = 2)', + [2, 3, 2], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ], + // Named parameters : Empty "integer" array DDC-1978 + [ + 'SELECT * FROM Foo WHERE foo IN (:foo)', + ['foo' => []], + ['foo' => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (NULL)', + [], + [], + ], + // Named parameters : Two empty "str" array DDC-1978 + [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)', + ['foo' => [], 'bar' => []], + ['foo' => Connection::PARAM_STR_ARRAY, 'bar' => Connection::PARAM_STR_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (NULL) OR bar IN (NULL)', + [], + [], + ], + [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar OR baz = :baz', + ['foo' => [1, 2], 'bar' => 'bar', 'baz' => 'baz'], + ['foo' => Connection::PARAM_INT_ARRAY, 'baz' => 'string'], + 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ? OR baz = ?', + [1, 2, 'bar', 'baz'], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING, 'string'], + ], + [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar', + ['foo' => [1, 2], 'bar' => 'bar'], + ['foo' => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?', + [1, 2, 'bar'], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], + ], + // Params/types with colons + [ + 'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar', + [':foo' => 'foo', ':bar' => 'bar'], + [':foo' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? OR bar = ?', + ['foo', 'bar'], + [ParameterType::INTEGER, ParameterType::STRING], + ], + [ + 'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar', + [':foo' => 'foo', ':bar' => 'bar'], + [':foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? OR bar = ?', + ['foo', 'bar'], + [ParameterType::INTEGER, ParameterType::INTEGER], + ], + [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar', + [':foo' => [1, 2], ':bar' => 'bar'], + ['foo' => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?', + [1, 2, 'bar'], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], + ], + [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar', + ['foo' => [1, 2], 'bar' => 'bar'], + [':foo' => Connection::PARAM_INT_ARRAY], + 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?', + [1, 2, 'bar'], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], + ], + // DBAL-522 - null valued parameters are not considered + [ + 'INSERT INTO Foo (foo, bar) values (:foo, :bar)', + ['foo' => 1, 'bar' => null], + [':foo' => ParameterType::INTEGER, ':bar' => ParameterType::NULL], + 'INSERT INTO Foo (foo, bar) values (?, ?)', + [1, null], + [ParameterType::INTEGER, ParameterType::NULL], + ], + [ + 'INSERT INTO Foo (foo, bar) values (?, ?)', + [1, null], + [ParameterType::INTEGER, ParameterType::NULL], + 'INSERT INTO Foo (foo, bar) values (?, ?)', + [1, null], + [ParameterType::INTEGER, ParameterType::NULL], + ], + // DBAL-1205 - Escaped single quotes SQL- and C-Style + [ + "SELECT * FROM Foo WHERE foo = :foo||''':not_a_param''\\'' OR bar = ''':not_a_param''\\'':bar", + [':foo' => 1, ':bar' => 2], + [':foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ?||\'\'\':not_a_param\'\'\\\'\' OR bar = \'\'\':not_a_param\'\'\\\'\'?', + [1, 2], + [ParameterType::INTEGER, ParameterType::INTEGER], + ], + ]; + } + + /** + * @param mixed[] $params + * @param mixed[] $types + * @param mixed[] $expectedParams + * @param mixed[] $expectedTypes + * + * @dataProvider dataExpandListParameters + */ + public function testExpandListParameters( + string $query, + array $params, + array $types, + string $expectedQuery, + array $expectedParams, + array $expectedTypes + ) : void { + [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types); + + self::assertEquals($expectedQuery, $query, 'Query was not rewritten correctly.'); + self::assertEquals($expectedParams, $params, 'Params dont match'); + self::assertEquals($expectedTypes, $types, 'Types dont match'); + } + + /** + * @return mixed[][] + */ + public static function dataQueryWithMissingParameters() : iterable + { + return [ + [ + 'SELECT * FROM foo WHERE bar = :param', + ['other' => 'val'], + [], + ], + [ + 'SELECT * FROM foo WHERE bar = :param', + [], + [], + ], + [ + 'SELECT * FROM foo WHERE bar = :param', + [], + ['param' => Connection::PARAM_INT_ARRAY], + ], + [ + 'SELECT * FROM foo WHERE bar = :param', + [], + [':param' => Connection::PARAM_INT_ARRAY], + ], + [ + 'SELECT * FROM foo WHERE bar = :param', + [], + ['bar' => Connection::PARAM_INT_ARRAY], + ], + [ + 'SELECT * FROM foo WHERE bar = :param', + ['bar' => 'value'], + ['bar' => Connection::PARAM_INT_ARRAY], + ], + ]; + } + + /** + * @param mixed[] $params + * @param mixed[] $types + * + * @dataProvider dataQueryWithMissingParameters + */ + public function testExceptionIsThrownForMissingParam(string $query, array $params, array $types = []) : void + { + $this->expectException(SQLParserUtilsException::class); + $this->expectExceptionMessage('Value for :param not found in params array. Params array key should be "param"'); + + SQLParserUtils::expandListParameters($query, $params, $types); + } +} diff --git a/tests/Unit/DBAL/Schema/ColumnDiffTest.php b/tests/Unit/DBAL/Schema/ColumnDiffTest.php new file mode 100644 index 0000000..aa838dd --- /dev/null +++ b/tests/Unit/DBAL/Schema/ColumnDiffTest.php @@ -0,0 +1,30 @@ +getOldColumnName()->isQuoted()); + + $columnDiff = new ColumnDiff('"foo"', $toColumn, [], $fromColumn); + self::assertTrue($columnDiff->getOldColumnName()->isQuoted()); + + $columnDiff = new ColumnDiff('foo', $toColumn, [], $fromColumn); + self::assertTrue($columnDiff->getOldColumnName()->isQuoted()); + } +} diff --git a/tests/Unit/DBAL/Schema/ColumnTest.php b/tests/Unit/DBAL/Schema/ColumnTest.php new file mode 100644 index 0000000..96ea5e7 --- /dev/null +++ b/tests/Unit/DBAL/Schema/ColumnTest.php @@ -0,0 +1,146 @@ +createColumn(); + + self::assertEquals('foo', $column->getName()); + self::assertSame(Type::getType('string'), $column->getType()); + + self::assertEquals(200, $column->getLength()); + self::assertEquals(5, $column->getPrecision()); + self::assertEquals(2, $column->getScale()); + self::assertTrue($column->getUnsigned()); + self::assertFalse($column->getNotNull()); + self::assertTrue($column->getFixed()); + self::assertEquals('baz', $column->getDefault()); + + self::assertEquals(['foo' => 'bar'], $column->getPlatformOptions()); + self::assertTrue($column->hasPlatformOption('foo')); + self::assertEquals('bar', $column->getPlatformOption('foo')); + self::assertFalse($column->hasPlatformOption('bar')); + + self::assertEquals(['bar' => 'baz'], $column->getCustomSchemaOptions()); + self::assertTrue($column->hasCustomSchemaOption('bar')); + self::assertEquals('baz', $column->getCustomSchemaOption('bar')); + self::assertFalse($column->hasCustomSchemaOption('foo')); + } + + public function testToArray() : void + { + $expected = [ + 'name' => 'foo', + 'type' => Type::getType('string'), + 'default' => 'baz', + 'notnull' => false, + 'length' => 200, + 'precision' => 5, + 'scale' => 2, + 'fixed' => true, + 'unsigned' => true, + 'autoincrement' => false, + 'columnDefinition' => null, + 'comment' => null, + 'foo' => 'bar', + 'bar' => 'baz', + ]; + + self::assertEquals($expected, $this->createColumn()->toArray()); + } + + public function createColumn() : Column + { + $options = [ + 'length' => 200, + 'precision' => 5, + 'scale' => 2, + 'unsigned' => true, + 'notnull' => false, + 'fixed' => true, + 'default' => 'baz', + 'platformOptions' => ['foo' => 'bar'], + 'customSchemaOptions' => ['bar' => 'baz'], + ]; + + $string = Type::getType('string'); + + return new Column('foo', $string, $options); + } + + /** + * @group DBAL-64 + * @group DBAL-830 + */ + public function testQuotedColumnName() : void + { + $string = Type::getType('string'); + $column = new Column('`bar`', $string, []); + + $mysqlPlatform = new MySqlPlatform(); + $sqlitePlatform = new SqlitePlatform(); + + self::assertEquals('bar', $column->getName()); + self::assertEquals('`bar`', $column->getQuotedName($mysqlPlatform)); + self::assertEquals('"bar"', $column->getQuotedName($sqlitePlatform)); + + $column = new Column('[bar]', $string); + + $sqlServerPlatform = new SQLServerPlatform(); + + self::assertEquals('bar', $column->getName()); + self::assertEquals('[bar]', $column->getQuotedName($sqlServerPlatform)); + } + + /** + * @dataProvider getIsQuoted + * @group DBAL-830 + */ + public function testIsQuoted(string $columnName, bool $isQuoted) : void + { + $type = Type::getType('string'); + $column = new Column($columnName, $type); + + self::assertSame($isQuoted, $column->isQuoted()); + } + + /** + * @return mixed[][] + */ + public static function getIsQuoted() : iterable + { + return [ + ['bar', false], + ['`bar`', true], + ['"bar"', true], + ['[bar]', true], + ]; + } + + /** + * @group DBAL-42 + */ + public function testColumnComment() : void + { + $column = new Column('bar', Type::getType('string')); + self::assertNull($column->getComment()); + + $column->setComment('foo'); + self::assertEquals('foo', $column->getComment()); + + $columnArray = $column->toArray(); + self::assertArrayHasKey('comment', $columnArray); + self::assertEquals('foo', $columnArray['comment']); + } +} diff --git a/tests/Unit/DBAL/Schema/ComparatorTest.php b/tests/Unit/DBAL/Schema/ComparatorTest.php new file mode 100644 index 0000000..b7798e8 --- /dev/null +++ b/tests/Unit/DBAL/Schema/ComparatorTest.php @@ -0,0 +1,1329 @@ + new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ] + ), + ]); + $schema2 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ] + ), + ]); + + $expected = new SchemaDiff(); + $expected->fromSchema = $schema1; + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareSame2() : void + { + $schema1 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ] + ), + ]); + $schema2 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ] + ), + ]); + + $expected = new SchemaDiff(); + $expected->fromSchema = $schema1; + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareMissingTable() : void + { + $schemaConfig = new SchemaConfig(); + $table = new Table('bugdb', ['integerfield1' => new Column('integerfield1', Type::getType('integer'))]); + $table->setSchemaConfig($schemaConfig); + + $schema1 = new Schema([$table], [], $schemaConfig); + $schema2 = new Schema([], [], $schemaConfig); + + $expected = new SchemaDiff([], [], ['bugdb' => $table], $schema1); + + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareNewTable() : void + { + $schemaConfig = new SchemaConfig(); + $table = new Table('bugdb', ['integerfield1' => new Column('integerfield1', Type::getType('integer'))]); + $table->setSchemaConfig($schemaConfig); + + $schema1 = new Schema([], [], $schemaConfig); + $schema2 = new Schema([$table], [], $schemaConfig); + + $expected = new SchemaDiff(['bugdb' => $table], [], [], $schema1); + + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareOnlyAutoincrementChanged() : void + { + $column1 = new Column('foo', Type::getType('integer'), ['autoincrement' => true]); + $column2 = new Column('foo', Type::getType('integer'), ['autoincrement' => false]); + + $comparator = new Comparator(); + $changedProperties = $comparator->diffColumn($column1, $column2); + + self::assertEquals(['autoincrement'], $changedProperties); + } + + public function testCompareMissingField() : void + { + $missingColumn = new Column('integerfield1', Type::getType('integer')); + $schema1 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => $missingColumn, + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ] + ), + ]); + $schema2 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ] + ), + ]); + + $expected = new SchemaDiff( + [], + [ + 'bugdb' => new TableDiff( + 'bugdb', + [], + [], + ['integerfield1' => $missingColumn] + ), + ] + ); + $expected->fromSchema = $schema1; + $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); + + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareNewField() : void + { + $schema1 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ] + ), + ]); + $schema2 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ] + ), + ]); + + $expected = new SchemaDiff( + [], + [ + 'bugdb' => new TableDiff( + 'bugdb', + [ + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ] + ), + ] + ); + $expected->fromSchema = $schema1; + $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); + + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareChangedColumnsChangeType() : void + { + $column1 = new Column('charfield1', Type::getType('string')); + $column2 = new Column('charfield1', Type::getType('integer')); + + $c = new Comparator(); + self::assertEquals(['type'], $c->diffColumn($column1, $column2)); + self::assertEquals([], $c->diffColumn($column1, $column1)); + } + + public function testCompareColumnsMultipleTypeInstances() : void + { + $integerType1 = Type::getType('integer'); + Type::overrideType('integer', get_class($integerType1)); + $integerType2 = Type::getType('integer'); + + $column1 = new Column('integerfield1', $integerType1); + $column2 = new Column('integerfield1', $integerType2); + + $c = new Comparator(); + self::assertEquals([], $c->diffColumn($column1, $column2)); + } + + public function testCompareColumnsOverriddenType() : void + { + $oldStringInstance = Type::getType('string'); + $integerType = Type::getType('integer'); + + Type::overrideType('string', get_class($integerType)); + $overriddenStringType = Type::getType('string'); + + Type::overrideType('string', get_class($oldStringInstance)); + + $column1 = new Column('integerfield1', $integerType); + $column2 = new Column('integerfield1', $overriddenStringType); + + $c = new Comparator(); + self::assertEquals([], $c->diffColumn($column1, $column2)); + } + + public function testCompareChangedColumnsChangeCustomSchemaOption() : void + { + $column1 = new Column('charfield1', Type::getType('string')); + $column2 = new Column('charfield1', Type::getType('string')); + + $column1->setCustomSchemaOption('foo', 'bar'); + $column2->setCustomSchemaOption('foo', 'bar'); + + $column1->setCustomSchemaOption('foo1', 'bar1'); + $column2->setCustomSchemaOption('foo2', 'bar2'); + + $c = new Comparator(); + self::assertEquals(['foo1', 'foo2'], $c->diffColumn($column1, $column2)); + self::assertEquals([], $c->diffColumn($column1, $column1)); + } + + public function testCompareChangeColumnsMultipleNewColumnsRename() : void + { + $tableA = new Table('foo'); + $tableA->addColumn('datefield1', 'datetime'); + + $tableB = new Table('foo'); + $tableB->addColumn('new_datefield1', 'datetime'); + $tableB->addColumn('new_datefield2', 'datetime'); + + $c = new Comparator(); + $tableDiff = $c->diffTable($tableA, $tableB); + + self::assertCount(1, $tableDiff->renamedColumns, 'we should have one rename datefield1 => new_datefield1.'); + self::assertArrayHasKey('datefield1', $tableDiff->renamedColumns, "'datefield1' should be set to be renamed to new_datefield1"); + self::assertCount(1, $tableDiff->addedColumns, "'new_datefield2' should be added"); + self::assertArrayHasKey('new_datefield2', $tableDiff->addedColumns, "'new_datefield2' should be added, not created through renaming!"); + self::assertCount(0, $tableDiff->removedColumns, 'Nothing should be removed.'); + self::assertCount(0, $tableDiff->changedColumns, 'Nothing should be changed as all fields old & new have diff names.'); + } + + public function testCompareRemovedIndex() : void + { + $schema1 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ], + [ + 'primary' => new Index( + 'primary', + ['integerfield1'], + true + ), + ] + ), + ]); + $schema2 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ] + ), + ]); + + $expected = new SchemaDiff( + [], + [ + 'bugdb' => new TableDiff( + 'bugdb', + [], + [], + [], + [], + [], + [ + 'primary' => new Index( + 'primary', + ['integerfield1'], + true + ), + ] + ), + ] + ); + $expected->fromSchema = $schema1; + $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); + + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareNewIndex() : void + { + $schema1 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ] + ), + ]); + $schema2 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ], + [ + 'primary' => new Index( + 'primary', + ['integerfield1'], + true + ), + ] + ), + ]); + + $expected = new SchemaDiff( + [], + [ + 'bugdb' => new TableDiff( + 'bugdb', + [], + [], + [], + [ + 'primary' => new Index( + 'primary', + ['integerfield1'], + true + ), + ] + ), + ] + ); + $expected->fromSchema = $schema1; + $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); + + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareChangedIndex() : void + { + $schema1 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ], + [ + 'primary' => new Index( + 'primary', + ['integerfield1'], + true + ), + ] + ), + ]); + $schema2 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ], + [ + 'primary' => new Index( + 'primary', + ['integerfield1', 'integerfield2'], + true + ), + ] + ), + ]); + + $expected = new SchemaDiff( + [], + [ + 'bugdb' => new TableDiff( + 'bugdb', + [], + [], + [], + [], + [ + 'primary' => new Index( + 'primary', + [ + 'integerfield1', + 'integerfield2', + ], + true + ), + ] + ), + ] + ); + $expected->fromSchema = $schema1; + $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); + + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareChangedIndexFieldPositions() : void + { + $schema1 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ], + [ + 'primary' => new Index('primary', ['integerfield1', 'integerfield2'], true), + ] + ), + ]); + $schema2 = new Schema([ + 'bugdb' => new Table( + 'bugdb', + [ + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ], + [ + 'primary' => new Index('primary', ['integerfield2', 'integerfield1'], true), + ] + ), + ]); + + $expected = new SchemaDiff( + [], + [ + 'bugdb' => new TableDiff( + 'bugdb', + [], + [], + [], + [], + [ + 'primary' => new Index('primary', ['integerfield2', 'integerfield1'], true), + ] + ), + ] + ); + $expected->fromSchema = $schema1; + $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); + + self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); + } + + public function testCompareSequences() : void + { + $seq1 = new Sequence('foo', 1, 1); + $seq2 = new Sequence('foo', 1, 2); + $seq3 = new Sequence('foo', 2, 1); + $seq4 = new Sequence('foo', '1', '1'); + + $c = new Comparator(); + + self::assertTrue($c->diffSequence($seq1, $seq2)); + self::assertTrue($c->diffSequence($seq1, $seq3)); + self::assertFalse($c->diffSequence($seq1, $seq4)); + } + + public function testRemovedSequence() : void + { + $schema1 = new Schema(); + $seq = $schema1->createSequence('foo'); + + $schema2 = new Schema(); + + $c = new Comparator(); + $diffSchema = $c->compare($schema1, $schema2); + + self::assertCount(1, $diffSchema->removedSequences); + self::assertSame($seq, $diffSchema->removedSequences[0]); + } + + public function testAddedSequence() : void + { + $schema1 = new Schema(); + + $schema2 = new Schema(); + $seq = $schema2->createSequence('foo'); + + $c = new Comparator(); + $diffSchema = $c->compare($schema1, $schema2); + + self::assertCount(1, $diffSchema->newSequences); + self::assertSame($seq, $diffSchema->newSequences[0]); + } + + public function testTableAddForeignKey() : void + { + $tableForeign = new Table('bar'); + $tableForeign->addColumn('id', 'integer'); + + $table1 = new Table('foo'); + $table1->addColumn('fk', 'integer'); + + $table2 = new Table('foo'); + $table2->addColumn('fk', 'integer'); + $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id']); + + $c = new Comparator(); + $tableDiff = $c->diffTable($table1, $table2); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertCount(1, $tableDiff->addedForeignKeys); + } + + public function testTableRemoveForeignKey() : void + { + $tableForeign = new Table('bar'); + $tableForeign->addColumn('id', 'integer'); + + $table1 = new Table('foo'); + $table1->addColumn('fk', 'integer'); + + $table2 = new Table('foo'); + $table2->addColumn('fk', 'integer'); + $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id']); + + $c = new Comparator(); + $tableDiff = $c->diffTable($table2, $table1); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertCount(1, $tableDiff->removedForeignKeys); + } + + public function testTableUpdateForeignKey() : void + { + $tableForeign = new Table('bar'); + $tableForeign->addColumn('id', 'integer'); + + $table1 = new Table('foo'); + $table1->addColumn('fk', 'integer'); + $table1->addForeignKeyConstraint($tableForeign, ['fk'], ['id']); + + $table2 = new Table('foo'); + $table2->addColumn('fk', 'integer'); + $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id'], ['onUpdate' => 'CASCADE']); + + $c = new Comparator(); + $tableDiff = $c->diffTable($table1, $table2); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertCount(1, $tableDiff->changedForeignKeys); + } + + public function testMovedForeignKeyForeignTable() : void + { + $tableForeign = new Table('bar'); + $tableForeign->addColumn('id', 'integer'); + + $tableForeign2 = new Table('bar2'); + $tableForeign2->addColumn('id', 'integer'); + + $table1 = new Table('foo'); + $table1->addColumn('fk', 'integer'); + $table1->addForeignKeyConstraint($tableForeign, ['fk'], ['id']); + + $table2 = new Table('foo'); + $table2->addColumn('fk', 'integer'); + $table2->addForeignKeyConstraint($tableForeign2, ['fk'], ['id']); + + $c = new Comparator(); + $tableDiff = $c->diffTable($table1, $table2); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertCount(1, $tableDiff->changedForeignKeys); + } + + public function testTablesCaseInsensitive() : void + { + $schemaA = new Schema(); + $schemaA->createTable('foo'); + $schemaA->createTable('bAr'); + $schemaA->createTable('BAZ'); + $schemaA->createTable('new'); + + $schemaB = new Schema(); + $schemaB->createTable('FOO'); + $schemaB->createTable('bar'); + $schemaB->createTable('Baz'); + $schemaB->createTable('old'); + + $c = new Comparator(); + $diff = $c->compare($schemaA, $schemaB); + + self::assertSchemaTableChangeCount($diff, 1, 0, 1); + } + + public function testSequencesCaseInsensitive() : void + { + $schemaA = new Schema(); + $schemaA->createSequence('foo'); + $schemaA->createSequence('BAR'); + $schemaA->createSequence('Baz'); + $schemaA->createSequence('new'); + + $schemaB = new Schema(); + $schemaB->createSequence('FOO'); + $schemaB->createSequence('Bar'); + $schemaB->createSequence('baz'); + $schemaB->createSequence('old'); + + $c = new Comparator(); + $diff = $c->compare($schemaA, $schemaB); + + self::assertSchemaSequenceChangeCount($diff, 1, 0, 1); + } + + public function testCompareColumnCompareCaseInsensitive() : void + { + $tableA = new Table('foo'); + $tableA->addColumn('id', 'integer'); + + $tableB = new Table('foo'); + $tableB->addColumn('ID', 'integer'); + + $c = new Comparator(); + $tableDiff = $c->diffTable($tableA, $tableB); + + self::assertFalse($tableDiff); + } + + public function testCompareIndexBasedOnPropertiesNotName() : void + { + $tableA = new Table('foo'); + $tableA->addColumn('id', 'integer'); + $tableA->addIndex(['id'], 'foo_bar_idx'); + + $tableB = new Table('foo'); + $tableB->addColumn('ID', 'integer'); + $tableB->addIndex(['id'], 'bar_foo_idx'); + + $c = new Comparator(); + $tableDiff = new TableDiff('foo'); + $tableDiff->fromTable = $tableA; + $tableDiff->renamedIndexes['foo_bar_idx'] = new Index('bar_foo_idx', ['id']); + + self::assertEquals( + $tableDiff, + $c->diffTable($tableA, $tableB) + ); + } + + public function testCompareForeignKeyBasedOnPropertiesNotName() : void + { + $tableA = new Table('foo'); + $tableA->addColumn('id', 'integer'); + $tableA->addNamedForeignKeyConstraint('foo_constraint', 'bar', ['id'], ['id']); + + $tableB = new Table('foo'); + $tableB->addColumn('ID', 'integer'); + $tableB->addNamedForeignKeyConstraint('bar_constraint', 'bar', ['id'], ['id']); + + $c = new Comparator(); + $tableDiff = $c->diffTable($tableA, $tableB); + + self::assertFalse($tableDiff); + } + + public function testCompareForeignKeyRestrictNoActionAreTheSame() : void + { + $fk1 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'NO ACTION']); + $fk2 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'RESTRICT']); + + $c = new Comparator(); + self::assertFalse($c->diffForeignKey($fk1, $fk2)); + } + + /** + * @group DBAL-492 + */ + public function testCompareForeignKeyNamesUnqualifiedAsNoSchemaInformationIsAvailable() : void + { + $fk1 = new ForeignKeyConstraint(['foo'], 'foo.bar', ['baz'], 'fk1'); + $fk2 = new ForeignKeyConstraint(['foo'], 'baz.bar', ['baz'], 'fk1'); + + $c = new Comparator(); + self::assertFalse($c->diffForeignKey($fk1, $fk2)); + } + + public function testDetectRenameColumn() : void + { + $tableA = new Table('foo'); + $tableA->addColumn('foo', 'integer'); + + $tableB = new Table('foo'); + $tableB->addColumn('bar', 'integer'); + + $c = new Comparator(); + $tableDiff = $c->diffTable($tableA, $tableB); + + self::assertCount(0, $tableDiff->addedColumns); + self::assertCount(0, $tableDiff->removedColumns); + self::assertArrayHasKey('foo', $tableDiff->renamedColumns); + self::assertEquals('bar', $tableDiff->renamedColumns['foo']->getName()); + } + + /** + * You can easily have ambiguities in the column renaming. If these + * are detected no renaming should take place, instead adding and dropping + * should be used exclusively. + * + * @group DBAL-24 + */ + public function testDetectRenameColumnAmbiguous() : void + { + $tableA = new Table('foo'); + $tableA->addColumn('foo', 'integer'); + $tableA->addColumn('bar', 'integer'); + + $tableB = new Table('foo'); + $tableB->addColumn('baz', 'integer'); + + $c = new Comparator(); + $tableDiff = $c->diffTable($tableA, $tableB); + + self::assertCount(1, $tableDiff->addedColumns, "'baz' should be added, not created through renaming!"); + self::assertArrayHasKey('baz', $tableDiff->addedColumns, "'baz' should be added, not created through renaming!"); + self::assertCount(2, $tableDiff->removedColumns, "'foo' and 'bar' should both be dropped, an ambiguity exists which one could be renamed to 'baz'."); + self::assertArrayHasKey('foo', $tableDiff->removedColumns, "'foo' should be removed."); + self::assertArrayHasKey('bar', $tableDiff->removedColumns, "'bar' should be removed."); + self::assertCount(0, $tableDiff->renamedColumns, 'no renamings should take place.'); + } + + /** + * @group DBAL-1063 + */ + public function testDetectRenameIndex() : void + { + $table1 = new Table('foo'); + $table1->addColumn('foo', 'integer'); + + $table2 = clone $table1; + + $table1->addIndex(['foo'], 'idx_foo'); + + $table2->addIndex(['foo'], 'idx_bar'); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($table1, $table2); + + self::assertCount(0, $tableDiff->addedIndexes); + self::assertCount(0, $tableDiff->removedIndexes); + self::assertArrayHasKey('idx_foo', $tableDiff->renamedIndexes); + self::assertEquals('idx_bar', $tableDiff->renamedIndexes['idx_foo']->getName()); + } + + /** + * You can easily have ambiguities in the index renaming. If these + * are detected no renaming should take place, instead adding and dropping + * should be used exclusively. + * + * @group DBAL-1063 + */ + public function testDetectRenameIndexAmbiguous() : void + { + $table1 = new Table('foo'); + $table1->addColumn('foo', 'integer'); + + $table2 = clone $table1; + + $table1->addIndex(['foo'], 'idx_foo'); + $table1->addIndex(['foo'], 'idx_bar'); + + $table2->addIndex(['foo'], 'idx_baz'); + + $comparator = new Comparator(); + $tableDiff = $comparator->diffTable($table1, $table2); + + self::assertCount(1, $tableDiff->addedIndexes); + self::assertArrayHasKey('idx_baz', $tableDiff->addedIndexes); + self::assertCount(2, $tableDiff->removedIndexes); + self::assertArrayHasKey('idx_foo', $tableDiff->removedIndexes); + self::assertArrayHasKey('idx_bar', $tableDiff->removedIndexes); + self::assertCount(0, $tableDiff->renamedIndexes); + } + + public function testDetectChangeIdentifierType() : void + { + $this->markTestSkipped('DBAL-2 was reopened, this test cannot work anymore.'); + + $tableA = new Table('foo'); + $tableA->addColumn('id', 'integer', ['autoincrement' => false]); + + $tableB = new Table('foo'); + $tableB->addColumn('id', 'integer', ['autoincrement' => true]); + + $c = new Comparator(); + $tableDiff = $c->diffTable($tableA, $tableB); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertArrayHasKey('id', $tableDiff->changedColumns); + } + + /** + * @group DBAL-105 + */ + public function testDiff() : void + { + $table = new Table('twitter_users'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('twitterId', 'integer'); + $table->addColumn('displayName', 'string'); + $table->setPrimaryKey(['id']); + + $newtable = new Table('twitter_users'); + $newtable->addColumn('id', 'integer', ['autoincrement' => true]); + $newtable->addColumn('twitter_id', 'integer'); + $newtable->addColumn('display_name', 'string'); + $newtable->addColumn('logged_in_at', 'datetime'); + $newtable->setPrimaryKey(['id']); + + $c = new Comparator(); + $tableDiff = $c->diffTable($table, $newtable); + + self::assertInstanceOf(TableDiff::class, $tableDiff); + self::assertEquals(['twitterid', 'displayname'], array_keys($tableDiff->renamedColumns)); + self::assertEquals(['logged_in_at'], array_keys($tableDiff->addedColumns)); + self::assertCount(0, $tableDiff->removedColumns); + } + + /** + * @group DBAL-112 + */ + public function testChangedSequence() : void + { + $schema = new Schema(); + $sequence = $schema->createSequence('baz'); + + $schemaNew = clone $schema; + $schemaNew->getSequence('baz')->setAllocationSize(20); + + $c = new Comparator(); + $diff = $c->compare($schema, $schemaNew); + + self::assertSame($diff->changedSequences[0], $schemaNew->getSequence('baz')); + } + + /** + * @group DBAL-106 + */ + public function testDiffDecimalWithNullPrecision() : void + { + $column = new Column('foo', Type::getType('decimal')); + $column->setPrecision(null); + + $column2 = new Column('foo', Type::getType('decimal')); + + $c = new Comparator(); + self::assertEquals([], $c->diffColumn($column, $column2)); + } + + /** + * @group DBAL-204 + */ + public function testFqnSchemaComparison() : void + { + $config = new SchemaConfig(); + $config->setName('foo'); + + $oldSchema = new Schema([], [], $config); + $oldSchema->createTable('bar'); + + $newSchema = new Schema([], [], $config); + $newSchema->createTable('foo.bar'); + + $expected = new SchemaDiff(); + $expected->fromSchema = $oldSchema; + + self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); + } + + /** + * @group DBAL-669 + */ + public function testNamespacesComparison() : void + { + $config = new SchemaConfig(); + $config->setName('schemaName'); + + $oldSchema = new Schema([], [], $config); + $oldSchema->createTable('taz'); + $oldSchema->createTable('war.tab'); + + $newSchema = new Schema([], [], $config); + $newSchema->createTable('bar.tab'); + $newSchema->createTable('baz.tab'); + $newSchema->createTable('war.tab'); + + $expected = new SchemaDiff(); + $expected->fromSchema = $oldSchema; + $expected->newNamespaces = ['bar' => 'bar', 'baz' => 'baz']; + + $diff = Comparator::compareSchemas($oldSchema, $newSchema); + + self::assertEquals(['bar' => 'bar', 'baz' => 'baz'], $diff->newNamespaces); + self::assertCount(2, $diff->newTables); + } + + /** + * @group DBAL-204 + */ + public function testFqnSchemaComparisonDifferentSchemaNameButSameTableNoDiff() : void + { + $config = new SchemaConfig(); + $config->setName('foo'); + + $oldSchema = new Schema([], [], $config); + $oldSchema->createTable('foo.bar'); + + $newSchema = new Schema(); + $newSchema->createTable('bar'); + + $expected = new SchemaDiff(); + $expected->fromSchema = $oldSchema; + + self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); + } + + /** + * @group DBAL-204 + */ + public function testFqnSchemaComparisonNoSchemaSame() : void + { + $config = new SchemaConfig(); + $config->setName('foo'); + $oldSchema = new Schema([], [], $config); + $oldSchema->createTable('bar'); + + $newSchema = new Schema(); + $newSchema->createTable('bar'); + + $expected = new SchemaDiff(); + $expected->fromSchema = $oldSchema; + + self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); + } + + /** + * @group DDC-1657 + */ + public function testAutoIncrementSequences() : void + { + $oldSchema = new Schema(); + $table = $oldSchema->createTable('foo'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['id']); + $oldSchema->createSequence('foo_id_seq'); + + $newSchema = new Schema(); + $table = $newSchema->createTable('foo'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['id']); + + $c = new Comparator(); + $diff = $c->compare($oldSchema, $newSchema); + + self::assertCount(0, $diff->removedSequences); + } + + /** + * Check that added autoincrement sequence is not populated in newSequences + * + * @group DBAL-562 + */ + public function testAutoIncrementNoSequences() : void + { + $oldSchema = new Schema(); + $table = $oldSchema->createTable('foo'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['id']); + + $newSchema = new Schema(); + $table = $newSchema->createTable('foo'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['id']); + $newSchema->createSequence('foo_id_seq'); + + $c = new Comparator(); + $diff = $c->compare($oldSchema, $newSchema); + + self::assertCount(0, $diff->newSequences); + } + + /** + * You can get multiple drops for a FK when a table referenced by a foreign + * key is deleted, as this FK is referenced twice, once on the orphanedForeignKeys + * array because of the dropped table, and once on changedTables array. We + * now check that the key is present once. + */ + public function testAvoidMultipleDropForeignKey() : void + { + $oldSchema = new Schema(); + + $tableA = $oldSchema->createTable('table_a'); + $tableA->addColumn('id', 'integer'); + + $tableB = $oldSchema->createTable('table_b'); + $tableB->addColumn('id', 'integer'); + + $tableC = $oldSchema->createTable('table_c'); + $tableC->addColumn('id', 'integer'); + $tableC->addColumn('table_a_id', 'integer'); + $tableC->addColumn('table_b_id', 'integer'); + + $tableC->addForeignKeyConstraint($tableA, ['table_a_id'], ['id']); + $tableC->addForeignKeyConstraint($tableB, ['table_b_id'], ['id']); + + $newSchema = new Schema(); + + $tableB = $newSchema->createTable('table_b'); + $tableB->addColumn('id', 'integer'); + + $tableC = $newSchema->createTable('table_c'); + $tableC->addColumn('id', 'integer'); + + $comparator = new Comparator(); + $schemaDiff = $comparator->compare($oldSchema, $newSchema); + + self::assertCount(1, $schemaDiff->changedTables['table_c']->removedForeignKeys); + self::assertCount(1, $schemaDiff->orphanedForeignKeys); + } + + public function testCompareChangedColumn() : void + { + $oldSchema = new Schema(); + + $tableFoo = $oldSchema->createTable('foo'); + $tableFoo->addColumn('id', 'integer'); + + $newSchema = new Schema(); + $table = $newSchema->createTable('foo'); + $table->addColumn('id', 'string'); + + $expected = new SchemaDiff(); + $expected->fromSchema = $oldSchema; + $tableDiff = $expected->changedTables['foo'] = new TableDiff('foo'); + $tableDiff->fromTable = $tableFoo; + $columnDiff = $tableDiff->changedColumns['id'] = new ColumnDiff('id', $table->getColumn('id')); + $columnDiff->fromColumn = $tableFoo->getColumn('id'); + $columnDiff->changedProperties = ['type']; + + self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); + } + + public function testCompareChangedBinaryColumn() : void + { + $oldSchema = new Schema(); + + $tableFoo = $oldSchema->createTable('foo'); + $tableFoo->addColumn('id', 'binary'); + + $newSchema = new Schema(); + $table = $newSchema->createTable('foo'); + $table->addColumn('id', 'binary', ['length' => 42, 'fixed' => true]); + + $expected = new SchemaDiff(); + $expected->fromSchema = $oldSchema; + $tableDiff = $expected->changedTables['foo'] = new TableDiff('foo'); + $tableDiff->fromTable = $tableFoo; + $columnDiff = $tableDiff->changedColumns['id'] = new ColumnDiff('id', $table->getColumn('id')); + $columnDiff->fromColumn = $tableFoo->getColumn('id'); + $columnDiff->changedProperties = ['length', 'fixed']; + + self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); + } + + /** + * @group DBAL-617 + */ + public function testCompareQuotedAndUnquotedForeignKeyColumns() : void + { + $fk1 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'NO ACTION']); + $fk2 = new ForeignKeyConstraint(['`foo`'], 'bar', ['`baz`'], 'fk1', ['onDelete' => 'NO ACTION']); + + $comparator = new Comparator(); + $diff = $comparator->diffForeignKey($fk1, $fk2); + + self::assertFalse($diff); + } + + public function assertSchemaTableChangeCount(SchemaDiff $diff, int $newTableCount = 0, int $changeTableCount = 0, int $removeTableCount = 0) : void + { + self::assertCount($newTableCount, $diff->newTables); + self::assertCount($changeTableCount, $diff->changedTables); + self::assertCount($removeTableCount, $diff->removedTables); + } + + public function assertSchemaSequenceChangeCount( + SchemaDiff $diff, + int $newSequenceCount = 0, + int $changeSequenceCount = 0, + int $removeSequenceCount = 0 + ) : void { + self::assertCount($newSequenceCount, $diff->newSequences, 'Expected number of new sequences is wrong.'); + self::assertCount($changeSequenceCount, $diff->changedSequences, 'Expected number of changed sequences is wrong.'); + self::assertCount($removeSequenceCount, $diff->removedSequences, 'Expected number of removed sequences is wrong.'); + } + + public function testDiffColumnPlatformOptions() : void + { + $column1 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'bar' => 'bar']]); + $column2 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'foobar' => 'foobar']]); + $column3 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'bar' => 'rab']]); + $column4 = new Column('foo', Type::getType('string')); + + $comparator = new Comparator(); + + self::assertEquals([], $comparator->diffColumn($column1, $column2)); + self::assertEquals([], $comparator->diffColumn($column2, $column1)); + self::assertEquals(['bar'], $comparator->diffColumn($column1, $column3)); + self::assertEquals(['bar'], $comparator->diffColumn($column3, $column1)); + self::assertEquals([], $comparator->diffColumn($column1, $column4)); + self::assertEquals([], $comparator->diffColumn($column4, $column1)); + } + + public function testComplexDiffColumn() : void + { + $column1 = new Column('foo', Type::getType('string'), [ + 'platformOptions' => ['foo' => 'foo'], + 'customSchemaOptions' => ['foo' => 'bar'], + ]); + + $column2 = new Column('foo', Type::getType('string'), [ + 'platformOptions' => ['foo' => 'bar'], + ]); + + $comparator = new Comparator(); + + self::assertEquals([], $comparator->diffColumn($column1, $column2)); + self::assertEquals([], $comparator->diffColumn($column2, $column1)); + } + + /** + * @group DBAL-669 + */ + public function testComparesNamespaces() : void + { + $comparator = new Comparator(); + $fromSchema = $this->getMockBuilder(Schema::class) + ->setMethods(['getNamespaces', 'hasNamespace']) + ->getMock(); + $toSchema = $this->getMockBuilder(Schema::class) + ->setMethods(['getNamespaces', 'hasNamespace']) + ->getMock(); + + $fromSchema->expects($this->once()) + ->method('getNamespaces') + ->will($this->returnValue(['foo', 'bar'])); + + $fromSchema->expects($this->at(0)) + ->method('hasNamespace') + ->with('bar') + ->will($this->returnValue(true)); + + $fromSchema->expects($this->at(1)) + ->method('hasNamespace') + ->with('baz') + ->will($this->returnValue(false)); + + $toSchema->expects($this->once()) + ->method('getNamespaces') + ->will($this->returnValue(['bar', 'baz'])); + + $toSchema->expects($this->at(1)) + ->method('hasNamespace') + ->with('foo') + ->will($this->returnValue(false)); + + $toSchema->expects($this->at(2)) + ->method('hasNamespace') + ->with('bar') + ->will($this->returnValue(true)); + + $expected = new SchemaDiff(); + $expected->fromSchema = $fromSchema; + $expected->newNamespaces = ['baz' => 'baz']; + $expected->removedNamespaces = ['foo' => 'foo']; + + self::assertEquals($expected, $comparator->compare($fromSchema, $toSchema)); + } + + public function testCompareGuidColumns() : void + { + $comparator = new Comparator(); + + $column1 = new Column('foo', Type::getType('guid'), ['comment' => 'GUID 1']); + $column2 = new Column( + 'foo', + Type::getType('guid'), + ['notnull' => false, 'length' => '36', 'fixed' => true, 'default' => 'NEWID()', 'comment' => 'GUID 2.'] + ); + + self::assertEquals(['notnull', 'default', 'comment'], $comparator->diffColumn($column1, $column2)); + self::assertEquals(['notnull', 'default', 'comment'], $comparator->diffColumn($column2, $column1)); + } + + /** + * @group DBAL-1009 + * @dataProvider getCompareColumnComments + */ + public function testCompareColumnComments(?string $comment1, ?string $comment2, bool $equals) : void + { + $column1 = new Column('foo', Type::getType('integer'), ['comment' => $comment1]); + $column2 = new Column('foo', Type::getType('integer'), ['comment' => $comment2]); + + $comparator = new Comparator(); + + $expectedDiff = $equals ? [] : ['comment']; + + $actualDiff = $comparator->diffColumn($column1, $column2); + + self::assertSame($expectedDiff, $actualDiff); + + $actualDiff = $comparator->diffColumn($column2, $column1); + + self::assertSame($expectedDiff, $actualDiff); + } + + /** + * @return mixed[][] + */ + public static function getCompareColumnComments() : iterable + { + return [ + [null, null, true], + ['', '', true], + [' ', ' ', true], + ['0', '0', true], + ['foo', 'foo', true], + + [null, '', true], + [null, ' ', false], + [null, '0', false], + [null, 'foo', false], + + ['', ' ', false], + ['', '0', false], + ['', 'foo', false], + + [' ', '0', false], + [' ', 'foo', false], + + ['0', 'foo', false], + ]; + } + + public function testForeignKeyRemovalWithRenamedLocalColumn() : void + { + $fromSchema = new Schema([ + 'table1' => new Table( + 'table1', + [ + 'id' => new Column('id', Type::getType('integer')), + ] + ), + 'table2' => new Table( + 'table2', + [ + 'id' => new Column('id', Type::getType('integer')), + 'id_table1' => new Column('id_table1', Type::getType('integer')), + ], + [], + [ + new ForeignKeyConstraint(['id_table1'], 'table1', ['id'], 'fk_table2_table1'), + ] + ), + ]); + $toSchema = new Schema([ + 'table2' => new Table( + 'table2', + [ + 'id' => new Column('id', Type::getType('integer')), + 'id_table3' => new Column('id_table3', Type::getType('integer')), + ], + [], + [ + new ForeignKeyConstraint(['id_table3'], 'table3', ['id'], 'fk_table2_table3'), + ] + ), + 'table3' => new Table( + 'table3', + [ + 'id' => new Column('id', Type::getType('integer')), + ] + ), + ]); + $actual = Comparator::compareSchemas($fromSchema, $toSchema); + self::assertArrayHasKey('table2', $actual->changedTables); + self::assertCount(1, $actual->orphanedForeignKeys); + self::assertEquals('fk_table2_table1', $actual->orphanedForeignKeys[0]->getName()); + self::assertCount(1, $actual->changedTables['table2']->addedForeignKeys, 'FK to table3 should be added.'); + self::assertEquals('table3', $actual->changedTables['table2']->addedForeignKeys[0]->getForeignTableName()); + } +} diff --git a/tests/Unit/DBAL/Schema/ForeignKeyConstraintTest.php b/tests/Unit/DBAL/Schema/ForeignKeyConstraintTest.php new file mode 100644 index 0000000..675b16f --- /dev/null +++ b/tests/Unit/DBAL/Schema/ForeignKeyConstraintTest.php @@ -0,0 +1,59 @@ +getMockBuilder(Index::class) + ->disableOriginalConstructor() + ->getMock(); + $index->expects($this->once()) + ->method('getColumns') + ->will($this->returnValue($indexColumns)); + + self::assertSame($expectedResult, $foreignKey->intersectsIndexColumns($index)); + } + + /** + * @return mixed[][] + */ + public static function getIntersectsIndexColumnsData() : iterable + { + return [ + [['baz'], false], + [['baz', 'bloo'], false], + + [['foo'], true], + [['bar'], true], + + [['foo', 'bar'], true], + [['bar', 'foo'], true], + + [['foo', 'baz'], true], + [['baz', 'foo'], true], + + [['bar', 'baz'], true], + [['baz', 'bar'], true], + + [['foo', 'bloo', 'baz'], true], + [['bloo', 'foo', 'baz'], true], + [['bloo', 'baz', 'foo'], true], + + [['FOO'], true], + ]; + } +} diff --git a/tests/Unit/DBAL/Schema/IndexTest.php b/tests/Unit/DBAL/Schema/IndexTest.php new file mode 100644 index 0000000..a6badab --- /dev/null +++ b/tests/Unit/DBAL/Schema/IndexTest.php @@ -0,0 +1,191 @@ +createIndex(); + self::assertEquals('foo', $idx->getName()); + $columns = $idx->getColumns(); + self::assertCount(2, $columns); + self::assertEquals(['bar', 'baz'], $columns); + self::assertFalse($idx->isUnique()); + self::assertFalse($idx->isPrimary()); + } + + public function testCreatePrimary() : void + { + $idx = $this->createIndex(false, true); + self::assertTrue($idx->isUnique()); + self::assertTrue($idx->isPrimary()); + } + + public function testCreateUnique() : void + { + $idx = $this->createIndex(true, false); + self::assertTrue($idx->isUnique()); + self::assertFalse($idx->isPrimary()); + } + + /** + * @group DBAL-50 + */ + public function testFulfilledByUnique() : void + { + $idx1 = $this->createIndex(true, false); + $idx2 = $this->createIndex(true, false); + $idx3 = $this->createIndex(); + + self::assertTrue($idx1->isFullfilledBy($idx2)); + self::assertFalse($idx1->isFullfilledBy($idx3)); + } + + /** + * @group DBAL-50 + */ + public function testFulfilledByPrimary() : void + { + $idx1 = $this->createIndex(true, true); + $idx2 = $this->createIndex(true, true); + $idx3 = $this->createIndex(true, false); + + self::assertTrue($idx1->isFullfilledBy($idx2)); + self::assertFalse($idx1->isFullfilledBy($idx3)); + } + + /** + * @group DBAL-50 + */ + public function testFulfilledByIndex() : void + { + $idx1 = $this->createIndex(); + $idx2 = $this->createIndex(); + $pri = $this->createIndex(true, true); + $uniq = $this->createIndex(true); + + self::assertTrue($idx1->isFullfilledBy($idx2)); + self::assertTrue($idx1->isFullfilledBy($pri)); + self::assertTrue($idx1->isFullfilledBy($uniq)); + } + + public function testFulfilledWithPartial() : void + { + $without = new Index('without', ['col1', 'col2'], true, false, [], []); + $partial = new Index('partial', ['col1', 'col2'], true, false, [], ['where' => 'col1 IS NULL']); + $another = new Index('another', ['col1', 'col2'], true, false, [], ['where' => 'col1 IS NULL']); + + self::assertFalse($partial->isFullfilledBy($without)); + self::assertFalse($without->isFullfilledBy($partial)); + + self::assertTrue($partial->isFullfilledBy($partial)); + + self::assertTrue($partial->isFullfilledBy($another)); + self::assertTrue($another->isFullfilledBy($partial)); + } + + public function testOverrulesWithPartial() : void + { + $without = new Index('without', ['col1', 'col2'], true, false, [], []); + $partial = new Index('partial', ['col1', 'col2'], true, false, [], ['where' => 'col1 IS NULL']); + $another = new Index('another', ['col1', 'col2'], true, false, [], ['where' => 'col1 IS NULL']); + + self::assertFalse($partial->overrules($without)); + self::assertFalse($without->overrules($partial)); + + self::assertTrue($partial->overrules($partial)); + + self::assertTrue($partial->overrules($another)); + self::assertTrue($another->overrules($partial)); + } + + /** + * @param string[] $columns + * @param int[]|null[] $lengths1 + * @param int[]|null[] $lengths2 + * + * @dataProvider indexLengthProvider + */ + public function testFulfilledWithLength(array $columns, array $lengths1, array $lengths2, bool $expected) : void + { + $index1 = new Index('index1', $columns, false, false, [], ['lengths' => $lengths1]); + $index2 = new Index('index2', $columns, false, false, [], ['lengths' => $lengths2]); + + self::assertSame($expected, $index1->isFullfilledBy($index2)); + self::assertSame($expected, $index2->isFullfilledBy($index1)); + } + + /** + * @return mixed[][] + */ + public static function indexLengthProvider() : iterable + { + return [ + 'empty' => [['column'], [], [], true], + 'same' => [['column'], [64], [64], true], + 'different' => [['column'], [32], [64], false], + 'sparse-different-positions' => [['column1', 'column2'], [0 => 32], [1 => 32], false], + 'sparse-same-positions' => [['column1', 'column2'], [null, 32], [1 => 32], true], + ]; + } + + /** + * @group DBAL-220 + */ + public function testFlags() : void + { + $idx1 = $this->createIndex(); + self::assertFalse($idx1->hasFlag('clustered')); + self::assertEmpty($idx1->getFlags()); + + $idx1->addFlag('clustered'); + self::assertTrue($idx1->hasFlag('clustered')); + self::assertTrue($idx1->hasFlag('CLUSTERED')); + self::assertSame(['clustered'], $idx1->getFlags()); + + $idx1->removeFlag('clustered'); + self::assertFalse($idx1->hasFlag('clustered')); + self::assertEmpty($idx1->getFlags()); + } + + /** + * @group DBAL-285 + */ + public function testIndexQuotes() : void + { + $index = new Index('foo', ['`bar`', '`baz`']); + + self::assertTrue($index->spansColumns(['bar', 'baz'])); + self::assertTrue($index->hasColumnAtPosition('bar', 0)); + self::assertTrue($index->hasColumnAtPosition('baz', 1)); + + self::assertFalse($index->hasColumnAtPosition('bar', 1)); + self::assertFalse($index->hasColumnAtPosition('baz', 0)); + } + + public function testOptions() : void + { + $idx1 = $this->createIndex(); + self::assertFalse($idx1->hasOption('where')); + self::assertEmpty($idx1->getOptions()); + + $idx2 = $this->createIndex(false, false, ['where' => 'name IS NULL']); + self::assertTrue($idx2->hasOption('where')); + self::assertTrue($idx2->hasOption('WHERE')); + self::assertSame('name IS NULL', $idx2->getOption('where')); + self::assertSame('name IS NULL', $idx2->getOption('WHERE')); + self::assertSame(['where' => 'name IS NULL'], $idx2->getOptions()); + } +} diff --git a/tests/Unit/DBAL/Schema/SchemaDiffTest.php b/tests/Unit/DBAL/Schema/SchemaDiffTest.php new file mode 100644 index 0000000..77ddd15 --- /dev/null +++ b/tests/Unit/DBAL/Schema/SchemaDiffTest.php @@ -0,0 +1,124 @@ +createSchemaDiff(); + $platform = $this->createPlatform(true); + + $sql = $diff->toSql($platform); + + $expected = ['create_schema', 'drop_orphan_fk', 'alter_seq', 'drop_seq', 'create_seq', 'create_table', 'create_foreign_key', 'drop_table', 'alter_table']; + + self::assertEquals($expected, $sql); + } + + public function testSchemaDiffToSaveSql() : void + { + $diff = $this->createSchemaDiff(); + $platform = $this->createPlatform(false); + + $sql = $diff->toSaveSql($platform); + + $expected = ['create_schema', 'alter_seq', 'create_seq', 'create_table', 'create_foreign_key', 'alter_table']; + + self::assertEquals($expected, $sql); + } + + /** + * @return AbstractPlatform|MockObject + */ + private function createPlatform(bool $unsafe) + { + /** @var AbstractPlatform|MockObject $platform */ + $platform = $this->createMock(AbstractPlatform::class); + $platform->expects($this->exactly(1)) + ->method('getCreateSchemaSQL') + ->with('foo_ns') + ->will($this->returnValue('create_schema')); + if ($unsafe) { + $platform->expects($this->exactly(1)) + ->method('getDropSequenceSql') + ->with($this->isInstanceOf(Sequence::class)) + ->will($this->returnValue('drop_seq')); + } + $platform->expects($this->exactly(1)) + ->method('getAlterSequenceSql') + ->with($this->isInstanceOf(Sequence::class)) + ->will($this->returnValue('alter_seq')); + $platform->expects($this->exactly(1)) + ->method('getCreateSequenceSql') + ->with($this->isInstanceOf(Sequence::class)) + ->will($this->returnValue('create_seq')); + if ($unsafe) { + $platform->expects($this->exactly(1)) + ->method('getDropTableSql') + ->with($this->isInstanceOf(Table::class)) + ->will($this->returnValue('drop_table')); + } + $platform->expects($this->exactly(1)) + ->method('getCreateTableSql') + ->with($this->isInstanceOf(Table::class)) + ->will($this->returnValue(['create_table'])); + $platform->expects($this->exactly(1)) + ->method('getCreateForeignKeySQL') + ->with($this->isInstanceOf(ForeignKeyConstraint::class)) + ->will($this->returnValue('create_foreign_key')); + $platform->expects($this->exactly(1)) + ->method('getAlterTableSql') + ->with($this->isInstanceOf(TableDiff::class)) + ->will($this->returnValue(['alter_table'])); + if ($unsafe) { + $platform->expects($this->exactly(1)) + ->method('getDropForeignKeySql') + ->with( + $this->isInstanceOf(ForeignKeyConstraint::class), + $this->isInstanceOf(Table::class) + ) + ->will($this->returnValue('drop_orphan_fk')); + } + $platform->expects($this->exactly(1)) + ->method('supportsSchemas') + ->will($this->returnValue(true)); + $platform->expects($this->exactly(1)) + ->method('supportsSequences') + ->will($this->returnValue(true)); + $platform->expects($this->exactly(2)) + ->method('supportsForeignKeyConstraints') + ->will($this->returnValue(true)); + + return $platform; + } + + public function createSchemaDiff() : SchemaDiff + { + $diff = new SchemaDiff(); + $diff->newNamespaces['foo_ns'] = 'foo_ns'; + $diff->removedNamespaces['bar_ns'] = 'bar_ns'; + $diff->changedSequences['foo_seq'] = new Sequence('foo_seq'); + $diff->newSequences['bar_seq'] = new Sequence('bar_seq'); + $diff->removedSequences['baz_seq'] = new Sequence('baz_seq'); + $diff->newTables['foo_table'] = new Table('foo_table'); + $diff->removedTables['bar_table'] = new Table('bar_table'); + $diff->changedTables['baz_table'] = new TableDiff('baz_table'); + $diff->newTables['foo_table']->addColumn('foreign_id', 'integer'); + $diff->newTables['foo_table']->addForeignKeyConstraint('foreign_table', ['foreign_id'], ['id']); + $fk = new ForeignKeyConstraint(['id'], 'foreign_table', ['id']); + $fk->setLocalTable(new Table('local_table')); + $diff->orphanedForeignKeys[] = $fk; + + return $diff; + } +} diff --git a/tests/Unit/DBAL/Schema/SchemaTest.php b/tests/Unit/DBAL/Schema/SchemaTest.php new file mode 100644 index 0000000..a87b787 --- /dev/null +++ b/tests/Unit/DBAL/Schema/SchemaTest.php @@ -0,0 +1,465 @@ +hasTable($tableName)); + + $tables = $schema->getTables(); + self::assertArrayHasKey($tableName, $tables); + self::assertSame($table, $tables[$tableName]); + self::assertSame($table, $schema->getTable($tableName)); + self::assertTrue($schema->hasTable($tableName)); + } + + public function testTableMatchingCaseInsensitive() : void + { + $table = new Table('Foo'); + + $schema = new Schema([$table]); + self::assertTrue($schema->hasTable('foo')); + self::assertTrue($schema->hasTable('FOO')); + + self::assertSame($table, $schema->getTable('FOO')); + self::assertSame($table, $schema->getTable('foo')); + self::assertSame($table, $schema->getTable('Foo')); + } + + public function testGetUnknownTableThrowsException() : void + { + $this->expectException(SchemaException::class); + + $schema = new Schema(); + $schema->getTable('unknown'); + } + + public function testCreateTableTwiceThrowsException() : void + { + $this->expectException(SchemaException::class); + + $tableName = 'foo'; + $table = new Table($tableName); + $tables = [$table, $table]; + + $schema = new Schema($tables); + } + + public function testRenameTable() : void + { + $tableName = 'foo'; + $table = new Table($tableName); + $schema = new Schema([$table]); + + self::assertTrue($schema->hasTable('foo')); + $schema->renameTable('foo', 'bar'); + self::assertFalse($schema->hasTable('foo')); + self::assertTrue($schema->hasTable('bar')); + self::assertSame($table, $schema->getTable('bar')); + } + + public function testDropTable() : void + { + $tableName = 'foo'; + $table = new Table($tableName); + $schema = new Schema([$table]); + + self::assertTrue($schema->hasTable('foo')); + + $schema->dropTable('foo'); + + self::assertFalse($schema->hasTable('foo')); + } + + public function testCreateTable() : void + { + $schema = new Schema(); + + self::assertFalse($schema->hasTable('foo')); + + $table = $schema->createTable('foo'); + + self::assertInstanceOf(Table::class, $table); + self::assertEquals('foo', $table->getName()); + self::assertTrue($schema->hasTable('foo')); + } + + public function testAddSequences() : void + { + $sequence = new Sequence('a_seq', 1, 1); + + $schema = new Schema([], [$sequence]); + + self::assertTrue($schema->hasSequence('a_seq')); + self::assertInstanceOf(Sequence::class, $schema->getSequence('a_seq')); + + $sequences = $schema->getSequences(); + self::assertArrayHasKey('public.a_seq', $sequences); + } + + public function testSequenceAccessCaseInsensitive() : void + { + $sequence = new Sequence('a_Seq'); + + $schema = new Schema([], [$sequence]); + self::assertTrue($schema->hasSequence('a_seq')); + self::assertTrue($schema->hasSequence('a_Seq')); + self::assertTrue($schema->hasSequence('A_SEQ')); + + self::assertEquals($sequence, $schema->getSequence('a_seq')); + self::assertEquals($sequence, $schema->getSequence('a_Seq')); + self::assertEquals($sequence, $schema->getSequence('A_SEQ')); + } + + public function testGetUnknownSequenceThrowsException() : void + { + $this->expectException(SchemaException::class); + + $schema = new Schema(); + $schema->getSequence('unknown'); + } + + public function testCreateSequence() : void + { + $schema = new Schema(); + $sequence = $schema->createSequence('a_seq', 10, 20); + + self::assertEquals('a_seq', $sequence->getName()); + self::assertEquals(10, $sequence->getAllocationSize()); + self::assertEquals(20, $sequence->getInitialValue()); + + self::assertTrue($schema->hasSequence('a_seq')); + self::assertInstanceOf(Sequence::class, $schema->getSequence('a_seq')); + + $sequences = $schema->getSequences(); + self::assertArrayHasKey('public.a_seq', $sequences); + } + + public function testDropSequence() : void + { + $sequence = new Sequence('a_seq', 1, 1); + + $schema = new Schema([], [$sequence]); + + $schema->dropSequence('a_seq'); + self::assertFalse($schema->hasSequence('a_seq')); + } + + public function testAddSequenceTwiceThrowsException() : void + { + $this->expectException(SchemaException::class); + + $sequence = new Sequence('a_seq', 1, 1); + + $schema = new Schema([], [$sequence, $sequence]); + } + + public function testConfigMaxIdentifierLength() : void + { + $schemaConfig = new SchemaConfig(); + $schemaConfig->setMaxIdentifierLength(5); + + $schema = new Schema([], [], $schemaConfig); + $table = $schema->createTable('smalltable'); + $table->addColumn('long_id', 'integer'); + $table->addIndex(['long_id']); + + $index = current($table->getIndexes()); + self::assertEquals(5, strlen($index->getName())); + } + + public function testDeepClone() : void + { + $schema = new Schema(); + $sequence = $schema->createSequence('baz'); + + $tableA = $schema->createTable('foo'); + $tableA->addColumn('id', 'integer'); + + $tableB = $schema->createTable('bar'); + $tableB->addColumn('id', 'integer'); + $tableB->addColumn('foo_id', 'integer'); + $tableB->addForeignKeyConstraint($tableA, ['foo_id'], ['id']); + + $schemaNew = clone $schema; + + self::assertNotSame($sequence, $schemaNew->getSequence('baz')); + + self::assertNotSame($tableA, $schemaNew->getTable('foo')); + self::assertNotSame($tableA->getColumn('id'), $schemaNew->getTable('foo')->getColumn('id')); + + self::assertNotSame($tableB, $schemaNew->getTable('bar')); + self::assertNotSame($tableB->getColumn('id'), $schemaNew->getTable('bar')->getColumn('id')); + + $fk = $schemaNew->getTable('bar')->getForeignKeys(); + $fk = current($fk); + + $re = new ReflectionProperty($fk, '_localTable'); + $re->setAccessible(true); + + self::assertSame($schemaNew->getTable('bar'), $re->getValue($fk)); + } + + /** + * @group DBAL-219 + */ + public function testHasTableForQuotedAsset() : void + { + $schema = new Schema(); + + $tableA = $schema->createTable('foo'); + $tableA->addColumn('id', 'integer'); + + self::assertTrue($schema->hasTable('`foo`')); + } + + /** + * @group DBAL-669 + */ + public function testHasNamespace() : void + { + $schema = new Schema(); + + self::assertFalse($schema->hasNamespace('foo')); + + $schema->createTable('foo'); + + self::assertFalse($schema->hasNamespace('foo')); + + $schema->createTable('bar.baz'); + + self::assertFalse($schema->hasNamespace('baz')); + self::assertTrue($schema->hasNamespace('bar')); + self::assertFalse($schema->hasNamespace('tab')); + + $schema->createTable('tab.taz'); + + self::assertTrue($schema->hasNamespace('tab')); + } + + /** + * @group DBAL-669 + */ + public function testCreatesNamespace() : void + { + $schema = new Schema(); + + self::assertFalse($schema->hasNamespace('foo')); + + $schema->createNamespace('foo'); + + self::assertTrue($schema->hasNamespace('foo')); + self::assertTrue($schema->hasNamespace('FOO')); + self::assertTrue($schema->hasNamespace('`foo`')); + self::assertTrue($schema->hasNamespace('`FOO`')); + + $schema->createNamespace('`bar`'); + + self::assertTrue($schema->hasNamespace('bar')); + self::assertTrue($schema->hasNamespace('BAR')); + self::assertTrue($schema->hasNamespace('`bar`')); + self::assertTrue($schema->hasNamespace('`BAR`')); + + self::assertSame(['foo' => 'foo', 'bar' => '`bar`'], $schema->getNamespaces()); + } + + /** + * @group DBAL-669 + */ + public function testThrowsExceptionOnCreatingNamespaceTwice() : void + { + $schema = new Schema(); + + $schema->createNamespace('foo'); + + $this->expectException(SchemaException::class); + + $schema->createNamespace('foo'); + } + + /** + * @group DBAL-669 + */ + public function testCreatesNamespaceThroughAddingTableImplicitly() : void + { + $schema = new Schema(); + + self::assertFalse($schema->hasNamespace('foo')); + + $schema->createTable('baz'); + + self::assertFalse($schema->hasNamespace('foo')); + self::assertFalse($schema->hasNamespace('baz')); + + $schema->createTable('foo.bar'); + + self::assertTrue($schema->hasNamespace('foo')); + self::assertFalse($schema->hasNamespace('bar')); + + $schema->createTable('`baz`.bloo'); + + self::assertTrue($schema->hasNamespace('baz')); + self::assertFalse($schema->hasNamespace('bloo')); + + $schema->createTable('`baz`.moo'); + + self::assertTrue($schema->hasNamespace('baz')); + self::assertFalse($schema->hasNamespace('moo')); + } + + /** + * @group DBAL-669 + */ + public function testCreatesNamespaceThroughAddingSequenceImplicitly() : void + { + $schema = new Schema(); + + self::assertFalse($schema->hasNamespace('foo')); + + $schema->createSequence('baz'); + + self::assertFalse($schema->hasNamespace('foo')); + self::assertFalse($schema->hasNamespace('baz')); + + $schema->createSequence('foo.bar'); + + self::assertTrue($schema->hasNamespace('foo')); + self::assertFalse($schema->hasNamespace('bar')); + + $schema->createSequence('`baz`.bloo'); + + self::assertTrue($schema->hasNamespace('baz')); + self::assertFalse($schema->hasNamespace('bloo')); + + $schema->createSequence('`baz`.moo'); + + self::assertTrue($schema->hasNamespace('baz')); + self::assertFalse($schema->hasNamespace('moo')); + } + + /** + * @group DBAL-669 + */ + public function testVisitsVisitor() : void + { + $schema = new Schema(); + $visitor = $this->createMock(Visitor::class); + + $schema->createNamespace('foo'); + $schema->createNamespace('bar'); + + $schema->createTable('baz'); + $schema->createTable('bla.bloo'); + + $schema->createSequence('moo'); + $schema->createSequence('war'); + + $visitor->expects($this->once()) + ->method('acceptSchema') + ->with($schema); + + $visitor->expects($this->at(1)) + ->method('acceptTable') + ->with($schema->getTable('baz')); + + $visitor->expects($this->at(2)) + ->method('acceptTable') + ->with($schema->getTable('bla.bloo')); + + $visitor->expects($this->exactly(2)) + ->method('acceptTable'); + + $visitor->expects($this->at(3)) + ->method('acceptSequence') + ->with($schema->getSequence('moo')); + + $visitor->expects($this->at(4)) + ->method('acceptSequence') + ->with($schema->getSequence('war')); + + $visitor->expects($this->exactly(2)) + ->method('acceptSequence'); + + self::assertNull($schema->visit($visitor)); + } + + /** + * @group DBAL-669 + */ + public function testVisitsNamespaceVisitor() : void + { + $schema = new Schema(); + $visitor = $this->createMock(AbstractVisitor::class); + + $schema->createNamespace('foo'); + $schema->createNamespace('bar'); + + $schema->createTable('baz'); + $schema->createTable('bla.bloo'); + + $schema->createSequence('moo'); + $schema->createSequence('war'); + + $visitor->expects($this->once()) + ->method('acceptSchema') + ->with($schema); + + $visitor->expects($this->at(1)) + ->method('acceptNamespace') + ->with('foo'); + + $visitor->expects($this->at(2)) + ->method('acceptNamespace') + ->with('bar'); + + $visitor->expects($this->at(3)) + ->method('acceptNamespace') + ->with('bla'); + + $visitor->expects($this->exactly(3)) + ->method('acceptNamespace'); + + $visitor->expects($this->at(4)) + ->method('acceptTable') + ->with($schema->getTable('baz')); + + $visitor->expects($this->at(5)) + ->method('acceptTable') + ->with($schema->getTable('bla.bloo')); + + $visitor->expects($this->exactly(2)) + ->method('acceptTable'); + + $visitor->expects($this->at(6)) + ->method('acceptSequence') + ->with($schema->getSequence('moo')); + + $visitor->expects($this->at(7)) + ->method('acceptSequence') + ->with($schema->getSequence('war')); + + $visitor->expects($this->exactly(2)) + ->method('acceptSequence'); + + self::assertNull($schema->visit($visitor)); + } +} diff --git a/tests/Unit/DBAL/Schema/SequenceTest.php b/tests/Unit/DBAL/Schema/SequenceTest.php new file mode 100644 index 0000000..d78c656 --- /dev/null +++ b/tests/Unit/DBAL/Schema/SequenceTest.php @@ -0,0 +1,47 @@ +addColumn('id', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['id']); + + $sequence = new Sequence('foo_id_seq'); + $sequence2 = new Sequence('bar_id_seq'); + $sequence3 = new Sequence('other.foo_id_seq'); + + self::assertTrue($sequence->isAutoIncrementsFor($table)); + self::assertFalse($sequence2->isAutoIncrementsFor($table)); + self::assertFalse($sequence3->isAutoIncrementsFor($table)); + } + + public function testIsAutoincrementForCaseInsensitive() : void + { + $table = new Table('foo'); + $table->addColumn('ID', 'integer', ['autoincrement' => true]); + $table->setPrimaryKey(['ID']); + + $sequence = new Sequence('foo_id_seq'); + $sequence1 = new Sequence('foo_ID_seq'); + $sequence2 = new Sequence('bar_id_seq'); + $sequence3 = new Sequence('bar_ID_seq'); + $sequence4 = new Sequence('other.foo_id_seq'); + + self::assertTrue($sequence->isAutoIncrementsFor($table)); + self::assertTrue($sequence1->isAutoIncrementsFor($table)); + self::assertFalse($sequence2->isAutoIncrementsFor($table)); + self::assertFalse($sequence3->isAutoIncrementsFor($table)); + self::assertFalse($sequence4->isAutoIncrementsFor($table)); + } +} diff --git a/tests/Unit/DBAL/Schema/TableDiffTest.php b/tests/Unit/DBAL/Schema/TableDiffTest.php new file mode 100644 index 0000000..f4cd320 --- /dev/null +++ b/tests/Unit/DBAL/Schema/TableDiffTest.php @@ -0,0 +1,65 @@ +platform = $this->createMock(AbstractPlatform::class); + } + + /** + * @group DBAL-1013 + */ + public function testReturnsName() : void + { + $tableDiff = new TableDiff('foo'); + + self::assertEquals(new Identifier('foo'), $tableDiff->getName($this->platform)); + } + + /** + * @group DBAL-1016 + */ + public function testPrefersNameFromTableObject() : void + { + $tableMock = $this->getMockBuilder(Table::class) + ->disableOriginalConstructor() + ->getMock(); + + $tableDiff = new TableDiff('foo'); + $tableDiff->fromTable = $tableMock; + + $tableMock->expects($this->once()) + ->method('getQuotedName') + ->with($this->platform) + ->will($this->returnValue('foo')); + + self::assertEquals(new Identifier('foo'), $tableDiff->getName($this->platform)); + } + + /** + * @group DBAL-1013 + */ + public function testReturnsNewName() : void + { + $tableDiff = new TableDiff('foo'); + + self::assertFalse($tableDiff->getNewName()); + + $tableDiff->newName = 'bar'; + + self::assertEquals(new Identifier('bar'), $tableDiff->getNewName()); + } +} diff --git a/tests/Unit/DBAL/Schema/TableTest.php b/tests/Unit/DBAL/Schema/TableTest.php new file mode 100644 index 0000000..faa7168 --- /dev/null +++ b/tests/Unit/DBAL/Schema/TableTest.php @@ -0,0 +1,888 @@ +expectException(DBALException::class); + + new Table(''); + } + + public function testGetName() : void + { + $table = new Table('foo', [], [], []); + self::assertEquals('foo', $table->getName()); + } + + public function testColumns() : void + { + $type = Type::getType('integer'); + $columns = []; + $columns[] = new Column('foo', $type); + $columns[] = new Column('bar', $type); + $table = new Table('foo', $columns, [], []); + + self::assertTrue($table->hasColumn('foo')); + self::assertTrue($table->hasColumn('bar')); + self::assertFalse($table->hasColumn('baz')); + + self::assertInstanceOf(Column::class, $table->getColumn('foo')); + self::assertInstanceOf(Column::class, $table->getColumn('bar')); + + self::assertCount(2, $table->getColumns()); + } + + public function testColumnsCaseInsensitive() : void + { + $table = new Table('foo'); + $column = $table->addColumn('Foo', 'integer'); + + self::assertTrue($table->hasColumn('Foo')); + self::assertTrue($table->hasColumn('foo')); + self::assertTrue($table->hasColumn('FOO')); + + self::assertSame($column, $table->getColumn('Foo')); + self::assertSame($column, $table->getColumn('foo')); + self::assertSame($column, $table->getColumn('FOO')); + } + + public function testCreateColumn() : void + { + $type = Type::getType('integer'); + + $table = new Table('foo'); + + self::assertFalse($table->hasColumn('bar')); + $table->addColumn('bar', 'integer'); + self::assertTrue($table->hasColumn('bar')); + self::assertSame($type, $table->getColumn('bar')->getType()); + } + + public function testDropColumn() : void + { + $type = Type::getType('integer'); + $columns = []; + $columns[] = new Column('foo', $type); + $columns[] = new Column('bar', $type); + $table = new Table('foo', $columns, [], []); + + self::assertTrue($table->hasColumn('foo')); + self::assertTrue($table->hasColumn('bar')); + + $table->dropColumn('foo')->dropColumn('bar'); + + self::assertFalse($table->hasColumn('foo')); + self::assertFalse($table->hasColumn('bar')); + } + + public function testGetUnknownColumnThrowsException() : void + { + $this->expectException(SchemaException::class); + + $table = new Table('foo', [], [], []); + $table->getColumn('unknown'); + } + + public function testAddColumnTwiceThrowsException() : void + { + $this->expectException(SchemaException::class); + + $type = Type::getType('integer'); + $columns = []; + $columns[] = new Column('foo', $type); + $columns[] = new Column('foo', $type); + $table = new Table('foo', $columns, [], []); + } + + public function testCreateIndex() : void + { + $type = Type::getType('integer'); + $columns = [new Column('foo', $type), new Column('bar', $type), new Column('baz', $type)]; + $table = new Table('foo', $columns); + + $table->addIndex(['foo', 'bar'], 'foo_foo_bar_idx'); + $table->addUniqueIndex(['bar', 'baz'], 'foo_bar_baz_uniq'); + + self::assertTrue($table->hasIndex('foo_foo_bar_idx')); + self::assertTrue($table->hasIndex('foo_bar_baz_uniq')); + } + + public function testIndexCaseInsensitive() : void + { + $type = Type::getType('integer'); + $columns = [ + new Column('foo', $type), + new Column('bar', $type), + new Column('baz', $type), + ]; + $table = new Table('foo', $columns); + + $table->addIndex(['foo', 'bar', 'baz'], 'Foo_Idx'); + + self::assertTrue($table->hasIndex('foo_idx')); + self::assertTrue($table->hasIndex('Foo_Idx')); + self::assertTrue($table->hasIndex('FOO_IDX')); + } + + public function testAddIndexes() : void + { + $type = Type::getType('integer'); + $columns = [ + new Column('foo', $type), + new Column('bar', $type), + ]; + $indexes = [ + new Index('the_primary', ['foo'], true, true), + new Index('bar_idx', ['bar'], false, false), + ]; + $table = new Table('foo', $columns, $indexes, []); + + self::assertTrue($table->hasIndex('the_primary')); + self::assertTrue($table->hasIndex('bar_idx')); + self::assertFalse($table->hasIndex('some_idx')); + + self::assertInstanceOf(Index::class, $table->getPrimaryKey()); + self::assertInstanceOf(Index::class, $table->getIndex('the_primary')); + self::assertInstanceOf(Index::class, $table->getIndex('bar_idx')); + } + + public function testGetUnknownIndexThrowsException() : void + { + $this->expectException(SchemaException::class); + + $table = new Table('foo', [], [], []); + $table->getIndex('unknownIndex'); + } + + public function testAddTwoPrimaryThrowsException() : void + { + $this->expectException(SchemaException::class); + + $type = Type::getType('integer'); + $columns = [new Column('foo', $type), new Column('bar', $type)]; + $indexes = [ + new Index('the_primary', ['foo'], true, true), + new Index('other_primary', ['bar'], true, true), + ]; + $table = new Table('foo', $columns, $indexes, []); + } + + public function testAddTwoIndexesWithSameNameThrowsException() : void + { + $this->expectException(SchemaException::class); + + $type = Type::getType('integer'); + $columns = [new Column('foo', $type), new Column('bar', $type)]; + $indexes = [ + new Index('an_idx', ['foo'], false, false), + new Index('an_idx', ['bar'], false, false), + ]; + $table = new Table('foo', $columns, $indexes, []); + } + + public function testConstraints() : void + { + $constraint = new ForeignKeyConstraint([], 'foo', []); + + $tableA = new Table('foo', [], [], [$constraint]); + $constraints = $tableA->getForeignKeys(); + + self::assertCount(1, $constraints); + self::assertSame($constraint, array_shift($constraints)); + } + + public function testOptions() : void + { + $table = new Table('foo', [], [], [], false, ['foo' => 'bar']); + + self::assertTrue($table->hasOption('foo')); + self::assertEquals('bar', $table->getOption('foo')); + } + + public function testBuilderSetPrimaryKey() : void + { + $table = new Table('foo'); + + $table->addColumn('bar', 'integer'); + $table->setPrimaryKey(['bar']); + + self::assertTrue($table->hasIndex('primary')); + self::assertInstanceOf(Index::class, $table->getPrimaryKey()); + self::assertTrue($table->getIndex('primary')->isUnique()); + self::assertTrue($table->getIndex('primary')->isPrimary()); + } + + public function testBuilderAddUniqueIndex() : void + { + $table = new Table('foo'); + + $table->addColumn('bar', 'integer'); + $table->addUniqueIndex(['bar'], 'my_idx'); + + self::assertTrue($table->hasIndex('my_idx')); + self::assertTrue($table->getIndex('my_idx')->isUnique()); + self::assertFalse($table->getIndex('my_idx')->isPrimary()); + } + + public function testBuilderAddIndex() : void + { + $table = new Table('foo'); + + $table->addColumn('bar', 'integer'); + $table->addIndex(['bar'], 'my_idx'); + + self::assertTrue($table->hasIndex('my_idx')); + self::assertFalse($table->getIndex('my_idx')->isUnique()); + self::assertFalse($table->getIndex('my_idx')->isPrimary()); + } + + public function testBuilderAddIndexWithInvalidNameThrowsException() : void + { + $this->expectException(SchemaException::class); + + $table = new Table('foo'); + $table->addColumn('bar', 'integer'); + $table->addIndex(['bar'], 'invalid name %&/'); + } + + public function testBuilderAddIndexWithUnknownColumnThrowsException() : void + { + $this->expectException(SchemaException::class); + + $table = new Table('foo'); + $table->addIndex(['bar'], 'invalidName'); + } + + public function testBuilderOptions() : void + { + $table = new Table('foo'); + $table->addOption('foo', 'bar'); + self::assertTrue($table->hasOption('foo')); + self::assertEquals('bar', $table->getOption('foo')); + } + + public function testAddForeignKeyConstraintUnknownLocalColumnThrowsException() : void + { + $this->expectException(SchemaException::class); + + $table = new Table('foo'); + $table->addColumn('id', 'integer'); + + $foreignTable = new Table('bar'); + $foreignTable->addColumn('id', 'integer'); + + $table->addForeignKeyConstraint($foreignTable, ['foo'], ['id']); + } + + public function testAddForeignKeyConstraintUnknownForeignColumnThrowsException() : void + { + $this->expectException(SchemaException::class); + + $table = new Table('foo'); + $table->addColumn('id', 'integer'); + + $foreignTable = new Table('bar'); + $foreignTable->addColumn('id', 'integer'); + + $table->addForeignKeyConstraint($foreignTable, ['id'], ['foo']); + } + + public function testAddForeignKeyConstraint() : void + { + $table = new Table('foo'); + $table->addColumn('id', 'integer'); + + $foreignTable = new Table('bar'); + $foreignTable->addColumn('id', 'integer'); + + $table->addForeignKeyConstraint($foreignTable, ['id'], ['id'], ['foo' => 'bar']); + + $constraints = $table->getForeignKeys(); + self::assertCount(1, $constraints); + $constraint = current($constraints); + + self::assertInstanceOf(ForeignKeyConstraint::class, $constraint); + + self::assertTrue($constraint->hasOption('foo')); + self::assertEquals('bar', $constraint->getOption('foo')); + } + + public function testAddIndexWithCaseSensitiveColumnProblem() : void + { + $table = new Table('foo'); + $table->addColumn('id', 'integer'); + + $table->addIndex(['ID'], 'my_idx'); + + self::assertTrue($table->hasIndex('my_idx')); + self::assertEquals(['ID'], $table->getIndex('my_idx')->getColumns()); + self::assertTrue($table->getIndex('my_idx')->spansColumns(['id'])); + } + + public function testAddPrimaryKeyColumnsAreExplicitlySetToNotNull() : void + { + $table = new Table('foo'); + $column = $table->addColumn('id', 'integer', ['notnull' => false]); + + self::assertFalse($column->getNotnull()); + + $table->setPrimaryKey(['id']); + + self::assertTrue($column->getNotnull()); + } + + /** + * @group DDC-133 + */ + public function testAllowImplicitSchemaTableInAutogeneratedIndexNames() : void + { + $table = new Table('foo.bar'); + $table->addColumn('baz', 'integer', []); + $table->addIndex(['baz']); + + self::assertCount(1, $table->getIndexes()); + } + + /** + * @group DBAL-50 + */ + public function testAddForeignKeyIndexImplicitly() : void + { + $table = new Table('foo'); + $table->addColumn('id', 'integer'); + + $foreignTable = new Table('bar'); + $foreignTable->addColumn('id', 'integer'); + + $table->addForeignKeyConstraint($foreignTable, ['id'], ['id'], ['foo' => 'bar']); + + $indexes = $table->getIndexes(); + self::assertCount(1, $indexes); + $index = current($indexes); + + self::assertTrue($table->hasIndex($index->getName())); + self::assertEquals(['id'], $index->getColumns()); + } + + /** + * @group DBAL-1063 + */ + public function testAddForeignKeyDoesNotCreateDuplicateIndex() : void + { + $table = new Table('foo'); + $table->addColumn('bar', 'integer'); + $table->addIndex(['bar'], 'bar_idx'); + + $foreignTable = new Table('bar'); + $foreignTable->addColumn('foo', 'integer'); + + $table->addForeignKeyConstraint($foreignTable, ['bar'], ['foo']); + + self::assertCount(1, $table->getIndexes()); + self::assertTrue($table->hasIndex('bar_idx')); + self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns()); + } + + /** + * @group DBAL-1063 + */ + public function testAddForeignKeyAddsImplicitIndexIfIndexColumnsDoNotSpan() : void + { + $table = new Table('foo'); + $table->addColumn('bar', 'integer'); + $table->addColumn('baz', 'string'); + $table->addColumn('bloo', 'string'); + $table->addIndex(['baz', 'bar'], 'composite_idx'); + $table->addIndex(['bar', 'baz', 'bloo'], 'full_idx'); + + $foreignTable = new Table('bar'); + $foreignTable->addColumn('foo', 'integer'); + $foreignTable->addColumn('baz', 'string'); + + $table->addForeignKeyConstraint($foreignTable, ['bar', 'baz'], ['foo', 'baz']); + + self::assertCount(3, $table->getIndexes()); + self::assertTrue($table->hasIndex('composite_idx')); + self::assertTrue($table->hasIndex('full_idx')); + self::assertTrue($table->hasIndex('idx_8c73652176ff8caa78240498')); + self::assertSame(['baz', 'bar'], $table->getIndex('composite_idx')->getColumns()); + self::assertSame(['bar', 'baz', 'bloo'], $table->getIndex('full_idx')->getColumns()); + self::assertSame(['bar', 'baz'], $table->getIndex('idx_8c73652176ff8caa78240498')->getColumns()); + } + + /** + * @group DBAL-50 + * @group DBAL-1063 + */ + public function testOverrulingIndexDoesNotDropOverruledIndex() : void + { + $table = new Table('bar'); + $table->addColumn('baz', 'integer', []); + $table->addIndex(['baz']); + + $indexes = $table->getIndexes(); + self::assertCount(1, $indexes); + $index = current($indexes); + + $table->addUniqueIndex(['baz']); + self::assertCount(2, $table->getIndexes()); + self::assertTrue($table->hasIndex($index->getName())); + } + + /** + * @group DBAL-1063 + */ + public function testAllowsAddingDuplicateIndexesBasedOnColumns() : void + { + $table = new Table('foo'); + $table->addColumn('bar', 'integer'); + $table->addIndex(['bar'], 'bar_idx'); + $table->addIndex(['bar'], 'duplicate_idx'); + + self::assertCount(2, $table->getIndexes()); + self::assertTrue($table->hasIndex('bar_idx')); + self::assertTrue($table->hasIndex('duplicate_idx')); + self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns()); + self::assertSame(['bar'], $table->getIndex('duplicate_idx')->getColumns()); + } + + /** + * @group DBAL-1063 + */ + public function testAllowsAddingFulfillingIndexesBasedOnColumns() : void + { + $table = new Table('foo'); + $table->addColumn('bar', 'integer'); + $table->addColumn('baz', 'string'); + $table->addIndex(['bar'], 'bar_idx'); + $table->addIndex(['bar', 'baz'], 'fulfilling_idx'); + + self::assertCount(2, $table->getIndexes()); + self::assertTrue($table->hasIndex('bar_idx')); + self::assertTrue($table->hasIndex('fulfilling_idx')); + self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns()); + self::assertSame(['bar', 'baz'], $table->getIndex('fulfilling_idx')->getColumns()); + } + + /** + * @group DBAL-50 + * @group DBAL-1063 + */ + public function testPrimaryKeyOverrulingUniqueIndexDoesNotDropUniqueIndex() : void + { + $table = new Table('bar'); + $table->addColumn('baz', 'integer', []); + $table->addUniqueIndex(['baz'], 'idx_unique'); + + $table->setPrimaryKey(['baz']); + + $indexes = $table->getIndexes(); + self::assertCount(2, $indexes, 'Table should only contain both the primary key table index and the unique one, even though it was overruled.'); + + self::assertTrue($table->hasPrimaryKey()); + self::assertTrue($table->hasIndex('idx_unique')); + } + + public function testAddingFulfillingRegularIndexOverridesImplicitForeignKeyConstraintIndex() : void + { + $foreignTable = new Table('foreign'); + $foreignTable->addColumn('id', 'integer'); + + $localTable = new Table('local'); + $localTable->addColumn('id', 'integer'); + $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']); + + self::assertCount(1, $localTable->getIndexes()); + + $localTable->addIndex(['id'], 'explicit_idx'); + + self::assertCount(1, $localTable->getIndexes()); + self::assertTrue($localTable->hasIndex('explicit_idx')); + } + + public function testAddingFulfillingUniqueIndexOverridesImplicitForeignKeyConstraintIndex() : void + { + $foreignTable = new Table('foreign'); + $foreignTable->addColumn('id', 'integer'); + + $localTable = new Table('local'); + $localTable->addColumn('id', 'integer'); + $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']); + + self::assertCount(1, $localTable->getIndexes()); + + $localTable->addUniqueIndex(['id'], 'explicit_idx'); + + self::assertCount(1, $localTable->getIndexes()); + self::assertTrue($localTable->hasIndex('explicit_idx')); + } + + public function testAddingFulfillingPrimaryKeyOverridesImplicitForeignKeyConstraintIndex() : void + { + $foreignTable = new Table('foreign'); + $foreignTable->addColumn('id', 'integer'); + + $localTable = new Table('local'); + $localTable->addColumn('id', 'integer'); + $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']); + + self::assertCount(1, $localTable->getIndexes()); + + $localTable->setPrimaryKey(['id'], 'explicit_idx'); + + self::assertCount(1, $localTable->getIndexes()); + self::assertTrue($localTable->hasIndex('explicit_idx')); + } + + public function testAddingFulfillingExplicitIndexOverridingImplicitForeignKeyConstraintIndexWithSameNameDoesNotThrowException() : void + { + $foreignTable = new Table('foreign'); + $foreignTable->addColumn('id', 'integer'); + + $localTable = new Table('local'); + $localTable->addColumn('id', 'integer'); + $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']); + + self::assertCount(1, $localTable->getIndexes()); + self::assertTrue($localTable->hasIndex('IDX_8BD688E8BF396750')); + + $implicitIndex = $localTable->getIndex('IDX_8BD688E8BF396750'); + + $localTable->addIndex(['id'], 'IDX_8BD688E8BF396750'); + + self::assertCount(1, $localTable->getIndexes()); + self::assertTrue($localTable->hasIndex('IDX_8BD688E8BF396750')); + self::assertNotSame($implicitIndex, $localTable->getIndex('IDX_8BD688E8BF396750')); + } + + /** + * @group DBAL-64 + */ + public function testQuotedTableName() : void + { + $table = new Table('`bar`'); + + $mysqlPlatform = new MySqlPlatform(); + $sqlitePlatform = new SqlitePlatform(); + + self::assertEquals('bar', $table->getName()); + self::assertEquals('`bar`', $table->getQuotedName($mysqlPlatform)); + self::assertEquals('"bar"', $table->getQuotedName($sqlitePlatform)); + } + + /** + * @group DBAL-79 + */ + public function testTableHasPrimaryKey() : void + { + $table = new Table('test'); + + self::assertFalse($table->hasPrimaryKey()); + + $table->addColumn('foo', 'integer'); + $table->setPrimaryKey(['foo']); + + self::assertTrue($table->hasPrimaryKey()); + } + + /** + * @group DBAL-91 + */ + public function testAddIndexWithQuotedColumns() : void + { + $table = new Table('test'); + $table->addColumn('"foo"', 'integer'); + $table->addColumn('bar', 'integer'); + $table->addIndex(['"foo"', '"bar"']); + + self::assertTrue($table->columnsAreIndexed(['"foo"', '"bar"'])); + } + + /** + * @group DBAL-91 + */ + public function testAddForeignKeyWithQuotedColumnsAndTable() : void + { + $table = new Table('test'); + $table->addColumn('"foo"', 'integer'); + $table->addColumn('bar', 'integer'); + $table->addForeignKeyConstraint('"boing"', ['"foo"', '"bar"'], ['id']); + + self::assertCount(1, $table->getForeignKeys()); + } + + /** + * @group DBAL-177 + */ + public function testQuoteSchemaPrefixed() : void + { + $table = new Table('`test`.`test`'); + self::assertEquals('test.test', $table->getName()); + self::assertEquals('`test`.`test`', $table->getQuotedName(new MySqlPlatform())); + } + + /** + * @group DBAL-204 + */ + public function testFullQualifiedTableName() : void + { + $table = new Table('`test`.`test`'); + self::assertEquals('test.test', $table->getFullQualifiedName('test')); + self::assertEquals('test.test', $table->getFullQualifiedName('other')); + + $table = new Table('test'); + self::assertEquals('test.test', $table->getFullQualifiedName('test')); + self::assertEquals('other.test', $table->getFullQualifiedName('other')); + } + + /** + * @group DBAL-224 + */ + public function testDropIndex() : void + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->addIndex(['id'], 'idx'); + + self::assertTrue($table->hasIndex('idx')); + + $table->dropIndex('idx'); + self::assertFalse($table->hasIndex('idx')); + } + + /** + * @group DBAL-224 + */ + public function testDropPrimaryKey() : void + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->setPrimaryKey(['id']); + + self::assertTrue($table->hasPrimaryKey()); + + $table->dropPrimaryKey(); + self::assertFalse($table->hasPrimaryKey()); + } + + /** + * @group DBAL-234 + */ + public function testRenameIndex() : void + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->addColumn('foo', 'integer'); + $table->addColumn('bar', 'integer'); + $table->addColumn('baz', 'integer'); + $table->setPrimaryKey(['id'], 'pk'); + $table->addIndex(['foo'], 'idx', ['flag']); + $table->addUniqueIndex(['bar', 'baz'], 'uniq'); + + // Rename to custom name. + self::assertSame($table, $table->renameIndex('pk', 'pk_new')); + self::assertSame($table, $table->renameIndex('idx', 'idx_new')); + self::assertSame($table, $table->renameIndex('uniq', 'uniq_new')); + + self::assertTrue($table->hasPrimaryKey()); + self::assertTrue($table->hasIndex('pk_new')); + self::assertTrue($table->hasIndex('idx_new')); + self::assertTrue($table->hasIndex('uniq_new')); + + self::assertFalse($table->hasIndex('pk')); + self::assertFalse($table->hasIndex('idx')); + self::assertFalse($table->hasIndex('uniq')); + + self::assertEquals(new Index('pk_new', ['id'], true, true), $table->getPrimaryKey()); + self::assertEquals(new Index('pk_new', ['id'], true, true), $table->getIndex('pk_new')); + self::assertEquals( + new Index('idx_new', ['foo'], false, false, ['flag']), + $table->getIndex('idx_new') + ); + self::assertEquals(new Index('uniq_new', ['bar', 'baz'], true), $table->getIndex('uniq_new')); + + // Rename to auto-generated name. + self::assertSame($table, $table->renameIndex('pk_new', null)); + self::assertSame($table, $table->renameIndex('idx_new', null)); + self::assertSame($table, $table->renameIndex('uniq_new', null)); + + self::assertTrue($table->hasPrimaryKey()); + self::assertTrue($table->hasIndex('primary')); + self::assertTrue($table->hasIndex('IDX_D87F7E0C8C736521')); + self::assertTrue($table->hasIndex('UNIQ_D87F7E0C76FF8CAA78240498')); + + self::assertFalse($table->hasIndex('pk_new')); + self::assertFalse($table->hasIndex('idx_new')); + self::assertFalse($table->hasIndex('uniq_new')); + + self::assertEquals(new Index('primary', ['id'], true, true), $table->getPrimaryKey()); + self::assertEquals(new Index('primary', ['id'], true, true), $table->getIndex('primary')); + self::assertEquals( + new Index('IDX_D87F7E0C8C736521', ['foo'], false, false, ['flag']), + $table->getIndex('IDX_D87F7E0C8C736521') + ); + self::assertEquals( + new Index('UNIQ_D87F7E0C76FF8CAA78240498', ['bar', 'baz'], true), + $table->getIndex('UNIQ_D87F7E0C76FF8CAA78240498') + ); + + // Rename to same name (changed case). + self::assertSame($table, $table->renameIndex('primary', 'PRIMARY')); + self::assertSame($table, $table->renameIndex('IDX_D87F7E0C8C736521', 'idx_D87F7E0C8C736521')); + self::assertSame($table, $table->renameIndex('UNIQ_D87F7E0C76FF8CAA78240498', 'uniq_D87F7E0C76FF8CAA78240498')); + + self::assertTrue($table->hasPrimaryKey()); + self::assertTrue($table->hasIndex('primary')); + self::assertTrue($table->hasIndex('IDX_D87F7E0C8C736521')); + self::assertTrue($table->hasIndex('UNIQ_D87F7E0C76FF8CAA78240498')); + } + + /** + * @group DBAL-2508 + */ + public function testKeepsIndexOptionsOnRenamingRegularIndex() : void + { + $table = new Table('foo'); + $table->addColumn('id', 'integer'); + $table->addIndex(['id'], 'idx_bar', [], ['where' => '1 = 1']); + + $table->renameIndex('idx_bar', 'idx_baz'); + + self::assertSame(['where' => '1 = 1'], $table->getIndex('idx_baz')->getOptions()); + } + + /** + * @group DBAL-2508 + */ + public function testKeepsIndexOptionsOnRenamingUniqueIndex() : void + { + $table = new Table('foo'); + $table->addColumn('id', 'integer'); + $table->addUniqueIndex(['id'], 'idx_bar', ['where' => '1 = 1']); + + $table->renameIndex('idx_bar', 'idx_baz'); + + self::assertSame(['where' => '1 = 1'], $table->getIndex('idx_baz')->getOptions()); + } + + /** + * @group DBAL-234 + */ + public function testThrowsExceptionOnRenamingNonExistingIndex() : void + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->addIndex(['id'], 'idx'); + + $this->expectException(SchemaException::class); + + $table->renameIndex('foo', 'bar'); + } + + /** + * @group DBAL-234 + */ + public function testThrowsExceptionOnRenamingToAlreadyExistingIndex() : void + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->addColumn('foo', 'integer'); + $table->addIndex(['id'], 'idx_id'); + $table->addIndex(['foo'], 'idx_foo'); + + $this->expectException(SchemaException::class); + + $table->renameIndex('idx_id', 'idx_foo'); + } + + /** + * @dataProvider getNormalizesAssetNames + * @group DBAL-831 + */ + public function testNormalizesColumnNames(string $assetName) : void + { + $table = new Table('test'); + + $table->addColumn($assetName, 'integer'); + $table->addIndex([$assetName], $assetName); + $table->addForeignKeyConstraint('test', [$assetName], [$assetName], [], $assetName); + + self::assertTrue($table->hasColumn($assetName)); + self::assertTrue($table->hasColumn('foo')); + self::assertInstanceOf(Column::class, $table->getColumn($assetName)); + self::assertInstanceOf(Column::class, $table->getColumn('foo')); + + self::assertTrue($table->hasIndex($assetName)); + self::assertTrue($table->hasIndex('foo')); + self::assertInstanceOf(Index::class, $table->getIndex($assetName)); + self::assertInstanceOf(Index::class, $table->getIndex('foo')); + + self::assertTrue($table->hasForeignKey($assetName)); + self::assertTrue($table->hasForeignKey('foo')); + self::assertInstanceOf(ForeignKeyConstraint::class, $table->getForeignKey($assetName)); + self::assertInstanceOf(ForeignKeyConstraint::class, $table->getForeignKey('foo')); + + $table->renameIndex($assetName, $assetName); + self::assertTrue($table->hasIndex($assetName)); + self::assertTrue($table->hasIndex('foo')); + + $table->renameIndex($assetName, 'foo'); + self::assertTrue($table->hasIndex($assetName)); + self::assertTrue($table->hasIndex('foo')); + + $table->renameIndex('foo', $assetName); + self::assertTrue($table->hasIndex($assetName)); + self::assertTrue($table->hasIndex('foo')); + + $table->renameIndex($assetName, 'bar'); + self::assertFalse($table->hasIndex($assetName)); + self::assertFalse($table->hasIndex('foo')); + self::assertTrue($table->hasIndex('bar')); + + $table->renameIndex('bar', $assetName); + + $table->dropColumn($assetName); + $table->dropIndex($assetName); + $table->removeForeignKey($assetName); + + self::assertFalse($table->hasColumn($assetName)); + self::assertFalse($table->hasColumn('foo')); + self::assertFalse($table->hasIndex($assetName)); + self::assertFalse($table->hasIndex('foo')); + self::assertFalse($table->hasForeignKey($assetName)); + self::assertFalse($table->hasForeignKey('foo')); + } + + /** + * @return mixed[][] + */ + public static function getNormalizesAssetNames() : iterable + { + return [ + ['foo'], + ['FOO'], + ['`foo`'], + ['`FOO`'], + ['"foo"'], + ['"FOO"'], + ['"foo"'], + ['"FOO"'], + ]; + } +} diff --git a/tests/Unit/DBAL/StatementTest.php b/tests/Unit/DBAL/StatementTest.php new file mode 100644 index 0000000..1186103 --- /dev/null +++ b/tests/Unit/DBAL/StatementTest.php @@ -0,0 +1,153 @@ +pdoStatement = $this->getMockBuilder(PDOStatement::class) + ->setMethods(['execute', 'bindParam', 'bindValue']) + ->getMock(); + + $driverConnection = $this->createMock(DriverConnection::class); + $driverConnection->expects($this->any()) + ->method('prepare') + ->will($this->returnValue($this->pdoStatement)); + + $driver = $this->createMock(Driver::class); + + $this->conn = $this->getMockBuilder(Connection::class) + ->setConstructorArgs([[], $driver]) + ->getMock(); + $this->conn->expects($this->atLeastOnce()) + ->method('getWrappedConnection') + ->will($this->returnValue($driverConnection)); + + $this->configuration = $this->createMock(Configuration::class); + $this->conn->expects($this->any()) + ->method('getConfiguration') + ->will($this->returnValue($this->configuration)); + + $this->conn->expects($this->any()) + ->method('getDriver') + ->will($this->returnValue($driver)); + } + + public function testExecuteCallsLoggerStartQueryWithParametersWhenValuesBound() : void + { + $name = 'foo'; + $var = 'bar'; + $type = ParameterType::STRING; + $values = [$name => $var]; + $types = [$name => $type]; + $sql = ''; + + $logger = $this->createMock(SQLLogger::class); + $logger->expects($this->once()) + ->method('startQuery') + ->with($this->equalTo($sql), $this->equalTo($values), $this->equalTo($types)); + + $this->configuration->expects($this->once()) + ->method('getSQLLogger') + ->will($this->returnValue($logger)); + + $statement = new Statement($sql, $this->conn); + $statement->bindValue($name, $var, $type); + $statement->execute(); + } + + public function testExecuteCallsLoggerStartQueryWithParametersWhenParamsPassedToExecute() : void + { + $name = 'foo'; + $var = 'bar'; + $values = [$name => $var]; + $types = []; + $sql = ''; + + $logger = $this->createMock(SQLLogger::class); + $logger->expects($this->once()) + ->method('startQuery') + ->with($this->equalTo($sql), $this->equalTo($values), $this->equalTo($types)); + + $this->configuration->expects($this->once()) + ->method('getSQLLogger') + ->will($this->returnValue($logger)); + + $statement = new Statement($sql, $this->conn); + $statement->execute($values); + } + + public function testExecuteCallsStartQueryWithTheParametersBoundViaBindParam() : void + { + $name = 'foo'; + $var = 'bar'; + $values = [$name => $var]; + $types = [$name => ParameterType::STRING]; + $sql = ''; + + $logger = $this->createMock(SQLLogger::class); + $logger->expects(self::once()) + ->method('startQuery') + ->with(self::equalTo($sql), self::equalTo($values), self::equalTo($types)); + + $this->configuration->expects(self::once()) + ->method('getSQLLogger') + ->willReturn($logger); + + $statement = new Statement($sql, $this->conn); + $statement->bindParam($name, $var); + $statement->execute(); + } + + public function testExecuteCallsLoggerStopQueryOnException() : void + { + $logger = $this->createMock(SQLLogger::class); + + $this->configuration->expects($this->once()) + ->method('getSQLLogger') + ->will($this->returnValue($logger)); + + // Needed to satisfy construction of DBALException + $this->conn->expects($this->any()) + ->method('resolveParams') + ->will($this->returnValue([])); + + $logger->expects($this->once()) + ->method('startQuery'); + + $logger->expects($this->once()) + ->method('stopQuery'); + + $this->pdoStatement->expects($this->once()) + ->method('execute') + ->will($this->throwException(new Exception('Mock test exception'))); + + $statement = new Statement('', $this->conn); + + $this->expectException(DBALException::class); + + $statement->execute(); + } +} diff --git a/tests/Unit/DBAL/UtilTest.php b/tests/Unit/DBAL/UtilTest.php new file mode 100644 index 0000000..393c4e7 --- /dev/null +++ b/tests/Unit/DBAL/UtilTest.php @@ -0,0 +1,81 @@ + ':param1'], + ], + [ + 'SELECT name FROM users WHERE id = ? AND status = ?', + 'SELECT name FROM users WHERE id = :param1 AND status = :param2', + [1 => ':param1', 2 => ':param2'], + ], + [ + "UPDATE users SET name = '???', status = ?", + "UPDATE users SET name = '???', status = :param1", + [1 => ':param1'], + ], + [ + "UPDATE users SET status = ?, name = '???'", + "UPDATE users SET status = :param1, name = '???'", + [1 => ':param1'], + ], + [ + "UPDATE users SET foo = ?, name = '???', status = ?", + "UPDATE users SET foo = :param1, name = '???', status = :param2", + [1 => ':param1', 2 => ':param2'], + ], + [ + 'UPDATE users SET name = "???", status = ?', + 'UPDATE users SET name = "???", status = :param1', + [1 => ':param1'], + ], + [ + 'UPDATE users SET status = ?, name = "???"', + 'UPDATE users SET status = :param1, name = "???"', + [1 => ':param1'], + ], + [ + 'UPDATE users SET foo = ?, name = "???", status = ?', + 'UPDATE users SET foo = :param1, name = "???", status = :param2', + [1 => ':param1', 2 => ':param2'], + ], + [ + 'SELECT * FROM users WHERE id = ? AND name = "" AND status = ?', + 'SELECT * FROM users WHERE id = :param1 AND name = "" AND status = :param2', + [1 => ':param1', 2 => ':param2'], + ], + [ + "SELECT * FROM users WHERE id = ? AND name = '' AND status = ?", + "SELECT * FROM users WHERE id = :param1 AND name = '' AND status = :param2", + [1 => ':param1', 2 => ':param2'], + ], + ]; + } + + /** + * @param mixed[] $expectedOutputParamsMap + * + * @dataProvider dataConvertPositionalToNamedParameters + */ + public function testConvertPositionalToNamedParameters(string $inputSQL, string $expectedOutputSQL, array $expectedOutputParamsMap) : void + { + [$statement, $params] = OCI8Statement::convertPositionalToNamedPlaceholders($inputSQL); + + self::assertEquals($expectedOutputSQL, $statement); + self::assertEquals($expectedOutputParamsMap, $params); + } +} diff --git a/tests/Unit/Types/CommentedType.php b/tests/Unit/Types/CommentedType.php new file mode 100644 index 0000000..7b63a24 --- /dev/null +++ b/tests/Unit/Types/CommentedType.php @@ -0,0 +1,34 @@ +getName()); + } + + /** + * {@inheritDoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/tests/Unit/Types/MySqlPointType.php b/tests/Unit/Types/MySqlPointType.php new file mode 100644 index 0000000..4ee897a --- /dev/null +++ b/tests/Unit/Types/MySqlPointType.php @@ -0,0 +1,34 @@ +getName()); + } + + /** + * {@inheritDoc} + */ + public function getMappedDatabaseTypes(AbstractPlatform $platform) + { + return ['point']; + } +} diff --git a/tests/travis/install-postgres-10.sh b/tests/travis/install-postgres-10.sh new file mode 100755 index 0000000..8804124 --- /dev/null +++ b/tests/travis/install-postgres-10.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -ex + +echo "Installing Postgres 10" +sudo service postgresql stop +sudo apt-get remove -q 'postgresql-*' +sudo apt-get update -q +sudo apt-get install -q postgresql-10 postgresql-client-10 +sudo cp /etc/postgresql/{9.6,10}/main/pg_hba.conf + +echo "Restarting Postgres 10" +sudo service postgresql restart diff --git a/tests/travis/install-postgres-11.sh b/tests/travis/install-postgres-11.sh new file mode 100755 index 0000000..2ef1aab --- /dev/null +++ b/tests/travis/install-postgres-11.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -ex + +echo "Preparing Postgres 11" + +sudo service postgresql stop || true + +sudo docker run -d --name postgres11 -p 5432:5432 postgres:11.1 +sudo docker exec -i postgres11 bash <<< 'until pg_isready -U postgres > /dev/null 2>&1 ; do sleep 1; done' + +echo "Postgres 11 ready" diff --git a/tests/travis/pgsql.travis.xml b/tests/travis/pgsql.travis.xml new file mode 100644 index 0000000..c328cd3 --- /dev/null +++ b/tests/travis/pgsql.travis.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + ../Doctrine/Tests/DBAL + + + + + + ../../lib/Doctrine + + + + + + + + + + performance + locking_functional + + + From 17d189e08b04dc8569fe49f7615934bab6b9eba2 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Thu, 18 Jul 2019 23:30:50 +0300 Subject: [PATCH 06/38] some fixes --- src/Connectors/ConnectionFactory.php | 5 +- .../Schema/Drivers/AbstractDoctrineDriver.php | 2 + src/Schema/Drivers/DoctrineDriver.php | 1 + .../Drivers/Platforms/SQL100Platform.php | 17 +- .../Drivers/Platforms/SQL91Platform.php | 18 +- .../Drivers/Platforms/SQL92Platform.php | 24 +- .../Drivers/Platforms/SQL94Platform.php | 2 +- src/Schema/Drivers/Platforms/SQLPlatform.php | 2 + tests/Functional/DBAL/BlobTest.php | 164 -- tests/Functional/DBAL/ConnectionTest.php | 327 ---- tests/Functional/DBAL/DataAccessTest.php | 972 ---------- .../DBAL/Driver/AbstractDriverTest.php | 69 - .../DBAL/Driver/PDOConnectionTest.php | 71 - .../DBAL/Driver/PDOPgSql/DriverTest.php | 79 - .../DBAL/Driver/PDOPgsqlConnectionTest.php | 61 - tests/Functional/DBAL/ExceptionTest.php | 426 ----- .../DBAL/LikeWildcardsEscapingTest.php | 28 - tests/Functional/DBAL/LoggingTest.php | 55 - .../DBAL/MasterSlaveConnectionTest.php | 241 --- .../Functional/DBAL/ModifyLimitQueryTest.php | 185 -- tests/Functional/DBAL/NamedParametersTest.php | 224 --- tests/Functional/DBAL/PDOStatementTest.php | 54 - .../DBAL/Platform/DateExpressionTest.php | 55 - .../DBAL/Platform/DefaultExpressionTest.php | 60 - .../Functional/DBAL/Platform/QuotingTest.php | 32 - tests/Functional/DBAL/PortabilityTest.php | 177 -- tests/Functional/DBAL/ResultCacheTest.php | 251 --- .../Functional/DBAL/Schema/ComparatorTest.php | 55 - .../DBAL/Schema/Db2SchemaManagerTest.php | 34 - .../DBAL/Schema/DefaultValueTest.php | 155 -- .../DBAL/Schema/DrizzleSchemaManagerTest.php | 48 - .../DBAL/Schema/MySqlSchemaManagerTest.php | 567 ------ .../DBAL/Schema/OracleSchemaManagerTest.php | 288 --- .../Schema/PostgreSqlSchemaManagerTest.php | 536 ------ .../Schema/SQLAnywhereSchemaManagerTest.php | 64 - .../Schema/SQLServerSchemaManagerTest.php | 347 ---- .../SchemaManagerFunctionalTestCase.php | 1597 ----------------- .../DBAL/Schema/SqliteSchemaManagerTest.php | 281 --- tests/Functional/DBAL/StatementTest.php | 322 ---- tests/Functional/DBAL/TableGeneratorTest.php | 62 - tests/Functional/DBAL/TemporaryTableTest.php | 108 -- tests/Functional/DBAL/Ticket/DBAL168Test.php | 30 - tests/Functional/DBAL/Ticket/DBAL202Test.php | 51 - tests/Functional/DBAL/Ticket/DBAL421Test.php | 56 - tests/Functional/DBAL/Ticket/DBAL461Test.php | 41 - tests/Functional/DBAL/Ticket/DBAL510Test.php | 40 - tests/Functional/DBAL/Ticket/DBAL630Test.php | 172 -- tests/Functional/DBAL/Ticket/DBAL752Test.php | 65 - tests/Functional/DBAL/TransactionTest.php | 39 - tests/Functional/DBAL/TypeConversionTest.php | 251 --- tests/Functional/DBAL/Types/BinaryTest.php | 95 - tests/Functional/DBAL/WriteTest.php | 359 ---- tests/Functional/DbalFunctionalTestCase.php | 113 -- tests/Functional/Schema/ChangeTest.php | 118 +- tests/Functional/TestUtil.php | 131 +- tests/FunctionalTestCase.php | 1 - tests/TestCase.php | 10 +- tests/Unit/DBAL/ConfigurationTest.php | 53 - tests/Unit/DBAL/ConnectionTest.php | 971 ---------- tests/Unit/DBAL/DBALExceptionTest.php | 88 - tests/Unit/DBAL/Driver/AbstractDriverTest.php | 239 --- .../Driver/AbstractPostgreSQLDriverTest.php | 125 -- .../Unit/DBAL/Driver/PDOPgSql/DriverTest.php | 120 -- tests/Unit/DBAL/DriverManagerTest.php | 542 ------ .../Platforms/AbstractPlatformTestCase.php | 1542 ---------------- .../AbstractPostgreSqlPlatformTestCase.php | 1068 ----------- .../Platforms/PostgreSQL100PlatformTest.php | 34 - .../Platforms/PostgreSQL91PlatformTest.php | 41 - .../Platforms/PostgreSQL92PlatformTest.php | 72 - .../Platforms/PostgreSQL94PlatformTest.php | 32 - .../DBAL/Platforms/PostgreSqlPlatformTest.php | 19 - .../ReservedKeywordsValidatorTest.php | 43 - tests/Unit/DBAL/SQLParserUtilsTest.php | 498 ----- tests/Unit/DBAL/Schema/ColumnDiffTest.php | 30 - tests/Unit/DBAL/Schema/ColumnTest.php | 146 -- tests/Unit/DBAL/Schema/ComparatorTest.php | 1329 -------------- .../DBAL/Schema/ForeignKeyConstraintTest.php | 59 - tests/Unit/DBAL/Schema/IndexTest.php | 191 -- tests/Unit/DBAL/Schema/SchemaDiffTest.php | 124 -- tests/Unit/DBAL/Schema/SchemaTest.php | 465 ----- tests/Unit/DBAL/Schema/SequenceTest.php | 47 - tests/Unit/DBAL/Schema/TableDiffTest.php | 65 - tests/Unit/DBAL/Schema/TableTest.php | 888 --------- tests/Unit/DBAL/StatementTest.php | 153 -- tests/Unit/DBAL/UtilTest.php | 81 - tests/Unit/Types/CommentedType.php | 34 - tests/Unit/Types/MySqlPointType.php | 34 - 87 files changed, 165 insertions(+), 18606 deletions(-) delete mode 100644 tests/Functional/DBAL/BlobTest.php delete mode 100644 tests/Functional/DBAL/ConnectionTest.php delete mode 100644 tests/Functional/DBAL/DataAccessTest.php delete mode 100644 tests/Functional/DBAL/Driver/AbstractDriverTest.php delete mode 100644 tests/Functional/DBAL/Driver/PDOConnectionTest.php delete mode 100644 tests/Functional/DBAL/Driver/PDOPgSql/DriverTest.php delete mode 100644 tests/Functional/DBAL/Driver/PDOPgsqlConnectionTest.php delete mode 100644 tests/Functional/DBAL/ExceptionTest.php delete mode 100644 tests/Functional/DBAL/LikeWildcardsEscapingTest.php delete mode 100644 tests/Functional/DBAL/LoggingTest.php delete mode 100644 tests/Functional/DBAL/MasterSlaveConnectionTest.php delete mode 100644 tests/Functional/DBAL/ModifyLimitQueryTest.php delete mode 100644 tests/Functional/DBAL/NamedParametersTest.php delete mode 100644 tests/Functional/DBAL/PDOStatementTest.php delete mode 100644 tests/Functional/DBAL/Platform/DateExpressionTest.php delete mode 100644 tests/Functional/DBAL/Platform/DefaultExpressionTest.php delete mode 100644 tests/Functional/DBAL/Platform/QuotingTest.php delete mode 100644 tests/Functional/DBAL/PortabilityTest.php delete mode 100644 tests/Functional/DBAL/ResultCacheTest.php delete mode 100644 tests/Functional/DBAL/Schema/ComparatorTest.php delete mode 100644 tests/Functional/DBAL/Schema/Db2SchemaManagerTest.php delete mode 100644 tests/Functional/DBAL/Schema/DefaultValueTest.php delete mode 100644 tests/Functional/DBAL/Schema/DrizzleSchemaManagerTest.php delete mode 100644 tests/Functional/DBAL/Schema/MySqlSchemaManagerTest.php delete mode 100644 tests/Functional/DBAL/Schema/OracleSchemaManagerTest.php delete mode 100644 tests/Functional/DBAL/Schema/PostgreSqlSchemaManagerTest.php delete mode 100644 tests/Functional/DBAL/Schema/SQLAnywhereSchemaManagerTest.php delete mode 100644 tests/Functional/DBAL/Schema/SQLServerSchemaManagerTest.php delete mode 100644 tests/Functional/DBAL/Schema/SchemaManagerFunctionalTestCase.php delete mode 100644 tests/Functional/DBAL/Schema/SqliteSchemaManagerTest.php delete mode 100644 tests/Functional/DBAL/StatementTest.php delete mode 100644 tests/Functional/DBAL/TableGeneratorTest.php delete mode 100644 tests/Functional/DBAL/TemporaryTableTest.php delete mode 100644 tests/Functional/DBAL/Ticket/DBAL168Test.php delete mode 100644 tests/Functional/DBAL/Ticket/DBAL202Test.php delete mode 100644 tests/Functional/DBAL/Ticket/DBAL421Test.php delete mode 100644 tests/Functional/DBAL/Ticket/DBAL461Test.php delete mode 100644 tests/Functional/DBAL/Ticket/DBAL510Test.php delete mode 100644 tests/Functional/DBAL/Ticket/DBAL630Test.php delete mode 100644 tests/Functional/DBAL/Ticket/DBAL752Test.php delete mode 100644 tests/Functional/DBAL/TransactionTest.php delete mode 100644 tests/Functional/DBAL/TypeConversionTest.php delete mode 100644 tests/Functional/DBAL/Types/BinaryTest.php delete mode 100644 tests/Functional/DBAL/WriteTest.php delete mode 100644 tests/Functional/DbalFunctionalTestCase.php delete mode 100644 tests/Unit/DBAL/ConfigurationTest.php delete mode 100644 tests/Unit/DBAL/ConnectionTest.php delete mode 100644 tests/Unit/DBAL/DBALExceptionTest.php delete mode 100644 tests/Unit/DBAL/Driver/AbstractDriverTest.php delete mode 100644 tests/Unit/DBAL/Driver/AbstractPostgreSQLDriverTest.php delete mode 100644 tests/Unit/DBAL/Driver/PDOPgSql/DriverTest.php delete mode 100644 tests/Unit/DBAL/DriverManagerTest.php delete mode 100644 tests/Unit/DBAL/Platforms/AbstractPlatformTestCase.php delete mode 100644 tests/Unit/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php delete mode 100644 tests/Unit/DBAL/Platforms/PostgreSQL100PlatformTest.php delete mode 100644 tests/Unit/DBAL/Platforms/PostgreSQL91PlatformTest.php delete mode 100644 tests/Unit/DBAL/Platforms/PostgreSQL92PlatformTest.php delete mode 100644 tests/Unit/DBAL/Platforms/PostgreSQL94PlatformTest.php delete mode 100644 tests/Unit/DBAL/Platforms/PostgreSqlPlatformTest.php delete mode 100644 tests/Unit/DBAL/Platforms/ReservedKeywordsValidatorTest.php delete mode 100644 tests/Unit/DBAL/SQLParserUtilsTest.php delete mode 100644 tests/Unit/DBAL/Schema/ColumnDiffTest.php delete mode 100644 tests/Unit/DBAL/Schema/ColumnTest.php delete mode 100644 tests/Unit/DBAL/Schema/ComparatorTest.php delete mode 100644 tests/Unit/DBAL/Schema/ForeignKeyConstraintTest.php delete mode 100644 tests/Unit/DBAL/Schema/IndexTest.php delete mode 100644 tests/Unit/DBAL/Schema/SchemaDiffTest.php delete mode 100644 tests/Unit/DBAL/Schema/SchemaTest.php delete mode 100644 tests/Unit/DBAL/Schema/SequenceTest.php delete mode 100644 tests/Unit/DBAL/Schema/TableDiffTest.php delete mode 100644 tests/Unit/DBAL/Schema/TableTest.php delete mode 100644 tests/Unit/DBAL/StatementTest.php delete mode 100644 tests/Unit/DBAL/UtilTest.php delete mode 100644 tests/Unit/Types/CommentedType.php delete mode 100644 tests/Unit/Types/MySqlPointType.php diff --git a/src/Connectors/ConnectionFactory.php b/src/Connectors/ConnectionFactory.php index 1eded11..0c78c2e 100644 --- a/src/Connectors/ConnectionFactory.php +++ b/src/Connectors/ConnectionFactory.php @@ -11,9 +11,6 @@ class ConnectionFactory extends ConnectionFactoryBase { protected function createConnection($driver, $connection, $database, $prefix = '', array $config = []) { - //dd($driver, $connection, $database, $prefix, $config); - $connection = new PostgresConnection($connection, $database, $prefix, $config); -// dd($connection->getName()); - return $connection; + return new PostgresConnection($connection, $database, $prefix, $config); } } diff --git a/src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php b/src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php index 904f216..6dd96a4 100644 --- a/src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php +++ b/src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php @@ -1,5 +1,7 @@ quoteStringLiteral($database) + ); } /** * {@inheritdoc} */ - protected function initializeDoctrineTypeMappings() + protected function getReservedKeywordsClass() { - parent::initializeDoctrineTypeMappings(); - - $this->doctrineTypeMapping['json'] = Types::JSON; + return PostgreSQL92Keywords::class; } /** * {@inheritdoc} */ - public function getCloseActiveDatabaseConnectionsSQL($database) + protected function initializeDoctrineTypeMappings() { - return sprintf( - 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = %s', - $this->quoteStringLiteral($database) - ); + parent::initializeDoctrineTypeMappings(); + + $this->doctrineTypeMapping['json'] = Types::JSON; } } diff --git a/src/Schema/Drivers/Platforms/SQL94Platform.php b/src/Schema/Drivers/Platforms/SQL94Platform.php index 71f95b2..19d3de5 100644 --- a/src/Schema/Drivers/Platforms/SQL94Platform.php +++ b/src/Schema/Drivers/Platforms/SQL94Platform.php @@ -14,7 +14,7 @@ class SQL94Platform extends SQL92Platform */ public function getJsonTypeDeclarationSQL(array $field) { - if (! empty($field['jsonb'])) { + if (!empty($field['jsonb'])) { return 'JSONB'; } diff --git a/src/Schema/Drivers/Platforms/SQLPlatform.php b/src/Schema/Drivers/Platforms/SQLPlatform.php index 50221dc..5d4cd81 100644 --- a/src/Schema/Drivers/Platforms/SQLPlatform.php +++ b/src/Schema/Drivers/Platforms/SQLPlatform.php @@ -1,5 +1,7 @@ addColumn('id', 'integer'); - $table->addColumn('clobfield', 'text'); - $table->addColumn('blobfield', 'blob'); - $table->setPrimaryKey(['id']); - - $sm = $this->connection->getSchemaManager(); - $sm->dropAndCreateTable($table); - } - - public function testInsert() : void - { - $ret = $this->connection->insert('blob_table', [ - 'id' => 1, - 'clobfield' => 'test', - 'blobfield' => 'test', - ], [ - ParameterType::INTEGER, - ParameterType::STRING, - ParameterType::LARGE_OBJECT, - ]); - - self::assertEquals(1, $ret); - } - - public function testInsertProcessesStream() : void - { - // https://github.com/doctrine/dbal/issues/3290 - if ($this->connection->getDriver() instanceof OCI8Driver) { - $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters'); - } - - $longBlob = str_repeat('x', 4 * 8192); // send 4 chunks - $this->connection->insert('blob_table', [ - 'id' => 1, - 'clobfield' => 'ignored', - 'blobfield' => fopen('data://text/plain,' . $longBlob, 'r'), - ], [ - ParameterType::INTEGER, - ParameterType::STRING, - ParameterType::LARGE_OBJECT, - ]); - - $this->assertBlobContains($longBlob); - } - - public function testSelect() : void - { - $this->connection->insert('blob_table', [ - 'id' => 1, - 'clobfield' => 'test', - 'blobfield' => 'test', - ], [ - ParameterType::INTEGER, - ParameterType::STRING, - ParameterType::LARGE_OBJECT, - ]); - - $this->assertBlobContains('test'); - } - - public function testUpdate() : void - { - $this->connection->insert('blob_table', [ - 'id' => 1, - 'clobfield' => 'test', - 'blobfield' => 'test', - ], [ - ParameterType::INTEGER, - ParameterType::STRING, - ParameterType::LARGE_OBJECT, - ]); - - $this->connection->update('blob_table', ['blobfield' => 'test2'], ['id' => 1], [ - ParameterType::LARGE_OBJECT, - ParameterType::INTEGER, - ]); - - $this->assertBlobContains('test2'); - } - - public function testUpdateProcessesStream() : void - { - // https://github.com/doctrine/dbal/issues/3290 - if ($this->connection->getDriver() instanceof OCI8Driver) { - $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters'); - } - - $this->connection->insert('blob_table', [ - 'id' => 1, - 'clobfield' => 'ignored', - 'blobfield' => 'test', - ], [ - ParameterType::INTEGER, - ParameterType::STRING, - ParameterType::LARGE_OBJECT, - ]); - - $this->connection->update('blob_table', [ - 'id' => 1, - 'blobfield' => fopen('data://text/plain,test2', 'r'), - ], ['id' => 1], [ - ParameterType::INTEGER, - ParameterType::LARGE_OBJECT, - ]); - - $this->assertBlobContains('test2'); - } - - public function testBindParamProcessesStream() : void - { - if ($this->connection->getDriver() instanceof OCI8Driver) { - $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters'); - } - - $stmt = $this->connection->prepare("INSERT INTO blob_table(id, clobfield, blobfield) VALUES (1, 'ignored', ?)"); - - $stream = null; - $stmt->bindParam(1, $stream, ParameterType::LARGE_OBJECT); - - // Bind param does late binding (bind by reference), so create the stream only now: - $stream = fopen('data://text/plain,test', 'r'); - - $stmt->execute(); - - $this->assertBlobContains('test'); - } - - private function assertBlobContains(string $text) : void - { - $rows = $this->connection->query('SELECT blobfield FROM blob_table')->fetchAll(FetchMode::COLUMN); - - self::assertCount(1, $rows); - - $blobValue = Type::getType('blob')->convertToPHPValue($rows[0], $this->connection->getDatabasePlatform()); - - self::assertIsResource($blobValue); - self::assertEquals($text, stream_get_contents($blobValue)); - } -} diff --git a/tests/Functional/DBAL/ConnectionTest.php b/tests/Functional/DBAL/ConnectionTest.php deleted file mode 100644 index 21d1d0f..0000000 --- a/tests/Functional/DBAL/ConnectionTest.php +++ /dev/null @@ -1,327 +0,0 @@ -resetSharedConn(); - parent::setUp(); - } - - protected function tearDown() : void - { - parent::tearDown(); - $this->resetSharedConn(); - } - - public function testGetWrappedConnection() : void - { - self::assertInstanceOf(DriverConnection::class, $this->connection->getWrappedConnection()); - } - - public function testCommitWithRollbackOnlyThrowsException() : void - { - $this->connection->beginTransaction(); - $this->connection->setRollbackOnly(); - - $this->expectException(ConnectionException::class); - $this->connection->commit(); - } - - public function testTransactionNestingBehavior() : void - { - try { - $this->connection->beginTransaction(); - self::assertEquals(1, $this->connection->getTransactionNestingLevel()); - - try { - $this->connection->beginTransaction(); - self::assertEquals(2, $this->connection->getTransactionNestingLevel()); - throw new Exception(); - $this->connection->commit(); // never reached - } catch (Throwable $e) { - $this->connection->rollBack(); - self::assertEquals(1, $this->connection->getTransactionNestingLevel()); - //no rethrow - } - self::assertTrue($this->connection->isRollbackOnly()); - - $this->connection->commit(); // should throw exception - $this->fail('Transaction commit after failed nested transaction should fail.'); - } catch (ConnectionException $e) { - self::assertEquals(1, $this->connection->getTransactionNestingLevel()); - $this->connection->rollBack(); - self::assertEquals(0, $this->connection->getTransactionNestingLevel()); - } - } - - public function testTransactionNestingBehaviorWithSavepoints() : void - { - if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) { - $this->markTestSkipped('This test requires the platform to support savepoints.'); - } - - $this->connection->setNestTransactionsWithSavepoints(true); - try { - $this->connection->beginTransaction(); - self::assertEquals(1, $this->connection->getTransactionNestingLevel()); - - try { - $this->connection->beginTransaction(); - self::assertEquals(2, $this->connection->getTransactionNestingLevel()); - $this->connection->beginTransaction(); - self::assertEquals(3, $this->connection->getTransactionNestingLevel()); - self::assertTrue($this->connection->commit()); - self::assertEquals(2, $this->connection->getTransactionNestingLevel()); - throw new Exception(); - $this->connection->commit(); // never reached - } catch (Throwable $e) { - $this->connection->rollBack(); - self::assertEquals(1, $this->connection->getTransactionNestingLevel()); - //no rethrow - } - self::assertFalse($this->connection->isRollbackOnly()); - try { - $this->connection->setNestTransactionsWithSavepoints(false); - $this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.'); - } catch (ConnectionException $e) { - self::assertTrue($this->connection->getNestTransactionsWithSavepoints()); - } - $this->connection->commit(); // should not throw exception - } catch (ConnectionException $e) { - $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.'); - $this->connection->rollBack(); - } - } - - public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction() : void - { - if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) { - $this->markTestSkipped('This test requires the platform to support savepoints.'); - } - - $this->connection->beginTransaction(); - $this->expectException(ConnectionException::class); - $this->connection->setNestTransactionsWithSavepoints(true); - } - - public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException() : void - { - if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { - $this->markTestSkipped('This test requires the platform not to support savepoints.'); - } - - $this->expectException(ConnectionException::class); - $this->expectExceptionMessage('Savepoints are not supported by this driver.'); - - $this->connection->setNestTransactionsWithSavepoints(true); - } - - public function testCreateSavepointsNotSupportedThrowsException() : void - { - if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { - $this->markTestSkipped('This test requires the platform not to support savepoints.'); - } - - $this->expectException(ConnectionException::class); - $this->expectExceptionMessage('Savepoints are not supported by this driver.'); - - $this->connection->createSavepoint('foo'); - } - - public function testReleaseSavepointsNotSupportedThrowsException() : void - { - if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { - $this->markTestSkipped('This test requires the platform not to support savepoints.'); - } - - $this->expectException(ConnectionException::class); - $this->expectExceptionMessage('Savepoints are not supported by this driver.'); - - $this->connection->releaseSavepoint('foo'); - } - - public function testRollbackSavepointsNotSupportedThrowsException() : void - { - if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { - $this->markTestSkipped('This test requires the platform not to support savepoints.'); - } - - $this->expectException(ConnectionException::class); - $this->expectExceptionMessage('Savepoints are not supported by this driver.'); - - $this->connection->rollbackSavepoint('foo'); - } - - public function testTransactionBehaviorWithRollback() : void - { - try { - $this->connection->beginTransaction(); - self::assertEquals(1, $this->connection->getTransactionNestingLevel()); - - throw new Exception(); - - $this->connection->commit(); // never reached - } catch (Throwable $e) { - self::assertEquals(1, $this->connection->getTransactionNestingLevel()); - $this->connection->rollBack(); - self::assertEquals(0, $this->connection->getTransactionNestingLevel()); - } - } - - public function testTransactionBehaviour() : void - { - try { - $this->connection->beginTransaction(); - self::assertEquals(1, $this->connection->getTransactionNestingLevel()); - $this->connection->commit(); - } catch (Throwable $e) { - $this->connection->rollBack(); - self::assertEquals(0, $this->connection->getTransactionNestingLevel()); - } - - self::assertEquals(0, $this->connection->getTransactionNestingLevel()); - } - - public function testTransactionalWithException() : void - { - try { - $this->connection->transactional(static function ($conn) : void { - /** @var Connection $conn */ - $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); - throw new RuntimeException('Ooops!'); - }); - $this->fail('Expected exception'); - } catch (RuntimeException $expected) { - self::assertEquals(0, $this->connection->getTransactionNestingLevel()); - } - } - - public function testTransactionalWithThrowable() : void - { - try { - $this->connection->transactional(static function ($conn) : void { - /** @var Connection $conn */ - $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); - throw new Error('Ooops!'); - }); - $this->fail('Expected exception'); - } catch (Error $expected) { - self::assertEquals(0, $this->connection->getTransactionNestingLevel()); - } - } - - public function testTransactional() : void - { - $res = $this->connection->transactional(static function ($conn) : void { - /** @var Connection $conn */ - $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); - }); - - self::assertNull($res); - } - - public function testTransactionalReturnValue() : void - { - $res = $this->connection->transactional(static function () { - return 42; - }); - - self::assertEquals(42, $res); - } - - /** - * Tests that the quote function accepts DBAL and PDO types. - */ - public function testQuote() : void - { - self::assertEquals( - $this->connection->quote('foo', Types::STRING), - $this->connection->quote('foo', ParameterType::STRING) - ); - } - - public function testPingDoesTriggersConnect() : void - { - self::assertTrue($this->connection->ping()); - self::assertTrue($this->connection->isConnected()); - } - - /** - * @group DBAL-1025 - */ - public function testConnectWithoutExplicitDatabaseName() : void - { - if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { - $this->markTestSkipped('Platform does not support connecting without database name.'); - } - - $params = $this->connection->getParams(); - unset($params['dbname']); - - $connection = DriverManager::getConnection( - $params, - $this->connection->getConfiguration(), - $this->connection->getEventManager() - ); - - self::assertTrue($connection->connect()); - - $connection->close(); - } - - /** - * @group DBAL-990 - */ - public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase() : void - { - if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { - $this->markTestSkipped('Platform does not support connecting without database name.'); - } - - $params = $this->connection->getParams(); - - $params['dbname'] = 'foo_bar'; - - $connection = DriverManager::getConnection( - $params, - $this->connection->getConfiguration(), - $this->connection->getEventManager() - ); - - self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform()); - self::assertFalse($connection->isConnected()); - self::assertSame($params, $connection->getParams()); - - $connection->close(); - } - - /** - * @requires extension pdo_sqlite - */ - public function testUserProvidedPDOConnection() : void - { - self::assertTrue( - DriverManager::getConnection([ - 'pdo' => new PDO('sqlite::memory:'), - ])->ping() - ); - } -} diff --git a/tests/Functional/DBAL/DataAccessTest.php b/tests/Functional/DBAL/DataAccessTest.php deleted file mode 100644 index 651aeda..0000000 --- a/tests/Functional/DBAL/DataAccessTest.php +++ /dev/null @@ -1,972 +0,0 @@ -addColumn('test_int', 'integer'); - $table->addColumn('test_string', 'string'); - $table->addColumn('test_datetime', 'datetime', ['notnull' => false]); - $table->setPrimaryKey(['test_int']); - - $sm = $this->connection->getSchemaManager(); - $sm->createTable($table); - - $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo', 'test_datetime' => '2010-01-01 10:10:10']); - self::$generated = true; - } - - public function testPrepareWithBindValue() : void - { - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $stmt = $this->connection->prepare($sql); - self::assertInstanceOf(Statement::class, $stmt); - - $stmt->bindValue(1, 1); - $stmt->bindValue(2, 'foo'); - $stmt->execute(); - - $row = $stmt->fetch(FetchMode::ASSOCIATIVE); - $row = array_change_key_case($row, CASE_LOWER); - self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row); - } - - public function testPrepareWithBindParam() : void - { - $paramInt = 1; - $paramStr = 'foo'; - - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $stmt = $this->connection->prepare($sql); - self::assertInstanceOf(Statement::class, $stmt); - - $stmt->bindParam(1, $paramInt); - $stmt->bindParam(2, $paramStr); - $stmt->execute(); - - $row = $stmt->fetch(FetchMode::ASSOCIATIVE); - $row = array_change_key_case($row, CASE_LOWER); - self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row); - } - - public function testPrepareWithFetchAll() : void - { - $paramInt = 1; - $paramStr = 'foo'; - - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $stmt = $this->connection->prepare($sql); - self::assertInstanceOf(Statement::class, $stmt); - - $stmt->bindParam(1, $paramInt); - $stmt->bindParam(2, $paramStr); - $stmt->execute(); - - $rows = $stmt->fetchAll(FetchMode::ASSOCIATIVE); - $rows[0] = array_change_key_case($rows[0], CASE_LOWER); - self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]); - } - - /** - * @group DBAL-228 - */ - public function testPrepareWithFetchAllBoth() : void - { - $paramInt = 1; - $paramStr = 'foo'; - - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $stmt = $this->connection->prepare($sql); - self::assertInstanceOf(Statement::class, $stmt); - - $stmt->bindParam(1, $paramInt); - $stmt->bindParam(2, $paramStr); - $stmt->execute(); - - $rows = $stmt->fetchAll(FetchMode::MIXED); - $rows[0] = array_change_key_case($rows[0], CASE_LOWER); - self::assertEquals(['test_int' => 1, 'test_string' => 'foo', 0 => 1, 1 => 'foo'], $rows[0]); - } - - public function testPrepareWithFetchColumn() : void - { - $paramInt = 1; - $paramStr = 'foo'; - - $sql = 'SELECT test_int FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $stmt = $this->connection->prepare($sql); - self::assertInstanceOf(Statement::class, $stmt); - - $stmt->bindParam(1, $paramInt); - $stmt->bindParam(2, $paramStr); - $stmt->execute(); - - $column = $stmt->fetchColumn(); - self::assertEquals(1, $column); - } - - public function testPrepareWithIterator() : void - { - $paramInt = 1; - $paramStr = 'foo'; - - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $stmt = $this->connection->prepare($sql); - self::assertInstanceOf(Statement::class, $stmt); - - $stmt->bindParam(1, $paramInt); - $stmt->bindParam(2, $paramStr); - $stmt->execute(); - - $rows = []; - $stmt->setFetchMode(FetchMode::ASSOCIATIVE); - foreach ($stmt as $row) { - $rows[] = array_change_key_case($row, CASE_LOWER); - } - - self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]); - } - - public function testPrepareWithQuoted() : void - { - $table = 'fetch_table'; - $paramInt = 1; - $paramStr = 'foo'; - - $stmt = $this->connection->prepare(sprintf( - 'SELECT test_int, test_string FROM %s WHERE test_int = %s AND test_string = %s', - $this->connection->quoteIdentifier($table), - $this->connection->quote($paramInt), - $this->connection->quote($paramStr) - )); - self::assertInstanceOf(Statement::class, $stmt); - } - - public function testPrepareWithExecuteParams() : void - { - $paramInt = 1; - $paramStr = 'foo'; - - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $stmt = $this->connection->prepare($sql); - self::assertInstanceOf(Statement::class, $stmt); - $stmt->execute([$paramInt, $paramStr]); - - $row = $stmt->fetch(FetchMode::ASSOCIATIVE); - self::assertNotFalse($row); - $row = array_change_key_case($row, CASE_LOWER); - self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row); - } - - public function testFetchAll() : void - { - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $data = $this->connection->fetchAll($sql, [1, 'foo']); - - self::assertCount(1, $data); - - $row = $data[0]; - self::assertCount(2, $row); - - $row = array_change_key_case($row, CASE_LOWER); - self::assertEquals(1, $row['test_int']); - self::assertEquals('foo', $row['test_string']); - } - - /** - * @group DBAL-209 - */ - public function testFetchAllWithTypes() : void - { - $datetimeString = '2010-01-01 10:10:10'; - $datetime = new DateTime($datetimeString); - - $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; - $data = $this->connection->fetchAll( - $sql, - [1, $datetime], - [ParameterType::STRING, Types::DATETIME_MUTABLE] - ); - - self::assertCount(1, $data); - - $row = $data[0]; - self::assertCount(2, $row); - - $row = array_change_key_case($row, CASE_LOWER); - self::assertEquals(1, $row['test_int']); - self::assertStringStartsWith($datetimeString, $row['test_datetime']); - } - - /** - * @group DBAL-209 - */ - public function testFetchAllWithMissingTypes() : void - { - if ($this->connection->getDriver() instanceof MySQLiDriver || - $this->connection->getDriver() instanceof SQLSrvDriver) { - $this->markTestSkipped('mysqli and sqlsrv actually supports this'); - } - - $datetimeString = '2010-01-01 10:10:10'; - $datetime = new DateTime($datetimeString); - $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; - - $this->expectException(DBALException::class); - - $this->connection->fetchAll($sql, [1, $datetime]); - } - - public function testFetchBoth() : void - { - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $row = $this->connection->executeQuery($sql, [1, 'foo'])->fetch(FetchMode::MIXED); - - self::assertNotFalse($row); - - $row = array_change_key_case($row, CASE_LOWER); - - self::assertEquals(1, $row['test_int']); - self::assertEquals('foo', $row['test_string']); - self::assertEquals(1, $row[0]); - self::assertEquals('foo', $row[1]); - } - - public function testFetchNoResult() : void - { - self::assertFalse( - $this->connection->executeQuery('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])->fetch() - ); - } - - public function testFetchAssoc() : void - { - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $row = $this->connection->fetchAssoc($sql, [1, 'foo']); - - self::assertNotFalse($row); - - $row = array_change_key_case($row, CASE_LOWER); - - self::assertEquals(1, $row['test_int']); - self::assertEquals('foo', $row['test_string']); - } - - public function testFetchAssocWithTypes() : void - { - $datetimeString = '2010-01-01 10:10:10'; - $datetime = new DateTime($datetimeString); - - $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; - $row = $this->connection->fetchAssoc( - $sql, - [1, $datetime], - [ParameterType::STRING, Types::DATETIME_MUTABLE] - ); - - self::assertNotFalse($row); - - $row = array_change_key_case($row, CASE_LOWER); - - self::assertEquals(1, $row['test_int']); - self::assertStringStartsWith($datetimeString, $row['test_datetime']); - } - - public function testFetchAssocWithMissingTypes() : void - { - if ($this->connection->getDriver() instanceof MySQLiDriver || - $this->connection->getDriver() instanceof SQLSrvDriver) { - $this->markTestSkipped('mysqli and sqlsrv actually supports this'); - } - - $datetimeString = '2010-01-01 10:10:10'; - $datetime = new DateTime($datetimeString); - $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; - - $this->expectException(DBALException::class); - - $this->connection->fetchAssoc($sql, [1, $datetime]); - } - - public function testFetchArray() : void - { - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $row = $this->connection->fetchArray($sql, [1, 'foo']); - - self::assertEquals(1, $row[0]); - self::assertEquals('foo', $row[1]); - } - - public function testFetchArrayWithTypes() : void - { - $datetimeString = '2010-01-01 10:10:10'; - $datetime = new DateTime($datetimeString); - - $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; - $row = $this->connection->fetchArray( - $sql, - [1, $datetime], - [ParameterType::STRING, Types::DATETIME_MUTABLE] - ); - - self::assertNotFalse($row); - - $row = array_change_key_case($row, CASE_LOWER); - - self::assertEquals(1, $row[0]); - self::assertStringStartsWith($datetimeString, $row[1]); - } - - public function testFetchArrayWithMissingTypes() : void - { - if ($this->connection->getDriver() instanceof MySQLiDriver || - $this->connection->getDriver() instanceof SQLSrvDriver) { - $this->markTestSkipped('mysqli and sqlsrv actually supports this'); - } - - $datetimeString = '2010-01-01 10:10:10'; - $datetime = new DateTime($datetimeString); - $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; - - $this->expectException(DBALException::class); - - $this->connection->fetchArray($sql, [1, $datetime]); - } - - public function testFetchColumn() : void - { - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $testInt = $this->connection->fetchColumn($sql, [1, 'foo'], 0); - - self::assertEquals(1, $testInt); - - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $testString = $this->connection->fetchColumn($sql, [1, 'foo'], 1); - - self::assertEquals('foo', $testString); - } - - public function testFetchColumnWithTypes() : void - { - $datetimeString = '2010-01-01 10:10:10'; - $datetime = new DateTime($datetimeString); - - $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; - $column = $this->connection->fetchColumn( - $sql, - [1, $datetime], - 1, - [ParameterType::STRING, Types::DATETIME_MUTABLE] - ); - - self::assertNotFalse($column); - - self::assertStringStartsWith($datetimeString, $column); - } - - public function testFetchColumnWithMissingTypes() : void - { - if ($this->connection->getDriver() instanceof MySQLiDriver || - $this->connection->getDriver() instanceof SQLSrvDriver) { - $this->markTestSkipped('mysqli and sqlsrv actually supports this'); - } - - $datetimeString = '2010-01-01 10:10:10'; - $datetime = new DateTime($datetimeString); - $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?'; - - $this->expectException(DBALException::class); - - $this->connection->fetchColumn($sql, [1, $datetime], 1); - } - - /** - * @group DDC-697 - */ - public function testExecuteQueryBindDateTimeType() : void - { - $sql = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?'; - $stmt = $this->connection->executeQuery( - $sql, - [1 => new DateTime('2010-01-01 10:10:10')], - [1 => Types::DATETIME_MUTABLE] - ); - - self::assertEquals(1, $stmt->fetchColumn()); - } - - /** - * @group DDC-697 - */ - public function testExecuteUpdateBindDateTimeType() : void - { - $datetime = new DateTime('2010-02-02 20:20:20'); - - $sql = 'INSERT INTO fetch_table (test_int, test_string, test_datetime) VALUES (?, ?, ?)'; - $affectedRows = $this->connection->executeUpdate($sql, [ - 1 => 50, - 2 => 'foo', - 3 => $datetime, - ], [ - 1 => ParameterType::INTEGER, - 2 => ParameterType::STRING, - 3 => Types::DATETIME_MUTABLE, - ]); - - self::assertEquals(1, $affectedRows); - self::assertEquals(1, $this->connection->executeQuery( - 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?', - [1 => $datetime], - [1 => Types::DATETIME_MUTABLE] - )->fetchColumn()); - } - - /** - * @group DDC-697 - */ - public function testPrepareQueryBindValueDateTimeType() : void - { - $sql = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?'; - $stmt = $this->connection->prepare($sql); - $stmt->bindValue(1, new DateTime('2010-01-01 10:10:10'), Types::DATETIME_MUTABLE); - $stmt->execute(); - - self::assertEquals(1, $stmt->fetchColumn()); - } - - /** - * @group DBAL-78 - */ - public function testNativeArrayListSupport() : void - { - for ($i = 100; $i < 110; $i++) { - $this->connection->insert('fetch_table', ['test_int' => $i, 'test_string' => 'foo' . $i, 'test_datetime' => '2010-01-01 10:10:10']); - } - - $stmt = $this->connection->executeQuery( - 'SELECT test_int FROM fetch_table WHERE test_int IN (?)', - [[100, 101, 102, 103, 104]], - [Connection::PARAM_INT_ARRAY] - ); - - $data = $stmt->fetchAll(FetchMode::NUMERIC); - self::assertCount(5, $data); - self::assertEquals([[100], [101], [102], [103], [104]], $data); - - $stmt = $this->connection->executeQuery( - 'SELECT test_int FROM fetch_table WHERE test_string IN (?)', - [['foo100', 'foo101', 'foo102', 'foo103', 'foo104']], - [Connection::PARAM_STR_ARRAY] - ); - - $data = $stmt->fetchAll(FetchMode::NUMERIC); - self::assertCount(5, $data); - self::assertEquals([[100], [101], [102], [103], [104]], $data); - } - - /** - * @param string|false $char - * - * @dataProvider getTrimExpressionData - */ - public function testTrimExpression(string $value, int $position, $char, string $expectedResult) : void - { - $sql = 'SELECT ' . - $this->connection->getDatabasePlatform()->getTrimExpression($value, $position, $char) . ' AS trimmed ' . - 'FROM fetch_table'; - - $row = $this->connection->fetchAssoc($sql); - $row = array_change_key_case($row, CASE_LOWER); - - self::assertEquals($expectedResult, $row['trimmed']); - } - - /** - * @return array> - */ - public static function getTrimExpressionData() : iterable - { - return [ - ['test_string', TrimMode::UNSPECIFIED, false, 'foo'], - ['test_string', TrimMode::LEADING, false, 'foo'], - ['test_string', TrimMode::TRAILING, false, 'foo'], - ['test_string', TrimMode::BOTH, false, 'foo'], - ['test_string', TrimMode::UNSPECIFIED, "'f'", 'oo'], - ['test_string', TrimMode::UNSPECIFIED, "'o'", 'f'], - ['test_string', TrimMode::UNSPECIFIED, "'.'", 'foo'], - ['test_string', TrimMode::LEADING, "'f'", 'oo'], - ['test_string', TrimMode::LEADING, "'o'", 'foo'], - ['test_string', TrimMode::LEADING, "'.'", 'foo'], - ['test_string', TrimMode::TRAILING, "'f'", 'foo'], - ['test_string', TrimMode::TRAILING, "'o'", 'f'], - ['test_string', TrimMode::TRAILING, "'.'", 'foo'], - ['test_string', TrimMode::BOTH, "'f'", 'oo'], - ['test_string', TrimMode::BOTH, "'o'", 'f'], - ['test_string', TrimMode::BOTH, "'.'", 'foo'], - ["' foo '", TrimMode::UNSPECIFIED, false, 'foo'], - ["' foo '", TrimMode::LEADING, false, 'foo '], - ["' foo '", TrimMode::TRAILING, false, ' foo'], - ["' foo '", TrimMode::BOTH, false, 'foo'], - ["' foo '", TrimMode::UNSPECIFIED, "'f'", ' foo '], - ["' foo '", TrimMode::UNSPECIFIED, "'o'", ' foo '], - ["' foo '", TrimMode::UNSPECIFIED, "'.'", ' foo '], - ["' foo '", TrimMode::UNSPECIFIED, "' '", 'foo'], - ["' foo '", TrimMode::LEADING, "'f'", ' foo '], - ["' foo '", TrimMode::LEADING, "'o'", ' foo '], - ["' foo '", TrimMode::LEADING, "'.'", ' foo '], - ["' foo '", TrimMode::LEADING, "' '", 'foo '], - ["' foo '", TrimMode::TRAILING, "'f'", ' foo '], - ["' foo '", TrimMode::TRAILING, "'o'", ' foo '], - ["' foo '", TrimMode::TRAILING, "'.'", ' foo '], - ["' foo '", TrimMode::TRAILING, "' '", ' foo'], - ["' foo '", TrimMode::BOTH, "'f'", ' foo '], - ["' foo '", TrimMode::BOTH, "'o'", ' foo '], - ["' foo '", TrimMode::BOTH, "'.'", ' foo '], - ["' foo '", TrimMode::BOTH, "' '", 'foo'], - ]; - } - - /** - * @group DDC-1014 - */ - public function testDateArithmetics() : void - { - $p = $this->connection->getDatabasePlatform(); - $sql = 'SELECT '; - $sql .= $p->getDateAddSecondsExpression('test_datetime', 1) . ' AS add_seconds, '; - $sql .= $p->getDateSubSecondsExpression('test_datetime', 1) . ' AS sub_seconds, '; - $sql .= $p->getDateAddMinutesExpression('test_datetime', 5) . ' AS add_minutes, '; - $sql .= $p->getDateSubMinutesExpression('test_datetime', 5) . ' AS sub_minutes, '; - $sql .= $p->getDateAddHourExpression('test_datetime', 3) . ' AS add_hour, '; - $sql .= $p->getDateSubHourExpression('test_datetime', 3) . ' AS sub_hour, '; - $sql .= $p->getDateAddDaysExpression('test_datetime', 10) . ' AS add_days, '; - $sql .= $p->getDateSubDaysExpression('test_datetime', 10) . ' AS sub_days, '; - $sql .= $p->getDateAddWeeksExpression('test_datetime', 1) . ' AS add_weeks, '; - $sql .= $p->getDateSubWeeksExpression('test_datetime', 1) . ' AS sub_weeks, '; - $sql .= $p->getDateAddMonthExpression('test_datetime', 2) . ' AS add_month, '; - $sql .= $p->getDateSubMonthExpression('test_datetime', 2) . ' AS sub_month, '; - $sql .= $p->getDateAddQuartersExpression('test_datetime', 3) . ' AS add_quarters, '; - $sql .= $p->getDateSubQuartersExpression('test_datetime', 3) . ' AS sub_quarters, '; - $sql .= $p->getDateAddYearsExpression('test_datetime', 6) . ' AS add_years, '; - $sql .= $p->getDateSubYearsExpression('test_datetime', 6) . ' AS sub_years '; - $sql .= 'FROM fetch_table'; - - $row = $this->connection->fetchAssoc($sql); - $row = array_change_key_case($row, CASE_LOWER); - - self::assertEquals('2010-01-01 10:10:11', date('Y-m-d H:i:s', strtotime($row['add_seconds'])), 'Adding second should end up on 2010-01-01 10:10:11'); - self::assertEquals('2010-01-01 10:10:09', date('Y-m-d H:i:s', strtotime($row['sub_seconds'])), 'Subtracting second should end up on 2010-01-01 10:10:09'); - self::assertEquals('2010-01-01 10:15:10', date('Y-m-d H:i:s', strtotime($row['add_minutes'])), 'Adding minutes should end up on 2010-01-01 10:15:10'); - self::assertEquals('2010-01-01 10:05:10', date('Y-m-d H:i:s', strtotime($row['sub_minutes'])), 'Subtracting minutes should end up on 2010-01-01 10:05:10'); - self::assertEquals('2010-01-01 13:10', date('Y-m-d H:i', strtotime($row['add_hour'])), 'Adding date should end up on 2010-01-01 13:10'); - self::assertEquals('2010-01-01 07:10', date('Y-m-d H:i', strtotime($row['sub_hour'])), 'Subtracting date should end up on 2010-01-01 07:10'); - self::assertEquals('2010-01-11', date('Y-m-d', strtotime($row['add_days'])), 'Adding date should end up on 2010-01-11'); - self::assertEquals('2009-12-22', date('Y-m-d', strtotime($row['sub_days'])), 'Subtracting date should end up on 2009-12-22'); - self::assertEquals('2010-01-08', date('Y-m-d', strtotime($row['add_weeks'])), 'Adding week should end up on 2010-01-08'); - self::assertEquals('2009-12-25', date('Y-m-d', strtotime($row['sub_weeks'])), 'Subtracting week should end up on 2009-12-25'); - self::assertEquals('2010-03-01', date('Y-m-d', strtotime($row['add_month'])), 'Adding month should end up on 2010-03-01'); - self::assertEquals('2009-11-01', date('Y-m-d', strtotime($row['sub_month'])), 'Subtracting month should end up on 2009-11-01'); - self::assertEquals('2010-10-01', date('Y-m-d', strtotime($row['add_quarters'])), 'Adding quarters should end up on 2010-04-01'); - self::assertEquals('2009-04-01', date('Y-m-d', strtotime($row['sub_quarters'])), 'Subtracting quarters should end up on 2009-10-01'); - self::assertEquals('2016-01-01', date('Y-m-d', strtotime($row['add_years'])), 'Adding years should end up on 2016-01-01'); - self::assertEquals('2004-01-01', date('Y-m-d', strtotime($row['sub_years'])), 'Subtracting years should end up on 2004-01-01'); - } - - public function testSqliteDateArithmeticWithDynamicInterval() : void - { - $platform = $this->connection->getDatabasePlatform(); - - if (! $platform instanceof SqlitePlatform) { - $this->markTestSkipped('test is for sqlite only'); - } - - $table = new Table('fetch_table_date_math'); - $table->addColumn('test_date', 'date'); - $table->addColumn('test_days', 'integer'); - $table->setPrimaryKey(['test_date']); - - $sm = $this->connection->getSchemaManager(); - $sm->createTable($table); - - $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-01-01', 'test_days' => 10]); - $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-06-01', 'test_days' => 20]); - - $sql = 'SELECT COUNT(*) FROM fetch_table_date_math WHERE '; - $sql .= $platform->getDateSubDaysExpression('test_date', 'test_days') . " < '2010-05-12'"; - - $rowCount = $this->connection->fetchColumn($sql, [], 0); - - $this->assertEquals(1, $rowCount); - } - - public function testLocateExpression() : void - { - $platform = $this->connection->getDatabasePlatform(); - - $sql = 'SELECT '; - $sql .= $platform->getLocateExpression('test_string', "'oo'") . ' AS locate1, '; - $sql .= $platform->getLocateExpression('test_string', "'foo'") . ' AS locate2, '; - $sql .= $platform->getLocateExpression('test_string', "'bar'") . ' AS locate3, '; - $sql .= $platform->getLocateExpression('test_string', 'test_string') . ' AS locate4, '; - $sql .= $platform->getLocateExpression("'foo'", 'test_string') . ' AS locate5, '; - $sql .= $platform->getLocateExpression("'barfoobaz'", 'test_string') . ' AS locate6, '; - $sql .= $platform->getLocateExpression("'bar'", 'test_string') . ' AS locate7, '; - $sql .= $platform->getLocateExpression('test_string', "'oo'", 2) . ' AS locate8, '; - $sql .= $platform->getLocateExpression('test_string', "'oo'", 3) . ' AS locate9 '; - $sql .= 'FROM fetch_table'; - - $row = $this->connection->fetchAssoc($sql); - $row = array_change_key_case($row, CASE_LOWER); - - self::assertEquals(2, $row['locate1']); - self::assertEquals(1, $row['locate2']); - self::assertEquals(0, $row['locate3']); - self::assertEquals(1, $row['locate4']); - self::assertEquals(1, $row['locate5']); - self::assertEquals(4, $row['locate6']); - self::assertEquals(0, $row['locate7']); - self::assertEquals(2, $row['locate8']); - self::assertEquals(0, $row['locate9']); - } - - public function testQuoteSQLInjection() : void - { - $sql = 'SELECT * FROM fetch_table WHERE test_string = ' . $this->connection->quote("bar' OR '1'='1"); - $rows = $this->connection->fetchAll($sql); - - self::assertCount(0, $rows, 'no result should be returned, otherwise SQL injection is possible'); - } - - /** - * @group DDC-1213 - */ - public function testBitComparisonExpressionSupport() : void - { - $this->connection->exec('DELETE FROM fetch_table'); - $platform = $this->connection->getDatabasePlatform(); - $bitmap = []; - - for ($i = 2; $i < 9; $i += 2) { - $bitmap[$i] = [ - 'bit_or' => ($i | 2), - 'bit_and' => ($i & 2), - ]; - $this->connection->insert('fetch_table', [ - 'test_int' => $i, - 'test_string' => json_encode($bitmap[$i]), - 'test_datetime' => '2010-01-01 10:10:10', - ]); - } - - $sql[] = 'SELECT '; - $sql[] = 'test_int, '; - $sql[] = 'test_string, '; - $sql[] = $platform->getBitOrComparisonExpression('test_int', 2) . ' AS bit_or, '; - $sql[] = $platform->getBitAndComparisonExpression('test_int', 2) . ' AS bit_and '; - $sql[] = 'FROM fetch_table'; - - $stmt = $this->connection->executeQuery(implode(PHP_EOL, $sql)); - $data = $stmt->fetchAll(FetchMode::ASSOCIATIVE); - - self::assertCount(4, $data); - self::assertEquals(count($bitmap), count($data)); - foreach ($data as $row) { - $row = array_change_key_case($row, CASE_LOWER); - - self::assertArrayHasKey('test_int', $row); - - $id = $row['test_int']; - - self::assertArrayHasKey($id, $bitmap); - self::assertArrayHasKey($id, $bitmap); - - self::assertArrayHasKey('bit_or', $row); - self::assertArrayHasKey('bit_and', $row); - - self::assertEquals($row['bit_or'], $bitmap[$id]['bit_or']); - self::assertEquals($row['bit_and'], $bitmap[$id]['bit_and']); - } - } - - public function testSetDefaultFetchMode() : void - { - $stmt = $this->connection->query('SELECT * FROM fetch_table'); - $stmt->setFetchMode(FetchMode::NUMERIC); - - $row = array_keys($stmt->fetch()); - self::assertCount(0, array_filter($row, static function ($v) { - return ! is_numeric($v); - }), 'should be no non-numerical elements in the result.'); - } - - /** - * @group DBAL-1091 - */ - public function testFetchAllStyleObject() : void - { - $this->setupFixture(); - - $sql = 'SELECT test_int, test_string, test_datetime FROM fetch_table'; - $stmt = $this->connection->prepare($sql); - - $stmt->execute(); - - $results = $stmt->fetchAll(FetchMode::STANDARD_OBJECT); - - self::assertCount(1, $results); - self::assertInstanceOf('stdClass', $results[0]); - - self::assertEquals( - 1, - property_exists($results[0], 'test_int') ? $results[0]->test_int : $results[0]->TEST_INT - ); - self::assertEquals( - 'foo', - property_exists($results[0], 'test_string') ? $results[0]->test_string : $results[0]->TEST_STRING - ); - self::assertStringStartsWith( - '2010-01-01 10:10:10', - property_exists($results[0], 'test_datetime') ? $results[0]->test_datetime : $results[0]->TEST_DATETIME - ); - } - - /** - * @group DBAL-196 - */ - public function testFetchAllSupportFetchClass() : void - { - $this->beforeFetchClassTest(); - $this->setupFixture(); - - $sql = 'SELECT test_int, test_string, test_datetime FROM fetch_table'; - $stmt = $this->connection->prepare($sql); - $stmt->execute(); - - $results = $stmt->fetchAll( - FetchMode::CUSTOM_OBJECT, - MyFetchClass::class - ); - - self::assertCount(1, $results); - self::assertInstanceOf(MyFetchClass::class, $results[0]); - - self::assertEquals(1, $results[0]->test_int); - self::assertEquals('foo', $results[0]->test_string); - self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime); - } - - /** - * @group DBAL-241 - */ - public function testFetchAllStyleColumn() : void - { - $sql = 'DELETE FROM fetch_table'; - $this->connection->executeUpdate($sql); - - $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo']); - $this->connection->insert('fetch_table', ['test_int' => 10, 'test_string' => 'foo']); - - $sql = 'SELECT test_int FROM fetch_table'; - $rows = $this->connection->query($sql)->fetchAll(FetchMode::COLUMN); - - self::assertEquals([1, 10], $rows); - } - - /** - * @group DBAL-214 - */ - public function testSetFetchModeClassFetchAll() : void - { - $this->beforeFetchClassTest(); - $this->setupFixture(); - - $sql = 'SELECT * FROM fetch_table'; - $stmt = $this->connection->query($sql); - $stmt->setFetchMode(FetchMode::CUSTOM_OBJECT, MyFetchClass::class); - - $results = $stmt->fetchAll(); - - self::assertCount(1, $results); - self::assertInstanceOf(MyFetchClass::class, $results[0]); - - self::assertEquals(1, $results[0]->test_int); - self::assertEquals('foo', $results[0]->test_string); - self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime); - } - - /** - * @group DBAL-214 - */ - public function testSetFetchModeClassFetch() : void - { - $this->beforeFetchClassTest(); - $this->setupFixture(); - - $sql = 'SELECT * FROM fetch_table'; - $stmt = $this->connection->query($sql); - $stmt->setFetchMode(FetchMode::CUSTOM_OBJECT, MyFetchClass::class); - - $results = []; - while ($row = $stmt->fetch()) { - $results[] = $row; - } - - self::assertCount(1, $results); - self::assertInstanceOf(MyFetchClass::class, $results[0]); - - self::assertEquals(1, $results[0]->test_int); - self::assertEquals('foo', $results[0]->test_string); - self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime); - } - - /** - * @group DBAL-257 - */ - public function testEmptyFetchColumnReturnsFalse() : void - { - $this->connection->beginTransaction(); - $this->connection->exec('DELETE FROM fetch_table'); - self::assertFalse($this->connection->fetchColumn('SELECT test_int FROM fetch_table')); - self::assertFalse($this->connection->query('SELECT test_int FROM fetch_table')->fetchColumn()); - $this->connection->rollBack(); - } - - /** - * @group DBAL-339 - */ - public function testSetFetchModeOnDbalStatement() : void - { - $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; - $stmt = $this->connection->executeQuery($sql, [1, 'foo']); - $stmt->setFetchMode(FetchMode::NUMERIC); - - $row = $stmt->fetch(); - - self::assertArrayHasKey(0, $row); - self::assertArrayHasKey(1, $row); - self::assertFalse($stmt->fetch()); - } - - /** - * @group DBAL-435 - */ - public function testEmptyParameters() : void - { - $sql = 'SELECT * FROM fetch_table WHERE test_int IN (?)'; - $stmt = $this->connection->executeQuery($sql, [[]], [Connection::PARAM_INT_ARRAY]); - $rows = $stmt->fetchAll(); - - self::assertEquals([], $rows); - } - - /** - * @group DBAL-1028 - */ - public function testFetchColumnNullValue() : void - { - $this->connection->executeUpdate( - 'INSERT INTO fetch_table (test_int, test_string) VALUES (?, ?)', - [2, 'foo'] - ); - - self::assertNull( - $this->connection->fetchColumn('SELECT test_datetime FROM fetch_table WHERE test_int = ?', [2]) - ); - } - - /** - * @group DBAL-1028 - */ - public function testFetchColumnNoResult() : void - { - self::assertFalse( - $this->connection->fetchColumn('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1]) - ); - } - - private function setupFixture() : void - { - $this->connection->exec('DELETE FROM fetch_table'); - $this->connection->insert('fetch_table', [ - 'test_int' => 1, - 'test_string' => 'foo', - 'test_datetime' => '2010-01-01 10:10:10', - ]); - } - - private function beforeFetchClassTest() : void - { - $driver = $this->connection->getDriver(); - - if ($driver instanceof Oci8Driver) { - $this->markTestSkipped('Not supported by OCI8'); - } - - if ($driver instanceof MySQLiDriver) { - $this->markTestSkipped('Mysqli driver dont support this feature.'); - } - - if (! $driver instanceof PDOOracleDriver) { - return; - } - - /** @var PDOConnection $connection */ - $connection = $this->connection->getWrappedConnection(); - $connection->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); - } -} - -class MyFetchClass -{ - /** @var int */ - public $test_int; - - /** @var string */ - public $test_string; - - /** @var string */ - public $test_datetime; -} diff --git a/tests/Functional/DBAL/Driver/AbstractDriverTest.php b/tests/Functional/DBAL/Driver/AbstractDriverTest.php deleted file mode 100644 index 030cebb..0000000 --- a/tests/Functional/DBAL/Driver/AbstractDriverTest.php +++ /dev/null @@ -1,69 +0,0 @@ -driver = $this->createDriver(); - } - - /** - * @group DBAL-1215 - */ - public function testConnectsWithoutDatabaseNameParameter() : void - { - $params = $this->connection->getParams(); - unset($params['dbname']); - - $user = $params['user'] ?? null; - $password = $params['password'] ?? null; - - $connection = $this->driver->connect($params, $user, $password); - - self::assertInstanceOf(DriverConnection::class, $connection); - } - - /** - * @group DBAL-1215 - */ - public function testReturnsDatabaseNameWithoutDatabaseNameParameter() : void - { - $params = $this->connection->getParams(); - unset($params['dbname']); - - $connection = new Connection( - $params, - $this->connection->getDriver(), - $this->connection->getConfiguration(), - $this->connection->getEventManager() - ); - - self::assertSame( - static::getDatabaseNameForConnectionWithoutDatabaseNameParameter(), - $this->driver->getDatabase($connection) - ); - } - - abstract protected function createDriver() : Driver; - - protected static function getDatabaseNameForConnectionWithoutDatabaseNameParameter() : ?string - { - return null; - } -} diff --git a/tests/Functional/DBAL/Driver/PDOConnectionTest.php b/tests/Functional/DBAL/Driver/PDOConnectionTest.php deleted file mode 100644 index 9b784ea..0000000 --- a/tests/Functional/DBAL/Driver/PDOConnectionTest.php +++ /dev/null @@ -1,71 +0,0 @@ -markTestSkipped('PDO is not installed.'); - } - - parent::setUp(); - - $this->driverConnection = $this->connection->getWrappedConnection(); - - if ($this->driverConnection instanceof PDOConnection) { - return; - } - - $this->markTestSkipped('PDO connection only test.'); - } - - protected function tearDown() : void - { - $this->resetSharedConn(); - - parent::tearDown(); - } - - public function testDoesNotRequireQueryForServerVersion() : void - { - self::assertFalse($this->driverConnection->requiresQueryForServerVersion()); - } - - public function testThrowsWrappedExceptionOnConstruct() : void - { - $this->expectException(PDOException::class); - - new PDOConnection('foo'); - } - - /** - * @group DBAL-1022 - */ - public function testThrowsWrappedExceptionOnExec() : void - { - $this->expectException(PDOException::class); - - $this->driverConnection->exec('foo'); - } - - public function testThrowsWrappedExceptionOnQuery() : void - { - $this->expectException(PDOException::class); - - $this->driverConnection->query('foo'); - } -} diff --git a/tests/Functional/DBAL/Driver/PDOPgSql/DriverTest.php b/tests/Functional/DBAL/Driver/PDOPgSql/DriverTest.php deleted file mode 100644 index d3907b9..0000000 --- a/tests/Functional/DBAL/Driver/PDOPgSql/DriverTest.php +++ /dev/null @@ -1,79 +0,0 @@ -markTestSkipped('pdo_pgsql is not installed.'); - } - - parent::setUp(); - - if ($this->connection->getDriver() instanceof Driver) { - return; - } - - $this->markTestSkipped('pdo_pgsql only test.'); - } - - /** - * @group DBAL-1146 - */ - public function testConnectsWithApplicationNameParameter() : void - { - $parameters = $this->connection->getParams(); - $parameters['application_name'] = 'doctrine'; - - $user = $parameters['user'] ?? null; - $password = $parameters['password'] ?? null; - - $connection = $this->driver->connect($parameters, $user, $password); - - $hash = microtime(true); // required to identify the record in the results uniquely - $sql = sprintf('SELECT * FROM pg_stat_activity WHERE %d = %d', $hash, $hash); - $statement = $connection->query($sql); - $records = $statement->fetchAll(); - - foreach ($records as $record) { - // The query column is named "current_query" on PostgreSQL < 9.2 - $queryColumnName = array_key_exists('current_query', $record) ? 'current_query' : 'query'; - - if ($record[$queryColumnName] === $sql) { - self::assertSame('doctrine', $record['application_name']); - - return; - } - } - - $this->fail(sprintf('Query result does not contain a record where column "query" equals "%s".', $sql)); - } - - /** - * {@inheritdoc} - */ - protected function createDriver() : DriverInterface - { - return new Driver(); - } - - /** - * {@inheritdoc} - */ - protected static function getDatabaseNameForConnectionWithoutDatabaseNameParameter() : ?string - { - return 'postgres'; - } -} diff --git a/tests/Functional/DBAL/Driver/PDOPgsqlConnectionTest.php b/tests/Functional/DBAL/Driver/PDOPgsqlConnectionTest.php deleted file mode 100644 index 96888b9..0000000 --- a/tests/Functional/DBAL/Driver/PDOPgsqlConnectionTest.php +++ /dev/null @@ -1,61 +0,0 @@ -markTestSkipped('pdo_pgsql is not loaded.'); - } - - parent::setUp(); - - if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { - return; - } - - $this->markTestSkipped('PDOPgsql only test.'); - } - - /** - * @group DBAL-1183 - * @group DBAL-1189 - * @dataProvider getValidCharsets - */ - public function testConnectsWithValidCharsetOption(string $charset) : void - { - $params = $this->connection->getParams(); - $params['charset'] = $charset; - - $connection = DriverManager::getConnection( - $params, - $this->connection->getConfiguration(), - $this->connection->getEventManager() - ); - - self::assertEquals( - $charset, - $connection->query('SHOW client_encoding') - ->fetch(FetchMode::COLUMN) - ); - } - - /** - * @return mixed[][] - */ - public static function getValidCharsets() : iterable - { - return [ - ['UTF8'], - ['LATIN1'], - ]; - } -} diff --git a/tests/Functional/DBAL/ExceptionTest.php b/tests/Functional/DBAL/ExceptionTest.php deleted file mode 100644 index 853b039..0000000 --- a/tests/Functional/DBAL/ExceptionTest.php +++ /dev/null @@ -1,426 +0,0 @@ -connection->getDriver() instanceof ExceptionConverterDriver) { - return; - } - - $this->markTestSkipped('Driver does not support special exception handling.'); - } - - public function testPrimaryConstraintViolationException() : void - { - $table = new Table('duplicatekey_table'); - $table->addColumn('id', 'integer', []); - $table->setPrimaryKey(['id']); - - $this->connection->getSchemaManager()->createTable($table); - - $this->connection->insert('duplicatekey_table', ['id' => 1]); - - $this->expectException(Exception\UniqueConstraintViolationException::class); - $this->connection->insert('duplicatekey_table', ['id' => 1]); - } - - public function testTableNotFoundException() : void - { - $sql = 'SELECT * FROM unknown_table'; - - $this->expectException(Exception\TableNotFoundException::class); - $this->connection->executeQuery($sql); - } - - public function testTableExistsException() : void - { - $schemaManager = $this->connection->getSchemaManager(); - $table = new Table('alreadyexist_table'); - $table->addColumn('id', 'integer', []); - $table->setPrimaryKey(['id']); - - $this->expectException(Exception\TableExistsException::class); - $schemaManager->createTable($table); - $schemaManager->createTable($table); - } - - public function testForeignKeyConstraintViolationExceptionOnInsert() : void - { - if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('Only fails on platforms with foreign key constraints.'); - } - - $this->setUpForeignKeyConstraintViolationExceptionTest(); - - try { - $this->connection->insert('constraint_error_table', ['id' => 1]); - $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]); - } catch (Throwable $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } - - $this->expectException(Exception\ForeignKeyConstraintViolationException::class); - - try { - $this->connection->insert('owning_table', ['id' => 2, 'constraint_id' => 2]); - } catch (Exception\ForeignKeyConstraintViolationException $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } catch (Throwable $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } - - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - } - - public function testForeignKeyConstraintViolationExceptionOnUpdate() : void - { - if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('Only fails on platforms with foreign key constraints.'); - } - - $this->setUpForeignKeyConstraintViolationExceptionTest(); - - try { - $this->connection->insert('constraint_error_table', ['id' => 1]); - $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]); - } catch (Throwable $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } - - $this->expectException(Exception\ForeignKeyConstraintViolationException::class); - - try { - $this->connection->update('constraint_error_table', ['id' => 2], ['id' => 1]); - } catch (Exception\ForeignKeyConstraintViolationException $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } catch (Throwable $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } - - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - } - - public function testForeignKeyConstraintViolationExceptionOnDelete() : void - { - if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('Only fails on platforms with foreign key constraints.'); - } - - $this->setUpForeignKeyConstraintViolationExceptionTest(); - - try { - $this->connection->insert('constraint_error_table', ['id' => 1]); - $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]); - } catch (Throwable $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } - - $this->expectException(Exception\ForeignKeyConstraintViolationException::class); - - try { - $this->connection->delete('constraint_error_table', ['id' => 1]); - } catch (Exception\ForeignKeyConstraintViolationException $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } catch (Throwable $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } - - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - } - - public function testForeignKeyConstraintViolationExceptionOnTruncate() : void - { - $platform = $this->connection->getDatabasePlatform(); - - if (! $platform->supportsForeignKeyConstraints()) { - $this->markTestSkipped('Only fails on platforms with foreign key constraints.'); - } - - $this->setUpForeignKeyConstraintViolationExceptionTest(); - - try { - $this->connection->insert('constraint_error_table', ['id' => 1]); - $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]); - } catch (Throwable $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } - - $this->expectException(Exception\ForeignKeyConstraintViolationException::class); - - try { - $this->connection->executeUpdate($platform->getTruncateTableSQL('constraint_error_table')); - } catch (Exception\ForeignKeyConstraintViolationException $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } catch (Throwable $exception) { - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - - throw $exception; - } - - $this->tearDownForeignKeyConstraintViolationExceptionTest(); - } - - public function testNotNullConstraintViolationException() : void - { - $schema = new Schema(); - - $table = $schema->createTable('notnull_table'); - $table->addColumn('id', 'integer', []); - $table->addColumn('value', 'integer', ['notnull' => true]); - $table->setPrimaryKey(['id']); - - foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); - } - - $this->expectException(Exception\NotNullConstraintViolationException::class); - $this->connection->insert('notnull_table', ['id' => 1, 'value' => null]); - } - - public function testInvalidFieldNameException() : void - { - $schema = new Schema(); - - $table = $schema->createTable('bad_fieldname_table'); - $table->addColumn('id', 'integer', []); - - foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); - } - - $this->expectException(Exception\InvalidFieldNameException::class); - $this->connection->insert('bad_fieldname_table', ['name' => 5]); - } - - public function testNonUniqueFieldNameException() : void - { - $schema = new Schema(); - - $table = $schema->createTable('ambiguous_list_table'); - $table->addColumn('id', 'integer'); - - $table2 = $schema->createTable('ambiguous_list_table_2'); - $table2->addColumn('id', 'integer'); - - foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); - } - - $sql = 'SELECT id FROM ambiguous_list_table, ambiguous_list_table_2'; - $this->expectException(Exception\NonUniqueFieldNameException::class); - $this->connection->executeQuery($sql); - } - - public function testUniqueConstraintViolationException() : void - { - $schema = new Schema(); - - $table = $schema->createTable('unique_field_table'); - $table->addColumn('id', 'integer'); - $table->addUniqueIndex(['id']); - - foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); - } - - $this->connection->insert('unique_field_table', ['id' => 5]); - $this->expectException(Exception\UniqueConstraintViolationException::class); - $this->connection->insert('unique_field_table', ['id' => 5]); - } - - public function testSyntaxErrorException() : void - { - $table = new Table('syntax_error_table'); - $table->addColumn('id', 'integer', []); - $table->setPrimaryKey(['id']); - - $this->connection->getSchemaManager()->createTable($table); - - $sql = 'SELECT id FRO syntax_error_table'; - $this->expectException(Exception\SyntaxErrorException::class); - $this->connection->executeQuery($sql); - } - - public function testConnectionExceptionSqLite() : void - { - if ($this->connection->getDatabasePlatform()->getName() !== 'sqlite') { - $this->markTestSkipped('Only fails this way on sqlite'); - } - - // mode 0 is considered read-only on Windows - $mode = PHP_OS === 'Linux' ? 0444 : 0000; - - $filename = sprintf('%s/%s', sys_get_temp_dir(), 'doctrine_failed_connection_' . $mode . '.db'); - - if (file_exists($filename)) { - $this->cleanupReadOnlyFile($filename); - } - - touch($filename); - chmod($filename, $mode); - - if ($this->isLinuxRoot()) { - exec(sprintf('chattr +i %s', $filename)); - } - - $params = [ - 'driver' => 'pdo_sqlite', - 'path' => $filename, - ]; - $conn = DriverManager::getConnection($params); - - $schema = new Schema(); - $table = $schema->createTable('no_connection'); - $table->addColumn('id', 'integer'); - - $this->expectException(Exception\ReadOnlyException::class); - $this->expectExceptionMessage(<<toSql($conn->getDatabasePlatform()) as $sql) { - $conn->exec($sql); - } - } finally { - $this->cleanupReadOnlyFile($filename); - } - } - - /** - * @param array $params - * - * @dataProvider getConnectionParams - */ - public function testConnectionException(array $params) : void - { - if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') { - $this->markTestSkipped('Only skipped if platform is not sqlite'); - } - - if ($this->connection->getDatabasePlatform()->getName() === 'drizzle') { - $this->markTestSkipped('Drizzle does not always support authentication'); - } - - if ($this->connection->getDatabasePlatform()->getName() === 'postgresql' && isset($params['password'])) { - $this->markTestSkipped('Does not work on Travis'); - } - - $defaultParams = $this->connection->getParams(); - $params = array_merge($defaultParams, $params); - - $conn = DriverManager::getConnection($params); - - $schema = new Schema(); - $table = $schema->createTable('no_connection'); - $table->addColumn('id', 'integer'); - - $this->expectException(Exception\ConnectionException::class); - - foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) { - $conn->exec($sql); - } - } - - /** - * @return array> - */ - public static function getConnectionParams() : iterable - { - return [ - [['user' => 'not_existing']], - [['password' => 'really_not']], - [['host' => 'localnope']], - ]; - } - - private function setUpForeignKeyConstraintViolationExceptionTest() : void - { - $schemaManager = $this->connection->getSchemaManager(); - - $table = new Table('constraint_error_table'); - $table->addColumn('id', 'integer', []); - $table->setPrimaryKey(['id']); - - $owningTable = new Table('owning_table'); - $owningTable->addColumn('id', 'integer', []); - $owningTable->addColumn('constraint_id', 'integer', []); - $owningTable->setPrimaryKey(['id']); - $owningTable->addForeignKeyConstraint($table, ['constraint_id'], ['id']); - - $schemaManager->createTable($table); - $schemaManager->createTable($owningTable); - } - - private function tearDownForeignKeyConstraintViolationExceptionTest() : void - { - $schemaManager = $this->connection->getSchemaManager(); - - $schemaManager->dropTable('owning_table'); - $schemaManager->dropTable('constraint_error_table'); - } - - private function isLinuxRoot() : bool - { - return PHP_OS === 'Linux' && posix_getpwuid(posix_geteuid())['name'] === 'root'; - } - - private function cleanupReadOnlyFile(string $filename) : void - { - if ($this->isLinuxRoot()) { - exec(sprintf('chattr -i %s', $filename)); - } - - chmod($filename, 0200); // make the file writable again, so it can be removed on Windows - unlink($filename); - } -} diff --git a/tests/Functional/DBAL/LikeWildcardsEscapingTest.php b/tests/Functional/DBAL/LikeWildcardsEscapingTest.php deleted file mode 100644 index a709022..0000000 --- a/tests/Functional/DBAL/LikeWildcardsEscapingTest.php +++ /dev/null @@ -1,28 +0,0 @@ -connection->getDatabasePlatform(); - $stmt = $this->connection->prepare( - $databasePlatform->getDummySelectSQL( - sprintf( - "(CASE WHEN '%s' LIKE '%s' ESCAPE '%s' THEN 1 ELSE 0 END)", - $string, - $databasePlatform->escapeStringForLike($string, $escapeChar), - $escapeChar - ) - ) - ); - $stmt->execute(); - $this->assertTrue((bool) $stmt->fetchColumn()); - } -} diff --git a/tests/Functional/DBAL/LoggingTest.php b/tests/Functional/DBAL/LoggingTest.php deleted file mode 100644 index ae6574c..0000000 --- a/tests/Functional/DBAL/LoggingTest.php +++ /dev/null @@ -1,55 +0,0 @@ -connection->getDatabasePlatform()->getDummySelectSQL(); - - $logMock = $this->createMock(SQLLogger::class); - $logMock->expects($this->at(0)) - ->method('startQuery') - ->with($this->equalTo($sql), $this->equalTo([]), $this->equalTo([])); - $logMock->expects($this->at(1)) - ->method('stopQuery'); - $this->connection->getConfiguration()->setSQLLogger($logMock); - $this->connection->executeQuery($sql, []); - } - - public function testLogExecuteUpdate() : void - { - $this->markTestSkipped('Test breaks MySQL but works on all other platforms (Unbuffered Queries stuff).'); - - $sql = $this->connection->getDatabasePlatform()->getDummySelectSQL(); - - $logMock = $this->createMock(SQLLogger::class); - $logMock->expects($this->at(0)) - ->method('startQuery') - ->with($this->equalTo($sql), $this->equalTo([]), $this->equalTo([])); - $logMock->expects($this->at(1)) - ->method('stopQuery'); - $this->connection->getConfiguration()->setSQLLogger($logMock); - $this->connection->executeUpdate($sql, []); - } - - public function testLogPrepareExecute() : void - { - $sql = $this->connection->getDatabasePlatform()->getDummySelectSQL(); - - $logMock = $this->createMock(SQLLogger::class); - $logMock->expects($this->once()) - ->method('startQuery') - ->with($this->equalTo($sql), $this->equalTo([])); - $logMock->expects($this->at(1)) - ->method('stopQuery'); - $this->connection->getConfiguration()->setSQLLogger($logMock); - - $stmt = $this->connection->prepare($sql); - $stmt->execute(); - } -} diff --git a/tests/Functional/DBAL/MasterSlaveConnectionTest.php b/tests/Functional/DBAL/MasterSlaveConnectionTest.php deleted file mode 100644 index 917655e..0000000 --- a/tests/Functional/DBAL/MasterSlaveConnectionTest.php +++ /dev/null @@ -1,241 +0,0 @@ -connection->getDatabasePlatform()->getName(); - - // This is a MySQL specific test, skip other vendors. - if ($platformName !== 'mysql') { - $this->markTestSkipped(sprintf('Test does not work on %s.', $platformName)); - } - - try { - $table = new Table('master_slave_table'); - $table->addColumn('test_int', 'integer'); - $table->setPrimaryKey(['test_int']); - - $sm = $this->connection->getSchemaManager(); - $sm->createTable($table); - } catch (Throwable $e) { - } - - $this->connection->executeUpdate('DELETE FROM master_slave_table'); - $this->connection->insert('master_slave_table', ['test_int' => 1]); - } - - private function createMasterSlaveConnection(bool $keepSlave = false) : MasterSlaveConnection - { - return DriverManager::getConnection($this->createMasterSlaveConnectionParams($keepSlave)); - } - - /** - * @return mixed[] - */ - private function createMasterSlaveConnectionParams(bool $keepSlave = false) : array - { - $params = $this->connection->getParams(); - $params['master'] = $params; - $params['slaves'] = [$params, $params]; - $params['keepSlave'] = $keepSlave; - $params['wrapperClass'] = MasterSlaveConnection::class; - - return $params; - } - - public function testInheritCharsetFromMaster() : void - { - $charsets = [ - 'utf8', - 'latin1', - ]; - - foreach ($charsets as $charset) { - $params = $this->createMasterSlaveConnectionParams(); - $params['master']['charset'] = $charset; - - foreach ($params['slaves'] as $index => $slaveParams) { - if (! isset($slaveParams['charset'])) { - continue; - } - - unset($params['slaves'][$index]['charset']); - } - - /** @var MasterSlaveConnection $conn */ - $conn = DriverManager::getConnection($params); - $conn->connect('slave'); - - self::assertFalse($conn->isConnectedToMaster()); - - $clientCharset = $conn->fetchColumn('select @@character_set_client as c'); - - self::assertSame( - $charset, - substr(strtolower($clientCharset), 0, strlen($charset)) - ); - } - } - - public function testMasterOnConnect() : void - { - $conn = $this->createMasterSlaveConnection(); - - self::assertFalse($conn->isConnectedToMaster()); - $conn->connect('slave'); - self::assertFalse($conn->isConnectedToMaster()); - $conn->connect('master'); - self::assertTrue($conn->isConnectedToMaster()); - } - - public function testNoMasterOnExecuteQuery() : void - { - $conn = $this->createMasterSlaveConnection(); - - $sql = 'SELECT count(*) as num FROM master_slave_table'; - $data = $conn->fetchAll($sql); - $data[0] = array_change_key_case($data[0], CASE_LOWER); - - self::assertEquals(1, $data[0]['num']); - self::assertFalse($conn->isConnectedToMaster()); - } - - public function testMasterOnWriteOperation() : void - { - $conn = $this->createMasterSlaveConnection(); - $conn->insert('master_slave_table', ['test_int' => 30]); - - self::assertTrue($conn->isConnectedToMaster()); - - $sql = 'SELECT count(*) as num FROM master_slave_table'; - $data = $conn->fetchAll($sql); - $data[0] = array_change_key_case($data[0], CASE_LOWER); - - self::assertEquals(2, $data[0]['num']); - self::assertTrue($conn->isConnectedToMaster()); - } - - /** - * @group DBAL-335 - */ - public function testKeepSlaveBeginTransactionStaysOnMaster() : void - { - $conn = $this->createMasterSlaveConnection($keepSlave = true); - $conn->connect('slave'); - - $conn->beginTransaction(); - $conn->insert('master_slave_table', ['test_int' => 30]); - $conn->commit(); - - self::assertTrue($conn->isConnectedToMaster()); - - $conn->connect(); - self::assertTrue($conn->isConnectedToMaster()); - - $conn->connect('slave'); - self::assertFalse($conn->isConnectedToMaster()); - } - - /** - * @group DBAL-335 - */ - public function testKeepSlaveInsertStaysOnMaster() : void - { - $conn = $this->createMasterSlaveConnection($keepSlave = true); - $conn->connect('slave'); - - $conn->insert('master_slave_table', ['test_int' => 30]); - - self::assertTrue($conn->isConnectedToMaster()); - - $conn->connect(); - self::assertTrue($conn->isConnectedToMaster()); - - $conn->connect('slave'); - self::assertFalse($conn->isConnectedToMaster()); - } - - public function testMasterSlaveConnectionCloseAndReconnect() : void - { - $conn = $this->createMasterSlaveConnection(); - $conn->connect('master'); - self::assertTrue($conn->isConnectedToMaster()); - - $conn->close(); - self::assertFalse($conn->isConnectedToMaster()); - - $conn->connect('master'); - self::assertTrue($conn->isConnectedToMaster()); - } - - public function testQueryOnMaster() : void - { - $conn = $this->createMasterSlaveConnection(); - - $query = 'SELECT count(*) as num FROM master_slave_table'; - - $statement = $conn->query($query); - - self::assertInstanceOf(Statement::class, $statement); - - //Query must be executed only on Master - self::assertTrue($conn->isConnectedToMaster()); - - $data = $statement->fetchAll(); - - //Default fetchmode is FetchMode::ASSOCIATIVE - self::assertArrayHasKey(0, $data); - self::assertArrayHasKey('num', $data[0]); - - //Could be set in other fetchmodes - self::assertArrayNotHasKey(0, $data[0]); - self::assertEquals(1, $data[0]['num']); - } - - public function testQueryOnSlave() : void - { - $conn = $this->createMasterSlaveConnection(); - $conn->connect('slave'); - - $query = 'SELECT count(*) as num FROM master_slave_table'; - - $statement = $conn->query($query); - - self::assertInstanceOf(Statement::class, $statement); - - //Query must be executed only on Master, even when we connect to the slave - self::assertTrue($conn->isConnectedToMaster()); - - $data = $statement->fetchAll(); - - //Default fetchmode is FetchMode::ASSOCIATIVE - self::assertArrayHasKey(0, $data); - self::assertArrayHasKey('num', $data[0]); - - //Could be set in other fetchmodes - self::assertArrayNotHasKey(0, $data[0]); - - self::assertEquals(1, $data[0]['num']); - } -} diff --git a/tests/Functional/DBAL/ModifyLimitQueryTest.php b/tests/Functional/DBAL/ModifyLimitQueryTest.php deleted file mode 100644 index 572ce54..0000000 --- a/tests/Functional/DBAL/ModifyLimitQueryTest.php +++ /dev/null @@ -1,185 +0,0 @@ -addColumn('test_int', 'integer'); - $table->setPrimaryKey(['test_int']); - - $table2 = new Table('modify_limit_table2'); - $table2->addColumn('id', 'integer', ['autoincrement' => true]); - $table2->addColumn('test_int', 'integer'); - $table2->setPrimaryKey(['id']); - - $sm = $this->connection->getSchemaManager(); - $sm->createTable($table); - $sm->createTable($table2); - self::$tableCreated = true; - } - $this->connection->exec($this->connection->getDatabasePlatform()->getTruncateTableSQL('modify_limit_table')); - $this->connection->exec($this->connection->getDatabasePlatform()->getTruncateTableSQL('modify_limit_table2')); - } - - public function testModifyLimitQuerySimpleQuery() : void - { - $this->connection->insert('modify_limit_table', ['test_int' => 1]); - $this->connection->insert('modify_limit_table', ['test_int' => 2]); - $this->connection->insert('modify_limit_table', ['test_int' => 3]); - $this->connection->insert('modify_limit_table', ['test_int' => 4]); - - $sql = 'SELECT * FROM modify_limit_table ORDER BY test_int ASC'; - - $this->assertLimitResult([1, 2, 3, 4], $sql, 10, 0); - $this->assertLimitResult([1, 2], $sql, 2, 0); - $this->assertLimitResult([3, 4], $sql, 2, 2); - $this->assertLimitResult([2, 3, 4], $sql, null, 1); - } - - public function testModifyLimitQueryJoinQuery() : void - { - $this->connection->insert('modify_limit_table', ['test_int' => 1]); - $this->connection->insert('modify_limit_table', ['test_int' => 2]); - - $this->connection->insert('modify_limit_table2', ['test_int' => 1]); - $this->connection->insert('modify_limit_table2', ['test_int' => 1]); - $this->connection->insert('modify_limit_table2', ['test_int' => 1]); - $this->connection->insert('modify_limit_table2', ['test_int' => 2]); - $this->connection->insert('modify_limit_table2', ['test_int' => 2]); - - $sql = 'SELECT modify_limit_table.test_int FROM modify_limit_table INNER JOIN modify_limit_table2 ON modify_limit_table.test_int = modify_limit_table2.test_int ORDER BY modify_limit_table.test_int DESC'; - - $this->assertLimitResult([2, 2, 1, 1, 1], $sql, 10, 0); - $this->assertLimitResult([1, 1, 1], $sql, 3, 2); - $this->assertLimitResult([2, 2], $sql, 2, 0); - } - - public function testModifyLimitQueryNonDeterministic() : void - { - $this->connection->insert('modify_limit_table', ['test_int' => 1]); - $this->connection->insert('modify_limit_table', ['test_int' => 2]); - $this->connection->insert('modify_limit_table', ['test_int' => 3]); - $this->connection->insert('modify_limit_table', ['test_int' => 4]); - - $sql = 'SELECT * FROM modify_limit_table'; - - $this->assertLimitResult([4, 3, 2, 1], $sql, 10, 0, false); - $this->assertLimitResult([4, 3], $sql, 2, 0, false); - $this->assertLimitResult([2, 1], $sql, 2, 2, false); - } - - public function testModifyLimitQueryGroupBy() : void - { - $this->connection->insert('modify_limit_table', ['test_int' => 1]); - $this->connection->insert('modify_limit_table', ['test_int' => 2]); - - $this->connection->insert('modify_limit_table2', ['test_int' => 1]); - $this->connection->insert('modify_limit_table2', ['test_int' => 1]); - $this->connection->insert('modify_limit_table2', ['test_int' => 1]); - $this->connection->insert('modify_limit_table2', ['test_int' => 2]); - $this->connection->insert('modify_limit_table2', ['test_int' => 2]); - - $sql = 'SELECT modify_limit_table.test_int FROM modify_limit_table ' . - 'INNER JOIN modify_limit_table2 ON modify_limit_table.test_int = modify_limit_table2.test_int ' . - 'GROUP BY modify_limit_table.test_int ' . - 'ORDER BY modify_limit_table.test_int ASC'; - $this->assertLimitResult([1, 2], $sql, 10, 0); - $this->assertLimitResult([1], $sql, 1, 0); - $this->assertLimitResult([2], $sql, 1, 1); - } - - public function testModifyLimitQuerySubSelect() : void - { - $this->connection->insert('modify_limit_table', ['test_int' => 1]); - $this->connection->insert('modify_limit_table', ['test_int' => 2]); - $this->connection->insert('modify_limit_table', ['test_int' => 3]); - $this->connection->insert('modify_limit_table', ['test_int' => 4]); - - $sql = 'SELECT modify_limit_table.*, (SELECT COUNT(*) FROM modify_limit_table) AS cnt FROM modify_limit_table ORDER BY test_int DESC'; - - $this->assertLimitResult([4, 3, 2, 1], $sql, 10, 0); - $this->assertLimitResult([4, 3], $sql, 2, 0); - $this->assertLimitResult([2, 1], $sql, 2, 2); - } - - public function testModifyLimitQueryFromSubSelect() : void - { - $this->connection->insert('modify_limit_table', ['test_int' => 1]); - $this->connection->insert('modify_limit_table', ['test_int' => 2]); - $this->connection->insert('modify_limit_table', ['test_int' => 3]); - $this->connection->insert('modify_limit_table', ['test_int' => 4]); - - $sql = 'SELECT * FROM (SELECT * FROM modify_limit_table) sub ORDER BY test_int DESC'; - - $this->assertLimitResult([4, 3, 2, 1], $sql, 10, 0); - $this->assertLimitResult([4, 3], $sql, 2, 0); - $this->assertLimitResult([2, 1], $sql, 2, 2); - } - - public function testModifyLimitQueryLineBreaks() : void - { - $this->connection->insert('modify_limit_table', ['test_int' => 1]); - $this->connection->insert('modify_limit_table', ['test_int' => 2]); - $this->connection->insert('modify_limit_table', ['test_int' => 3]); - - $sql = <<assertLimitResult([2], $sql, 1, 1); - } - - public function testModifyLimitQueryZeroOffsetNoLimit() : void - { - $this->connection->insert('modify_limit_table', ['test_int' => 1]); - $this->connection->insert('modify_limit_table', ['test_int' => 2]); - - $sql = 'SELECT test_int FROM modify_limit_table ORDER BY test_int ASC'; - - $this->assertLimitResult([1, 2], $sql, null, 0); - } - - /** - * @param array $expectedResults - */ - private function assertLimitResult(array $expectedResults, string $sql, ?int $limit, int $offset, bool $deterministic = true) : void - { - $p = $this->connection->getDatabasePlatform(); - $data = []; - foreach ($this->connection->fetchAll($p->modifyLimitQuery($sql, $limit, $offset)) as $row) { - $row = array_change_key_case($row, CASE_LOWER); - $data[] = $row['test_int']; - } - - /** - * Do not assert the order of results when results are non-deterministic - */ - if ($deterministic) { - self::assertEquals($expectedResults, $data); - } else { - self::assertCount(count($expectedResults), $data); - } - } -} diff --git a/tests/Functional/DBAL/NamedParametersTest.php b/tests/Functional/DBAL/NamedParametersTest.php deleted file mode 100644 index d85f643..0000000 --- a/tests/Functional/DBAL/NamedParametersTest.php +++ /dev/null @@ -1,224 +0,0 @@ -> - */ - public static function ticketProvider() : iterable - { - return [ - [ - 'SELECT * FROM ddc1372_foobar f WHERE f.foo = :foo AND f.bar IN (:bar)', - [ - 'foo' => 1, - 'bar' => [1, 2, 3], - ], - [ - 'foo' => ParameterType::INTEGER, - 'bar' => Connection::PARAM_INT_ARRAY, - ], - [ - ['id' => 1, 'foo' => 1, 'bar' => 1], - ['id' => 2, 'foo' => 1, 'bar' => 2], - ['id' => 3, 'foo' => 1, 'bar' => 3], - ], - ], - - [ - 'SELECT * FROM ddc1372_foobar f WHERE f.foo = :foo AND f.bar IN (:bar)', - [ - 'foo' => 1, - 'bar' => [1, 2, 3], - ], - [ - 'bar' => Connection::PARAM_INT_ARRAY, - 'foo' => ParameterType::INTEGER, - ], - [ - ['id' => 1, 'foo' => 1, 'bar' => 1], - ['id' => 2, 'foo' => 1, 'bar' => 2], - ['id' => 3, 'foo' => 1, 'bar' => 3], - ], - ], - - [ - 'SELECT * FROM ddc1372_foobar f WHERE f.bar IN (:bar) AND f.foo = :foo', - [ - 'foo' => 1, - 'bar' => [1, 2, 3], - ], - [ - 'bar' => Connection::PARAM_INT_ARRAY, - 'foo' => ParameterType::INTEGER, - ], - [ - ['id' => 1, 'foo' => 1, 'bar' => 1], - ['id' => 2, 'foo' => 1, 'bar' => 2], - ['id' => 3, 'foo' => 1, 'bar' => 3], - ], - ], - - [ - 'SELECT * FROM ddc1372_foobar f WHERE f.bar IN (:bar) AND f.foo = :foo', - [ - 'foo' => 1, - 'bar' => ['1', '2', '3'], - ], - [ - 'bar' => Connection::PARAM_STR_ARRAY, - 'foo' => ParameterType::INTEGER, - ], - [ - ['id' => 1, 'foo' => 1, 'bar' => 1], - ['id' => 2, 'foo' => 1, 'bar' => 2], - ['id' => 3, 'foo' => 1, 'bar' => 3], - ], - ], - - [ - 'SELECT * FROM ddc1372_foobar f WHERE f.bar IN (:bar) AND f.foo IN (:foo)', - [ - 'foo' => ['1'], - 'bar' => [1, 2, 3, 4], - ], - [ - 'bar' => Connection::PARAM_STR_ARRAY, - 'foo' => Connection::PARAM_INT_ARRAY, - ], - [ - ['id' => 1, 'foo' => 1, 'bar' => 1], - ['id' => 2, 'foo' => 1, 'bar' => 2], - ['id' => 3, 'foo' => 1, 'bar' => 3], - ['id' => 4, 'foo' => 1, 'bar' => 4], - ], - ], - - [ - 'SELECT * FROM ddc1372_foobar f WHERE f.bar IN (:bar) AND f.foo IN (:foo)', - [ - 'foo' => 1, - 'bar' => 2, - ], - [ - 'bar' => ParameterType::INTEGER, - 'foo' => ParameterType::INTEGER, - ], - [ - ['id' => 2, 'foo' => 1, 'bar' => 2], - ], - ], - - [ - 'SELECT * FROM ddc1372_foobar f WHERE f.bar = :arg AND f.foo <> :arg', - ['arg' => '1'], - [ - 'arg' => ParameterType::STRING, - ], - [ - ['id' => 5, 'foo' => 2, 'bar' => 1], - ], - ], - - [ - 'SELECT * FROM ddc1372_foobar f WHERE f.bar NOT IN (:arg) AND f.foo IN (:arg)', - [ - 'arg' => [1, 2], - ], - [ - 'arg' => Connection::PARAM_INT_ARRAY, - ], - [ - ['id' => 3, 'foo' => 1, 'bar' => 3], - ['id' => 4, 'foo' => 1, 'bar' => 4], - ], - ], - ]; - } - - protected function setUp() : void - { - parent::setUp(); - - if ($this->connection->getSchemaManager()->tablesExist('ddc1372_foobar')) { - return; - } - - try { - $table = new Table('ddc1372_foobar'); - $table->addColumn('id', 'integer'); - $table->addColumn('foo', 'string'); - $table->addColumn('bar', 'string'); - $table->setPrimaryKey(['id']); - - $sm = $this->connection->getSchemaManager(); - $sm->createTable($table); - - $this->connection->insert('ddc1372_foobar', [ - 'id' => 1, - 'foo' => 1, - 'bar' => 1, - ]); - $this->connection->insert('ddc1372_foobar', [ - 'id' => 2, - 'foo' => 1, - 'bar' => 2, - ]); - $this->connection->insert('ddc1372_foobar', [ - 'id' => 3, - 'foo' => 1, - 'bar' => 3, - ]); - $this->connection->insert('ddc1372_foobar', [ - 'id' => 4, - 'foo' => 1, - 'bar' => 4, - ]); - $this->connection->insert('ddc1372_foobar', [ - 'id' => 5, - 'foo' => 2, - 'bar' => 1, - ]); - $this->connection->insert('ddc1372_foobar', [ - 'id' => 6, - 'foo' => 2, - 'bar' => 2, - ]); - } catch (Throwable $e) { - $this->fail($e->getMessage()); - } - } - - /** - * @param mixed[] $params - * @param int[] $types - * @param int[] $expected - * - * @dataProvider ticketProvider - */ - public function testTicket(string $query, array $params, array $types, array $expected) : void - { - $stmt = $this->connection->executeQuery($query, $params, $types); - $result = $stmt->fetchAll(FetchMode::ASSOCIATIVE); - - foreach ($result as $k => $v) { - $result[$k] = array_change_key_case($v, CASE_LOWER); - } - - self::assertEquals($result, $expected); - } -} diff --git a/tests/Functional/DBAL/PDOStatementTest.php b/tests/Functional/DBAL/PDOStatementTest.php deleted file mode 100644 index 9baaf4d..0000000 --- a/tests/Functional/DBAL/PDOStatementTest.php +++ /dev/null @@ -1,54 +0,0 @@ -markTestSkipped('PDO is not installed'); - } - - parent::setUp(); - - if (! $this->connection->getWrappedConnection() instanceof PDOConnection) { - $this->markTestSkipped('PDO-only test'); - } - - $table = new Table('stmt_test'); - $table->addColumn('id', 'integer'); - $table->addColumn('name', 'string'); - $this->connection->getSchemaManager()->dropAndCreateTable($table); - } - - /** - * @group legacy - * @expectedDeprecation Using a PDO fetch mode or their combination (%d given) is deprecated and will cause an error in Doctrine 3.0 - */ - public function testPDOSpecificModeIsAccepted() : void - { - $this->connection->insert('stmt_test', [ - 'id' => 1, - 'name' => 'Alice', - ]); - $this->connection->insert('stmt_test', [ - 'id' => 2, - 'name' => 'Bob', - ]); - - $data = $this->connection->query('SELECT id, name FROM stmt_test ORDER BY id') - ->fetchAll(PDO::FETCH_KEY_PAIR); - - self::assertSame([ - 1 => 'Alice', - 2 => 'Bob', - ], $data); - } -} diff --git a/tests/Functional/DBAL/Platform/DateExpressionTest.php b/tests/Functional/DBAL/Platform/DateExpressionTest.php deleted file mode 100644 index 8427185..0000000 --- a/tests/Functional/DBAL/Platform/DateExpressionTest.php +++ /dev/null @@ -1,55 +0,0 @@ -addColumn('date1', 'datetime'); - $table->addColumn('date2', 'datetime'); - $this->connection->getSchemaManager()->dropAndCreateTable($table); - $this->connection->insert('date_expr_test', [ - 'date1' => $date1, - 'date2' => $date2, - ]); - - $platform = $this->connection->getDatabasePlatform(); - - $sql = sprintf('SELECT %s FROM date_expr_test', $platform->getDateDiffExpression('date1', 'date2')); - $diff = $this->connection->query($sql)->fetchColumn(); - - self::assertEquals($expected, $diff); - } - - /** - * @return string[][]|int[][] - */ - public static function differenceProvider() : iterable - { - $date1 = new DateTimeImmutable(); - $date2 = new DateTimeImmutable('2018-04-10 10:10:10'); - $expected = $date1->modify('midnight')->diff( - $date2->modify('midnight') - )->days; - - return [ - 'dynamic' => [ - $date1->format('Y-m-d H:i:s'), - $date2->format('Y-m-d H:i:s'), - $expected, - ], - 'same day' => ['2018-04-14 23:59:59', '2018-04-14 00:00:00', 0], - 'midnight' => ['2018-04-14 00:00:00', '2018-04-13 23:59:59', 1], - ]; - } -} diff --git a/tests/Functional/DBAL/Platform/DefaultExpressionTest.php b/tests/Functional/DBAL/Platform/DefaultExpressionTest.php deleted file mode 100644 index f46fde8..0000000 --- a/tests/Functional/DBAL/Platform/DefaultExpressionTest.php +++ /dev/null @@ -1,60 +0,0 @@ -assertDefaultExpression(Types::DATE_MUTABLE, static function (AbstractPlatform $platform) : string { - return $platform->getCurrentDateSQL(); - }); - } - - public function testCurrentTime() : void - { - $this->assertDefaultExpression(Types::TIME_MUTABLE, static function (AbstractPlatform $platform) : string { - return $platform->getCurrentTimeSQL(); - }); - } - - public function testCurrentTimestamp() : void - { - $this->assertDefaultExpression(Types::DATETIME_MUTABLE, static function (AbstractPlatform $platform) : string { - return $platform->getCurrentTimestampSQL(); - }); - } - - private function assertDefaultExpression(string $type, callable $expression) : void - { - $platform = $this->connection->getDatabasePlatform(); - $defaultSql = $expression($platform, $this); - - $table = new Table('default_expr_test'); - $table->addColumn('actual_value', $type); - $table->addColumn('default_value', $type, ['default' => $defaultSql]); - $this->connection->getSchemaManager()->dropAndCreateTable($table); - - $this->connection->exec( - sprintf( - 'INSERT INTO default_expr_test (actual_value) VALUES (%s)', - $defaultSql - ) - ); - - [$actualValue, $defaultValue] = $this->connection->query( - 'SELECT default_value, actual_value FROM default_expr_test' - )->fetch(FetchMode::NUMERIC); - - self::assertEquals($actualValue, $defaultValue); - } -} diff --git a/tests/Functional/DBAL/Platform/QuotingTest.php b/tests/Functional/DBAL/Platform/QuotingTest.php deleted file mode 100644 index d340775..0000000 --- a/tests/Functional/DBAL/Platform/QuotingTest.php +++ /dev/null @@ -1,32 +0,0 @@ -connection->getDatabasePlatform(); - $query = $platform->getDummySelectSQL( - $platform->quoteStringLiteral($string) - ); - - self::assertSame($string, $this->connection->fetchColumn($query)); - } - - /** - * @return mixed[][] - */ - public static function stringLiteralProvider() : iterable - { - return [ - 'backslash' => ['\\'], - 'single-quote' => ["'"], - ]; - } -} diff --git a/tests/Functional/DBAL/PortabilityTest.php b/tests/Functional/DBAL/PortabilityTest.php deleted file mode 100644 index 178846c..0000000 --- a/tests/Functional/DBAL/PortabilityTest.php +++ /dev/null @@ -1,177 +0,0 @@ -portableConnection) { - $this->portableConnection->close(); - } - - parent::tearDown(); - } - - private function getPortableConnection( - int $portabilityMode = ConnectionPortability::PORTABILITY_ALL, - int $case = ColumnCase::LOWER - ) : Connection { - if (! $this->portableConnection) { - $params = $this->connection->getParams(); - - $params['wrapperClass'] = ConnectionPortability::class; - $params['portability'] = $portabilityMode; - $params['fetch_case'] = $case; - - $this->portableConnection = DriverManager::getConnection($params, $this->connection->getConfiguration(), $this->connection->getEventManager()); - - try { - $table = new Table('portability_table'); - $table->addColumn('Test_Int', 'integer'); - $table->addColumn('Test_String', 'string', ['fixed' => true, 'length' => 32]); - $table->addColumn('Test_Null', 'string', ['notnull' => false]); - $table->setPrimaryKey(['Test_Int']); - - $sm = $this->portableConnection->getSchemaManager(); - $sm->createTable($table); - - $this->portableConnection->insert('portability_table', ['Test_Int' => 1, 'Test_String' => 'foo', 'Test_Null' => '']); - $this->portableConnection->insert('portability_table', ['Test_Int' => 2, 'Test_String' => 'foo ', 'Test_Null' => null]); - } catch (Throwable $e) { - } - } - - return $this->portableConnection; - } - - public function testFullFetchMode() : void - { - $rows = $this->getPortableConnection()->fetchAll('SELECT * FROM portability_table'); - $this->assertFetchResultRows($rows); - - $stmt = $this->getPortableConnection()->query('SELECT * FROM portability_table'); - $stmt->setFetchMode(FetchMode::ASSOCIATIVE); - - foreach ($stmt as $row) { - $this->assertFetchResultRow($row); - } - - $stmt = $this->getPortableConnection()->query('SELECT * FROM portability_table'); - - while (($row = $stmt->fetch(FetchMode::ASSOCIATIVE))) { - $this->assertFetchResultRow($row); - } - - $stmt = $this->getPortableConnection()->prepare('SELECT * FROM portability_table'); - $stmt->execute(); - - while (($row = $stmt->fetch(FetchMode::ASSOCIATIVE))) { - $this->assertFetchResultRow($row); - } - } - - public function testConnFetchMode() : void - { - $conn = $this->getPortableConnection(); - $conn->setFetchMode(FetchMode::ASSOCIATIVE); - - $rows = $conn->fetchAll('SELECT * FROM portability_table'); - $this->assertFetchResultRows($rows); - - $stmt = $conn->query('SELECT * FROM portability_table'); - foreach ($stmt as $row) { - $this->assertFetchResultRow($row); - } - - $stmt = $conn->query('SELECT * FROM portability_table'); - while (($row = $stmt->fetch())) { - $this->assertFetchResultRow($row); - } - - $stmt = $conn->prepare('SELECT * FROM portability_table'); - $stmt->execute(); - while (($row = $stmt->fetch())) { - $this->assertFetchResultRow($row); - } - } - - /** - * @param array> $rows - */ - private function assertFetchResultRows(array $rows) : void - { - self::assertCount(2, $rows); - foreach ($rows as $row) { - $this->assertFetchResultRow($row); - } - } - - /** - * @param array $row - */ - public function assertFetchResultRow(array $row) : void - { - self::assertContains($row['test_int'], [1, 2], 'Primary key test_int should either be 1 or 2.'); - self::assertArrayHasKey('test_string', $row, 'Case should be lowered.'); - self::assertEquals(3, strlen($row['test_string']), 'test_string should be rtrimed to length of three for CHAR(32) column.'); - self::assertNull($row['test_null']); - self::assertArrayNotHasKey(0, $row, 'The row should not contain numerical keys.'); - } - - /** - * @param mixed[] $expected - * - * @dataProvider fetchAllColumnProvider - */ - public function testFetchAllColumn(string $field, array $expected) : void - { - $conn = $this->getPortableConnection(); - $stmt = $conn->query('SELECT ' . $field . ' FROM portability_table'); - - $column = $stmt->fetchAll(FetchMode::COLUMN); - self::assertEquals($expected, $column); - } - - /** - * @return iterable> - */ - public static function fetchAllColumnProvider() : iterable - { - return [ - 'int' => [ - 'Test_Int', - [1, 2], - ], - 'string' => [ - 'Test_String', - ['foo', 'foo'], - ], - ]; - } - - public function testFetchAllNullColumn() : void - { - $conn = $this->getPortableConnection(); - $stmt = $conn->query('SELECT Test_Null FROM portability_table'); - - $column = $stmt->fetchAll(FetchMode::COLUMN); - self::assertSame([null, null], $column); - } -} diff --git a/tests/Functional/DBAL/ResultCacheTest.php b/tests/Functional/DBAL/ResultCacheTest.php deleted file mode 100644 index b3c61b8..0000000 --- a/tests/Functional/DBAL/ResultCacheTest.php +++ /dev/null @@ -1,251 +0,0 @@ -> */ - private $expectedResult = [['test_int' => 100, 'test_string' => 'foo'], ['test_int' => 200, 'test_string' => 'bar'], ['test_int' => 300, 'test_string' => 'baz']]; - - /** @var DebugStack */ - private $sqlLogger; - - protected function setUp() : void - { - parent::setUp(); - - $table = new Table('caching'); - $table->addColumn('test_int', 'integer'); - $table->addColumn('test_string', 'string', ['notnull' => false]); - $table->setPrimaryKey(['test_int']); - - $sm = $this->connection->getSchemaManager(); - $sm->createTable($table); - - foreach ($this->expectedResult as $row) { - $this->connection->insert('caching', $row); - } - - $config = $this->connection->getConfiguration(); - $config->setSQLLogger($this->sqlLogger = new DebugStack()); - - $cache = new ArrayCache(); - $config->setResultCacheImpl($cache); - } - - protected function tearDown() : void - { - $this->connection->getSchemaManager()->dropTable('caching'); - - parent::tearDown(); - } - - public function testCacheFetchAssoc() : void - { - $this->assertCacheNonCacheSelectSameFetchModeAreEqual( - $this->expectedResult, - FetchMode::ASSOCIATIVE - ); - } - - public function testFetchNum() : void - { - $expectedResult = []; - foreach ($this->expectedResult as $v) { - $expectedResult[] = array_values($v); - } - - $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::NUMERIC); - } - - public function testFetchBoth() : void - { - $expectedResult = []; - foreach ($this->expectedResult as $v) { - $expectedResult[] = array_merge($v, array_values($v)); - } - - $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::MIXED); - } - - public function testFetchColumn() : void - { - $expectedResult = []; - foreach ($this->expectedResult as $v) { - $expectedResult[] = array_shift($v); - } - - $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::COLUMN); - } - - public function testMixingFetch() : void - { - $numExpectedResult = []; - foreach ($this->expectedResult as $v) { - $numExpectedResult[] = array_values($v); - } - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - - $data = $this->hydrateStmt($stmt, FetchMode::ASSOCIATIVE); - - self::assertEquals($this->expectedResult, $data); - - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - - $data = $this->hydrateStmt($stmt, FetchMode::NUMERIC); - - self::assertEquals($numExpectedResult, $data); - } - - public function testIteratorFetch() : void - { - self::assertStandardAndIteratorFetchAreEqual(FetchMode::MIXED); - self::assertStandardAndIteratorFetchAreEqual(FetchMode::ASSOCIATIVE); - self::assertStandardAndIteratorFetchAreEqual(FetchMode::NUMERIC); - } - - private function assertStandardAndIteratorFetchAreEqual(int $fetchMode) : void - { - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - $data = $this->hydrateStmt($stmt, $fetchMode); - - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - $data_iterator = $this->hydrateStmtIterator($stmt, $fetchMode); - - self::assertEquals($data, $data_iterator); - } - - public function testDontCloseNoCache() : void - { - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - - $data = []; - - while ($row = $stmt->fetch(FetchMode::ASSOCIATIVE)) { - $data[] = $row; - } - - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - - $data = []; - - while ($row = $stmt->fetch(FetchMode::NUMERIC)) { - $data[] = $row; - } - - self::assertCount(2, $this->sqlLogger->queries); - } - - public function testDontFinishNoCache() : void - { - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - - $stmt->fetch(FetchMode::ASSOCIATIVE); - $stmt->closeCursor(); - - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - - $this->hydrateStmt($stmt, FetchMode::NUMERIC); - - self::assertCount(2, $this->sqlLogger->queries); - } - - public function testFetchAllAndFinishSavesCache() : void - { - $layerCache = new ArrayCache(); - $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'testcachekey', $layerCache)); - $stmt->fetchAll(); - $stmt->closeCursor(); - - self::assertCount(1, $layerCache->fetch('testcachekey')); - } - - /** - * @param array> $expectedResult - */ - private function assertCacheNonCacheSelectSameFetchModeAreEqual(array $expectedResult, int $fetchMode) : void - { - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - - self::assertEquals(2, $stmt->columnCount()); - $data = $this->hydrateStmt($stmt, $fetchMode); - self::assertEquals($expectedResult, $data); - - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey')); - - self::assertEquals(2, $stmt->columnCount()); - $data = $this->hydrateStmt($stmt, $fetchMode); - self::assertEquals($expectedResult, $data); - self::assertCount(1, $this->sqlLogger->queries, 'just one dbal hit'); - } - - public function testEmptyResultCache() : void - { - $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey')); - $data = $this->hydrateStmt($stmt); - - $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey')); - $data = $this->hydrateStmt($stmt); - - self::assertCount(1, $this->sqlLogger->queries, 'just one dbal hit'); - } - - public function testChangeCacheImpl() : void - { - $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey')); - $data = $this->hydrateStmt($stmt); - - $secondCache = new ArrayCache(); - $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey', $secondCache)); - $data = $this->hydrateStmt($stmt); - - self::assertCount(2, $this->sqlLogger->queries, 'two hits'); - self::assertCount(1, $secondCache->fetch('emptycachekey')); - } - - /** - * @return array - */ - private function hydrateStmt(ResultStatement $stmt, int $fetchMode = FetchMode::ASSOCIATIVE) : array - { - $data = []; - while ($row = $stmt->fetch($fetchMode)) { - $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row; - } - $stmt->closeCursor(); - - return $data; - } - - /** - * @return array - */ - private function hydrateStmtIterator(ResultStatement $stmt, int $fetchMode = FetchMode::ASSOCIATIVE) : array - { - $data = []; - $stmt->setFetchMode($fetchMode); - foreach ($stmt as $row) { - $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row; - } - $stmt->closeCursor(); - - return $data; - } -} diff --git a/tests/Functional/DBAL/Schema/ComparatorTest.php b/tests/Functional/DBAL/Schema/ComparatorTest.php deleted file mode 100644 index ad75d88..0000000 --- a/tests/Functional/DBAL/Schema/ComparatorTest.php +++ /dev/null @@ -1,55 +0,0 @@ -schemaManager = $this->connection->getSchemaManager(); - $this->comparator = new Comparator(); - } - - /** - * @param mixed $value - * - * @dataProvider defaultValueProvider - */ - public function testDefaultValueComparison(string $type, $value) : void - { - $table = new Table('default_value'); - $table->addColumn('test', $type, ['default' => $value]); - - $this->schemaManager->dropAndCreateTable($table); - - $onlineTable = $this->schemaManager->listTableDetails('default_value'); - - self::assertFalse($this->comparator->diffTable($table, $onlineTable)); - } - - /** - * @return mixed[][] - */ - public static function defaultValueProvider() : iterable - { - return [ - ['integer', 1], - ['boolean', false], - ]; - } -} diff --git a/tests/Functional/DBAL/Schema/Db2SchemaManagerTest.php b/tests/Functional/DBAL/Schema/Db2SchemaManagerTest.php deleted file mode 100644 index d9b863f..0000000 --- a/tests/Functional/DBAL/Schema/Db2SchemaManagerTest.php +++ /dev/null @@ -1,34 +0,0 @@ -addColumn('bool', 'boolean'); - $table->addColumn('bool_commented', 'boolean', ['comment' => "That's a comment"]); - - $this->schemaManager->createTable($table); - - $columns = $this->schemaManager->listTableColumns('boolean_column_test'); - - self::assertInstanceOf(BooleanType::class, $columns['bool']->getType()); - self::assertInstanceOf(BooleanType::class, $columns['bool_commented']->getType()); - - self::assertNull($columns['bool']->getComment()); - self::assertSame("That's a comment", $columns['bool_commented']->getComment()); - } - - public function testListTableWithBinary() : void - { - self::markTestSkipped('Binary data type is currently not supported on DB2 LUW'); - } -} diff --git a/tests/Functional/DBAL/Schema/DefaultValueTest.php b/tests/Functional/DBAL/Schema/DefaultValueTest.php deleted file mode 100644 index 7aaa743..0000000 --- a/tests/Functional/DBAL/Schema/DefaultValueTest.php +++ /dev/null @@ -1,155 +0,0 @@ -addColumn('id', 'integer'); - - foreach (self::columnProvider() as [$name, $default]) { - $table->addColumn($name, 'string', [ - 'default' => $default, - 'notnull' => false, - ]); - } - - $this->connection->getSchemaManager() - ->dropAndCreateTable($table); - - $this->connection->insert('default_value', ['id' => 1]); - } - - /** - * @param mixed $expectedDefault - * - * @dataProvider columnProvider - */ - public function testEscapedDefaultValueCanBeIntrospected(string $name, ?string $expectedDefault) : void - { - self::assertSame( - $expectedDefault, - $this->connection - ->getSchemaManager() - ->listTableDetails('default_value') - ->getColumn($name) - ->getDefault() - ); - } - - /** - * @param mixed $expectedDefault - * - * @dataProvider columnProvider - */ - public function testEscapedDefaultValueCanBeInserted(string $name, ?string $expectedDefault) : void - { - $value = $this->connection->fetchColumn( - sprintf('SELECT %s FROM default_value', $name) - ); - - self::assertSame($expectedDefault, $value); - } - - /** - * Returns potential escaped literals from all platforms combined. - * - * @see https://dev.mysql.com/doc/refman/5.7/en/string-literals.html - * @see http://www.sqlite.org/lang_expr.html - * @see https://www.postgresql.org/docs/9.6/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE - * - * @return mixed[][] - */ - public static function columnProvider() : iterable - { - return [ - 'Single quote' => [ - 'single_quote', - "foo'bar", - ], - 'Single quote, doubled' => [ - 'single_quote_doubled', - "foo''bar", - ], - 'Double quote' => [ - 'double_quote', - 'foo"bar', - ], - 'Double quote, doubled' => [ - 'double_quote_doubled', - 'foo""bar', - ], - 'Backspace' => [ - 'backspace', - "foo\x08bar", - ], - 'New line' => [ - 'new_line', - "foo\nbar", - ], - 'Carriage return' => [ - 'carriage_return', - "foo\rbar", - ], - 'Tab' => [ - 'tab', - "foo\tbar", - ], - 'Substitute' => [ - 'substitute', - "foo\x1abar", - ], - 'Backslash' => [ - 'backslash', - 'foo\\bar', - ], - 'Backslash, doubled' => [ - 'backslash_doubled', - 'foo\\\\bar', - ], - 'Percent' => [ - 'percent_sign', - 'foo%bar', - ], - 'Underscore' => [ - 'underscore', - 'foo_bar', - ], - 'NULL string' => [ - 'null_string', - 'NULL', - ], - 'NULL value' => [ - 'null_value', - null, - ], - 'SQL expression' => [ - 'sql_expression', - "'; DROP DATABASE doctrine --", - ], - 'No double conversion' => [ - 'no_double_conversion', - "\\'", - ], - ]; - } -} diff --git a/tests/Functional/DBAL/Schema/DrizzleSchemaManagerTest.php b/tests/Functional/DBAL/Schema/DrizzleSchemaManagerTest.php deleted file mode 100644 index 291d356..0000000 --- a/tests/Functional/DBAL/Schema/DrizzleSchemaManagerTest.php +++ /dev/null @@ -1,48 +0,0 @@ -addColumn('id', 'integer'); - $table->addColumn('column_varbinary', 'binary', []); - $table->addColumn('column_binary', 'binary', ['fixed' => true]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $table = $this->schemaManager->listTableDetails($tableName); - - self::assertInstanceOf(BinaryType::class, $table->getColumn('column_varbinary')->getType()); - self::assertFalse($table->getColumn('column_varbinary')->getFixed()); - - self::assertInstanceOf(BinaryType::class, $table->getColumn('column_binary')->getType()); - self::assertFalse($table->getColumn('column_binary')->getFixed()); - } - - public function testColumnCollation() : void - { - $table = new Table('test_collation'); - $table->addOption('collate', $collation = 'utf8_unicode_ci'); - $table->addColumn('id', 'integer'); - $table->addColumn('text', 'text'); - $table->addColumn('foo', 'text')->setPlatformOption('collation', 'utf8_swedish_ci'); - $table->addColumn('bar', 'text')->setPlatformOption('collation', 'utf8_general_ci'); - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('test_collation'); - - self::assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); - self::assertEquals('utf8_unicode_ci', $columns['text']->getPlatformOption('collation')); - self::assertEquals('utf8_swedish_ci', $columns['foo']->getPlatformOption('collation')); - self::assertEquals('utf8_general_ci', $columns['bar']->getPlatformOption('collation')); - } -} diff --git a/tests/Functional/DBAL/Schema/MySqlSchemaManagerTest.php b/tests/Functional/DBAL/Schema/MySqlSchemaManagerTest.php deleted file mode 100644 index a4d6b3d..0000000 --- a/tests/Functional/DBAL/Schema/MySqlSchemaManagerTest.php +++ /dev/null @@ -1,567 +0,0 @@ -resetSharedConn(); - - Type::addType('point', MySqlPointType::class); - } - - public function testSwitchPrimaryKeyColumns() : void - { - $tableOld = new Table('switch_primary_key_columns'); - $tableOld->addColumn('foo_id', 'integer'); - $tableOld->addColumn('bar_id', 'integer'); - - $this->schemaManager->createTable($tableOld); - $tableFetched = $this->schemaManager->listTableDetails('switch_primary_key_columns'); - $tableNew = clone $tableFetched; - $tableNew->setPrimaryKey(['bar_id', 'foo_id']); - - $comparator = new Comparator(); - $this->schemaManager->alterTable($comparator->diffTable($tableFetched, $tableNew)); - - $table = $this->schemaManager->listTableDetails('switch_primary_key_columns'); - $primaryKey = $table->getPrimaryKeyColumns(); - - self::assertCount(2, $primaryKey); - self::assertContains('bar_id', $primaryKey); - self::assertContains('foo_id', $primaryKey); - } - - public function testDiffTableBug() : void - { - $schema = new Schema(); - $table = $schema->createTable('diffbug_routing_translations'); - $table->addColumn('id', 'integer'); - $table->addColumn('route', 'string'); - $table->addColumn('locale', 'string'); - $table->addColumn('attribute', 'string'); - $table->addColumn('localized_value', 'string'); - $table->addColumn('original_value', 'string'); - $table->setPrimaryKey(['id']); - $table->addUniqueIndex(['route', 'locale', 'attribute']); - $table->addIndex(['localized_value']); // this is much more selective than the unique index - - $this->schemaManager->createTable($table); - $tableFetched = $this->schemaManager->listTableDetails('diffbug_routing_translations'); - - $comparator = new Comparator(); - $diff = $comparator->diffTable($tableFetched, $table); - - self::assertFalse($diff, 'no changes expected.'); - } - - public function testFulltextIndex() : void - { - $table = new Table('fulltext_index'); - $table->addColumn('text', 'text'); - $table->addIndex(['text'], 'f_index'); - $table->addOption('engine', 'MyISAM'); - - $index = $table->getIndex('f_index'); - $index->addFlag('fulltext'); - - $this->schemaManager->dropAndCreateTable($table); - - $indexes = $this->schemaManager->listTableIndexes('fulltext_index'); - self::assertArrayHasKey('f_index', $indexes); - self::assertTrue($indexes['f_index']->hasFlag('fulltext')); - } - - public function testSpatialIndex() : void - { - $table = new Table('spatial_index'); - $table->addColumn('point', 'point'); - $table->addIndex(['point'], 's_index'); - $table->addOption('engine', 'MyISAM'); - - $index = $table->getIndex('s_index'); - $index->addFlag('spatial'); - - $this->schemaManager->dropAndCreateTable($table); - - $indexes = $this->schemaManager->listTableIndexes('spatial_index'); - self::assertArrayHasKey('s_index', $indexes); - self::assertTrue($indexes['s_index']->hasFlag('spatial')); - } - - public function testIndexWithLength() : void - { - $table = new Table('index_length'); - $table->addColumn('text', 'string', ['length' => 255]); - $table->addIndex(['text'], 'text_index', [], ['lengths' => [128]]); - - $this->schemaManager->dropAndCreateTable($table); - - $indexes = $this->schemaManager->listTableIndexes('index_length'); - self::assertArrayHasKey('text_index', $indexes); - self::assertSame([128], $indexes['text_index']->getOption('lengths')); - } - - /** - * @group DBAL-400 - */ - public function testAlterTableAddPrimaryKey() : void - { - $table = new Table('alter_table_add_pk'); - $table->addColumn('id', 'integer'); - $table->addColumn('foo', 'integer'); - $table->addIndex(['id'], 'idx_id'); - - $this->schemaManager->createTable($table); - - $comparator = new Comparator(); - $diffTable = clone $table; - - $diffTable->dropIndex('idx_id'); - $diffTable->setPrimaryKey(['id']); - - $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); - - $table = $this->schemaManager->listTableDetails('alter_table_add_pk'); - - self::assertFalse($table->hasIndex('idx_id')); - self::assertTrue($table->hasPrimaryKey()); - } - - /** - * @group DBAL-464 - */ - public function testDropPrimaryKeyWithAutoincrementColumn() : void - { - $table = new Table('drop_primary_key'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->addColumn('foo', 'integer'); - $table->setPrimaryKey(['id', 'foo']); - - $this->schemaManager->dropAndCreateTable($table); - - $diffTable = clone $table; - - $diffTable->dropPrimaryKey(); - - $comparator = new Comparator(); - - $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); - - $table = $this->schemaManager->listTableDetails('drop_primary_key'); - - self::assertFalse($table->hasPrimaryKey()); - self::assertFalse($table->getColumn('id')->getAutoincrement()); - } - - /** - * @group DBAL-789 - */ - public function testDoesNotPropagateDefaultValuesForUnsupportedColumnTypes() : void - { - if ($this->schemaManager->getDatabasePlatform() instanceof MariaDb1027Platform) { - $this->markTestSkipped( - 'MariaDb102Platform supports default values for BLOB and TEXT columns and will propagate values' - ); - } - - $table = new Table('text_blob_default_value'); - $table->addColumn('def_text', 'text', ['default' => 'def']); - $table->addColumn('def_text_null', 'text', ['notnull' => false, 'default' => 'def']); - $table->addColumn('def_blob', 'blob', ['default' => 'def']); - $table->addColumn('def_blob_null', 'blob', ['notnull' => false, 'default' => 'def']); - - $this->schemaManager->dropAndCreateTable($table); - - $onlineTable = $this->schemaManager->listTableDetails('text_blob_default_value'); - - self::assertNull($onlineTable->getColumn('def_text')->getDefault()); - self::assertNull($onlineTable->getColumn('def_text_null')->getDefault()); - self::assertFalse($onlineTable->getColumn('def_text_null')->getNotnull()); - self::assertNull($onlineTable->getColumn('def_blob')->getDefault()); - self::assertNull($onlineTable->getColumn('def_blob_null')->getDefault()); - self::assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull()); - - $comparator = new Comparator(); - - $this->schemaManager->alterTable($comparator->diffTable($table, $onlineTable)); - - $onlineTable = $this->schemaManager->listTableDetails('text_blob_default_value'); - - self::assertNull($onlineTable->getColumn('def_text')->getDefault()); - self::assertNull($onlineTable->getColumn('def_text_null')->getDefault()); - self::assertFalse($onlineTable->getColumn('def_text_null')->getNotnull()); - self::assertNull($onlineTable->getColumn('def_blob')->getDefault()); - self::assertNull($onlineTable->getColumn('def_blob_null')->getDefault()); - self::assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull()); - } - - public function testColumnCharset() : void - { - $table = new Table('test_column_charset'); - $table->addColumn('id', 'integer'); - $table->addColumn('no_charset', 'text'); - $table->addColumn('foo', 'text')->setPlatformOption('charset', 'ascii'); - $table->addColumn('bar', 'text')->setPlatformOption('charset', 'latin1'); - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('test_column_charset'); - - self::assertFalse($columns['id']->hasPlatformOption('charset')); - self::assertEquals('utf8', $columns['no_charset']->getPlatformOption('charset')); - self::assertEquals('ascii', $columns['foo']->getPlatformOption('charset')); - self::assertEquals('latin1', $columns['bar']->getPlatformOption('charset')); - } - - public function testAlterColumnCharset() : void - { - $tableName = 'test_alter_column_charset'; - - $table = new Table($tableName); - $table->addColumn('col_text', 'text')->setPlatformOption('charset', 'utf8'); - - $this->schemaManager->dropAndCreateTable($table); - - $diffTable = clone $table; - $diffTable->getColumn('col_text')->setPlatformOption('charset', 'ascii'); - - $comparator = new Comparator(); - - $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); - - $table = $this->schemaManager->listTableDetails($tableName); - - self::assertEquals('ascii', $table->getColumn('col_text')->getPlatformOption('charset')); - } - - public function testColumnCharsetChange() : void - { - $table = new Table('test_column_charset_change'); - $table->addColumn('col_string', 'string')->setLength(100)->setNotnull(true)->setPlatformOption('charset', 'utf8'); - - $diffTable = clone $table; - $diffTable->getColumn('col_string')->setPlatformOption('charset', 'ascii'); - - $fromSchema = new Schema([$table]); - $toSchema = new Schema([$diffTable]); - - $diff = $fromSchema->getMigrateToSql($toSchema, $this->connection->getDatabasePlatform()); - self::assertContains('ALTER TABLE test_column_charset_change CHANGE col_string col_string VARCHAR(100) CHARACTER SET ascii NOT NULL', $diff); - } - - public function testColumnCollation() : void - { - $table = new Table('test_collation'); - $table->addOption('collate', $collation = 'latin1_swedish_ci'); - $table->addOption('charset', 'latin1'); - $table->addColumn('id', 'integer'); - $table->addColumn('text', 'text'); - $table->addColumn('foo', 'text')->setPlatformOption('collation', 'latin1_swedish_ci'); - $table->addColumn('bar', 'text')->setPlatformOption('collation', 'utf8_general_ci'); - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('test_collation'); - - self::assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); - self::assertEquals('latin1_swedish_ci', $columns['text']->getPlatformOption('collation')); - self::assertEquals('latin1_swedish_ci', $columns['foo']->getPlatformOption('collation')); - self::assertEquals('utf8_general_ci', $columns['bar']->getPlatformOption('collation')); - } - - /** - * @group DBAL-843 - */ - public function testListLobTypeColumns() : void - { - $tableName = 'lob_type_columns'; - $table = new Table($tableName); - - $table->addColumn('col_tinytext', 'text', ['length' => MySqlPlatform::LENGTH_LIMIT_TINYTEXT]); - $table->addColumn('col_text', 'text', ['length' => MySqlPlatform::LENGTH_LIMIT_TEXT]); - $table->addColumn('col_mediumtext', 'text', ['length' => MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT]); - $table->addColumn('col_longtext', 'text'); - - $table->addColumn('col_tinyblob', 'text', ['length' => MySqlPlatform::LENGTH_LIMIT_TINYBLOB]); - $table->addColumn('col_blob', 'blob', ['length' => MySqlPlatform::LENGTH_LIMIT_BLOB]); - $table->addColumn('col_mediumblob', 'blob', ['length' => MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB]); - $table->addColumn('col_longblob', 'blob'); - - $this->schemaManager->dropAndCreateTable($table); - - $platform = $this->schemaManager->getDatabasePlatform(); - $offlineColumns = $table->getColumns(); - $onlineColumns = $this->schemaManager->listTableColumns($tableName); - - self::assertSame( - $platform->getClobTypeDeclarationSQL($offlineColumns['col_tinytext']->toArray()), - $platform->getClobTypeDeclarationSQL($onlineColumns['col_tinytext']->toArray()) - ); - self::assertSame( - $platform->getClobTypeDeclarationSQL($offlineColumns['col_text']->toArray()), - $platform->getClobTypeDeclarationSQL($onlineColumns['col_text']->toArray()) - ); - self::assertSame( - $platform->getClobTypeDeclarationSQL($offlineColumns['col_mediumtext']->toArray()), - $platform->getClobTypeDeclarationSQL($onlineColumns['col_mediumtext']->toArray()) - ); - self::assertSame( - $platform->getClobTypeDeclarationSQL($offlineColumns['col_longtext']->toArray()), - $platform->getClobTypeDeclarationSQL($onlineColumns['col_longtext']->toArray()) - ); - - self::assertSame( - $platform->getBlobTypeDeclarationSQL($offlineColumns['col_tinyblob']->toArray()), - $platform->getBlobTypeDeclarationSQL($onlineColumns['col_tinyblob']->toArray()) - ); - self::assertSame( - $platform->getBlobTypeDeclarationSQL($offlineColumns['col_blob']->toArray()), - $platform->getBlobTypeDeclarationSQL($onlineColumns['col_blob']->toArray()) - ); - self::assertSame( - $platform->getBlobTypeDeclarationSQL($offlineColumns['col_mediumblob']->toArray()), - $platform->getBlobTypeDeclarationSQL($onlineColumns['col_mediumblob']->toArray()) - ); - self::assertSame( - $platform->getBlobTypeDeclarationSQL($offlineColumns['col_longblob']->toArray()), - $platform->getBlobTypeDeclarationSQL($onlineColumns['col_longblob']->toArray()) - ); - } - - /** - * @group DBAL-423 - */ - public function testDiffListGuidTableColumn() : void - { - $offlineTable = new Table('list_guid_table_column'); - $offlineTable->addColumn('col_guid', 'guid'); - - $this->schemaManager->dropAndCreateTable($offlineTable); - - $onlineTable = $this->schemaManager->listTableDetails('list_guid_table_column'); - - $comparator = new Comparator(); - - self::assertFalse( - $comparator->diffTable($offlineTable, $onlineTable), - 'No differences should be detected with the offline vs online schema.' - ); - } - - /** - * @group DBAL-1082 - */ - public function testListDecimalTypeColumns() : void - { - $tableName = 'test_list_decimal_columns'; - $table = new Table($tableName); - - $table->addColumn('col', 'decimal'); - $table->addColumn('col_unsigned', 'decimal', ['unsigned' => true]); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertArrayHasKey('col', $columns); - self::assertArrayHasKey('col_unsigned', $columns); - self::assertFalse($columns['col']->getUnsigned()); - self::assertTrue($columns['col_unsigned']->getUnsigned()); - } - - /** - * @group DBAL-1082 - */ - public function testListFloatTypeColumns() : void - { - $tableName = 'test_list_float_columns'; - $table = new Table($tableName); - - $table->addColumn('col', 'float'); - $table->addColumn('col_unsigned', 'float', ['unsigned' => true]); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertArrayHasKey('col', $columns); - self::assertArrayHasKey('col_unsigned', $columns); - self::assertFalse($columns['col']->getUnsigned()); - self::assertTrue($columns['col_unsigned']->getUnsigned()); - } - - public function testJsonColumnType() : void - { - $table = new Table('test_mysql_json'); - $table->addColumn('col_json', 'json'); - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('test_mysql_json'); - - self::assertSame(Types::JSON, $columns['col_json']->getType()->getName()); - } - - public function testColumnDefaultCurrentTimestamp() : void - { - $platform = $this->schemaManager->getDatabasePlatform(); - - $table = new Table('test_column_defaults_current_timestamp'); - - $currentTimeStampSql = $platform->getCurrentTimestampSQL(); - - $table->addColumn('col_datetime', 'datetime', ['notnull' => true, 'default' => $currentTimeStampSql]); - $table->addColumn('col_datetime_nullable', 'datetime', ['default' => $currentTimeStampSql]); - - $this->schemaManager->dropAndCreateTable($table); - - $onlineTable = $this->schemaManager->listTableDetails('test_column_defaults_current_timestamp'); - self::assertSame($currentTimeStampSql, $onlineTable->getColumn('col_datetime')->getDefault()); - self::assertSame($currentTimeStampSql, $onlineTable->getColumn('col_datetime_nullable')->getDefault()); - - $comparator = new Comparator(); - - $diff = $comparator->diffTable($table, $onlineTable); - self::assertFalse($diff, 'Tables should be identical with column defaults.'); - } - - public function testColumnDefaultsAreValid() : void - { - $table = new Table('test_column_defaults_are_valid'); - - $currentTimeStampSql = $this->schemaManager->getDatabasePlatform()->getCurrentTimestampSQL(); - $table->addColumn('col_datetime', 'datetime', ['default' => $currentTimeStampSql]); - $table->addColumn('col_datetime_null', 'datetime', ['notnull' => false, 'default' => null]); - $table->addColumn('col_int', 'integer', ['default' => 1]); - $table->addColumn('col_neg_int', 'integer', ['default' => -1]); - $table->addColumn('col_string', 'string', ['default' => 'A']); - $table->addColumn('col_decimal', 'decimal', ['scale' => 3, 'precision' => 6, 'default' => -2.3]); - $table->addColumn('col_date', 'date', ['default' => '2012-12-12']); - - $this->schemaManager->dropAndCreateTable($table); - - $this->connection->executeUpdate( - 'INSERT INTO test_column_defaults_are_valid () VALUES()' - ); - - $row = $this->connection->fetchAssoc( - 'SELECT *, DATEDIFF(CURRENT_TIMESTAMP(), col_datetime) as diff_seconds FROM test_column_defaults_are_valid' - ); - - self::assertInstanceOf(DateTime::class, DateTime::createFromFormat('Y-m-d H:i:s', $row['col_datetime'])); - self::assertNull($row['col_datetime_null']); - self::assertSame('2012-12-12', $row['col_date']); - self::assertSame('A', $row['col_string']); - self::assertEquals(1, $row['col_int']); - self::assertEquals(-1, $row['col_neg_int']); - self::assertEquals('-2.300', $row['col_decimal']); - self::assertLessThan(5, $row['diff_seconds']); - } - - /** - * MariaDB 10.2+ does support CURRENT_TIME and CURRENT_DATE as - * column default values for time and date columns. - * (Not supported on Mysql as of 5.7.19) - * - * Note that MariaDB 10.2+, when storing default in information_schema, - * silently change CURRENT_TIMESTAMP as 'current_timestamp()', - * CURRENT_TIME as 'currtime()' and CURRENT_DATE as 'currdate()'. - * This test also ensure proper aliasing to not trigger a table diff. - */ - public function testColumnDefaultValuesCurrentTimeAndDate() : void - { - if (! $this->schemaManager->getDatabasePlatform() instanceof MariaDb1027Platform) { - $this->markTestSkipped('Only relevant for MariaDb102Platform.'); - } - - $platform = $this->schemaManager->getDatabasePlatform(); - - $table = new Table('test_column_defaults_current_time_and_date'); - - $currentTimestampSql = $platform->getCurrentTimestampSQL(); - $currentTimeSql = $platform->getCurrentTimeSQL(); - $currentDateSql = $platform->getCurrentDateSQL(); - - $table->addColumn('col_datetime', 'datetime', ['default' => $currentTimestampSql]); - $table->addColumn('col_date', 'date', ['default' => $currentDateSql]); - $table->addColumn('col_time', 'time', ['default' => $currentTimeSql]); - - $this->schemaManager->dropAndCreateTable($table); - - $onlineTable = $this->schemaManager->listTableDetails('test_column_defaults_current_time_and_date'); - - self::assertSame($currentTimestampSql, $onlineTable->getColumn('col_datetime')->getDefault()); - self::assertSame($currentDateSql, $onlineTable->getColumn('col_date')->getDefault()); - self::assertSame($currentTimeSql, $onlineTable->getColumn('col_time')->getDefault()); - - $comparator = new Comparator(); - - $diff = $comparator->diffTable($table, $onlineTable); - self::assertFalse($diff, 'Tables should be identical with column defauts time and date.'); - } - - public function testEnsureTableOptionsAreReflectedInMetadata() : void - { - $this->connection->query('DROP TABLE IF EXISTS test_table_metadata'); - - $sql = <<<'SQL' -CREATE TABLE test_table_metadata( - col1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY -) -COLLATE utf8_general_ci -ENGINE InnoDB -ROW_FORMAT COMPRESSED -COMMENT 'This is a test' -AUTO_INCREMENT=42 -PARTITION BY HASH (col1) -SQL; - - $this->connection->query($sql); - $onlineTable = $this->schemaManager->listTableDetails('test_table_metadata'); - - self::assertEquals('InnoDB', $onlineTable->getOption('engine')); - self::assertEquals('utf8_general_ci', $onlineTable->getOption('collation')); - self::assertEquals(42, $onlineTable->getOption('autoincrement')); - self::assertEquals('This is a test', $onlineTable->getOption('comment')); - self::assertEquals([ - 'row_format' => 'COMPRESSED', - 'partitioned' => true, - ], $onlineTable->getOption('create_options')); - } - - public function testEnsureTableWithoutOptionsAreReflectedInMetadata() : void - { - $this->connection->query('DROP TABLE IF EXISTS test_table_empty_metadata'); - - $this->connection->query('CREATE TABLE test_table_empty_metadata(col1 INT NOT NULL)'); - $onlineTable = $this->schemaManager->listTableDetails('test_table_empty_metadata'); - - self::assertNotEmpty($onlineTable->getOption('engine')); - // collation could be set to default or not set, information_schema indicate a possibly null value - self::assertFalse($onlineTable->hasOption('autoincrement')); - self::assertEquals('', $onlineTable->getOption('comment')); - self::assertEquals([], $onlineTable->getOption('create_options')); - } - - public function testParseNullCreateOptions() : void - { - $table = $this->schemaManager->listTableDetails('sys.processlist'); - - self::assertEquals([], $table->getOption('create_options')); - } -} diff --git a/tests/Functional/DBAL/Schema/OracleSchemaManagerTest.php b/tests/Functional/DBAL/Schema/OracleSchemaManagerTest.php deleted file mode 100644 index 6783f71..0000000 --- a/tests/Functional/DBAL/Schema/OracleSchemaManagerTest.php +++ /dev/null @@ -1,288 +0,0 @@ -exec('GRANT ALL PRIVILEGES TO ' . $GLOBALS['db_username']); - - self::$privilegesGranted = true; - } - - public function testRenameTable() : void - { - $this->schemaManager->tryMethod('DropTable', 'list_tables_test'); - $this->schemaManager->tryMethod('DropTable', 'list_tables_test_new_name'); - - $this->createTestTable('list_tables_test'); - $this->schemaManager->renameTable('list_tables_test', 'list_tables_test_new_name'); - - $tables = $this->schemaManager->listTables(); - - self::assertHasTable($tables, 'list_tables_test_new_name'); - } - - public function testListTableWithBinary() : void - { - $tableName = 'test_binary_table'; - - $table = new Table($tableName); - $table->addColumn('id', 'integer'); - $table->addColumn('column_varbinary', 'binary', []); - $table->addColumn('column_binary', 'binary', ['fixed' => true]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $table = $this->schemaManager->listTableDetails($tableName); - - self::assertInstanceOf(BinaryType::class, $table->getColumn('column_varbinary')->getType()); - self::assertFalse($table->getColumn('column_varbinary')->getFixed()); - - self::assertInstanceOf(BinaryType::class, $table->getColumn('column_binary')->getType()); - self::assertFalse($table->getColumn('column_binary')->getFixed()); - } - - /** - * @group DBAL-472 - * @group DBAL-1001 - */ - public function testAlterTableColumnNotNull() : void - { - $comparator = new Schema\Comparator(); - $tableName = 'list_table_column_notnull'; - $table = new Schema\Table($tableName); - - $table->addColumn('id', 'integer'); - $table->addColumn('foo', 'integer'); - $table->addColumn('bar', 'string'); - $table->setPrimaryKey(['id']); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertTrue($columns['id']->getNotnull()); - self::assertTrue($columns['foo']->getNotnull()); - self::assertTrue($columns['bar']->getNotnull()); - - $diffTable = clone $table; - $diffTable->changeColumn('foo', ['notnull' => false]); - $diffTable->changeColumn('bar', ['length' => 1024]); - - $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); - - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertTrue($columns['id']->getNotnull()); - self::assertFalse($columns['foo']->getNotnull()); - self::assertTrue($columns['bar']->getNotnull()); - } - - public function testListDatabases() : void - { - // We need the temp connection that has privileges to create a database. - $sm = TestUtil::getTempConnection()->getSchemaManager(); - - $sm->dropAndCreateDatabase('c##test_create_database'); - - $databases = $this->schemaManager->listDatabases(); - $databases = array_map('strtolower', $databases); - - self::assertContains('c##test_create_database', $databases); - } - - /** - * @group DBAL-831 - */ - public function testListTableDetailsWithDifferentIdentifierQuotingRequirements() : void - { - $primaryTableName = '"Primary_Table"'; - $offlinePrimaryTable = new Schema\Table($primaryTableName); - $offlinePrimaryTable->addColumn( - '"Id"', - 'integer', - ['autoincrement' => true, 'comment' => 'Explicit casing.'] - ); - $offlinePrimaryTable->addColumn('select', 'integer', ['comment' => 'Reserved keyword.']); - $offlinePrimaryTable->addColumn('foo', 'integer', ['comment' => 'Implicit uppercasing.']); - $offlinePrimaryTable->addColumn('BAR', 'integer'); - $offlinePrimaryTable->addColumn('"BAZ"', 'integer'); - $offlinePrimaryTable->addIndex(['select'], 'from'); - $offlinePrimaryTable->addIndex(['foo'], 'foo_index'); - $offlinePrimaryTable->addIndex(['BAR'], 'BAR_INDEX'); - $offlinePrimaryTable->addIndex(['"BAZ"'], 'BAZ_INDEX'); - $offlinePrimaryTable->setPrimaryKey(['"Id"']); - - $foreignTableName = 'foreign'; - $offlineForeignTable = new Schema\Table($foreignTableName); - $offlineForeignTable->addColumn('id', 'integer', ['autoincrement' => true]); - $offlineForeignTable->addColumn('"Fk"', 'integer'); - $offlineForeignTable->addIndex(['"Fk"'], '"Fk_index"'); - $offlineForeignTable->addForeignKeyConstraint( - $primaryTableName, - ['"Fk"'], - ['"Id"'], - [], - '"Primary_Table_Fk"' - ); - $offlineForeignTable->setPrimaryKey(['id']); - - $this->schemaManager->tryMethod('dropTable', $foreignTableName); - $this->schemaManager->tryMethod('dropTable', $primaryTableName); - - $this->schemaManager->createTable($offlinePrimaryTable); - $this->schemaManager->createTable($offlineForeignTable); - - $onlinePrimaryTable = $this->schemaManager->listTableDetails($primaryTableName); - $onlineForeignTable = $this->schemaManager->listTableDetails($foreignTableName); - - $platform = $this->schemaManager->getDatabasePlatform(); - - // Primary table assertions - self::assertSame($primaryTableName, $onlinePrimaryTable->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('"Id"')); - self::assertSame('"Id"', $onlinePrimaryTable->getColumn('"Id"')->getQuotedName($platform)); - self::assertTrue($onlinePrimaryTable->hasPrimaryKey()); - self::assertSame(['"Id"'], $onlinePrimaryTable->getPrimaryKey()->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('select')); - self::assertSame('"select"', $onlinePrimaryTable->getColumn('select')->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('foo')); - self::assertSame('FOO', $onlinePrimaryTable->getColumn('foo')->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('BAR')); - self::assertSame('BAR', $onlinePrimaryTable->getColumn('BAR')->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('"BAZ"')); - self::assertSame('BAZ', $onlinePrimaryTable->getColumn('"BAZ"')->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasIndex('from')); - self::assertTrue($onlinePrimaryTable->getIndex('from')->hasColumnAtPosition('"select"')); - self::assertSame(['"select"'], $onlinePrimaryTable->getIndex('from')->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasIndex('foo_index')); - self::assertTrue($onlinePrimaryTable->getIndex('foo_index')->hasColumnAtPosition('foo')); - self::assertSame(['FOO'], $onlinePrimaryTable->getIndex('foo_index')->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasIndex('BAR_INDEX')); - self::assertTrue($onlinePrimaryTable->getIndex('BAR_INDEX')->hasColumnAtPosition('BAR')); - self::assertSame(['BAR'], $onlinePrimaryTable->getIndex('BAR_INDEX')->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasIndex('BAZ_INDEX')); - self::assertTrue($onlinePrimaryTable->getIndex('BAZ_INDEX')->hasColumnAtPosition('"BAZ"')); - self::assertSame(['BAZ'], $onlinePrimaryTable->getIndex('BAZ_INDEX')->getQuotedColumns($platform)); - - // Foreign table assertions - self::assertTrue($onlineForeignTable->hasColumn('id')); - self::assertSame('ID', $onlineForeignTable->getColumn('id')->getQuotedName($platform)); - self::assertTrue($onlineForeignTable->hasPrimaryKey()); - self::assertSame(['ID'], $onlineForeignTable->getPrimaryKey()->getQuotedColumns($platform)); - - self::assertTrue($onlineForeignTable->hasColumn('"Fk"')); - self::assertSame('"Fk"', $onlineForeignTable->getColumn('"Fk"')->getQuotedName($platform)); - - self::assertTrue($onlineForeignTable->hasIndex('"Fk_index"')); - self::assertTrue($onlineForeignTable->getIndex('"Fk_index"')->hasColumnAtPosition('"Fk"')); - self::assertSame(['"Fk"'], $onlineForeignTable->getIndex('"Fk_index"')->getQuotedColumns($platform)); - - self::assertTrue($onlineForeignTable->hasForeignKey('"Primary_Table_Fk"')); - self::assertSame( - $primaryTableName, - $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignTableName($platform) - ); - self::assertSame( - ['"Fk"'], - $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedLocalColumns($platform) - ); - self::assertSame( - ['"Id"'], - $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignColumns($platform) - ); - } - - public function testListTableColumnsSameTableNamesInDifferentSchemas() : void - { - $table = $this->createListTableColumns(); - $this->schemaManager->dropAndCreateTable($table); - - $otherTable = new Table($table->getName()); - $otherTable->addColumn('id', Types::STRING); - TestUtil::getTempConnection()->getSchemaManager()->dropAndCreateTable($otherTable); - - $columns = $this->schemaManager->listTableColumns($table->getName(), $this->connection->getUsername()); - self::assertCount(7, $columns); - } - - /** - * @group DBAL-1234 - */ - public function testListTableIndexesPrimaryKeyConstraintNameDiffersFromIndexName() : void - { - $table = new Table('list_table_indexes_pk_id_test'); - $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); - $table->addColumn('id', 'integer', ['notnull' => true]); - $table->addUniqueIndex(['id'], 'id_unique_index'); - $this->schemaManager->dropAndCreateTable($table); - - // Adding a primary key on already indexed columns - // Oracle will reuse the unique index, which cause a constraint name differing from the index name - $this->schemaManager->createConstraint(new Schema\Index('id_pk_id_index', ['id'], true, true), 'list_table_indexes_pk_id_test'); - - $tableIndexes = $this->schemaManager->listTableIndexes('list_table_indexes_pk_id_test'); - - self::assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.'); - self::assertEquals(['id'], array_map('strtolower', $tableIndexes['primary']->getColumns())); - self::assertTrue($tableIndexes['primary']->isUnique()); - self::assertTrue($tableIndexes['primary']->isPrimary()); - } - - /** - * @group DBAL-2555 - */ - public function testListTableDateTypeColumns() : void - { - $table = new Table('tbl_date'); - $table->addColumn('col_date', 'date'); - $table->addColumn('col_datetime', 'datetime'); - $table->addColumn('col_datetimetz', 'datetimetz'); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('tbl_date'); - - self::assertSame('date', $columns['col_date']->getType()->getName()); - self::assertSame('datetime', $columns['col_datetime']->getType()->getName()); - self::assertSame('datetimetz', $columns['col_datetimetz']->getType()->getName()); - } - - public function testCreateAndListSequences() : void - { - self::markTestSkipped("Skipped for uppercase letters are contained in sequences' names. Fix the schema manager in 3.0."); - } -} diff --git a/tests/Functional/DBAL/Schema/PostgreSqlSchemaManagerTest.php b/tests/Functional/DBAL/Schema/PostgreSqlSchemaManagerTest.php deleted file mode 100644 index 0c579c3..0000000 --- a/tests/Functional/DBAL/Schema/PostgreSqlSchemaManagerTest.php +++ /dev/null @@ -1,536 +0,0 @@ -connection) { - return; - } - - $this->connection->getConfiguration()->setFilterSchemaAssetsExpression(null); - } - - /** - * @group DBAL-244 - */ - public function testGetSchemaNames() : void - { - $names = $this->schemaManager->getSchemaNames(); - - self::assertIsArray($names); - self::assertNotEmpty($names); - self::assertContains('public', $names, 'The public schema should be found.'); - } - - /** - * @group DBAL-21 - */ - public function testSupportDomainTypeFallback() : void - { - $createDomainTypeSQL = 'CREATE DOMAIN MyMoney AS DECIMAL(18,2)'; - $this->connection->exec($createDomainTypeSQL); - - $createTableSQL = 'CREATE TABLE domain_type_test (id INT PRIMARY KEY, value MyMoney)'; - $this->connection->exec($createTableSQL); - - $table = $this->connection->getSchemaManager()->listTableDetails('domain_type_test'); - self::assertInstanceOf(DecimalType::class, $table->getColumn('value')->getType()); - - Type::addType('MyMoney', MoneyType::class); - $this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('MyMoney', 'MyMoney'); - - $table = $this->connection->getSchemaManager()->listTableDetails('domain_type_test'); - self::assertInstanceOf(MoneyType::class, $table->getColumn('value')->getType()); - } - - /** - * @group DBAL-37 - */ - public function testDetectsAutoIncrement() : void - { - $autoincTable = new Table('autoinc_table'); - $column = $autoincTable->addColumn('id', 'integer'); - $column->setAutoincrement(true); - $this->schemaManager->createTable($autoincTable); - $autoincTable = $this->schemaManager->listTableDetails('autoinc_table'); - - self::assertTrue($autoincTable->getColumn('id')->getAutoincrement()); - } - - /** - * @group DBAL-37 - */ - public function testAlterTableAutoIncrementAdd() : void - { - $tableFrom = new Table('autoinc_table_add'); - $column = $tableFrom->addColumn('id', 'integer'); - $this->schemaManager->createTable($tableFrom); - $tableFrom = $this->schemaManager->listTableDetails('autoinc_table_add'); - self::assertFalse($tableFrom->getColumn('id')->getAutoincrement()); - - $tableTo = new Table('autoinc_table_add'); - $column = $tableTo->addColumn('id', 'integer'); - $column->setAutoincrement(true); - - $c = new Comparator(); - $diff = $c->diffTable($tableFrom, $tableTo); - $sql = $this->connection->getDatabasePlatform()->getAlterTableSQL($diff); - self::assertEquals([ - 'CREATE SEQUENCE autoinc_table_add_id_seq', - "SELECT setval('autoinc_table_add_id_seq', (SELECT MAX(id) FROM autoinc_table_add))", - "ALTER TABLE autoinc_table_add ALTER id SET DEFAULT nextval('autoinc_table_add_id_seq')", - ], $sql); - - $this->schemaManager->alterTable($diff); - $tableFinal = $this->schemaManager->listTableDetails('autoinc_table_add'); - self::assertTrue($tableFinal->getColumn('id')->getAutoincrement()); - } - - /** - * @group DBAL-37 - */ - public function testAlterTableAutoIncrementDrop() : void - { - $tableFrom = new Table('autoinc_table_drop'); - $column = $tableFrom->addColumn('id', 'integer'); - $column->setAutoincrement(true); - $this->schemaManager->createTable($tableFrom); - $tableFrom = $this->schemaManager->listTableDetails('autoinc_table_drop'); - self::assertTrue($tableFrom->getColumn('id')->getAutoincrement()); - - $tableTo = new Table('autoinc_table_drop'); - $column = $tableTo->addColumn('id', 'integer'); - - $c = new Comparator(); - $diff = $c->diffTable($tableFrom, $tableTo); - self::assertInstanceOf(TableDiff::class, $diff, 'There should be a difference and not false being returned from the table comparison'); - self::assertEquals(['ALTER TABLE autoinc_table_drop ALTER id DROP DEFAULT'], $this->connection->getDatabasePlatform()->getAlterTableSQL($diff)); - - $this->schemaManager->alterTable($diff); - $tableFinal = $this->schemaManager->listTableDetails('autoinc_table_drop'); - self::assertFalse($tableFinal->getColumn('id')->getAutoincrement()); - } - - /** - * @group DBAL-75 - */ - public function testTableWithSchema() : void - { - $this->connection->exec('CREATE SCHEMA nested'); - - $nestedRelatedTable = new Table('nested.schemarelated'); - $column = $nestedRelatedTable->addColumn('id', 'integer'); - $column->setAutoincrement(true); - $nestedRelatedTable->setPrimaryKey(['id']); - - $nestedSchemaTable = new Table('nested.schematable'); - $column = $nestedSchemaTable->addColumn('id', 'integer'); - $column->setAutoincrement(true); - $nestedSchemaTable->setPrimaryKey(['id']); - $nestedSchemaTable->addUnnamedForeignKeyConstraint($nestedRelatedTable, ['id'], ['id']); - - $this->schemaManager->createTable($nestedRelatedTable); - $this->schemaManager->createTable($nestedSchemaTable); - - $tables = $this->schemaManager->listTableNames(); - self::assertContains('nested.schematable', $tables, 'The table should be detected with its non-public schema.'); - - $nestedSchemaTable = $this->schemaManager->listTableDetails('nested.schematable'); - self::assertTrue($nestedSchemaTable->hasColumn('id')); - self::assertEquals(['id'], $nestedSchemaTable->getPrimaryKey()->getColumns()); - - $relatedFks = $nestedSchemaTable->getForeignKeys(); - self::assertCount(1, $relatedFks); - $relatedFk = array_pop($relatedFks); - self::assertEquals('nested.schemarelated', $relatedFk->getForeignTableName()); - } - - /** - * @group DBAL-91 - * @group DBAL-88 - */ - public function testReturnQuotedAssets() : void - { - $sql = 'create table dbal91_something ( id integer CONSTRAINT id_something PRIMARY KEY NOT NULL ,"table" integer );'; - $this->connection->exec($sql); - - $sql = 'ALTER TABLE dbal91_something ADD CONSTRAINT something_input FOREIGN KEY( "table" ) REFERENCES dbal91_something ON UPDATE CASCADE;'; - $this->connection->exec($sql); - - $table = $this->schemaManager->listTableDetails('dbal91_something'); - - self::assertEquals( - [ - 'CREATE TABLE dbal91_something (id INT NOT NULL, "table" INT DEFAULT NULL, PRIMARY KEY(id))', - 'CREATE INDEX IDX_A9401304ECA7352B ON dbal91_something ("table")', - ], - $this->connection->getDatabasePlatform()->getCreateTableSQL($table) - ); - } - - /** - * @group DBAL-204 - */ - public function testFilterSchemaExpression() : void - { - $testTable = new Table('dbal204_test_prefix'); - $column = $testTable->addColumn('id', 'integer'); - $this->schemaManager->createTable($testTable); - $testTable = new Table('dbal204_without_prefix'); - $column = $testTable->addColumn('id', 'integer'); - $this->schemaManager->createTable($testTable); - - $this->connection->getConfiguration()->setFilterSchemaAssetsExpression('#^dbal204_#'); - $names = $this->schemaManager->listTableNames(); - self::assertCount(2, $names); - - $this->connection->getConfiguration()->setFilterSchemaAssetsExpression('#^dbal204_test#'); - $names = $this->schemaManager->listTableNames(); - self::assertCount(1, $names); - } - - public function testListForeignKeys() : void - { - if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('Does not support foreign key constraints.'); - } - - $fkOptions = ['SET NULL', 'SET DEFAULT', 'NO ACTION','CASCADE', 'RESTRICT']; - $foreignKeys = []; - $fkTable = $this->getTestTable('test_create_fk1'); - for ($i = 0; $i < count($fkOptions); $i++) { - $fkTable->addColumn('foreign_key_test' . $i, 'integer'); - $foreignKeys[] = new ForeignKeyConstraint( - ['foreign_key_test' . $i], - 'test_create_fk2', - ['id'], - 'foreign_key_test' . $i . '_fk', - ['onDelete' => $fkOptions[$i]] - ); - } - $this->schemaManager->dropAndCreateTable($fkTable); - $this->createTestTable('test_create_fk2'); - - foreach ($foreignKeys as $foreignKey) { - $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk1'); - } - $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk1'); - self::assertEquals(count($foreignKeys), count($fkeys), "Table 'test_create_fk1' has to have " . count($foreignKeys) . ' foreign keys.'); - for ($i = 0; $i < count($fkeys); $i++) { - self::assertEquals(['foreign_key_test' . $i], array_map('strtolower', $fkeys[$i]->getLocalColumns())); - self::assertEquals(['id'], array_map('strtolower', $fkeys[$i]->getForeignColumns())); - self::assertEquals('test_create_fk2', strtolower($fkeys[0]->getForeignTableName())); - if ($foreignKeys[$i]->getOption('onDelete') === 'NO ACTION') { - self::assertFalse($fkeys[$i]->hasOption('onDelete'), 'Unexpected option: ' . $fkeys[$i]->getOption('onDelete')); - } else { - self::assertEquals($foreignKeys[$i]->getOption('onDelete'), $fkeys[$i]->getOption('onDelete')); - } - } - } - - /** - * @group DBAL-511 - */ - public function testDefaultValueCharacterVarying() : void - { - $testTable = new Table('dbal511_default'); - $testTable->addColumn('id', 'integer'); - $testTable->addColumn('def', 'string', ['default' => 'foo']); - $testTable->setPrimaryKey(['id']); - - $this->schemaManager->createTable($testTable); - - $databaseTable = $this->schemaManager->listTableDetails($testTable->getName()); - - self::assertEquals('foo', $databaseTable->getColumn('def')->getDefault()); - } - - /** - * @group DDC-2843 - */ - public function testBooleanDefault() : void - { - $table = new Table('ddc2843_bools'); - $table->addColumn('id', 'integer'); - $table->addColumn('checked', 'boolean', ['default' => false]); - - $this->schemaManager->createTable($table); - - $databaseTable = $this->schemaManager->listTableDetails($table->getName()); - - $c = new Comparator(); - $diff = $c->diffTable($table, $databaseTable); - - self::assertFalse($diff); - } - - public function testListTableWithBinary() : void - { - $tableName = 'test_binary_table'; - - $table = new Table($tableName); - $table->addColumn('id', 'integer'); - $table->addColumn('column_varbinary', 'binary', []); - $table->addColumn('column_binary', 'binary', ['fixed' => true]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $table = $this->schemaManager->listTableDetails($tableName); - - self::assertInstanceOf(BlobType::class, $table->getColumn('column_varbinary')->getType()); - self::assertFalse($table->getColumn('column_varbinary')->getFixed()); - - self::assertInstanceOf(BlobType::class, $table->getColumn('column_binary')->getType()); - self::assertFalse($table->getColumn('column_binary')->getFixed()); - } - - public function testListQuotedTable() : void - { - $offlineTable = new Schema\Table('user'); - $offlineTable->addColumn('id', 'integer'); - $offlineTable->addColumn('username', 'string'); - $offlineTable->addColumn('fk', 'integer'); - $offlineTable->setPrimaryKey(['id']); - $offlineTable->addForeignKeyConstraint($offlineTable, ['fk'], ['id']); - - $this->schemaManager->dropAndCreateTable($offlineTable); - - $onlineTable = $this->schemaManager->listTableDetails('"user"'); - - $comparator = new Schema\Comparator(); - - self::assertFalse($comparator->diffTable($offlineTable, $onlineTable)); - } - - public function testListTablesExcludesViews() : void - { - $this->createTestTable('list_tables_excludes_views'); - - $name = 'list_tables_excludes_views_test_view'; - $sql = 'SELECT * from list_tables_excludes_views'; - - $view = new Schema\View($name, $sql); - - $this->schemaManager->dropAndCreateView($view); - - $tables = $this->schemaManager->listTables(); - - $foundTable = false; - foreach ($tables as $table) { - self::assertInstanceOf(Table::class, $table, 'No Table instance was found in tables array.'); - if (strtolower($table->getName()) !== 'list_tables_excludes_views_test_view') { - continue; - } - - $foundTable = true; - } - - self::assertFalse($foundTable, 'View "list_tables_excludes_views_test_view" must not be found in table list'); - } - - /** - * @group DBAL-1033 - */ - public function testPartialIndexes() : void - { - $offlineTable = new Schema\Table('person'); - $offlineTable->addColumn('id', 'integer'); - $offlineTable->addColumn('name', 'string'); - $offlineTable->addColumn('email', 'string'); - $offlineTable->addUniqueIndex(['id', 'name'], 'simple_partial_index', ['where' => '(id IS NULL)']); - - $this->schemaManager->dropAndCreateTable($offlineTable); - - $onlineTable = $this->schemaManager->listTableDetails('person'); - - $comparator = new Schema\Comparator(); - - self::assertFalse($comparator->diffTable($offlineTable, $onlineTable)); - self::assertTrue($onlineTable->hasIndex('simple_partial_index')); - self::assertTrue($onlineTable->getIndex('simple_partial_index')->hasOption('where')); - self::assertSame('(id IS NULL)', $onlineTable->getIndex('simple_partial_index')->getOption('where')); - } - - /** - * @dataProvider jsonbColumnTypeProvider - */ - public function testJsonbColumn(string $type) : void - { - if (! $this->schemaManager->getDatabasePlatform() instanceof PostgreSQL94Platform) { - $this->markTestSkipped('Requires PostgresSQL 9.4+'); - - return; - } - - $table = new Schema\Table('test_jsonb'); - $table->addColumn('foo', $type)->setPlatformOption('jsonb', true); - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('test_jsonb'); - - self::assertSame($type, $columns['foo']->getType()->getName()); - self::assertTrue(true, $columns['foo']->getPlatformOption('jsonb')); - } - - /** - * @return mixed[][] - */ - public function jsonbColumnTypeProvider() : array - { - return [ - [Types::JSON], - [Types::JSON_ARRAY], - ]; - } - - /** - * @group DBAL-2427 - */ - public function testListNegativeColumnDefaultValue() : void - { - $table = new Schema\Table('test_default_negative'); - $table->addColumn('col_smallint', 'smallint', ['default' => -1]); - $table->addColumn('col_integer', 'integer', ['default' => -1]); - $table->addColumn('col_bigint', 'bigint', ['default' => -1]); - $table->addColumn('col_float', 'float', ['default' => -1.1]); - $table->addColumn('col_decimal', 'decimal', ['default' => -1.1]); - $table->addColumn('col_string', 'string', ['default' => '(-1)']); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('test_default_negative'); - - self::assertEquals(-1, $columns['col_smallint']->getDefault()); - self::assertEquals(-1, $columns['col_integer']->getDefault()); - self::assertEquals(-1, $columns['col_bigint']->getDefault()); - self::assertEquals(-1.1, $columns['col_float']->getDefault()); - self::assertEquals(-1.1, $columns['col_decimal']->getDefault()); - self::assertEquals('(-1)', $columns['col_string']->getDefault()); - } - - /** - * @return mixed[][] - */ - public static function serialTypes() : iterable - { - return [ - ['integer'], - ['bigint'], - ]; - } - - /** - * @dataProvider serialTypes - * @group 2906 - */ - public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValue(string $type) : void - { - $tableName = 'test_serial_type_' . $type; - - $table = new Schema\Table($tableName); - $table->addColumn('id', $type, ['autoincrement' => true, 'notnull' => false]); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertNull($columns['id']->getDefault()); - } - - /** - * @dataProvider serialTypes - * @group 2906 - */ - public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValueEvenWhenDefaultIsSet(string $type) : void - { - $tableName = 'test_serial_type_with_default_' . $type; - - $table = new Schema\Table($tableName); - $table->addColumn('id', $type, ['autoincrement' => true, 'notnull' => false, 'default' => 1]); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertNull($columns['id']->getDefault()); - } - - /** - * @group 2916 - * @dataProvider autoIncrementTypeMigrations - */ - public function testAlterTableAutoIncrementIntToBigInt(string $from, string $to, string $expected) : void - { - $tableFrom = new Table('autoinc_type_modification'); - $column = $tableFrom->addColumn('id', $from); - $column->setAutoincrement(true); - $this->schemaManager->dropAndCreateTable($tableFrom); - $tableFrom = $this->schemaManager->listTableDetails('autoinc_type_modification'); - self::assertTrue($tableFrom->getColumn('id')->getAutoincrement()); - - $tableTo = new Table('autoinc_type_modification'); - $column = $tableTo->addColumn('id', $to); - $column->setAutoincrement(true); - - $c = new Comparator(); - $diff = $c->diffTable($tableFrom, $tableTo); - self::assertInstanceOf(TableDiff::class, $diff, 'There should be a difference and not false being returned from the table comparison'); - self::assertSame(['ALTER TABLE autoinc_type_modification ALTER id TYPE ' . $expected . ' USING id::' . $expected], $this->connection->getDatabasePlatform()->getAlterTableSQL($diff)); - - $this->schemaManager->alterTable($diff); - $tableFinal = $this->schemaManager->listTableDetails('autoinc_type_modification'); - self::assertTrue($tableFinal->getColumn('id')->getAutoincrement()); - } - - /** - * @return mixed[][] - */ - public static function autoIncrementTypeMigrations() : iterable - { - return [ - 'int->bigint' => ['integer', 'bigint', 'BIGINT'], - 'bigint->int' => ['bigint', 'integer', 'INT'], - ]; - } -} - -class MoneyType extends Type -{ - /** - * {@inheritDoc} - */ - public function getName() - { - return 'MyMoney'; - } - - /** - * {@inheritDoc} - */ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) - { - return 'MyMoney'; - } -} diff --git a/tests/Functional/DBAL/Schema/SQLAnywhereSchemaManagerTest.php b/tests/Functional/DBAL/Schema/SQLAnywhereSchemaManagerTest.php deleted file mode 100644 index 9de342e..0000000 --- a/tests/Functional/DBAL/Schema/SQLAnywhereSchemaManagerTest.php +++ /dev/null @@ -1,64 +0,0 @@ -createTestTable('view_test_table'); - - $name = 'doctrine_test_view'; - $sql = 'SELECT * from DBA.view_test_table'; - - $view = new View($name, $sql); - - $this->schemaManager->dropAndCreateView($view); - - $views = $this->schemaManager->listViews(); - - self::assertCount(1, $views, 'Database has to have one view.'); - self::assertInstanceOf(View::class, $views[$name]); - self::assertEquals($name, $views[$name]->getName()); - self::assertRegExp('/^SELECT \* from "?DBA"?\."?view_test_table"?$/', $views[$name]->getSql()); - } - - public function testDropAndCreateAdvancedIndex() : void - { - $table = $this->getTestTable('test_create_advanced_index'); - $this->schemaManager->dropAndCreateTable($table); - $this->schemaManager->dropAndCreateIndex( - new Index('test', ['test'], true, false, ['clustered', 'with_nulls_not_distinct', 'for_olap_workload']), - $table->getName() - ); - - $tableIndexes = $this->schemaManager->listTableIndexes('test_create_advanced_index'); - self::assertIsArray($tableIndexes); - self::assertEquals('test', $tableIndexes['test']->getName()); - self::assertEquals(['test'], $tableIndexes['test']->getColumns()); - self::assertTrue($tableIndexes['test']->isUnique()); - self::assertFalse($tableIndexes['test']->isPrimary()); - self::assertTrue($tableIndexes['test']->hasFlag('clustered')); - self::assertTrue($tableIndexes['test']->hasFlag('with_nulls_not_distinct')); - self::assertTrue($tableIndexes['test']->hasFlag('for_olap_workload')); - } - - public function testListTableColumnsWithFixedStringTypeColumn() : void - { - $table = new Table('list_table_columns_char'); - $table->addColumn('id', 'integer', ['notnull' => true]); - $table->addColumn('test', 'string', ['fixed' => true]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('list_table_columns_char'); - - self::assertArrayHasKey('test', $columns); - self::assertTrue($columns['test']->getFixed()); - } -} diff --git a/tests/Functional/DBAL/Schema/SQLServerSchemaManagerTest.php b/tests/Functional/DBAL/Schema/SQLServerSchemaManagerTest.php deleted file mode 100644 index 94c85cb..0000000 --- a/tests/Functional/DBAL/Schema/SQLServerSchemaManagerTest.php +++ /dev/null @@ -1,347 +0,0 @@ -addColumn('id', 'integer'); - $table->addColumn('todrop', 'decimal', ['default' => 10.2]); - - $this->schemaManager->createTable($table); - - $diff = new TableDiff('sqlsrv_drop_column', [], [], [new Column('todrop', Type::getType('decimal'))]); - $this->schemaManager->alterTable($diff); - - $columns = $this->schemaManager->listTableColumns('sqlsrv_drop_column'); - self::assertCount(1, $columns); - } - - public function testColumnCollation() : void - { - $table = new Table($tableName = 'test_collation'); - $column = $table->addColumn($columnName = 'test', 'string'); - - $this->schemaManager->dropAndCreateTable($table); - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertTrue($columns[$columnName]->hasPlatformOption('collation')); // SQL Server should report a default collation on the column - - $column->setPlatformOption('collation', $collation = 'Icelandic_CS_AS'); - - $this->schemaManager->dropAndCreateTable($table); - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertEquals($collation, $columns[$columnName]->getPlatformOption('collation')); - } - - public function testDefaultConstraints() : void - { - $table = new Table('sqlsrv_default_constraints'); - $table->addColumn('no_default', 'string'); - $table->addColumn('df_integer', 'integer', ['default' => 666]); - $table->addColumn('df_string_1', 'string', ['default' => 'foobar']); - $table->addColumn('df_string_2', 'string', ['default' => 'Doctrine rocks!!!']); - $table->addColumn('df_string_3', 'string', ['default' => 'another default value']); - $table->addColumn('df_string_4', 'string', ['default' => 'column to rename']); - $table->addColumn('df_boolean', 'boolean', ['default' => true]); - - $this->schemaManager->createTable($table); - $columns = $this->schemaManager->listTableColumns('sqlsrv_default_constraints'); - - self::assertNull($columns['no_default']->getDefault()); - self::assertEquals(666, $columns['df_integer']->getDefault()); - self::assertEquals('foobar', $columns['df_string_1']->getDefault()); - self::assertEquals('Doctrine rocks!!!', $columns['df_string_2']->getDefault()); - self::assertEquals('another default value', $columns['df_string_3']->getDefault()); - self::assertEquals(1, $columns['df_boolean']->getDefault()); - - $diff = new TableDiff( - 'sqlsrv_default_constraints', - [], - [ - 'df_integer' => new ColumnDiff( - 'df_integer', - new Column('df_integer', Type::getType('integer'), ['default' => 0]), - ['default'], - new Column('df_integer', Type::getType('integer'), ['default' => 666]) - ), - 'df_string_2' => new ColumnDiff( - 'df_string_2', - new Column('df_string_2', Type::getType('string')), - ['default'], - new Column('df_string_2', Type::getType('string'), ['default' => 'Doctrine rocks!!!']) - ), - 'df_string_3' => new ColumnDiff( - 'df_string_3', - new Column('df_string_3', Type::getType('string'), ['length' => 50, 'default' => 'another default value']), - ['length'], - new Column('df_string_3', Type::getType('string'), ['length' => 50, 'default' => 'another default value']) - ), - 'df_boolean' => new ColumnDiff( - 'df_boolean', - new Column('df_boolean', Type::getType('boolean'), ['default' => false]), - ['default'], - new Column('df_boolean', Type::getType('boolean'), ['default' => true]) - ), - ], - [ - 'df_string_1' => new Column('df_string_1', Type::getType('string')), - ], - [], - [], - [], - $table - ); - $diff->newName = 'sqlsrv_default_constraints'; - $diff->renamedColumns['df_string_4'] = new Column( - 'df_string_renamed', - Type::getType('string'), - ['default' => 'column to rename'] - ); - - $this->schemaManager->alterTable($diff); - $columns = $this->schemaManager->listTableColumns('sqlsrv_default_constraints'); - - self::assertNull($columns['no_default']->getDefault()); - self::assertEquals(0, $columns['df_integer']->getDefault()); - self::assertNull($columns['df_string_2']->getDefault()); - self::assertEquals('another default value', $columns['df_string_3']->getDefault()); - self::assertEquals(0, $columns['df_boolean']->getDefault()); - self::assertEquals('column to rename', $columns['df_string_renamed']->getDefault()); - - /** - * Test that column default constraints can still be referenced after table rename - */ - $diff = new TableDiff( - 'sqlsrv_default_constraints', - [], - [ - 'df_integer' => new ColumnDiff( - 'df_integer', - new Column('df_integer', Type::getType('integer'), ['default' => 666]), - ['default'], - new Column('df_integer', Type::getType('integer'), ['default' => 0]) - ), - ], - [], - [], - [], - [], - $table - ); - - $this->schemaManager->alterTable($diff); - $columns = $this->schemaManager->listTableColumns('sqlsrv_default_constraints'); - - self::assertEquals(666, $columns['df_integer']->getDefault()); - } - - /** - * @group DBAL-543 - */ - public function testColumnComments() : void - { - $table = new Table('sqlsrv_column_comment'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->addColumn('comment_null', 'integer', ['comment' => null]); - $table->addColumn('comment_false', 'integer', ['comment' => false]); - $table->addColumn('comment_empty_string', 'integer', ['comment' => '']); - $table->addColumn('comment_integer_0', 'integer', ['comment' => 0]); - $table->addColumn('comment_float_0', 'integer', ['comment' => 0.0]); - $table->addColumn('comment_string_0', 'integer', ['comment' => '0']); - $table->addColumn('comment', 'integer', ['comment' => 'Doctrine 0wnz you!']); - $table->addColumn('`comment_quoted`', 'integer', ['comment' => 'Doctrine 0wnz comments for explicitly quoted columns!']); - $table->addColumn('create', 'integer', ['comment' => 'Doctrine 0wnz comments for reserved keyword columns!']); - $table->addColumn('commented_type', 'object'); - $table->addColumn('commented_type_with_comment', 'array', ['comment' => 'Doctrine array type.']); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $columns = $this->schemaManager->listTableColumns('sqlsrv_column_comment'); - self::assertCount(12, $columns); - self::assertNull($columns['id']->getComment()); - self::assertNull($columns['comment_null']->getComment()); - self::assertNull($columns['comment_false']->getComment()); - self::assertNull($columns['comment_empty_string']->getComment()); - self::assertEquals('0', $columns['comment_integer_0']->getComment()); - self::assertEquals('0', $columns['comment_float_0']->getComment()); - self::assertEquals('0', $columns['comment_string_0']->getComment()); - self::assertEquals('Doctrine 0wnz you!', $columns['comment']->getComment()); - self::assertEquals('Doctrine 0wnz comments for explicitly quoted columns!', $columns['comment_quoted']->getComment()); - self::assertEquals('Doctrine 0wnz comments for reserved keyword columns!', $columns['[create]']->getComment()); - self::assertNull($columns['commented_type']->getComment()); - self::assertEquals('Doctrine array type.', $columns['commented_type_with_comment']->getComment()); - - $tableDiff = new TableDiff('sqlsrv_column_comment'); - $tableDiff->fromTable = $table; - $tableDiff->addedColumns['added_comment_none'] = new Column('added_comment_none', Type::getType('integer')); - $tableDiff->addedColumns['added_comment_null'] = new Column('added_comment_null', Type::getType('integer'), ['comment' => null]); - $tableDiff->addedColumns['added_comment_false'] = new Column('added_comment_false', Type::getType('integer'), ['comment' => false]); - $tableDiff->addedColumns['added_comment_empty_string'] = new Column('added_comment_empty_string', Type::getType('integer'), ['comment' => '']); - $tableDiff->addedColumns['added_comment_integer_0'] = new Column('added_comment_integer_0', Type::getType('integer'), ['comment' => 0]); - $tableDiff->addedColumns['added_comment_float_0'] = new Column('added_comment_float_0', Type::getType('integer'), ['comment' => 0.0]); - $tableDiff->addedColumns['added_comment_string_0'] = new Column('added_comment_string_0', Type::getType('integer'), ['comment' => '0']); - $tableDiff->addedColumns['added_comment'] = new Column('added_comment', Type::getType('integer'), ['comment' => 'Doctrine']); - $tableDiff->addedColumns['`added_comment_quoted`'] = new Column('`added_comment_quoted`', Type::getType('integer'), ['comment' => 'rulez']); - $tableDiff->addedColumns['select'] = new Column('select', Type::getType('integer'), ['comment' => '666']); - $tableDiff->addedColumns['added_commented_type'] = new Column('added_commented_type', Type::getType('object')); - $tableDiff->addedColumns['added_commented_type_with_comment'] = new Column('added_commented_type_with_comment', Type::getType('array'), ['comment' => '666']); - - $tableDiff->renamedColumns['comment_float_0'] = new Column('comment_double_0', Type::getType('decimal'), ['comment' => 'Double for real!']); - - // Add comment to non-commented column. - $tableDiff->changedColumns['id'] = new ColumnDiff( - 'id', - new Column('id', Type::getType('integer'), ['autoincrement' => true, 'comment' => 'primary']), - ['comment'], - new Column('id', Type::getType('integer'), ['autoincrement' => true]) - ); - - // Remove comment from null-commented column. - $tableDiff->changedColumns['comment_null'] = new ColumnDiff( - 'comment_null', - new Column('comment_null', Type::getType('string')), - ['type'], - new Column('comment_null', Type::getType('integer'), ['comment' => null]) - ); - - // Add comment to false-commented column. - $tableDiff->changedColumns['comment_false'] = new ColumnDiff( - 'comment_false', - new Column('comment_false', Type::getType('integer'), ['comment' => 'false']), - ['comment'], - new Column('comment_false', Type::getType('integer'), ['comment' => false]) - ); - - // Change type to custom type from empty string commented column. - $tableDiff->changedColumns['comment_empty_string'] = new ColumnDiff( - 'comment_empty_string', - new Column('comment_empty_string', Type::getType('object')), - ['type'], - new Column('comment_empty_string', Type::getType('integer'), ['comment' => '']) - ); - - // Change comment to false-comment from zero-string commented column. - $tableDiff->changedColumns['comment_string_0'] = new ColumnDiff( - 'comment_string_0', - new Column('comment_string_0', Type::getType('integer'), ['comment' => false]), - ['comment'], - new Column('comment_string_0', Type::getType('integer'), ['comment' => '0']) - ); - - // Remove comment from regular commented column. - $tableDiff->changedColumns['comment'] = new ColumnDiff( - 'comment', - new Column('comment', Type::getType('integer')), - ['comment'], - new Column('comment', Type::getType('integer'), ['comment' => 'Doctrine 0wnz you!']) - ); - - // Change comment and change type to custom type from regular commented column. - $tableDiff->changedColumns['`comment_quoted`'] = new ColumnDiff( - '`comment_quoted`', - new Column('`comment_quoted`', Type::getType('array'), ['comment' => 'Doctrine array.']), - ['comment', 'type'], - new Column('`comment_quoted`', Type::getType('integer'), ['comment' => 'Doctrine 0wnz you!']) - ); - - // Remove comment and change type to custom type from regular commented column. - $tableDiff->changedColumns['create'] = new ColumnDiff( - 'create', - new Column('create', Type::getType('object')), - ['comment', 'type'], - new Column('create', Type::getType('integer'), ['comment' => 'Doctrine 0wnz comments for reserved keyword columns!']) - ); - - // Add comment and change custom type to regular type from non-commented column. - $tableDiff->changedColumns['commented_type'] = new ColumnDiff( - 'commented_type', - new Column('commented_type', Type::getType('integer'), ['comment' => 'foo']), - ['comment', 'type'], - new Column('commented_type', Type::getType('object')) - ); - - // Remove comment from commented custom type column. - $tableDiff->changedColumns['commented_type_with_comment'] = new ColumnDiff( - 'commented_type_with_comment', - new Column('commented_type_with_comment', Type::getType('array')), - ['comment'], - new Column('commented_type_with_comment', Type::getType('array'), ['comment' => 'Doctrine array type.']) - ); - - $tableDiff->removedColumns['comment_integer_0'] = new Column('comment_integer_0', Type::getType('integer'), ['comment' => 0]); - - $this->schemaManager->alterTable($tableDiff); - - $columns = $this->schemaManager->listTableColumns('sqlsrv_column_comment'); - self::assertCount(23, $columns); - self::assertEquals('primary', $columns['id']->getComment()); - self::assertNull($columns['comment_null']->getComment()); - self::assertEquals('false', $columns['comment_false']->getComment()); - self::assertNull($columns['comment_empty_string']->getComment()); - self::assertEquals('0', $columns['comment_double_0']->getComment()); - self::assertNull($columns['comment_string_0']->getComment()); - self::assertNull($columns['comment']->getComment()); - self::assertEquals('Doctrine array.', $columns['comment_quoted']->getComment()); - self::assertNull($columns['[create]']->getComment()); - self::assertEquals('foo', $columns['commented_type']->getComment()); - self::assertNull($columns['commented_type_with_comment']->getComment()); - self::assertNull($columns['added_comment_none']->getComment()); - self::assertNull($columns['added_comment_null']->getComment()); - self::assertNull($columns['added_comment_false']->getComment()); - self::assertNull($columns['added_comment_empty_string']->getComment()); - self::assertEquals('0', $columns['added_comment_integer_0']->getComment()); - self::assertEquals('0', $columns['added_comment_float_0']->getComment()); - self::assertEquals('0', $columns['added_comment_string_0']->getComment()); - self::assertEquals('Doctrine', $columns['added_comment']->getComment()); - self::assertEquals('rulez', $columns['added_comment_quoted']->getComment()); - self::assertEquals('666', $columns['[select]']->getComment()); - self::assertNull($columns['added_commented_type']->getComment()); - self::assertEquals('666', $columns['added_commented_type_with_comment']->getComment()); - } - - public function testPkOrdering() : void - { - // SQL Server stores index column information in a system table with two - // columns that almost always have the same value: index_column_id and key_ordinal. - // The only situation when the two values doesn't match up is when a clustered index - // is declared that references columns in a different order from which they are - // declared in the table. In that case, key_ordinal != index_column_id. - // key_ordinal holds the index ordering. index_column_id is just a unique identifier - // for index columns within the given index. - $table = new Table('sqlsrv_pk_ordering'); - $table->addColumn('colA', 'integer', ['notnull' => true]); - $table->addColumn('colB', 'integer', ['notnull' => true]); - $table->setPrimaryKey(['colB', 'colA']); - $this->schemaManager->createTable($table); - - $indexes = $this->schemaManager->listTableIndexes('sqlsrv_pk_ordering'); - - self::assertCount(1, $indexes); - - $firstIndex = current($indexes); - $columns = $firstIndex->getColumns(); - self::assertCount(2, $columns); - self::assertEquals('colB', $columns[0]); - self::assertEquals('colA', $columns[1]); - } -} diff --git a/tests/Functional/DBAL/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/DBAL/Schema/SchemaManagerFunctionalTestCase.php deleted file mode 100644 index 2b2a74e..0000000 --- a/tests/Functional/DBAL/Schema/SchemaManagerFunctionalTestCase.php +++ /dev/null @@ -1,1597 +0,0 @@ -getPlatformName(); - - if ($this->connection->getDatabasePlatform()->getName() !== $dbms) { - $this->markTestSkipped(static::class . ' requires the use of ' . $dbms); - } - - $this->schemaManager = $this->connection->getSchemaManager(); - } - - protected function tearDown() : void - { - parent::tearDown(); - - $this->schemaManager->tryMethod('dropTable', 'testschema.my_table_in_namespace'); - - //TODO: SchemaDiff does not drop removed namespaces? - try { - //sql server versions below 2016 do not support 'IF EXISTS' so we have to catch the exception here - $this->connection->exec('DROP SCHEMA testschema'); - } catch (DBALException $e) { - return; - } - } - - /** - * @group DBAL-1220 - */ - public function testDropsDatabaseWithActiveConnections() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsCreateDropDatabase()) { - $this->markTestSkipped('Cannot drop Database client side with this Driver.'); - } - - $this->schemaManager->dropAndCreateDatabase('test_drop_database'); - - $knownDatabases = $this->schemaManager->listDatabases(); - if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { - self::assertContains('TEST_DROP_DATABASE', $knownDatabases); - } else { - self::assertContains('test_drop_database', $knownDatabases); - } - - $params = $this->connection->getParams(); - if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { - $params['user'] = 'test_drop_database'; - } else { - $params['dbname'] = 'test_drop_database'; - } - - $user = $params['user'] ?? null; - $password = $params['password'] ?? null; - - $connection = $this->connection->getDriver()->connect($params, $user, $password); - - self::assertInstanceOf(Connection::class, $connection); - - $this->schemaManager->dropDatabase('test_drop_database'); - - self::assertNotContains('test_drop_database', $this->schemaManager->listDatabases()); - } - - /** - * @group DBAL-195 - */ - public function testDropAndCreateSequence() : void - { - $platform = $this->connection->getDatabasePlatform(); - - if (! $platform->supportsSequences()) { - $this->markTestSkipped( - sprintf('The "%s" platform does not support sequences.', $platform->getName()) - ); - } - - $name = 'dropcreate_sequences_test_seq'; - - $this->schemaManager->dropAndCreateSequence(new Sequence($name, 20, 10)); - - self::assertTrue($this->hasElementWithName($this->schemaManager->listSequences(), $name)); - } - - /** - * @param AbstractAsset[] $items - */ - private function hasElementWithName(array $items, string $name) : bool - { - $filteredList = array_filter( - $items, - static function (AbstractAsset $item) use ($name) : bool { - return $item->getShortestName($item->getNamespaceName()) === $name; - } - ); - - return count($filteredList) === 1; - } - - public function testListSequences() : void - { - $platform = $this->connection->getDatabasePlatform(); - - if (! $platform->supportsSequences()) { - $this->markTestSkipped( - sprintf('The "%s" platform does not support sequences.', $platform->getName()) - ); - } - - $sequence = new Sequence('list_sequences_test_seq', 20, 10); - $this->schemaManager->createSequence($sequence); - - $sequences = $this->schemaManager->listSequences(); - - self::assertIsArray($sequences, 'listSequences() should return an array.'); - - $foundSequence = null; - foreach ($sequences as $sequence) { - self::assertInstanceOf(Sequence::class, $sequence, 'Array elements of listSequences() should be Sequence instances.'); - if (strtolower($sequence->getName()) !== 'list_sequences_test_seq') { - continue; - } - - $foundSequence = $sequence; - } - - self::assertNotNull($foundSequence, "Sequence with name 'list_sequences_test_seq' was not found."); - self::assertSame(20, $foundSequence->getAllocationSize(), 'Allocation Size is expected to be 20.'); - self::assertSame(10, $foundSequence->getInitialValue(), 'Initial Value is expected to be 10.'); - } - - public function testListDatabases() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsCreateDropDatabase()) { - $this->markTestSkipped('Cannot drop Database client side with this Driver.'); - } - - $this->schemaManager->dropAndCreateDatabase('test_create_database'); - $databases = $this->schemaManager->listDatabases(); - - $databases = array_map('strtolower', $databases); - - self::assertContains('test_create_database', $databases); - } - - /** - * @group DBAL-1058 - */ - public function testListNamespaceNames() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) { - $this->markTestSkipped('Platform does not support schemas.'); - } - - // Currently dropping schemas is not supported, so we have to workaround here. - $namespaces = $this->schemaManager->listNamespaceNames(); - $namespaces = array_map('strtolower', $namespaces); - - if (! in_array('test_create_schema', $namespaces)) { - $this->connection->executeUpdate($this->schemaManager->getDatabasePlatform()->getCreateSchemaSQL('test_create_schema')); - - $namespaces = $this->schemaManager->listNamespaceNames(); - $namespaces = array_map('strtolower', $namespaces); - } - - self::assertContains('test_create_schema', $namespaces); - } - - public function testListTables() : void - { - $this->createTestTable('list_tables_test'); - $tables = $this->schemaManager->listTables(); - - self::assertIsArray($tables); - self::assertTrue(count($tables) > 0, "List Tables has to find at least one table named 'list_tables_test'."); - - $foundTable = false; - foreach ($tables as $table) { - self::assertInstanceOf(Table::class, $table); - if (strtolower($table->getName()) !== 'list_tables_test') { - continue; - } - - $foundTable = true; - - self::assertTrue($table->hasColumn('id')); - self::assertTrue($table->hasColumn('test')); - self::assertTrue($table->hasColumn('foreign_key_test')); - } - - self::assertTrue($foundTable, "The 'list_tables_test' table has to be found."); - } - - public function createListTableColumns() : Table - { - $table = new Table('list_table_columns'); - $table->addColumn('id', 'integer', ['notnull' => true]); - $table->addColumn('test', 'string', ['length' => 255, 'notnull' => false, 'default' => 'expected default']); - $table->addColumn('foo', 'text', ['notnull' => true]); - $table->addColumn('bar', 'decimal', ['precision' => 10, 'scale' => 4, 'notnull' => false]); - $table->addColumn('baz1', 'datetime'); - $table->addColumn('baz2', 'time'); - $table->addColumn('baz3', 'date'); - $table->setPrimaryKey(['id']); - - return $table; - } - - public function testListTableColumns() : void - { - $table = $this->createListTableColumns(); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('list_table_columns'); - $columnsKeys = array_keys($columns); - - self::assertArrayHasKey('id', $columns); - self::assertEquals(0, array_search('id', $columnsKeys)); - self::assertEquals('id', strtolower($columns['id']->getname())); - self::assertInstanceOf(IntegerType::class, $columns['id']->gettype()); - self::assertEquals(false, $columns['id']->getunsigned()); - self::assertEquals(true, $columns['id']->getnotnull()); - self::assertEquals(null, $columns['id']->getdefault()); - self::assertIsArray($columns['id']->getPlatformOptions()); - - self::assertArrayHasKey('test', $columns); - self::assertEquals(1, array_search('test', $columnsKeys)); - self::assertEquals('test', strtolower($columns['test']->getname())); - self::assertInstanceOf(StringType::class, $columns['test']->gettype()); - self::assertEquals(255, $columns['test']->getlength()); - self::assertEquals(false, $columns['test']->getfixed()); - self::assertEquals(false, $columns['test']->getnotnull()); - self::assertEquals('expected default', $columns['test']->getdefault()); - self::assertIsArray($columns['test']->getPlatformOptions()); - - self::assertEquals('foo', strtolower($columns['foo']->getname())); - self::assertEquals(2, array_search('foo', $columnsKeys)); - self::assertInstanceOf(TextType::class, $columns['foo']->gettype()); - self::assertEquals(false, $columns['foo']->getunsigned()); - self::assertEquals(false, $columns['foo']->getfixed()); - self::assertEquals(true, $columns['foo']->getnotnull()); - self::assertEquals(null, $columns['foo']->getdefault()); - self::assertIsArray($columns['foo']->getPlatformOptions()); - - self::assertEquals('bar', strtolower($columns['bar']->getname())); - self::assertEquals(3, array_search('bar', $columnsKeys)); - self::assertInstanceOf(DecimalType::class, $columns['bar']->gettype()); - self::assertEquals(null, $columns['bar']->getlength()); - self::assertEquals(10, $columns['bar']->getprecision()); - self::assertEquals(4, $columns['bar']->getscale()); - self::assertEquals(false, $columns['bar']->getunsigned()); - self::assertEquals(false, $columns['bar']->getfixed()); - self::assertEquals(false, $columns['bar']->getnotnull()); - self::assertEquals(null, $columns['bar']->getdefault()); - self::assertIsArray($columns['bar']->getPlatformOptions()); - - self::assertEquals('baz1', strtolower($columns['baz1']->getname())); - self::assertEquals(4, array_search('baz1', $columnsKeys)); - self::assertInstanceOf(DateTimeType::class, $columns['baz1']->gettype()); - self::assertEquals(true, $columns['baz1']->getnotnull()); - self::assertEquals(null, $columns['baz1']->getdefault()); - self::assertIsArray($columns['baz1']->getPlatformOptions()); - - self::assertEquals('baz2', strtolower($columns['baz2']->getname())); - self::assertEquals(5, array_search('baz2', $columnsKeys)); - self::assertContains($columns['baz2']->gettype()->getName(), ['time', 'date', 'datetime']); - self::assertEquals(true, $columns['baz2']->getnotnull()); - self::assertEquals(null, $columns['baz2']->getdefault()); - self::assertIsArray($columns['baz2']->getPlatformOptions()); - - self::assertEquals('baz3', strtolower($columns['baz3']->getname())); - self::assertEquals(6, array_search('baz3', $columnsKeys)); - self::assertContains($columns['baz3']->gettype()->getName(), ['time', 'date', 'datetime']); - self::assertEquals(true, $columns['baz3']->getnotnull()); - self::assertEquals(null, $columns['baz3']->getdefault()); - self::assertIsArray($columns['baz3']->getPlatformOptions()); - } - - /** - * @group DBAL-1078 - */ - public function testListTableColumnsWithFixedStringColumn() : void - { - $tableName = 'test_list_table_fixed_string'; - - $table = new Table($tableName); - $table->addColumn('column_char', 'string', ['fixed' => true, 'length' => 2]); - - $this->schemaManager->createTable($table); - - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertArrayHasKey('column_char', $columns); - self::assertInstanceOf(StringType::class, $columns['column_char']->getType()); - self::assertTrue($columns['column_char']->getFixed()); - self::assertSame(2, $columns['column_char']->getLength()); - } - - public function testListTableColumnsDispatchEvent() : void - { - $table = $this->createListTableColumns(); - - $this->schemaManager->dropAndCreateTable($table); - - $listenerMock = $this - ->getMockBuilder('ListTableColumnsDispatchEventListener') - ->setMethods(['onSchemaColumnDefinition']) - ->getMock(); - $listenerMock - ->expects($this->exactly(7)) - ->method('onSchemaColumnDefinition'); - - $oldEventManager = $this->schemaManager->getDatabasePlatform()->getEventManager(); - - $eventManager = new EventManager(); - $eventManager->addEventListener([Events::onSchemaColumnDefinition], $listenerMock); - - $this->schemaManager->getDatabasePlatform()->setEventManager($eventManager); - - $this->schemaManager->listTableColumns('list_table_columns'); - - $this->schemaManager->getDatabasePlatform()->setEventManager($oldEventManager); - } - - public function testListTableIndexesDispatchEvent() : void - { - $table = $this->getTestTable('list_table_indexes_test'); - $table->addUniqueIndex(['test'], 'test_index_name'); - $table->addIndex(['id', 'test'], 'test_composite_idx'); - - $this->schemaManager->dropAndCreateTable($table); - - $listenerMock = $this - ->getMockBuilder('ListTableIndexesDispatchEventListener') - ->setMethods(['onSchemaIndexDefinition']) - ->getMock(); - $listenerMock - ->expects($this->exactly(3)) - ->method('onSchemaIndexDefinition'); - - $oldEventManager = $this->schemaManager->getDatabasePlatform()->getEventManager(); - - $eventManager = new EventManager(); - $eventManager->addEventListener([Events::onSchemaIndexDefinition], $listenerMock); - - $this->schemaManager->getDatabasePlatform()->setEventManager($eventManager); - - $this->schemaManager->listTableIndexes('list_table_indexes_test'); - - $this->schemaManager->getDatabasePlatform()->setEventManager($oldEventManager); - } - - public function testDiffListTableColumns() : void - { - if ($this->schemaManager->getDatabasePlatform()->getName() === 'oracle') { - $this->markTestSkipped('Does not work with Oracle, since it cannot detect DateTime, Date and Time differenecs (at the moment).'); - } - - $offlineTable = $this->createListTableColumns(); - $this->schemaManager->dropAndCreateTable($offlineTable); - $onlineTable = $this->schemaManager->listTableDetails('list_table_columns'); - - $comparator = new Comparator(); - $diff = $comparator->diffTable($offlineTable, $onlineTable); - - self::assertFalse($diff, 'No differences should be detected with the offline vs online schema.'); - } - - public function testListTableIndexes() : void - { - $table = $this->getTestCompositeTable('list_table_indexes_test'); - $table->addUniqueIndex(['test'], 'test_index_name'); - $table->addIndex(['id', 'test'], 'test_composite_idx'); - - $this->schemaManager->dropAndCreateTable($table); - - $tableIndexes = $this->schemaManager->listTableIndexes('list_table_indexes_test'); - - self::assertEquals(3, count($tableIndexes)); - - self::assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.'); - self::assertEquals(['id', 'other_id'], array_map('strtolower', $tableIndexes['primary']->getColumns())); - self::assertTrue($tableIndexes['primary']->isUnique()); - self::assertTrue($tableIndexes['primary']->isPrimary()); - - self::assertEquals('test_index_name', strtolower($tableIndexes['test_index_name']->getName())); - self::assertEquals(['test'], array_map('strtolower', $tableIndexes['test_index_name']->getColumns())); - self::assertTrue($tableIndexes['test_index_name']->isUnique()); - self::assertFalse($tableIndexes['test_index_name']->isPrimary()); - - self::assertEquals('test_composite_idx', strtolower($tableIndexes['test_composite_idx']->getName())); - self::assertEquals(['id', 'test'], array_map('strtolower', $tableIndexes['test_composite_idx']->getColumns())); - self::assertFalse($tableIndexes['test_composite_idx']->isUnique()); - self::assertFalse($tableIndexes['test_composite_idx']->isPrimary()); - } - - public function testDropAndCreateIndex() : void - { - $table = $this->getTestTable('test_create_index'); - $table->addUniqueIndex(['test'], 'test'); - $this->schemaManager->dropAndCreateTable($table); - - $this->schemaManager->dropAndCreateIndex($table->getIndex('test'), $table); - $tableIndexes = $this->schemaManager->listTableIndexes('test_create_index'); - self::assertIsArray($tableIndexes); - - self::assertEquals('test', strtolower($tableIndexes['test']->getName())); - self::assertEquals(['test'], array_map('strtolower', $tableIndexes['test']->getColumns())); - self::assertTrue($tableIndexes['test']->isUnique()); - self::assertFalse($tableIndexes['test']->isPrimary()); - } - - public function testCreateTableWithForeignKeys() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('Platform does not support foreign keys.'); - } - - $tableB = $this->getTestTable('test_foreign'); - - $this->schemaManager->dropAndCreateTable($tableB); - - $tableA = $this->getTestTable('test_create_fk'); - $tableA->addForeignKeyConstraint('test_foreign', ['foreign_key_test'], ['id']); - - $this->schemaManager->dropAndCreateTable($tableA); - - $fkTable = $this->schemaManager->listTableDetails('test_create_fk'); - $fkConstraints = $fkTable->getForeignKeys(); - self::assertEquals(1, count($fkConstraints), "Table 'test_create_fk1' has to have one foreign key."); - - $fkConstraint = current($fkConstraints); - self::assertInstanceOf(ForeignKeyConstraint::class, $fkConstraint); - self::assertEquals('test_foreign', strtolower($fkConstraint->getForeignTableName())); - self::assertEquals(['foreign_key_test'], array_map('strtolower', $fkConstraint->getColumns())); - self::assertEquals(['id'], array_map('strtolower', $fkConstraint->getForeignColumns())); - - self::assertTrue($fkTable->columnsAreIndexed($fkConstraint->getColumns()), 'The columns of a foreign key constraint should always be indexed.'); - } - - public function testListForeignKeys() : void - { - if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('Does not support foreign key constraints.'); - } - - $this->createTestTable('test_create_fk1'); - $this->createTestTable('test_create_fk2'); - - $foreignKey = new ForeignKeyConstraint( - ['foreign_key_test'], - 'test_create_fk2', - ['id'], - 'foreign_key_test_fk', - ['onDelete' => 'CASCADE'] - ); - - $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk1'); - - $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk1'); - - self::assertEquals(1, count($fkeys), "Table 'test_create_fk1' has to have one foreign key."); - - self::assertInstanceOf(ForeignKeyConstraint::class, $fkeys[0]); - self::assertEquals(['foreign_key_test'], array_map('strtolower', $fkeys[0]->getLocalColumns())); - self::assertEquals(['id'], array_map('strtolower', $fkeys[0]->getForeignColumns())); - self::assertEquals('test_create_fk2', strtolower($fkeys[0]->getForeignTableName())); - - if (! $fkeys[0]->hasOption('onDelete')) { - return; - } - - self::assertEquals('CASCADE', $fkeys[0]->getOption('onDelete')); - } - - protected function getCreateExampleViewSql() : void - { - $this->markTestSkipped('No Create Example View SQL was defined for this SchemaManager'); - } - - public function testCreateSchema() : void - { - $this->createTestTable('test_table'); - - $schema = $this->schemaManager->createSchema(); - self::assertTrue($schema->hasTable('test_table')); - } - - public function testAlterTableScenario() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsAlterTable()) { - $this->markTestSkipped('Alter Table is not supported by this platform.'); - } - - $alterTable = $this->createTestTable('alter_table'); - $this->createTestTable('alter_table_foreign'); - - $table = $this->schemaManager->listTableDetails('alter_table'); - self::assertTrue($table->hasColumn('id')); - self::assertTrue($table->hasColumn('test')); - self::assertTrue($table->hasColumn('foreign_key_test')); - self::assertEquals(0, count($table->getForeignKeys())); - self::assertEquals(1, count($table->getIndexes())); - - $tableDiff = new TableDiff('alter_table'); - $tableDiff->fromTable = $alterTable; - $tableDiff->addedColumns['foo'] = new Column('foo', Type::getType('integer')); - $tableDiff->removedColumns['test'] = $table->getColumn('test'); - - $this->schemaManager->alterTable($tableDiff); - - $table = $this->schemaManager->listTableDetails('alter_table'); - self::assertFalse($table->hasColumn('test')); - self::assertTrue($table->hasColumn('foo')); - - $tableDiff = new TableDiff('alter_table'); - $tableDiff->fromTable = $table; - $tableDiff->addedIndexes[] = new Index('foo_idx', ['foo']); - - $this->schemaManager->alterTable($tableDiff); - - $table = $this->schemaManager->listTableDetails('alter_table'); - self::assertEquals(2, count($table->getIndexes())); - self::assertTrue($table->hasIndex('foo_idx')); - self::assertEquals(['foo'], array_map('strtolower', $table->getIndex('foo_idx')->getColumns())); - self::assertFalse($table->getIndex('foo_idx')->isPrimary()); - self::assertFalse($table->getIndex('foo_idx')->isUnique()); - - $tableDiff = new TableDiff('alter_table'); - $tableDiff->fromTable = $table; - $tableDiff->changedIndexes[] = new Index('foo_idx', ['foo', 'foreign_key_test']); - - $this->schemaManager->alterTable($tableDiff); - - $table = $this->schemaManager->listTableDetails('alter_table'); - self::assertEquals(2, count($table->getIndexes())); - self::assertTrue($table->hasIndex('foo_idx')); - self::assertEquals(['foo', 'foreign_key_test'], array_map('strtolower', $table->getIndex('foo_idx')->getColumns())); - - $tableDiff = new TableDiff('alter_table'); - $tableDiff->fromTable = $table; - $tableDiff->renamedIndexes['foo_idx'] = new Index('bar_idx', ['foo', 'foreign_key_test']); - - $this->schemaManager->alterTable($tableDiff); - - $table = $this->schemaManager->listTableDetails('alter_table'); - self::assertEquals(2, count($table->getIndexes())); - self::assertTrue($table->hasIndex('bar_idx')); - self::assertFalse($table->hasIndex('foo_idx')); - self::assertEquals(['foo', 'foreign_key_test'], array_map('strtolower', $table->getIndex('bar_idx')->getColumns())); - self::assertFalse($table->getIndex('bar_idx')->isPrimary()); - self::assertFalse($table->getIndex('bar_idx')->isUnique()); - - $tableDiff = new TableDiff('alter_table'); - $tableDiff->fromTable = $table; - $tableDiff->removedIndexes[] = new Index('bar_idx', ['foo', 'foreign_key_test']); - $fk = new ForeignKeyConstraint(['foreign_key_test'], 'alter_table_foreign', ['id']); - $tableDiff->addedForeignKeys[] = $fk; - - $this->schemaManager->alterTable($tableDiff); - $table = $this->schemaManager->listTableDetails('alter_table'); - - // dont check for index size here, some platforms automatically add indexes for foreign keys. - self::assertFalse($table->hasIndex('bar_idx')); - - if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { - return; - } - - $fks = $table->getForeignKeys(); - self::assertCount(1, $fks); - $foreignKey = current($fks); - self::assertEquals('alter_table_foreign', strtolower($foreignKey->getForeignTableName())); - self::assertEquals(['foreign_key_test'], array_map('strtolower', $foreignKey->getColumns())); - self::assertEquals(['id'], array_map('strtolower', $foreignKey->getForeignColumns())); - } - - public function testTableInNamespace() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) { - $this->markTestSkipped('Schema definition is not supported by this platform.'); - } - - //create schema - $diff = new SchemaDiff(); - $diff->newNamespaces[] = 'testschema'; - - foreach ($diff->toSql($this->schemaManager->getDatabasePlatform()) as $sql) { - $this->connection->exec($sql); - } - - //test if table is create in namespace - $this->createTestTable('testschema.my_table_in_namespace'); - self::assertContains('testschema.my_table_in_namespace', $this->schemaManager->listTableNames()); - - //tables without namespace should be created in default namespace - //default namespaces are ignored in table listings - $this->createTestTable('my_table_not_in_namespace'); - self::assertContains('my_table_not_in_namespace', $this->schemaManager->listTableNames()); - } - - public function testCreateAndListViews() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsViews()) { - $this->markTestSkipped('Views is not supported by this platform.'); - } - - $this->createTestTable('view_test_table'); - - $name = 'doctrine_test_view'; - $sql = 'SELECT * FROM view_test_table'; - - $view = new View($name, $sql); - - $this->schemaManager->dropAndCreateView($view); - - self::assertTrue($this->hasElementWithName($this->schemaManager->listViews(), $name)); - } - - public function testAutoincrementDetection() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsIdentityColumns()) { - $this->markTestSkipped('This test is only supported on platforms that have autoincrement'); - } - - $table = new Table('test_autoincrement'); - $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $inferredTable = $this->schemaManager->listTableDetails('test_autoincrement'); - self::assertTrue($inferredTable->hasColumn('id')); - self::assertTrue($inferredTable->getColumn('id')->getAutoincrement()); - } - - /** - * @group DBAL-792 - */ - public function testAutoincrementDetectionMulticolumns() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsIdentityColumns()) { - $this->markTestSkipped('This test is only supported on platforms that have autoincrement'); - } - - $table = new Table('test_not_autoincrement'); - $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); - $table->addColumn('id', 'integer'); - $table->addColumn('other_id', 'integer'); - $table->setPrimaryKey(['id', 'other_id']); - - $this->schemaManager->createTable($table); - - $inferredTable = $this->schemaManager->listTableDetails('test_not_autoincrement'); - self::assertTrue($inferredTable->hasColumn('id')); - self::assertFalse($inferredTable->getColumn('id')->getAutoincrement()); - } - - /** - * @group DDC-887 - */ - public function testUpdateSchemaWithForeignKeyRenaming() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('This test is only supported on platforms that have foreign keys.'); - } - - $table = new Table('test_fk_base'); - $table->addColumn('id', 'integer'); - $table->setPrimaryKey(['id']); - - $tableFK = new Table('test_fk_rename'); - $tableFK->setSchemaConfig($this->schemaManager->createSchemaConfig()); - $tableFK->addColumn('id', 'integer'); - $tableFK->addColumn('fk_id', 'integer'); - $tableFK->setPrimaryKey(['id']); - $tableFK->addIndex(['fk_id'], 'fk_idx'); - $tableFK->addForeignKeyConstraint('test_fk_base', ['fk_id'], ['id']); - - $this->schemaManager->createTable($table); - $this->schemaManager->createTable($tableFK); - - $tableFKNew = new Table('test_fk_rename'); - $tableFKNew->setSchemaConfig($this->schemaManager->createSchemaConfig()); - $tableFKNew->addColumn('id', 'integer'); - $tableFKNew->addColumn('rename_fk_id', 'integer'); - $tableFKNew->setPrimaryKey(['id']); - $tableFKNew->addIndex(['rename_fk_id'], 'fk_idx'); - $tableFKNew->addForeignKeyConstraint('test_fk_base', ['rename_fk_id'], ['id']); - - $c = new Comparator(); - $tableDiff = $c->diffTable($tableFK, $tableFKNew); - - $this->schemaManager->alterTable($tableDiff); - - $table = $this->schemaManager->listTableDetails('test_fk_rename'); - $foreignKeys = $table->getForeignKeys(); - - self::assertTrue($table->hasColumn('rename_fk_id')); - self::assertCount(1, $foreignKeys); - self::assertSame(['rename_fk_id'], array_map('strtolower', current($foreignKeys)->getColumns())); - } - - /** - * @group DBAL-1062 - */ - public function testRenameIndexUsedInForeignKeyConstraint() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('This test is only supported on platforms that have foreign keys.'); - } - - $primaryTable = new Table('test_rename_index_primary'); - $primaryTable->addColumn('id', 'integer'); - $primaryTable->setPrimaryKey(['id']); - - $foreignTable = new Table('test_rename_index_foreign'); - $foreignTable->addColumn('fk', 'integer'); - $foreignTable->addIndex(['fk'], 'rename_index_fk_idx'); - $foreignTable->addForeignKeyConstraint( - 'test_rename_index_primary', - ['fk'], - ['id'], - [], - 'fk_constraint' - ); - - $this->schemaManager->dropAndCreateTable($primaryTable); - $this->schemaManager->dropAndCreateTable($foreignTable); - - $foreignTable2 = clone $foreignTable; - $foreignTable2->renameIndex('rename_index_fk_idx', 'renamed_index_fk_idx'); - - $comparator = new Comparator(); - - $this->schemaManager->alterTable($comparator->diffTable($foreignTable, $foreignTable2)); - - $foreignTable = $this->schemaManager->listTableDetails('test_rename_index_foreign'); - - self::assertFalse($foreignTable->hasIndex('rename_index_fk_idx')); - self::assertTrue($foreignTable->hasIndex('renamed_index_fk_idx')); - self::assertTrue($foreignTable->hasForeignKey('fk_constraint')); - } - - /** - * @group DBAL-42 - */ - public function testGetColumnComment() : void - { - if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && - ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && - $this->connection->getDatabasePlatform()->getName() !== 'mssql') { - $this->markTestSkipped('Database does not support column comments.'); - } - - $table = new Table('column_comment_test'); - $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $columns = $this->schemaManager->listTableColumns('column_comment_test'); - self::assertEquals(1, count($columns)); - self::assertEquals('This is a comment', $columns['id']->getComment()); - - $tableDiff = new TableDiff('column_comment_test'); - $tableDiff->fromTable = $table; - $tableDiff->changedColumns['id'] = new ColumnDiff( - 'id', - new Column( - 'id', - Type::getType('integer') - ), - ['comment'], - new Column( - 'id', - Type::getType('integer'), - ['comment' => 'This is a comment'] - ) - ); - - $this->schemaManager->alterTable($tableDiff); - - $columns = $this->schemaManager->listTableColumns('column_comment_test'); - self::assertEquals(1, count($columns)); - self::assertEmpty($columns['id']->getComment()); - } - - /** - * @group DBAL-42 - */ - public function testAutomaticallyAppendCommentOnMarkedColumns() : void - { - if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && - ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && - $this->connection->getDatabasePlatform()->getName() !== 'mssql') { - $this->markTestSkipped('Database does not support column comments.'); - } - - $table = new Table('column_comment_test2'); - $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); - $table->addColumn('obj', 'object', ['comment' => 'This is a comment']); - $table->addColumn('arr', 'array', ['comment' => 'This is a comment']); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $columns = $this->schemaManager->listTableColumns('column_comment_test2'); - self::assertEquals(3, count($columns)); - self::assertEquals('This is a comment', $columns['id']->getComment()); - self::assertEquals('This is a comment', $columns['obj']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.'); - self::assertInstanceOf(ObjectType::class, $columns['obj']->getType(), 'The Doctrine2 should be detected from comment hint.'); - self::assertEquals('This is a comment', $columns['arr']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.'); - self::assertInstanceOf(ArrayType::class, $columns['arr']->getType(), 'The Doctrine2 should be detected from comment hint.'); - } - - /** - * @group DBAL-1228 - */ - public function testCommentHintOnDateIntervalTypeColumn() : void - { - if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && - ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && - $this->connection->getDatabasePlatform()->getName() !== 'mssql') { - $this->markTestSkipped('Database does not support column comments.'); - } - - $table = new Table('column_dateinterval_comment'); - $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); - $table->addColumn('date_interval', 'dateinterval', ['comment' => 'This is a comment']); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $columns = $this->schemaManager->listTableColumns('column_dateinterval_comment'); - self::assertEquals(2, count($columns)); - self::assertEquals('This is a comment', $columns['id']->getComment()); - self::assertEquals('This is a comment', $columns['date_interval']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.'); - self::assertInstanceOf(DateIntervalType::class, $columns['date_interval']->getType(), 'The Doctrine2 should be detected from comment hint.'); - } - - /** - * @group DBAL-825 - */ - public function testChangeColumnsTypeWithDefaultValue() : void - { - $tableName = 'column_def_change_type'; - $table = new Table($tableName); - - $table->addColumn('col_int', 'smallint', ['default' => 666]); - $table->addColumn('col_string', 'string', ['default' => 'foo']); - - $this->schemaManager->dropAndCreateTable($table); - - $tableDiff = new TableDiff($tableName); - $tableDiff->fromTable = $table; - $tableDiff->changedColumns['col_int'] = new ColumnDiff( - 'col_int', - new Column('col_int', Type::getType('integer'), ['default' => 666]), - ['type'], - new Column('col_int', Type::getType('smallint'), ['default' => 666]) - ); - - $tableDiff->changedColumns['col_string'] = new ColumnDiff( - 'col_string', - new Column('col_string', Type::getType('string'), ['default' => 'foo', 'fixed' => true]), - ['fixed'], - new Column('col_string', Type::getType('string'), ['default' => 'foo']) - ); - - $this->schemaManager->alterTable($tableDiff); - - $columns = $this->schemaManager->listTableColumns($tableName); - - self::assertInstanceOf(IntegerType::class, $columns['col_int']->getType()); - self::assertEquals(666, $columns['col_int']->getDefault()); - - self::assertInstanceOf(StringType::class, $columns['col_string']->getType()); - self::assertEquals('foo', $columns['col_string']->getDefault()); - } - - /** - * @group DBAL-197 - */ - public function testListTableWithBlob() : void - { - $table = new Table('test_blob_table'); - $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); - $table->addColumn('binarydata', 'blob', []); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $created = $this->schemaManager->listTableDetails('test_blob_table'); - - self::assertTrue($created->hasColumn('id')); - self::assertTrue($created->hasColumn('binarydata')); - self::assertTrue($created->hasPrimaryKey()); - } - - /** - * @param mixed[] $data - */ - protected function createTestTable(string $name = 'test_table', array $data = []) : Table - { - $options = $data['options'] ?? []; - - $table = $this->getTestTable($name, $options); - - $this->schemaManager->dropAndCreateTable($table); - - return $table; - } - - /** - * @param mixed[] $options - */ - protected function getTestTable(string $name, array $options = []) : Table - { - $table = new Table($name, [], [], [], false, $options); - $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); - $table->addColumn('id', 'integer', ['notnull' => true]); - $table->setPrimaryKey(['id']); - $table->addColumn('test', 'string', ['length' => 255]); - $table->addColumn('foreign_key_test', 'integer'); - - return $table; - } - - protected function getTestCompositeTable(string $name) : Table - { - $table = new Table($name, [], [], [], false, []); - $table->setSchemaConfig($this->schemaManager->createSchemaConfig()); - $table->addColumn('id', 'integer', ['notnull' => true]); - $table->addColumn('other_id', 'integer', ['notnull' => true]); - $table->setPrimaryKey(['id', 'other_id']); - $table->addColumn('test', 'string', ['length' => 255]); - - return $table; - } - - /** - * @param Table[] $tables - */ - protected function assertHasTable(array $tables) : void - { - $foundTable = false; - foreach ($tables as $table) { - self::assertInstanceOf(Table::class, $table, 'No Table instance was found in tables array.'); - if (strtolower($table->getName()) !== 'list_tables_test_new_name') { - continue; - } - - $foundTable = true; - } - self::assertTrue($foundTable, 'Could not find new table'); - } - - public function testListForeignKeysComposite() : void - { - if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('Does not support foreign key constraints.'); - } - - $this->schemaManager->createTable($this->getTestTable('test_create_fk3')); - $this->schemaManager->createTable($this->getTestCompositeTable('test_create_fk4')); - - $foreignKey = new ForeignKeyConstraint( - ['id', 'foreign_key_test'], - 'test_create_fk4', - ['id', 'other_id'], - 'foreign_key_test_fk2' - ); - - $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk3'); - - $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk3'); - - self::assertEquals(1, count($fkeys), "Table 'test_create_fk3' has to have one foreign key."); - - self::assertInstanceOf(ForeignKeyConstraint::class, $fkeys[0]); - self::assertEquals(['id', 'foreign_key_test'], array_map('strtolower', $fkeys[0]->getLocalColumns())); - self::assertEquals(['id', 'other_id'], array_map('strtolower', $fkeys[0]->getForeignColumns())); - } - - /** - * @group DBAL-44 - */ - public function testColumnDefaultLifecycle() : void - { - $table = new Table('col_def_lifecycle'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->addColumn('column1', 'string', ['default' => null]); - $table->addColumn('column2', 'string', ['default' => false]); - $table->addColumn('column3', 'string', ['default' => true]); - $table->addColumn('column4', 'string', ['default' => 0]); - $table->addColumn('column5', 'string', ['default' => '']); - $table->addColumn('column6', 'string', ['default' => 'def']); - $table->addColumn('column7', 'integer', ['default' => 0]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('col_def_lifecycle'); - - self::assertNull($columns['id']->getDefault()); - self::assertNull($columns['column1']->getDefault()); - self::assertSame('', $columns['column2']->getDefault()); - self::assertSame('1', $columns['column3']->getDefault()); - self::assertSame('0', $columns['column4']->getDefault()); - self::assertSame('', $columns['column5']->getDefault()); - self::assertSame('def', $columns['column6']->getDefault()); - self::assertSame('0', $columns['column7']->getDefault()); - - $diffTable = clone $table; - - $diffTable->changeColumn('column1', ['default' => false]); - $diffTable->changeColumn('column2', ['default' => null]); - $diffTable->changeColumn('column3', ['default' => false]); - $diffTable->changeColumn('column4', ['default' => null]); - $diffTable->changeColumn('column5', ['default' => false]); - $diffTable->changeColumn('column6', ['default' => 666]); - $diffTable->changeColumn('column7', ['default' => null]); - - $comparator = new Comparator(); - - $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable)); - - $columns = $this->schemaManager->listTableColumns('col_def_lifecycle'); - - self::assertSame('', $columns['column1']->getDefault()); - self::assertNull($columns['column2']->getDefault()); - self::assertSame('', $columns['column3']->getDefault()); - self::assertNull($columns['column4']->getDefault()); - self::assertSame('', $columns['column5']->getDefault()); - self::assertSame('666', $columns['column6']->getDefault()); - self::assertNull($columns['column7']->getDefault()); - } - - public function testListTableWithBinary() : void - { - $tableName = 'test_binary_table'; - - $table = new Table($tableName); - $table->addColumn('id', 'integer'); - $table->addColumn('column_varbinary', 'binary', []); - $table->addColumn('column_binary', 'binary', ['fixed' => true]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $table = $this->schemaManager->listTableDetails($tableName); - - self::assertInstanceOf(BinaryType::class, $table->getColumn('column_varbinary')->getType()); - self::assertFalse($table->getColumn('column_varbinary')->getFixed()); - - self::assertInstanceOf(BinaryType::class, $table->getColumn('column_binary')->getType()); - self::assertTrue($table->getColumn('column_binary')->getFixed()); - } - - public function testListTableDetailsWithFullQualifiedTableName() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) { - $this->markTestSkipped('Test only works on platforms that support schemas.'); - } - - $defaultSchemaName = $this->schemaManager->getDatabasePlatform()->getDefaultSchemaName(); - $primaryTableName = 'primary_table'; - $foreignTableName = 'foreign_table'; - - $table = new Table($foreignTableName); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->dropAndCreateTable($table); - - $table = new Table($primaryTableName); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->addColumn('foo', 'integer'); - $table->addColumn('bar', 'string'); - $table->addForeignKeyConstraint($foreignTableName, ['foo'], ['id']); - $table->addIndex(['bar']); - $table->setPrimaryKey(['id']); - - $this->schemaManager->dropAndCreateTable($table); - - self::assertEquals( - $this->schemaManager->listTableColumns($primaryTableName), - $this->schemaManager->listTableColumns($defaultSchemaName . '.' . $primaryTableName) - ); - self::assertEquals( - $this->schemaManager->listTableIndexes($primaryTableName), - $this->schemaManager->listTableIndexes($defaultSchemaName . '.' . $primaryTableName) - ); - self::assertEquals( - $this->schemaManager->listTableForeignKeys($primaryTableName), - $this->schemaManager->listTableForeignKeys($defaultSchemaName . '.' . $primaryTableName) - ); - } - - public function testCommentStringsAreQuoted() : void - { - if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && - ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && - $this->connection->getDatabasePlatform()->getName() !== 'mssql') { - $this->markTestSkipped('Database does not support column comments.'); - } - - $table = new Table('my_table'); - $table->addColumn('id', 'integer', ['comment' => "It's a comment with a quote"]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $columns = $this->schemaManager->listTableColumns('my_table'); - self::assertEquals("It's a comment with a quote", $columns['id']->getComment()); - } - - public function testCommentNotDuplicated() : void - { - if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments()) { - $this->markTestSkipped('Database does not support column comments.'); - } - - $options = [ - 'type' => Type::getType('integer'), - 'default' => 0, - 'notnull' => true, - 'comment' => 'expected+column+comment', - ]; - $columnDefinition = substr($this->connection->getDatabasePlatform()->getColumnDeclarationSQL('id', $options), strlen('id') + 1); - - $table = new Table('my_table'); - $table->addColumn('id', 'integer', ['columnDefinition' => $columnDefinition, 'comment' => 'unexpected_column_comment']); - $sql = $this->connection->getDatabasePlatform()->getCreateTableSQL($table); - - self::assertStringContainsString('expected+column+comment', $sql[0]); - self::assertStringNotContainsString('unexpected_column_comment', $sql[0]); - } - - /** - * @group DBAL-1009 - * @dataProvider getAlterColumnComment - */ - public function testAlterColumnComment( - ?string $comment1, - ?string $expectedComment1, - ?string $comment2, - ?string $expectedComment2 - ) : void { - if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() && - ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() && - $this->connection->getDatabasePlatform()->getName() !== 'mssql') { - $this->markTestSkipped('Database does not support column comments.'); - } - - $offlineTable = new Table('alter_column_comment_test'); - $offlineTable->addColumn('comment1', 'integer', ['comment' => $comment1]); - $offlineTable->addColumn('comment2', 'integer', ['comment' => $comment2]); - $offlineTable->addColumn('no_comment1', 'integer'); - $offlineTable->addColumn('no_comment2', 'integer'); - $this->schemaManager->dropAndCreateTable($offlineTable); - - $onlineTable = $this->schemaManager->listTableDetails('alter_column_comment_test'); - - self::assertSame($expectedComment1, $onlineTable->getColumn('comment1')->getComment()); - self::assertSame($expectedComment2, $onlineTable->getColumn('comment2')->getComment()); - self::assertNull($onlineTable->getColumn('no_comment1')->getComment()); - self::assertNull($onlineTable->getColumn('no_comment2')->getComment()); - - $onlineTable->changeColumn('comment1', ['comment' => $comment2]); - $onlineTable->changeColumn('comment2', ['comment' => $comment1]); - $onlineTable->changeColumn('no_comment1', ['comment' => $comment1]); - $onlineTable->changeColumn('no_comment2', ['comment' => $comment2]); - - $comparator = new Comparator(); - - $tableDiff = $comparator->diffTable($offlineTable, $onlineTable); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - - $this->schemaManager->alterTable($tableDiff); - - $onlineTable = $this->schemaManager->listTableDetails('alter_column_comment_test'); - - self::assertSame($expectedComment2, $onlineTable->getColumn('comment1')->getComment()); - self::assertSame($expectedComment1, $onlineTable->getColumn('comment2')->getComment()); - self::assertSame($expectedComment1, $onlineTable->getColumn('no_comment1')->getComment()); - self::assertSame($expectedComment2, $onlineTable->getColumn('no_comment2')->getComment()); - } - - /** - * @return mixed[][] - */ - public static function getAlterColumnComment() : iterable - { - return [ - [null, null, ' ', ' '], - [null, null, '0', '0'], - [null, null, 'foo', 'foo'], - - ['', null, ' ', ' '], - ['', null, '0', '0'], - ['', null, 'foo', 'foo'], - - [' ', ' ', '0', '0'], - [' ', ' ', 'foo', 'foo'], - - ['0', '0', 'foo', 'foo'], - ]; - } - - /** - * @group DBAL-1095 - */ - public function testDoesNotListIndexesImplicitlyCreatedByForeignKeys() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->markTestSkipped('This test is only supported on platforms that have foreign keys.'); - } - - $primaryTable = new Table('test_list_index_impl_primary'); - $primaryTable->addColumn('id', 'integer'); - $primaryTable->setPrimaryKey(['id']); - - $foreignTable = new Table('test_list_index_impl_foreign'); - $foreignTable->addColumn('fk1', 'integer'); - $foreignTable->addColumn('fk2', 'integer'); - $foreignTable->addIndex(['fk1'], 'explicit_fk1_idx'); - $foreignTable->addForeignKeyConstraint('test_list_index_impl_primary', ['fk1'], ['id']); - $foreignTable->addForeignKeyConstraint('test_list_index_impl_primary', ['fk2'], ['id']); - - $this->schemaManager->dropAndCreateTable($primaryTable); - $this->schemaManager->dropAndCreateTable($foreignTable); - - $indexes = $this->schemaManager->listTableIndexes('test_list_index_impl_foreign'); - - self::assertCount(2, $indexes); - self::assertArrayHasKey('explicit_fk1_idx', $indexes); - self::assertArrayHasKey('idx_3d6c147fdc58d6c', $indexes); - } - - /** - * @after - */ - public function removeJsonArrayTable() : void - { - if (! $this->schemaManager->tablesExist(['json_array_test'])) { - return; - } - - $this->schemaManager->dropTable('json_array_test'); - } - - /** - * @group 2782 - * @group 6654 - */ - public function testComparatorShouldReturnFalseWhenLegacyJsonArrayColumnHasComment() : void - { - $table = new Table('json_array_test'); - $table->addColumn('parameters', 'json_array'); - - $this->schemaManager->createTable($table); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); - - self::assertFalse($tableDiff); - } - - /** - * @group 2782 - * @group 6654 - */ - public function testComparatorShouldModifyOnlyTheCommentWhenUpdatingFromJsonArrayTypeOnLegacyPlatforms() : void - { - if ($this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { - $this->markTestSkipped('This test is only supported on platforms that do not have native JSON type.'); - } - - $table = new Table('json_array_test'); - $table->addColumn('parameters', 'json_array'); - - $this->schemaManager->createTable($table); - - $table = new Table('json_array_test'); - $table->addColumn('parameters', 'json'); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - - $changedColumn = $tableDiff->changedColumns['parameters'] ?? $tableDiff->changedColumns['PARAMETERS']; - - self::assertSame(['comment'], $changedColumn->changedProperties); - } - - /** - * @group 2782 - * @group 6654 - */ - public function testComparatorShouldAddCommentToLegacyJsonArrayTypeThatDoesNotHaveIt() : void - { - if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { - $this->markTestSkipped('This test is only supported on platforms that have native JSON type.'); - } - - $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON NOT NULL)'); - - $table = new Table('json_array_test'); - $table->addColumn('parameters', 'json_array'); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertSame(['comment'], $tableDiff->changedColumns['parameters']->changedProperties); - } - - /** - * @group 2782 - * @group 6654 - */ - public function testComparatorShouldReturnAllChangesWhenUsingLegacyJsonArrayType() : void - { - if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { - $this->markTestSkipped('This test is only supported on platforms that have native JSON type.'); - } - - $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON DEFAULT NULL)'); - - $table = new Table('json_array_test'); - $table->addColumn('parameters', 'json_array'); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertSame(['notnull', 'comment'], $tableDiff->changedColumns['parameters']->changedProperties); - } - - /** - * @group 2782 - * @group 6654 - */ - public function testComparatorShouldReturnAllChangesWhenUsingLegacyJsonArrayTypeEvenWhenPlatformHasJsonSupport() : void - { - if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { - $this->markTestSkipped('This test is only supported on platforms that have native JSON type.'); - } - - $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON DEFAULT NULL)'); - - $table = new Table('json_array_test'); - $table->addColumn('parameters', 'json_array'); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertSame(['notnull', 'comment'], $tableDiff->changedColumns['parameters']->changedProperties); - } - - /** - * @group 2782 - * @group 6654 - */ - public function testComparatorShouldNotAddCommentToJsonTypeSinceItIsTheDefaultNow() : void - { - if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) { - $this->markTestSkipped('This test is only supported on platforms that have native JSON type.'); - } - - $this->connection->executeQuery('CREATE TABLE json_test (parameters JSON NOT NULL)'); - - $table = new Table('json_test'); - $table->addColumn('parameters', 'json'); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($this->schemaManager->listTableDetails('json_test'), $table); - - self::assertFalse($tableDiff); - } - - /** - * @dataProvider commentsProvider - * @group 2596 - */ - public function testExtractDoctrineTypeFromComment(string $comment, string $expected, string $currentType) : void - { - $result = $this->schemaManager->extractDoctrineTypeFromComment($comment, $currentType); - - self::assertSame($expected, $result); - } - - /** - * @return string[][] - */ - public function commentsProvider() : array - { - $currentType = 'current type'; - - return [ - 'invalid custom type comments' => ['should.return.current.type', $currentType, $currentType], - 'valid doctrine type' => ['(DC2Type:guid)', 'guid', $currentType], - 'valid with dots' => ['(DC2Type:type.should.return)', 'type.should.return', $currentType], - 'valid with namespace' => ['(DC2Type:Namespace\Class)', 'Namespace\Class', $currentType], - 'valid with extra closing bracket' => ['(DC2Type:should.stop)).before)', 'should.stop', $currentType], - 'valid with extra opening brackets' => ['(DC2Type:should((.stop)).before)', 'should((.stop', $currentType], - ]; - } - - public function testCreateAndListSequences() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsSequences()) { - self::markTestSkipped('This test is only supported on platforms that support sequences.'); - } - - $sequence1Name = 'sequence_1'; - $sequence1AllocationSize = 1; - $sequence1InitialValue = 2; - $sequence2Name = 'sequence_2'; - $sequence2AllocationSize = 3; - $sequence2InitialValue = 4; - $sequence1 = new Sequence($sequence1Name, $sequence1AllocationSize, $sequence1InitialValue); - $sequence2 = new Sequence($sequence2Name, $sequence2AllocationSize, $sequence2InitialValue); - - $this->schemaManager->createSequence($sequence1); - $this->schemaManager->createSequence($sequence2); - - /** @var Sequence[] $actualSequences */ - $actualSequences = []; - foreach ($this->schemaManager->listSequences() as $sequence) { - $actualSequences[$sequence->getName()] = $sequence; - } - - $actualSequence1 = $actualSequences[$sequence1Name]; - $actualSequence2 = $actualSequences[$sequence2Name]; - - self::assertSame($sequence1Name, $actualSequence1->getName()); - self::assertEquals($sequence1AllocationSize, $actualSequence1->getAllocationSize()); - self::assertEquals($sequence1InitialValue, $actualSequence1->getInitialValue()); - - self::assertSame($sequence2Name, $actualSequence2->getName()); - self::assertEquals($sequence2AllocationSize, $actualSequence2->getAllocationSize()); - self::assertEquals($sequence2InitialValue, $actualSequence2->getInitialValue()); - } - - /** - * @group #3086 - */ - public function testComparisonWithAutoDetectedSequenceDefinition() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsSequences()) { - self::markTestSkipped('This test is only supported on platforms that support sequences.'); - } - - $sequenceName = 'sequence_auto_detect_test'; - $sequenceAllocationSize = 5; - $sequenceInitialValue = 10; - $sequence = new Sequence($sequenceName, $sequenceAllocationSize, $sequenceInitialValue); - - $this->schemaManager->dropAndCreateSequence($sequence); - - $createdSequence = array_values( - array_filter( - $this->schemaManager->listSequences(), - static function (Sequence $sequence) use ($sequenceName) : bool { - return strcasecmp($sequence->getName(), $sequenceName) === 0; - } - ) - )[0] ?? null; - - self::assertNotNull($createdSequence); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffSequence($createdSequence, $sequence); - - self::assertFalse($tableDiff); - } - - /** - * @group DBAL-2921 - */ - public function testPrimaryKeyAutoIncrement() : void - { - $table = new Table('test_pk_auto_increment'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->addColumn('text', 'string'); - $table->setPrimaryKey(['id']); - $this->schemaManager->dropAndCreateTable($table); - - $this->connection->insert('test_pk_auto_increment', ['text' => '1']); - - $query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'1\''); - $query->execute(); - $lastUsedIdBeforeDelete = (int) $query->fetchColumn(); - - $this->connection->query('DELETE FROM test_pk_auto_increment'); - - $this->connection->insert('test_pk_auto_increment', ['text' => '2']); - - $query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'2\''); - $query->execute(); - $lastUsedIdAfterDelete = (int) $query->fetchColumn(); - - $this->assertGreaterThan($lastUsedIdBeforeDelete, $lastUsedIdAfterDelete); - } - - public function testGenerateAnIndexWithPartialColumnLength() : void - { - if (! $this->schemaManager->getDatabasePlatform()->supportsColumnLengthIndexes()) { - self::markTestSkipped('This test is only supported on platforms that support indexes with column length definitions.'); - } - - $table = new Table('test_partial_column_index'); - $table->addColumn('long_column', 'string', ['length' => 40]); - $table->addColumn('standard_column', 'integer'); - $table->addIndex(['long_column'], 'partial_long_column_idx', [], ['lengths' => [4]]); - $table->addIndex(['standard_column', 'long_column'], 'standard_and_partial_idx', [], ['lengths' => [null, 2]]); - - $expected = $table->getIndexes(); - - $this->schemaManager->dropAndCreateTable($table); - - $onlineTable = $this->schemaManager->listTableDetails('test_partial_column_index'); - self::assertEquals($expected, $onlineTable->getIndexes()); - } -} diff --git a/tests/Functional/DBAL/Schema/SqliteSchemaManagerTest.php b/tests/Functional/DBAL/Schema/SqliteSchemaManagerTest.php deleted file mode 100644 index 7b62751..0000000 --- a/tests/Functional/DBAL/Schema/SqliteSchemaManagerTest.php +++ /dev/null @@ -1,281 +0,0 @@ -expectException(DBALException::class); - - $this->schemaManager->listDatabases(); - } - - public function testCreateAndDropDatabase() : void - { - $path = dirname(__FILE__) . '/test_create_and_drop_sqlite_database.sqlite'; - - $this->schemaManager->createDatabase($path); - self::assertFileExists($path); - $this->schemaManager->dropDatabase($path); - self::assertFileNotExists($path); - } - - /** - * @group DBAL-1220 - */ - public function testDropsDatabaseWithActiveConnections() : void - { - $this->schemaManager->dropAndCreateDatabase('test_drop_database'); - - self::assertFileExists('test_drop_database'); - - $params = $this->connection->getParams(); - $params['dbname'] = 'test_drop_database'; - - $user = $params['user'] ?? null; - $password = $params['password'] ?? null; - - $connection = $this->connection->getDriver()->connect($params, $user, $password); - - self::assertInstanceOf(Connection::class, $connection); - - $this->schemaManager->dropDatabase('test_drop_database'); - - self::assertFileNotExists('test_drop_database'); - - unset($connection); - } - - public function testRenameTable() : void - { - $this->createTestTable('oldname'); - $this->schemaManager->renameTable('oldname', 'newname'); - - $tables = $this->schemaManager->listTableNames(); - self::assertContains('newname', $tables); - self::assertNotContains('oldname', $tables); - } - - public function createListTableColumns() : Table - { - $table = parent::createListTableColumns(); - $table->getColumn('id')->setAutoincrement(true); - - return $table; - } - - public function testListForeignKeysFromExistingDatabase() : void - { - $this->connection->exec(<< 'SET NULL', 'onDelete' => 'NO ACTION', 'deferrable' => false, 'deferred' => false] - ), - new Schema\ForeignKeyConstraint( - ['parent'], - 'user', - ['id'], - '1', - ['onUpdate' => 'NO ACTION', 'onDelete' => 'CASCADE', 'deferrable' => false, 'deferred' => false] - ), - new Schema\ForeignKeyConstraint( - ['page'], - 'page', - ['key'], - 'FK_1', - ['onUpdate' => 'NO ACTION', 'onDelete' => 'NO ACTION', 'deferrable' => true, 'deferred' => true] - ), - ]; - - self::assertEquals($expected, $this->schemaManager->listTableForeignKeys('user')); - } - - public function testColumnCollation() : void - { - $table = new Schema\Table('test_collation'); - $table->addColumn('id', 'integer'); - $table->addColumn('text', 'text'); - $table->addColumn('foo', 'text')->setPlatformOption('collation', 'BINARY'); - $table->addColumn('bar', 'text')->setPlatformOption('collation', 'NOCASE'); - $this->schemaManager->dropAndCreateTable($table); - - $columns = $this->schemaManager->listTableColumns('test_collation'); - - self::assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); - self::assertEquals('BINARY', $columns['text']->getPlatformOption('collation')); - self::assertEquals('BINARY', $columns['foo']->getPlatformOption('collation')); - self::assertEquals('NOCASE', $columns['bar']->getPlatformOption('collation')); - } - - public function testListTableWithBinary() : void - { - $tableName = 'test_binary_table'; - - $table = new Table($tableName); - $table->addColumn('id', 'integer'); - $table->addColumn('column_varbinary', 'binary', []); - $table->addColumn('column_binary', 'binary', ['fixed' => true]); - $table->setPrimaryKey(['id']); - - $this->schemaManager->createTable($table); - - $table = $this->schemaManager->listTableDetails($tableName); - - self::assertInstanceOf(BlobType::class, $table->getColumn('column_varbinary')->getType()); - self::assertFalse($table->getColumn('column_varbinary')->getFixed()); - - self::assertInstanceOf(BlobType::class, $table->getColumn('column_binary')->getType()); - self::assertFalse($table->getColumn('column_binary')->getFixed()); - } - - public function testNonDefaultPKOrder() : void - { - if (! extension_loaded('sqlite3')) { - $this->markTestSkipped('This test requires the SQLite3 extension.'); - } - - $version = SQLite3::version(); - if (version_compare($version['versionString'], '3.7.16', '<')) { - $this->markTestSkipped('This version of sqlite doesn\'t return the order of the Primary Key.'); - } - $this->connection->exec(<<schemaManager->listTableIndexes('non_default_pk_order'); - - self::assertCount(1, $tableIndexes); - - self::assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.'); - self::assertEquals(['other_id', 'id'], array_map('strtolower', $tableIndexes['primary']->getColumns())); - } - - /** - * @group DBAL-1779 - */ - public function testListTableColumnsWithWhitespacesInTypeDeclarations() : void - { - $sql = <<connection->exec($sql); - - $columns = $this->schemaManager->listTableColumns('dbal_1779'); - - self::assertCount(2, $columns); - - self::assertArrayHasKey('foo', $columns); - self::assertArrayHasKey('bar', $columns); - - self::assertSame(Type::getType(Types::STRING), $columns['foo']->getType()); - self::assertSame(Type::getType(Types::TEXT), $columns['bar']->getType()); - - self::assertSame(64, $columns['foo']->getLength()); - self::assertSame(100, $columns['bar']->getLength()); - } - - /** - * @dataProvider getDiffListIntegerAutoincrementTableColumnsData - * @group DBAL-924 - */ - public function testDiffListIntegerAutoincrementTableColumns(string $integerType, bool $unsigned, bool $expectedComparatorDiff) : void - { - $tableName = 'test_int_autoincrement_table'; - - $offlineTable = new Table($tableName); - $offlineTable->addColumn('id', $integerType, ['autoincrement' => true, 'unsigned' => $unsigned]); - $offlineTable->setPrimaryKey(['id']); - - $this->schemaManager->dropAndCreateTable($offlineTable); - - $onlineTable = $this->schemaManager->listTableDetails($tableName); - $comparator = new Schema\Comparator(); - $diff = $comparator->diffTable($offlineTable, $onlineTable); - - if ($expectedComparatorDiff) { - self::assertEmpty($this->schemaManager->getDatabasePlatform()->getAlterTableSQL($diff)); - } else { - self::assertFalse($diff); - } - } - - /** - * @return mixed[][] - */ - public static function getDiffListIntegerAutoincrementTableColumnsData() : iterable - { - return [ - ['smallint', false, true], - ['smallint', true, true], - ['integer', false, false], - ['integer', true, true], - ['bigint', false, true], - ['bigint', true, true], - ]; - } - - /** - * @group DBAL-2921 - */ - public function testPrimaryKeyNoAutoIncrement() : void - { - $table = new Schema\Table('test_pk_auto_increment'); - $table->addColumn('id', 'integer'); - $table->addColumn('text', 'text'); - $table->setPrimaryKey(['id']); - $this->schemaManager->dropAndCreateTable($table); - - $this->connection->insert('test_pk_auto_increment', ['text' => '1']); - - $this->connection->query('DELETE FROM test_pk_auto_increment'); - - $this->connection->insert('test_pk_auto_increment', ['text' => '2']); - - $query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = "2"'); - $query->execute(); - $lastUsedIdAfterDelete = (int) $query->fetchColumn(); - - // with an empty table, non autoincrement rowid is always 1 - $this->assertEquals(1, $lastUsedIdAfterDelete); - } -} diff --git a/tests/Functional/DBAL/StatementTest.php b/tests/Functional/DBAL/StatementTest.php deleted file mode 100644 index 593d244..0000000 --- a/tests/Functional/DBAL/StatementTest.php +++ /dev/null @@ -1,322 +0,0 @@ -addColumn('id', 'integer'); - $table->addColumn('name', 'text', ['notnull' => false]); - $this->connection->getSchemaManager()->dropAndCreateTable($table); - } - - public function testStatementIsReusableAfterClosingCursor() : void - { - if ($this->connection->getDriver() instanceof PDOOracleDriver) { - $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=77181'); - } - - $this->connection->insert('stmt_test', ['id' => 1]); - $this->connection->insert('stmt_test', ['id' => 2]); - - $stmt = $this->connection->prepare('SELECT id FROM stmt_test ORDER BY id'); - - $stmt->execute(); - - $id = $stmt->fetchColumn(); - self::assertEquals(1, $id); - - $stmt->closeCursor(); - - $stmt->execute(); - $id = $stmt->fetchColumn(); - self::assertEquals(1, $id); - $id = $stmt->fetchColumn(); - self::assertEquals(2, $id); - } - - public function testReuseStatementWithLongerResults() : void - { - if ($this->connection->getDriver() instanceof PDOOracleDriver) { - $this->markTestIncomplete('PDO_OCI doesn\'t support fetching blobs via PDOStatement::fetchAll()'); - } - - $sm = $this->connection->getSchemaManager(); - $table = new Table('stmt_longer_results'); - $table->addColumn('param', 'string'); - $table->addColumn('val', 'text'); - $sm->createTable($table); - - $row1 = [ - 'param' => 'param1', - 'val' => 'X', - ]; - $this->connection->insert('stmt_longer_results', $row1); - - $stmt = $this->connection->prepare('SELECT param, val FROM stmt_longer_results ORDER BY param'); - $stmt->execute(); - self::assertEquals([ - ['param1', 'X'], - ], $stmt->fetchAll(FetchMode::NUMERIC)); - - $row2 = [ - 'param' => 'param2', - 'val' => 'A bit longer value', - ]; - $this->connection->insert('stmt_longer_results', $row2); - - $stmt->execute(); - self::assertEquals([ - ['param1', 'X'], - ['param2', 'A bit longer value'], - ], $stmt->fetchAll(FetchMode::NUMERIC)); - } - - public function testFetchLongBlob() : void - { - if ($this->connection->getDriver() instanceof PDOOracleDriver) { - // inserting BLOBs as streams on Oracle requires Oracle-specific SQL syntax which is currently not supported - // see http://php.net/manual/en/pdo.lobs.php#example-1035 - $this->markTestSkipped('DBAL doesn\'t support storing LOBs represented as streams using PDO_OCI'); - } - - // make sure memory limit is large enough to not cause false positives, - // but is still not enough to store a LONGBLOB of the max possible size - $this->iniSet('memory_limit', '4G'); - - $sm = $this->connection->getSchemaManager(); - $table = new Table('stmt_long_blob'); - $table->addColumn('contents', 'blob', ['length' => 0xFFFFFFFF]); - $sm->createTable($table); - - $contents = base64_decode(<<connection->insert('stmt_long_blob', ['contents' => $contents], [ParameterType::LARGE_OBJECT]); - - $stmt = $this->connection->prepare('SELECT contents FROM stmt_long_blob'); - $stmt->execute(); - - $stream = Type::getType('blob') - ->convertToPHPValue( - $stmt->fetchColumn(), - $this->connection->getDatabasePlatform() - ); - - self::assertSame($contents, stream_get_contents($stream)); - } - - public function testIncompletelyFetchedStatementDoesNotBlockConnection() : void - { - $this->connection->insert('stmt_test', ['id' => 1]); - $this->connection->insert('stmt_test', ['id' => 2]); - - $stmt1 = $this->connection->prepare('SELECT id FROM stmt_test'); - $stmt1->execute(); - $stmt1->fetch(); - $stmt1->execute(); - // fetching only one record out of two - $stmt1->fetch(); - - $stmt2 = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); - $stmt2->execute([1]); - self::assertEquals(1, $stmt2->fetchColumn()); - } - - public function testReuseStatementAfterClosingCursor() : void - { - if ($this->connection->getDriver() instanceof PDOOracleDriver) { - $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=77181'); - } - - $this->connection->insert('stmt_test', ['id' => 1]); - $this->connection->insert('stmt_test', ['id' => 2]); - - $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); - - $stmt->execute([1]); - $id = $stmt->fetchColumn(); - self::assertEquals(1, $id); - - $stmt->closeCursor(); - - $stmt->execute([2]); - $id = $stmt->fetchColumn(); - self::assertEquals(2, $id); - } - - public function testReuseStatementWithParameterBoundByReference() : void - { - $this->connection->insert('stmt_test', ['id' => 1]); - $this->connection->insert('stmt_test', ['id' => 2]); - - $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); - $stmt->bindParam(1, $id); - - $id = 1; - $stmt->execute(); - self::assertEquals(1, $stmt->fetchColumn()); - - $id = 2; - $stmt->execute(); - self::assertEquals(2, $stmt->fetchColumn()); - } - - public function testReuseStatementWithReboundValue() : void - { - $this->connection->insert('stmt_test', ['id' => 1]); - $this->connection->insert('stmt_test', ['id' => 2]); - - $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); - - $stmt->bindValue(1, 1); - $stmt->execute(); - self::assertEquals(1, $stmt->fetchColumn()); - - $stmt->bindValue(1, 2); - $stmt->execute(); - self::assertEquals(2, $stmt->fetchColumn()); - } - - public function testReuseStatementWithReboundParam() : void - { - $this->connection->insert('stmt_test', ['id' => 1]); - $this->connection->insert('stmt_test', ['id' => 2]); - - $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); - - $x = 1; - $stmt->bindParam(1, $x); - $stmt->execute(); - self::assertEquals(1, $stmt->fetchColumn()); - - $y = 2; - $stmt->bindParam(1, $y); - $stmt->execute(); - self::assertEquals(2, $stmt->fetchColumn()); - } - - /** - * @param mixed $expected - * - * @dataProvider emptyFetchProvider - */ - public function testFetchFromNonExecutedStatement(callable $fetch, $expected) : void - { - $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); - - self::assertSame($expected, $fetch($stmt)); - } - - public function testCloseCursorOnNonExecutedStatement() : void - { - $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); - self::assertTrue($stmt->closeCursor()); - } - - /** - * @group DBAL-2637 - */ - public function testCloseCursorAfterCursorEnd() : void - { - $stmt = $this->connection->prepare('SELECT name FROM stmt_test'); - - $stmt->execute(); - $stmt->fetch(); - - self::assertTrue($stmt->closeCursor()); - } - - /** - * @param mixed $expected - * - * @dataProvider emptyFetchProvider - */ - public function testFetchFromNonExecutedStatementWithClosedCursor(callable $fetch, $expected) : void - { - $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); - $stmt->closeCursor(); - - self::assertSame($expected, $fetch($stmt)); - } - - /** - * @param mixed $expected - * - * @dataProvider emptyFetchProvider - */ - public function testFetchFromExecutedStatementWithClosedCursor(callable $fetch, $expected) : void - { - $this->connection->insert('stmt_test', ['id' => 1]); - - $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); - $stmt->execute(); - $stmt->closeCursor(); - - self::assertSame($expected, $fetch($stmt)); - } - - /** - * @return mixed[][] - */ - public static function emptyFetchProvider() : iterable - { - return [ - 'fetch' => [ - static function (Statement $stmt) { - return $stmt->fetch(); - }, - false, - ], - 'fetch-column' => [ - static function (Statement $stmt) { - return $stmt->fetchColumn(); - }, - false, - ], - 'fetch-all' => [ - static function (Statement $stmt) { - return $stmt->fetchAll(); - }, - [], - ], - ]; - } - - public function testFetchInColumnMode() : void - { - $platform = $this->connection->getDatabasePlatform(); - $query = $platform->getDummySelectSQL(); - $result = $this->connection->executeQuery($query)->fetch(FetchMode::COLUMN); - - self::assertEquals(1, $result); - } -} diff --git a/tests/Functional/DBAL/TableGeneratorTest.php b/tests/Functional/DBAL/TableGeneratorTest.php deleted file mode 100644 index 9fa636e..0000000 --- a/tests/Functional/DBAL/TableGeneratorTest.php +++ /dev/null @@ -1,62 +0,0 @@ -connection->getDatabasePlatform(); - if ($platform->getName() === 'sqlite') { - $this->markTestSkipped('TableGenerator does not work with SQLite'); - } - - try { - $schema = new Schema(); - $visitor = new TableGeneratorSchemaVisitor(); - $schema->visit($visitor); - - foreach ($schema->toSql($platform) as $sql) { - $this->connection->exec($sql); - } - } catch (Throwable $e) { - } - $this->generator = new TableGenerator($this->connection); - } - - public function testNextVal() : void - { - $id1 = $this->generator->nextValue('tbl1'); - $id2 = $this->generator->nextValue('tbl1'); - $id3 = $this->generator->nextValue('tbl2'); - - self::assertGreaterThan(0, $id1, 'First id has to be larger than 0'); - self::assertEquals($id1 + 1, $id2, 'Second id is one larger than first one.'); - self::assertEquals($id1, $id3, 'First ids from different tables are equal.'); - } - - public function testNextValNotAffectedByOuterTransactions() : void - { - $this->connection->beginTransaction(); - $id1 = $this->generator->nextValue('tbl1'); - $this->connection->rollBack(); - $id2 = $this->generator->nextValue('tbl1'); - - self::assertGreaterThan(0, $id1, 'First id has to be larger than 0'); - self::assertEquals($id1 + 1, $id2, 'Second id is one larger than first one.'); - } -} diff --git a/tests/Functional/DBAL/TemporaryTableTest.php b/tests/Functional/DBAL/TemporaryTableTest.php deleted file mode 100644 index a60fe0e..0000000 --- a/tests/Functional/DBAL/TemporaryTableTest.php +++ /dev/null @@ -1,108 +0,0 @@ -connection->exec($this->connection->getDatabasePlatform()->getDropTableSQL('nontemporary')); - } catch (Throwable $e) { - } - } - - protected function tearDown() : void - { - if ($this->connection) { - try { - $tempTable = $this->connection->getDatabasePlatform()->getTemporaryTableName('my_temporary'); - $this->connection->exec($this->connection->getDatabasePlatform()->getDropTemporaryTableSQL($tempTable)); - } catch (Throwable $e) { - } - } - - parent::tearDown(); - } - - /** - * @group DDC-1337 - */ - public function testDropTemporaryTableNotAutoCommitTransaction() : void - { - if ($this->connection->getDatabasePlatform()->getName() === 'sqlanywhere' || - $this->connection->getDatabasePlatform()->getName() === 'oracle') { - $this->markTestSkipped('Test does not work on Oracle and SQL Anywhere.'); - } - - $platform = $this->connection->getDatabasePlatform(); - $columnDefinitions = ['id' => ['type' => Type::getType('integer'), 'notnull' => true]]; - $tempTable = $platform->getTemporaryTableName('my_temporary'); - - $createTempTableSQL = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' - . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; - $this->connection->executeUpdate($createTempTableSQL); - - $table = new Table('nontemporary'); - $table->addColumn('id', 'integer'); - $table->setPrimaryKey(['id']); - - $this->connection->getSchemaManager()->createTable($table); - - $this->connection->beginTransaction(); - $this->connection->insert('nontemporary', ['id' => 1]); - $this->connection->exec($platform->getDropTemporaryTableSQL($tempTable)); - $this->connection->insert('nontemporary', ['id' => 2]); - - $this->connection->rollBack(); - - $rows = $this->connection->fetchAll('SELECT * FROM nontemporary'); - self::assertEquals([], $rows, 'In an event of an error this result has one row, because of an implicit commit.'); - } - - /** - * @group DDC-1337 - */ - public function testCreateTemporaryTableNotAutoCommitTransaction() : void - { - if ($this->connection->getDatabasePlatform()->getName() === 'sqlanywhere' || - $this->connection->getDatabasePlatform()->getName() === 'oracle') { - $this->markTestSkipped('Test does not work on Oracle and SQL Anywhere.'); - } - - $platform = $this->connection->getDatabasePlatform(); - $columnDefinitions = ['id' => ['type' => Type::getType('integer'), 'notnull' => true]]; - $tempTable = $platform->getTemporaryTableName('my_temporary'); - - $createTempTableSQL = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' - . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; - - $table = new Table('nontemporary'); - $table->addColumn('id', 'integer'); - $table->setPrimaryKey(['id']); - - $this->connection->getSchemaManager()->createTable($table); - - $this->connection->beginTransaction(); - $this->connection->insert('nontemporary', ['id' => 1]); - - $this->connection->exec($createTempTableSQL); - $this->connection->insert('nontemporary', ['id' => 2]); - - $this->connection->rollBack(); - - try { - $this->connection->exec($platform->getDropTemporaryTableSQL($tempTable)); - } catch (Throwable $e) { - } - - $rows = $this->connection->fetchAll('SELECT * FROM nontemporary'); - self::assertEquals([], $rows, 'In an event of an error this result has one row, because of an implicit commit.'); - } -} diff --git a/tests/Functional/DBAL/Ticket/DBAL168Test.php b/tests/Functional/DBAL/Ticket/DBAL168Test.php deleted file mode 100644 index 91dc785..0000000 --- a/tests/Functional/DBAL/Ticket/DBAL168Test.php +++ /dev/null @@ -1,30 +0,0 @@ -connection->getDatabasePlatform()->getName() !== 'postgresql') { - $this->markTestSkipped('PostgreSQL only test'); - } - - $table = new Table('domains'); - $table->addColumn('id', 'integer'); - $table->addColumn('parent_id', 'integer'); - $table->setPrimaryKey(['id']); - $table->addForeignKeyConstraint('domains', ['parent_id'], ['id']); - - $this->connection->getSchemaManager()->createTable($table); - $table = $this->connection->getSchemaManager()->listTableDetails('domains'); - - self::assertEquals('domains', $table->getName()); - } -} diff --git a/tests/Functional/DBAL/Ticket/DBAL202Test.php b/tests/Functional/DBAL/Ticket/DBAL202Test.php deleted file mode 100644 index 9cf5200..0000000 --- a/tests/Functional/DBAL/Ticket/DBAL202Test.php +++ /dev/null @@ -1,51 +0,0 @@ -connection->getDatabasePlatform()->getName() !== 'oracle') { - $this->markTestSkipped('OCI8 only test'); - } - - if ($this->connection->getSchemaManager()->tablesExist('DBAL202')) { - $this->connection->exec('DELETE FROM DBAL202'); - } else { - $table = new Table('DBAL202'); - $table->addColumn('id', 'integer'); - $table->setPrimaryKey(['id']); - - $this->connection->getSchemaManager()->createTable($table); - } - } - - public function testStatementRollback() : void - { - $stmt = $this->connection->prepare('INSERT INTO DBAL202 VALUES (8)'); - $this->connection->beginTransaction(); - $stmt->execute(); - $this->connection->rollBack(); - - self::assertEquals(0, $this->connection->query('SELECT COUNT(1) FROM DBAL202')->fetchColumn()); - } - - public function testStatementCommit() : void - { - $stmt = $this->connection->prepare('INSERT INTO DBAL202 VALUES (8)'); - $this->connection->beginTransaction(); - $stmt->execute(); - $this->connection->commit(); - - self::assertEquals(1, $this->connection->query('SELECT COUNT(1) FROM DBAL202')->fetchColumn()); - } -} diff --git a/tests/Functional/DBAL/Ticket/DBAL421Test.php b/tests/Functional/DBAL/Ticket/DBAL421Test.php deleted file mode 100644 index 2a8d50e..0000000 --- a/tests/Functional/DBAL/Ticket/DBAL421Test.php +++ /dev/null @@ -1,56 +0,0 @@ -connection->getDatabasePlatform()->getName(); - if (in_array($platform, ['mysql', 'sqlite'])) { - return; - } - - $this->markTestSkipped('Currently restricted to MySQL and SQLite.'); - } - - public function testGuidShouldMatchPattern() : void - { - $guid = $this->connection->query($this->getSelectGuidSql())->fetchColumn(); - $pattern = '/[0-9A-F]{8}\-[0-9A-F]{4}\-[0-9A-F]{4}\-[8-9A-B][0-9A-F]{3}\-[0-9A-F]{12}/i'; - self::assertEquals(1, preg_match($pattern, $guid), 'GUID does not match pattern'); - } - - /** - * This test does (of course) not proof that all generated GUIDs are - * random, it should however provide some basic confidence. - */ - public function testGuidShouldBeRandom() : void - { - $statement = $this->connection->prepare($this->getSelectGuidSql()); - $guids = []; - - for ($i = 0; $i < 99; $i++) { - $statement->execute(); - $guid = $statement->fetchColumn(); - self::assertNotContains($guid, $guids, 'Duplicate GUID detected'); - $guids[] = $guid; - } - - $statement->closeCursor(); - } - - private function getSelectGuidSql() : string - { - return 'SELECT ' . $this->connection->getDatabasePlatform()->getGuidExpression(); - } -} diff --git a/tests/Functional/DBAL/Ticket/DBAL461Test.php b/tests/Functional/DBAL/Ticket/DBAL461Test.php deleted file mode 100644 index 165af4d..0000000 --- a/tests/Functional/DBAL/Ticket/DBAL461Test.php +++ /dev/null @@ -1,41 +0,0 @@ -createMock(Connection::class); - $platform = $this->getMockForAbstractClass(AbstractPlatform::class); - $platform->registerDoctrineTypeMapping('numeric', 'decimal'); - - $schemaManager = new SQLServerSchemaManager($conn, $platform); - - $reflectionMethod = new ReflectionMethod($schemaManager, '_getPortableTableColumnDefinition'); - $reflectionMethod->setAccessible(true); - $column = $reflectionMethod->invoke($schemaManager, [ - 'type' => 'numeric(18,0)', - 'length' => null, - 'default' => null, - 'notnull' => false, - 'scale' => 18, - 'precision' => 0, - 'autoincrement' => false, - 'collation' => 'foo', - 'comment' => null, - ]); - - $this->assertInstanceOf(DecimalType::class, $column->getType()); - } -} diff --git a/tests/Functional/DBAL/Ticket/DBAL510Test.php b/tests/Functional/DBAL/Ticket/DBAL510Test.php deleted file mode 100644 index 74e297f..0000000 --- a/tests/Functional/DBAL/Ticket/DBAL510Test.php +++ /dev/null @@ -1,40 +0,0 @@ -connection->getDatabasePlatform()->getName() === 'postgresql') { - return; - } - - $this->markTestSkipped('PostgreSQL Only test'); - } - - public function testSearchPathSchemaChanges() : void - { - $table = new Table('dbal510tbl'); - $table->addColumn('id', 'integer'); - $table->setPrimaryKey(['id']); - - $this->connection->getSchemaManager()->createTable($table); - - $onlineTable = $this->connection->getSchemaManager()->listTableDetails('dbal510tbl'); - - $comparator = new Comparator(); - $diff = $comparator->diffTable($onlineTable, $table); - - self::assertFalse($diff); - } -} diff --git a/tests/Functional/DBAL/Ticket/DBAL630Test.php b/tests/Functional/DBAL/Ticket/DBAL630Test.php deleted file mode 100644 index 635687c..0000000 --- a/tests/Functional/DBAL/Ticket/DBAL630Test.php +++ /dev/null @@ -1,172 +0,0 @@ -connection->getDatabasePlatform()->getName(); - - if (! in_array($platform, ['postgresql'])) { - $this->markTestSkipped('Currently restricted to PostgreSQL'); - } - - try { - $this->connection->exec('CREATE TABLE dbal630 (id SERIAL, bool_col BOOLEAN NOT NULL);'); - $this->connection->exec('CREATE TABLE dbal630_allow_nulls (id SERIAL, bool_col BOOLEAN);'); - } catch (DBALException $e) { - } - $this->running = true; - } - - protected function tearDown() : void - { - if ($this->running) { - $this->connection->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); - } - - parent::tearDown(); - } - - public function testBooleanConversionSqlLiteral() : void - { - $this->connection->executeUpdate('INSERT INTO dbal630 (bool_col) VALUES(false)'); - $id = $this->connection->lastInsertId('dbal630_id_seq'); - self::assertNotEmpty($id); - - $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', [$id]); - - self::assertFalse($row['bool_col']); - } - - public function testBooleanConversionBoolParamRealPrepares() : void - { - $this->connection->executeUpdate( - 'INSERT INTO dbal630 (bool_col) VALUES(?)', - ['false'], - [ParameterType::BOOLEAN] - ); - $id = $this->connection->lastInsertId('dbal630_id_seq'); - self::assertNotEmpty($id); - - $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', [$id]); - - self::assertFalse($row['bool_col']); - } - - public function testBooleanConversionBoolParamEmulatedPrepares() : void - { - $this->connection->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); - - $platform = $this->connection->getDatabasePlatform(); - - $stmt = $this->connection->prepare('INSERT INTO dbal630 (bool_col) VALUES(?)'); - $stmt->bindValue(1, $platform->convertBooleansToDatabaseValue('false'), ParameterType::BOOLEAN); - $stmt->execute(); - - $id = $this->connection->lastInsertId('dbal630_id_seq'); - - self::assertNotEmpty($id); - - $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', [$id]); - - self::assertFalse($row['bool_col']); - } - - /** - * @dataProvider booleanTypeConversionWithoutPdoTypeProvider - */ - public function testBooleanConversionNullParamEmulatedPrepares( - ?bool $statementValue, - ?bool $databaseConvertedValue - ) : void { - $this->connection->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); - - $platform = $this->connection->getDatabasePlatform(); - - $stmt = $this->connection->prepare('INSERT INTO dbal630_allow_nulls (bool_col) VALUES(?)'); - $stmt->bindValue(1, $platform->convertBooleansToDatabaseValue($statementValue)); - $stmt->execute(); - - $id = $this->connection->lastInsertId('dbal630_allow_nulls_id_seq'); - - self::assertNotEmpty($id); - - $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630_allow_nulls WHERE id = ?', [$id]); - - self::assertSame($databaseConvertedValue, $row['bool_col']); - } - - /** - * @dataProvider booleanTypeConversionUsingBooleanTypeProvider - */ - public function testBooleanConversionNullParamEmulatedPreparesWithBooleanTypeInBindValue( - ?bool $statementValue, - bool $databaseConvertedValue - ) : void { - $this->connection->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); - - $platform = $this->connection->getDatabasePlatform(); - - $stmt = $this->connection->prepare('INSERT INTO dbal630_allow_nulls (bool_col) VALUES(?)'); - $stmt->bindValue( - 1, - $platform->convertBooleansToDatabaseValue($statementValue), - ParameterType::BOOLEAN - ); - $stmt->execute(); - - $id = $this->connection->lastInsertId('dbal630_allow_nulls_id_seq'); - - self::assertNotEmpty($id); - - $row = $this->connection->fetchAssoc('SELECT bool_col FROM dbal630_allow_nulls WHERE id = ?', [$id]); - - self::assertSame($databaseConvertedValue, $row['bool_col']); - } - - /** - * Boolean conversion mapping provider - * - * @return mixed[][] - */ - public static function booleanTypeConversionUsingBooleanTypeProvider() : iterable - { - return [ - // statement value, database converted value result - [true, true], - [false, false], - [null, false], - ]; - } - - /** - * Boolean conversion mapping provider - * - * @return mixed[][] - */ - public static function booleanTypeConversionWithoutPdoTypeProvider() : iterable - { - return [ - // statement value, database converted value result - [true, true], - [false, false], - [null, null], - ]; - } -} diff --git a/tests/Functional/DBAL/Ticket/DBAL752Test.php b/tests/Functional/DBAL/Ticket/DBAL752Test.php deleted file mode 100644 index 8f0b16c..0000000 --- a/tests/Functional/DBAL/Ticket/DBAL752Test.php +++ /dev/null @@ -1,65 +0,0 @@ -connection->getDatabasePlatform()->getName(); - - if (in_array($platform, ['sqlite'])) { - return; - } - - $this->markTestSkipped('Related to SQLite only'); - } - - public function testUnsignedIntegerDetection() : void - { - $this->connection->exec(<<connection->getSchemaManager(); - - $fetchedTable = $schemaManager->listTableDetails('dbal752_unsigneds'); - - self::assertEquals('smallint', $fetchedTable->getColumn('small')->getType()->getName()); - self::assertEquals('smallint', $fetchedTable->getColumn('small_unsigned')->getType()->getName()); - self::assertEquals('integer', $fetchedTable->getColumn('medium')->getType()->getName()); - self::assertEquals('integer', $fetchedTable->getColumn('medium_unsigned')->getType()->getName()); - self::assertEquals('integer', $fetchedTable->getColumn('integer')->getType()->getName()); - self::assertEquals('integer', $fetchedTable->getColumn('integer_unsigned')->getType()->getName()); - self::assertEquals('bigint', $fetchedTable->getColumn('big')->getType()->getName()); - self::assertEquals('bigint', $fetchedTable->getColumn('big_unsigned')->getType()->getName()); - - self::assertTrue($fetchedTable->getColumn('small_unsigned')->getUnsigned()); - self::assertTrue($fetchedTable->getColumn('medium_unsigned')->getUnsigned()); - self::assertTrue($fetchedTable->getColumn('integer_unsigned')->getUnsigned()); - self::assertTrue($fetchedTable->getColumn('big_unsigned')->getUnsigned()); - - self::assertFalse($fetchedTable->getColumn('small')->getUnsigned()); - self::assertFalse($fetchedTable->getColumn('medium')->getUnsigned()); - self::assertFalse($fetchedTable->getColumn('integer')->getUnsigned()); - self::assertFalse($fetchedTable->getColumn('big')->getUnsigned()); - } -} diff --git a/tests/Functional/DBAL/TransactionTest.php b/tests/Functional/DBAL/TransactionTest.php deleted file mode 100644 index 7ec7375..0000000 --- a/tests/Functional/DBAL/TransactionTest.php +++ /dev/null @@ -1,39 +0,0 @@ -connection->getDatabasePlatform() instanceof MySqlPlatform) { - return; - } - - $this->markTestSkipped('Restricted to MySQL.'); - } - - protected function tearDown() : void - { - $this->resetSharedConn(); - - parent::tearDown(); - } - - public function testCommitFalse() : void - { - $this->connection->query('SET SESSION wait_timeout=1'); - - $this->assertTrue($this->connection->beginTransaction()); - - sleep(2); // during the sleep mysql will close the connection - - $this->assertFalse(@$this->connection->commit()); // we will ignore `MySQL server has gone away` warnings - } -} diff --git a/tests/Functional/DBAL/TypeConversionTest.php b/tests/Functional/DBAL/TypeConversionTest.php deleted file mode 100644 index 0352bd8..0000000 --- a/tests/Functional/DBAL/TypeConversionTest.php +++ /dev/null @@ -1,251 +0,0 @@ -addColumn('id', 'integer', ['notnull' => false]); - $table->addColumn('test_string', 'string', ['notnull' => false]); - $table->addColumn('test_boolean', 'boolean', ['notnull' => false]); - $table->addColumn('test_bigint', 'bigint', ['notnull' => false]); - $table->addColumn('test_smallint', 'bigint', ['notnull' => false]); - $table->addColumn('test_datetime', 'datetime', ['notnull' => false]); - $table->addColumn('test_datetimetz', 'datetimetz', ['notnull' => false]); - $table->addColumn('test_date', 'date', ['notnull' => false]); - $table->addColumn('test_time', 'time', ['notnull' => false]); - $table->addColumn('test_text', 'text', ['notnull' => false]); - $table->addColumn('test_array', 'array', ['notnull' => false]); - $table->addColumn('test_json_array', 'json_array', ['notnull' => false]); - $table->addColumn('test_object', 'object', ['notnull' => false]); - $table->addColumn('test_float', 'float', ['notnull' => false]); - $table->addColumn('test_decimal', 'decimal', ['notnull' => false, 'scale' => 2, 'precision' => 10]); - $table->setPrimaryKey(['id']); - - $this->connection - ->getSchemaManager() - ->dropAndCreateTable($table); - } - - /** - * @param mixed $originalValue - * - * @dataProvider booleanProvider - */ - public function testIdempotentConversionToBoolean(string $type, $originalValue) : void - { - $dbValue = $this->processValue($type, $originalValue); - - self::assertIsBool($dbValue); - self::assertEquals($originalValue, $dbValue); - } - - /** - * @return mixed[][] - */ - public static function booleanProvider() : iterable - { - return [ - 'true' => ['boolean', true], - 'false' => ['boolean', false], - ]; - } - - /** - * @param mixed $originalValue - * - * @dataProvider integerProvider - */ - public function testIdempotentConversionToInteger(string $type, $originalValue) : void - { - $dbValue = $this->processValue($type, $originalValue); - - self::assertIsInt($dbValue); - self::assertEquals($originalValue, $dbValue); - } - - /** - * @return mixed[][] - */ - public static function integerProvider() : iterable - { - return [ - 'smallint' => ['smallint', 123], - ]; - } - - /** - * @param mixed $originalValue - * - * @dataProvider floatProvider - */ - public function testIdempotentConversionToFloat(string $type, $originalValue) : void - { - $dbValue = $this->processValue($type, $originalValue); - - self::assertIsFloat($dbValue); - self::assertEquals($originalValue, $dbValue); - } - - /** - * @return mixed[][] - */ - public static function floatProvider() : iterable - { - return [ - 'float' => ['float', 1.5], - ]; - } - - /** - * @param mixed $originalValue - * - * @dataProvider toStringProvider - */ - public function testIdempotentConversionToString(string $type, $originalValue) : void - { - if ($type === 'text' && $this->connection->getDriver() instanceof PDOOracleDriver) { - // inserting BLOBs as streams on Oracle requires Oracle-specific SQL syntax which is currently not supported - // see http://php.net/manual/en/pdo.lobs.php#example-1035 - $this->markTestSkipped('DBAL doesn\'t support storing LOBs represented as streams using PDO_OCI'); - } - - $dbValue = $this->processValue($type, $originalValue); - - self::assertIsString($dbValue); - self::assertEquals($originalValue, $dbValue); - } - - /** - * @return mixed[][] - */ - public static function toStringProvider() : iterable - { - return [ - 'string' => ['string', 'ABCDEFGabcdefg'], - 'bigint' => ['bigint', 12345678], - 'text' => ['text', str_repeat('foo ', 1000)], - 'decimal' => ['decimal', 1.55], - ]; - } - - /** - * @param mixed $originalValue - * - * @dataProvider toArrayProvider - */ - public function testIdempotentConversionToArray(string $type, $originalValue) : void - { - $dbValue = $this->processValue($type, $originalValue); - - self::assertIsArray($dbValue); - self::assertEquals($originalValue, $dbValue); - } - - /** - * @return mixed[][] - */ - public static function toArrayProvider() : iterable - { - return [ - 'array' => ['array', ['foo' => 'bar']], - 'json_array' => ['json_array', ['foo' => 'bar']], - ]; - } - - /** - * @param mixed $originalValue - * - * @dataProvider toObjectProvider - */ - public function testIdempotentConversionToObject(string $type, $originalValue) : void - { - $dbValue = $this->processValue($type, $originalValue); - - self::assertIsObject($dbValue); - self::assertEquals($originalValue, $dbValue); - } - - /** - * @return mixed[][] - */ - public static function toObjectProvider() : iterable - { - $obj = new stdClass(); - $obj->foo = 'bar'; - $obj->bar = 'baz'; - - return [ - 'object' => ['object', $obj], - ]; - } - - /** - * @dataProvider toDateTimeProvider - */ - public function testIdempotentConversionToDateTime(string $type, DateTime $originalValue) : void - { - $dbValue = $this->processValue($type, $originalValue); - - self::assertInstanceOf(DateTime::class, $dbValue); - - if ($type === 'datetimetz') { - return; - } - - self::assertEquals($originalValue, $dbValue); - self::assertEquals( - $originalValue->getTimezone(), - $dbValue->getTimezone() - ); - } - - /** - * @return mixed[][] - */ - public static function toDateTimeProvider() : iterable - { - return [ - 'datetime' => ['datetime', new DateTime('2010-04-05 10:10:10')], - 'datetimetz' => ['datetimetz', new DateTime('2010-04-05 10:10:10')], - 'date' => ['date', new DateTime('2010-04-05')], - 'time' => ['time', new DateTime('1970-01-01 10:10:10')], - ]; - } - - /** - * @param mixed $originalValue - * - * @return mixed - */ - private function processValue(string $type, $originalValue) - { - $columnName = 'test_' . $type; - $typeInstance = Type::getType($type); - $insertionValue = $typeInstance->convertToDatabaseValue($originalValue, $this->connection->getDatabasePlatform()); - - $this->connection->insert('type_conversion', ['id' => ++self::$typeCounter, $columnName => $insertionValue]); - - $sql = 'SELECT ' . $columnName . ' FROM type_conversion WHERE id = ' . self::$typeCounter; - - return $typeInstance->convertToPHPValue( - $this->connection->fetchColumn($sql), - $this->connection->getDatabasePlatform() - ); - } -} diff --git a/tests/Functional/DBAL/Types/BinaryTest.php b/tests/Functional/DBAL/Types/BinaryTest.php deleted file mode 100644 index 783d4ec..0000000 --- a/tests/Functional/DBAL/Types/BinaryTest.php +++ /dev/null @@ -1,95 +0,0 @@ -connection->getDriver() instanceof PDOOracleDriver) { - $this->markTestSkipped('PDO_OCI doesn\'t support binding binary values'); - } - - $table = new Table('binary_table'); - $table->addColumn('id', 'binary', [ - 'length' => 16, - 'fixed' => true, - ]); - $table->addColumn('val', 'binary', ['length' => 64]); - $table->setPrimaryKey(['id']); - - $sm = $this->connection->getSchemaManager(); - $sm->dropAndCreateTable($table); - } - - public function testInsertAndSelect() : void - { - $id1 = random_bytes(16); - $id2 = random_bytes(16); - - $value1 = random_bytes(64); - $value2 = random_bytes(64); - - /** @see https://bugs.php.net/bug.php?id=76322 */ - if ($this->connection->getDriver() instanceof DB2Driver) { - $value1 = str_replace("\x00", "\xFF", $value1); - $value2 = str_replace("\x00", "\xFF", $value2); - } - - $this->insert($id1, $value1); - $this->insert($id2, $value2); - - $this->assertSame($value1, $this->select($id1)); - $this->assertSame($value2, $this->select($id2)); - } - - private function insert(string $id, string $value) : void - { - $result = $this->connection->insert('binary_table', [ - 'id' => $id, - 'val' => $value, - ], [ - ParameterType::BINARY, - ParameterType::BINARY, - ]); - - self::assertSame(1, $result); - } - - /** - * @return mixed - */ - private function select(string $id) - { - $value = $this->connection->fetchColumn( - 'SELECT val FROM binary_table WHERE id = ?', - [$id], - 0, - [ParameterType::BINARY] - ); - - // Currently, `BinaryType` mistakenly converts string values fetched from the DB to a stream. - // It should be the opposite. Streams should be used to represent large objects, not binary - // strings. The confusion comes from the PostgreSQL's type system where binary strings and - // large objects are represented by the same BYTEA type - if (is_resource($value)) { - $value = stream_get_contents($value); - } - - return $value; - } -} diff --git a/tests/Functional/DBAL/WriteTest.php b/tests/Functional/DBAL/WriteTest.php deleted file mode 100644 index 6589a72..0000000 --- a/tests/Functional/DBAL/WriteTest.php +++ /dev/null @@ -1,359 +0,0 @@ -addColumn('id', 'integer', ['autoincrement' => true]); - $table->addColumn('test_int', 'integer'); - $table->addColumn('test_string', 'string', ['notnull' => false]); - $table->setPrimaryKey(['id']); - - $this->connection->getSchemaManager()->createTable($table); - } catch (Throwable $e) { - } - $this->connection->executeUpdate('DELETE FROM write_table'); - } - - /** - * @group DBAL-80 - */ - public function testExecuteUpdateFirstTypeIsNull() : void - { - $sql = 'INSERT INTO write_table (test_string, test_int) VALUES (?, ?)'; - $this->connection->executeUpdate($sql, ['text', 1111], [null, ParameterType::INTEGER]); - - $sql = 'SELECT * FROM write_table WHERE test_string = ? AND test_int = ?'; - self::assertTrue((bool) $this->connection->fetchColumn($sql, ['text', 1111])); - } - - public function testExecuteUpdate() : void - { - $sql = 'INSERT INTO write_table (test_int) VALUES ( ' . $this->connection->quote(1) . ')'; - $affected = $this->connection->executeUpdate($sql); - - self::assertEquals(1, $affected, 'executeUpdate() should return the number of affected rows!'); - } - - public function testExecuteUpdateWithTypes() : void - { - $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; - $affected = $this->connection->executeUpdate( - $sql, - [1, 'foo'], - [ParameterType::INTEGER, ParameterType::STRING] - ); - - self::assertEquals(1, $affected, 'executeUpdate() should return the number of affected rows!'); - } - - public function testPrepareRowCountReturnsAffectedRows() : void - { - $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; - $stmt = $this->connection->prepare($sql); - - $stmt->bindValue(1, 1); - $stmt->bindValue(2, 'foo'); - $stmt->execute(); - - self::assertEquals(1, $stmt->rowCount()); - } - - public function testPrepareWithPdoTypes() : void - { - $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; - $stmt = $this->connection->prepare($sql); - - $stmt->bindValue(1, 1, ParameterType::INTEGER); - $stmt->bindValue(2, 'foo', ParameterType::STRING); - $stmt->execute(); - - self::assertEquals(1, $stmt->rowCount()); - } - - public function testPrepareWithDbalTypes() : void - { - $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; - $stmt = $this->connection->prepare($sql); - - $stmt->bindValue(1, 1, Type::getType('integer')); - $stmt->bindValue(2, 'foo', Type::getType('string')); - $stmt->execute(); - - self::assertEquals(1, $stmt->rowCount()); - } - - public function testPrepareWithDbalTypeNames() : void - { - $sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)'; - $stmt = $this->connection->prepare($sql); - - $stmt->bindValue(1, 1, 'integer'); - $stmt->bindValue(2, 'foo', 'string'); - $stmt->execute(); - - self::assertEquals(1, $stmt->rowCount()); - } - - public function insertRows() : void - { - self::assertEquals(1, $this->connection->insert('write_table', ['test_int' => 1, 'test_string' => 'foo'])); - self::assertEquals(1, $this->connection->insert('write_table', ['test_int' => 2, 'test_string' => 'bar'])); - } - - public function testInsert() : void - { - $this->insertRows(); - } - - public function testDelete() : void - { - $this->insertRows(); - - self::assertEquals(1, $this->connection->delete('write_table', ['test_int' => 2])); - self::assertCount(1, $this->connection->fetchAll('SELECT * FROM write_table')); - - self::assertEquals(1, $this->connection->delete('write_table', ['test_int' => 1])); - self::assertCount(0, $this->connection->fetchAll('SELECT * FROM write_table')); - } - - public function testUpdate() : void - { - $this->insertRows(); - - self::assertEquals(1, $this->connection->update('write_table', ['test_string' => 'bar'], ['test_string' => 'foo'])); - self::assertEquals(2, $this->connection->update('write_table', ['test_string' => 'baz'], ['test_string' => 'bar'])); - self::assertEquals(0, $this->connection->update('write_table', ['test_string' => 'baz'], ['test_string' => 'bar'])); - } - - public function testLastInsertId() : void - { - if (! $this->connection->getDatabasePlatform()->prefersIdentityColumns()) { - $this->markTestSkipped('Test only works on platforms with identity columns.'); - } - - self::assertEquals(1, $this->connection->insert('write_table', ['test_int' => 2, 'test_string' => 'bar'])); - $num = $this->lastInsertId(); - - self::assertNotNull($num, 'LastInsertId() should not be null.'); - self::assertGreaterThan(0, $num, 'LastInsertId() should be non-negative number.'); - } - - public function testLastInsertIdSequence() : void - { - if (! $this->connection->getDatabasePlatform()->supportsSequences()) { - $this->markTestSkipped('Test only works on platforms with sequences.'); - } - - $sequence = new Sequence('write_table_id_seq'); - try { - $this->connection->getSchemaManager()->createSequence($sequence); - } catch (Throwable $e) { - } - - $sequences = $this->connection->getSchemaManager()->listSequences(); - self::assertCount(1, array_filter($sequences, static function ($sequence) { - return strtolower($sequence->getName()) === 'write_table_id_seq'; - })); - - $stmt = $this->connection->query($this->connection->getDatabasePlatform()->getSequenceNextValSQL('write_table_id_seq')); - $nextSequenceVal = $stmt->fetchColumn(); - - $lastInsertId = $this->lastInsertId('write_table_id_seq'); - - self::assertGreaterThan(0, $lastInsertId); - self::assertEquals($nextSequenceVal, $lastInsertId); - } - - public function testLastInsertIdNoSequenceGiven() : void - { - if (! $this->connection->getDatabasePlatform()->supportsSequences() || $this->connection->getDatabasePlatform()->supportsIdentityColumns()) { - $this->markTestSkipped("Test only works consistently on platforms that support sequences and don't support identity columns."); - } - - self::assertFalse($this->lastInsertId()); - } - - /** - * @group DBAL-445 - */ - public function testInsertWithKeyValueTypes() : void - { - $testString = new DateTime('2013-04-14 10:10:10'); - - $this->connection->insert( - 'write_table', - ['test_int' => '30', 'test_string' => $testString], - ['test_string' => 'datetime', 'test_int' => 'integer'] - ); - - $data = $this->connection->fetchColumn('SELECT test_string FROM write_table WHERE test_int = 30'); - - self::assertEquals($testString->format($this->connection->getDatabasePlatform()->getDateTimeFormatString()), $data); - } - - /** - * @group DBAL-445 - */ - public function testUpdateWithKeyValueTypes() : void - { - $testString = new DateTime('2013-04-14 10:10:10'); - - $this->connection->insert( - 'write_table', - ['test_int' => '30', 'test_string' => $testString], - ['test_string' => 'datetime', 'test_int' => 'integer'] - ); - - $testString = new DateTime('2013-04-15 10:10:10'); - - $this->connection->update( - 'write_table', - ['test_string' => $testString], - ['test_int' => '30'], - ['test_string' => 'datetime', 'test_int' => 'integer'] - ); - - $data = $this->connection->fetchColumn('SELECT test_string FROM write_table WHERE test_int = 30'); - - self::assertEquals($testString->format($this->connection->getDatabasePlatform()->getDateTimeFormatString()), $data); - } - - /** - * @group DBAL-445 - */ - public function testDeleteWithKeyValueTypes() : void - { - $val = new DateTime('2013-04-14 10:10:10'); - $this->connection->insert( - 'write_table', - ['test_int' => '30', 'test_string' => $val], - ['test_string' => 'datetime', 'test_int' => 'integer'] - ); - - $this->connection->delete('write_table', ['test_int' => 30, 'test_string' => $val], ['test_string' => 'datetime', 'test_int' => 'integer']); - - $data = $this->connection->fetchColumn('SELECT test_string FROM write_table WHERE test_int = 30'); - - self::assertFalse($data); - } - - public function testEmptyIdentityInsert() : void - { - $platform = $this->connection->getDatabasePlatform(); - - if (! ($platform->supportsIdentityColumns() || $platform->usesSequenceEmulatedIdentityColumns())) { - $this->markTestSkipped( - 'Test only works on platforms with identity columns or sequence emulated identity columns.' - ); - } - - $table = new Table('test_empty_identity'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['id']); - - try { - $this->connection->getSchemaManager()->dropTable($table->getQuotedName($platform)); - } catch (Throwable $e) { - } - - foreach ($platform->getCreateTableSQL($table) as $sql) { - $this->connection->exec($sql); - } - - $seqName = $platform->usesSequenceEmulatedIdentityColumns() - ? $platform->getIdentitySequenceName('test_empty_identity', 'id') - : null; - - $sql = $platform->getEmptyIdentityInsertSQL('test_empty_identity', 'id'); - - $this->connection->exec($sql); - - $firstId = $this->lastInsertId($seqName); - - $this->connection->exec($sql); - - $secondId = $this->lastInsertId($seqName); - - self::assertGreaterThan($firstId, $secondId); - } - - /** - * @group DBAL-2688 - */ - public function testUpdateWhereIsNull() : void - { - $this->connection->insert( - 'write_table', - ['test_int' => '30', 'test_string' => null], - ['test_string' => 'string', 'test_int' => 'integer'] - ); - - $data = $this->connection->fetchAll('SELECT * FROM write_table WHERE test_int = 30'); - - self::assertCount(1, $data); - - $this->connection->update('write_table', ['test_int' => 10], ['test_string' => null], ['test_string' => 'string', 'test_int' => 'integer']); - - $data = $this->connection->fetchAll('SELECT * FROM write_table WHERE test_int = 30'); - - self::assertCount(0, $data); - } - - public function testDeleteWhereIsNull() : void - { - $this->connection->insert( - 'write_table', - ['test_int' => '30', 'test_string' => null], - ['test_string' => 'string', 'test_int' => 'integer'] - ); - - $data = $this->connection->fetchAll('SELECT * FROM write_table WHERE test_int = 30'); - - self::assertCount(1, $data); - - $this->connection->delete('write_table', ['test_string' => null], ['test_string' => 'string']); - - $data = $this->connection->fetchAll('SELECT * FROM write_table WHERE test_int = 30'); - - self::assertCount(0, $data); - } - - /** - * Returns the ID of the last inserted row or skips the test if the currently used driver - * doesn't support this feature - * - * @return string|false - * - * @throws DriverException - */ - private function lastInsertId(?string $name = null) - { - try { - return $this->connection->lastInsertId($name); - } catch (DriverException $e) { - if ($e->getCode() === 'IM001') { - $this->markTestSkipped($e->getMessage()); - } - - throw $e; - } - } -} diff --git a/tests/Functional/DbalFunctionalTestCase.php b/tests/Functional/DbalFunctionalTestCase.php deleted file mode 100644 index e81c82c..0000000 --- a/tests/Functional/DbalFunctionalTestCase.php +++ /dev/null @@ -1,113 +0,0 @@ -close(); - self::$sharedConnection = null; - } - - protected function setUp() : void - { - parent::setUp(); - - if (! isset(self::$sharedConnection)) { - self::$sharedConnection = TestUtil::getConnection(); - } - $this->connection = self::$sharedConnection; - - $this->sqlLoggerStack = new DebugStack(); - $this->connection->getConfiguration()->setSQLLogger($this->sqlLoggerStack); - } - - protected function tearDown() : void - { - while ($this->connection->isTransactionActive()) { - $this->connection->rollBack(); - } - } - - protected function onNotSuccessfulTest(Throwable $t) : void - { - if ($t instanceof AssertionFailedError) { - throw $t; - } - - if (isset($this->sqlLoggerStack->queries) && count($this->sqlLoggerStack->queries)) { - $queries = ''; - $i = count($this->sqlLoggerStack->queries); - foreach (array_reverse($this->sqlLoggerStack->queries) as $query) { - $params = array_map(static function ($p) { - if (is_object($p)) { - return get_class($p); - } - - if (is_scalar($p)) { - return "'" . $p . "'"; - } - - return var_export($p, true); - }, $query['params'] ?: []); - $queries .= $i . ". SQL: '" . $query['sql'] . "' Params: " . implode(', ', $params) . PHP_EOL; - $i--; - } - - $trace = $t->getTrace(); - $traceMsg = ''; - foreach ($trace as $part) { - if (! isset($part['file'])) { - continue; - } - - if (strpos($part['file'], 'PHPUnit/') !== false) { - // Beginning with PHPUnit files we don't print the trace anymore. - break; - } - - $traceMsg .= $part['file'] . ':' . $part['line'] . PHP_EOL; - } - - $message = '[' . get_class($t) . '] ' . $t->getMessage() . PHP_EOL . PHP_EOL . 'With queries:' . PHP_EOL . $queries . PHP_EOL . 'Trace:' . PHP_EOL . $traceMsg; - - throw new Exception($message, (int) $t->getCode(), $t); - } - throw $t; - } -} diff --git a/tests/Functional/Schema/ChangeTest.php b/tests/Functional/Schema/ChangeTest.php index 848a714..182238d 100644 --- a/tests/Functional/Schema/ChangeTest.php +++ b/tests/Functional/Schema/ChangeTest.php @@ -14,63 +14,63 @@ class ChangeTest extends FunctionalTestCase { use RefreshDatabase; -// /** @test */ -// public function schemaChanges(): void -// { -// Schema::create('test_table', function (Blueprint $table) { -// $table->integer('id')->default(1); -// $table->string('name')->nullable()->default(new Expression('NULL')); -// $table->binary('binaries'); -// $table->string('email')->unique(); -// $table->tinyInteger('phone')->unsigned(); -// $table->boolean('enabled')->default(0)->comment('Enabled'); -// }); -// -// $this->assertTrue(Schema::hasTable('test_table')); -// $this->assertSame( -// ['id', 'name', 'binaries', 'email', 'phone', 'enabled'], -// Schema::getColumnListing('test_table') -// ); -// -// Schema::table('test_table', function (Blueprint $table) { -// $table->integer('id')->primary()->change(); -// $table->string('email')->nullable()->default(new Expression('NULL'))->change(); -// $table->string('binaries')->nullable()->change(); -// $table->string('test1'); -// $table->string('test2')->comment('test_comment'); -// $table->string('dump1')->nullable(); -// $table->dropColumn(['dump1']); -// }); -// -// $this->assertSame( -// ['id', 'name', 'binaries', 'email', 'phone', 'enabled', 'test1', 'test2'], -// Schema::getColumnListing('test_table') -// ); -// -// Schema::table('test_table', function (Blueprint $table) { -// $table->integer('id')->autoIncrement()->change(); -// $table->string('email')->nullable()->default('player@example.om')->change(); -// $table->renameColumn('binaries', 'code'); -// $table->dropColumn(['phone', 'enabled']); -// $table->binary('test1')->nullable()->using('test1::bytea')->change(); -// $table->string('test2')->comment('new_comment')->change(); -// }); -// -// $this->assertSame(['id', 'name', 'code', 'email', 'test1', 'test2'], Schema::getColumnListing('test_table')); -// -// Schema::table('test_table', function (Blueprint $table) { -// $table->dropPrimary(['id']); -// $table->string('name', 100)->change(); -// $table->integer('id')->change(); -// $table->rename('some_table'); -// }); -// -// $this->assertTrue(Schema::hasTable('some_table')); -// -// Schema::table('some_table', function (Blueprint $table) { -// $table->drop(); -// }); -// -// $this->assertFalse(Schema::hasTable('some_table')); -// } + /** @test */ + public function schemaChanges(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->integer('id')->default(1); + $table->string('name')->nullable()->default(new Expression('NULL')); + $table->binary('binaries'); + $table->string('email')->unique(); + $table->tinyInteger('phone')->unsigned(); + $table->boolean('enabled')->default(0)->comment('Enabled'); + }); + + $this->assertTrue(Schema::hasTable('test_table')); + $this->assertSame( + ['id', 'name', 'binaries', 'email', 'phone', 'enabled'], + Schema::getColumnListing('test_table') + ); + + Schema::table('test_table', function (Blueprint $table) { + $table->integer('id')->primary()->change(); + $table->string('email')->nullable()->default(new Expression('NULL'))->change(); + $table->string('binaries')->nullable()->change(); + $table->string('test1'); + $table->string('test2')->comment('test_comment'); + $table->string('dump1')->nullable(); + $table->dropColumn(['dump1']); + }); + + $this->assertSame( + ['id', 'name', 'binaries', 'email', 'phone', 'enabled', 'test1', 'test2'], + Schema::getColumnListing('test_table') + ); + + Schema::table('test_table', function (Blueprint $table) { + $table->integer('id')->autoIncrement()->change(); + $table->string('email')->nullable()->default('player@example.om')->change(); + $table->renameColumn('binaries', 'code'); + $table->dropColumn(['phone', 'enabled']); + $table->binary('test1')->nullable()->using('test1::bytea')->change(); + $table->string('test2')->comment('new_comment')->change(); + }); + + $this->assertSame(['id', 'name', 'code', 'email', 'test1', 'test2'], Schema::getColumnListing('test_table')); + + Schema::table('test_table', function (Blueprint $table) { + $table->dropPrimary(['id']); + $table->string('name', 100)->change(); + $table->integer('id')->change(); + $table->rename('some_table'); + }); + + $this->assertTrue(Schema::hasTable('some_table')); + + Schema::table('some_table', function (Blueprint $table) { + $table->drop(); + }); + + $this->assertFalse(Schema::hasTable('some_table')); + } } diff --git a/tests/Functional/TestUtil.php b/tests/Functional/TestUtil.php index a921ef2..dc1aaa3 100644 --- a/tests/Functional/TestUtil.php +++ b/tests/Functional/TestUtil.php @@ -1,11 +1,13 @@ $GLOBALS['db_type'], + 'user' => $GLOBALS['db_username'], + 'password' => $GLOBALS['db_password'], + 'host' => $GLOBALS['db_host'], + 'dbname' => $GLOBALS['db_name'], + 'port' => $GLOBALS['db_port'], + ]; + + if (isset($GLOBALS['db_server'])) { + $connectionParams['server'] = $GLOBALS['db_server']; + } + + if (isset($GLOBALS['db_unix_socket'])) { + $connectionParams['unix_socket'] = $GLOBALS['db_unix_socket']; + } + + return $connectionParams; + } + + /** + * @return mixed[] + */ + public static function getParamsForTemporaryConnection(): array + { + $connectionParams = [ + 'driver' => $GLOBALS['tmpdb_type'], + 'user' => $GLOBALS['tmpdb_username'], + 'password' => $GLOBALS['tmpdb_password'], + 'host' => $GLOBALS['tmpdb_host'], + 'dbname' => null, + 'port' => $GLOBALS['tmpdb_port'], + ]; + + if (isset($GLOBALS['tmpdb_name'])) { + $connectionParams['dbname'] = $GLOBALS['tmpdb_name']; + } + + if (isset($GLOBALS['tmpdb_server'])) { + $connectionParams['server'] = $GLOBALS['tmpdb_server']; + } + + if (isset($GLOBALS['tmpdb_unix_socket'])) { + $connectionParams['unix_socket'] = $GLOBALS['tmpdb_unix_socket']; + } + + return $connectionParams; + } + + private static function hasRequiredConnectionParams(): bool { return isset( $GLOBALS['db_type'], @@ -70,12 +128,11 @@ private static function hasRequiredConnectionParams() : bool ); } - private static function initializeDatabase() : void + private static function initializeDatabase(): void { $realConn = static::createDoctrineConnection(self::getParamsForMainConnection()); $tmpConn = static::createDoctrineConnection(self::getParamsForTemporaryConnection()); - $platform = $tmpConn->getDatabasePlatform(); if ($platform->supportsCreateDropDatabase()) { @@ -90,7 +147,7 @@ private static function initializeDatabase() : void $sm = $realConn->getSchemaManager(); $schema = $sm->createSchema(); - $stmts = $schema->toDropSql($realConn->getDatabasePlatform()); + $stmts = $schema->toDropSql($realConn->getDatabasePlatform()); foreach ($stmts as $stmt) { $realConn->exec($stmt); @@ -98,9 +155,9 @@ private static function initializeDatabase() : void } } - private static function addDbEventSubscribers(Connection $conn) : void + private static function addDbEventSubscribers(Connection $conn): void { - if (! isset($GLOBALS['db_event_subscribers'])) { + if (!isset($GLOBALS['db_event_subscribers'])) { return; } @@ -111,11 +168,6 @@ private static function addDbEventSubscribers(Connection $conn) : void } } - public static function getTempConnection() : Connection - { - return static::getDoctrineConnection('temporary'); - } - private static function getDoctrineConnection(string $name = null): Connection { /** @var PostgresConnection $connection */ @@ -127,55 +179,4 @@ private static function createDoctrineConnection($params): Connection { return DriverManager::getConnection($params); } - - public static function getParamsForMainConnection() : array - { - $connectionParams = [ - 'driver' => $GLOBALS['db_type'], - 'user' => $GLOBALS['db_username'], - 'password' => $GLOBALS['db_password'], - 'host' => $GLOBALS['db_host'], - 'dbname' => $GLOBALS['db_name'], - 'port' => $GLOBALS['db_port'], - ]; - - if (isset($GLOBALS['db_server'])) { - $connectionParams['server'] = $GLOBALS['db_server']; - } - - if (isset($GLOBALS['db_unix_socket'])) { - $connectionParams['unix_socket'] = $GLOBALS['db_unix_socket']; - } - - return $connectionParams; - } - - /** - * @return mixed[] - */ - public static function getParamsForTemporaryConnection() : array - { - $connectionParams = [ - 'driver' => $GLOBALS['tmpdb_type'], - 'user' => $GLOBALS['tmpdb_username'], - 'password' => $GLOBALS['tmpdb_password'], - 'host' => $GLOBALS['tmpdb_host'], - 'dbname' => null, - 'port' => $GLOBALS['tmpdb_port'], - ]; - - if (isset($GLOBALS['tmpdb_name'])) { - $connectionParams['dbname'] = $GLOBALS['tmpdb_name']; - } - - if (isset($GLOBALS['tmpdb_server'])) { - $connectionParams['server'] = $GLOBALS['tmpdb_server']; - } - - if (isset($GLOBALS['tmpdb_unix_socket'])) { - $connectionParams['unix_socket'] = $GLOBALS['tmpdb_unix_socket']; - } - - return $connectionParams; - } } diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index 66d74b1..e9260ab 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -4,7 +4,6 @@ namespace Umbrellio\Postgres\Tests; -use Illuminate\Foundation\Testing\RefreshDatabase; use Umbrellio\Postgres\Tests\Functional\TestUtil; abstract class FunctionalTestCase extends TestCase diff --git a/tests/TestCase.php b/tests/TestCase.php index dece52d..c126697 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,18 +4,12 @@ namespace Umbrellio\Postgres\Tests; -use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Facade; use Orchestra\Testbench\TestCase as BaseTestCase; use Umbrellio\Postgres\UmbrellioPostgresProvider; abstract class TestCase extends BaseTestCase { - protected function getPackageProviders($app) - { - return [UmbrellioPostgresProvider::class]; - } - protected function setUp(): void { if (!$this->app) { @@ -27,4 +21,8 @@ protected function setUp(): void Facade::clearResolvedInstances(); } + protected function getPackageProviders($app) + { + return [UmbrellioPostgresProvider::class]; + } } diff --git a/tests/Unit/DBAL/ConfigurationTest.php b/tests/Unit/DBAL/ConfigurationTest.php deleted file mode 100644 index b037c44..0000000 --- a/tests/Unit/DBAL/ConfigurationTest.php +++ /dev/null @@ -1,53 +0,0 @@ -config = new Configuration(); - } - - /** - * Tests that the default auto-commit mode for connections can be retrieved from the configuration container. - * - * @group DBAL-81 - */ - public function testReturnsDefaultConnectionAutoCommitMode() : void - { - self::assertTrue($this->config->getAutoCommit()); - } - - /** - * Tests that the default auto-commit mode for connections can be set in the configuration container. - * - * @group DBAL-81 - */ - public function testSetsDefaultConnectionAutoCommitMode() : void - { - $this->config->setAutoCommit(false); - - self::assertFalse($this->config->getAutoCommit()); - - $this->config->setAutoCommit(0); - - self::assertFalse($this->config->getAutoCommit()); - } -} diff --git a/tests/Unit/DBAL/ConnectionTest.php b/tests/Unit/DBAL/ConnectionTest.php deleted file mode 100644 index d5d56fe..0000000 --- a/tests/Unit/DBAL/ConnectionTest.php +++ /dev/null @@ -1,971 +0,0 @@ - 'pdo_mysql', - 'host' => 'localhost', - 'user' => 'root', - 'password' => 'password', - 'port' => '1234', - ]; - - protected function setUp() : void - { - $this->connection = DriverManager::getConnection($this->params); - } - - /** - * @return Connection|MockObject - */ - private function getExecuteUpdateMockConnection() - { - $driverMock = $this->createMock(Driver::class); - - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - - $platform = $this->getMockForAbstractClass(AbstractPlatform::class); - - return $this->getMockBuilder(Connection::class) - ->setMethods(['executeUpdate']) - ->setConstructorArgs([['platform' => $platform], $driverMock]) - ->getMock(); - } - - public function testIsConnected() : void - { - self::assertFalse($this->connection->isConnected()); - } - - public function testNoTransactionActiveByDefault() : void - { - self::assertFalse($this->connection->isTransactionActive()); - } - - public function testCommitWithNoActiveTransactionThrowsException() : void - { - $this->expectException(ConnectionException::class); - $this->connection->commit(); - } - - public function testRollbackWithNoActiveTransactionThrowsException() : void - { - $this->expectException(ConnectionException::class); - $this->connection->rollBack(); - } - - public function testSetRollbackOnlyNoActiveTransactionThrowsException() : void - { - $this->expectException(ConnectionException::class); - $this->connection->setRollbackOnly(); - } - - public function testIsRollbackOnlyNoActiveTransactionThrowsException() : void - { - $this->expectException(ConnectionException::class); - $this->connection->isRollbackOnly(); - } - - public function testGetConfiguration() : void - { - $config = $this->connection->getConfiguration(); - - self::assertInstanceOf(Configuration::class, $config); - } - - public function testGetHost() : void - { - self::assertEquals('localhost', $this->connection->getHost()); - } - - public function testGetPort() : void - { - self::assertEquals('1234', $this->connection->getPort()); - } - - public function testGetUsername() : void - { - self::assertEquals('root', $this->connection->getUsername()); - } - - public function testGetPassword() : void - { - self::assertEquals('password', $this->connection->getPassword()); - } - - public function testGetDriver() : void - { - self::assertInstanceOf(\Doctrine\DBAL\Driver\PDOMySql\Driver::class, $this->connection->getDriver()); - } - - public function testGetEventManager() : void - { - self::assertInstanceOf(EventManager::class, $this->connection->getEventManager()); - } - - public function testConnectDispatchEvent() : void - { - $listenerMock = $this->getMockBuilder('ConnectDispatchEventListener') - ->setMethods(['postConnect']) - ->getMock(); - $listenerMock->expects($this->once())->method('postConnect'); - - $eventManager = new EventManager(); - $eventManager->addEventListener([Events::postConnect], $listenerMock); - - $driverMock = $this->createMock(Driver::class); - $driverMock->expects($this->at(0)) - ->method('connect'); - - $conn = new Connection([], $driverMock, new Configuration(), $eventManager); - $conn->connect(); - } - - public function testEventManagerPassedToPlatform() : void - { - $eventManager = new EventManager(); - - /** @var AbstractPlatform|MockObject $driver */ - $platform = $this->createMock(AbstractPlatform::class); - $platform->expects($this->once()) - ->method('setEventManager') - ->with($eventManager); - - /** @var Driver|MockObject $driver */ - $driver = $this->createMock(Driver::class); - $driver->expects($this->any()) - ->method('getDatabasePlatform') - ->willReturn($platform); - - $connection = new Connection($this->params, $driver, null, $eventManager); - $connection->getDatabasePlatform(); - } - - /** - * @requires extension pdo_sqlite - * @dataProvider getQueryMethods - */ - public function testDriverExceptionIsWrapped(string $method) : void - { - $this->expectException(DBALException::class); - $this->expectExceptionMessage("An exception occurred while executing 'MUUHAAAAHAAAA':\n\nSQLSTATE[HY000]: General error: 1 near \"MUUHAAAAHAAAA\""); - - $connection = DriverManager::getConnection([ - 'driver' => 'pdo_sqlite', - 'memory' => true, - ]); - - $connection->$method('MUUHAAAAHAAAA'); - } - - /** - * @return array> - */ - public static function getQueryMethods() : iterable - { - return [ - ['exec'], - ['query'], - ['executeQuery'], - ['executeUpdate'], - ['prepare'], - ]; - } - - /** - * Pretty dumb test, however we want to check that the EchoSQLLogger correctly implements the interface. - * - * @group DBAL-11 - */ - public function testEchoSQLLogger() : void - { - $logger = new EchoSQLLogger(); - $this->connection->getConfiguration()->setSQLLogger($logger); - self::assertSame($logger, $this->connection->getConfiguration()->getSQLLogger()); - } - - /** - * Pretty dumb test, however we want to check that the DebugStack correctly implements the interface. - * - * @group DBAL-11 - */ - public function testDebugSQLStack() : void - { - $logger = new DebugStack(); - $this->connection->getConfiguration()->setSQLLogger($logger); - self::assertSame($logger, $this->connection->getConfiguration()->getSQLLogger()); - } - - /** - * @group DBAL-81 - */ - public function testIsAutoCommit() : void - { - self::assertTrue($this->connection->isAutoCommit()); - } - - /** - * @group DBAL-81 - */ - public function testSetAutoCommit() : void - { - $this->connection->setAutoCommit(false); - self::assertFalse($this->connection->isAutoCommit()); - $this->connection->setAutoCommit(0); - self::assertFalse($this->connection->isAutoCommit()); - } - - /** - * @group DBAL-81 - */ - public function testConnectStartsTransactionInNoAutoCommitMode() : void - { - $driverMock = $this->createMock(Driver::class); - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - $conn = new Connection([], $driverMock); - - $conn->setAutoCommit(false); - - self::assertFalse($conn->isTransactionActive()); - - $conn->connect(); - - self::assertTrue($conn->isTransactionActive()); - } - - /** - * @group DBAL-81 - */ - public function testCommitStartsTransactionInNoAutoCommitMode() : void - { - $driverMock = $this->createMock(Driver::class); - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - $conn = new Connection([], $driverMock); - - $conn->setAutoCommit(false); - $conn->connect(); - $conn->commit(); - - self::assertTrue($conn->isTransactionActive()); - } - - /** - * @dataProvider resultProvider - */ - public function testCommitReturn(bool $expectedResult) : void - { - $driverConnection = $this->createMock(DriverConnection::class); - $driverConnection->expects($this->once()) - ->method('commit')->willReturn($expectedResult); - - $driverMock = $this->createMock(Driver::class); - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue($driverConnection)); - - $conn = new Connection([], $driverMock); - - $conn->connect(); - $conn->beginTransaction(); - - self::assertSame($expectedResult, $conn->commit()); - } - - /** - * @return bool[][] - */ - public function resultProvider() : array - { - return [[true], [false]]; - } - - /** - * @group DBAL-81 - */ - public function testRollBackStartsTransactionInNoAutoCommitMode() : void - { - $driverMock = $this->createMock(Driver::class); - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - $conn = new Connection([], $driverMock); - - $conn->setAutoCommit(false); - $conn->connect(); - $conn->rollBack(); - - self::assertTrue($conn->isTransactionActive()); - } - - /** - * @group DBAL-81 - */ - public function testSwitchingAutoCommitModeCommitsAllCurrentTransactions() : void - { - $driverMock = $this->createMock(Driver::class); - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - $conn = new Connection([], $driverMock); - - $conn->connect(); - $conn->beginTransaction(); - $conn->beginTransaction(); - $conn->setAutoCommit(false); - - self::assertSame(1, $conn->getTransactionNestingLevel()); - - $conn->beginTransaction(); - $conn->beginTransaction(); - $conn->setAutoCommit(true); - - self::assertFalse($conn->isTransactionActive()); - } - - public function testEmptyInsert() : void - { - $conn = $this->getExecuteUpdateMockConnection(); - - $conn->expects($this->once()) - ->method('executeUpdate') - ->with('INSERT INTO footable () VALUES ()'); - - $conn->insert('footable', []); - } - - /** - * @group DBAL-2511 - */ - public function testUpdateWithDifferentColumnsInDataAndIdentifiers() : void - { - $conn = $this->getExecuteUpdateMockConnection(); - - $conn->expects($this->once()) - ->method('executeUpdate') - ->with( - 'UPDATE TestTable SET text = ?, is_edited = ? WHERE id = ? AND name = ?', - [ - 'some text', - true, - 1, - 'foo', - ], - [ - 'string', - 'boolean', - 'integer', - 'string', - ] - ); - - $conn->update( - 'TestTable', - [ - 'text' => 'some text', - 'is_edited' => true, - ], - [ - 'id' => 1, - 'name' => 'foo', - ], - [ - 'text' => 'string', - 'is_edited' => 'boolean', - 'id' => 'integer', - 'name' => 'string', - ] - ); - } - - /** - * @group DBAL-2511 - */ - public function testUpdateWithSameColumnInDataAndIdentifiers() : void - { - $conn = $this->getExecuteUpdateMockConnection(); - - $conn->expects($this->once()) - ->method('executeUpdate') - ->with( - 'UPDATE TestTable SET text = ?, is_edited = ? WHERE id = ? AND is_edited = ?', - [ - 'some text', - true, - 1, - false, - ], - [ - 'string', - 'boolean', - 'integer', - 'boolean', - ] - ); - - $conn->update( - 'TestTable', - [ - 'text' => 'some text', - 'is_edited' => true, - ], - [ - 'id' => 1, - 'is_edited' => false, - ], - [ - 'text' => 'string', - 'is_edited' => 'boolean', - 'id' => 'integer', - ] - ); - } - - /** - * @group DBAL-2688 - */ - public function testUpdateWithIsNull() : void - { - $conn = $this->getExecuteUpdateMockConnection(); - - $conn->expects($this->once()) - ->method('executeUpdate') - ->with( - 'UPDATE TestTable SET text = ?, is_edited = ? WHERE id IS NULL AND name = ?', - [ - 'some text', - null, - 'foo', - ], - [ - 'string', - 'boolean', - 'string', - ] - ); - - $conn->update( - 'TestTable', - [ - 'text' => 'some text', - 'is_edited' => null, - ], - [ - 'id' => null, - 'name' => 'foo', - ], - [ - 'text' => 'string', - 'is_edited' => 'boolean', - 'id' => 'integer', - 'name' => 'string', - ] - ); - } - - /** - * @group DBAL-2688 - */ - public function testDeleteWithIsNull() : void - { - $conn = $this->getExecuteUpdateMockConnection(); - - $conn->expects($this->once()) - ->method('executeUpdate') - ->with( - 'DELETE FROM TestTable WHERE id IS NULL AND name = ?', - ['foo'], - ['string'] - ); - - $conn->delete( - 'TestTable', - [ - 'id' => null, - 'name' => 'foo', - ], - [ - 'id' => 'integer', - 'name' => 'string', - ] - ); - } - - public function testFetchAssoc() : void - { - $statement = 'SELECT * FROM foo WHERE bar = ?'; - $params = [666]; - $types = [ParameterType::INTEGER]; - $result = []; - - $driverMock = $this->createMock(Driver::class); - - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - - $driverStatementMock = $this->createMock(Statement::class); - - $driverStatementMock->expects($this->once()) - ->method('fetch') - ->with(FetchMode::ASSOCIATIVE) - ->will($this->returnValue($result)); - - /** @var Connection|MockObject $conn */ - $conn = $this->getMockBuilder(Connection::class) - ->setMethods(['executeQuery']) - ->setConstructorArgs([[], $driverMock]) - ->getMock(); - - $conn->expects($this->once()) - ->method('executeQuery') - ->with($statement, $params, $types) - ->will($this->returnValue($driverStatementMock)); - - self::assertSame($result, $conn->fetchAssoc($statement, $params, $types)); - } - - public function testFetchArray() : void - { - $statement = 'SELECT * FROM foo WHERE bar = ?'; - $params = [666]; - $types = [ParameterType::INTEGER]; - $result = []; - - $driverMock = $this->createMock(Driver::class); - - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - - $driverStatementMock = $this->createMock(Statement::class); - - $driverStatementMock->expects($this->once()) - ->method('fetch') - ->with(FetchMode::NUMERIC) - ->will($this->returnValue($result)); - - /** @var Connection|MockObject $conn */ - $conn = $this->getMockBuilder(Connection::class) - ->setMethods(['executeQuery']) - ->setConstructorArgs([[], $driverMock]) - ->getMock(); - - $conn->expects($this->once()) - ->method('executeQuery') - ->with($statement, $params, $types) - ->will($this->returnValue($driverStatementMock)); - - self::assertSame($result, $conn->fetchArray($statement, $params, $types)); - } - - public function testFetchColumn() : void - { - $statement = 'SELECT * FROM foo WHERE bar = ?'; - $params = [666]; - $types = [ParameterType::INTEGER]; - $column = 0; - $result = []; - - $driverMock = $this->createMock(Driver::class); - - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - - $driverStatementMock = $this->createMock(Statement::class); - - $driverStatementMock->expects($this->once()) - ->method('fetchColumn') - ->with($column) - ->will($this->returnValue($result)); - - /** @var Connection|MockObject $conn */ - $conn = $this->getMockBuilder(Connection::class) - ->setMethods(['executeQuery']) - ->setConstructorArgs([[], $driverMock]) - ->getMock(); - - $conn->expects($this->once()) - ->method('executeQuery') - ->with($statement, $params, $types) - ->will($this->returnValue($driverStatementMock)); - - self::assertSame($result, $conn->fetchColumn($statement, $params, $column, $types)); - } - - public function testFetchAll() : void - { - $statement = 'SELECT * FROM foo WHERE bar = ?'; - $params = [666]; - $types = [ParameterType::INTEGER]; - $result = []; - - $driverMock = $this->createMock(Driver::class); - - $driverMock->expects($this->any()) - ->method('connect') - ->will($this->returnValue( - $this->createMock(DriverConnection::class) - )); - - $driverStatementMock = $this->createMock(Statement::class); - - $driverStatementMock->expects($this->once()) - ->method('fetchAll') - ->will($this->returnValue($result)); - - /** @var Connection|MockObject $conn */ - $conn = $this->getMockBuilder(Connection::class) - ->setMethods(['executeQuery']) - ->setConstructorArgs([[], $driverMock]) - ->getMock(); - - $conn->expects($this->once()) - ->method('executeQuery') - ->with($statement, $params, $types) - ->will($this->returnValue($driverStatementMock)); - - self::assertSame($result, $conn->fetchAll($statement, $params, $types)); - } - - public function testConnectionDoesNotMaintainTwoReferencesToExternalPDO() : void - { - $params['pdo'] = new stdClass(); - - $driverMock = $this->createMock(Driver::class); - - $conn = new Connection($params, $driverMock); - - self::assertArrayNotHasKey('pdo', $conn->getParams(), 'Connection is maintaining additional reference to the PDO connection'); - } - - public function testPassingExternalPDOMeansConnectionIsConnected() : void - { - $params['pdo'] = new stdClass(); - - $driverMock = $this->createMock(Driver::class); - - $conn = new Connection($params, $driverMock); - - self::assertTrue($conn->isConnected(), 'Connection is not connected after passing external PDO'); - } - - public function testCallingDeleteWithNoDeletionCriteriaResultsInInvalidArgumentException() : void - { - /** @var Driver $driver */ - $driver = $this->createMock(Driver::class); - $pdoMock = $this->createMock(\Doctrine\DBAL\Driver\Connection::class); - - // should never execute queries with invalid arguments - $pdoMock->expects($this->never())->method('exec'); - $pdoMock->expects($this->never())->method('prepare'); - - $conn = new Connection(['pdo' => $pdoMock], $driver); - - $this->expectException(InvalidArgumentException::class); - $conn->delete('kittens', []); - } - - /** - * @return array> - */ - public static function dataCallConnectOnce() : iterable - { - return [ - ['delete', ['tbl', ['id' => 12345]]], - ['insert', ['tbl', ['data' => 'foo']]], - ['update', ['tbl', ['data' => 'bar'], ['id' => 12345]]], - ['prepare', ['select * from dual']], - ['executeUpdate', ['insert into tbl (id) values (?)'], [123]], - ]; - } - - /** - * @param array $params - * - * @dataProvider dataCallConnectOnce - */ - public function testCallConnectOnce(string $method, array $params) : void - { - $driverMock = $this->createMock(Driver::class); - $pdoMock = $this->createMock(Connection::class); - $platformMock = $this->createMock(AbstractPlatform::class); - $stmtMock = $this->createMock(Statement::class); - - $pdoMock->expects($this->any()) - ->method('prepare') - ->will($this->returnValue($stmtMock)); - - $conn = $this->getMockBuilder(Connection::class) - ->setConstructorArgs([['pdo' => $pdoMock, 'platform' => $platformMock], $driverMock]) - ->setMethods(['connect']) - ->getMock(); - - $conn->expects($this->once())->method('connect'); - - call_user_func_array([$conn, $method], $params); - } - - /** - * @group DBAL-1127 - */ - public function testPlatformDetectionIsTriggerOnlyOnceOnRetrievingPlatform() : void - { - /** @var Driver|VersionAwarePlatformDriver|MockObject $driverMock */ - $driverMock = $this->createMock([Driver::class, VersionAwarePlatformDriver::class]); - - /** @var ServerInfoAwareConnection|MockObject $driverConnectionMock */ - $driverConnectionMock = $this->createMock(ServerInfoAwareConnection::class); - - /** @var AbstractPlatform|MockObject $platformMock */ - $platformMock = $this->getMockForAbstractClass(AbstractPlatform::class); - - $connection = new Connection([], $driverMock); - - $driverMock->expects($this->once()) - ->method('connect') - ->will($this->returnValue($driverConnectionMock)); - - $driverConnectionMock->expects($this->once()) - ->method('requiresQueryForServerVersion') - ->will($this->returnValue(false)); - - $driverConnectionMock->expects($this->once()) - ->method('getServerVersion') - ->will($this->returnValue('6.6.6')); - - $driverMock->expects($this->once()) - ->method('createDatabasePlatformForVersion') - ->with('6.6.6') - ->will($this->returnValue($platformMock)); - - self::assertSame($platformMock, $connection->getDatabasePlatform()); - } - - public function testConnectionParamsArePassedToTheQueryCacheProfileInExecuteCacheQuery() : void - { - $resultCacheDriverMock = $this->createMock(Cache::class); - - $resultCacheDriverMock - ->expects($this->atLeastOnce()) - ->method('fetch') - ->with('cacheKey') - ->will($this->returnValue(['realKey' => []])); - - $query = 'SELECT * FROM foo WHERE bar = ?'; - $params = [666]; - $types = [ParameterType::INTEGER]; - - /** @var QueryCacheProfile|MockObject $queryCacheProfileMock */ - $queryCacheProfileMock = $this->createMock(QueryCacheProfile::class); - - $queryCacheProfileMock - ->expects($this->any()) - ->method('getResultCacheDriver') - ->will($this->returnValue($resultCacheDriverMock)); - - // This is our main expectation - $queryCacheProfileMock - ->expects($this->once()) - ->method('generateCacheKeys') - ->with($query, $params, $types, $this->params) - ->will($this->returnValue(['cacheKey', 'realKey'])); - - /** @var Driver $driver */ - $driver = $this->createMock(Driver::class); - - self::assertInstanceOf( - ArrayStatement::class, - (new Connection($this->params, $driver))->executeCacheQuery($query, $params, $types, $queryCacheProfileMock) - ); - } - - /** - * @group #2821 - */ - public function testShouldNotPassPlatformInParamsToTheQueryCacheProfileInExecuteCacheQuery() : void - { - $resultCacheDriverMock = $this->createMock(Cache::class); - - $resultCacheDriverMock - ->expects($this->atLeastOnce()) - ->method('fetch') - ->with('cacheKey') - ->will($this->returnValue(['realKey' => []])); - - /** @var QueryCacheProfile|MockObject $queryCacheProfileMock */ - $queryCacheProfileMock = $this->createMock(QueryCacheProfile::class); - - $queryCacheProfileMock - ->expects($this->any()) - ->method('getResultCacheDriver') - ->will($this->returnValue($resultCacheDriverMock)); - - $query = 'SELECT 1'; - - $connectionParams = $this->params; - - $queryCacheProfileMock - ->expects($this->once()) - ->method('generateCacheKeys') - ->with($query, [], [], $connectionParams) - ->will($this->returnValue(['cacheKey', 'realKey'])); - - $connectionParams['platform'] = $this->createMock(AbstractPlatform::class); - - /** @var Driver $driver */ - $driver = $this->createMock(Driver::class); - - (new Connection($connectionParams, $driver))->executeCacheQuery($query, [], [], $queryCacheProfileMock); - } - - /** - * @group #2821 - */ - public function testThrowsExceptionWhenInValidPlatformSpecified() : void - { - $connectionParams = $this->params; - $connectionParams['platform'] = new stdClass(); - - /** @var Driver $driver */ - $driver = $this->createMock(Driver::class); - - $this->expectException(DBALException::class); - - new Connection($connectionParams, $driver); - } - - /** - * @group DBAL-990 - */ - public function testRethrowsOriginalExceptionOnDeterminingPlatformWhenConnectingToNonExistentDatabase() : void - { - /** @var Driver|VersionAwarePlatformDriver|MockObject $driverMock */ - $driverMock = $this->createMock([Driver::class, VersionAwarePlatformDriver::class]); - - $connection = new Connection(['dbname' => 'foo'], $driverMock); - $originalException = new Exception('Original exception'); - $fallbackException = new Exception('Fallback exception'); - - $driverMock->expects($this->at(0)) - ->method('connect') - ->willThrowException($originalException); - - $driverMock->expects($this->at(1)) - ->method('connect') - ->willThrowException($fallbackException); - - $this->expectExceptionMessage($originalException->getMessage()); - - $connection->getDatabasePlatform(); - } - - /** - * @group #3194 - */ - public function testExecuteCacheQueryStripsPlatformFromConnectionParamsBeforeGeneratingCacheKeys() : void - { - /** @var Driver|MockObject $driver */ - $driver = $this->createMock(Driver::class); - - /** @var AbstractPlatform|MockObject $platform */ - $platform = $this->createMock(AbstractPlatform::class); - - /** @var QueryCacheProfile|MockObject $queryCacheProfile */ - $queryCacheProfile = $this->createMock(QueryCacheProfile::class); - - /** @var Cache|MockObject $resultCacheDriver */ - $resultCacheDriver = $this->createMock(Cache::class); - - $queryCacheProfile - ->expects($this->any()) - ->method('getResultCacheDriver') - ->will($this->returnValue($resultCacheDriver)); - - $resultCacheDriver - ->expects($this->atLeastOnce()) - ->method('fetch') - ->with('cacheKey') - ->will($this->returnValue(['realKey' => []])); - - $query = 'SELECT 1'; - - $params = [ - 'dbname' => 'foo', - 'platform' => $platform, - ]; - - $paramsWithoutPlatform = $params; - unset($paramsWithoutPlatform['platform']); - - $queryCacheProfile - ->expects($this->once()) - ->method('generateCacheKeys') - ->with($query, [], [], $paramsWithoutPlatform) - ->will($this->returnValue(['cacheKey', 'realKey'])); - - $connection = new Connection($params, $driver); - - self::assertSame($params, $connection->getParams()); - - $connection->executeCacheQuery($query, [], [], $queryCacheProfile); - } -} diff --git a/tests/Unit/DBAL/DBALExceptionTest.php b/tests/Unit/DBAL/DBALExceptionTest.php deleted file mode 100644 index 4d60c79..0000000 --- a/tests/Unit/DBAL/DBALExceptionTest.php +++ /dev/null @@ -1,88 +0,0 @@ -createMock(Driver::class); - $e = DBALException::driverExceptionDuringQuery($driver, new Exception(), '', ['ABC', chr(128)]); - self::assertStringContainsString('with params ["ABC", "\x80"]', $e->getMessage()); - } - - public function testDriverExceptionDuringQueryAcceptsResource() : void - { - /** @var Driver $driver */ - $driver = $this->createMock(Driver::class); - $e = DBALException::driverExceptionDuringQuery($driver, new Exception(), 'INSERT INTO file (`content`) VALUES (?)', [1 => fopen(__FILE__, 'r')]); - self::assertStringContainsString('Resource', $e->getMessage()); - } - - public function testAvoidOverWrappingOnDriverException() : void - { - /** @var Driver $driver */ - $driver = $this->createMock(Driver::class); - - /** @var InnerDriverException $inner */ - $inner = $this->createMock(InnerDriverException::class); - - $ex = new DriverException('', $inner); - $e = DBALException::driverExceptionDuringQuery($driver, $ex, ''); - self::assertSame($ex, $e); - } - - public function testDriverRequiredWithUrl() : void - { - $url = 'mysql://localhost'; - $exception = DBALException::driverRequired($url); - - self::assertInstanceOf(DBALException::class, $exception); - self::assertSame( - sprintf( - "The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " . - 'is given to DriverManager::getConnection(). Given URL: %s', - $url - ), - $exception->getMessage() - ); - } - - /** - * @group #2821 - */ - public function testInvalidPlatformTypeObject() : void - { - $exception = DBALException::invalidPlatformType(new stdClass()); - - self::assertSame( - "Option 'platform' must be a subtype of 'Doctrine\DBAL\Platforms\AbstractPlatform', instance of 'stdClass' given", - $exception->getMessage() - ); - } - - /** - * @group #2821 - */ - public function testInvalidPlatformTypeScalar() : void - { - $exception = DBALException::invalidPlatformType('some string'); - - self::assertSame( - "Option 'platform' must be an object and subtype of 'Doctrine\DBAL\Platforms\AbstractPlatform'. Got 'string'", - $exception->getMessage() - ); - } -} diff --git a/tests/Unit/DBAL/Driver/AbstractDriverTest.php b/tests/Unit/DBAL/Driver/AbstractDriverTest.php deleted file mode 100644 index 14e7d0c..0000000 --- a/tests/Unit/DBAL/Driver/AbstractDriverTest.php +++ /dev/null @@ -1,239 +0,0 @@ -driver = $this->createDriver(); - } - - /** - * @param int|string $errorCode - * - * @dataProvider exceptionConversionProvider - */ - public function testConvertsException($errorCode, ?string $sqlState, ?string $message, string $expectedClass) : void - { - if (! $this->driver instanceof ExceptionConverterDriver) { - $this->markTestSkipped('This test is only intended for exception converter drivers.'); - } - - /** @var DriverExceptionInterface|MockObject $driverException */ - $driverException = $this->getMockBuilder(DriverExceptionInterface::class) - ->setConstructorArgs([$message]) - ->getMock(); - $driverException->method('getErrorCode') - ->willReturn($errorCode); - $driverException->method('getSQLState') - ->willReturn($sqlState); - - $dbalMessage = 'DBAL exception message'; - $dbalException = $this->driver->convertException($dbalMessage, $driverException); - - self::assertInstanceOf($expectedClass, $dbalException); - - self::assertSame($driverException->getErrorCode(), $dbalException->getErrorCode()); - self::assertSame($driverException->getSQLState(), $dbalException->getSQLState()); - self::assertSame($driverException, $dbalException->getPrevious()); - self::assertSame($dbalMessage, $dbalException->getMessage()); - } - - public function testCreatesDatabasePlatformForVersion() : void - { - if (! $this->driver instanceof VersionAwarePlatformDriver) { - $this->markTestSkipped('This test is only intended for version aware platform drivers.'); - } - - $data = $this->getDatabasePlatformsForVersions(); - - self::assertNotEmpty( - $data, - sprintf( - 'No test data found for test %s. You have to return test data from %s.', - static::class . '::' . __FUNCTION__, - static::class . '::getDatabasePlatformsForVersions' - ) - ); - - foreach ($data as $item) { - $generatedVersion = get_class($this->driver->createDatabasePlatformForVersion($item[0])); - - self::assertSame( - $item[1], - $generatedVersion, - sprintf( - 'Expected platform for version "%s" should be "%s", "%s" given', - $item[0], - $item[1], - $generatedVersion - ) - ); - } - } - - public function testThrowsExceptionOnCreatingDatabasePlatformsForInvalidVersion() : void - { - if (! $this->driver instanceof VersionAwarePlatformDriver) { - $this->markTestSkipped('This test is only intended for version aware platform drivers.'); - } - - $this->expectException(DBALException::class); - $this->driver->createDatabasePlatformForVersion('foo'); - } - - public function testReturnsDatabaseName() : void - { - $params = [ - 'user' => 'foo', - 'password' => 'bar', - 'dbname' => 'baz', - ]; - - $connection = $this->getConnectionMock(); - - $connection->expects($this->once()) - ->method('getParams') - ->will($this->returnValue($params)); - - self::assertSame($params['dbname'], $this->driver->getDatabase($connection)); - } - - public function testReturnsDatabasePlatform() : void - { - self::assertEquals($this->createPlatform(), $this->driver->getDatabasePlatform()); - } - - public function testReturnsSchemaManager() : void - { - $connection = $this->getConnectionMock(); - $schemaManager = $this->driver->getSchemaManager($connection); - - self::assertEquals($this->createSchemaManager($connection), $schemaManager); - - $re = new ReflectionProperty($schemaManager, '_conn'); - $re->setAccessible(true); - - self::assertSame($connection, $re->getValue($schemaManager)); - } - - /** - * Factory method for creating the driver instance under test. - */ - abstract protected function createDriver() : Driver; - - /** - * Factory method for creating the the platform instance return by the driver under test. - * - * The platform instance returned by this method must be the same as returned by - * the driver's getDatabasePlatform() method. - */ - abstract protected function createPlatform() : AbstractPlatform; - - /** - * Factory method for creating the the schema manager instance return by the driver under test. - * - * The schema manager instance returned by this method must be the same as returned by - * the driver's getSchemaManager() method. - * - * @param Connection $connection The underlying connection to use. - */ - abstract protected function createSchemaManager(Connection $connection) : AbstractSchemaManager; - - protected function getConnectionMock() : Connection - { - return $this->getMockBuilder(Connection::class) - ->disableOriginalConstructor() - ->getMock(); - } - - /** - * @return array> - */ - protected function getDatabasePlatformsForVersions() : array - { - return []; - } - - /** - * @return mixed[][] - */ - public static function exceptionConversionProvider() : iterable - { - foreach (static::getExceptionConversionData() as $expectedClass => $items) { - foreach ($items as $item) { - yield array_merge($item, [$expectedClass]); - } - } - - yield ['foo', 'bar', 'baz', self::EXCEPTION_DRIVER]; - } - - /** - * @return array - */ - protected static function getExceptionConversionData() : array - { - return []; - } -} diff --git a/tests/Unit/DBAL/Driver/AbstractPostgreSQLDriverTest.php b/tests/Unit/DBAL/Driver/AbstractPostgreSQLDriverTest.php deleted file mode 100644 index b6c96e4..0000000 --- a/tests/Unit/DBAL/Driver/AbstractPostgreSQLDriverTest.php +++ /dev/null @@ -1,125 +0,0 @@ - 'foo', - 'password' => 'bar', - ]; - - $statement = $this->createMock(ResultStatement::class); - - $statement->expects($this->once()) - ->method('fetchColumn') - ->will($this->returnValue($database)); - - $connection = $this->getConnectionMock(); - - $connection->expects($this->once()) - ->method('getParams') - ->will($this->returnValue($params)); - - $connection->expects($this->once()) - ->method('query') - ->will($this->returnValue($statement)); - - self::assertSame($database, $this->driver->getDatabase($connection)); - } - - protected function createDriver() : Driver - { - return $this->getMockForAbstractClass(AbstractPostgreSQLDriver::class); - } - - protected function createPlatform() : AbstractPlatform - { - return new PostgreSqlPlatform(); - } - - protected function createSchemaManager(Connection $connection) : AbstractSchemaManager - { - return new PostgreSqlSchemaManager($connection); - } - - /** - * {@inheritDoc} - */ - protected function getDatabasePlatformsForVersions() : array - { - return [ - ['9.0.9', PostgreSqlPlatform::class], - ['9.1', PostgreSQL91Platform::class], - ['9.1.0', PostgreSQL91Platform::class], - ['9.1.1', PostgreSQL91Platform::class], - ['9.1.9', PostgreSQL91Platform::class], - ['9.2', PostgreSQL92Platform::class], - ['9.2.0', PostgreSQL92Platform::class], - ['9.2.1', PostgreSQL92Platform::class], - ['9.3.6', PostgreSQL92Platform::class], - ['9.4', PostgreSQL94Platform::class], - ['9.4.0', PostgreSQL94Platform::class], - ['9.4.1', PostgreSQL94Platform::class], - ['10', PostgreSQL100Platform::class], - ]; - } - - /** - * {@inheritDoc} - */ - protected static function getExceptionConversionData() : array - { - return [ - self::EXCEPTION_CONNECTION => [ - [null, '7', 'SQLSTATE[08006]'], - ], - self::EXCEPTION_FOREIGN_KEY_CONSTRAINT_VIOLATION => [ - [null, '23503', null], - ], - self::EXCEPTION_INVALID_FIELD_NAME => [ - [null, '42703', null], - ], - self::EXCEPTION_NON_UNIQUE_FIELD_NAME => [ - [null, '42702', null], - ], - self::EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION => [ - [null, '23502', null], - ], - self::EXCEPTION_SYNTAX_ERROR => [ - [null, '42601', null], - ], - self::EXCEPTION_TABLE_EXISTS => [ - [null, '42P07', null], - ], - self::EXCEPTION_TABLE_NOT_FOUND => [ - [null, '42P01', null], - ], - self::EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION => [ - [null, '23505', null], - ], - self::EXCEPTION_DEADLOCK => [ - [null, '40001', null], - [null, '40P01', null], - ], - ]; - } -} diff --git a/tests/Unit/DBAL/Driver/PDOPgSql/DriverTest.php b/tests/Unit/DBAL/Driver/PDOPgSql/DriverTest.php deleted file mode 100644 index cf00d7a..0000000 --- a/tests/Unit/DBAL/Driver/PDOPgSql/DriverTest.php +++ /dev/null @@ -1,120 +0,0 @@ -driver->getName()); - } - - /** - * @group DBAL-920 - */ - public function testConnectionDisablesPreparesOnPhp56() : void - { - $this->skipWhenNotUsingPhp56AndPdoPgsql(); - - $connection = $this->createDriver()->connect( - [ - 'host' => $GLOBALS['db_host'], - 'port' => $GLOBALS['db_port'], - ], - $GLOBALS['db_username'], - $GLOBALS['db_password'] - ); - - self::assertInstanceOf(PDOConnection::class, $connection); - - try { - self::assertTrue($connection->getAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES)); - } catch (PDOException $ignored) { - /** @link https://bugs.php.net/bug.php?id=68371 */ - $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=68371'); - } - } - - /** - * @group DBAL-920 - */ - public function testConnectionDoesNotDisablePreparesOnPhp56WhenAttributeDefined() : void - { - $this->skipWhenNotUsingPhp56AndPdoPgsql(); - - $connection = $this->createDriver()->connect( - [ - 'host' => $GLOBALS['db_host'], - 'port' => $GLOBALS['db_port'], - ], - $GLOBALS['db_username'], - $GLOBALS['db_password'], - [PDO::PGSQL_ATTR_DISABLE_PREPARES => false] - ); - - self::assertInstanceOf(PDOConnection::class, $connection); - - try { - self::assertNotSame(true, $connection->getAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES)); - } catch (PDOException $ignored) { - /** @link https://bugs.php.net/bug.php?id=68371 */ - $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=68371'); - } - } - - /** - * @group DBAL-920 - */ - public function testConnectionDisablePreparesOnPhp56WhenDisablePreparesIsExplicitlyDefined() : void - { - $this->skipWhenNotUsingPhp56AndPdoPgsql(); - - $connection = $this->createDriver()->connect( - [ - 'host' => $GLOBALS['db_host'], - 'port' => $GLOBALS['db_port'], - ], - $GLOBALS['db_username'], - $GLOBALS['db_password'], - [PDO::PGSQL_ATTR_DISABLE_PREPARES => true] - ); - - self::assertInstanceOf(PDOConnection::class, $connection); - - try { - self::assertTrue($connection->getAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES)); - } catch (PDOException $ignored) { - /** @link https://bugs.php.net/bug.php?id=68371 */ - $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=68371'); - } - } - - /** - * {@inheritDoc} - */ - protected function createDriver() : DriverInterface - { - return new Driver(); - } - - private function skipWhenNotUsingPhp56AndPdoPgsql() : void - { - if (! defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) { - $this->markTestSkipped('Test requires PHP 5.6+'); - } - - if (isset($GLOBALS['db_type']) && $GLOBALS['db_type'] === 'pdo_pgsql') { - return; - } - - $this->markTestSkipped('Test enabled only when using pdo_pgsql specific phpunit.xml'); - } -} diff --git a/tests/Unit/DBAL/DriverManagerTest.php b/tests/Unit/DBAL/DriverManagerTest.php deleted file mode 100644 index 4b72cff..0000000 --- a/tests/Unit/DBAL/DriverManagerTest.php +++ /dev/null @@ -1,542 +0,0 @@ -expectException(DBALException::class); - DriverManager::getConnection(['pdo' => 'test']); - } - - /** - * @requires extension pdo_sqlite - */ - public function testValidPdoInstance() : void - { - $conn = DriverManager::getConnection([ - 'pdo' => new PDO('sqlite::memory:'), - ]); - - self::assertEquals('sqlite', $conn->getDatabasePlatform()->getName()); - } - - /** - * @group DBAL-32 - * @requires extension pdo_sqlite - */ - public function testPdoInstanceSetErrorMode() : void - { - $pdo = new PDO('sqlite::memory:'); - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); - $options = ['pdo' => $pdo]; - - DriverManager::getConnection($options); - self::assertEquals(PDO::ERRMODE_EXCEPTION, $pdo->getAttribute(PDO::ATTR_ERRMODE)); - } - - public function testCheckParams() : void - { - $this->expectException(DBALException::class); - - DriverManager::getConnection([]); - } - - public function testInvalidDriver() : void - { - $this->expectException(DBALException::class); - - DriverManager::getConnection(['driver' => 'invalid_driver']); - } - - /** - * @requires extension pdo_sqlite - */ - public function testCustomPlatform() : void - { - $platform = $this->createMock(AbstractPlatform::class); - $options = [ - 'pdo' => new PDO('sqlite::memory:'), - 'platform' => $platform, - ]; - - $conn = DriverManager::getConnection($options); - self::assertSame($platform, $conn->getDatabasePlatform()); - } - - /** - * @requires extension pdo_sqlite - */ - public function testCustomWrapper() : void - { - $wrapper = $this->createMock(Connection::class); - $wrapperClass = get_class($wrapper); - - $options = [ - 'pdo' => new PDO('sqlite::memory:'), - 'wrapperClass' => $wrapperClass, - ]; - - $conn = DriverManager::getConnection($options); - self::assertInstanceOf($wrapperClass, $conn); - } - - /** - * @requires extension pdo_sqlite - */ - public function testInvalidWrapperClass() : void - { - $this->expectException(DBALException::class); - - $options = [ - 'pdo' => new PDO('sqlite::memory:'), - 'wrapperClass' => stdClass::class, - ]; - - DriverManager::getConnection($options); - } - - public function testInvalidDriverClass() : void - { - $this->expectException(DBALException::class); - - $options = ['driverClass' => stdClass::class]; - - DriverManager::getConnection($options); - } - - public function testValidDriverClass() : void - { - $options = ['driverClass' => PDOMySQLDriver::class]; - - $conn = DriverManager::getConnection($options); - self::assertInstanceOf(PDOMySQLDriver::class, $conn->getDriver()); - } - - public function testDatabaseUrlMasterSlave() : void - { - $options = [ - 'driver' => 'pdo_mysql', - 'master' => ['url' => 'mysql://foo:bar@localhost:11211/baz'], - 'slaves' => [ - 'slave1' => ['url' => 'mysql://foo:bar@localhost:11211/baz_slave'], - ], - 'wrapperClass' => MasterSlaveConnection::class, - ]; - - $conn = DriverManager::getConnection($options); - - $params = $conn->getParams(); - self::assertInstanceOf(PDOMySQLDriver::class, $conn->getDriver()); - - $expected = [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'port' => 11211, - ]; - - foreach ($expected as $key => $value) { - self::assertEquals($value, $params['master'][$key]); - self::assertEquals($value, $params['slaves']['slave1'][$key]); - } - - self::assertEquals('baz', $params['master']['dbname']); - self::assertEquals('baz_slave', $params['slaves']['slave1']['dbname']); - } - - public function testDatabaseUrlShard() : void - { - $options = [ - 'driver' => 'pdo_mysql', - 'shardChoser' => MultiTenantShardChoser::class, - 'global' => ['url' => 'mysql://foo:bar@localhost:11211/baz'], - 'shards' => [ - [ - 'id' => 1, - 'url' => 'mysql://foo:bar@localhost:11211/baz_slave', - ], - ], - 'wrapperClass' => PoolingShardConnection::class, - ]; - - $conn = DriverManager::getConnection($options); - - $params = $conn->getParams(); - self::assertInstanceOf(PDOMySQLDriver::class, $conn->getDriver()); - - $expected = [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'port' => 11211, - ]; - - foreach ($expected as $key => $value) { - self::assertEquals($value, $params['global'][$key]); - self::assertEquals($value, $params['shards'][0][$key]); - } - - self::assertEquals('baz', $params['global']['dbname']); - self::assertEquals('baz_slave', $params['shards'][0]['dbname']); - } - - /** - * @param mixed $url - * @param mixed $expected - * - * @dataProvider databaseUrls - */ - public function testDatabaseUrl($url, $expected) : void - { - $options = is_array($url) ? $url : ['url' => $url]; - - if (isset($options['pdo'])) { - if (! extension_loaded('pdo')) { - $this->markTestSkipped('PDO is not installed'); - } - - $options['pdo'] = $this->createMock(PDO::class); - } - - $options = is_array($url) ? $url : ['url' => $url]; - - if ($expected === false) { - $this->expectException(DBALException::class); - } - - $conn = DriverManager::getConnection($options); - - $params = $conn->getParams(); - foreach ($expected as $key => $value) { - if (in_array($key, ['pdo', 'driver', 'driverClass'], true)) { - self::assertInstanceOf($value, $conn->getDriver()); - } else { - self::assertEquals($value, $params[$key]); - } - } - } - - /** - * @return array> - */ - public function databaseUrls() : iterable - { - $driver = $this->createMock(Driver::class); - $driverClass = get_class($driver); - - return [ - 'simple URL' => [ - 'mysql://foo:bar@localhost/baz', - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'simple URL with port' => [ - 'mysql://foo:bar@localhost:11211/baz', - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'port' => 11211, - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'sqlite relative URL with host' => [ - 'sqlite://localhost/foo/dbname.sqlite', - [ - 'path' => 'foo/dbname.sqlite', - 'driver' => PDOSqliteDriver::class, - ], - ], - 'sqlite absolute URL with host' => [ - 'sqlite://localhost//tmp/dbname.sqlite', - [ - 'path' => '/tmp/dbname.sqlite', - 'driver' => PDOSqliteDriver::class, - ], - ], - 'sqlite relative URL without host' => [ - 'sqlite:///foo/dbname.sqlite', - [ - 'path' => 'foo/dbname.sqlite', - 'driver' => PDOSqliteDriver::class, - ], - ], - 'sqlite absolute URL without host' => [ - 'sqlite:////tmp/dbname.sqlite', - [ - 'path' => '/tmp/dbname.sqlite', - 'driver' => PDOSqliteDriver::class, - ], - ], - 'sqlite memory' => [ - 'sqlite:///:memory:', - [ - 'memory' => true, - 'driver' => PDOSqliteDriver::class, - ], - ], - 'sqlite memory with host' => [ - 'sqlite://localhost/:memory:', - [ - 'memory' => true, - 'driver' => PDOSqliteDriver::class, - ], - ], - 'params parsed from URL override individual params' => [ - [ - 'url' => 'mysql://foo:bar@localhost/baz', - 'password' => 'lulz', - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'params not parsed from URL but individual params are preserved' => [ - [ - 'url' => 'mysql://foo:bar@localhost/baz', - 'port' => 1234, - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'port' => 1234, - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'query params from URL are used as extra params' => [ - 'url' => 'mysql://foo:bar@localhost/dbname?charset=UTF-8', - ['charset' => 'UTF-8'], - ], - 'simple URL with fallthrough scheme not defined in map' => [ - 'sqlsrv://foo:bar@localhost/baz', - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => SQLSrvDriver::class, - ], - ], - 'simple URL with fallthrough scheme containing underscores fails' => [ - 'drizzle_pdo_mysql://foo:bar@localhost/baz', - false, - ], - 'simple URL with fallthrough scheme containing dashes works' => [ - 'drizzle-pdo-mysql://foo:bar@localhost/baz', - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => DrizzlePDOMySqlDriver::class, - ], - ], - 'simple URL with percent encoding' => [ - 'mysql://foo%3A:bar%2F@localhost/baz+baz%40', - [ - 'user' => 'foo:', - 'password' => 'bar/', - 'host' => 'localhost', - 'dbname' => 'baz+baz@', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'simple URL with percent sign in password' => [ - 'mysql://foo:bar%25bar@localhost/baz', - [ - 'user' => 'foo', - 'password' => 'bar%bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - - // DBAL-1234 - 'URL without scheme and without any driver information' => [ - ['url' => '//foo:bar@localhost/baz'], - false, - ], - 'URL without scheme but default PDO driver' => [ - [ - 'url' => '//foo:bar@localhost/baz', - 'pdo' => true, - ], - false, - ], - 'URL without scheme but default driver' => [ - [ - 'url' => '//foo:bar@localhost/baz', - 'driver' => 'pdo_mysql', - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'URL without scheme but custom driver' => [ - [ - 'url' => '//foo:bar@localhost/baz', - 'driverClass' => $driverClass, - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driverClass' => $driverClass, - ], - ], - 'URL without scheme but default PDO driver and default driver' => [ - [ - 'url' => '//foo:bar@localhost/baz', - 'pdo' => true, - 'driver' => 'pdo_mysql', - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'URL without scheme but driver and custom driver' => [ - [ - 'url' => '//foo:bar@localhost/baz', - 'driver' => 'pdo_mysql', - 'driverClass' => $driverClass, - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driverClass' => $driverClass, - ], - ], - 'URL with default PDO driver' => [ - [ - 'url' => 'mysql://foo:bar@localhost/baz', - 'pdo' => true, - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'URL with default driver' => [ - [ - 'url' => 'mysql://foo:bar@localhost/baz', - 'driver' => 'sqlite', - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'URL with default custom driver' => [ - [ - 'url' => 'mysql://foo:bar@localhost/baz', - 'driverClass' => $driverClass, - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'URL with default PDO driver and default driver' => [ - [ - 'url' => 'mysql://foo:bar@localhost/baz', - 'pdo' => true, - 'driver' => 'sqlite', - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'URL with default driver and default custom driver' => [ - [ - 'url' => 'mysql://foo:bar@localhost/baz', - 'driver' => 'sqlite', - 'driverClass' => $driverClass, - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - 'URL with default PDO driver and default driver and default custom driver' => [ - [ - 'url' => 'mysql://foo:bar@localhost/baz', - 'pdo' => true, - 'driver' => 'sqlite', - 'driverClass' => $driverClass, - ], - [ - 'user' => 'foo', - 'password' => 'bar', - 'host' => 'localhost', - 'dbname' => 'baz', - 'driver' => PDOMySQLDriver::class, - ], - ], - ]; - } -} diff --git a/tests/Unit/DBAL/Platforms/AbstractPlatformTestCase.php b/tests/Unit/DBAL/Platforms/AbstractPlatformTestCase.php deleted file mode 100644 index eca3c53..0000000 --- a/tests/Unit/DBAL/Platforms/AbstractPlatformTestCase.php +++ /dev/null @@ -1,1542 +0,0 @@ -platform = $this->createPlatform(); - } - - /** - * @group DDC-1360 - */ - public function testQuoteIdentifier() : void - { - if ($this->platform->getName() === 'mssql') { - $this->markTestSkipped('Not working this way on mssql.'); - } - - $c = $this->platform->getIdentifierQuoteCharacter(); - self::assertEquals($c . 'test' . $c, $this->platform->quoteIdentifier('test')); - self::assertEquals($c . 'test' . $c . '.' . $c . 'test' . $c, $this->platform->quoteIdentifier('test.test')); - self::assertEquals(str_repeat($c, 4), $this->platform->quoteIdentifier($c)); - } - - /** - * @group DDC-1360 - */ - public function testQuoteSingleIdentifier() : void - { - if ($this->platform->getName() === 'mssql') { - $this->markTestSkipped('Not working this way on mssql.'); - } - - $c = $this->platform->getIdentifierQuoteCharacter(); - self::assertEquals($c . 'test' . $c, $this->platform->quoteSingleIdentifier('test')); - self::assertEquals($c . 'test.test' . $c, $this->platform->quoteSingleIdentifier('test.test')); - self::assertEquals(str_repeat($c, 4), $this->platform->quoteSingleIdentifier($c)); - } - - /** - * @group DBAL-1029 - * @dataProvider getReturnsForeignKeyReferentialActionSQL - */ - public function testReturnsForeignKeyReferentialActionSQL(string $action, string $expectedSQL) : void - { - self::assertSame($expectedSQL, $this->platform->getForeignKeyReferentialActionSQL($action)); - } - - /** - * @return mixed[][] - */ - public static function getReturnsForeignKeyReferentialActionSQL() : iterable - { - return [ - ['CASCADE', 'CASCADE'], - ['SET NULL', 'SET NULL'], - ['NO ACTION', 'NO ACTION'], - ['RESTRICT', 'RESTRICT'], - ['SET DEFAULT', 'SET DEFAULT'], - ['CaScAdE', 'CASCADE'], - ]; - } - - public function testGetInvalidForeignKeyReferentialActionSQL() : void - { - $this->expectException('InvalidArgumentException'); - $this->platform->getForeignKeyReferentialActionSQL('unknown'); - } - - public function testGetUnknownDoctrineMappingType() : void - { - $this->expectException(DBALException::class); - $this->platform->getDoctrineTypeMapping('foobar'); - } - - public function testRegisterDoctrineMappingType() : void - { - $this->platform->registerDoctrineTypeMapping('foo', 'integer'); - self::assertEquals('integer', $this->platform->getDoctrineTypeMapping('foo')); - } - - public function testRegisterUnknownDoctrineMappingType() : void - { - $this->expectException(DBALException::class); - $this->platform->registerDoctrineTypeMapping('foo', 'bar'); - } - - /** - * @group DBAL-2594 - */ - public function testRegistersCommentedDoctrineMappingTypeImplicitly() : void - { - if (! Type::hasType('my_commented')) { - Type::addType('my_commented', CommentedType::class); - } - - $type = Type::getType('my_commented'); - $this->platform->registerDoctrineTypeMapping('foo', 'my_commented'); - - self::assertTrue($this->platform->isCommentedDoctrineType($type)); - } - - /** - * @group DBAL-939 - * @dataProvider getIsCommentedDoctrineType - */ - public function testIsCommentedDoctrineType(Type $type, bool $commented) : void - { - self::assertSame($commented, $this->platform->isCommentedDoctrineType($type)); - } - - /** - * @return mixed[] - */ - public function getIsCommentedDoctrineType() : iterable - { - $this->setUp(); - - $data = []; - - foreach (Type::getTypesMap() as $typeName => $className) { - $type = Type::getType($typeName); - - $data[$typeName] = [ - $type, - $type->requiresSQLCommentHint($this->platform), - ]; - } - - return $data; - } - - public function testCreateWithNoColumns() : void - { - $table = new Table('test'); - - $this->expectException(DBALException::class); - $sql = $this->platform->getCreateTableSQL($table); - } - - public function testGeneratesTableCreationSql() : void - { - $table = new Table('test'); - $table->addColumn('id', 'integer', ['notnull' => true, 'autoincrement' => true]); - $table->addColumn('test', 'string', ['notnull' => false, 'length' => 255]); - $table->setPrimaryKey(['id']); - - $sql = $this->platform->getCreateTableSQL($table); - self::assertEquals($this->getGenerateTableSql(), $sql[0]); - } - - abstract public function getGenerateTableSql() : string; - - public function testGenerateTableWithMultiColumnUniqueIndex() : void - { - $table = new Table('test'); - $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]); - $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]); - $table->addUniqueIndex(['foo', 'bar']); - - $sql = $this->platform->getCreateTableSQL($table); - self::assertEquals($this->getGenerateTableWithMultiColumnUniqueIndexSql(), $sql); - } - - /** - * @return string[] - */ - abstract public function getGenerateTableWithMultiColumnUniqueIndexSql() : array; - - public function testGeneratesIndexCreationSql() : void - { - $indexDef = new Index('my_idx', ['user_name', 'last_login']); - - self::assertEquals( - $this->getGenerateIndexSql(), - $this->platform->getCreateIndexSQL($indexDef, 'mytable') - ); - } - - abstract public function getGenerateIndexSql() : string; - - public function testGeneratesUniqueIndexCreationSql() : void - { - $indexDef = new Index('index_name', ['test', 'test2'], true); - - $sql = $this->platform->getCreateIndexSQL($indexDef, 'test'); - self::assertEquals($this->getGenerateUniqueIndexSql(), $sql); - } - - abstract public function getGenerateUniqueIndexSql() : string; - - public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes() : void - { - $where = 'test IS NULL AND test2 IS NOT NULL'; - $indexDef = new Index('name', ['test', 'test2'], false, false, [], ['where' => $where]); - $uniqueIndex = new Index('name', ['test', 'test2'], true, false, [], ['where' => $where]); - - $expected = ' WHERE ' . $where; - - $actuals = []; - - if ($this->supportsInlineIndexDeclaration()) { - $actuals[] = $this->platform->getIndexDeclarationSQL('name', $indexDef); - } - - $actuals[] = $this->platform->getUniqueConstraintDeclarationSQL('name', $uniqueIndex); - $actuals[] = $this->platform->getCreateIndexSQL($indexDef, 'table'); - - foreach ($actuals as $actual) { - if ($this->platform->supportsPartialIndexes()) { - self::assertStringEndsWith($expected, $actual, 'WHERE clause should be present'); - } else { - self::assertStringEndsNotWith($expected, $actual, 'WHERE clause should NOT be present'); - } - } - } - - public function testGeneratesForeignKeyCreationSql() : void - { - $fk = new ForeignKeyConstraint(['fk_name_id'], 'other_table', ['id'], ''); - - $sql = $this->platform->getCreateForeignKeySQL($fk, 'test'); - self::assertEquals($sql, $this->getGenerateForeignKeySql()); - } - - abstract public function getGenerateForeignKeySql() : string; - - public function testGeneratesConstraintCreationSql() : void - { - $idx = new Index('constraint_name', ['test'], true, false); - $sql = $this->platform->getCreateConstraintSQL($idx, 'test'); - self::assertEquals($this->getGenerateConstraintUniqueIndexSql(), $sql); - - $pk = new Index('constraint_name', ['test'], true, true); - $sql = $this->platform->getCreateConstraintSQL($pk, 'test'); - self::assertEquals($this->getGenerateConstraintPrimaryIndexSql(), $sql); - - $fk = new ForeignKeyConstraint(['fk_name'], 'foreign', ['id'], 'constraint_fk'); - $sql = $this->platform->getCreateConstraintSQL($fk, 'test'); - self::assertEquals($this->getGenerateConstraintForeignKeySql($fk), $sql); - } - - public function testGeneratesForeignKeySqlOnlyWhenSupportingForeignKeys() : void - { - $fk = new ForeignKeyConstraint(['fk_name'], 'foreign', ['id'], 'constraint_fk'); - - if ($this->platform->supportsForeignKeyConstraints()) { - self::assertIsString($this->platform->getCreateForeignKeySQL($fk, 'test')); - } else { - $this->expectException(DBALException::class); - $this->platform->getCreateForeignKeySQL($fk, 'test'); - } - } - - protected function getBitAndComparisonExpressionSql(string $value1, string $value2) : string - { - return '(' . $value1 . ' & ' . $value2 . ')'; - } - - /** - * @group DDC-1213 - */ - public function testGeneratesBitAndComparisonExpressionSql() : void - { - $sql = $this->platform->getBitAndComparisonExpression(2, 4); - self::assertEquals($this->getBitAndComparisonExpressionSql(2, 4), $sql); - } - - protected function getBitOrComparisonExpressionSql(string $value1, string $value2) : string - { - return '(' . $value1 . ' | ' . $value2 . ')'; - } - - /** - * @group DDC-1213 - */ - public function testGeneratesBitOrComparisonExpressionSql() : void - { - $sql = $this->platform->getBitOrComparisonExpression(2, 4); - self::assertEquals($this->getBitOrComparisonExpressionSql(2, 4), $sql); - } - - public function getGenerateConstraintUniqueIndexSql() : string - { - return 'ALTER TABLE test ADD CONSTRAINT constraint_name UNIQUE (test)'; - } - - public function getGenerateConstraintPrimaryIndexSql() : string - { - return 'ALTER TABLE test ADD CONSTRAINT constraint_name PRIMARY KEY (test)'; - } - - public function getGenerateConstraintForeignKeySql(ForeignKeyConstraint $fk) : string - { - $quotedForeignTable = $fk->getQuotedForeignTableName($this->platform); - - return sprintf( - 'ALTER TABLE test ADD CONSTRAINT constraint_fk FOREIGN KEY (fk_name) REFERENCES %s (id)', - $quotedForeignTable - ); - } - - /** - * @return string[] - */ - abstract public function getGenerateAlterTableSql() : array; - - public function testGeneratesTableAlterationSql() : void - { - $expectedSql = $this->getGenerateAlterTableSql(); - - $table = new Table('mytable'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->addColumn('foo', 'integer'); - $table->addColumn('bar', 'string'); - $table->addColumn('bloo', 'boolean'); - $table->setPrimaryKey(['id']); - - $tableDiff = new TableDiff('mytable'); - $tableDiff->fromTable = $table; - $tableDiff->newName = 'userlist'; - $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType('integer'), ['notnull' => false]); - $tableDiff->removedColumns['foo'] = new Column('foo', Type::getType('integer')); - $tableDiff->changedColumns['bar'] = new ColumnDiff( - 'bar', - new Column( - 'baz', - Type::getType('string'), - ['default' => 'def'] - ), - ['type', 'notnull', 'default'] - ); - $tableDiff->changedColumns['bloo'] = new ColumnDiff( - 'bloo', - new Column( - 'bloo', - Type::getType('boolean'), - ['default' => false] - ), - ['type', 'notnull', 'default'] - ); - - $sql = $this->platform->getAlterTableSQL($tableDiff); - - self::assertEquals($expectedSql, $sql); - } - - public function testGetCustomColumnDeclarationSql() : void - { - $field = ['columnDefinition' => 'MEDIUMINT(6) UNSIGNED']; - self::assertEquals('foo MEDIUMINT(6) UNSIGNED', $this->platform->getColumnDeclarationSQL('foo', $field)); - } - - public function testGetCreateTableSqlDispatchEvent() : void - { - $listenerMock = $this->getMockBuilder('GetCreateTableSqlDispatchEvenListener') - ->setMethods(['onSchemaCreateTable', 'onSchemaCreateTableColumn']) - ->getMock(); - $listenerMock - ->expects($this->once()) - ->method('onSchemaCreateTable'); - $listenerMock - ->expects($this->exactly(2)) - ->method('onSchemaCreateTableColumn'); - - $eventManager = new EventManager(); - $eventManager->addEventListener([Events::onSchemaCreateTable, Events::onSchemaCreateTableColumn], $listenerMock); - - $this->platform->setEventManager($eventManager); - - $table = new Table('test'); - $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]); - $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]); - - $this->platform->getCreateTableSQL($table); - } - - public function testGetDropTableSqlDispatchEvent() : void - { - $listenerMock = $this->getMockBuilder('GetDropTableSqlDispatchEventListener') - ->setMethods(['onSchemaDropTable']) - ->getMock(); - $listenerMock - ->expects($this->once()) - ->method('onSchemaDropTable'); - - $eventManager = new EventManager(); - $eventManager->addEventListener([Events::onSchemaDropTable], $listenerMock); - - $this->platform->setEventManager($eventManager); - - $this->platform->getDropTableSQL('TABLE'); - } - - public function testGetAlterTableSqlDispatchEvent() : void - { - $events = [ - 'onSchemaAlterTable', - 'onSchemaAlterTableAddColumn', - 'onSchemaAlterTableRemoveColumn', - 'onSchemaAlterTableChangeColumn', - 'onSchemaAlterTableRenameColumn', - ]; - - $listenerMock = $this->getMockBuilder('GetAlterTableSqlDispatchEvenListener') - ->setMethods($events) - ->getMock(); - $listenerMock - ->expects($this->once()) - ->method('onSchemaAlterTable'); - $listenerMock - ->expects($this->once()) - ->method('onSchemaAlterTableAddColumn'); - $listenerMock - ->expects($this->once()) - ->method('onSchemaAlterTableRemoveColumn'); - $listenerMock - ->expects($this->once()) - ->method('onSchemaAlterTableChangeColumn'); - $listenerMock - ->expects($this->once()) - ->method('onSchemaAlterTableRenameColumn'); - - $eventManager = new EventManager(); - $events = [ - Events::onSchemaAlterTable, - Events::onSchemaAlterTableAddColumn, - Events::onSchemaAlterTableRemoveColumn, - Events::onSchemaAlterTableChangeColumn, - Events::onSchemaAlterTableRenameColumn, - ]; - $eventManager->addEventListener($events, $listenerMock); - - $this->platform->setEventManager($eventManager); - - $table = new Table('mytable'); - $table->addColumn('removed', 'integer'); - $table->addColumn('changed', 'integer'); - $table->addColumn('renamed', 'integer'); - - $tableDiff = new TableDiff('mytable'); - $tableDiff->fromTable = $table; - $tableDiff->addedColumns['added'] = new Column('added', Type::getType('integer'), []); - $tableDiff->removedColumns['removed'] = new Column('removed', Type::getType('integer'), []); - $tableDiff->changedColumns['changed'] = new ColumnDiff( - 'changed', - new Column( - 'changed2', - Type::getType('string'), - [] - ), - [] - ); - $tableDiff->renamedColumns['renamed'] = new Column('renamed2', Type::getType('integer'), []); - - $this->platform->getAlterTableSQL($tableDiff); - } - - /** - * @group DBAL-42 - */ - public function testCreateTableColumnComments() : void - { - $table = new Table('test'); - $table->addColumn('id', 'integer', ['comment' => 'This is a comment']); - $table->setPrimaryKey(['id']); - - self::assertEquals($this->getCreateTableColumnCommentsSQL(), $this->platform->getCreateTableSQL($table)); - } - - /** - * @group DBAL-42 - */ - public function testAlterTableColumnComments() : void - { - $tableDiff = new TableDiff('mytable'); - $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType('integer'), ['comment' => 'A comment']); - $tableDiff->changedColumns['foo'] = new ColumnDiff( - 'foo', - new Column( - 'foo', - Type::getType('string') - ), - ['comment'] - ); - $tableDiff->changedColumns['bar'] = new ColumnDiff( - 'bar', - new Column( - 'baz', - Type::getType('string'), - ['comment' => 'B comment'] - ), - ['comment'] - ); - - self::assertEquals($this->getAlterTableColumnCommentsSQL(), $this->platform->getAlterTableSQL($tableDiff)); - } - - public function testCreateTableColumnTypeComments() : void - { - $table = new Table('test'); - $table->addColumn('id', 'integer'); - $table->addColumn('data', 'array'); - $table->setPrimaryKey(['id']); - - self::assertEquals($this->getCreateTableColumnTypeCommentsSQL(), $this->platform->getCreateTableSQL($table)); - } - - /** - * @return string[] - */ - public function getCreateTableColumnCommentsSQL() : array - { - $this->markTestSkipped('Platform does not support Column comments.'); - } - - /** - * @return string[] - */ - public function getAlterTableColumnCommentsSQL() : array - { - $this->markTestSkipped('Platform does not support Column comments.'); - } - - /** - * @return string[] - */ - public function getCreateTableColumnTypeCommentsSQL() : array - { - $this->markTestSkipped('Platform does not support Column comments.'); - } - - public function testGetDefaultValueDeclarationSQL() : void - { - // non-timestamp value will get single quotes - $field = [ - 'type' => Type::getType('string'), - 'default' => 'non_timestamp', - ]; - - self::assertEquals(" DEFAULT 'non_timestamp'", $this->platform->getDefaultValueDeclarationSQL($field)); - } - - /** - * @group 2859 - */ - public function testGetDefaultValueDeclarationSQLDateTime() : void - { - // timestamps on datetime types should not be quoted - foreach (['datetime', 'datetimetz', 'datetime_immutable', 'datetimetz_immutable'] as $type) { - $field = [ - 'type' => Type::getType($type), - 'default' => $this->platform->getCurrentTimestampSQL(), - ]; - - self::assertSame( - ' DEFAULT ' . $this->platform->getCurrentTimestampSQL(), - $this->platform->getDefaultValueDeclarationSQL($field) - ); - } - } - - public function testGetDefaultValueDeclarationSQLForIntegerTypes() : void - { - foreach (['bigint', 'integer', 'smallint'] as $type) { - $field = [ - 'type' => Type::getType($type), - 'default' => 1, - ]; - - self::assertEquals( - ' DEFAULT 1', - $this->platform->getDefaultValueDeclarationSQL($field) - ); - } - } - - /** - * @group 2859 - */ - public function testGetDefaultValueDeclarationSQLForDateType() : void - { - $currentDateSql = $this->platform->getCurrentDateSQL(); - foreach (['date', 'date_immutable'] as $type) { - $field = [ - 'type' => Type::getType($type), - 'default' => $currentDateSql, - ]; - - self::assertSame( - ' DEFAULT ' . $currentDateSql, - $this->platform->getDefaultValueDeclarationSQL($field) - ); - } - } - - /** - * @group DBAL-45 - */ - public function testKeywordList() : void - { - $keywordList = $this->platform->getReservedKeywordsList(); - self::assertInstanceOf(KeywordList::class, $keywordList); - - self::assertTrue($keywordList->isKeyword('table')); - } - - /** - * @group DBAL-374 - */ - public function testQuotedColumnInPrimaryKeyPropagation() : void - { - $table = new Table('`quoted`'); - $table->addColumn('create', 'string'); - $table->setPrimaryKey(['create']); - - $sql = $this->platform->getCreateTableSQL($table); - self::assertEquals($this->getQuotedColumnInPrimaryKeySQL(), $sql); - } - - /** - * @return string[] - */ - abstract protected function getQuotedColumnInPrimaryKeySQL() : array; - - /** - * @return string[] - */ - abstract protected function getQuotedColumnInIndexSQL() : array; - - /** - * @return string[] - */ - abstract protected function getQuotedNameInIndexSQL() : array; - - /** - * @return string[] - */ - abstract protected function getQuotedColumnInForeignKeySQL() : array; - - /** - * @group DBAL-374 - */ - public function testQuotedColumnInIndexPropagation() : void - { - $table = new Table('`quoted`'); - $table->addColumn('create', 'string'); - $table->addIndex(['create']); - - $sql = $this->platform->getCreateTableSQL($table); - self::assertEquals($this->getQuotedColumnInIndexSQL(), $sql); - } - - public function testQuotedNameInIndexSQL() : void - { - $table = new Table('test'); - $table->addColumn('column1', 'string'); - $table->addIndex(['column1'], '`key`'); - - $sql = $this->platform->getCreateTableSQL($table); - self::assertEquals($this->getQuotedNameInIndexSQL(), $sql); - } - - /** - * @group DBAL-374 - */ - public function testQuotedColumnInForeignKeyPropagation() : void - { - $table = new Table('`quoted`'); - $table->addColumn('create', 'string'); - $table->addColumn('foo', 'string'); - $table->addColumn('`bar`', 'string'); - - // Foreign table with reserved keyword as name (needs quotation). - $foreignTable = new Table('foreign'); - $foreignTable->addColumn('create', 'string'); // Foreign column with reserved keyword as name (needs quotation). - $foreignTable->addColumn('bar', 'string'); // Foreign column with non-reserved keyword as name (does not need quotation). - $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite). - - $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_RESERVED_KEYWORD'); - - // Foreign table with non-reserved keyword as name (does not need quotation). - $foreignTable = new Table('foo'); - $foreignTable->addColumn('create', 'string'); // Foreign column with reserved keyword as name (needs quotation). - $foreignTable->addColumn('bar', 'string'); // Foreign column with non-reserved keyword as name (does not need quotation). - $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite). - - $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_NON_RESERVED_KEYWORD'); - - // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite). - $foreignTable = new Table('`foo-bar`'); - $foreignTable->addColumn('create', 'string'); // Foreign column with reserved keyword as name (needs quotation). - $foreignTable->addColumn('bar', 'string'); // Foreign column with non-reserved keyword as name (does not need quotation). - $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite). - - $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_INTENDED_QUOTATION'); - - $sql = $this->platform->getCreateTableSQL($table, AbstractPlatform::CREATE_FOREIGNKEYS); - self::assertEquals($this->getQuotedColumnInForeignKeySQL(), $sql); - } - - /** - * @group DBAL-1051 - */ - public function testQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : void - { - $index = new Index('select', ['foo'], true); - - self::assertSame( - $this->getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(), - $this->platform->getUniqueConstraintDeclarationSQL('select', $index) - ); - } - - abstract protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string; - - /** - * @group DBAL-2270 - */ - public function testQuotesReservedKeywordInTruncateTableSQL() : void - { - self::assertSame( - $this->getQuotesReservedKeywordInTruncateTableSQL(), - $this->platform->getTruncateTableSQL('select') - ); - } - - abstract protected function getQuotesReservedKeywordInTruncateTableSQL() : string; - - /** - * @group DBAL-1051 - */ - public function testQuotesReservedKeywordInIndexDeclarationSQL() : void - { - $index = new Index('select', ['foo']); - - if (! $this->supportsInlineIndexDeclaration()) { - $this->expectException(DBALException::class); - } - - self::assertSame( - $this->getQuotesReservedKeywordInIndexDeclarationSQL(), - $this->platform->getIndexDeclarationSQL('select', $index) - ); - } - - abstract protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string; - - protected function supportsInlineIndexDeclaration() : bool - { - return true; - } - - public function testSupportsCommentOnStatement() : void - { - self::assertSame($this->supportsCommentOnStatement(), $this->platform->supportsCommentOnStatement()); - } - - protected function supportsCommentOnStatement() : bool - { - return false; - } - - public function testGetCreateSchemaSQL() : void - { - $this->expectException(DBALException::class); - - $this->platform->getCreateSchemaSQL('schema'); - } - - /** - * @group DBAL-585 - */ - public function testAlterTableChangeQuotedColumn() : void - { - $tableDiff = new TableDiff('mytable'); - $tableDiff->fromTable = new Table('mytable'); - $tableDiff->changedColumns['foo'] = new ColumnDiff( - 'select', - new Column( - 'select', - Type::getType('string') - ), - ['type'] - ); - - self::assertStringContainsString( - $this->platform->quoteIdentifier('select'), - implode(';', $this->platform->getAlterTableSQL($tableDiff)) - ); - } - - /** - * @group DBAL-563 - */ - public function testUsesSequenceEmulatedIdentityColumns() : void - { - self::assertFalse($this->platform->usesSequenceEmulatedIdentityColumns()); - } - - /** - * @group DBAL-563 - */ - public function testReturnsIdentitySequenceName() : void - { - $this->expectException(DBALException::class); - - $this->platform->getIdentitySequenceName('mytable', 'mycolumn'); - } - - public function testReturnsBinaryDefaultLength() : void - { - self::assertSame($this->getBinaryDefaultLength(), $this->platform->getBinaryDefaultLength()); - } - - protected function getBinaryDefaultLength() : int - { - return 255; - } - - public function testReturnsBinaryMaxLength() : void - { - self::assertSame($this->getBinaryMaxLength(), $this->platform->getBinaryMaxLength()); - } - - protected function getBinaryMaxLength() : int - { - return 4000; - } - - public function testReturnsBinaryTypeDeclarationSQL() : void - { - $this->expectException(DBALException::class); - - $this->platform->getBinaryTypeDeclarationSQL([]); - } - - public function testReturnsBinaryTypeLongerThanMaxDeclarationSQL() : void - { - $this->markTestSkipped('Not applicable to the platform'); - } - - /** - * @group DBAL-553 - */ - public function hasNativeJsonType() : void - { - self::assertFalse($this->platform->hasNativeJsonType()); - } - - /** - * @group DBAL-553 - */ - public function testReturnsJsonTypeDeclarationSQL() : void - { - $column = [ - 'length' => 666, - 'notnull' => true, - 'type' => Type::getType('json_array'), - ]; - - self::assertSame( - $this->platform->getClobTypeDeclarationSQL($column), - $this->platform->getJsonTypeDeclarationSQL($column) - ); - } - - /** - * @group DBAL-234 - */ - public function testAlterTableRenameIndex() : void - { - $tableDiff = new TableDiff('mytable'); - $tableDiff->fromTable = new Table('mytable'); - $tableDiff->fromTable->addColumn('id', 'integer'); - $tableDiff->fromTable->setPrimaryKey(['id']); - $tableDiff->renamedIndexes = [ - 'idx_foo' => new Index('idx_bar', ['id']), - ]; - - self::assertSame( - $this->getAlterTableRenameIndexSQL(), - $this->platform->getAlterTableSQL($tableDiff) - ); - } - - /** - * @return string[] - * - * @group DBAL-234 - */ - protected function getAlterTableRenameIndexSQL() : array - { - return [ - 'DROP INDEX idx_foo', - 'CREATE INDEX idx_bar ON mytable (id)', - ]; - } - - /** - * @group DBAL-234 - */ - public function testQuotesAlterTableRenameIndex() : void - { - $tableDiff = new TableDiff('table'); - $tableDiff->fromTable = new Table('table'); - $tableDiff->fromTable->addColumn('id', 'integer'); - $tableDiff->fromTable->setPrimaryKey(['id']); - $tableDiff->renamedIndexes = [ - 'create' => new Index('select', ['id']), - '`foo`' => new Index('`bar`', ['id']), - ]; - - self::assertSame( - $this->getQuotedAlterTableRenameIndexSQL(), - $this->platform->getAlterTableSQL($tableDiff) - ); - } - - /** - * @return string[] - * - * @group DBAL-234 - */ - protected function getQuotedAlterTableRenameIndexSQL() : array - { - return [ - 'DROP INDEX "create"', - 'CREATE INDEX "select" ON "table" (id)', - 'DROP INDEX "foo"', - 'CREATE INDEX "bar" ON "table" (id)', - ]; - } - - /** - * @group DBAL-835 - */ - public function testQuotesAlterTableRenameColumn() : void - { - $fromTable = new Table('mytable'); - - $fromTable->addColumn('unquoted1', 'integer', ['comment' => 'Unquoted 1']); - $fromTable->addColumn('unquoted2', 'integer', ['comment' => 'Unquoted 2']); - $fromTable->addColumn('unquoted3', 'integer', ['comment' => 'Unquoted 3']); - - $fromTable->addColumn('create', 'integer', ['comment' => 'Reserved keyword 1']); - $fromTable->addColumn('table', 'integer', ['comment' => 'Reserved keyword 2']); - $fromTable->addColumn('select', 'integer', ['comment' => 'Reserved keyword 3']); - - $fromTable->addColumn('`quoted1`', 'integer', ['comment' => 'Quoted 1']); - $fromTable->addColumn('`quoted2`', 'integer', ['comment' => 'Quoted 2']); - $fromTable->addColumn('`quoted3`', 'integer', ['comment' => 'Quoted 3']); - - $toTable = new Table('mytable'); - - $toTable->addColumn('unquoted', 'integer', ['comment' => 'Unquoted 1']); // unquoted -> unquoted - $toTable->addColumn('where', 'integer', ['comment' => 'Unquoted 2']); // unquoted -> reserved keyword - $toTable->addColumn('`foo`', 'integer', ['comment' => 'Unquoted 3']); // unquoted -> quoted - - $toTable->addColumn('reserved_keyword', 'integer', ['comment' => 'Reserved keyword 1']); // reserved keyword -> unquoted - $toTable->addColumn('from', 'integer', ['comment' => 'Reserved keyword 2']); // reserved keyword -> reserved keyword - $toTable->addColumn('`bar`', 'integer', ['comment' => 'Reserved keyword 3']); // reserved keyword -> quoted - - $toTable->addColumn('quoted', 'integer', ['comment' => 'Quoted 1']); // quoted -> unquoted - $toTable->addColumn('and', 'integer', ['comment' => 'Quoted 2']); // quoted -> reserved keyword - $toTable->addColumn('`baz`', 'integer', ['comment' => 'Quoted 3']); // quoted -> quoted - - $comparator = new Comparator(); - - self::assertEquals( - $this->getQuotedAlterTableRenameColumnSQL(), - $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable)) - ); - } - - /** - * Returns SQL statements for {@link testQuotesAlterTableRenameColumn}. - * - * @return string[] - * - * @group DBAL-835 - */ - abstract protected function getQuotedAlterTableRenameColumnSQL() : array; - - /** - * @group DBAL-835 - */ - public function testQuotesAlterTableChangeColumnLength() : void - { - $fromTable = new Table('mytable'); - - $fromTable->addColumn('unquoted1', 'string', ['comment' => 'Unquoted 1', 'length' => 10]); - $fromTable->addColumn('unquoted2', 'string', ['comment' => 'Unquoted 2', 'length' => 10]); - $fromTable->addColumn('unquoted3', 'string', ['comment' => 'Unquoted 3', 'length' => 10]); - - $fromTable->addColumn('create', 'string', ['comment' => 'Reserved keyword 1', 'length' => 10]); - $fromTable->addColumn('table', 'string', ['comment' => 'Reserved keyword 2', 'length' => 10]); - $fromTable->addColumn('select', 'string', ['comment' => 'Reserved keyword 3', 'length' => 10]); - - $toTable = new Table('mytable'); - - $toTable->addColumn('unquoted1', 'string', ['comment' => 'Unquoted 1', 'length' => 255]); - $toTable->addColumn('unquoted2', 'string', ['comment' => 'Unquoted 2', 'length' => 255]); - $toTable->addColumn('unquoted3', 'string', ['comment' => 'Unquoted 3', 'length' => 255]); - - $toTable->addColumn('create', 'string', ['comment' => 'Reserved keyword 1', 'length' => 255]); - $toTable->addColumn('table', 'string', ['comment' => 'Reserved keyword 2', 'length' => 255]); - $toTable->addColumn('select', 'string', ['comment' => 'Reserved keyword 3', 'length' => 255]); - - $comparator = new Comparator(); - - self::assertEquals( - $this->getQuotedAlterTableChangeColumnLengthSQL(), - $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable)) - ); - } - - /** - * Returns SQL statements for {@link testQuotesAlterTableChangeColumnLength}. - * - * @return string[] - * - * @group DBAL-835 - */ - abstract protected function getQuotedAlterTableChangeColumnLengthSQL() : array; - - /** - * @group DBAL-807 - */ - public function testAlterTableRenameIndexInSchema() : void - { - $tableDiff = new TableDiff('myschema.mytable'); - $tableDiff->fromTable = new Table('myschema.mytable'); - $tableDiff->fromTable->addColumn('id', 'integer'); - $tableDiff->fromTable->setPrimaryKey(['id']); - $tableDiff->renamedIndexes = [ - 'idx_foo' => new Index('idx_bar', ['id']), - ]; - - self::assertSame( - $this->getAlterTableRenameIndexInSchemaSQL(), - $this->platform->getAlterTableSQL($tableDiff) - ); - } - - /** - * @return string[] - * - * @group DBAL-807 - */ - protected function getAlterTableRenameIndexInSchemaSQL() : array - { - return [ - 'DROP INDEX idx_foo', - 'CREATE INDEX idx_bar ON myschema.mytable (id)', - ]; - } - - /** - * @group DBAL-807 - */ - public function testQuotesAlterTableRenameIndexInSchema() : void - { - $tableDiff = new TableDiff('`schema`.table'); - $tableDiff->fromTable = new Table('`schema`.table'); - $tableDiff->fromTable->addColumn('id', 'integer'); - $tableDiff->fromTable->setPrimaryKey(['id']); - $tableDiff->renamedIndexes = [ - 'create' => new Index('select', ['id']), - '`foo`' => new Index('`bar`', ['id']), - ]; - - self::assertSame( - $this->getQuotedAlterTableRenameIndexInSchemaSQL(), - $this->platform->getAlterTableSQL($tableDiff) - ); - } - - /** - * @return string[] - * - * @group DBAL-234 - */ - protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array - { - return [ - 'DROP INDEX "schema"."create"', - 'CREATE INDEX "select" ON "schema"."table" (id)', - 'DROP INDEX "schema"."foo"', - 'CREATE INDEX "bar" ON "schema"."table" (id)', - ]; - } - - /** - * @group DBAL-1237 - */ - public function testQuotesDropForeignKeySQL() : void - { - if (! $this->platform->supportsForeignKeyConstraints()) { - $this->markTestSkipped( - sprintf('%s does not support foreign key constraints.', get_class($this->platform)) - ); - } - - $tableName = 'table'; - $table = new Table($tableName); - $foreignKeyName = 'select'; - $foreignKey = new ForeignKeyConstraint([], 'foo', [], 'select'); - $expectedSql = $this->getQuotesDropForeignKeySQL(); - - self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKeyName, $tableName)); - self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKey, $table)); - } - - protected function getQuotesDropForeignKeySQL() : string - { - return 'ALTER TABLE "table" DROP FOREIGN KEY "select"'; - } - - /** - * @group DBAL-1237 - */ - public function testQuotesDropConstraintSQL() : void - { - $tableName = 'table'; - $table = new Table($tableName); - $constraintName = 'select'; - $constraint = new ForeignKeyConstraint([], 'foo', [], 'select'); - $expectedSql = $this->getQuotesDropConstraintSQL(); - - self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraintName, $tableName)); - self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraint, $table)); - } - - protected function getQuotesDropConstraintSQL() : string - { - return 'ALTER TABLE "table" DROP CONSTRAINT "select"'; - } - - protected function getStringLiteralQuoteCharacter() : string - { - return "'"; - } - - public function testGetStringLiteralQuoteCharacter() : void - { - self::assertSame($this->getStringLiteralQuoteCharacter(), $this->platform->getStringLiteralQuoteCharacter()); - } - - protected function getQuotedCommentOnColumnSQLWithoutQuoteCharacter() : string - { - return "COMMENT ON COLUMN mytable.id IS 'This is a comment'"; - } - - public function testGetCommentOnColumnSQLWithoutQuoteCharacter() : void - { - self::assertEquals( - $this->getQuotedCommentOnColumnSQLWithoutQuoteCharacter(), - $this->platform->getCommentOnColumnSQL('mytable', 'id', 'This is a comment') - ); - } - - protected function getQuotedCommentOnColumnSQLWithQuoteCharacter() : string - { - return "COMMENT ON COLUMN mytable.id IS 'It''s a quote !'"; - } - - public function testGetCommentOnColumnSQLWithQuoteCharacter() : void - { - $c = $this->getStringLiteralQuoteCharacter(); - - self::assertEquals( - $this->getQuotedCommentOnColumnSQLWithQuoteCharacter(), - $this->platform->getCommentOnColumnSQL('mytable', 'id', 'It' . $c . 's a quote !') - ); - } - - /** - * @see testGetCommentOnColumnSQL - * - * @return string[] - */ - abstract protected function getCommentOnColumnSQL() : array; - - /** - * @group DBAL-1004 - */ - public function testGetCommentOnColumnSQL() : void - { - self::assertSame( - $this->getCommentOnColumnSQL(), - [ - $this->platform->getCommentOnColumnSQL('foo', 'bar', 'comment'), // regular identifiers - $this->platform->getCommentOnColumnSQL('`Foo`', '`BAR`', 'comment'), // explicitly quoted identifiers - $this->platform->getCommentOnColumnSQL('select', 'from', 'comment'), // reserved keyword identifiers - ] - ); - } - - /** - * @group DBAL-1176 - * @dataProvider getGeneratesInlineColumnCommentSQL - */ - public function testGeneratesInlineColumnCommentSQL(?string $comment, string $expectedSql) : void - { - if (! $this->platform->supportsInlineColumnComments()) { - $this->markTestSkipped(sprintf('%s does not support inline column comments.', get_class($this->platform))); - } - - self::assertSame($expectedSql, $this->platform->getInlineColumnCommentSQL($comment)); - } - - /** - * @return mixed[][] - */ - public static function getGeneratesInlineColumnCommentSQL() : iterable - { - return [ - 'regular comment' => ['Regular comment', static::getInlineColumnRegularCommentSQL()], - 'comment requiring escaping' => [ - sprintf( - 'Using inline comment delimiter %s works', - static::getInlineColumnCommentDelimiter() - ), - static::getInlineColumnCommentRequiringEscapingSQL(), - ], - 'empty comment' => ['', static::getInlineColumnEmptyCommentSQL()], - ]; - } - - protected static function getInlineColumnCommentDelimiter() : string - { - return "'"; - } - - protected static function getInlineColumnRegularCommentSQL() : string - { - return "COMMENT 'Regular comment'"; - } - - protected static function getInlineColumnCommentRequiringEscapingSQL() : string - { - return "COMMENT 'Using inline comment delimiter '' works'"; - } - - protected static function getInlineColumnEmptyCommentSQL() : string - { - return "COMMENT ''"; - } - - protected function getQuotedStringLiteralWithoutQuoteCharacter() : string - { - return "'No quote'"; - } - - protected function getQuotedStringLiteralWithQuoteCharacter() : string - { - return "'It''s a quote'"; - } - - protected function getQuotedStringLiteralQuoteCharacter() : string - { - return "''''"; - } - - /** - * @group DBAL-1176 - */ - public function testThrowsExceptionOnGeneratingInlineColumnCommentSQLIfUnsupported() : void - { - if ($this->platform->supportsInlineColumnComments()) { - $this->markTestSkipped(sprintf('%s supports inline column comments.', get_class($this->platform))); - } - - $this->expectException(DBALException::class); - $this->expectExceptionMessage("Operation 'Doctrine\\DBAL\\Platforms\\AbstractPlatform::getInlineColumnCommentSQL' is not supported by platform."); - $this->expectExceptionCode(0); - - $this->platform->getInlineColumnCommentSQL('unsupported'); - } - - public function testQuoteStringLiteral() : void - { - $c = $this->getStringLiteralQuoteCharacter(); - - self::assertEquals( - $this->getQuotedStringLiteralWithoutQuoteCharacter(), - $this->platform->quoteStringLiteral('No quote') - ); - self::assertEquals( - $this->getQuotedStringLiteralWithQuoteCharacter(), - $this->platform->quoteStringLiteral('It' . $c . 's a quote') - ); - self::assertEquals( - $this->getQuotedStringLiteralQuoteCharacter(), - $this->platform->quoteStringLiteral($c) - ); - } - - /** - * @group DBAL-423 - */ - public function testReturnsGuidTypeDeclarationSQL() : void - { - $this->expectException(DBALException::class); - - $this->platform->getGuidTypeDeclarationSQL([]); - } - - /** - * @group DBAL-1010 - */ - public function testGeneratesAlterTableRenameColumnSQL() : void - { - $table = new Table('foo'); - $table->addColumn( - 'bar', - 'integer', - ['notnull' => true, 'default' => 666, 'comment' => 'rename test'] - ); - - $tableDiff = new TableDiff('foo'); - $tableDiff->fromTable = $table; - $tableDiff->renamedColumns['bar'] = new Column( - 'baz', - Type::getType('integer'), - ['notnull' => true, 'default' => 666, 'comment' => 'rename test'] - ); - - self::assertSame($this->getAlterTableRenameColumnSQL(), $this->platform->getAlterTableSQL($tableDiff)); - } - - /** - * @return string[] - */ - abstract public function getAlterTableRenameColumnSQL() : array; - - /** - * @group DBAL-1016 - */ - public function testQuotesTableIdentifiersInAlterTableSQL() : void - { - $table = new Table('"foo"'); - $table->addColumn('id', 'integer'); - $table->addColumn('fk', 'integer'); - $table->addColumn('fk2', 'integer'); - $table->addColumn('fk3', 'integer'); - $table->addColumn('bar', 'integer'); - $table->addColumn('baz', 'integer'); - $table->addForeignKeyConstraint('fk_table', ['fk'], ['id'], [], 'fk1'); - $table->addForeignKeyConstraint('fk_table', ['fk2'], ['id'], [], 'fk2'); - - $tableDiff = new TableDiff('"foo"'); - $tableDiff->fromTable = $table; - $tableDiff->newName = 'table'; - $tableDiff->addedColumns['bloo'] = new Column('bloo', Type::getType('integer')); - $tableDiff->changedColumns['bar'] = new ColumnDiff( - 'bar', - new Column('bar', Type::getType('integer'), ['notnull' => false]), - ['notnull'], - $table->getColumn('bar') - ); - $tableDiff->renamedColumns['id'] = new Column('war', Type::getType('integer')); - $tableDiff->removedColumns['baz'] = new Column('baz', Type::getType('integer')); - $tableDiff->addedForeignKeys[] = new ForeignKeyConstraint(['fk3'], 'fk_table', ['id'], 'fk_add'); - $tableDiff->changedForeignKeys[] = new ForeignKeyConstraint(['fk2'], 'fk_table2', ['id'], 'fk2'); - $tableDiff->removedForeignKeys[] = new ForeignKeyConstraint(['fk'], 'fk_table', ['id'], 'fk1'); - - self::assertSame( - $this->getQuotesTableIdentifiersInAlterTableSQL(), - $this->platform->getAlterTableSQL($tableDiff) - ); - } - - /** - * @return string[] - */ - abstract protected function getQuotesTableIdentifiersInAlterTableSQL() : array; - - /** - * @group DBAL-1090 - */ - public function testAlterStringToFixedString() : void - { - $table = new Table('mytable'); - $table->addColumn('name', 'string', ['length' => 2]); - - $tableDiff = new TableDiff('mytable'); - $tableDiff->fromTable = $table; - - $tableDiff->changedColumns['name'] = new ColumnDiff( - 'name', - new Column( - 'name', - Type::getType('string'), - ['fixed' => true, 'length' => 2] - ), - ['fixed'] - ); - - $sql = $this->platform->getAlterTableSQL($tableDiff); - - $expectedSql = $this->getAlterStringToFixedStringSQL(); - - self::assertEquals($expectedSql, $sql); - } - - /** - * @return string[] - */ - abstract protected function getAlterStringToFixedStringSQL() : array; - - /** - * @group DBAL-1062 - */ - public function testGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : void - { - $foreignTable = new Table('foreign_table'); - $foreignTable->addColumn('id', 'integer'); - $foreignTable->setPrimaryKey(['id']); - - $primaryTable = new Table('mytable'); - $primaryTable->addColumn('foo', 'integer'); - $primaryTable->addColumn('bar', 'integer'); - $primaryTable->addColumn('baz', 'integer'); - $primaryTable->addIndex(['foo'], 'idx_foo'); - $primaryTable->addIndex(['bar'], 'idx_bar'); - $primaryTable->addForeignKeyConstraint($foreignTable, ['foo'], ['id'], [], 'fk_foo'); - $primaryTable->addForeignKeyConstraint($foreignTable, ['bar'], ['id'], [], 'fk_bar'); - - $tableDiff = new TableDiff('mytable'); - $tableDiff->fromTable = $primaryTable; - $tableDiff->renamedIndexes['idx_foo'] = new Index('idx_foo_renamed', ['foo']); - - self::assertSame( - $this->getGeneratesAlterTableRenameIndexUsedByForeignKeySQL(), - $this->platform->getAlterTableSQL($tableDiff) - ); - } - - /** - * @return string[] - */ - abstract protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array; - - /** - * @param mixed[] $column - * - * @group DBAL-1082 - * @dataProvider getGeneratesDecimalTypeDeclarationSQL - */ - public function testGeneratesDecimalTypeDeclarationSQL(array $column, string $expectedSql) : void - { - self::assertSame($expectedSql, $this->platform->getDecimalTypeDeclarationSQL($column)); - } - - /** - * @return mixed[][] - */ - public static function getGeneratesDecimalTypeDeclarationSQL() : iterable - { - return [ - [[], 'NUMERIC(10, 0)'], - [['unsigned' => true], 'NUMERIC(10, 0)'], - [['unsigned' => false], 'NUMERIC(10, 0)'], - [['precision' => 5], 'NUMERIC(5, 0)'], - [['scale' => 5], 'NUMERIC(10, 5)'], - [['precision' => 8, 'scale' => 2], 'NUMERIC(8, 2)'], - ]; - } - - /** - * @param mixed[] $column - * - * @group DBAL-1082 - * @dataProvider getGeneratesFloatDeclarationSQL - */ - public function testGeneratesFloatDeclarationSQL(array $column, string $expectedSql) : void - { - self::assertSame($expectedSql, $this->platform->getFloatDeclarationSQL($column)); - } - - /** - * @return mixed[][] - */ - public static function getGeneratesFloatDeclarationSQL() : iterable - { - return [ - [[], 'DOUBLE PRECISION'], - [['unsigned' => true], 'DOUBLE PRECISION'], - [['unsigned' => false], 'DOUBLE PRECISION'], - [['precision' => 5], 'DOUBLE PRECISION'], - [['scale' => 5], 'DOUBLE PRECISION'], - [['precision' => 8, 'scale' => 2], 'DOUBLE PRECISION'], - ]; - } - - public function testItEscapesStringsForLike() : void - { - self::assertSame( - '\_25\% off\_ your next purchase \\\\o/', - $this->platform->escapeStringForLike('_25% off_ your next purchase \o/', '\\') - ); - } - - public function testZeroOffsetWithoutLimitIsIgnored() : void - { - $query = 'SELECT * FROM user'; - - self::assertSame( - $query, - $this->platform->modifyLimitQuery($query, null, 0) - ); - } -} diff --git a/tests/Unit/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php b/tests/Unit/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php deleted file mode 100644 index 228ffc8..0000000 --- a/tests/Unit/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php +++ /dev/null @@ -1,1068 +0,0 @@ - 'CASCADE'] - ); - self::assertEquals( - 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE', - $this->platform->getForeignKeyDeclarationSQL($foreignKey) - ); - - $foreignKey = new ForeignKeyConstraint( - ['foreign_id'], - 'my_table', - ['id'], - 'my_fk', - ['match' => 'full'] - ); - self::assertEquals( - 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) MATCH full NOT DEFERRABLE INITIALLY IMMEDIATE', - $this->platform->getForeignKeyDeclarationSQL($foreignKey) - ); - - $foreignKey = new ForeignKeyConstraint( - ['foreign_id'], - 'my_table', - ['id'], - 'my_fk', - ['deferrable' => true] - ); - self::assertEquals( - 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) DEFERRABLE INITIALLY IMMEDIATE', - $this->platform->getForeignKeyDeclarationSQL($foreignKey) - ); - - $foreignKey = new ForeignKeyConstraint( - ['foreign_id'], - 'my_table', - ['id'], - 'my_fk', - ['deferred' => true] - ); - self::assertEquals( - 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) NOT DEFERRABLE INITIALLY DEFERRED', - $this->platform->getForeignKeyDeclarationSQL($foreignKey) - ); - - $foreignKey = new ForeignKeyConstraint( - ['foreign_id'], - 'my_table', - ['id'], - 'my_fk', - ['feferred' => true] - ); - self::assertEquals( - 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) NOT DEFERRABLE INITIALLY DEFERRED', - $this->platform->getForeignKeyDeclarationSQL($foreignKey) - ); - - $foreignKey = new ForeignKeyConstraint( - ['foreign_id'], - 'my_table', - ['id'], - 'my_fk', - ['deferrable' => true, 'deferred' => true, 'match' => 'full'] - ); - self::assertEquals( - 'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) MATCH full DEFERRABLE INITIALLY DEFERRED', - $this->platform->getForeignKeyDeclarationSQL($foreignKey) - ); - } - - public function testGeneratesSqlSnippets() : void - { - self::assertEquals('SIMILAR TO', $this->platform->getRegexpExpression(), 'Regular expression operator is not correct'); - self::assertEquals('"', $this->platform->getIdentifierQuoteCharacter(), 'Identifier quote character is not correct'); - self::assertEquals('column1 || column2 || column3', $this->platform->getConcatExpression('column1', 'column2', 'column3'), 'Concatenation expression is not correct'); - self::assertEquals('SUBSTRING(column FROM 5)', $this->platform->getSubstringExpression('column', 5), 'Substring expression without length is not correct'); - self::assertEquals('SUBSTRING(column FROM 1 FOR 5)', $this->platform->getSubstringExpression('column', 1, 5), 'Substring expression with length is not correct'); - } - - public function testGeneratesTransactionCommands() : void - { - self::assertEquals( - 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', - $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_UNCOMMITTED) - ); - self::assertEquals( - 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED', - $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_COMMITTED) - ); - self::assertEquals( - 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ', - $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::REPEATABLE_READ) - ); - self::assertEquals( - 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE', - $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::SERIALIZABLE) - ); - } - - public function testGeneratesDDLSnippets() : void - { - self::assertEquals('CREATE DATABASE foobar', $this->platform->getCreateDatabaseSQL('foobar')); - self::assertEquals('DROP DATABASE foobar', $this->platform->getDropDatabaseSQL('foobar')); - self::assertEquals('DROP TABLE foobar', $this->platform->getDropTableSQL('foobar')); - } - - public function testGenerateTableWithAutoincrement() : void - { - $table = new Table('autoinc_table'); - $column = $table->addColumn('id', 'integer'); - $column->setAutoincrement(true); - - self::assertEquals(['CREATE TABLE autoinc_table (id SERIAL NOT NULL)'], $this->platform->getCreateTableSQL($table)); - } - - /** - * @return mixed[][] - */ - public static function serialTypes() : iterable - { - return [ - ['integer', 'SERIAL'], - ['bigint', 'BIGSERIAL'], - ]; - } - - /** - * @dataProvider serialTypes - * @group 2906 - */ - public function testGenerateTableWithAutoincrementDoesNotSetDefault(string $type, string $definition) : void - { - $table = new Table('autoinc_table_notnull'); - $column = $table->addColumn('id', $type); - $column->setAutoIncrement(true); - $column->setNotNull(false); - - $sql = $this->platform->getCreateTableSQL($table); - - self::assertEquals([sprintf('CREATE TABLE autoinc_table_notnull (id %s)', $definition)], $sql); - } - - /** - * @dataProvider serialTypes - * @group 2906 - */ - public function testCreateTableWithAutoincrementAndNotNullAddsConstraint(string $type, string $definition) : void - { - $table = new Table('autoinc_table_notnull_enabled'); - $column = $table->addColumn('id', $type); - $column->setAutoIncrement(true); - $column->setNotNull(true); - - $sql = $this->platform->getCreateTableSQL($table); - - self::assertEquals([sprintf('CREATE TABLE autoinc_table_notnull_enabled (id %s NOT NULL)', $definition)], $sql); - } - - /** - * @dataProvider serialTypes - * @group 2906 - */ - public function testGetDefaultValueDeclarationSQLIgnoresTheDefaultKeyWhenTheFieldIsSerial(string $type) : void - { - $sql = $this->platform->getDefaultValueDeclarationSQL( - [ - 'autoincrement' => true, - 'type' => Type::getType($type), - 'default' => 1, - ] - ); - - self::assertSame('', $sql); - } - - public function testGeneratesTypeDeclarationForIntegers() : void - { - self::assertEquals( - 'INT', - $this->platform->getIntegerTypeDeclarationSQL([]) - ); - self::assertEquals( - 'SERIAL', - $this->platform->getIntegerTypeDeclarationSQL(['autoincrement' => true]) - ); - self::assertEquals( - 'SERIAL', - $this->platform->getIntegerTypeDeclarationSQL( - ['autoincrement' => true, 'primary' => true] - ) - ); - } - - public function testGeneratesTypeDeclarationForStrings() : void - { - self::assertEquals( - 'CHAR(10)', - $this->platform->getVarcharTypeDeclarationSQL( - ['length' => 10, 'fixed' => true] - ) - ); - self::assertEquals( - 'VARCHAR(50)', - $this->platform->getVarcharTypeDeclarationSQL(['length' => 50]), - 'Variable string declaration is not correct' - ); - self::assertEquals( - 'VARCHAR(255)', - $this->platform->getVarcharTypeDeclarationSQL([]), - 'Long string declaration is not correct' - ); - } - - public function getGenerateUniqueIndexSql() : string - { - return 'CREATE UNIQUE INDEX index_name ON test (test, test2)'; - } - - public function testGeneratesSequenceSqlCommands() : void - { - $sequence = new Sequence('myseq', 20, 1); - self::assertEquals( - 'CREATE SEQUENCE myseq INCREMENT BY 20 MINVALUE 1 START 1', - $this->platform->getCreateSequenceSQL($sequence) - ); - self::assertEquals( - 'DROP SEQUENCE myseq CASCADE', - $this->platform->getDropSequenceSQL('myseq') - ); - self::assertEquals( - "SELECT NEXTVAL('myseq')", - $this->platform->getSequenceNextValSQL('myseq') - ); - } - - public function testDoesNotPreferIdentityColumns() : void - { - self::assertFalse($this->platform->prefersIdentityColumns()); - } - - public function testPrefersSequences() : void - { - self::assertTrue($this->platform->prefersSequences()); - } - - public function testSupportsIdentityColumns() : void - { - self::assertTrue($this->platform->supportsIdentityColumns()); - } - - public function testSupportsSavePoints() : void - { - self::assertTrue($this->platform->supportsSavepoints()); - } - - public function testSupportsSequences() : void - { - self::assertTrue($this->platform->supportsSequences()); - } - - /** - * {@inheritdoc} - */ - protected function supportsCommentOnStatement() : bool - { - return true; - } - - public function testModifyLimitQuery() : void - { - $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10, 0); - self::assertEquals('SELECT * FROM user LIMIT 10', $sql); - } - - public function testModifyLimitQueryWithEmptyOffset() : void - { - $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10); - self::assertEquals('SELECT * FROM user LIMIT 10', $sql); - } - - /** - * {@inheritDoc} - */ - public function getCreateTableColumnCommentsSQL() : array - { - return [ - 'CREATE TABLE test (id INT NOT NULL, PRIMARY KEY(id))', - "COMMENT ON COLUMN test.id IS 'This is a comment'", - ]; - } - - /** - * {@inheritDoc} - */ - public function getAlterTableColumnCommentsSQL() : array - { - return [ - 'ALTER TABLE mytable ADD quota INT NOT NULL', - "COMMENT ON COLUMN mytable.quota IS 'A comment'", - 'COMMENT ON COLUMN mytable.foo IS NULL', - "COMMENT ON COLUMN mytable.baz IS 'B comment'", - ]; - } - - /** - * {@inheritDoc} - */ - public function getCreateTableColumnTypeCommentsSQL() : array - { - return [ - 'CREATE TABLE test (id INT NOT NULL, data TEXT NOT NULL, PRIMARY KEY(id))', - "COMMENT ON COLUMN test.data IS '(DC2Type:array)'", - ]; - } - - /** - * {@inheritDoc} - */ - protected function getQuotedColumnInPrimaryKeySQL() : array - { - return ['CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, PRIMARY KEY("create"))']; - } - - /** - * {@inheritDoc} - */ - protected function getQuotedColumnInIndexSQL() : array - { - return [ - 'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL)', - 'CREATE INDEX IDX_22660D028FD6E0FB ON "quoted" ("create")', - ]; - } - - /** - * {@inheritDoc} - */ - protected function getQuotedNameInIndexSQL() : array - { - return [ - 'CREATE TABLE test (column1 VARCHAR(255) NOT NULL)', - 'CREATE INDEX "key" ON test (column1)', - ]; - } - - /** - * {@inheritDoc} - */ - protected function getQuotedColumnInForeignKeySQL() : array - { - return [ - 'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, foo VARCHAR(255) NOT NULL, "bar" VARCHAR(255) NOT NULL)', - 'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES "foreign" ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE', - 'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_NON_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES foo ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE', - 'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_INTENDED_QUOTATION FOREIGN KEY ("create", foo, "bar") REFERENCES "foo-bar" ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE', - ]; - } - - /** - * @param string|bool $databaseValue - * - * @group DBAL-457 - * @dataProvider pgBooleanProvider - */ - public function testConvertBooleanAsLiteralStrings( - $databaseValue, - string $preparedStatementValue, - ?int $integerValue, - ?bool $booleanValue - ) : void { - $platform = $this->createPlatform(); - - self::assertEquals($preparedStatementValue, $platform->convertBooleans($databaseValue)); - } - - /** - * @group DBAL-457 - */ - public function testConvertBooleanAsLiteralIntegers() : void - { - $platform = $this->createPlatform(); - $platform->setUseBooleanTrueFalseStrings(false); - - self::assertEquals(1, $platform->convertBooleans(true)); - self::assertEquals(1, $platform->convertBooleans('1')); - - self::assertEquals(0, $platform->convertBooleans(false)); - self::assertEquals(0, $platform->convertBooleans('0')); - } - - /** - * @param string|bool $databaseValue - * - * @group DBAL-630 - * @dataProvider pgBooleanProvider - */ - public function testConvertBooleanAsDatabaseValueStrings( - $databaseValue, - string $preparedStatementValue, - ?int $integerValue, - ?bool $booleanValue - ) : void { - $platform = $this->createPlatform(); - - self::assertSame($integerValue, $platform->convertBooleansToDatabaseValue($booleanValue)); - } - - /** - * @group DBAL-630 - */ - public function testConvertBooleanAsDatabaseValueIntegers() : void - { - $platform = $this->createPlatform(); - $platform->setUseBooleanTrueFalseStrings(false); - - self::assertSame(1, $platform->convertBooleansToDatabaseValue(true)); - self::assertSame(0, $platform->convertBooleansToDatabaseValue(false)); - } - - /** - * @param string|bool $databaseValue - * - * @dataProvider pgBooleanProvider - */ - public function testConvertFromBoolean($databaseValue, string $prepareStatementValue, ?int $integerValue, ?bool $booleanValue) : void - { - $platform = $this->createPlatform(); - - self::assertSame($booleanValue, $platform->convertFromBoolean($databaseValue)); - } - - public function testThrowsExceptionWithInvalidBooleanLiteral() : void - { - $platform = $this->createPlatform(); - - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage("Unrecognized boolean literal 'my-bool'"); - - $platform->convertBooleansToDatabaseValue('my-bool'); - } - - public function testGetCreateSchemaSQL() : void - { - $schemaName = 'schema'; - $sql = $this->platform->getCreateSchemaSQL($schemaName); - self::assertEquals('CREATE SCHEMA ' . $schemaName, $sql); - } - - public function testAlterDecimalPrecisionScale() : void - { - $table = new Table('mytable'); - $table->addColumn('dfoo1', 'decimal'); - $table->addColumn('dfoo2', 'decimal', ['precision' => 10, 'scale' => 6]); - $table->addColumn('dfoo3', 'decimal', ['precision' => 10, 'scale' => 6]); - $table->addColumn('dfoo4', 'decimal', ['precision' => 10, 'scale' => 6]); - - $tableDiff = new TableDiff('mytable'); - $tableDiff->fromTable = $table; - - $tableDiff->changedColumns['dloo1'] = new ColumnDiff( - 'dloo1', - new Column( - 'dloo1', - Type::getType('decimal'), - ['precision' => 16, 'scale' => 6] - ), - ['precision'] - ); - $tableDiff->changedColumns['dloo2'] = new ColumnDiff( - 'dloo2', - new Column( - 'dloo2', - Type::getType('decimal'), - ['precision' => 10, 'scale' => 4] - ), - ['scale'] - ); - $tableDiff->changedColumns['dloo3'] = new ColumnDiff( - 'dloo3', - new Column( - 'dloo3', - Type::getType('decimal'), - ['precision' => 10, 'scale' => 6] - ), - [] - ); - $tableDiff->changedColumns['dloo4'] = new ColumnDiff( - 'dloo4', - new Column( - 'dloo4', - Type::getType('decimal'), - ['precision' => 16, 'scale' => 8] - ), - ['precision', 'scale'] - ); - - $sql = $this->platform->getAlterTableSQL($tableDiff); - - $expectedSql = [ - 'ALTER TABLE mytable ALTER dloo1 TYPE NUMERIC(16, 6)', - 'ALTER TABLE mytable ALTER dloo2 TYPE NUMERIC(10, 4)', - 'ALTER TABLE mytable ALTER dloo4 TYPE NUMERIC(16, 8)', - ]; - - self::assertEquals($expectedSql, $sql); - } - - /** - * @group DBAL-365 - */ - public function testDroppingConstraintsBeforeColumns() : void - { - $newTable = new Table('mytable'); - $newTable->addColumn('id', 'integer'); - $newTable->setPrimaryKey(['id']); - - $oldTable = clone $newTable; - $oldTable->addColumn('parent_id', 'integer'); - $oldTable->addUnnamedForeignKeyConstraint('mytable', ['parent_id'], ['id']); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($oldTable, $newTable); - - $sql = $this->platform->getAlterTableSQL($tableDiff); - - $expectedSql = [ - 'ALTER TABLE mytable DROP CONSTRAINT FK_6B2BD609727ACA70', - 'DROP INDEX IDX_6B2BD609727ACA70', - 'ALTER TABLE mytable DROP parent_id', - ]; - - self::assertEquals($expectedSql, $sql); - } - - /** - * @group DBAL-563 - */ - public function testUsesSequenceEmulatedIdentityColumns() : void - { - self::assertTrue($this->platform->usesSequenceEmulatedIdentityColumns()); - } - - /** - * @group DBAL-563 - */ - public function testReturnsIdentitySequenceName() : void - { - self::assertSame('mytable_mycolumn_seq', $this->platform->getIdentitySequenceName('mytable', 'mycolumn')); - } - - /** - * @dataProvider dataCreateSequenceWithCache - * @group DBAL-139 - */ - public function testCreateSequenceWithCache(int $cacheSize, string $expectedSql) : void - { - $sequence = new Sequence('foo', 1, 1, $cacheSize); - self::assertStringContainsString($expectedSql, $this->platform->getCreateSequenceSQL($sequence)); - } - - /** - * @return mixed[][] - */ - public static function dataCreateSequenceWithCache() : iterable - { - return [ - [3, 'CACHE 3'], - ]; - } - - protected function getBinaryDefaultLength() : int - { - return 0; - } - - protected function getBinaryMaxLength() : int - { - return 0; - } - - public function testReturnsBinaryTypeDeclarationSQL() : void - { - self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL([])); - self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['length' => 0])); - self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['length' => 9999999])); - - self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true])); - self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 0])); - self::assertSame('BYTEA', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 9999999])); - } - - public function testDoesNotPropagateUnnecessaryTableAlterationOnBinaryType() : void - { - $table1 = new Table('mytable'); - $table1->addColumn('column_varbinary', 'binary'); - $table1->addColumn('column_binary', 'binary', ['fixed' => true]); - $table1->addColumn('column_blob', 'blob'); - - $table2 = new Table('mytable'); - $table2->addColumn('column_varbinary', 'binary', ['fixed' => true]); - $table2->addColumn('column_binary', 'binary'); - $table2->addColumn('column_blob', 'binary'); - - $comparator = new Comparator(); - - // VARBINARY -> BINARY - // BINARY -> VARBINARY - // BLOB -> VARBINARY - self::assertEmpty($this->platform->getAlterTableSQL($comparator->diffTable($table1, $table2))); - - $table2 = new Table('mytable'); - $table2->addColumn('column_varbinary', 'binary', ['length' => 42]); - $table2->addColumn('column_binary', 'blob'); - $table2->addColumn('column_blob', 'binary', ['length' => 11, 'fixed' => true]); - - // VARBINARY -> VARBINARY with changed length - // BINARY -> BLOB - // BLOB -> BINARY - self::assertEmpty($this->platform->getAlterTableSQL($comparator->diffTable($table1, $table2))); - - $table2 = new Table('mytable'); - $table2->addColumn('column_varbinary', 'blob'); - $table2->addColumn('column_binary', 'binary', ['length' => 42, 'fixed' => true]); - $table2->addColumn('column_blob', 'blob'); - - // VARBINARY -> BLOB - // BINARY -> BINARY with changed length - // BLOB -> BLOB - self::assertEmpty($this->platform->getAlterTableSQL($comparator->diffTable($table1, $table2))); - } - - /** - * {@inheritDoc} - * - * @group DBAL-234 - */ - protected function getAlterTableRenameIndexSQL() : array - { - return ['ALTER INDEX idx_foo RENAME TO idx_bar']; - } - - /** - * {@inheritDoc} - * - * @group DBAL-234 - */ - protected function getQuotedAlterTableRenameIndexSQL() : array - { - return [ - 'ALTER INDEX "create" RENAME TO "select"', - 'ALTER INDEX "foo" RENAME TO "bar"', - ]; - } - - /** - * PostgreSQL boolean strings provider - * - * @return mixed[][] - */ - public static function pgBooleanProvider() : iterable - { - return [ - // Database value, prepared statement value, boolean integer value, boolean value. - [true, 'true', 1, true], - ['t', 'true', 1, true], - ['true', 'true', 1, true], - ['y', 'true', 1, true], - ['yes', 'true', 1, true], - ['on', 'true', 1, true], - ['1', 'true', 1, true], - - [false, 'false', 0, false], - ['f', 'false', 0, false], - ['false', 'false', 0, false], - [ 'n', 'false', 0, false], - ['no', 'false', 0, false], - ['off', 'false', 0, false], - ['0', 'false', 0, false], - - [null, 'NULL', null, null], - ]; - } - - /** - * {@inheritdoc} - */ - protected function getQuotedAlterTableRenameColumnSQL() : array - { - return [ - 'ALTER TABLE mytable RENAME COLUMN unquoted1 TO unquoted', - 'ALTER TABLE mytable RENAME COLUMN unquoted2 TO "where"', - 'ALTER TABLE mytable RENAME COLUMN unquoted3 TO "foo"', - 'ALTER TABLE mytable RENAME COLUMN "create" TO reserved_keyword', - 'ALTER TABLE mytable RENAME COLUMN "table" TO "from"', - 'ALTER TABLE mytable RENAME COLUMN "select" TO "bar"', - 'ALTER TABLE mytable RENAME COLUMN quoted1 TO quoted', - 'ALTER TABLE mytable RENAME COLUMN quoted2 TO "and"', - 'ALTER TABLE mytable RENAME COLUMN quoted3 TO "baz"', - ]; - } - - /** - * {@inheritdoc} - */ - protected function getQuotedAlterTableChangeColumnLengthSQL() : array - { - return [ - 'ALTER TABLE mytable ALTER unquoted1 TYPE VARCHAR(255)', - 'ALTER TABLE mytable ALTER unquoted2 TYPE VARCHAR(255)', - 'ALTER TABLE mytable ALTER unquoted3 TYPE VARCHAR(255)', - 'ALTER TABLE mytable ALTER "create" TYPE VARCHAR(255)', - 'ALTER TABLE mytable ALTER "table" TYPE VARCHAR(255)', - 'ALTER TABLE mytable ALTER "select" TYPE VARCHAR(255)', - ]; - } - - /** - * {@inheritDoc} - * - * @group DBAL-807 - */ - protected function getAlterTableRenameIndexInSchemaSQL() : array - { - return ['ALTER INDEX myschema.idx_foo RENAME TO idx_bar']; - } - - /** - * {@inheritDoc} - * - * @group DBAL-807 - */ - protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array - { - return [ - 'ALTER INDEX "schema"."create" RENAME TO "select"', - 'ALTER INDEX "schema"."foo" RENAME TO "bar"', - ]; - } - - protected function getQuotesDropForeignKeySQL() : string - { - return 'ALTER TABLE "table" DROP CONSTRAINT "select"'; - } - - public function testGetNullCommentOnColumnSQL() : void - { - self::assertEquals( - 'COMMENT ON COLUMN mytable.id IS NULL', - $this->platform->getCommentOnColumnSQL('mytable', 'id', null) - ); - } - - /** - * @group DBAL-423 - */ - public function testReturnsGuidTypeDeclarationSQL() : void - { - self::assertSame('UUID', $this->platform->getGuidTypeDeclarationSQL([])); - } - - /** - * {@inheritdoc} - */ - public function getAlterTableRenameColumnSQL() : array - { - return ['ALTER TABLE foo RENAME COLUMN bar TO baz']; - } - - /** - * {@inheritdoc} - */ - protected function getQuotesTableIdentifiersInAlterTableSQL() : array - { - return [ - 'ALTER TABLE "foo" DROP CONSTRAINT fk1', - 'ALTER TABLE "foo" DROP CONSTRAINT fk2', - 'ALTER TABLE "foo" ADD bloo INT NOT NULL', - 'ALTER TABLE "foo" DROP baz', - 'ALTER TABLE "foo" ALTER bar DROP NOT NULL', - 'ALTER TABLE "foo" RENAME COLUMN id TO war', - 'ALTER TABLE "foo" RENAME TO "table"', - 'ALTER TABLE "table" ADD CONSTRAINT fk_add FOREIGN KEY (fk3) REFERENCES fk_table (id) NOT DEFERRABLE ' . - 'INITIALLY IMMEDIATE', - 'ALTER TABLE "table" ADD CONSTRAINT fk2 FOREIGN KEY (fk2) REFERENCES fk_table2 (id) NOT DEFERRABLE ' . - 'INITIALLY IMMEDIATE', - ]; - } - - /** - * {@inheritdoc} - */ - protected function getCommentOnColumnSQL() : array - { - return [ - 'COMMENT ON COLUMN foo.bar IS \'comment\'', - 'COMMENT ON COLUMN "Foo"."BAR" IS \'comment\'', - 'COMMENT ON COLUMN "select"."from" IS \'comment\'', - ]; - } - - /** - * @group DBAL-1004 - */ - public function testAltersTableColumnCommentWithExplicitlyQuotedIdentifiers() : void - { - $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'))]); - $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'), ['comment' => 'baz'])]); - - $comparator = new Comparator(); - - $tableDiff = $comparator->diffTable($table1, $table2); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertSame( - ['COMMENT ON COLUMN "foo"."bar" IS \'baz\''], - $this->platform->getAlterTableSQL($tableDiff) - ); - } - - /** - * @group 3158 - */ - public function testAltersTableColumnCommentIfRequiredByType() : void - { - $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('datetime'))]); - $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('datetime_immutable'))]); - - $comparator = new Comparator(); - - $tableDiff = $comparator->diffTable($table1, $table2); - - $this->assertInstanceOf('Doctrine\DBAL\Schema\TableDiff', $tableDiff); - $this->assertSame( - [ - 'ALTER TABLE "foo" ALTER "bar" TYPE TIMESTAMP(0) WITHOUT TIME ZONE', - 'ALTER TABLE "foo" ALTER "bar" DROP DEFAULT', - 'COMMENT ON COLUMN "foo"."bar" IS \'(DC2Type:datetime_immutable)\'', - ], - $this->platform->getAlterTableSQL($tableDiff) - ); - } - - /** - * {@inheritdoc} - */ - protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string - { - return 'CONSTRAINT "select" UNIQUE (foo)'; - } - - /** - * {@inheritdoc} - */ - protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string - { - return 'INDEX "select" (foo)'; - } - - /** - * {@inheritdoc} - */ - protected function getQuotesReservedKeywordInTruncateTableSQL() : string - { - return 'TRUNCATE "select"'; - } - - /** - * {@inheritdoc} - */ - protected function getAlterStringToFixedStringSQL() : array - { - return ['ALTER TABLE mytable ALTER name TYPE CHAR(2)']; - } - - /** - * {@inheritdoc} - */ - protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array - { - return ['ALTER INDEX idx_foo RENAME TO idx_foo_renamed']; - } - - /** - * @group DBAL-1142 - */ - public function testInitializesTsvectorTypeMapping() : void - { - self::assertTrue($this->platform->hasDoctrineTypeMappingFor('tsvector')); - self::assertEquals('text', $this->platform->getDoctrineTypeMapping('tsvector')); - } - - /** - * @group DBAL-1220 - */ - public function testReturnsDisallowDatabaseConnectionsSQL() : void - { - self::assertSame( - "UPDATE pg_database SET datallowconn = 'false' WHERE datname = 'foo'", - $this->platform->getDisallowDatabaseConnectionsSQL('foo') - ); - } - - /** - * @group DBAL-1220 - */ - public function testReturnsCloseActiveDatabaseConnectionsSQL() : void - { - self::assertSame( - "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = 'foo'", - $this->platform->getCloseActiveDatabaseConnectionsSQL('foo') - ); - } - - /** - * @group DBAL-2436 - */ - public function testQuotesTableNameInListTableForeignKeysSQL() : void - { - self::assertStringContainsStringIgnoringCase( - "'Foo''Bar\\'", - $this->platform->getListTableForeignKeysSQL("Foo'Bar\\") - ); - } - - /** - * @group DBAL-2436 - */ - public function testQuotesSchemaNameInListTableForeignKeysSQL() : void - { - self::assertStringContainsStringIgnoringCase( - "'Foo''Bar\\'", - $this->platform->getListTableForeignKeysSQL("Foo'Bar\\.baz_table") - ); - } - - /** - * @group DBAL-2436 - */ - public function testQuotesTableNameInListTableConstraintsSQL() : void - { - self::assertStringContainsStringIgnoringCase( - "'Foo''Bar\\'", - $this->platform->getListTableConstraintsSQL("Foo'Bar\\") - ); - } - - /** - * @group DBAL-2436 - */ - public function testQuotesTableNameInListTableIndexesSQL() : void - { - self::assertStringContainsStringIgnoringCase( - "'Foo''Bar\\'", - $this->platform->getListTableIndexesSQL("Foo'Bar\\") - ); - } - - /** - * @group DBAL-2436 - */ - public function testQuotesSchemaNameInListTableIndexesSQL() : void - { - self::assertStringContainsStringIgnoringCase( - "'Foo''Bar\\'", - $this->platform->getListTableIndexesSQL("Foo'Bar\\.baz_table") - ); - } - - /** - * @group DBAL-2436 - */ - public function testQuotesTableNameInListTableColumnsSQL() : void - { - self::assertStringContainsStringIgnoringCase( - "'Foo''Bar\\'", - $this->platform->getListTableColumnsSQL("Foo'Bar\\") - ); - } - - /** - * @group DBAL-2436 - */ - public function testQuotesSchemaNameInListTableColumnsSQL() : void - { - self::assertStringContainsStringIgnoringCase( - "'Foo''Bar\\'", - $this->platform->getListTableColumnsSQL("Foo'Bar\\.baz_table") - ); - } - - /** - * @group DBAL-2436 - */ - public function testQuotesDatabaseNameInCloseActiveDatabaseConnectionsSQL() : void - { - self::assertStringContainsStringIgnoringCase( - "'Foo''Bar\\'", - $this->platform->getCloseActiveDatabaseConnectionsSQL("Foo'Bar\\") - ); - } -} diff --git a/tests/Unit/DBAL/Platforms/PostgreSQL100PlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSQL100PlatformTest.php deleted file mode 100644 index 79ebe59..0000000 --- a/tests/Unit/DBAL/Platforms/PostgreSQL100PlatformTest.php +++ /dev/null @@ -1,34 +0,0 @@ -platform->getListSequencesSQL('test_db') - ); - } -} diff --git a/tests/Unit/DBAL/Platforms/PostgreSQL91PlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSQL91PlatformTest.php deleted file mode 100644 index deaecd6..0000000 --- a/tests/Unit/DBAL/Platforms/PostgreSQL91PlatformTest.php +++ /dev/null @@ -1,41 +0,0 @@ -platform->supportsColumnCollation()); - } - - public function testColumnCollationDeclarationSQL() : void - { - self::assertSame( - 'COLLATE "en_US.UTF-8"', - $this->platform->getColumnCollationDeclarationSQL('en_US.UTF-8') - ); - } - - public function testGetCreateTableSQLWithColumnCollation() : void - { - $table = new Table('foo'); - $table->addColumn('no_collation', 'string'); - $table->addColumn('column_collation', 'string')->setPlatformOption('collation', 'en_US.UTF-8'); - - self::assertSame( - ['CREATE TABLE foo (no_collation VARCHAR(255) NOT NULL, column_collation VARCHAR(255) NOT NULL COLLATE "en_US.UTF-8")'], - $this->platform->getCreateTableSQL($table), - 'Column "no_collation" will use the default collation from the table/database and "column_collation" overwrites the collation on this column' - ); - } -} diff --git a/tests/Unit/DBAL/Platforms/PostgreSQL92PlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSQL92PlatformTest.php deleted file mode 100644 index e987053..0000000 --- a/tests/Unit/DBAL/Platforms/PostgreSQL92PlatformTest.php +++ /dev/null @@ -1,72 +0,0 @@ -platform->hasNativeJsonType()); - } - - /** - * @group DBAL-553 - */ - public function testReturnsJsonTypeDeclarationSQL() : void - { - self::assertSame('JSON', $this->platform->getJsonTypeDeclarationSQL([])); - } - - public function testReturnsSmallIntTypeDeclarationSQL() : void - { - self::assertSame( - 'SMALLSERIAL', - $this->platform->getSmallIntTypeDeclarationSQL(['autoincrement' => true]) - ); - - self::assertSame( - 'SMALLINT', - $this->platform->getSmallIntTypeDeclarationSQL(['autoincrement' => false]) - ); - - self::assertSame( - 'SMALLINT', - $this->platform->getSmallIntTypeDeclarationSQL([]) - ); - } - - /** - * @group DBAL-553 - */ - public function testInitializesJsonTypeMapping() : void - { - self::assertTrue($this->platform->hasDoctrineTypeMappingFor('json')); - self::assertEquals(Types::JSON, $this->platform->getDoctrineTypeMapping('json')); - } - - /** - * @group DBAL-1220 - */ - public function testReturnsCloseActiveDatabaseConnectionsSQL() : void - { - self::assertSame( - "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'foo'", - $this->platform->getCloseActiveDatabaseConnectionsSQL('foo') - ); - } -} diff --git a/tests/Unit/DBAL/Platforms/PostgreSQL94PlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSQL94PlatformTest.php deleted file mode 100644 index 1a843ce..0000000 --- a/tests/Unit/DBAL/Platforms/PostgreSQL94PlatformTest.php +++ /dev/null @@ -1,32 +0,0 @@ -platform->getJsonTypeDeclarationSQL(['jsonb' => false])); - self::assertSame('JSONB', $this->platform->getJsonTypeDeclarationSQL(['jsonb' => true])); - } - - public function testInitializesJsonTypeMapping() : void - { - parent::testInitializesJsonTypeMapping(); - self::assertTrue($this->platform->hasDoctrineTypeMappingFor('jsonb')); - self::assertEquals(Types::JSON, $this->platform->getDoctrineTypeMapping('jsonb')); - } -} diff --git a/tests/Unit/DBAL/Platforms/PostgreSqlPlatformTest.php b/tests/Unit/DBAL/Platforms/PostgreSqlPlatformTest.php deleted file mode 100644 index 1111dda..0000000 --- a/tests/Unit/DBAL/Platforms/PostgreSqlPlatformTest.php +++ /dev/null @@ -1,19 +0,0 @@ -platform->supportsPartialIndexes()); - } -} diff --git a/tests/Unit/DBAL/Platforms/ReservedKeywordsValidatorTest.php b/tests/Unit/DBAL/Platforms/ReservedKeywordsValidatorTest.php deleted file mode 100644 index 0976ce0..0000000 --- a/tests/Unit/DBAL/Platforms/ReservedKeywordsValidatorTest.php +++ /dev/null @@ -1,43 +0,0 @@ -validator = new ReservedKeywordsValidator([new MySQLKeywords()]); - } - - public function testReservedTableName() : void - { - $table = new Table('TABLE'); - $this->validator->acceptTable($table); - - self::assertEquals( - ['Table TABLE keyword violations: MySQL'], - $this->validator->getViolations() - ); - } - - public function testReservedColumnName() : void - { - $table = new Table('TABLE'); - $column = $table->addColumn('table', 'string'); - - $this->validator->acceptColumn($table, $column); - - self::assertEquals( - ['Table TABLE column table keyword violations: MySQL'], - $this->validator->getViolations() - ); - } -} diff --git a/tests/Unit/DBAL/SQLParserUtilsTest.php b/tests/Unit/DBAL/SQLParserUtilsTest.php deleted file mode 100644 index 8265bc3..0000000 --- a/tests/Unit/DBAL/SQLParserUtilsTest.php +++ /dev/null @@ -1,498 +0,0 @@ - 42, 0 => 30]], // explicit keys - - // named - ['SELECT :foo FROM :bar', false, [7 => 'foo', 17 => 'bar']], - ['SELECT * FROM Foo WHERE bar IN (:name1, :name2)', false, [32 => 'name1', 40 => 'name2']], - ['SELECT ":foo" FROM Foo WHERE bar IN (:name1, :name2)', false, [37 => 'name1', 45 => 'name2']], - ["SELECT ':foo' FROM Foo WHERE bar IN (:name1, :name2)", false, [37 => 'name1', 45 => 'name2']], - ['SELECT :foo_id', false, [7 => 'foo_id']], // Ticket DBAL-231 - ['SELECT @rank := 1', false, []], // Ticket DBAL-398 - ['SELECT @rank := 1 AS rank, :foo AS foo FROM :bar', false, [27 => 'foo', 44 => 'bar']], // Ticket DBAL-398 - ['SELECT * FROM Foo WHERE bar > :start_date AND baz > :start_date', false, [30 => 'start_date', 52 => 'start_date']], // Ticket GH-113 - ['SELECT foo::date as date FROM Foo WHERE bar > :start_date AND baz > :start_date', false, [46 => 'start_date', 68 => 'start_date']], // Ticket GH-259 - ['SELECT `d.ns:col_name` FROM my_table d WHERE `d.date` >= :param1', false, [57 => 'param1']], // Ticket DBAL-552 - ['SELECT [d.ns:col_name] FROM my_table d WHERE [d.date] >= :param1', false, [57 => 'param1']], // Ticket DBAL-552 - ['SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, ARRAY[:foo])', false, [56 => 'foo']], // Ticket GH-2295 - ['SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, array[:foo])', false, [56 => 'foo']], - ['SELECT table.field1, ARRAY[\'3\'] FROM schema.table table WHERE table.f1 = :foo AND ARRAY[\'3\']', false, [73 => 'foo']], - ['SELECT table.field1, ARRAY[\'3\']::integer[] FROM schema.table table WHERE table.f1 = :foo AND ARRAY[\'3\']::integer[]', false, [84 => 'foo']], - ['SELECT table.field1, ARRAY[:foo] FROM schema.table table WHERE table.f1 = :bar AND ARRAY[\'3\']', false, [27 => 'foo', 74 => 'bar']], - ['SELECT table.field1, ARRAY[:foo]::integer[] FROM schema.table table WHERE table.f1 = :bar AND ARRAY[\'3\']::integer[]', false, [27 => 'foo', 85 => 'bar']], - [ - <<<'SQLDATA' -SELECT * FROM foo WHERE -bar = ':not_a_param1 ''":not_a_param2"''' -OR bar=:a_param1 -OR bar=:a_param2||':not_a_param3' -OR bar=':not_a_param4 '':not_a_param5'' :not_a_param6' -OR bar='' -OR bar=:a_param3 -SQLDATA - , - false, - [ - 74 => 'a_param1', - 91 => 'a_param2', - 190 => 'a_param3', - ], - ], - ["SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE '\\\\') AND (data.description LIKE :condition_1 ESCAPE '\\\\') ORDER BY id ASC", false, [121 => 'condition_0', 174 => 'condition_1']], - ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE "\\\\") AND (data.description LIKE :condition_1 ESCAPE "\\\\") ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']], - ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE "\\\\") AND (data.description LIKE :condition_1 ESCAPE \'\\\\\') ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']], - ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE `\\\\`) AND (data.description LIKE :condition_1 ESCAPE `\\\\`) ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']], - ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE \'\\\\\') AND (data.description LIKE :condition_1 ESCAPE `\\\\`) ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']], - - ]; - } - - /** - * @param int[] $expectedParamPos - * - * @dataProvider dataGetPlaceholderPositions - */ - public function testGetPlaceholderPositions(string $query, bool $isPositional, array $expectedParamPos) : void - { - $actualParamPos = SQLParserUtils::getPlaceholderPositions($query, $isPositional); - self::assertEquals($expectedParamPos, $actualParamPos); - } - - /** - * @return mixed[][] - */ - public static function dataExpandListParameters() : iterable - { - return [ - // Positional: Very simple with one needle - [ - 'SELECT * FROM Foo WHERE foo IN (?)', - [[1, 2, 3]], - [Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?)', - [1, 2, 3], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - // Positional: One non-list before d one after list-needle - [ - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?)', - ['string', [1, 2, 3]], - [ParameterType::STRING, Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)', - ['string', 1, 2, 3], - [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - // Positional: One non-list after list-needle - [ - 'SELECT * FROM Foo WHERE bar IN (?) AND baz = ?', - [[1, 2, 3], 'foo'], - [Connection::PARAM_INT_ARRAY, ParameterType::STRING], - 'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?', - [1, 2, 3, 'foo'], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], - ], - // Positional: One non-list before and one after list-needle - [ - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?) AND baz = ?', - [1, [1, 2, 3], 4], - [ParameterType::INTEGER, Connection::PARAM_INT_ARRAY, ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?', - [1, 1, 2, 3, 4], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ], - ], - // Positional: Two lists - [ - 'SELECT * FROM Foo WHERE foo IN (?, ?)', - [[1, 2, 3], [4, 5]], - [Connection::PARAM_INT_ARRAY, Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)', - [1, 2, 3, 4, 5], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ], - ], - // Positional: Empty "integer" array DDC-1978 - [ - 'SELECT * FROM Foo WHERE foo IN (?)', - [[]], - [Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (NULL)', - [], - [], - ], - // Positional: Empty "str" array DDC-1978 - [ - 'SELECT * FROM Foo WHERE foo IN (?)', - [[]], - [Connection::PARAM_STR_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (NULL)', - [], - [], - ], - // Positional: explicit keys for params and types - [ - 'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?', - [1 => 'bar', 2 => 'baz', 0 => 1], - [2 => ParameterType::STRING, 1 => ParameterType::STRING], - 'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?', - [1 => 'bar', 0 => 1, 2 => 'baz'], - [1 => ParameterType::STRING, 2 => ParameterType::STRING], - ], - // Positional: explicit keys for array params and array types - [ - 'SELECT * FROM Foo WHERE foo IN (?) AND bar IN (?) AND baz = ?', - [1 => ['bar1', 'bar2'], 2 => true, 0 => [1, 2, 3]], - [2 => ParameterType::BOOLEAN, 1 => Connection::PARAM_STR_ARRAY, 0 => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND bar IN (?, ?) AND baz = ?', - [1, 2, 3, 'bar1', 'bar2', true], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::STRING, - ParameterType::STRING, - ParameterType::BOOLEAN, - ], - ], - // Positional starts from 1: One non-list before and one after list-needle - [ - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?) AND baz = ? AND foo IN (?)', - [1 => 1, 2 => [1, 2, 3], 3 => 4, 4 => [5, 6]], - [ - 1 => ParameterType::INTEGER, - 2 => Connection::PARAM_INT_ARRAY, - 3 => ParameterType::INTEGER, - 4 => Connection::PARAM_INT_ARRAY, - ], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ? AND foo IN (?, ?)', - [1, 1, 2, 3, 4, 5, 6], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ], - ], - // Named parameters : Very simple with param int - [ - 'SELECT * FROM Foo WHERE foo = :foo', - ['foo' => 1], - ['foo' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ?', - [1], - [ParameterType::INTEGER], - ], - - // Named parameters : Very simple with param int and string - [ - 'SELECT * FROM Foo WHERE foo = :foo AND bar = :bar', - ['bar' => 'Some String','foo' => 1], - ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::STRING], - 'SELECT * FROM Foo WHERE foo = ? AND bar = ?', - [1,'Some String'], - [ParameterType::INTEGER, ParameterType::STRING], - ], - // Named parameters : Very simple with one needle - [ - 'SELECT * FROM Foo WHERE foo IN (:foo)', - ['foo' => [1, 2, 3]], - ['foo' => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?)', - [1, 2, 3], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - // Named parameters: One non-list before d one after list-needle - [ - 'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar)', - ['foo' => 'string', 'bar' => [1, 2, 3]], - ['foo' => ParameterType::STRING, 'bar' => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)', - ['string', 1, 2, 3], - [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - // Named parameters: One non-list after list-needle - [ - 'SELECT * FROM Foo WHERE bar IN (:bar) AND baz = :baz', - ['bar' => [1, 2, 3], 'baz' => 'foo'], - ['bar' => Connection::PARAM_INT_ARRAY, 'baz' => ParameterType::STRING], - 'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?', - [1, 2, 3, 'foo'], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], - ], - // Named parameters: One non-list before and one after list-needle - [ - 'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar) AND baz = :baz', - ['bar' => [1, 2, 3],'foo' => 1, 'baz' => 4], - ['bar' => Connection::PARAM_INT_ARRAY, 'foo' => ParameterType::INTEGER, 'baz' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?', - [1, 1, 2, 3, 4], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - // Named parameters: Two lists - [ - 'SELECT * FROM Foo WHERE foo IN (:a, :b)', - ['b' => [4, 5],'a' => [1, 2, 3]], - ['a' => Connection::PARAM_INT_ARRAY, 'b' => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)', - [1, 2, 3, 4, 5], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - // Named parameters : With the same name arg type string - [ - 'SELECT * FROM Foo WHERE foo <> :arg AND bar = :arg', - ['arg' => 'Some String'], - ['arg' => ParameterType::STRING], - 'SELECT * FROM Foo WHERE foo <> ? AND bar = ?', - ['Some String','Some String'], - [ParameterType::STRING,ParameterType::STRING], - ], - // Named parameters : With the same name arg - [ - 'SELECT * FROM Foo WHERE foo IN (:arg) AND NOT bar IN (:arg)', - ['arg' => [1, 2, 3]], - ['arg' => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND NOT bar IN (?, ?, ?)', - [1, 2, 3, 1, 2, 3], - [ParameterType::INTEGER,ParameterType::INTEGER, ParameterType::INTEGER,ParameterType::INTEGER,ParameterType::INTEGER, ParameterType::INTEGER], - ], - - // Named parameters : Same name, other name in between DBAL-299 - [ - 'SELECT * FROM Foo WHERE (:foo = 2) AND (:bar = 3) AND (:foo = 2)', - ['foo' => 2,'bar' => 3], - ['foo' => ParameterType::INTEGER,'bar' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE (? = 2) AND (? = 3) AND (? = 2)', - [2, 3, 2], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - // Named parameters : Empty "integer" array DDC-1978 - [ - 'SELECT * FROM Foo WHERE foo IN (:foo)', - ['foo' => []], - ['foo' => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (NULL)', - [], - [], - ], - // Named parameters : Two empty "str" array DDC-1978 - [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)', - ['foo' => [], 'bar' => []], - ['foo' => Connection::PARAM_STR_ARRAY, 'bar' => Connection::PARAM_STR_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (NULL) OR bar IN (NULL)', - [], - [], - ], - [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar OR baz = :baz', - ['foo' => [1, 2], 'bar' => 'bar', 'baz' => 'baz'], - ['foo' => Connection::PARAM_INT_ARRAY, 'baz' => 'string'], - 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ? OR baz = ?', - [1, 2, 'bar', 'baz'], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING, 'string'], - ], - [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar', - ['foo' => [1, 2], 'bar' => 'bar'], - ['foo' => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?', - [1, 2, 'bar'], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], - ], - // Params/types with colons - [ - 'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar', - [':foo' => 'foo', ':bar' => 'bar'], - [':foo' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? OR bar = ?', - ['foo', 'bar'], - [ParameterType::INTEGER, ParameterType::STRING], - ], - [ - 'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar', - [':foo' => 'foo', ':bar' => 'bar'], - [':foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? OR bar = ?', - ['foo', 'bar'], - [ParameterType::INTEGER, ParameterType::INTEGER], - ], - [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar', - [':foo' => [1, 2], ':bar' => 'bar'], - ['foo' => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?', - [1, 2, 'bar'], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], - ], - [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar', - ['foo' => [1, 2], 'bar' => 'bar'], - [':foo' => Connection::PARAM_INT_ARRAY], - 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?', - [1, 2, 'bar'], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], - ], - // DBAL-522 - null valued parameters are not considered - [ - 'INSERT INTO Foo (foo, bar) values (:foo, :bar)', - ['foo' => 1, 'bar' => null], - [':foo' => ParameterType::INTEGER, ':bar' => ParameterType::NULL], - 'INSERT INTO Foo (foo, bar) values (?, ?)', - [1, null], - [ParameterType::INTEGER, ParameterType::NULL], - ], - [ - 'INSERT INTO Foo (foo, bar) values (?, ?)', - [1, null], - [ParameterType::INTEGER, ParameterType::NULL], - 'INSERT INTO Foo (foo, bar) values (?, ?)', - [1, null], - [ParameterType::INTEGER, ParameterType::NULL], - ], - // DBAL-1205 - Escaped single quotes SQL- and C-Style - [ - "SELECT * FROM Foo WHERE foo = :foo||''':not_a_param''\\'' OR bar = ''':not_a_param''\\'':bar", - [':foo' => 1, ':bar' => 2], - [':foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ?||\'\'\':not_a_param\'\'\\\'\' OR bar = \'\'\':not_a_param\'\'\\\'\'?', - [1, 2], - [ParameterType::INTEGER, ParameterType::INTEGER], - ], - ]; - } - - /** - * @param mixed[] $params - * @param mixed[] $types - * @param mixed[] $expectedParams - * @param mixed[] $expectedTypes - * - * @dataProvider dataExpandListParameters - */ - public function testExpandListParameters( - string $query, - array $params, - array $types, - string $expectedQuery, - array $expectedParams, - array $expectedTypes - ) : void { - [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types); - - self::assertEquals($expectedQuery, $query, 'Query was not rewritten correctly.'); - self::assertEquals($expectedParams, $params, 'Params dont match'); - self::assertEquals($expectedTypes, $types, 'Types dont match'); - } - - /** - * @return mixed[][] - */ - public static function dataQueryWithMissingParameters() : iterable - { - return [ - [ - 'SELECT * FROM foo WHERE bar = :param', - ['other' => 'val'], - [], - ], - [ - 'SELECT * FROM foo WHERE bar = :param', - [], - [], - ], - [ - 'SELECT * FROM foo WHERE bar = :param', - [], - ['param' => Connection::PARAM_INT_ARRAY], - ], - [ - 'SELECT * FROM foo WHERE bar = :param', - [], - [':param' => Connection::PARAM_INT_ARRAY], - ], - [ - 'SELECT * FROM foo WHERE bar = :param', - [], - ['bar' => Connection::PARAM_INT_ARRAY], - ], - [ - 'SELECT * FROM foo WHERE bar = :param', - ['bar' => 'value'], - ['bar' => Connection::PARAM_INT_ARRAY], - ], - ]; - } - - /** - * @param mixed[] $params - * @param mixed[] $types - * - * @dataProvider dataQueryWithMissingParameters - */ - public function testExceptionIsThrownForMissingParam(string $query, array $params, array $types = []) : void - { - $this->expectException(SQLParserUtilsException::class); - $this->expectExceptionMessage('Value for :param not found in params array. Params array key should be "param"'); - - SQLParserUtils::expandListParameters($query, $params, $types); - } -} diff --git a/tests/Unit/DBAL/Schema/ColumnDiffTest.php b/tests/Unit/DBAL/Schema/ColumnDiffTest.php deleted file mode 100644 index aa838dd..0000000 --- a/tests/Unit/DBAL/Schema/ColumnDiffTest.php +++ /dev/null @@ -1,30 +0,0 @@ -getOldColumnName()->isQuoted()); - - $columnDiff = new ColumnDiff('"foo"', $toColumn, [], $fromColumn); - self::assertTrue($columnDiff->getOldColumnName()->isQuoted()); - - $columnDiff = new ColumnDiff('foo', $toColumn, [], $fromColumn); - self::assertTrue($columnDiff->getOldColumnName()->isQuoted()); - } -} diff --git a/tests/Unit/DBAL/Schema/ColumnTest.php b/tests/Unit/DBAL/Schema/ColumnTest.php deleted file mode 100644 index 96ea5e7..0000000 --- a/tests/Unit/DBAL/Schema/ColumnTest.php +++ /dev/null @@ -1,146 +0,0 @@ -createColumn(); - - self::assertEquals('foo', $column->getName()); - self::assertSame(Type::getType('string'), $column->getType()); - - self::assertEquals(200, $column->getLength()); - self::assertEquals(5, $column->getPrecision()); - self::assertEquals(2, $column->getScale()); - self::assertTrue($column->getUnsigned()); - self::assertFalse($column->getNotNull()); - self::assertTrue($column->getFixed()); - self::assertEquals('baz', $column->getDefault()); - - self::assertEquals(['foo' => 'bar'], $column->getPlatformOptions()); - self::assertTrue($column->hasPlatformOption('foo')); - self::assertEquals('bar', $column->getPlatformOption('foo')); - self::assertFalse($column->hasPlatformOption('bar')); - - self::assertEquals(['bar' => 'baz'], $column->getCustomSchemaOptions()); - self::assertTrue($column->hasCustomSchemaOption('bar')); - self::assertEquals('baz', $column->getCustomSchemaOption('bar')); - self::assertFalse($column->hasCustomSchemaOption('foo')); - } - - public function testToArray() : void - { - $expected = [ - 'name' => 'foo', - 'type' => Type::getType('string'), - 'default' => 'baz', - 'notnull' => false, - 'length' => 200, - 'precision' => 5, - 'scale' => 2, - 'fixed' => true, - 'unsigned' => true, - 'autoincrement' => false, - 'columnDefinition' => null, - 'comment' => null, - 'foo' => 'bar', - 'bar' => 'baz', - ]; - - self::assertEquals($expected, $this->createColumn()->toArray()); - } - - public function createColumn() : Column - { - $options = [ - 'length' => 200, - 'precision' => 5, - 'scale' => 2, - 'unsigned' => true, - 'notnull' => false, - 'fixed' => true, - 'default' => 'baz', - 'platformOptions' => ['foo' => 'bar'], - 'customSchemaOptions' => ['bar' => 'baz'], - ]; - - $string = Type::getType('string'); - - return new Column('foo', $string, $options); - } - - /** - * @group DBAL-64 - * @group DBAL-830 - */ - public function testQuotedColumnName() : void - { - $string = Type::getType('string'); - $column = new Column('`bar`', $string, []); - - $mysqlPlatform = new MySqlPlatform(); - $sqlitePlatform = new SqlitePlatform(); - - self::assertEquals('bar', $column->getName()); - self::assertEquals('`bar`', $column->getQuotedName($mysqlPlatform)); - self::assertEquals('"bar"', $column->getQuotedName($sqlitePlatform)); - - $column = new Column('[bar]', $string); - - $sqlServerPlatform = new SQLServerPlatform(); - - self::assertEquals('bar', $column->getName()); - self::assertEquals('[bar]', $column->getQuotedName($sqlServerPlatform)); - } - - /** - * @dataProvider getIsQuoted - * @group DBAL-830 - */ - public function testIsQuoted(string $columnName, bool $isQuoted) : void - { - $type = Type::getType('string'); - $column = new Column($columnName, $type); - - self::assertSame($isQuoted, $column->isQuoted()); - } - - /** - * @return mixed[][] - */ - public static function getIsQuoted() : iterable - { - return [ - ['bar', false], - ['`bar`', true], - ['"bar"', true], - ['[bar]', true], - ]; - } - - /** - * @group DBAL-42 - */ - public function testColumnComment() : void - { - $column = new Column('bar', Type::getType('string')); - self::assertNull($column->getComment()); - - $column->setComment('foo'); - self::assertEquals('foo', $column->getComment()); - - $columnArray = $column->toArray(); - self::assertArrayHasKey('comment', $columnArray); - self::assertEquals('foo', $columnArray['comment']); - } -} diff --git a/tests/Unit/DBAL/Schema/ComparatorTest.php b/tests/Unit/DBAL/Schema/ComparatorTest.php deleted file mode 100644 index b7798e8..0000000 --- a/tests/Unit/DBAL/Schema/ComparatorTest.php +++ /dev/null @@ -1,1329 +0,0 @@ - new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - ] - ), - ]); - $schema2 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - ] - ), - ]); - - $expected = new SchemaDiff(); - $expected->fromSchema = $schema1; - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareSame2() : void - { - $schema1 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ] - ), - ]); - $schema2 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - ] - ), - ]); - - $expected = new SchemaDiff(); - $expected->fromSchema = $schema1; - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareMissingTable() : void - { - $schemaConfig = new SchemaConfig(); - $table = new Table('bugdb', ['integerfield1' => new Column('integerfield1', Type::getType('integer'))]); - $table->setSchemaConfig($schemaConfig); - - $schema1 = new Schema([$table], [], $schemaConfig); - $schema2 = new Schema([], [], $schemaConfig); - - $expected = new SchemaDiff([], [], ['bugdb' => $table], $schema1); - - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareNewTable() : void - { - $schemaConfig = new SchemaConfig(); - $table = new Table('bugdb', ['integerfield1' => new Column('integerfield1', Type::getType('integer'))]); - $table->setSchemaConfig($schemaConfig); - - $schema1 = new Schema([], [], $schemaConfig); - $schema2 = new Schema([$table], [], $schemaConfig); - - $expected = new SchemaDiff(['bugdb' => $table], [], [], $schema1); - - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareOnlyAutoincrementChanged() : void - { - $column1 = new Column('foo', Type::getType('integer'), ['autoincrement' => true]); - $column2 = new Column('foo', Type::getType('integer'), ['autoincrement' => false]); - - $comparator = new Comparator(); - $changedProperties = $comparator->diffColumn($column1, $column2); - - self::assertEquals(['autoincrement'], $changedProperties); - } - - public function testCompareMissingField() : void - { - $missingColumn = new Column('integerfield1', Type::getType('integer')); - $schema1 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => $missingColumn, - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ] - ), - ]); - $schema2 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ] - ), - ]); - - $expected = new SchemaDiff( - [], - [ - 'bugdb' => new TableDiff( - 'bugdb', - [], - [], - ['integerfield1' => $missingColumn] - ), - ] - ); - $expected->fromSchema = $schema1; - $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); - - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareNewField() : void - { - $schema1 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - ] - ), - ]); - $schema2 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ] - ), - ]); - - $expected = new SchemaDiff( - [], - [ - 'bugdb' => new TableDiff( - 'bugdb', - [ - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ] - ), - ] - ); - $expected->fromSchema = $schema1; - $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); - - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareChangedColumnsChangeType() : void - { - $column1 = new Column('charfield1', Type::getType('string')); - $column2 = new Column('charfield1', Type::getType('integer')); - - $c = new Comparator(); - self::assertEquals(['type'], $c->diffColumn($column1, $column2)); - self::assertEquals([], $c->diffColumn($column1, $column1)); - } - - public function testCompareColumnsMultipleTypeInstances() : void - { - $integerType1 = Type::getType('integer'); - Type::overrideType('integer', get_class($integerType1)); - $integerType2 = Type::getType('integer'); - - $column1 = new Column('integerfield1', $integerType1); - $column2 = new Column('integerfield1', $integerType2); - - $c = new Comparator(); - self::assertEquals([], $c->diffColumn($column1, $column2)); - } - - public function testCompareColumnsOverriddenType() : void - { - $oldStringInstance = Type::getType('string'); - $integerType = Type::getType('integer'); - - Type::overrideType('string', get_class($integerType)); - $overriddenStringType = Type::getType('string'); - - Type::overrideType('string', get_class($oldStringInstance)); - - $column1 = new Column('integerfield1', $integerType); - $column2 = new Column('integerfield1', $overriddenStringType); - - $c = new Comparator(); - self::assertEquals([], $c->diffColumn($column1, $column2)); - } - - public function testCompareChangedColumnsChangeCustomSchemaOption() : void - { - $column1 = new Column('charfield1', Type::getType('string')); - $column2 = new Column('charfield1', Type::getType('string')); - - $column1->setCustomSchemaOption('foo', 'bar'); - $column2->setCustomSchemaOption('foo', 'bar'); - - $column1->setCustomSchemaOption('foo1', 'bar1'); - $column2->setCustomSchemaOption('foo2', 'bar2'); - - $c = new Comparator(); - self::assertEquals(['foo1', 'foo2'], $c->diffColumn($column1, $column2)); - self::assertEquals([], $c->diffColumn($column1, $column1)); - } - - public function testCompareChangeColumnsMultipleNewColumnsRename() : void - { - $tableA = new Table('foo'); - $tableA->addColumn('datefield1', 'datetime'); - - $tableB = new Table('foo'); - $tableB->addColumn('new_datefield1', 'datetime'); - $tableB->addColumn('new_datefield2', 'datetime'); - - $c = new Comparator(); - $tableDiff = $c->diffTable($tableA, $tableB); - - self::assertCount(1, $tableDiff->renamedColumns, 'we should have one rename datefield1 => new_datefield1.'); - self::assertArrayHasKey('datefield1', $tableDiff->renamedColumns, "'datefield1' should be set to be renamed to new_datefield1"); - self::assertCount(1, $tableDiff->addedColumns, "'new_datefield2' should be added"); - self::assertArrayHasKey('new_datefield2', $tableDiff->addedColumns, "'new_datefield2' should be added, not created through renaming!"); - self::assertCount(0, $tableDiff->removedColumns, 'Nothing should be removed.'); - self::assertCount(0, $tableDiff->changedColumns, 'Nothing should be changed as all fields old & new have diff names.'); - } - - public function testCompareRemovedIndex() : void - { - $schema1 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ], - [ - 'primary' => new Index( - 'primary', - ['integerfield1'], - true - ), - ] - ), - ]); - $schema2 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ] - ), - ]); - - $expected = new SchemaDiff( - [], - [ - 'bugdb' => new TableDiff( - 'bugdb', - [], - [], - [], - [], - [], - [ - 'primary' => new Index( - 'primary', - ['integerfield1'], - true - ), - ] - ), - ] - ); - $expected->fromSchema = $schema1; - $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); - - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareNewIndex() : void - { - $schema1 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ] - ), - ]); - $schema2 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ], - [ - 'primary' => new Index( - 'primary', - ['integerfield1'], - true - ), - ] - ), - ]); - - $expected = new SchemaDiff( - [], - [ - 'bugdb' => new TableDiff( - 'bugdb', - [], - [], - [], - [ - 'primary' => new Index( - 'primary', - ['integerfield1'], - true - ), - ] - ), - ] - ); - $expected->fromSchema = $schema1; - $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); - - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareChangedIndex() : void - { - $schema1 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ], - [ - 'primary' => new Index( - 'primary', - ['integerfield1'], - true - ), - ] - ), - ]); - $schema2 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ], - [ - 'primary' => new Index( - 'primary', - ['integerfield1', 'integerfield2'], - true - ), - ] - ), - ]); - - $expected = new SchemaDiff( - [], - [ - 'bugdb' => new TableDiff( - 'bugdb', - [], - [], - [], - [], - [ - 'primary' => new Index( - 'primary', - [ - 'integerfield1', - 'integerfield2', - ], - true - ), - ] - ), - ] - ); - $expected->fromSchema = $schema1; - $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); - - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareChangedIndexFieldPositions() : void - { - $schema1 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ], - [ - 'primary' => new Index('primary', ['integerfield1', 'integerfield2'], true), - ] - ), - ]); - $schema2 = new Schema([ - 'bugdb' => new Table( - 'bugdb', - [ - 'integerfield1' => new Column('integerfield1', Type::getType('integer')), - 'integerfield2' => new Column('integerfield2', Type::getType('integer')), - ], - [ - 'primary' => new Index('primary', ['integerfield2', 'integerfield1'], true), - ] - ), - ]); - - $expected = new SchemaDiff( - [], - [ - 'bugdb' => new TableDiff( - 'bugdb', - [], - [], - [], - [], - [ - 'primary' => new Index('primary', ['integerfield2', 'integerfield1'], true), - ] - ), - ] - ); - $expected->fromSchema = $schema1; - $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb'); - - self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2)); - } - - public function testCompareSequences() : void - { - $seq1 = new Sequence('foo', 1, 1); - $seq2 = new Sequence('foo', 1, 2); - $seq3 = new Sequence('foo', 2, 1); - $seq4 = new Sequence('foo', '1', '1'); - - $c = new Comparator(); - - self::assertTrue($c->diffSequence($seq1, $seq2)); - self::assertTrue($c->diffSequence($seq1, $seq3)); - self::assertFalse($c->diffSequence($seq1, $seq4)); - } - - public function testRemovedSequence() : void - { - $schema1 = new Schema(); - $seq = $schema1->createSequence('foo'); - - $schema2 = new Schema(); - - $c = new Comparator(); - $diffSchema = $c->compare($schema1, $schema2); - - self::assertCount(1, $diffSchema->removedSequences); - self::assertSame($seq, $diffSchema->removedSequences[0]); - } - - public function testAddedSequence() : void - { - $schema1 = new Schema(); - - $schema2 = new Schema(); - $seq = $schema2->createSequence('foo'); - - $c = new Comparator(); - $diffSchema = $c->compare($schema1, $schema2); - - self::assertCount(1, $diffSchema->newSequences); - self::assertSame($seq, $diffSchema->newSequences[0]); - } - - public function testTableAddForeignKey() : void - { - $tableForeign = new Table('bar'); - $tableForeign->addColumn('id', 'integer'); - - $table1 = new Table('foo'); - $table1->addColumn('fk', 'integer'); - - $table2 = new Table('foo'); - $table2->addColumn('fk', 'integer'); - $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id']); - - $c = new Comparator(); - $tableDiff = $c->diffTable($table1, $table2); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertCount(1, $tableDiff->addedForeignKeys); - } - - public function testTableRemoveForeignKey() : void - { - $tableForeign = new Table('bar'); - $tableForeign->addColumn('id', 'integer'); - - $table1 = new Table('foo'); - $table1->addColumn('fk', 'integer'); - - $table2 = new Table('foo'); - $table2->addColumn('fk', 'integer'); - $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id']); - - $c = new Comparator(); - $tableDiff = $c->diffTable($table2, $table1); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertCount(1, $tableDiff->removedForeignKeys); - } - - public function testTableUpdateForeignKey() : void - { - $tableForeign = new Table('bar'); - $tableForeign->addColumn('id', 'integer'); - - $table1 = new Table('foo'); - $table1->addColumn('fk', 'integer'); - $table1->addForeignKeyConstraint($tableForeign, ['fk'], ['id']); - - $table2 = new Table('foo'); - $table2->addColumn('fk', 'integer'); - $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id'], ['onUpdate' => 'CASCADE']); - - $c = new Comparator(); - $tableDiff = $c->diffTable($table1, $table2); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertCount(1, $tableDiff->changedForeignKeys); - } - - public function testMovedForeignKeyForeignTable() : void - { - $tableForeign = new Table('bar'); - $tableForeign->addColumn('id', 'integer'); - - $tableForeign2 = new Table('bar2'); - $tableForeign2->addColumn('id', 'integer'); - - $table1 = new Table('foo'); - $table1->addColumn('fk', 'integer'); - $table1->addForeignKeyConstraint($tableForeign, ['fk'], ['id']); - - $table2 = new Table('foo'); - $table2->addColumn('fk', 'integer'); - $table2->addForeignKeyConstraint($tableForeign2, ['fk'], ['id']); - - $c = new Comparator(); - $tableDiff = $c->diffTable($table1, $table2); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertCount(1, $tableDiff->changedForeignKeys); - } - - public function testTablesCaseInsensitive() : void - { - $schemaA = new Schema(); - $schemaA->createTable('foo'); - $schemaA->createTable('bAr'); - $schemaA->createTable('BAZ'); - $schemaA->createTable('new'); - - $schemaB = new Schema(); - $schemaB->createTable('FOO'); - $schemaB->createTable('bar'); - $schemaB->createTable('Baz'); - $schemaB->createTable('old'); - - $c = new Comparator(); - $diff = $c->compare($schemaA, $schemaB); - - self::assertSchemaTableChangeCount($diff, 1, 0, 1); - } - - public function testSequencesCaseInsensitive() : void - { - $schemaA = new Schema(); - $schemaA->createSequence('foo'); - $schemaA->createSequence('BAR'); - $schemaA->createSequence('Baz'); - $schemaA->createSequence('new'); - - $schemaB = new Schema(); - $schemaB->createSequence('FOO'); - $schemaB->createSequence('Bar'); - $schemaB->createSequence('baz'); - $schemaB->createSequence('old'); - - $c = new Comparator(); - $diff = $c->compare($schemaA, $schemaB); - - self::assertSchemaSequenceChangeCount($diff, 1, 0, 1); - } - - public function testCompareColumnCompareCaseInsensitive() : void - { - $tableA = new Table('foo'); - $tableA->addColumn('id', 'integer'); - - $tableB = new Table('foo'); - $tableB->addColumn('ID', 'integer'); - - $c = new Comparator(); - $tableDiff = $c->diffTable($tableA, $tableB); - - self::assertFalse($tableDiff); - } - - public function testCompareIndexBasedOnPropertiesNotName() : void - { - $tableA = new Table('foo'); - $tableA->addColumn('id', 'integer'); - $tableA->addIndex(['id'], 'foo_bar_idx'); - - $tableB = new Table('foo'); - $tableB->addColumn('ID', 'integer'); - $tableB->addIndex(['id'], 'bar_foo_idx'); - - $c = new Comparator(); - $tableDiff = new TableDiff('foo'); - $tableDiff->fromTable = $tableA; - $tableDiff->renamedIndexes['foo_bar_idx'] = new Index('bar_foo_idx', ['id']); - - self::assertEquals( - $tableDiff, - $c->diffTable($tableA, $tableB) - ); - } - - public function testCompareForeignKeyBasedOnPropertiesNotName() : void - { - $tableA = new Table('foo'); - $tableA->addColumn('id', 'integer'); - $tableA->addNamedForeignKeyConstraint('foo_constraint', 'bar', ['id'], ['id']); - - $tableB = new Table('foo'); - $tableB->addColumn('ID', 'integer'); - $tableB->addNamedForeignKeyConstraint('bar_constraint', 'bar', ['id'], ['id']); - - $c = new Comparator(); - $tableDiff = $c->diffTable($tableA, $tableB); - - self::assertFalse($tableDiff); - } - - public function testCompareForeignKeyRestrictNoActionAreTheSame() : void - { - $fk1 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'NO ACTION']); - $fk2 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'RESTRICT']); - - $c = new Comparator(); - self::assertFalse($c->diffForeignKey($fk1, $fk2)); - } - - /** - * @group DBAL-492 - */ - public function testCompareForeignKeyNamesUnqualifiedAsNoSchemaInformationIsAvailable() : void - { - $fk1 = new ForeignKeyConstraint(['foo'], 'foo.bar', ['baz'], 'fk1'); - $fk2 = new ForeignKeyConstraint(['foo'], 'baz.bar', ['baz'], 'fk1'); - - $c = new Comparator(); - self::assertFalse($c->diffForeignKey($fk1, $fk2)); - } - - public function testDetectRenameColumn() : void - { - $tableA = new Table('foo'); - $tableA->addColumn('foo', 'integer'); - - $tableB = new Table('foo'); - $tableB->addColumn('bar', 'integer'); - - $c = new Comparator(); - $tableDiff = $c->diffTable($tableA, $tableB); - - self::assertCount(0, $tableDiff->addedColumns); - self::assertCount(0, $tableDiff->removedColumns); - self::assertArrayHasKey('foo', $tableDiff->renamedColumns); - self::assertEquals('bar', $tableDiff->renamedColumns['foo']->getName()); - } - - /** - * You can easily have ambiguities in the column renaming. If these - * are detected no renaming should take place, instead adding and dropping - * should be used exclusively. - * - * @group DBAL-24 - */ - public function testDetectRenameColumnAmbiguous() : void - { - $tableA = new Table('foo'); - $tableA->addColumn('foo', 'integer'); - $tableA->addColumn('bar', 'integer'); - - $tableB = new Table('foo'); - $tableB->addColumn('baz', 'integer'); - - $c = new Comparator(); - $tableDiff = $c->diffTable($tableA, $tableB); - - self::assertCount(1, $tableDiff->addedColumns, "'baz' should be added, not created through renaming!"); - self::assertArrayHasKey('baz', $tableDiff->addedColumns, "'baz' should be added, not created through renaming!"); - self::assertCount(2, $tableDiff->removedColumns, "'foo' and 'bar' should both be dropped, an ambiguity exists which one could be renamed to 'baz'."); - self::assertArrayHasKey('foo', $tableDiff->removedColumns, "'foo' should be removed."); - self::assertArrayHasKey('bar', $tableDiff->removedColumns, "'bar' should be removed."); - self::assertCount(0, $tableDiff->renamedColumns, 'no renamings should take place.'); - } - - /** - * @group DBAL-1063 - */ - public function testDetectRenameIndex() : void - { - $table1 = new Table('foo'); - $table1->addColumn('foo', 'integer'); - - $table2 = clone $table1; - - $table1->addIndex(['foo'], 'idx_foo'); - - $table2->addIndex(['foo'], 'idx_bar'); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($table1, $table2); - - self::assertCount(0, $tableDiff->addedIndexes); - self::assertCount(0, $tableDiff->removedIndexes); - self::assertArrayHasKey('idx_foo', $tableDiff->renamedIndexes); - self::assertEquals('idx_bar', $tableDiff->renamedIndexes['idx_foo']->getName()); - } - - /** - * You can easily have ambiguities in the index renaming. If these - * are detected no renaming should take place, instead adding and dropping - * should be used exclusively. - * - * @group DBAL-1063 - */ - public function testDetectRenameIndexAmbiguous() : void - { - $table1 = new Table('foo'); - $table1->addColumn('foo', 'integer'); - - $table2 = clone $table1; - - $table1->addIndex(['foo'], 'idx_foo'); - $table1->addIndex(['foo'], 'idx_bar'); - - $table2->addIndex(['foo'], 'idx_baz'); - - $comparator = new Comparator(); - $tableDiff = $comparator->diffTable($table1, $table2); - - self::assertCount(1, $tableDiff->addedIndexes); - self::assertArrayHasKey('idx_baz', $tableDiff->addedIndexes); - self::assertCount(2, $tableDiff->removedIndexes); - self::assertArrayHasKey('idx_foo', $tableDiff->removedIndexes); - self::assertArrayHasKey('idx_bar', $tableDiff->removedIndexes); - self::assertCount(0, $tableDiff->renamedIndexes); - } - - public function testDetectChangeIdentifierType() : void - { - $this->markTestSkipped('DBAL-2 was reopened, this test cannot work anymore.'); - - $tableA = new Table('foo'); - $tableA->addColumn('id', 'integer', ['autoincrement' => false]); - - $tableB = new Table('foo'); - $tableB->addColumn('id', 'integer', ['autoincrement' => true]); - - $c = new Comparator(); - $tableDiff = $c->diffTable($tableA, $tableB); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertArrayHasKey('id', $tableDiff->changedColumns); - } - - /** - * @group DBAL-105 - */ - public function testDiff() : void - { - $table = new Table('twitter_users'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->addColumn('twitterId', 'integer'); - $table->addColumn('displayName', 'string'); - $table->setPrimaryKey(['id']); - - $newtable = new Table('twitter_users'); - $newtable->addColumn('id', 'integer', ['autoincrement' => true]); - $newtable->addColumn('twitter_id', 'integer'); - $newtable->addColumn('display_name', 'string'); - $newtable->addColumn('logged_in_at', 'datetime'); - $newtable->setPrimaryKey(['id']); - - $c = new Comparator(); - $tableDiff = $c->diffTable($table, $newtable); - - self::assertInstanceOf(TableDiff::class, $tableDiff); - self::assertEquals(['twitterid', 'displayname'], array_keys($tableDiff->renamedColumns)); - self::assertEquals(['logged_in_at'], array_keys($tableDiff->addedColumns)); - self::assertCount(0, $tableDiff->removedColumns); - } - - /** - * @group DBAL-112 - */ - public function testChangedSequence() : void - { - $schema = new Schema(); - $sequence = $schema->createSequence('baz'); - - $schemaNew = clone $schema; - $schemaNew->getSequence('baz')->setAllocationSize(20); - - $c = new Comparator(); - $diff = $c->compare($schema, $schemaNew); - - self::assertSame($diff->changedSequences[0], $schemaNew->getSequence('baz')); - } - - /** - * @group DBAL-106 - */ - public function testDiffDecimalWithNullPrecision() : void - { - $column = new Column('foo', Type::getType('decimal')); - $column->setPrecision(null); - - $column2 = new Column('foo', Type::getType('decimal')); - - $c = new Comparator(); - self::assertEquals([], $c->diffColumn($column, $column2)); - } - - /** - * @group DBAL-204 - */ - public function testFqnSchemaComparison() : void - { - $config = new SchemaConfig(); - $config->setName('foo'); - - $oldSchema = new Schema([], [], $config); - $oldSchema->createTable('bar'); - - $newSchema = new Schema([], [], $config); - $newSchema->createTable('foo.bar'); - - $expected = new SchemaDiff(); - $expected->fromSchema = $oldSchema; - - self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); - } - - /** - * @group DBAL-669 - */ - public function testNamespacesComparison() : void - { - $config = new SchemaConfig(); - $config->setName('schemaName'); - - $oldSchema = new Schema([], [], $config); - $oldSchema->createTable('taz'); - $oldSchema->createTable('war.tab'); - - $newSchema = new Schema([], [], $config); - $newSchema->createTable('bar.tab'); - $newSchema->createTable('baz.tab'); - $newSchema->createTable('war.tab'); - - $expected = new SchemaDiff(); - $expected->fromSchema = $oldSchema; - $expected->newNamespaces = ['bar' => 'bar', 'baz' => 'baz']; - - $diff = Comparator::compareSchemas($oldSchema, $newSchema); - - self::assertEquals(['bar' => 'bar', 'baz' => 'baz'], $diff->newNamespaces); - self::assertCount(2, $diff->newTables); - } - - /** - * @group DBAL-204 - */ - public function testFqnSchemaComparisonDifferentSchemaNameButSameTableNoDiff() : void - { - $config = new SchemaConfig(); - $config->setName('foo'); - - $oldSchema = new Schema([], [], $config); - $oldSchema->createTable('foo.bar'); - - $newSchema = new Schema(); - $newSchema->createTable('bar'); - - $expected = new SchemaDiff(); - $expected->fromSchema = $oldSchema; - - self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); - } - - /** - * @group DBAL-204 - */ - public function testFqnSchemaComparisonNoSchemaSame() : void - { - $config = new SchemaConfig(); - $config->setName('foo'); - $oldSchema = new Schema([], [], $config); - $oldSchema->createTable('bar'); - - $newSchema = new Schema(); - $newSchema->createTable('bar'); - - $expected = new SchemaDiff(); - $expected->fromSchema = $oldSchema; - - self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); - } - - /** - * @group DDC-1657 - */ - public function testAutoIncrementSequences() : void - { - $oldSchema = new Schema(); - $table = $oldSchema->createTable('foo'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['id']); - $oldSchema->createSequence('foo_id_seq'); - - $newSchema = new Schema(); - $table = $newSchema->createTable('foo'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['id']); - - $c = new Comparator(); - $diff = $c->compare($oldSchema, $newSchema); - - self::assertCount(0, $diff->removedSequences); - } - - /** - * Check that added autoincrement sequence is not populated in newSequences - * - * @group DBAL-562 - */ - public function testAutoIncrementNoSequences() : void - { - $oldSchema = new Schema(); - $table = $oldSchema->createTable('foo'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['id']); - - $newSchema = new Schema(); - $table = $newSchema->createTable('foo'); - $table->addColumn('id', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['id']); - $newSchema->createSequence('foo_id_seq'); - - $c = new Comparator(); - $diff = $c->compare($oldSchema, $newSchema); - - self::assertCount(0, $diff->newSequences); - } - - /** - * You can get multiple drops for a FK when a table referenced by a foreign - * key is deleted, as this FK is referenced twice, once on the orphanedForeignKeys - * array because of the dropped table, and once on changedTables array. We - * now check that the key is present once. - */ - public function testAvoidMultipleDropForeignKey() : void - { - $oldSchema = new Schema(); - - $tableA = $oldSchema->createTable('table_a'); - $tableA->addColumn('id', 'integer'); - - $tableB = $oldSchema->createTable('table_b'); - $tableB->addColumn('id', 'integer'); - - $tableC = $oldSchema->createTable('table_c'); - $tableC->addColumn('id', 'integer'); - $tableC->addColumn('table_a_id', 'integer'); - $tableC->addColumn('table_b_id', 'integer'); - - $tableC->addForeignKeyConstraint($tableA, ['table_a_id'], ['id']); - $tableC->addForeignKeyConstraint($tableB, ['table_b_id'], ['id']); - - $newSchema = new Schema(); - - $tableB = $newSchema->createTable('table_b'); - $tableB->addColumn('id', 'integer'); - - $tableC = $newSchema->createTable('table_c'); - $tableC->addColumn('id', 'integer'); - - $comparator = new Comparator(); - $schemaDiff = $comparator->compare($oldSchema, $newSchema); - - self::assertCount(1, $schemaDiff->changedTables['table_c']->removedForeignKeys); - self::assertCount(1, $schemaDiff->orphanedForeignKeys); - } - - public function testCompareChangedColumn() : void - { - $oldSchema = new Schema(); - - $tableFoo = $oldSchema->createTable('foo'); - $tableFoo->addColumn('id', 'integer'); - - $newSchema = new Schema(); - $table = $newSchema->createTable('foo'); - $table->addColumn('id', 'string'); - - $expected = new SchemaDiff(); - $expected->fromSchema = $oldSchema; - $tableDiff = $expected->changedTables['foo'] = new TableDiff('foo'); - $tableDiff->fromTable = $tableFoo; - $columnDiff = $tableDiff->changedColumns['id'] = new ColumnDiff('id', $table->getColumn('id')); - $columnDiff->fromColumn = $tableFoo->getColumn('id'); - $columnDiff->changedProperties = ['type']; - - self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); - } - - public function testCompareChangedBinaryColumn() : void - { - $oldSchema = new Schema(); - - $tableFoo = $oldSchema->createTable('foo'); - $tableFoo->addColumn('id', 'binary'); - - $newSchema = new Schema(); - $table = $newSchema->createTable('foo'); - $table->addColumn('id', 'binary', ['length' => 42, 'fixed' => true]); - - $expected = new SchemaDiff(); - $expected->fromSchema = $oldSchema; - $tableDiff = $expected->changedTables['foo'] = new TableDiff('foo'); - $tableDiff->fromTable = $tableFoo; - $columnDiff = $tableDiff->changedColumns['id'] = new ColumnDiff('id', $table->getColumn('id')); - $columnDiff->fromColumn = $tableFoo->getColumn('id'); - $columnDiff->changedProperties = ['length', 'fixed']; - - self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema)); - } - - /** - * @group DBAL-617 - */ - public function testCompareQuotedAndUnquotedForeignKeyColumns() : void - { - $fk1 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'NO ACTION']); - $fk2 = new ForeignKeyConstraint(['`foo`'], 'bar', ['`baz`'], 'fk1', ['onDelete' => 'NO ACTION']); - - $comparator = new Comparator(); - $diff = $comparator->diffForeignKey($fk1, $fk2); - - self::assertFalse($diff); - } - - public function assertSchemaTableChangeCount(SchemaDiff $diff, int $newTableCount = 0, int $changeTableCount = 0, int $removeTableCount = 0) : void - { - self::assertCount($newTableCount, $diff->newTables); - self::assertCount($changeTableCount, $diff->changedTables); - self::assertCount($removeTableCount, $diff->removedTables); - } - - public function assertSchemaSequenceChangeCount( - SchemaDiff $diff, - int $newSequenceCount = 0, - int $changeSequenceCount = 0, - int $removeSequenceCount = 0 - ) : void { - self::assertCount($newSequenceCount, $diff->newSequences, 'Expected number of new sequences is wrong.'); - self::assertCount($changeSequenceCount, $diff->changedSequences, 'Expected number of changed sequences is wrong.'); - self::assertCount($removeSequenceCount, $diff->removedSequences, 'Expected number of removed sequences is wrong.'); - } - - public function testDiffColumnPlatformOptions() : void - { - $column1 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'bar' => 'bar']]); - $column2 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'foobar' => 'foobar']]); - $column3 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'bar' => 'rab']]); - $column4 = new Column('foo', Type::getType('string')); - - $comparator = new Comparator(); - - self::assertEquals([], $comparator->diffColumn($column1, $column2)); - self::assertEquals([], $comparator->diffColumn($column2, $column1)); - self::assertEquals(['bar'], $comparator->diffColumn($column1, $column3)); - self::assertEquals(['bar'], $comparator->diffColumn($column3, $column1)); - self::assertEquals([], $comparator->diffColumn($column1, $column4)); - self::assertEquals([], $comparator->diffColumn($column4, $column1)); - } - - public function testComplexDiffColumn() : void - { - $column1 = new Column('foo', Type::getType('string'), [ - 'platformOptions' => ['foo' => 'foo'], - 'customSchemaOptions' => ['foo' => 'bar'], - ]); - - $column2 = new Column('foo', Type::getType('string'), [ - 'platformOptions' => ['foo' => 'bar'], - ]); - - $comparator = new Comparator(); - - self::assertEquals([], $comparator->diffColumn($column1, $column2)); - self::assertEquals([], $comparator->diffColumn($column2, $column1)); - } - - /** - * @group DBAL-669 - */ - public function testComparesNamespaces() : void - { - $comparator = new Comparator(); - $fromSchema = $this->getMockBuilder(Schema::class) - ->setMethods(['getNamespaces', 'hasNamespace']) - ->getMock(); - $toSchema = $this->getMockBuilder(Schema::class) - ->setMethods(['getNamespaces', 'hasNamespace']) - ->getMock(); - - $fromSchema->expects($this->once()) - ->method('getNamespaces') - ->will($this->returnValue(['foo', 'bar'])); - - $fromSchema->expects($this->at(0)) - ->method('hasNamespace') - ->with('bar') - ->will($this->returnValue(true)); - - $fromSchema->expects($this->at(1)) - ->method('hasNamespace') - ->with('baz') - ->will($this->returnValue(false)); - - $toSchema->expects($this->once()) - ->method('getNamespaces') - ->will($this->returnValue(['bar', 'baz'])); - - $toSchema->expects($this->at(1)) - ->method('hasNamespace') - ->with('foo') - ->will($this->returnValue(false)); - - $toSchema->expects($this->at(2)) - ->method('hasNamespace') - ->with('bar') - ->will($this->returnValue(true)); - - $expected = new SchemaDiff(); - $expected->fromSchema = $fromSchema; - $expected->newNamespaces = ['baz' => 'baz']; - $expected->removedNamespaces = ['foo' => 'foo']; - - self::assertEquals($expected, $comparator->compare($fromSchema, $toSchema)); - } - - public function testCompareGuidColumns() : void - { - $comparator = new Comparator(); - - $column1 = new Column('foo', Type::getType('guid'), ['comment' => 'GUID 1']); - $column2 = new Column( - 'foo', - Type::getType('guid'), - ['notnull' => false, 'length' => '36', 'fixed' => true, 'default' => 'NEWID()', 'comment' => 'GUID 2.'] - ); - - self::assertEquals(['notnull', 'default', 'comment'], $comparator->diffColumn($column1, $column2)); - self::assertEquals(['notnull', 'default', 'comment'], $comparator->diffColumn($column2, $column1)); - } - - /** - * @group DBAL-1009 - * @dataProvider getCompareColumnComments - */ - public function testCompareColumnComments(?string $comment1, ?string $comment2, bool $equals) : void - { - $column1 = new Column('foo', Type::getType('integer'), ['comment' => $comment1]); - $column2 = new Column('foo', Type::getType('integer'), ['comment' => $comment2]); - - $comparator = new Comparator(); - - $expectedDiff = $equals ? [] : ['comment']; - - $actualDiff = $comparator->diffColumn($column1, $column2); - - self::assertSame($expectedDiff, $actualDiff); - - $actualDiff = $comparator->diffColumn($column2, $column1); - - self::assertSame($expectedDiff, $actualDiff); - } - - /** - * @return mixed[][] - */ - public static function getCompareColumnComments() : iterable - { - return [ - [null, null, true], - ['', '', true], - [' ', ' ', true], - ['0', '0', true], - ['foo', 'foo', true], - - [null, '', true], - [null, ' ', false], - [null, '0', false], - [null, 'foo', false], - - ['', ' ', false], - ['', '0', false], - ['', 'foo', false], - - [' ', '0', false], - [' ', 'foo', false], - - ['0', 'foo', false], - ]; - } - - public function testForeignKeyRemovalWithRenamedLocalColumn() : void - { - $fromSchema = new Schema([ - 'table1' => new Table( - 'table1', - [ - 'id' => new Column('id', Type::getType('integer')), - ] - ), - 'table2' => new Table( - 'table2', - [ - 'id' => new Column('id', Type::getType('integer')), - 'id_table1' => new Column('id_table1', Type::getType('integer')), - ], - [], - [ - new ForeignKeyConstraint(['id_table1'], 'table1', ['id'], 'fk_table2_table1'), - ] - ), - ]); - $toSchema = new Schema([ - 'table2' => new Table( - 'table2', - [ - 'id' => new Column('id', Type::getType('integer')), - 'id_table3' => new Column('id_table3', Type::getType('integer')), - ], - [], - [ - new ForeignKeyConstraint(['id_table3'], 'table3', ['id'], 'fk_table2_table3'), - ] - ), - 'table3' => new Table( - 'table3', - [ - 'id' => new Column('id', Type::getType('integer')), - ] - ), - ]); - $actual = Comparator::compareSchemas($fromSchema, $toSchema); - self::assertArrayHasKey('table2', $actual->changedTables); - self::assertCount(1, $actual->orphanedForeignKeys); - self::assertEquals('fk_table2_table1', $actual->orphanedForeignKeys[0]->getName()); - self::assertCount(1, $actual->changedTables['table2']->addedForeignKeys, 'FK to table3 should be added.'); - self::assertEquals('table3', $actual->changedTables['table2']->addedForeignKeys[0]->getForeignTableName()); - } -} diff --git a/tests/Unit/DBAL/Schema/ForeignKeyConstraintTest.php b/tests/Unit/DBAL/Schema/ForeignKeyConstraintTest.php deleted file mode 100644 index 675b16f..0000000 --- a/tests/Unit/DBAL/Schema/ForeignKeyConstraintTest.php +++ /dev/null @@ -1,59 +0,0 @@ -getMockBuilder(Index::class) - ->disableOriginalConstructor() - ->getMock(); - $index->expects($this->once()) - ->method('getColumns') - ->will($this->returnValue($indexColumns)); - - self::assertSame($expectedResult, $foreignKey->intersectsIndexColumns($index)); - } - - /** - * @return mixed[][] - */ - public static function getIntersectsIndexColumnsData() : iterable - { - return [ - [['baz'], false], - [['baz', 'bloo'], false], - - [['foo'], true], - [['bar'], true], - - [['foo', 'bar'], true], - [['bar', 'foo'], true], - - [['foo', 'baz'], true], - [['baz', 'foo'], true], - - [['bar', 'baz'], true], - [['baz', 'bar'], true], - - [['foo', 'bloo', 'baz'], true], - [['bloo', 'foo', 'baz'], true], - [['bloo', 'baz', 'foo'], true], - - [['FOO'], true], - ]; - } -} diff --git a/tests/Unit/DBAL/Schema/IndexTest.php b/tests/Unit/DBAL/Schema/IndexTest.php deleted file mode 100644 index a6badab..0000000 --- a/tests/Unit/DBAL/Schema/IndexTest.php +++ /dev/null @@ -1,191 +0,0 @@ -createIndex(); - self::assertEquals('foo', $idx->getName()); - $columns = $idx->getColumns(); - self::assertCount(2, $columns); - self::assertEquals(['bar', 'baz'], $columns); - self::assertFalse($idx->isUnique()); - self::assertFalse($idx->isPrimary()); - } - - public function testCreatePrimary() : void - { - $idx = $this->createIndex(false, true); - self::assertTrue($idx->isUnique()); - self::assertTrue($idx->isPrimary()); - } - - public function testCreateUnique() : void - { - $idx = $this->createIndex(true, false); - self::assertTrue($idx->isUnique()); - self::assertFalse($idx->isPrimary()); - } - - /** - * @group DBAL-50 - */ - public function testFulfilledByUnique() : void - { - $idx1 = $this->createIndex(true, false); - $idx2 = $this->createIndex(true, false); - $idx3 = $this->createIndex(); - - self::assertTrue($idx1->isFullfilledBy($idx2)); - self::assertFalse($idx1->isFullfilledBy($idx3)); - } - - /** - * @group DBAL-50 - */ - public function testFulfilledByPrimary() : void - { - $idx1 = $this->createIndex(true, true); - $idx2 = $this->createIndex(true, true); - $idx3 = $this->createIndex(true, false); - - self::assertTrue($idx1->isFullfilledBy($idx2)); - self::assertFalse($idx1->isFullfilledBy($idx3)); - } - - /** - * @group DBAL-50 - */ - public function testFulfilledByIndex() : void - { - $idx1 = $this->createIndex(); - $idx2 = $this->createIndex(); - $pri = $this->createIndex(true, true); - $uniq = $this->createIndex(true); - - self::assertTrue($idx1->isFullfilledBy($idx2)); - self::assertTrue($idx1->isFullfilledBy($pri)); - self::assertTrue($idx1->isFullfilledBy($uniq)); - } - - public function testFulfilledWithPartial() : void - { - $without = new Index('without', ['col1', 'col2'], true, false, [], []); - $partial = new Index('partial', ['col1', 'col2'], true, false, [], ['where' => 'col1 IS NULL']); - $another = new Index('another', ['col1', 'col2'], true, false, [], ['where' => 'col1 IS NULL']); - - self::assertFalse($partial->isFullfilledBy($without)); - self::assertFalse($without->isFullfilledBy($partial)); - - self::assertTrue($partial->isFullfilledBy($partial)); - - self::assertTrue($partial->isFullfilledBy($another)); - self::assertTrue($another->isFullfilledBy($partial)); - } - - public function testOverrulesWithPartial() : void - { - $without = new Index('without', ['col1', 'col2'], true, false, [], []); - $partial = new Index('partial', ['col1', 'col2'], true, false, [], ['where' => 'col1 IS NULL']); - $another = new Index('another', ['col1', 'col2'], true, false, [], ['where' => 'col1 IS NULL']); - - self::assertFalse($partial->overrules($without)); - self::assertFalse($without->overrules($partial)); - - self::assertTrue($partial->overrules($partial)); - - self::assertTrue($partial->overrules($another)); - self::assertTrue($another->overrules($partial)); - } - - /** - * @param string[] $columns - * @param int[]|null[] $lengths1 - * @param int[]|null[] $lengths2 - * - * @dataProvider indexLengthProvider - */ - public function testFulfilledWithLength(array $columns, array $lengths1, array $lengths2, bool $expected) : void - { - $index1 = new Index('index1', $columns, false, false, [], ['lengths' => $lengths1]); - $index2 = new Index('index2', $columns, false, false, [], ['lengths' => $lengths2]); - - self::assertSame($expected, $index1->isFullfilledBy($index2)); - self::assertSame($expected, $index2->isFullfilledBy($index1)); - } - - /** - * @return mixed[][] - */ - public static function indexLengthProvider() : iterable - { - return [ - 'empty' => [['column'], [], [], true], - 'same' => [['column'], [64], [64], true], - 'different' => [['column'], [32], [64], false], - 'sparse-different-positions' => [['column1', 'column2'], [0 => 32], [1 => 32], false], - 'sparse-same-positions' => [['column1', 'column2'], [null, 32], [1 => 32], true], - ]; - } - - /** - * @group DBAL-220 - */ - public function testFlags() : void - { - $idx1 = $this->createIndex(); - self::assertFalse($idx1->hasFlag('clustered')); - self::assertEmpty($idx1->getFlags()); - - $idx1->addFlag('clustered'); - self::assertTrue($idx1->hasFlag('clustered')); - self::assertTrue($idx1->hasFlag('CLUSTERED')); - self::assertSame(['clustered'], $idx1->getFlags()); - - $idx1->removeFlag('clustered'); - self::assertFalse($idx1->hasFlag('clustered')); - self::assertEmpty($idx1->getFlags()); - } - - /** - * @group DBAL-285 - */ - public function testIndexQuotes() : void - { - $index = new Index('foo', ['`bar`', '`baz`']); - - self::assertTrue($index->spansColumns(['bar', 'baz'])); - self::assertTrue($index->hasColumnAtPosition('bar', 0)); - self::assertTrue($index->hasColumnAtPosition('baz', 1)); - - self::assertFalse($index->hasColumnAtPosition('bar', 1)); - self::assertFalse($index->hasColumnAtPosition('baz', 0)); - } - - public function testOptions() : void - { - $idx1 = $this->createIndex(); - self::assertFalse($idx1->hasOption('where')); - self::assertEmpty($idx1->getOptions()); - - $idx2 = $this->createIndex(false, false, ['where' => 'name IS NULL']); - self::assertTrue($idx2->hasOption('where')); - self::assertTrue($idx2->hasOption('WHERE')); - self::assertSame('name IS NULL', $idx2->getOption('where')); - self::assertSame('name IS NULL', $idx2->getOption('WHERE')); - self::assertSame(['where' => 'name IS NULL'], $idx2->getOptions()); - } -} diff --git a/tests/Unit/DBAL/Schema/SchemaDiffTest.php b/tests/Unit/DBAL/Schema/SchemaDiffTest.php deleted file mode 100644 index 77ddd15..0000000 --- a/tests/Unit/DBAL/Schema/SchemaDiffTest.php +++ /dev/null @@ -1,124 +0,0 @@ -createSchemaDiff(); - $platform = $this->createPlatform(true); - - $sql = $diff->toSql($platform); - - $expected = ['create_schema', 'drop_orphan_fk', 'alter_seq', 'drop_seq', 'create_seq', 'create_table', 'create_foreign_key', 'drop_table', 'alter_table']; - - self::assertEquals($expected, $sql); - } - - public function testSchemaDiffToSaveSql() : void - { - $diff = $this->createSchemaDiff(); - $platform = $this->createPlatform(false); - - $sql = $diff->toSaveSql($platform); - - $expected = ['create_schema', 'alter_seq', 'create_seq', 'create_table', 'create_foreign_key', 'alter_table']; - - self::assertEquals($expected, $sql); - } - - /** - * @return AbstractPlatform|MockObject - */ - private function createPlatform(bool $unsafe) - { - /** @var AbstractPlatform|MockObject $platform */ - $platform = $this->createMock(AbstractPlatform::class); - $platform->expects($this->exactly(1)) - ->method('getCreateSchemaSQL') - ->with('foo_ns') - ->will($this->returnValue('create_schema')); - if ($unsafe) { - $platform->expects($this->exactly(1)) - ->method('getDropSequenceSql') - ->with($this->isInstanceOf(Sequence::class)) - ->will($this->returnValue('drop_seq')); - } - $platform->expects($this->exactly(1)) - ->method('getAlterSequenceSql') - ->with($this->isInstanceOf(Sequence::class)) - ->will($this->returnValue('alter_seq')); - $platform->expects($this->exactly(1)) - ->method('getCreateSequenceSql') - ->with($this->isInstanceOf(Sequence::class)) - ->will($this->returnValue('create_seq')); - if ($unsafe) { - $platform->expects($this->exactly(1)) - ->method('getDropTableSql') - ->with($this->isInstanceOf(Table::class)) - ->will($this->returnValue('drop_table')); - } - $platform->expects($this->exactly(1)) - ->method('getCreateTableSql') - ->with($this->isInstanceOf(Table::class)) - ->will($this->returnValue(['create_table'])); - $platform->expects($this->exactly(1)) - ->method('getCreateForeignKeySQL') - ->with($this->isInstanceOf(ForeignKeyConstraint::class)) - ->will($this->returnValue('create_foreign_key')); - $platform->expects($this->exactly(1)) - ->method('getAlterTableSql') - ->with($this->isInstanceOf(TableDiff::class)) - ->will($this->returnValue(['alter_table'])); - if ($unsafe) { - $platform->expects($this->exactly(1)) - ->method('getDropForeignKeySql') - ->with( - $this->isInstanceOf(ForeignKeyConstraint::class), - $this->isInstanceOf(Table::class) - ) - ->will($this->returnValue('drop_orphan_fk')); - } - $platform->expects($this->exactly(1)) - ->method('supportsSchemas') - ->will($this->returnValue(true)); - $platform->expects($this->exactly(1)) - ->method('supportsSequences') - ->will($this->returnValue(true)); - $platform->expects($this->exactly(2)) - ->method('supportsForeignKeyConstraints') - ->will($this->returnValue(true)); - - return $platform; - } - - public function createSchemaDiff() : SchemaDiff - { - $diff = new SchemaDiff(); - $diff->newNamespaces['foo_ns'] = 'foo_ns'; - $diff->removedNamespaces['bar_ns'] = 'bar_ns'; - $diff->changedSequences['foo_seq'] = new Sequence('foo_seq'); - $diff->newSequences['bar_seq'] = new Sequence('bar_seq'); - $diff->removedSequences['baz_seq'] = new Sequence('baz_seq'); - $diff->newTables['foo_table'] = new Table('foo_table'); - $diff->removedTables['bar_table'] = new Table('bar_table'); - $diff->changedTables['baz_table'] = new TableDiff('baz_table'); - $diff->newTables['foo_table']->addColumn('foreign_id', 'integer'); - $diff->newTables['foo_table']->addForeignKeyConstraint('foreign_table', ['foreign_id'], ['id']); - $fk = new ForeignKeyConstraint(['id'], 'foreign_table', ['id']); - $fk->setLocalTable(new Table('local_table')); - $diff->orphanedForeignKeys[] = $fk; - - return $diff; - } -} diff --git a/tests/Unit/DBAL/Schema/SchemaTest.php b/tests/Unit/DBAL/Schema/SchemaTest.php deleted file mode 100644 index a87b787..0000000 --- a/tests/Unit/DBAL/Schema/SchemaTest.php +++ /dev/null @@ -1,465 +0,0 @@ -hasTable($tableName)); - - $tables = $schema->getTables(); - self::assertArrayHasKey($tableName, $tables); - self::assertSame($table, $tables[$tableName]); - self::assertSame($table, $schema->getTable($tableName)); - self::assertTrue($schema->hasTable($tableName)); - } - - public function testTableMatchingCaseInsensitive() : void - { - $table = new Table('Foo'); - - $schema = new Schema([$table]); - self::assertTrue($schema->hasTable('foo')); - self::assertTrue($schema->hasTable('FOO')); - - self::assertSame($table, $schema->getTable('FOO')); - self::assertSame($table, $schema->getTable('foo')); - self::assertSame($table, $schema->getTable('Foo')); - } - - public function testGetUnknownTableThrowsException() : void - { - $this->expectException(SchemaException::class); - - $schema = new Schema(); - $schema->getTable('unknown'); - } - - public function testCreateTableTwiceThrowsException() : void - { - $this->expectException(SchemaException::class); - - $tableName = 'foo'; - $table = new Table($tableName); - $tables = [$table, $table]; - - $schema = new Schema($tables); - } - - public function testRenameTable() : void - { - $tableName = 'foo'; - $table = new Table($tableName); - $schema = new Schema([$table]); - - self::assertTrue($schema->hasTable('foo')); - $schema->renameTable('foo', 'bar'); - self::assertFalse($schema->hasTable('foo')); - self::assertTrue($schema->hasTable('bar')); - self::assertSame($table, $schema->getTable('bar')); - } - - public function testDropTable() : void - { - $tableName = 'foo'; - $table = new Table($tableName); - $schema = new Schema([$table]); - - self::assertTrue($schema->hasTable('foo')); - - $schema->dropTable('foo'); - - self::assertFalse($schema->hasTable('foo')); - } - - public function testCreateTable() : void - { - $schema = new Schema(); - - self::assertFalse($schema->hasTable('foo')); - - $table = $schema->createTable('foo'); - - self::assertInstanceOf(Table::class, $table); - self::assertEquals('foo', $table->getName()); - self::assertTrue($schema->hasTable('foo')); - } - - public function testAddSequences() : void - { - $sequence = new Sequence('a_seq', 1, 1); - - $schema = new Schema([], [$sequence]); - - self::assertTrue($schema->hasSequence('a_seq')); - self::assertInstanceOf(Sequence::class, $schema->getSequence('a_seq')); - - $sequences = $schema->getSequences(); - self::assertArrayHasKey('public.a_seq', $sequences); - } - - public function testSequenceAccessCaseInsensitive() : void - { - $sequence = new Sequence('a_Seq'); - - $schema = new Schema([], [$sequence]); - self::assertTrue($schema->hasSequence('a_seq')); - self::assertTrue($schema->hasSequence('a_Seq')); - self::assertTrue($schema->hasSequence('A_SEQ')); - - self::assertEquals($sequence, $schema->getSequence('a_seq')); - self::assertEquals($sequence, $schema->getSequence('a_Seq')); - self::assertEquals($sequence, $schema->getSequence('A_SEQ')); - } - - public function testGetUnknownSequenceThrowsException() : void - { - $this->expectException(SchemaException::class); - - $schema = new Schema(); - $schema->getSequence('unknown'); - } - - public function testCreateSequence() : void - { - $schema = new Schema(); - $sequence = $schema->createSequence('a_seq', 10, 20); - - self::assertEquals('a_seq', $sequence->getName()); - self::assertEquals(10, $sequence->getAllocationSize()); - self::assertEquals(20, $sequence->getInitialValue()); - - self::assertTrue($schema->hasSequence('a_seq')); - self::assertInstanceOf(Sequence::class, $schema->getSequence('a_seq')); - - $sequences = $schema->getSequences(); - self::assertArrayHasKey('public.a_seq', $sequences); - } - - public function testDropSequence() : void - { - $sequence = new Sequence('a_seq', 1, 1); - - $schema = new Schema([], [$sequence]); - - $schema->dropSequence('a_seq'); - self::assertFalse($schema->hasSequence('a_seq')); - } - - public function testAddSequenceTwiceThrowsException() : void - { - $this->expectException(SchemaException::class); - - $sequence = new Sequence('a_seq', 1, 1); - - $schema = new Schema([], [$sequence, $sequence]); - } - - public function testConfigMaxIdentifierLength() : void - { - $schemaConfig = new SchemaConfig(); - $schemaConfig->setMaxIdentifierLength(5); - - $schema = new Schema([], [], $schemaConfig); - $table = $schema->createTable('smalltable'); - $table->addColumn('long_id', 'integer'); - $table->addIndex(['long_id']); - - $index = current($table->getIndexes()); - self::assertEquals(5, strlen($index->getName())); - } - - public function testDeepClone() : void - { - $schema = new Schema(); - $sequence = $schema->createSequence('baz'); - - $tableA = $schema->createTable('foo'); - $tableA->addColumn('id', 'integer'); - - $tableB = $schema->createTable('bar'); - $tableB->addColumn('id', 'integer'); - $tableB->addColumn('foo_id', 'integer'); - $tableB->addForeignKeyConstraint($tableA, ['foo_id'], ['id']); - - $schemaNew = clone $schema; - - self::assertNotSame($sequence, $schemaNew->getSequence('baz')); - - self::assertNotSame($tableA, $schemaNew->getTable('foo')); - self::assertNotSame($tableA->getColumn('id'), $schemaNew->getTable('foo')->getColumn('id')); - - self::assertNotSame($tableB, $schemaNew->getTable('bar')); - self::assertNotSame($tableB->getColumn('id'), $schemaNew->getTable('bar')->getColumn('id')); - - $fk = $schemaNew->getTable('bar')->getForeignKeys(); - $fk = current($fk); - - $re = new ReflectionProperty($fk, '_localTable'); - $re->setAccessible(true); - - self::assertSame($schemaNew->getTable('bar'), $re->getValue($fk)); - } - - /** - * @group DBAL-219 - */ - public function testHasTableForQuotedAsset() : void - { - $schema = new Schema(); - - $tableA = $schema->createTable('foo'); - $tableA->addColumn('id', 'integer'); - - self::assertTrue($schema->hasTable('`foo`')); - } - - /** - * @group DBAL-669 - */ - public function testHasNamespace() : void - { - $schema = new Schema(); - - self::assertFalse($schema->hasNamespace('foo')); - - $schema->createTable('foo'); - - self::assertFalse($schema->hasNamespace('foo')); - - $schema->createTable('bar.baz'); - - self::assertFalse($schema->hasNamespace('baz')); - self::assertTrue($schema->hasNamespace('bar')); - self::assertFalse($schema->hasNamespace('tab')); - - $schema->createTable('tab.taz'); - - self::assertTrue($schema->hasNamespace('tab')); - } - - /** - * @group DBAL-669 - */ - public function testCreatesNamespace() : void - { - $schema = new Schema(); - - self::assertFalse($schema->hasNamespace('foo')); - - $schema->createNamespace('foo'); - - self::assertTrue($schema->hasNamespace('foo')); - self::assertTrue($schema->hasNamespace('FOO')); - self::assertTrue($schema->hasNamespace('`foo`')); - self::assertTrue($schema->hasNamespace('`FOO`')); - - $schema->createNamespace('`bar`'); - - self::assertTrue($schema->hasNamespace('bar')); - self::assertTrue($schema->hasNamespace('BAR')); - self::assertTrue($schema->hasNamespace('`bar`')); - self::assertTrue($schema->hasNamespace('`BAR`')); - - self::assertSame(['foo' => 'foo', 'bar' => '`bar`'], $schema->getNamespaces()); - } - - /** - * @group DBAL-669 - */ - public function testThrowsExceptionOnCreatingNamespaceTwice() : void - { - $schema = new Schema(); - - $schema->createNamespace('foo'); - - $this->expectException(SchemaException::class); - - $schema->createNamespace('foo'); - } - - /** - * @group DBAL-669 - */ - public function testCreatesNamespaceThroughAddingTableImplicitly() : void - { - $schema = new Schema(); - - self::assertFalse($schema->hasNamespace('foo')); - - $schema->createTable('baz'); - - self::assertFalse($schema->hasNamespace('foo')); - self::assertFalse($schema->hasNamespace('baz')); - - $schema->createTable('foo.bar'); - - self::assertTrue($schema->hasNamespace('foo')); - self::assertFalse($schema->hasNamespace('bar')); - - $schema->createTable('`baz`.bloo'); - - self::assertTrue($schema->hasNamespace('baz')); - self::assertFalse($schema->hasNamespace('bloo')); - - $schema->createTable('`baz`.moo'); - - self::assertTrue($schema->hasNamespace('baz')); - self::assertFalse($schema->hasNamespace('moo')); - } - - /** - * @group DBAL-669 - */ - public function testCreatesNamespaceThroughAddingSequenceImplicitly() : void - { - $schema = new Schema(); - - self::assertFalse($schema->hasNamespace('foo')); - - $schema->createSequence('baz'); - - self::assertFalse($schema->hasNamespace('foo')); - self::assertFalse($schema->hasNamespace('baz')); - - $schema->createSequence('foo.bar'); - - self::assertTrue($schema->hasNamespace('foo')); - self::assertFalse($schema->hasNamespace('bar')); - - $schema->createSequence('`baz`.bloo'); - - self::assertTrue($schema->hasNamespace('baz')); - self::assertFalse($schema->hasNamespace('bloo')); - - $schema->createSequence('`baz`.moo'); - - self::assertTrue($schema->hasNamespace('baz')); - self::assertFalse($schema->hasNamespace('moo')); - } - - /** - * @group DBAL-669 - */ - public function testVisitsVisitor() : void - { - $schema = new Schema(); - $visitor = $this->createMock(Visitor::class); - - $schema->createNamespace('foo'); - $schema->createNamespace('bar'); - - $schema->createTable('baz'); - $schema->createTable('bla.bloo'); - - $schema->createSequence('moo'); - $schema->createSequence('war'); - - $visitor->expects($this->once()) - ->method('acceptSchema') - ->with($schema); - - $visitor->expects($this->at(1)) - ->method('acceptTable') - ->with($schema->getTable('baz')); - - $visitor->expects($this->at(2)) - ->method('acceptTable') - ->with($schema->getTable('bla.bloo')); - - $visitor->expects($this->exactly(2)) - ->method('acceptTable'); - - $visitor->expects($this->at(3)) - ->method('acceptSequence') - ->with($schema->getSequence('moo')); - - $visitor->expects($this->at(4)) - ->method('acceptSequence') - ->with($schema->getSequence('war')); - - $visitor->expects($this->exactly(2)) - ->method('acceptSequence'); - - self::assertNull($schema->visit($visitor)); - } - - /** - * @group DBAL-669 - */ - public function testVisitsNamespaceVisitor() : void - { - $schema = new Schema(); - $visitor = $this->createMock(AbstractVisitor::class); - - $schema->createNamespace('foo'); - $schema->createNamespace('bar'); - - $schema->createTable('baz'); - $schema->createTable('bla.bloo'); - - $schema->createSequence('moo'); - $schema->createSequence('war'); - - $visitor->expects($this->once()) - ->method('acceptSchema') - ->with($schema); - - $visitor->expects($this->at(1)) - ->method('acceptNamespace') - ->with('foo'); - - $visitor->expects($this->at(2)) - ->method('acceptNamespace') - ->with('bar'); - - $visitor->expects($this->at(3)) - ->method('acceptNamespace') - ->with('bla'); - - $visitor->expects($this->exactly(3)) - ->method('acceptNamespace'); - - $visitor->expects($this->at(4)) - ->method('acceptTable') - ->with($schema->getTable('baz')); - - $visitor->expects($this->at(5)) - ->method('acceptTable') - ->with($schema->getTable('bla.bloo')); - - $visitor->expects($this->exactly(2)) - ->method('acceptTable'); - - $visitor->expects($this->at(6)) - ->method('acceptSequence') - ->with($schema->getSequence('moo')); - - $visitor->expects($this->at(7)) - ->method('acceptSequence') - ->with($schema->getSequence('war')); - - $visitor->expects($this->exactly(2)) - ->method('acceptSequence'); - - self::assertNull($schema->visit($visitor)); - } -} diff --git a/tests/Unit/DBAL/Schema/SequenceTest.php b/tests/Unit/DBAL/Schema/SequenceTest.php deleted file mode 100644 index d78c656..0000000 --- a/tests/Unit/DBAL/Schema/SequenceTest.php +++ /dev/null @@ -1,47 +0,0 @@ -addColumn('id', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['id']); - - $sequence = new Sequence('foo_id_seq'); - $sequence2 = new Sequence('bar_id_seq'); - $sequence3 = new Sequence('other.foo_id_seq'); - - self::assertTrue($sequence->isAutoIncrementsFor($table)); - self::assertFalse($sequence2->isAutoIncrementsFor($table)); - self::assertFalse($sequence3->isAutoIncrementsFor($table)); - } - - public function testIsAutoincrementForCaseInsensitive() : void - { - $table = new Table('foo'); - $table->addColumn('ID', 'integer', ['autoincrement' => true]); - $table->setPrimaryKey(['ID']); - - $sequence = new Sequence('foo_id_seq'); - $sequence1 = new Sequence('foo_ID_seq'); - $sequence2 = new Sequence('bar_id_seq'); - $sequence3 = new Sequence('bar_ID_seq'); - $sequence4 = new Sequence('other.foo_id_seq'); - - self::assertTrue($sequence->isAutoIncrementsFor($table)); - self::assertTrue($sequence1->isAutoIncrementsFor($table)); - self::assertFalse($sequence2->isAutoIncrementsFor($table)); - self::assertFalse($sequence3->isAutoIncrementsFor($table)); - self::assertFalse($sequence4->isAutoIncrementsFor($table)); - } -} diff --git a/tests/Unit/DBAL/Schema/TableDiffTest.php b/tests/Unit/DBAL/Schema/TableDiffTest.php deleted file mode 100644 index f4cd320..0000000 --- a/tests/Unit/DBAL/Schema/TableDiffTest.php +++ /dev/null @@ -1,65 +0,0 @@ -platform = $this->createMock(AbstractPlatform::class); - } - - /** - * @group DBAL-1013 - */ - public function testReturnsName() : void - { - $tableDiff = new TableDiff('foo'); - - self::assertEquals(new Identifier('foo'), $tableDiff->getName($this->platform)); - } - - /** - * @group DBAL-1016 - */ - public function testPrefersNameFromTableObject() : void - { - $tableMock = $this->getMockBuilder(Table::class) - ->disableOriginalConstructor() - ->getMock(); - - $tableDiff = new TableDiff('foo'); - $tableDiff->fromTable = $tableMock; - - $tableMock->expects($this->once()) - ->method('getQuotedName') - ->with($this->platform) - ->will($this->returnValue('foo')); - - self::assertEquals(new Identifier('foo'), $tableDiff->getName($this->platform)); - } - - /** - * @group DBAL-1013 - */ - public function testReturnsNewName() : void - { - $tableDiff = new TableDiff('foo'); - - self::assertFalse($tableDiff->getNewName()); - - $tableDiff->newName = 'bar'; - - self::assertEquals(new Identifier('bar'), $tableDiff->getNewName()); - } -} diff --git a/tests/Unit/DBAL/Schema/TableTest.php b/tests/Unit/DBAL/Schema/TableTest.php deleted file mode 100644 index faa7168..0000000 --- a/tests/Unit/DBAL/Schema/TableTest.php +++ /dev/null @@ -1,888 +0,0 @@ -expectException(DBALException::class); - - new Table(''); - } - - public function testGetName() : void - { - $table = new Table('foo', [], [], []); - self::assertEquals('foo', $table->getName()); - } - - public function testColumns() : void - { - $type = Type::getType('integer'); - $columns = []; - $columns[] = new Column('foo', $type); - $columns[] = new Column('bar', $type); - $table = new Table('foo', $columns, [], []); - - self::assertTrue($table->hasColumn('foo')); - self::assertTrue($table->hasColumn('bar')); - self::assertFalse($table->hasColumn('baz')); - - self::assertInstanceOf(Column::class, $table->getColumn('foo')); - self::assertInstanceOf(Column::class, $table->getColumn('bar')); - - self::assertCount(2, $table->getColumns()); - } - - public function testColumnsCaseInsensitive() : void - { - $table = new Table('foo'); - $column = $table->addColumn('Foo', 'integer'); - - self::assertTrue($table->hasColumn('Foo')); - self::assertTrue($table->hasColumn('foo')); - self::assertTrue($table->hasColumn('FOO')); - - self::assertSame($column, $table->getColumn('Foo')); - self::assertSame($column, $table->getColumn('foo')); - self::assertSame($column, $table->getColumn('FOO')); - } - - public function testCreateColumn() : void - { - $type = Type::getType('integer'); - - $table = new Table('foo'); - - self::assertFalse($table->hasColumn('bar')); - $table->addColumn('bar', 'integer'); - self::assertTrue($table->hasColumn('bar')); - self::assertSame($type, $table->getColumn('bar')->getType()); - } - - public function testDropColumn() : void - { - $type = Type::getType('integer'); - $columns = []; - $columns[] = new Column('foo', $type); - $columns[] = new Column('bar', $type); - $table = new Table('foo', $columns, [], []); - - self::assertTrue($table->hasColumn('foo')); - self::assertTrue($table->hasColumn('bar')); - - $table->dropColumn('foo')->dropColumn('bar'); - - self::assertFalse($table->hasColumn('foo')); - self::assertFalse($table->hasColumn('bar')); - } - - public function testGetUnknownColumnThrowsException() : void - { - $this->expectException(SchemaException::class); - - $table = new Table('foo', [], [], []); - $table->getColumn('unknown'); - } - - public function testAddColumnTwiceThrowsException() : void - { - $this->expectException(SchemaException::class); - - $type = Type::getType('integer'); - $columns = []; - $columns[] = new Column('foo', $type); - $columns[] = new Column('foo', $type); - $table = new Table('foo', $columns, [], []); - } - - public function testCreateIndex() : void - { - $type = Type::getType('integer'); - $columns = [new Column('foo', $type), new Column('bar', $type), new Column('baz', $type)]; - $table = new Table('foo', $columns); - - $table->addIndex(['foo', 'bar'], 'foo_foo_bar_idx'); - $table->addUniqueIndex(['bar', 'baz'], 'foo_bar_baz_uniq'); - - self::assertTrue($table->hasIndex('foo_foo_bar_idx')); - self::assertTrue($table->hasIndex('foo_bar_baz_uniq')); - } - - public function testIndexCaseInsensitive() : void - { - $type = Type::getType('integer'); - $columns = [ - new Column('foo', $type), - new Column('bar', $type), - new Column('baz', $type), - ]; - $table = new Table('foo', $columns); - - $table->addIndex(['foo', 'bar', 'baz'], 'Foo_Idx'); - - self::assertTrue($table->hasIndex('foo_idx')); - self::assertTrue($table->hasIndex('Foo_Idx')); - self::assertTrue($table->hasIndex('FOO_IDX')); - } - - public function testAddIndexes() : void - { - $type = Type::getType('integer'); - $columns = [ - new Column('foo', $type), - new Column('bar', $type), - ]; - $indexes = [ - new Index('the_primary', ['foo'], true, true), - new Index('bar_idx', ['bar'], false, false), - ]; - $table = new Table('foo', $columns, $indexes, []); - - self::assertTrue($table->hasIndex('the_primary')); - self::assertTrue($table->hasIndex('bar_idx')); - self::assertFalse($table->hasIndex('some_idx')); - - self::assertInstanceOf(Index::class, $table->getPrimaryKey()); - self::assertInstanceOf(Index::class, $table->getIndex('the_primary')); - self::assertInstanceOf(Index::class, $table->getIndex('bar_idx')); - } - - public function testGetUnknownIndexThrowsException() : void - { - $this->expectException(SchemaException::class); - - $table = new Table('foo', [], [], []); - $table->getIndex('unknownIndex'); - } - - public function testAddTwoPrimaryThrowsException() : void - { - $this->expectException(SchemaException::class); - - $type = Type::getType('integer'); - $columns = [new Column('foo', $type), new Column('bar', $type)]; - $indexes = [ - new Index('the_primary', ['foo'], true, true), - new Index('other_primary', ['bar'], true, true), - ]; - $table = new Table('foo', $columns, $indexes, []); - } - - public function testAddTwoIndexesWithSameNameThrowsException() : void - { - $this->expectException(SchemaException::class); - - $type = Type::getType('integer'); - $columns = [new Column('foo', $type), new Column('bar', $type)]; - $indexes = [ - new Index('an_idx', ['foo'], false, false), - new Index('an_idx', ['bar'], false, false), - ]; - $table = new Table('foo', $columns, $indexes, []); - } - - public function testConstraints() : void - { - $constraint = new ForeignKeyConstraint([], 'foo', []); - - $tableA = new Table('foo', [], [], [$constraint]); - $constraints = $tableA->getForeignKeys(); - - self::assertCount(1, $constraints); - self::assertSame($constraint, array_shift($constraints)); - } - - public function testOptions() : void - { - $table = new Table('foo', [], [], [], false, ['foo' => 'bar']); - - self::assertTrue($table->hasOption('foo')); - self::assertEquals('bar', $table->getOption('foo')); - } - - public function testBuilderSetPrimaryKey() : void - { - $table = new Table('foo'); - - $table->addColumn('bar', 'integer'); - $table->setPrimaryKey(['bar']); - - self::assertTrue($table->hasIndex('primary')); - self::assertInstanceOf(Index::class, $table->getPrimaryKey()); - self::assertTrue($table->getIndex('primary')->isUnique()); - self::assertTrue($table->getIndex('primary')->isPrimary()); - } - - public function testBuilderAddUniqueIndex() : void - { - $table = new Table('foo'); - - $table->addColumn('bar', 'integer'); - $table->addUniqueIndex(['bar'], 'my_idx'); - - self::assertTrue($table->hasIndex('my_idx')); - self::assertTrue($table->getIndex('my_idx')->isUnique()); - self::assertFalse($table->getIndex('my_idx')->isPrimary()); - } - - public function testBuilderAddIndex() : void - { - $table = new Table('foo'); - - $table->addColumn('bar', 'integer'); - $table->addIndex(['bar'], 'my_idx'); - - self::assertTrue($table->hasIndex('my_idx')); - self::assertFalse($table->getIndex('my_idx')->isUnique()); - self::assertFalse($table->getIndex('my_idx')->isPrimary()); - } - - public function testBuilderAddIndexWithInvalidNameThrowsException() : void - { - $this->expectException(SchemaException::class); - - $table = new Table('foo'); - $table->addColumn('bar', 'integer'); - $table->addIndex(['bar'], 'invalid name %&/'); - } - - public function testBuilderAddIndexWithUnknownColumnThrowsException() : void - { - $this->expectException(SchemaException::class); - - $table = new Table('foo'); - $table->addIndex(['bar'], 'invalidName'); - } - - public function testBuilderOptions() : void - { - $table = new Table('foo'); - $table->addOption('foo', 'bar'); - self::assertTrue($table->hasOption('foo')); - self::assertEquals('bar', $table->getOption('foo')); - } - - public function testAddForeignKeyConstraintUnknownLocalColumnThrowsException() : void - { - $this->expectException(SchemaException::class); - - $table = new Table('foo'); - $table->addColumn('id', 'integer'); - - $foreignTable = new Table('bar'); - $foreignTable->addColumn('id', 'integer'); - - $table->addForeignKeyConstraint($foreignTable, ['foo'], ['id']); - } - - public function testAddForeignKeyConstraintUnknownForeignColumnThrowsException() : void - { - $this->expectException(SchemaException::class); - - $table = new Table('foo'); - $table->addColumn('id', 'integer'); - - $foreignTable = new Table('bar'); - $foreignTable->addColumn('id', 'integer'); - - $table->addForeignKeyConstraint($foreignTable, ['id'], ['foo']); - } - - public function testAddForeignKeyConstraint() : void - { - $table = new Table('foo'); - $table->addColumn('id', 'integer'); - - $foreignTable = new Table('bar'); - $foreignTable->addColumn('id', 'integer'); - - $table->addForeignKeyConstraint($foreignTable, ['id'], ['id'], ['foo' => 'bar']); - - $constraints = $table->getForeignKeys(); - self::assertCount(1, $constraints); - $constraint = current($constraints); - - self::assertInstanceOf(ForeignKeyConstraint::class, $constraint); - - self::assertTrue($constraint->hasOption('foo')); - self::assertEquals('bar', $constraint->getOption('foo')); - } - - public function testAddIndexWithCaseSensitiveColumnProblem() : void - { - $table = new Table('foo'); - $table->addColumn('id', 'integer'); - - $table->addIndex(['ID'], 'my_idx'); - - self::assertTrue($table->hasIndex('my_idx')); - self::assertEquals(['ID'], $table->getIndex('my_idx')->getColumns()); - self::assertTrue($table->getIndex('my_idx')->spansColumns(['id'])); - } - - public function testAddPrimaryKeyColumnsAreExplicitlySetToNotNull() : void - { - $table = new Table('foo'); - $column = $table->addColumn('id', 'integer', ['notnull' => false]); - - self::assertFalse($column->getNotnull()); - - $table->setPrimaryKey(['id']); - - self::assertTrue($column->getNotnull()); - } - - /** - * @group DDC-133 - */ - public function testAllowImplicitSchemaTableInAutogeneratedIndexNames() : void - { - $table = new Table('foo.bar'); - $table->addColumn('baz', 'integer', []); - $table->addIndex(['baz']); - - self::assertCount(1, $table->getIndexes()); - } - - /** - * @group DBAL-50 - */ - public function testAddForeignKeyIndexImplicitly() : void - { - $table = new Table('foo'); - $table->addColumn('id', 'integer'); - - $foreignTable = new Table('bar'); - $foreignTable->addColumn('id', 'integer'); - - $table->addForeignKeyConstraint($foreignTable, ['id'], ['id'], ['foo' => 'bar']); - - $indexes = $table->getIndexes(); - self::assertCount(1, $indexes); - $index = current($indexes); - - self::assertTrue($table->hasIndex($index->getName())); - self::assertEquals(['id'], $index->getColumns()); - } - - /** - * @group DBAL-1063 - */ - public function testAddForeignKeyDoesNotCreateDuplicateIndex() : void - { - $table = new Table('foo'); - $table->addColumn('bar', 'integer'); - $table->addIndex(['bar'], 'bar_idx'); - - $foreignTable = new Table('bar'); - $foreignTable->addColumn('foo', 'integer'); - - $table->addForeignKeyConstraint($foreignTable, ['bar'], ['foo']); - - self::assertCount(1, $table->getIndexes()); - self::assertTrue($table->hasIndex('bar_idx')); - self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns()); - } - - /** - * @group DBAL-1063 - */ - public function testAddForeignKeyAddsImplicitIndexIfIndexColumnsDoNotSpan() : void - { - $table = new Table('foo'); - $table->addColumn('bar', 'integer'); - $table->addColumn('baz', 'string'); - $table->addColumn('bloo', 'string'); - $table->addIndex(['baz', 'bar'], 'composite_idx'); - $table->addIndex(['bar', 'baz', 'bloo'], 'full_idx'); - - $foreignTable = new Table('bar'); - $foreignTable->addColumn('foo', 'integer'); - $foreignTable->addColumn('baz', 'string'); - - $table->addForeignKeyConstraint($foreignTable, ['bar', 'baz'], ['foo', 'baz']); - - self::assertCount(3, $table->getIndexes()); - self::assertTrue($table->hasIndex('composite_idx')); - self::assertTrue($table->hasIndex('full_idx')); - self::assertTrue($table->hasIndex('idx_8c73652176ff8caa78240498')); - self::assertSame(['baz', 'bar'], $table->getIndex('composite_idx')->getColumns()); - self::assertSame(['bar', 'baz', 'bloo'], $table->getIndex('full_idx')->getColumns()); - self::assertSame(['bar', 'baz'], $table->getIndex('idx_8c73652176ff8caa78240498')->getColumns()); - } - - /** - * @group DBAL-50 - * @group DBAL-1063 - */ - public function testOverrulingIndexDoesNotDropOverruledIndex() : void - { - $table = new Table('bar'); - $table->addColumn('baz', 'integer', []); - $table->addIndex(['baz']); - - $indexes = $table->getIndexes(); - self::assertCount(1, $indexes); - $index = current($indexes); - - $table->addUniqueIndex(['baz']); - self::assertCount(2, $table->getIndexes()); - self::assertTrue($table->hasIndex($index->getName())); - } - - /** - * @group DBAL-1063 - */ - public function testAllowsAddingDuplicateIndexesBasedOnColumns() : void - { - $table = new Table('foo'); - $table->addColumn('bar', 'integer'); - $table->addIndex(['bar'], 'bar_idx'); - $table->addIndex(['bar'], 'duplicate_idx'); - - self::assertCount(2, $table->getIndexes()); - self::assertTrue($table->hasIndex('bar_idx')); - self::assertTrue($table->hasIndex('duplicate_idx')); - self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns()); - self::assertSame(['bar'], $table->getIndex('duplicate_idx')->getColumns()); - } - - /** - * @group DBAL-1063 - */ - public function testAllowsAddingFulfillingIndexesBasedOnColumns() : void - { - $table = new Table('foo'); - $table->addColumn('bar', 'integer'); - $table->addColumn('baz', 'string'); - $table->addIndex(['bar'], 'bar_idx'); - $table->addIndex(['bar', 'baz'], 'fulfilling_idx'); - - self::assertCount(2, $table->getIndexes()); - self::assertTrue($table->hasIndex('bar_idx')); - self::assertTrue($table->hasIndex('fulfilling_idx')); - self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns()); - self::assertSame(['bar', 'baz'], $table->getIndex('fulfilling_idx')->getColumns()); - } - - /** - * @group DBAL-50 - * @group DBAL-1063 - */ - public function testPrimaryKeyOverrulingUniqueIndexDoesNotDropUniqueIndex() : void - { - $table = new Table('bar'); - $table->addColumn('baz', 'integer', []); - $table->addUniqueIndex(['baz'], 'idx_unique'); - - $table->setPrimaryKey(['baz']); - - $indexes = $table->getIndexes(); - self::assertCount(2, $indexes, 'Table should only contain both the primary key table index and the unique one, even though it was overruled.'); - - self::assertTrue($table->hasPrimaryKey()); - self::assertTrue($table->hasIndex('idx_unique')); - } - - public function testAddingFulfillingRegularIndexOverridesImplicitForeignKeyConstraintIndex() : void - { - $foreignTable = new Table('foreign'); - $foreignTable->addColumn('id', 'integer'); - - $localTable = new Table('local'); - $localTable->addColumn('id', 'integer'); - $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']); - - self::assertCount(1, $localTable->getIndexes()); - - $localTable->addIndex(['id'], 'explicit_idx'); - - self::assertCount(1, $localTable->getIndexes()); - self::assertTrue($localTable->hasIndex('explicit_idx')); - } - - public function testAddingFulfillingUniqueIndexOverridesImplicitForeignKeyConstraintIndex() : void - { - $foreignTable = new Table('foreign'); - $foreignTable->addColumn('id', 'integer'); - - $localTable = new Table('local'); - $localTable->addColumn('id', 'integer'); - $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']); - - self::assertCount(1, $localTable->getIndexes()); - - $localTable->addUniqueIndex(['id'], 'explicit_idx'); - - self::assertCount(1, $localTable->getIndexes()); - self::assertTrue($localTable->hasIndex('explicit_idx')); - } - - public function testAddingFulfillingPrimaryKeyOverridesImplicitForeignKeyConstraintIndex() : void - { - $foreignTable = new Table('foreign'); - $foreignTable->addColumn('id', 'integer'); - - $localTable = new Table('local'); - $localTable->addColumn('id', 'integer'); - $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']); - - self::assertCount(1, $localTable->getIndexes()); - - $localTable->setPrimaryKey(['id'], 'explicit_idx'); - - self::assertCount(1, $localTable->getIndexes()); - self::assertTrue($localTable->hasIndex('explicit_idx')); - } - - public function testAddingFulfillingExplicitIndexOverridingImplicitForeignKeyConstraintIndexWithSameNameDoesNotThrowException() : void - { - $foreignTable = new Table('foreign'); - $foreignTable->addColumn('id', 'integer'); - - $localTable = new Table('local'); - $localTable->addColumn('id', 'integer'); - $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']); - - self::assertCount(1, $localTable->getIndexes()); - self::assertTrue($localTable->hasIndex('IDX_8BD688E8BF396750')); - - $implicitIndex = $localTable->getIndex('IDX_8BD688E8BF396750'); - - $localTable->addIndex(['id'], 'IDX_8BD688E8BF396750'); - - self::assertCount(1, $localTable->getIndexes()); - self::assertTrue($localTable->hasIndex('IDX_8BD688E8BF396750')); - self::assertNotSame($implicitIndex, $localTable->getIndex('IDX_8BD688E8BF396750')); - } - - /** - * @group DBAL-64 - */ - public function testQuotedTableName() : void - { - $table = new Table('`bar`'); - - $mysqlPlatform = new MySqlPlatform(); - $sqlitePlatform = new SqlitePlatform(); - - self::assertEquals('bar', $table->getName()); - self::assertEquals('`bar`', $table->getQuotedName($mysqlPlatform)); - self::assertEquals('"bar"', $table->getQuotedName($sqlitePlatform)); - } - - /** - * @group DBAL-79 - */ - public function testTableHasPrimaryKey() : void - { - $table = new Table('test'); - - self::assertFalse($table->hasPrimaryKey()); - - $table->addColumn('foo', 'integer'); - $table->setPrimaryKey(['foo']); - - self::assertTrue($table->hasPrimaryKey()); - } - - /** - * @group DBAL-91 - */ - public function testAddIndexWithQuotedColumns() : void - { - $table = new Table('test'); - $table->addColumn('"foo"', 'integer'); - $table->addColumn('bar', 'integer'); - $table->addIndex(['"foo"', '"bar"']); - - self::assertTrue($table->columnsAreIndexed(['"foo"', '"bar"'])); - } - - /** - * @group DBAL-91 - */ - public function testAddForeignKeyWithQuotedColumnsAndTable() : void - { - $table = new Table('test'); - $table->addColumn('"foo"', 'integer'); - $table->addColumn('bar', 'integer'); - $table->addForeignKeyConstraint('"boing"', ['"foo"', '"bar"'], ['id']); - - self::assertCount(1, $table->getForeignKeys()); - } - - /** - * @group DBAL-177 - */ - public function testQuoteSchemaPrefixed() : void - { - $table = new Table('`test`.`test`'); - self::assertEquals('test.test', $table->getName()); - self::assertEquals('`test`.`test`', $table->getQuotedName(new MySqlPlatform())); - } - - /** - * @group DBAL-204 - */ - public function testFullQualifiedTableName() : void - { - $table = new Table('`test`.`test`'); - self::assertEquals('test.test', $table->getFullQualifiedName('test')); - self::assertEquals('test.test', $table->getFullQualifiedName('other')); - - $table = new Table('test'); - self::assertEquals('test.test', $table->getFullQualifiedName('test')); - self::assertEquals('other.test', $table->getFullQualifiedName('other')); - } - - /** - * @group DBAL-224 - */ - public function testDropIndex() : void - { - $table = new Table('test'); - $table->addColumn('id', 'integer'); - $table->addIndex(['id'], 'idx'); - - self::assertTrue($table->hasIndex('idx')); - - $table->dropIndex('idx'); - self::assertFalse($table->hasIndex('idx')); - } - - /** - * @group DBAL-224 - */ - public function testDropPrimaryKey() : void - { - $table = new Table('test'); - $table->addColumn('id', 'integer'); - $table->setPrimaryKey(['id']); - - self::assertTrue($table->hasPrimaryKey()); - - $table->dropPrimaryKey(); - self::assertFalse($table->hasPrimaryKey()); - } - - /** - * @group DBAL-234 - */ - public function testRenameIndex() : void - { - $table = new Table('test'); - $table->addColumn('id', 'integer'); - $table->addColumn('foo', 'integer'); - $table->addColumn('bar', 'integer'); - $table->addColumn('baz', 'integer'); - $table->setPrimaryKey(['id'], 'pk'); - $table->addIndex(['foo'], 'idx', ['flag']); - $table->addUniqueIndex(['bar', 'baz'], 'uniq'); - - // Rename to custom name. - self::assertSame($table, $table->renameIndex('pk', 'pk_new')); - self::assertSame($table, $table->renameIndex('idx', 'idx_new')); - self::assertSame($table, $table->renameIndex('uniq', 'uniq_new')); - - self::assertTrue($table->hasPrimaryKey()); - self::assertTrue($table->hasIndex('pk_new')); - self::assertTrue($table->hasIndex('idx_new')); - self::assertTrue($table->hasIndex('uniq_new')); - - self::assertFalse($table->hasIndex('pk')); - self::assertFalse($table->hasIndex('idx')); - self::assertFalse($table->hasIndex('uniq')); - - self::assertEquals(new Index('pk_new', ['id'], true, true), $table->getPrimaryKey()); - self::assertEquals(new Index('pk_new', ['id'], true, true), $table->getIndex('pk_new')); - self::assertEquals( - new Index('idx_new', ['foo'], false, false, ['flag']), - $table->getIndex('idx_new') - ); - self::assertEquals(new Index('uniq_new', ['bar', 'baz'], true), $table->getIndex('uniq_new')); - - // Rename to auto-generated name. - self::assertSame($table, $table->renameIndex('pk_new', null)); - self::assertSame($table, $table->renameIndex('idx_new', null)); - self::assertSame($table, $table->renameIndex('uniq_new', null)); - - self::assertTrue($table->hasPrimaryKey()); - self::assertTrue($table->hasIndex('primary')); - self::assertTrue($table->hasIndex('IDX_D87F7E0C8C736521')); - self::assertTrue($table->hasIndex('UNIQ_D87F7E0C76FF8CAA78240498')); - - self::assertFalse($table->hasIndex('pk_new')); - self::assertFalse($table->hasIndex('idx_new')); - self::assertFalse($table->hasIndex('uniq_new')); - - self::assertEquals(new Index('primary', ['id'], true, true), $table->getPrimaryKey()); - self::assertEquals(new Index('primary', ['id'], true, true), $table->getIndex('primary')); - self::assertEquals( - new Index('IDX_D87F7E0C8C736521', ['foo'], false, false, ['flag']), - $table->getIndex('IDX_D87F7E0C8C736521') - ); - self::assertEquals( - new Index('UNIQ_D87F7E0C76FF8CAA78240498', ['bar', 'baz'], true), - $table->getIndex('UNIQ_D87F7E0C76FF8CAA78240498') - ); - - // Rename to same name (changed case). - self::assertSame($table, $table->renameIndex('primary', 'PRIMARY')); - self::assertSame($table, $table->renameIndex('IDX_D87F7E0C8C736521', 'idx_D87F7E0C8C736521')); - self::assertSame($table, $table->renameIndex('UNIQ_D87F7E0C76FF8CAA78240498', 'uniq_D87F7E0C76FF8CAA78240498')); - - self::assertTrue($table->hasPrimaryKey()); - self::assertTrue($table->hasIndex('primary')); - self::assertTrue($table->hasIndex('IDX_D87F7E0C8C736521')); - self::assertTrue($table->hasIndex('UNIQ_D87F7E0C76FF8CAA78240498')); - } - - /** - * @group DBAL-2508 - */ - public function testKeepsIndexOptionsOnRenamingRegularIndex() : void - { - $table = new Table('foo'); - $table->addColumn('id', 'integer'); - $table->addIndex(['id'], 'idx_bar', [], ['where' => '1 = 1']); - - $table->renameIndex('idx_bar', 'idx_baz'); - - self::assertSame(['where' => '1 = 1'], $table->getIndex('idx_baz')->getOptions()); - } - - /** - * @group DBAL-2508 - */ - public function testKeepsIndexOptionsOnRenamingUniqueIndex() : void - { - $table = new Table('foo'); - $table->addColumn('id', 'integer'); - $table->addUniqueIndex(['id'], 'idx_bar', ['where' => '1 = 1']); - - $table->renameIndex('idx_bar', 'idx_baz'); - - self::assertSame(['where' => '1 = 1'], $table->getIndex('idx_baz')->getOptions()); - } - - /** - * @group DBAL-234 - */ - public function testThrowsExceptionOnRenamingNonExistingIndex() : void - { - $table = new Table('test'); - $table->addColumn('id', 'integer'); - $table->addIndex(['id'], 'idx'); - - $this->expectException(SchemaException::class); - - $table->renameIndex('foo', 'bar'); - } - - /** - * @group DBAL-234 - */ - public function testThrowsExceptionOnRenamingToAlreadyExistingIndex() : void - { - $table = new Table('test'); - $table->addColumn('id', 'integer'); - $table->addColumn('foo', 'integer'); - $table->addIndex(['id'], 'idx_id'); - $table->addIndex(['foo'], 'idx_foo'); - - $this->expectException(SchemaException::class); - - $table->renameIndex('idx_id', 'idx_foo'); - } - - /** - * @dataProvider getNormalizesAssetNames - * @group DBAL-831 - */ - public function testNormalizesColumnNames(string $assetName) : void - { - $table = new Table('test'); - - $table->addColumn($assetName, 'integer'); - $table->addIndex([$assetName], $assetName); - $table->addForeignKeyConstraint('test', [$assetName], [$assetName], [], $assetName); - - self::assertTrue($table->hasColumn($assetName)); - self::assertTrue($table->hasColumn('foo')); - self::assertInstanceOf(Column::class, $table->getColumn($assetName)); - self::assertInstanceOf(Column::class, $table->getColumn('foo')); - - self::assertTrue($table->hasIndex($assetName)); - self::assertTrue($table->hasIndex('foo')); - self::assertInstanceOf(Index::class, $table->getIndex($assetName)); - self::assertInstanceOf(Index::class, $table->getIndex('foo')); - - self::assertTrue($table->hasForeignKey($assetName)); - self::assertTrue($table->hasForeignKey('foo')); - self::assertInstanceOf(ForeignKeyConstraint::class, $table->getForeignKey($assetName)); - self::assertInstanceOf(ForeignKeyConstraint::class, $table->getForeignKey('foo')); - - $table->renameIndex($assetName, $assetName); - self::assertTrue($table->hasIndex($assetName)); - self::assertTrue($table->hasIndex('foo')); - - $table->renameIndex($assetName, 'foo'); - self::assertTrue($table->hasIndex($assetName)); - self::assertTrue($table->hasIndex('foo')); - - $table->renameIndex('foo', $assetName); - self::assertTrue($table->hasIndex($assetName)); - self::assertTrue($table->hasIndex('foo')); - - $table->renameIndex($assetName, 'bar'); - self::assertFalse($table->hasIndex($assetName)); - self::assertFalse($table->hasIndex('foo')); - self::assertTrue($table->hasIndex('bar')); - - $table->renameIndex('bar', $assetName); - - $table->dropColumn($assetName); - $table->dropIndex($assetName); - $table->removeForeignKey($assetName); - - self::assertFalse($table->hasColumn($assetName)); - self::assertFalse($table->hasColumn('foo')); - self::assertFalse($table->hasIndex($assetName)); - self::assertFalse($table->hasIndex('foo')); - self::assertFalse($table->hasForeignKey($assetName)); - self::assertFalse($table->hasForeignKey('foo')); - } - - /** - * @return mixed[][] - */ - public static function getNormalizesAssetNames() : iterable - { - return [ - ['foo'], - ['FOO'], - ['`foo`'], - ['`FOO`'], - ['"foo"'], - ['"FOO"'], - ['"foo"'], - ['"FOO"'], - ]; - } -} diff --git a/tests/Unit/DBAL/StatementTest.php b/tests/Unit/DBAL/StatementTest.php deleted file mode 100644 index 1186103..0000000 --- a/tests/Unit/DBAL/StatementTest.php +++ /dev/null @@ -1,153 +0,0 @@ -pdoStatement = $this->getMockBuilder(PDOStatement::class) - ->setMethods(['execute', 'bindParam', 'bindValue']) - ->getMock(); - - $driverConnection = $this->createMock(DriverConnection::class); - $driverConnection->expects($this->any()) - ->method('prepare') - ->will($this->returnValue($this->pdoStatement)); - - $driver = $this->createMock(Driver::class); - - $this->conn = $this->getMockBuilder(Connection::class) - ->setConstructorArgs([[], $driver]) - ->getMock(); - $this->conn->expects($this->atLeastOnce()) - ->method('getWrappedConnection') - ->will($this->returnValue($driverConnection)); - - $this->configuration = $this->createMock(Configuration::class); - $this->conn->expects($this->any()) - ->method('getConfiguration') - ->will($this->returnValue($this->configuration)); - - $this->conn->expects($this->any()) - ->method('getDriver') - ->will($this->returnValue($driver)); - } - - public function testExecuteCallsLoggerStartQueryWithParametersWhenValuesBound() : void - { - $name = 'foo'; - $var = 'bar'; - $type = ParameterType::STRING; - $values = [$name => $var]; - $types = [$name => $type]; - $sql = ''; - - $logger = $this->createMock(SQLLogger::class); - $logger->expects($this->once()) - ->method('startQuery') - ->with($this->equalTo($sql), $this->equalTo($values), $this->equalTo($types)); - - $this->configuration->expects($this->once()) - ->method('getSQLLogger') - ->will($this->returnValue($logger)); - - $statement = new Statement($sql, $this->conn); - $statement->bindValue($name, $var, $type); - $statement->execute(); - } - - public function testExecuteCallsLoggerStartQueryWithParametersWhenParamsPassedToExecute() : void - { - $name = 'foo'; - $var = 'bar'; - $values = [$name => $var]; - $types = []; - $sql = ''; - - $logger = $this->createMock(SQLLogger::class); - $logger->expects($this->once()) - ->method('startQuery') - ->with($this->equalTo($sql), $this->equalTo($values), $this->equalTo($types)); - - $this->configuration->expects($this->once()) - ->method('getSQLLogger') - ->will($this->returnValue($logger)); - - $statement = new Statement($sql, $this->conn); - $statement->execute($values); - } - - public function testExecuteCallsStartQueryWithTheParametersBoundViaBindParam() : void - { - $name = 'foo'; - $var = 'bar'; - $values = [$name => $var]; - $types = [$name => ParameterType::STRING]; - $sql = ''; - - $logger = $this->createMock(SQLLogger::class); - $logger->expects(self::once()) - ->method('startQuery') - ->with(self::equalTo($sql), self::equalTo($values), self::equalTo($types)); - - $this->configuration->expects(self::once()) - ->method('getSQLLogger') - ->willReturn($logger); - - $statement = new Statement($sql, $this->conn); - $statement->bindParam($name, $var); - $statement->execute(); - } - - public function testExecuteCallsLoggerStopQueryOnException() : void - { - $logger = $this->createMock(SQLLogger::class); - - $this->configuration->expects($this->once()) - ->method('getSQLLogger') - ->will($this->returnValue($logger)); - - // Needed to satisfy construction of DBALException - $this->conn->expects($this->any()) - ->method('resolveParams') - ->will($this->returnValue([])); - - $logger->expects($this->once()) - ->method('startQuery'); - - $logger->expects($this->once()) - ->method('stopQuery'); - - $this->pdoStatement->expects($this->once()) - ->method('execute') - ->will($this->throwException(new Exception('Mock test exception'))); - - $statement = new Statement('', $this->conn); - - $this->expectException(DBALException::class); - - $statement->execute(); - } -} diff --git a/tests/Unit/DBAL/UtilTest.php b/tests/Unit/DBAL/UtilTest.php deleted file mode 100644 index 393c4e7..0000000 --- a/tests/Unit/DBAL/UtilTest.php +++ /dev/null @@ -1,81 +0,0 @@ - ':param1'], - ], - [ - 'SELECT name FROM users WHERE id = ? AND status = ?', - 'SELECT name FROM users WHERE id = :param1 AND status = :param2', - [1 => ':param1', 2 => ':param2'], - ], - [ - "UPDATE users SET name = '???', status = ?", - "UPDATE users SET name = '???', status = :param1", - [1 => ':param1'], - ], - [ - "UPDATE users SET status = ?, name = '???'", - "UPDATE users SET status = :param1, name = '???'", - [1 => ':param1'], - ], - [ - "UPDATE users SET foo = ?, name = '???', status = ?", - "UPDATE users SET foo = :param1, name = '???', status = :param2", - [1 => ':param1', 2 => ':param2'], - ], - [ - 'UPDATE users SET name = "???", status = ?', - 'UPDATE users SET name = "???", status = :param1', - [1 => ':param1'], - ], - [ - 'UPDATE users SET status = ?, name = "???"', - 'UPDATE users SET status = :param1, name = "???"', - [1 => ':param1'], - ], - [ - 'UPDATE users SET foo = ?, name = "???", status = ?', - 'UPDATE users SET foo = :param1, name = "???", status = :param2', - [1 => ':param1', 2 => ':param2'], - ], - [ - 'SELECT * FROM users WHERE id = ? AND name = "" AND status = ?', - 'SELECT * FROM users WHERE id = :param1 AND name = "" AND status = :param2', - [1 => ':param1', 2 => ':param2'], - ], - [ - "SELECT * FROM users WHERE id = ? AND name = '' AND status = ?", - "SELECT * FROM users WHERE id = :param1 AND name = '' AND status = :param2", - [1 => ':param1', 2 => ':param2'], - ], - ]; - } - - /** - * @param mixed[] $expectedOutputParamsMap - * - * @dataProvider dataConvertPositionalToNamedParameters - */ - public function testConvertPositionalToNamedParameters(string $inputSQL, string $expectedOutputSQL, array $expectedOutputParamsMap) : void - { - [$statement, $params] = OCI8Statement::convertPositionalToNamedPlaceholders($inputSQL); - - self::assertEquals($expectedOutputSQL, $statement); - self::assertEquals($expectedOutputParamsMap, $params); - } -} diff --git a/tests/Unit/Types/CommentedType.php b/tests/Unit/Types/CommentedType.php deleted file mode 100644 index 7b63a24..0000000 --- a/tests/Unit/Types/CommentedType.php +++ /dev/null @@ -1,34 +0,0 @@ -getName()); - } - - /** - * {@inheritDoc} - */ - public function requiresSQLCommentHint(AbstractPlatform $platform) - { - return true; - } -} diff --git a/tests/Unit/Types/MySqlPointType.php b/tests/Unit/Types/MySqlPointType.php deleted file mode 100644 index 4ee897a..0000000 --- a/tests/Unit/Types/MySqlPointType.php +++ /dev/null @@ -1,34 +0,0 @@ -getName()); - } - - /** - * {@inheritDoc} - */ - public function getMappedDatabaseTypes(AbstractPlatform $platform) - { - return ['point']; - } -} From c205e19fa62f34e83fb5c684b06332e207bb2c07 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Thu, 18 Jul 2019 23:38:09 +0300 Subject: [PATCH 07/38] some fixes --- tests/Functional/TestUtil.php | 34 ++---------------------- tests/travis/pgsql.travis.xml | 49 ----------------------------------- 2 files changed, 2 insertions(+), 81 deletions(-) delete mode 100644 tests/travis/pgsql.travis.xml diff --git a/tests/Functional/TestUtil.php b/tests/Functional/TestUtil.php index dc1aaa3..49ca2e2 100644 --- a/tests/Functional/TestUtil.php +++ b/tests/Functional/TestUtil.php @@ -8,37 +8,11 @@ use Doctrine\DBAL\DriverManager; use function explode; use Illuminate\Support\Facades\DB; -use Umbrellio\Postgres\PostgresConnection; -/** - * TestUtil is a class with static utility methods used during tests. - */ class TestUtil { - /** @var bool Whether the database schema is initialized. */ private static $initialized = false; - /** - * Gets a real database connection using the following parameters - * of the $GLOBALS array: - * - * 'db_type' : The name of the Doctrine DBAL database driver to use. - * 'db_username' : The username to use for connecting. - * 'db_password' : The password to use for connecting. - * 'db_host' : The hostname of the database to connect to. - * 'db_server' : The server name of the database to connect to - * (optional, some vendors allow multiple server instances with different names on the same host). - * 'db_name' : The name of the database to connect to. - * 'db_port' : The port of the database to connect to. - * - * Usually these variables of the $GLOBALS array are filled by PHPUnit based - * on an XML configuration file. If no such parameters exist, an SQLite - * in-memory database is used. - * - * IMPORTANT: Each invocation of this method returns a NEW database connection. - * - * @return Connection The database connection instance. - */ public static function getConnection(): Connection { if (self::hasRequiredConnectionParams() && !self::$initialized) { @@ -60,8 +34,9 @@ public static function getTempConnection(): Connection public static function getParamsForMainConnection(): array { + print_r($GLOBALS); $connectionParams = [ - 'driver' => $GLOBALS['db_type'], + 'driver' => $GLOBALS['db_type'] ?? 'pdo_pgsql', 'user' => $GLOBALS['db_username'], 'password' => $GLOBALS['db_password'], 'host' => $GLOBALS['db_host'], @@ -80,9 +55,6 @@ public static function getParamsForMainConnection(): array return $connectionParams; } - /** - * @return mixed[] - */ public static function getParamsForTemporaryConnection(): array { $connectionParams = [ @@ -143,7 +115,6 @@ private static function initializeDatabase(): void $tmpConn->close(); } else { - //UPDATE pg_database SET datallowconn = true WHERE datname = 'my_database'; $sm = $realConn->getSchemaManager(); $schema = $sm->createSchema(); @@ -170,7 +141,6 @@ private static function addDbEventSubscribers(Connection $conn): void private static function getDoctrineConnection(string $name = null): Connection { - /** @var PostgresConnection $connection */ $connection = DB::connection($name); return $connection->getDoctrineConnection(); } diff --git a/tests/travis/pgsql.travis.xml b/tests/travis/pgsql.travis.xml deleted file mode 100644 index c328cd3..0000000 --- a/tests/travis/pgsql.travis.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - ../Doctrine/Tests/DBAL - - - - - - ../../lib/Doctrine - - - - - - - - - - performance - locking_functional - - - From 45e985592abba78ef3d072adcc41900119934003 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Fri, 19 Jul 2019 08:37:34 +0300 Subject: [PATCH 08/38] fix travis postgresql --- .travis.yml | 7 ++----- phpunit.travis.xml | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index b95591e..db5d9e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,16 +18,13 @@ install: - rm composer.lock - travis_retry composer -n update --prefer-dist -before_script: - - cp phpunit.travis.xml phpunit.xml - script: - vendor/bin/ecs check --config=ecs.yml . - | if [ "x$COVERAGE" == "xyes" ]; then - php -d pcov.directory='.' vendor/bin/phpunit --coverage-clover build/logs/clover.xml + ./vendor/bin/phpunit --configuration phpunit.travis.xml --coverage-clover build/logs/clover.xml else - ./vendor/bin/phpunit + ./vendor/bin/phpunit --configuration phpunit.travis.xml fi after_success: diff --git a/phpunit.travis.xml b/phpunit.travis.xml index 3f62946..07f4297 100644 --- a/phpunit.travis.xml +++ b/phpunit.travis.xml @@ -1,22 +1,11 @@ - - - - ./src - - ./src/.meta.php - ./src/Commands - - - @@ -36,4 +25,13 @@ ./tests + + + ./src + + ./src/.meta.php + ./src/Commands + + + From d75ba7e322c2c2cbf6043ec70dc3f347acd6e6fd Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Fri, 19 Jul 2019 08:49:20 +0300 Subject: [PATCH 09/38] fix phpunit --- tests/Functional/TestUtil.php | 8 ++++++-- tests/TestCase.php | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/Functional/TestUtil.php b/tests/Functional/TestUtil.php index 49ca2e2..9c4a315 100644 --- a/tests/Functional/TestUtil.php +++ b/tests/Functional/TestUtil.php @@ -13,12 +13,17 @@ class TestUtil { private static $initialized = false; - public static function getConnection(): Connection + public static function createDatabase() { if (self::hasRequiredConnectionParams() && !self::$initialized) { self::initializeDatabase(); self::$initialized = true; } + } + + public static function getConnection(): Connection + { + static::createDatabase(); $conn = static::getDoctrineConnection(); @@ -34,7 +39,6 @@ public static function getTempConnection(): Connection public static function getParamsForMainConnection(): array { - print_r($GLOBALS); $connectionParams = [ 'driver' => $GLOBALS['db_type'] ?? 'pdo_pgsql', 'user' => $GLOBALS['db_username'], diff --git a/tests/TestCase.php b/tests/TestCase.php index c126697..943c88f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,12 +6,15 @@ use Illuminate\Support\Facades\Facade; use Orchestra\Testbench\TestCase as BaseTestCase; +use Umbrellio\Postgres\Tests\Functional\TestUtil; use Umbrellio\Postgres\UmbrellioPostgresProvider; abstract class TestCase extends BaseTestCase { protected function setUp(): void { + TestUtil::createDatabase(); + if (!$this->app) { putenv('APP_ENV=testing'); $this->app = $this->createApplication(); From ae004de8616a4e29fd21c94930fe44f2448e06d6 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Fri, 19 Jul 2019 14:36:27 +0300 Subject: [PATCH 10/38] remove testst --- composer.json | 3 +- composer.lock | 77 ++------------------------ tests/Functional/Schema/ChangeTest.php | 76 ------------------------- 3 files changed, 7 insertions(+), 149 deletions(-) delete mode 100644 tests/Functional/Schema/ChangeTest.php diff --git a/composer.json b/composer.json index 4dc3726..49ee59b 100644 --- a/composer.json +++ b/composer.json @@ -17,8 +17,7 @@ "require-dev": { "umbrellio/code-style-php": "^1.0", "orchestra/testbench": "^3.5", - "php-coveralls/php-coveralls": "^2.1", - "symfony/phpunit-bridge": "^3.4.5|^4.0.5|^5.0" + "php-coveralls/php-coveralls": "^2.1" }, "scripts": { "lint": [ diff --git a/composer.lock b/composer.lock index 602dabe..4d6cc35 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "74d899a67a8901f76c05815efc6ef67c", + "content-hash": "a9b56c70136605e96d824acfe6f3ab54", "packages": [ { "name": "doctrine/cache", @@ -850,16 +850,16 @@ }, { "name": "nesbot/carbon", - "version": "2.21.2", + "version": "2.21.3", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "a8bfb6631e3bc982427406d13ad817fc45d47b61" + "reference": "58bdbbfab17ccd2ec7347b99e997f18232def4dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/a8bfb6631e3bc982427406d13ad817fc45d47b61", - "reference": "a8bfb6631e3bc982427406d13ad817fc45d47b61", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/58bdbbfab17ccd2ec7347b99e997f18232def4dc", + "reference": "58bdbbfab17ccd2ec7347b99e997f18232def4dc", "shasum": "" }, "require": { @@ -913,7 +913,7 @@ "datetime", "time" ], - "time": "2019-07-17T07:49:05+00:00" + "time": "2019-07-18T18:47:28+00:00" }, { "name": "opis/closure", @@ -6011,71 +6011,6 @@ ], "time": "2019-06-13T11:01:17+00:00" }, - { - "name": "symfony/phpunit-bridge", - "version": "v4.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "573b5c4a36a171b94cf031d8dc6cc568082190f1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/573b5c4a36a171b94cf031d8dc6cc568082190f1", - "reference": "573b5c4a36a171b94cf031d8dc6cc568082190f1", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" - }, - "suggest": { - "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" - }, - "bin": [ - "bin/simple-phpunit" - ], - "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - }, - "thanks": { - "name": "phpunit/phpunit", - "url": "https://github.com/sebastianbergmann/phpunit" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Bridge\\PhpUnit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony PHPUnit Bridge", - "homepage": "https://symfony.com", - "time": "2019-06-26T12:14:14+00:00" - }, { "name": "symfony/polyfill-php70", "version": "v1.11.0", diff --git a/tests/Functional/Schema/ChangeTest.php b/tests/Functional/Schema/ChangeTest.php deleted file mode 100644 index 182238d..0000000 --- a/tests/Functional/Schema/ChangeTest.php +++ /dev/null @@ -1,76 +0,0 @@ -integer('id')->default(1); - $table->string('name')->nullable()->default(new Expression('NULL')); - $table->binary('binaries'); - $table->string('email')->unique(); - $table->tinyInteger('phone')->unsigned(); - $table->boolean('enabled')->default(0)->comment('Enabled'); - }); - - $this->assertTrue(Schema::hasTable('test_table')); - $this->assertSame( - ['id', 'name', 'binaries', 'email', 'phone', 'enabled'], - Schema::getColumnListing('test_table') - ); - - Schema::table('test_table', function (Blueprint $table) { - $table->integer('id')->primary()->change(); - $table->string('email')->nullable()->default(new Expression('NULL'))->change(); - $table->string('binaries')->nullable()->change(); - $table->string('test1'); - $table->string('test2')->comment('test_comment'); - $table->string('dump1')->nullable(); - $table->dropColumn(['dump1']); - }); - - $this->assertSame( - ['id', 'name', 'binaries', 'email', 'phone', 'enabled', 'test1', 'test2'], - Schema::getColumnListing('test_table') - ); - - Schema::table('test_table', function (Blueprint $table) { - $table->integer('id')->autoIncrement()->change(); - $table->string('email')->nullable()->default('player@example.om')->change(); - $table->renameColumn('binaries', 'code'); - $table->dropColumn(['phone', 'enabled']); - $table->binary('test1')->nullable()->using('test1::bytea')->change(); - $table->string('test2')->comment('new_comment')->change(); - }); - - $this->assertSame(['id', 'name', 'code', 'email', 'test1', 'test2'], Schema::getColumnListing('test_table')); - - Schema::table('test_table', function (Blueprint $table) { - $table->dropPrimary(['id']); - $table->string('name', 100)->change(); - $table->integer('id')->change(); - $table->rename('some_table'); - }); - - $this->assertTrue(Schema::hasTable('some_table')); - - Schema::table('some_table', function (Blueprint $table) { - $table->drop(); - }); - - $this->assertFalse(Schema::hasTable('some_table')); - } -} From dd3e083c10032651dd3350d4523d9841ee329641 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Fri, 19 Jul 2019 16:02:23 +0300 Subject: [PATCH 11/38] use database transactions use matrix in travis --- .travis.yml | 11 ++++--- tests/Functional/Schema/HasIndexTest.php | 4 +-- tests/Functional/Schema/SchemaTest.php | 4 +-- tests/Functional/Schema/UniqueIndexTest.php | 36 ++++++++++----------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index db5d9e7..3e1c1c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,12 +28,15 @@ script: fi after_success: - - travis_retry vendor/bin/php-coveralls -v + - | + if [ "x$COVERAGE" == "xyes" ]; then + travis_retry vendor/bin/php-coveralls -v + fi -jobs: +matrix: + fast_finish: true allow_failures: - php: "7.4snapshot" - include: - stage: Test php: "7.2" @@ -98,7 +101,7 @@ jobs: - bash ./tests/travis/install-postgres-11.sh - stage: Test php: "7.4snapshot" - env: DB=pgsql POSTGRESQL_VERSION=11.0 + env: DB=pgsql POSTGRESQL_VERSION=11.0 COVERAGE=yes sudo: required services: - docker diff --git a/tests/Functional/Schema/HasIndexTest.php b/tests/Functional/Schema/HasIndexTest.php index 4e4851d..e5ecc5c 100644 --- a/tests/Functional/Schema/HasIndexTest.php +++ b/tests/Functional/Schema/HasIndexTest.php @@ -4,7 +4,7 @@ namespace Umbrellio\Postgres\Tests\Functional\Schema; -use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; @@ -12,7 +12,7 @@ class HasIndexTest extends FunctionalTestCase { - use RefreshDatabase; + use DatabaseTransactions; /** @test */ public function createIndexIfNotExists(): void diff --git a/tests/Functional/Schema/SchemaTest.php b/tests/Functional/Schema/SchemaTest.php index 334e1de..0c76ccf 100644 --- a/tests/Functional/Schema/SchemaTest.php +++ b/tests/Functional/Schema/SchemaTest.php @@ -4,14 +4,14 @@ namespace Umbrellio\Postgres\Tests\Functional\Schema; -use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; use Umbrellio\Postgres\Tests\FunctionalTestCase; class SchemaTest extends FunctionalTestCase { - use RefreshDatabase; + use DatabaseTransactions; /** @test */ public function create(): void diff --git a/tests/Functional/Schema/UniqueIndexTest.php b/tests/Functional/Schema/UniqueIndexTest.php index b70220a..7d7a47f 100644 --- a/tests/Functional/Schema/UniqueIndexTest.php +++ b/tests/Functional/Schema/UniqueIndexTest.php @@ -5,7 +5,7 @@ namespace Umbrellio\Postgres\Tests\Functional\Schema; use Generator; -use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; @@ -13,7 +13,7 @@ class UniqueIndexTest extends FunctionalTestCase { - use RefreshDatabase; + use DatabaseTransactions; /** * @test @@ -37,7 +37,7 @@ public function createPartialUniqueWithNull($expected, $callback): void $indexes = $this->getIndexByName('test_table_name_unique'); $this->assertTrue(isset($indexes->indexdef)); - $this->assertSame($this->getDummyIndex() . $expected, $indexes->indexdef); + $this->assertRegExp('/' . $this->getDummyIndex() . $expected . '/', $indexes->indexdef); } /** @test */ @@ -49,8 +49,8 @@ public function createSpecifyIndex(): void $this->assertTrue(Schema::hasTable('test_table')); - $this->assertSame( - 'CREATE INDEX specify_index_name ON public.test_table USING btree (name)', + $this->assertRegExp( + '/CREATE INDEX specify_index_name ON (public.)?test_table USING btree \(name\)/', $this->getIndexByName('specify_index_name')->indexdef ); } @@ -61,73 +61,73 @@ public function provideIndexes(): Generator $table->uniquePartial('name'); }]; yield [ - ' WHERE (deleted_at IS NULL)', + ' WHERE \(deleted_at IS NULL\)', function (Blueprint $table) { $table->uniquePartial('name')->whereNull('deleted_at'); }, ]; yield [ - ' WHERE (deleted_at IS NOT NULL)', + ' WHERE \(deleted_at IS NOT NULL\)', function (Blueprint $table) { $table->uniquePartial('name')->whereNotNull('deleted_at'); }, ]; yield [ - ' WHERE (phone = 1234)', + ' WHERE \(phone = 1234\)', function (Blueprint $table) { $table->uniquePartial('name')->where('phone', '=', 1234); }, ]; yield [ - " WHERE ((code)::text = 'test'::text)", + " WHERE \(\(code\)::text = 'test'::text\)", function (Blueprint $table) { $table->uniquePartial('name')->where('code', '=', 'test'); }, ]; yield [ - ' WHERE ((phone >= 1) AND (phone <= 2))', + ' WHERE \(\(phone >= 1\) AND \(phone <= 2\)\)', function (Blueprint $table) { $table->uniquePartial('name')->whereBetween('phone', [1, 2]); }, ]; yield [ - ' WHERE ((phone < 1) OR (phone > 2))', + ' WHERE \(\(phone < 1\) OR \(phone > 2\)\)', function (Blueprint $table) { $table->uniquePartial('name')->whereNotBetween('phone', [1, 2]); }, ]; yield [ - ' WHERE (phone <> icq)', + ' WHERE \(phone <> icq\)', function (Blueprint $table) { $table->uniquePartial('name')->whereColumn('phone', '<>', 'icq'); }, ]; yield [ - ' WHERE ((phone = 1) AND (icq < 2))', + ' WHERE \(\(phone = 1\) AND \(icq < 2\)\)', function (Blueprint $table) { $table->uniquePartial('name')->whereRaw('phone = ? and icq < ?', [1, 2]); }, ]; yield [ - ' WHERE (phone = ANY (ARRAY[1, 2, 4]))', + ' WHERE \(phone = ANY \(ARRAY\[1, 2, 4\]\)\)', function (Blueprint $table) { $table->uniquePartial('name')->whereIn('phone', [1, 2, 4]); }, ]; yield [ - ' WHERE (0 = 1)', + ' WHERE \(0 = 1\)', function (Blueprint $table) { $table->uniquePartial('name')->whereIn('phone', []); }, ]; yield [ - ' WHERE (phone <> ALL (ARRAY[1, 2, 4]))', + ' WHERE \(phone <> ALL \(ARRAY\[1, 2, 4\]\)\)', function (Blueprint $table) { $table->uniquePartial('name')->whereNotIn('phone', [1, 2, 4]); }, ]; yield [ - ' WHERE (1 = 1)', + ' WHERE \(1 = 1\)', function (Blueprint $table) { $table->uniquePartial('name')->whereNotIn('phone', []); }, @@ -136,7 +136,7 @@ function (Blueprint $table) { protected function getDummyIndex() { - return 'CREATE UNIQUE INDEX test_table_name_unique ON public.test_table USING btree (name)'; + return 'CREATE UNIQUE INDEX test_table_name_unique ON (public.)?test_table USING btree \(name\)'; } protected function getIndexByName($name) From 1f46d22ef59519ae9222cacf249111cf0f1c30c7 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Fri, 19 Jul 2019 17:29:27 +0300 Subject: [PATCH 12/38] remove comments add abstract components for extensions --- .travis.yml | 2 +- .../Schema/AbstractSQLSchemaManager.php | 13 +++++++++++++ .../Schema/Drivers/AbstractSQLPlatform.php | 13 +++++++++++++ .../Platforms/AbstractSQL100Platform.php | 13 +++++++++++++ .../Platforms/AbstractSQL91Platform.php | 13 +++++++++++++ .../Platforms/AbstractSQL92Platform.php | 13 +++++++++++++ .../Platforms/AbstractSQL94Platform.php | 13 +++++++++++++ .../Drivers/Platforms/SQL100Platform.php | 4 +--- src/Schema/Drivers/Platforms/SQL91Platform.php | 12 ------------ src/Schema/Drivers/Platforms/SQL92Platform.php | 18 ------------------ src/Schema/Drivers/Platforms/SQL94Platform.php | 9 --------- src/Schema/Drivers/Platforms/SQLPlatform.php | 3 ++- tests/Functional/TestUtil.php | 1 + 13 files changed, 83 insertions(+), 44 deletions(-) create mode 100644 src/Extensions/Schema/AbstractSQLSchemaManager.php create mode 100644 src/Extensions/Schema/Drivers/AbstractSQLPlatform.php create mode 100644 src/Extensions/Schema/Drivers/Platforms/AbstractSQL100Platform.php create mode 100644 src/Extensions/Schema/Drivers/Platforms/AbstractSQL91Platform.php create mode 100644 src/Extensions/Schema/Drivers/Platforms/AbstractSQL92Platform.php create mode 100644 src/Extensions/Schema/Drivers/Platforms/AbstractSQL94Platform.php diff --git a/.travis.yml b/.travis.yml index 3e1c1c9..4979026 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,7 +101,7 @@ matrix: - bash ./tests/travis/install-postgres-11.sh - stage: Test php: "7.4snapshot" - env: DB=pgsql POSTGRESQL_VERSION=11.0 COVERAGE=yes + env: DB=pgsql POSTGRESQL_VERSION=11.0 sudo: required services: - docker diff --git a/src/Extensions/Schema/AbstractSQLSchemaManager.php b/src/Extensions/Schema/AbstractSQLSchemaManager.php new file mode 100644 index 0000000..9055842 --- /dev/null +++ b/src/Extensions/Schema/AbstractSQLSchemaManager.php @@ -0,0 +1,13 @@ +quoteSingleIdentifier($collation); } - /** - * {@inheritDoc} - */ public function getListTableColumnsSQL($table, $database = null) { $sql = parent::getListTableColumnsSQL($table, $database); @@ -35,9 +26,6 @@ public function getListTableColumnsSQL($table, $database = null) return $parts[0] . 'AS complete_type, (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,' . $parts[1]; } - /** - * {@inheritdoc} - */ protected function getReservedKeywordsClass() { return PostgreSQL91Keywords::class; diff --git a/src/Schema/Drivers/Platforms/SQL92Platform.php b/src/Schema/Drivers/Platforms/SQL92Platform.php index 3fe1356..83b76d3 100644 --- a/src/Schema/Drivers/Platforms/SQL92Platform.php +++ b/src/Schema/Drivers/Platforms/SQL92Platform.php @@ -9,17 +9,11 @@ class SQL92Platform extends SQL91Platform { - /** - * {@inheritdoc} - */ public function getJsonTypeDeclarationSQL(array $field) { return 'JSON'; } - /** - * {@inheritdoc} - */ public function getSmallIntTypeDeclarationSQL(array $field) { if (!empty($field['autoincrement'])) { @@ -29,17 +23,11 @@ public function getSmallIntTypeDeclarationSQL(array $field) return parent::getSmallIntTypeDeclarationSQL($field); } - /** - * {@inheritdoc} - */ public function hasNativeJsonType() { return true; } - /** - * {@inheritdoc} - */ public function getCloseActiveDatabaseConnectionsSQL($database) { return sprintf( @@ -48,17 +36,11 @@ public function getCloseActiveDatabaseConnectionsSQL($database) ); } - /** - * {@inheritdoc} - */ protected function getReservedKeywordsClass() { return PostgreSQL92Keywords::class; } - /** - * {@inheritdoc} - */ protected function initializeDoctrineTypeMappings() { parent::initializeDoctrineTypeMappings(); diff --git a/src/Schema/Drivers/Platforms/SQL94Platform.php b/src/Schema/Drivers/Platforms/SQL94Platform.php index 19d3de5..ee6a4ac 100644 --- a/src/Schema/Drivers/Platforms/SQL94Platform.php +++ b/src/Schema/Drivers/Platforms/SQL94Platform.php @@ -9,9 +9,6 @@ class SQL94Platform extends SQL92Platform { - /** - * {@inheritdoc} - */ public function getJsonTypeDeclarationSQL(array $field) { if (!empty($field['jsonb'])) { @@ -21,17 +18,11 @@ public function getJsonTypeDeclarationSQL(array $field) return 'JSON'; } - /** - * {@inheritdoc} - */ protected function getReservedKeywordsClass() { return PostgreSQL94Keywords::class; } - /** - * {@inheritdoc} - */ protected function initializeDoctrineTypeMappings() { parent::initializeDoctrineTypeMappings(); diff --git a/src/Schema/Drivers/Platforms/SQLPlatform.php b/src/Schema/Drivers/Platforms/SQLPlatform.php index 5d4cd81..c5859d3 100644 --- a/src/Schema/Drivers/Platforms/SQLPlatform.php +++ b/src/Schema/Drivers/Platforms/SQLPlatform.php @@ -5,9 +5,10 @@ namespace Umbrellio\Postgres\Schema\Drivers\Platforms; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Illuminate\Support\Traits\Macroable; use Umbrellio\Postgres\Schema\Drivers\Traits\AlterTableSQLDeclarations; class SQLPlatform extends PostgreSqlPlatform { - use AlterTableSQLDeclarations; + use AlterTableSQLDeclarations, Macroable; } diff --git a/tests/Functional/TestUtil.php b/tests/Functional/TestUtil.php index 9c4a315..792a481 100644 --- a/tests/Functional/TestUtil.php +++ b/tests/Functional/TestUtil.php @@ -7,6 +7,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use function explode; +use const PHP_EOL; use Illuminate\Support\Facades\DB; class TestUtil From 3624080f6ba428d17c8c67f7d18f18d59f469e9c Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Fri, 19 Jul 2019 17:36:42 +0300 Subject: [PATCH 13/38] linter fixes --- src/Extensions/Schema/AbstractSQLSchemaManager.php | 2 ++ src/Extensions/Schema/Drivers/AbstractSQLPlatform.php | 2 ++ .../Schema/Drivers/Platforms/AbstractSQL100Platform.php | 2 ++ .../Schema/Drivers/Platforms/AbstractSQL91Platform.php | 2 ++ .../Schema/Drivers/Platforms/AbstractSQL92Platform.php | 2 ++ .../Schema/Drivers/Platforms/AbstractSQL94Platform.php | 2 ++ tests/Functional/TestUtil.php | 1 - 7 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Extensions/Schema/AbstractSQLSchemaManager.php b/src/Extensions/Schema/AbstractSQLSchemaManager.php index 9055842..118e62e 100644 --- a/src/Extensions/Schema/AbstractSQLSchemaManager.php +++ b/src/Extensions/Schema/AbstractSQLSchemaManager.php @@ -1,5 +1,7 @@ Date: Sat, 20 Jul 2019 01:03:44 +0300 Subject: [PATCH 14/38] optimize tests --- phpunit.travis.xml | 6 --- ...{UniqueIndexTest.php => BlueprintTest.php} | 27 +++++++++- .../{SchemaTest.php => BuilderTest.php} | 2 +- tests/Functional/Schema/HasIndexTest.php | 46 ---------------- tests/Functional/TestUtil.php | 53 ++----------------- tests/FunctionalTestCase.php | 4 +- 6 files changed, 31 insertions(+), 107 deletions(-) rename tests/Functional/Schema/{UniqueIndexTest.php => BlueprintTest.php} (85%) rename tests/Functional/Schema/{SchemaTest.php => BuilderTest.php} (97%) delete mode 100644 tests/Functional/Schema/HasIndexTest.php diff --git a/phpunit.travis.xml b/phpunit.travis.xml index 07f4297..a83988c 100644 --- a/phpunit.travis.xml +++ b/phpunit.travis.xml @@ -13,12 +13,6 @@ - - - - - - diff --git a/tests/Functional/Schema/UniqueIndexTest.php b/tests/Functional/Schema/BlueprintTest.php similarity index 85% rename from tests/Functional/Schema/UniqueIndexTest.php rename to tests/Functional/Schema/BlueprintTest.php index 7d7a47f..dcae59c 100644 --- a/tests/Functional/Schema/UniqueIndexTest.php +++ b/tests/Functional/Schema/BlueprintTest.php @@ -11,10 +11,35 @@ use Umbrellio\Postgres\Schema\Blueprint; use Umbrellio\Postgres\Tests\FunctionalTestCase; -class UniqueIndexTest extends FunctionalTestCase +class BlueprintTest extends FunctionalTestCase { use DatabaseTransactions; + /** @test */ + public function createIndexIfNotExists(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + + if (!$table->hasIndex(['name'], true)) { + $table->unique(['name']); + } + }); + + $this->assertTrue(Schema::hasTable('test_table')); + + $indexes = $this->getIndexByName('test_table_name_unique'); + + Schema::table('test_table', function (Blueprint $table) { + if (!$table->hasIndex(['name'], true)) { + $table->unique(['name']); + } + }); + + $this->assertTrue(isset($indexes->indexdef)); + } + /** * @test * @dataProvider provideIndexes diff --git a/tests/Functional/Schema/SchemaTest.php b/tests/Functional/Schema/BuilderTest.php similarity index 97% rename from tests/Functional/Schema/SchemaTest.php rename to tests/Functional/Schema/BuilderTest.php index 0c76ccf..c2bce24 100644 --- a/tests/Functional/Schema/SchemaTest.php +++ b/tests/Functional/Schema/BuilderTest.php @@ -9,7 +9,7 @@ use Umbrellio\Postgres\Schema\Blueprint; use Umbrellio\Postgres\Tests\FunctionalTestCase; -class SchemaTest extends FunctionalTestCase +class BuilderTest extends FunctionalTestCase { use DatabaseTransactions; diff --git a/tests/Functional/Schema/HasIndexTest.php b/tests/Functional/Schema/HasIndexTest.php deleted file mode 100644 index e5ecc5c..0000000 --- a/tests/Functional/Schema/HasIndexTest.php +++ /dev/null @@ -1,46 +0,0 @@ -increments('id'); - $table->string('name'); - - if (!$table->hasIndex(['name'], true)) { - $table->unique(['name']); - } - }); - - $this->assertTrue(Schema::hasTable('test_table')); - - $indexes = $this->getIndexByName('test_table_name_unique'); - - Schema::table('test_table', function (Blueprint $table) { - if (!$table->hasIndex(['name'], true)) { - $table->unique(['name']); - } - }); - - $this->assertTrue(isset($indexes->indexdef)); - } - - protected function getIndexByName($name) - { - return collect(DB::select("SELECT indexdef FROM pg_indexes WHERE indexname = '{$name}'"))->first(); - } -} diff --git a/tests/Functional/TestUtil.php b/tests/Functional/TestUtil.php index 9c4a315..a25c3ad 100644 --- a/tests/Functional/TestUtil.php +++ b/tests/Functional/TestUtil.php @@ -24,19 +24,12 @@ public static function createDatabase() public static function getConnection(): Connection { static::createDatabase(); - $conn = static::getDoctrineConnection(); - self::addDbEventSubscribers($conn); return $conn; } - public static function getTempConnection(): Connection - { - return static::getDoctrineConnection('temporary'); - } - public static function getParamsForMainConnection(): array { $connectionParams = [ @@ -59,32 +52,6 @@ public static function getParamsForMainConnection(): array return $connectionParams; } - public static function getParamsForTemporaryConnection(): array - { - $connectionParams = [ - 'driver' => $GLOBALS['tmpdb_type'], - 'user' => $GLOBALS['tmpdb_username'], - 'password' => $GLOBALS['tmpdb_password'], - 'host' => $GLOBALS['tmpdb_host'], - 'dbname' => null, - 'port' => $GLOBALS['tmpdb_port'], - ]; - - if (isset($GLOBALS['tmpdb_name'])) { - $connectionParams['dbname'] = $GLOBALS['tmpdb_name']; - } - - if (isset($GLOBALS['tmpdb_server'])) { - $connectionParams['server'] = $GLOBALS['tmpdb_server']; - } - - if (isset($GLOBALS['tmpdb_unix_socket'])) { - $connectionParams['unix_socket'] = $GLOBALS['tmpdb_unix_socket']; - } - - return $connectionParams; - } - private static function hasRequiredConnectionParams(): bool { return isset( @@ -94,33 +61,20 @@ private static function hasRequiredConnectionParams(): bool $GLOBALS['db_host'], $GLOBALS['db_name'], $GLOBALS['db_port'] - ) - && isset( - $GLOBALS['tmpdb_type'], - $GLOBALS['tmpdb_username'], - $GLOBALS['tmpdb_password'], - $GLOBALS['tmpdb_host'], - $GLOBALS['tmpdb_port'] ); } private static function initializeDatabase(): void { $realConn = static::createDoctrineConnection(self::getParamsForMainConnection()); - $tmpConn = static::createDoctrineConnection(self::getParamsForTemporaryConnection()); - $platform = $tmpConn->getDatabasePlatform(); + $platform = $realConn->getDatabasePlatform(); if ($platform->supportsCreateDropDatabase()) { - $dbname = $realConn->getDatabase(); + $realConn->getDatabase(); $realConn->close(); - - $tmpConn->getSchemaManager()->dropAndCreateDatabase($dbname); - - $tmpConn->close(); } else { $sm = $realConn->getSchemaManager(); - $schema = $sm->createSchema(); $stmts = $schema->toDropSql($realConn->getDatabasePlatform()); @@ -145,8 +99,7 @@ private static function addDbEventSubscribers(Connection $conn): void private static function getDoctrineConnection(string $name = null): Connection { - $connection = DB::connection($name); - return $connection->getDoctrineConnection(); + return DB::connection($name)->getDoctrineConnection(); } private static function createDoctrineConnection($params): Connection diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index e9260ab..7b9408c 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -13,12 +13,10 @@ protected function getEnvironmentSetUp($app) parent::getEnvironmentSetUp($app); $app['config']->set('database.default', 'main'); - $this->setConnectionConfig($app, 'main', TestUtil::getParamsForMainConnection()); - $this->setConnectionConfig($app, 'temporary', TestUtil::getParamsForTemporaryConnection()); } - private function setConnectionConfig($app, $name, $params) + private function setConnectionConfig($app, $name, $params): void { $app['config']->set('database.connections.' . $name, [ 'driver' => 'pgsql', From 860a1b966618453edbbdc46df21202ca0399218a Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Sat, 20 Jul 2019 03:13:12 +0300 Subject: [PATCH 15/38] add some tests (using, set default) --- tests/Functional/Schema/BlueprintTest.php | 65 +++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/Functional/Schema/BlueprintTest.php b/tests/Functional/Schema/BlueprintTest.php index dcae59c..dad2a20 100644 --- a/tests/Functional/Schema/BlueprintTest.php +++ b/tests/Functional/Schema/BlueprintTest.php @@ -5,6 +5,7 @@ namespace Umbrellio\Postgres\Tests\Functional\Schema; use Generator; +use Illuminate\Database\Query\Expression; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -15,6 +16,70 @@ class BlueprintTest extends FunctionalTestCase { use DatabaseTransactions; + public function testAlterTableUsing(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->increments('id'); + $table->string('code'); + }); + + DB::table('test_table')->insert([ + ['code' => '1'], + ]); + + Schema::table('test_table', function (Blueprint $table) { + $table->integer('code')->change(); + }); + + $this->assertSame('integer', Schema::getColumnType('test_table', 'code')); + + Schema::table('test_table', function (Blueprint $table) { + $table->string('code')->using("('[' || code || ']')::character varying")->change(); + }); + + $this->assertSame('string', Schema::getColumnType('test_table', 'code')); + $this->assertSame('[1]', DB::table('test_table')->first()->code); + } + + public function testAlterTableDefault(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->increments('id'); + $table->string('code')->default('test_string'); + }); + + DB::table('test_table')->insert(['id' => 1]); + + $this->assertSame('test_string', DB::table('test_table')->first()->code); + + Schema::table('test_table', function (Blueprint $table) { + $table->string('code')->nullable()->default(null)->change(); + }); + + DB::table('test_table')->truncate(); + DB::table('test_table')->insert(['id' => 1]); + + $this->assertNull(DB::table('test_table')->first()->code); + + Schema::table('test_table', function (Blueprint $table) { + $table->string('code')->default(new Expression("''::character varying"))->change(); + }); + + DB::table('test_table')->truncate(); + DB::table('test_table')->insert(['id' => 1]); + + $this->assertSame('', DB::table('test_table')->first()->code); + + Schema::table('test_table', function (Blueprint $table) { + $table->string('code')->default('')->change(); + }); + + DB::table('test_table')->truncate(); + DB::table('test_table')->insert(['id' => 1]); + + $this->assertSame('', DB::table('test_table')->first()->code); + } + /** @test */ public function createIndexIfNotExists(): void { From fa04f451ba8a08a3548264d299e3d7da38423fd1 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Sat, 20 Jul 2019 03:14:03 +0300 Subject: [PATCH 16/38] linter fixes --- tests/Functional/Schema/BlueprintTest.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/Functional/Schema/BlueprintTest.php b/tests/Functional/Schema/BlueprintTest.php index dad2a20..a56f939 100644 --- a/tests/Functional/Schema/BlueprintTest.php +++ b/tests/Functional/Schema/BlueprintTest.php @@ -16,7 +16,10 @@ class BlueprintTest extends FunctionalTestCase { use DatabaseTransactions; - public function testAlterTableUsing(): void + /** + * @test + */ + public function alterTableUsing(): void { Schema::create('test_table', function (Blueprint $table) { $table->increments('id'); @@ -41,7 +44,10 @@ public function testAlterTableUsing(): void $this->assertSame('[1]', DB::table('test_table')->first()->code); } - public function testAlterTableDefault(): void + /** + * @test + */ + public function alterTableDefault(): void { Schema::create('test_table', function (Blueprint $table) { $table->increments('id'); From cdb79d81f8a892bab6205e4d967f727bfd9d603e Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 12:01:08 +0300 Subject: [PATCH 17/38] refactor --- ecs.yml | 2 + .../Schema/AbstractSQLSchemaManager.php | 15 - .../Schema/Drivers/AbstractDoctrineDriver.php | 15 - .../Schema/Drivers/AbstractSQLPlatform.php | 15 - .../Platforms/AbstractSQL100Platform.php | 15 - .../Platforms/AbstractSQL91Platform.php | 15 - .../Platforms/AbstractSQL92Platform.php | 15 - .../Platforms/AbstractSQL94Platform.php | 15 - src/PostgresConnection.php | 28 +- src/Schema/Drivers/DoctrineDriver.php | 51 ---- .../Drivers/Platforms/SQL100Platform.php | 27 -- .../Drivers/Platforms/SQL91Platform.php | 33 --- .../Drivers/Platforms/SQL92Platform.php | 50 ---- .../Drivers/Platforms/SQL94Platform.php | 32 --- src/Schema/Drivers/Platforms/SQLPlatform.php | 14 - .../Traits/AlterTableSQLDeclarations.php | 261 ------------------ src/Schema/Grammars/PostgresGrammar.php | 6 + .../SchemaAlterTableChangeColumnListener.php | 26 ++ src/Schema/SQLSchemaManager.php | 13 - .../Traits/AlterTableChangeColumnTrait.php | 222 +++++++++++++++ tests/Functional/Schema/AlterColumnsTest.php | 217 +++++++++++++++ ...{BlueprintTest.php => CreateIndexTest.php} | 73 +---- .../{BuilderTest.php => CreateTableTest.php} | 8 +- tests/FunctionalTestCase.php | 47 +++- tests/TestCase.php | 2 +- tests/{Functional => }/TestUtil.php | 2 +- tests/Unit/BlueprintTestCase.php | 52 ++++ .../PartitionTest.php} | 40 +-- tests/Unit/Schema/Grammars/GrammarTest.php | 34 +-- tests/Unit/Schema/IndexTest.php | 42 --- 30 files changed, 614 insertions(+), 773 deletions(-) delete mode 100644 src/Extensions/Schema/AbstractSQLSchemaManager.php delete mode 100644 src/Extensions/Schema/Drivers/AbstractDoctrineDriver.php delete mode 100644 src/Extensions/Schema/Drivers/AbstractSQLPlatform.php delete mode 100644 src/Extensions/Schema/Drivers/Platforms/AbstractSQL100Platform.php delete mode 100644 src/Extensions/Schema/Drivers/Platforms/AbstractSQL91Platform.php delete mode 100644 src/Extensions/Schema/Drivers/Platforms/AbstractSQL92Platform.php delete mode 100644 src/Extensions/Schema/Drivers/Platforms/AbstractSQL94Platform.php delete mode 100644 src/Schema/Drivers/DoctrineDriver.php delete mode 100644 src/Schema/Drivers/Platforms/SQL100Platform.php delete mode 100644 src/Schema/Drivers/Platforms/SQL91Platform.php delete mode 100644 src/Schema/Drivers/Platforms/SQL92Platform.php delete mode 100644 src/Schema/Drivers/Platforms/SQL94Platform.php delete mode 100644 src/Schema/Drivers/Platforms/SQLPlatform.php delete mode 100644 src/Schema/Drivers/Traits/AlterTableSQLDeclarations.php create mode 100644 src/Schema/Listeners/SchemaAlterTableChangeColumnListener.php delete mode 100644 src/Schema/SQLSchemaManager.php create mode 100644 src/Schema/Traits/AlterTableChangeColumnTrait.php create mode 100644 tests/Functional/Schema/AlterColumnsTest.php rename tests/Functional/Schema/{BlueprintTest.php => CreateIndexTest.php} (69%) rename tests/Functional/Schema/{BuilderTest.php => CreateTableTest.php} (90%) rename tests/{Functional => }/TestUtil.php (98%) create mode 100644 tests/Unit/BlueprintTestCase.php rename tests/Unit/Schema/{BlueprintTest.php => Blueprint/PartitionTest.php} (59%) delete mode 100644 tests/Unit/Schema/IndexTest.php diff --git a/ecs.yml b/ecs.yml index 7f5cf1a..dc913e1 100644 --- a/ecs.yml +++ b/ecs.yml @@ -15,3 +15,5 @@ parameters: skip: Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff: - src/Schema/Blueprint.php + Symplify\CodingStandard\Sniffs\CleanCode\ForbiddenReferenceSniff: + - src/Schema/Traits/AlterTableChangeColumnTrait.php diff --git a/src/Extensions/Schema/AbstractSQLSchemaManager.php b/src/Extensions/Schema/AbstractSQLSchemaManager.php deleted file mode 100644 index 118e62e..0000000 --- a/src/Extensions/Schema/AbstractSQLSchemaManager.php +++ /dev/null @@ -1,15 +0,0 @@ -registerExtensions(); } - protected function getDoctrineDriver() + public function getDoctrineConnection(): Connection { - return new DoctrineDriver(); + return $this->fixDoctrineConnection(parent::getDoctrineConnection()); } protected function getDefaultSchemaGrammar() @@ -61,9 +64,9 @@ protected function getDefaultSchemaGrammar() /** * @codeCoverageIgnore */ - final private function registerExtensions(): void + private function registerExtensions(): void { - collect(self::$extensions)->each(function ($extension, $key) { + collect(self::$extensions)->each(function ($extension) { /** @var AbstractExtension $extension */ $extension::register(); foreach ($extension::getTypes() as $type => $typeClass) { @@ -71,4 +74,17 @@ final private function registerExtensions(): void } }); } + + private function fixDoctrineConnection(Connection $connection): Connection + { + $eventManager = $connection->getEventManager(); + if (!$eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { + $eventManager->addEventListener( + Events::onSchemaAlterTableChangeColumn, + new SchemaAlterTableChangeColumnListener() + ); + } + $connection->getDatabasePlatform()->setEventManager($eventManager); + return $connection; + } } diff --git a/src/Schema/Drivers/DoctrineDriver.php b/src/Schema/Drivers/DoctrineDriver.php deleted file mode 100644 index 88fe66d..0000000 --- a/src/Schema/Drivers/DoctrineDriver.php +++ /dev/null @@ -1,51 +0,0 @@ -getDatabasePlatform(); - } - } -} diff --git a/src/Schema/Drivers/Platforms/SQL100Platform.php b/src/Schema/Drivers/Platforms/SQL100Platform.php deleted file mode 100644 index 7129411..0000000 --- a/src/Schema/Drivers/Platforms/SQL100Platform.php +++ /dev/null @@ -1,27 +0,0 @@ -quoteStringLiteral($database) . " - AND sequence_schema NOT LIKE 'pg\_%' - AND sequence_schema != 'information_schema'"; - } - - protected function getReservedKeywordsClass(): string - { - return PostgreSQL100Keywords::class; - } -} diff --git a/src/Schema/Drivers/Platforms/SQL91Platform.php b/src/Schema/Drivers/Platforms/SQL91Platform.php deleted file mode 100644 index 04c36f8..0000000 --- a/src/Schema/Drivers/Platforms/SQL91Platform.php +++ /dev/null @@ -1,33 +0,0 @@ -quoteSingleIdentifier($collation); - } - - public function getListTableColumnsSQL($table, $database = null) - { - $sql = parent::getListTableColumnsSQL($table, $database); - $parts = explode('AS complete_type,', $sql, 2); - - return $parts[0] . 'AS complete_type, (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,' . $parts[1]; - } - - protected function getReservedKeywordsClass() - { - return PostgreSQL91Keywords::class; - } -} diff --git a/src/Schema/Drivers/Platforms/SQL92Platform.php b/src/Schema/Drivers/Platforms/SQL92Platform.php deleted file mode 100644 index 83b76d3..0000000 --- a/src/Schema/Drivers/Platforms/SQL92Platform.php +++ /dev/null @@ -1,50 +0,0 @@ -quoteStringLiteral($database) - ); - } - - protected function getReservedKeywordsClass() - { - return PostgreSQL92Keywords::class; - } - - protected function initializeDoctrineTypeMappings() - { - parent::initializeDoctrineTypeMappings(); - - $this->doctrineTypeMapping['json'] = Types::JSON; - } -} diff --git a/src/Schema/Drivers/Platforms/SQL94Platform.php b/src/Schema/Drivers/Platforms/SQL94Platform.php deleted file mode 100644 index ee6a4ac..0000000 --- a/src/Schema/Drivers/Platforms/SQL94Platform.php +++ /dev/null @@ -1,32 +0,0 @@ -doctrineTypeMapping['jsonb'] = Types::JSON; - } -} diff --git a/src/Schema/Drivers/Platforms/SQLPlatform.php b/src/Schema/Drivers/Platforms/SQLPlatform.php deleted file mode 100644 index c5859d3..0000000 --- a/src/Schema/Drivers/Platforms/SQLPlatform.php +++ /dev/null @@ -1,14 +0,0 @@ -getDefault() === null) { - return sprintf('ALTER TABLE %s ALTER %s DROP DEFAULT', $table, $columnName); - } - - return sprintf( - 'ALTER TABLE %s ALTER %s SET %s', - $table, - $columnName, - trim($this->fixDefaultValueDeclarationSQL($column)) - ); - } - - public function fixDefaultValueDeclarationSQL(Column $column): string - { - if ($column->getDefault() instanceof Expression) { - return ' DEFAULT ' . $column->getDefault(); - } - return $this->getDefaultValueDeclarationSQL($column->toArray()); - } - - /** - * {@inheritDoc} - */ - public function getAlterTableSQL(TableDiff $diff) - { - $sql = []; - $commentsSQL = []; - $columnSql = []; - - foreach ($diff->addedColumns as $column) { - if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { - continue; - } - - $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); - $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; - - $comment = $this->getColumnComment($column); - - if ($comment === null || $comment === '') { - continue; - } - - $commentsSQL[] = $this->getCommentOnColumnSQL( - $diff->getName($this)->getQuotedName($this), - $column->getQuotedName($this), - $comment - ); - } - - foreach ($diff->removedColumns as $column) { - if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { - continue; - } - - $query = 'DROP ' . $column->getQuotedName($this); - $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; - } - - foreach ($diff->changedColumns as $columnDiff) { - /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ - if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { - continue; - } - - if ($this->isUnchangedBinaryColumn($columnDiff)) { - continue; - } - - $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this); - $column = $columnDiff->column; - - if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed')) { - $type = $column->getType(); - - // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type - $columnDefinition = $column->toArray(); - $columnDefinition['autoincrement'] = false; - - // here was a server version check before, but DBAL API does not support this anymore. - $sql[] = $this->getAlterTableAlterTypeColumnSQL( - $diff->getName($this)->getQuotedName($this), - $oldColumnName, - $type->getSQLDeclaration($columnDefinition, $this), - $columnDefinition - ); - } - - if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) { - $sql[] = $this->getAlterTableAlterDefaultSQL( - $diff->getName($this)->getQuotedName($this), - $oldColumnName, - $column - ); - } - - if ($columnDiff->hasChanged('notnull')) { - $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL'; - $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; - } - - if ($columnDiff->hasChanged('autoincrement')) { - if ($column->getAutoincrement()) { - // add autoincrement - $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName); - - $sql[] = 'CREATE SEQUENCE ' . $seqName; - $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ') FROM ' . $diff->getName($this)->getQuotedName($this) . '))'; - $query = 'ALTER ' . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; - $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; - } else { - // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have - $query = 'ALTER ' . $oldColumnName . ' DROP DEFAULT'; - $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; - } - } - - $newComment = $this->getColumnComment($column); - $oldComment = $this->getOldColumnComment($columnDiff); - - if ($columnDiff->hasChanged('comment') || ($columnDiff->fromColumn !== null && $oldComment !== $newComment)) { - $commentsSQL[] = $this->getCommentOnColumnSQL( - $diff->getName($this)->getQuotedName($this), - $column->getQuotedName($this), - $newComment - ); - } - - if (! $columnDiff->hasChanged('length')) { - continue; - } - - $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $column->getType()->getSQLDeclaration($column->toArray(), $this); - $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; - } - - foreach ($diff->renamedColumns as $oldColumnName => $column) { - if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { - continue; - } - - $oldColumnName = new Identifier($oldColumnName); - - $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . - ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); - } - - $tableSql = []; - - if (! $this->onSchemaAlterTable($diff, $tableSql)) { - $sql = array_merge($sql, $commentsSQL); - - $newName = $diff->getNewName(); - - if ($newName !== false) { - $sql[] = sprintf( - 'ALTER TABLE %s RENAME TO %s', - $diff->getName($this)->getQuotedName($this), - $newName->getQuotedName($this) - ); - } - - $sql = array_merge( - $this->getPreAlterTableIndexForeignKeySQL($diff), - $sql, - $this->getPostAlterTableIndexForeignKeySQL($diff) - ); - } - - return array_merge($sql, $tableSql, $columnSql); - } - - private function isUnchangedBinaryColumn(ColumnDiff $columnDiff): bool - { - $columnType = $columnDiff->column->getType(); - - if (!$columnType instanceof BinaryType && !$columnType instanceof BlobType) { - return false; - } - - $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null; - - if ($fromColumn) { - $fromColumnType = $fromColumn->getType(); - - if (!$fromColumnType instanceof BinaryType && !$fromColumnType instanceof BlobType) { - return false; - } - - return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0; - } - - if ($columnDiff->hasChanged('type')) { - return false; - } - - return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0; - } - - private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff): bool - { - if (!$columnDiff->fromColumn) { - return $columnDiff->hasChanged('type'); - } - - $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType()); - $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType()); - - // default should not be changed when switching between numeric types and the default comes from a sequence - return $columnDiff->hasChanged('type') - && !($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement()); - } - - private function isNumericType(Type $type): bool - { - return $type instanceof IntegerType || $type instanceof BigIntType; - } - - private function getOldColumnComment(ColumnDiff $columnDiff): ?string - { - return $columnDiff->fromColumn ? $this->getColumnComment($columnDiff->fromColumn) : null; - } -} diff --git a/src/Schema/Grammars/PostgresGrammar.php b/src/Schema/Grammars/PostgresGrammar.php index 088a14e..3a321ed 100644 --- a/src/Schema/Grammars/PostgresGrammar.php +++ b/src/Schema/Grammars/PostgresGrammar.php @@ -4,6 +4,7 @@ namespace Umbrellio\Postgres\Schema\Grammars; +use Illuminate\Database\Connection; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Grammars\PostgresGrammar as BasePostgresGrammar; use Illuminate\Support\Fluent; @@ -28,6 +29,11 @@ public function compileCreate(Blueprint $blueprint, Fluent $command): string ); } + public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + { + return parent::compileChange($blueprint, $command, $connection); // TODO: Change the autogenerated stub + } + public function compileAttachPartition(Blueprint $blueprint, Fluent $command): string { return AttachPartitionCompiler::compile($this, $blueprint, $command); diff --git a/src/Schema/Listeners/SchemaAlterTableChangeColumnListener.php b/src/Schema/Listeners/SchemaAlterTableChangeColumnListener.php new file mode 100644 index 0000000..bce9820 --- /dev/null +++ b/src/Schema/Listeners/SchemaAlterTableChangeColumnListener.php @@ -0,0 +1,26 @@ +preventDefault(); + + $sql = array_unique(array_merge($event->getSql(), $this->getAlterTableChangeColumnSQL( + $event->getPlatform(), + $event->getTableDiff(), + $event->getColumnDiff() + ))); + + $event->addSql($sql); + } +} diff --git a/src/Schema/SQLSchemaManager.php b/src/Schema/SQLSchemaManager.php deleted file mode 100644 index 3d62971..0000000 --- a/src/Schema/SQLSchemaManager.php +++ /dev/null @@ -1,13 +0,0 @@ -quoteName($platform, $diff); + + $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($platform); + $column = $columnDiff->column; + + $this->compileAlterColumnType($platform, $columnDiff, $column, $quoteName, $oldColumnName, $sql); + + $this->compileAlterColumnDefault($platform, $columnDiff, $column, $quoteName, $oldColumnName, $sql); + + $this->compileAlterColumnNull($columnDiff, $column, $quoteName, $oldColumnName, $sql); + + $this->compileAlterColumnSequence($platform, $columnDiff, $diff, $column, $quoteName, $oldColumnName, $sql); + + $this->compileAlterColumnComment($platform, $columnDiff, $column, $quoteName, $sql); + + if (!$columnDiff->hasChanged('length')) { + return $sql; + } + + $sql[] = sprintf( + 'ALTER TABLE %s ALTER %s TYPE %s', + $quoteName, + $oldColumnName, + $column->getType()->getSQLDeclaration($column->toArray(), $platform) + ); + + return $sql; + } + + private function compileAlterColumnComment( + AbstractPlatform $platform, + ColumnDiff $columnDiff, + Column $column, + string $quoteName, + &$sql + ): void { + $newComment = $this->getColumnComment($platform, $column); + $oldComment = $this->getOldColumnComment($platform, $columnDiff); + + if (($columnDiff->fromColumn !== null && $oldComment !== $newComment) + || $columnDiff->hasChanged('comment') + ) { + $sql[] = $platform->getCommentOnColumnSQL($quoteName, $column->getQuotedName($platform), $newComment); + } + } + + private function compileAlterColumnNull( + ColumnDiff $columnDiff, + Column $column, + string $quoteName, + string $oldColumnName, + &$sql + ): void { + if ($columnDiff->hasChanged('notnull')) { + $sql[] = sprintf( + 'ALTER TABLE %s ALTER %s %s NOT NULL', + $quoteName, + $oldColumnName, + ($column->getNotnull() ? 'SET' : 'DROP') + ); + } + } + + private function compileAlterColumnDefault( + AbstractPlatform $platform, + ColumnDiff $columnDiff, + Column $column, + string $quoteName, + string $oldColumnName, + &$sql + ): void { + if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) { + $defaultClause = $column->getDefault() === null + ? ' DROP DEFAULT' + : ' SET' . $this->getDefaultValueDeclarationSQL($platform, $column); + $sql[] = sprintf('ALTER TABLE %s ALTER %s %s', $quoteName, $oldColumnName, trim($defaultClause)); + } + } + + private function compileAlterColumnSequence( + AbstractPlatform $platform, + ColumnDiff $columnDiff, + TableDiff $diff, + Column $column, + string $quoteName, + string $oldColumnName, + &$sql + ): void { + if ($columnDiff->hasChanged('autoincrement')) { + if ($column->getAutoincrement()) { + $seqName = $platform->getIdentitySequenceName($diff->name, $oldColumnName); + + $sql[] = sprintf('CREATE SEQUENCE %s', $seqName); + $sql[] = sprintf( + "SELECT setval('%s', (SELECT MAX(%s) FROM %s))", + $seqName, + $oldColumnName, + $quoteName + ); + $sql[] = sprintf( + "ALTER TABLE %s ALTER %s SET DEFAULT nextval('%s')", + $quoteName, + $oldColumnName, + $seqName + ); + } else { + $sql[] = sprintf('ALTER TABLE %s ALTER %s DROP DEFAULT', $quoteName, $oldColumnName); + } + } + } + + private function compileAlterColumnType( + AbstractPlatform $platform, + ColumnDiff $columnDiff, + Column $column, + string $quoteName, + string $oldColumnName, + &$sql + ): void { + if ($columnDiff->hasChanged('type') + || $columnDiff->hasChanged('precision') + || $columnDiff->hasChanged('scale') + || $columnDiff->hasChanged('fixed') + ) { + $type = $column->getType(); + + $columnDefinition = $column->toArray(); + $columnDefinition['autoincrement'] = false; + + if ($this->typeChangeBreaksDefaultValue($columnDiff)) { + $sql[] = sprintf('ALTER TABLE %s ALTER %s DROP DEFAULT', $quoteName, $oldColumnName); + } + + $typeName = $type->getSQLDeclaration($columnDefinition, $platform); + + $using = ''; + if ($columnDiff->hasChanged('type')) { + $using = sprintf('USING %s::%s', $oldColumnName, $typeName); + + if ($columnDefinition['using'] ?? false) { + $using = 'USING ' . $columnDefinition['using']; + } + } + + $sql[] = trim(sprintf( + 'ALTER TABLE %s ALTER %s TYPE %s %s', + $quoteName, + $oldColumnName, + $typeName, + $using + )); + } + } + + private function getDefaultValueDeclarationSQL(AbstractPlatform $platform, Column $column): string + { + if ($column->getDefault() instanceof Expression) { + return ' DEFAULT ' . $column->getDefault(); + } + + return $platform->getDefaultValueDeclarationSQL($column->toArray()); + } + + private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff): bool + { + $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType()); + $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType()); + + return $columnDiff->hasChanged('type') + && !($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement()); + } + + private function isNumericType(Type $type): bool + { + return $type instanceof IntegerType || $type instanceof BigIntType; + } + + private function quoteName(AbstractPlatform $platform, TableDiff $diff): string + { + return $diff->getName($platform)->getQuotedName($platform); + } + + private function getOldColumnComment(AbstractPlatform $platform, ColumnDiff $columnDiff): ?string + { + return $columnDiff->fromColumn ? $this->getColumnComment($platform, $columnDiff->fromColumn) : null; + } + + private function getColumnComment(AbstractPlatform $platform, Column $column): ?string + { + $comment = $column->getComment(); + + if ($platform->isCommentedDoctrineType($column->getType())) { + $comment .= $platform->getDoctrineTypeComment($column->getType()); + } + + return $comment; + } +} diff --git a/tests/Functional/Schema/AlterColumnsTest.php b/tests/Functional/Schema/AlterColumnsTest.php new file mode 100644 index 0000000..d819c84 --- /dev/null +++ b/tests/Functional/Schema/AlterColumnsTest.php @@ -0,0 +1,217 @@ +string('code')->default('1'); + }); + Schema::table('test_table', function (Blueprint $table) { + $table->string('code')->comment('some comment')->change(); + }); + $this->assertCommentOnColumn('test_table', 'code', 'some comment'); + } + + /** @test */ + public function alterTableJsonSetComment(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->json('myjson')->comment('(DC2Type:json_array)'); + }); + Schema::table('test_table', function (Blueprint $table) { + $table->json('myjson')->comment('(DC2Type:json_array)')->change(); + }); + $this->assertCommentOnColumn('test_table', 'myjson', '(DC2Type:json_array)'); + } + + /** @test */ + public function alterTableSetDCComment(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->string('code')->default('1'); + }); + Schema::table('test_table', function (Blueprint $table) { + $table->string('code')->comment('(DC2Type:string)')->change(); + }); + $this->assertCommentOnColumn('test_table', 'code', '(DC2Type:string)'); + } + + /** @test */ + public function alterTableDropDCComment(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->integer('number')->comment('(DC2Type:integer)')->default(1); + }); + $this->assertCommentOnColumn('test_table', 'number', '(DC2Type:integer)'); + + Schema::table('test_table', function (Blueprint $table) { + $table->integer('number')->comment('test')->change(); + }); + + $this->assertCommentOnColumn('test_table', 'number', 'test'); + } + + /** @test */ + public function alterTableChangeSimpleComment(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->integer('number')->comment('(DC2Type:integer)')->default(1); + }); + Schema::table('test_table', function (Blueprint $table) { + $table->string('number')->comment('some comment')->change(); + }); + $this->assertCommentOnColumn('test_table', 'number', 'some comment'); + } + + /** @test */ + public function alterTableUsingByDefault(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->string('code')->default('1'); + }); + Schema::table('test_table', function (Blueprint $table) { + $table->integer('code')->default(null)->change(); + }); + + $this->assertSame('integer', Schema::getColumnType('test_table', 'code')); + $this->assertDefaultOnColumn('test_table', 'code'); + } + + /** @test */ + public function alterTableUsingWithExpression(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->integer('id')->primary(); + $table->integer('number')->default('1')->nullable(); + }); + + DB::table('test_table')->insert([['id' => 1]]); + $this->assertDatabaseHas('test_table', ['id' => 1]); + $this->assertSame('integer', Schema::getColumnType('test_table', 'number')); + + Schema::table('test_table', function (Blueprint $table) { + $table->string('number') + ->using("('[' || number || ']')::character varying") + ->change(); + }); + + $this->assertSame('string', Schema::getColumnType('test_table', 'number')); + $this->assertDatabaseHas('test_table', [ + 'id' => 1, + 'number' => '[1]', + ]); + } + + /** @test */ + public function alterTableSetDefault(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->integer('code')->nullable(); + }); + + $this->assertSame('integer', Schema::getColumnType('test_table', 'code')); + $this->assertDefaultOnColumn('test_table', 'code'); + + Schema::table('test_table', function (Blueprint $table) { + $table->string('code')->default('test_string')->change(); + }); + + $this->assertSame('string', Schema::getColumnType('test_table', 'code')); + $this->assertDefaultOnColumn('test_table', 'code', "'test_string'::character varying"); + } + + /** @test */ + public function alterTableChangeDefault(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->string('description')->default('default1'); + }); + + $this->assertDefaultOnColumn('test_table', 'description', "'default1'::character varying"); + + Schema::table('test_table', function (Blueprint $table) { + $table->text('description', 25)->default('default2')->change(); + }); + + $this->assertDefaultOnColumn('test_table', 'description', "'default2'::text"); + } + + /** @test */ + public function alterTableDropDefault(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->string('description')->default('default_value'); + }); + + $this->assertDefaultOnColumn('test_table', 'description', "'default_value'::character varying"); + + Schema::table('test_table', function (Blueprint $table) { + $table->string('description')->nullable()->default(null)->change(); + }); + + $this->assertDefaultOnColumn('test_table', 'description'); + } + + /** @test */ + public function alterTableSetDefaultExpression(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->string('code')->nullable(); + }); + + $this->assertDefaultOnColumn('test_table', 'code'); + + Schema::table('test_table', function (Blueprint $table) { + $table->string('code')->default(new Expression("''::character varying"))->change(); + }); + + $this->assertDefaultOnColumn('test_table', 'code', "''::character varying"); + } + + /** @test */ + public function alterTableCreateSequence(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->integer('id')->default(1); + }); + + $this->assertDefaultOnColumn('test_table', 'id', '1'); + + Schema::table('test_table', function (Blueprint $table) { + $table->increments('id')->change(); + }); + + $this->assertDefaultOnColumn('test_table', 'id', "nextval('test_table_id_seq'::regclass)"); + } + + /** @test */ + public function alterTableDropSequence(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->increments('id'); + }); + + $this->assertDefaultOnColumn('test_table', 'id', "nextval('test_table_id_seq'::regclass)"); + + Schema::table('test_table', function (Blueprint $table) { + $table->integer('id')->change(); + }); + + $this->assertDefaultOnColumn('test_table', 'id'); + } +} diff --git a/tests/Functional/Schema/BlueprintTest.php b/tests/Functional/Schema/CreateIndexTest.php similarity index 69% rename from tests/Functional/Schema/BlueprintTest.php rename to tests/Functional/Schema/CreateIndexTest.php index a56f939..a56baab 100644 --- a/tests/Functional/Schema/BlueprintTest.php +++ b/tests/Functional/Schema/CreateIndexTest.php @@ -5,87 +5,16 @@ namespace Umbrellio\Postgres\Tests\Functional\Schema; use Generator; -use Illuminate\Database\Query\Expression; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; use Umbrellio\Postgres\Tests\FunctionalTestCase; -class BlueprintTest extends FunctionalTestCase +class CreateIndexTest extends FunctionalTestCase { use DatabaseTransactions; - /** - * @test - */ - public function alterTableUsing(): void - { - Schema::create('test_table', function (Blueprint $table) { - $table->increments('id'); - $table->string('code'); - }); - - DB::table('test_table')->insert([ - ['code' => '1'], - ]); - - Schema::table('test_table', function (Blueprint $table) { - $table->integer('code')->change(); - }); - - $this->assertSame('integer', Schema::getColumnType('test_table', 'code')); - - Schema::table('test_table', function (Blueprint $table) { - $table->string('code')->using("('[' || code || ']')::character varying")->change(); - }); - - $this->assertSame('string', Schema::getColumnType('test_table', 'code')); - $this->assertSame('[1]', DB::table('test_table')->first()->code); - } - - /** - * @test - */ - public function alterTableDefault(): void - { - Schema::create('test_table', function (Blueprint $table) { - $table->increments('id'); - $table->string('code')->default('test_string'); - }); - - DB::table('test_table')->insert(['id' => 1]); - - $this->assertSame('test_string', DB::table('test_table')->first()->code); - - Schema::table('test_table', function (Blueprint $table) { - $table->string('code')->nullable()->default(null)->change(); - }); - - DB::table('test_table')->truncate(); - DB::table('test_table')->insert(['id' => 1]); - - $this->assertNull(DB::table('test_table')->first()->code); - - Schema::table('test_table', function (Blueprint $table) { - $table->string('code')->default(new Expression("''::character varying"))->change(); - }); - - DB::table('test_table')->truncate(); - DB::table('test_table')->insert(['id' => 1]); - - $this->assertSame('', DB::table('test_table')->first()->code); - - Schema::table('test_table', function (Blueprint $table) { - $table->string('code')->default('')->change(); - }); - - DB::table('test_table')->truncate(); - DB::table('test_table')->insert(['id' => 1]); - - $this->assertSame('', DB::table('test_table')->first()->code); - } - /** @test */ public function createIndexIfNotExists(): void { diff --git a/tests/Functional/Schema/BuilderTest.php b/tests/Functional/Schema/CreateTableTest.php similarity index 90% rename from tests/Functional/Schema/BuilderTest.php rename to tests/Functional/Schema/CreateTableTest.php index c2bce24..955eee5 100644 --- a/tests/Functional/Schema/BuilderTest.php +++ b/tests/Functional/Schema/CreateTableTest.php @@ -9,12 +9,12 @@ use Umbrellio\Postgres\Schema\Blueprint; use Umbrellio\Postgres\Tests\FunctionalTestCase; -class BuilderTest extends FunctionalTestCase +class CreateTableTest extends FunctionalTestCase { use DatabaseTransactions; /** @test */ - public function create(): void + public function createSimple(): void { Schema::create('test_table', function (Blueprint $table) { $table->increments('id'); @@ -26,7 +26,7 @@ public function create(): void } /** @test */ - public function createLikeSimple(): void + public function createViaLike(): void { Schema::create('test_table', function (Blueprint $table) { $table->increments('id'); @@ -44,7 +44,7 @@ public function createLikeSimple(): void } /** @test */ - public function createLikeFull(): void + public function createViaLikeIncludingAll(): void { Schema::create('test_table', function (Blueprint $table) { $table->increments('id'); diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index 7b9408c..bb157ac 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -4,7 +4,7 @@ namespace Umbrellio\Postgres\Tests; -use Umbrellio\Postgres\Tests\Functional\TestUtil; +use Illuminate\Support\Facades\DB; abstract class FunctionalTestCase extends TestCase { @@ -16,6 +16,26 @@ protected function getEnvironmentSetUp($app) $this->setConnectionConfig($app, 'main', TestUtil::getParamsForMainConnection()); } + protected function assertCommentOnColumn(string $table, string $column, ?string $expected = null): void + { + $comment = $this->getCommentListing($table, $column); + + if ($expected === null) { + $this->assertNull($comment); + } + $this->assertSame($expected, $comment); + } + + protected function assertDefaultOnColumn(string $table, string $column, ?string $expected = null): void + { + $defaultValue = $this->getDefaultListing($table, $column); + + if ($expected === null) { + $this->assertNull($defaultValue); + } + $this->assertSame($expected, $defaultValue); + } + private function setConnectionConfig($app, $name, $params): void { $app['config']->set('database.connections.' . $name, [ @@ -30,4 +50,29 @@ private function setConnectionConfig($app, $name, $params): void 'schema' => 'public', ]); } + + private function getCommentListing(string $table, string $column) + { + $definition = DB::selectOne( + ' + SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st + INNER JOIN pg_catalog.pg_description pgd ON (pgd.objoid = st.relid) + INNER JOIN information_schema.columns c ON pgd.objsubid = c.ordinal_position AND c.table_schema = st.schemaname AND c.table_name = st.relname + WHERE c.table_name = ? AND c.column_name = ? + ', + [$table, $column] + ); + + return $definition ? $definition->description : null; + } + + private function getDefaultListing(string $table, string $column) + { + $definition = DB::selectOne( + 'SELECT column_default FROM information_schema.columns WHERE table_name = ? and column_name = ?', + [$table, $column] + ); + + return $definition ? $definition->column_default : null; + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 943c88f..d851b9e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,7 +6,6 @@ use Illuminate\Support\Facades\Facade; use Orchestra\Testbench\TestCase as BaseTestCase; -use Umbrellio\Postgres\Tests\Functional\TestUtil; use Umbrellio\Postgres\UmbrellioPostgresProvider; abstract class TestCase extends BaseTestCase @@ -24,6 +23,7 @@ protected function setUp(): void Facade::clearResolvedInstances(); } + protected function getPackageProviders($app) { return [UmbrellioPostgresProvider::class]; diff --git a/tests/Functional/TestUtil.php b/tests/TestUtil.php similarity index 98% rename from tests/Functional/TestUtil.php rename to tests/TestUtil.php index a25c3ad..737c7da 100644 --- a/tests/Functional/TestUtil.php +++ b/tests/TestUtil.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Umbrellio\Postgres\Tests\Functional; +namespace Umbrellio\Postgres\Tests; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; diff --git a/tests/Unit/BlueprintTestCase.php b/tests/Unit/BlueprintTestCase.php new file mode 100644 index 0000000..45cd371 --- /dev/null +++ b/tests/Unit/BlueprintTestCase.php @@ -0,0 +1,52 @@ +blueprint = new Blueprint('test_table'); + $this->postgresConnection = $this->createMock(PostgresConnection::class); + $this->postgresGrammar = new PostgresGrammar(); + } + + /** + * @param string|array $sql + */ + protected function assertSameSql($sql): void + { + $this->assertSame((array) $sql, $this->runToSql()); + } + + /** + * @param string|array $regexpExpected + */ + protected function assertRegExpSql($regexpExpected): void + { + foreach ($this->runToSql() as $sql) { + $this->assertRegExp($regexpExpected, $sql); + } + } + + protected function runToSql(): array + { + return $this->blueprint->toSql($this->postgresConnection, $this->postgresGrammar); + } +} diff --git a/tests/Unit/Schema/BlueprintTest.php b/tests/Unit/Schema/Blueprint/PartitionTest.php similarity index 59% rename from tests/Unit/Schema/BlueprintTest.php rename to tests/Unit/Schema/Blueprint/PartitionTest.php index e70633f..64f8f45 100644 --- a/tests/Unit/Schema/BlueprintTest.php +++ b/tests/Unit/Schema/Blueprint/PartitionTest.php @@ -2,32 +2,18 @@ declare(strict_types=1); -namespace Umbrellio\Postgres\Unit\Schema; +namespace Umbrellio\Postgres\Unit\Schema\Blueprint; use Illuminate\Support\Carbon; use InvalidArgumentException; -use Umbrellio\Postgres\PostgresConnection; -use Umbrellio\Postgres\Schema\Blueprint; -use Umbrellio\Postgres\Schema\Grammars\PostgresGrammar; -use Umbrellio\Postgres\Tests\TestCase; +use Umbrellio\Postgres\Tests\Unit\BlueprintTestCase; -class BlueprintTest extends TestCase +class PartitionTest extends BlueprintTestCase { - /** @var Blueprint */ - private $blueprint; - - protected function setUp(): void - { - parent::setUp(); - - $this->blueprint = new Blueprint('test_table'); - } - /** @test */ public function detachPartition(): void { $this->blueprint->detachPartition('some_partition'); - $this->assertSameSql('alter table "test_table" detach partition some_partition'); } @@ -38,7 +24,6 @@ public function attachPartitionRangeInt(): void 'from' => 10, 'to' => 100, ]); - $this->assertSameSql('alter table "test_table" attach partition some_partition for values from (10) to (100)'); } @@ -46,7 +31,6 @@ public function attachPartitionRangeInt(): void public function attachPartitionFailedWithoutForValuesPart(): void { $this->blueprint->attachPartition('some_partition'); - $this->expectException(InvalidArgumentException::class); $this->runToSql(); } @@ -61,18 +45,10 @@ public function attachPartitionRangeDates(): void 'to' => $tomorrow, ]); - $this->assertSameSql( - 'alter table "test_table" attach partition some_partition ' - . "for values from ('{$today->toDateTimeString()}') to ('{$tomorrow->toDateTimeString()}')"); - } - - private function assertSameSql(string $sql): void - { - $this->assertSame([$sql], $this->runToSql()); - } - - private function runToSql(): array - { - return $this->blueprint->toSql($this->createMock(PostgresConnection::class), new PostgresGrammar()); + $this->assertSameSql(sprintf( + 'alter table "test_table" attach partition some_partition for values from (\'%s\') to (\'%s\')', + $today->toDateTimeString(), + $tomorrow->toDateTimeString() + )); } } diff --git a/tests/Unit/Schema/Grammars/GrammarTest.php b/tests/Unit/Schema/Grammars/GrammarTest.php index 808628b..7626f76 100644 --- a/tests/Unit/Schema/Grammars/GrammarTest.php +++ b/tests/Unit/Schema/Grammars/GrammarTest.php @@ -4,43 +4,21 @@ namespace Umbrellio\Postgres\Tests\Unit\Schema\Grammars; -use Mockery; -use Umbrellio\Postgres\PostgresConnection; -use Umbrellio\Postgres\Schema\Blueprint; -use Umbrellio\Postgres\Schema\Grammars\PostgresGrammar; -use Umbrellio\Postgres\Tests\TestCase; +use Umbrellio\Postgres\Tests\Unit\BlueprintTestCase; -class GrammarTest extends TestCase +class GrammarTest extends BlueprintTestCase { /** @test */ public function addingGinIndex() { - $blueprint = new Blueprint('test'); - $blueprint->gin('foo'); - $statements = $blueprint->toSql($this->getConnectionMock(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertStringContainsString('CREATE INDEX', $statements[0]); - $this->assertStringContainsString('GIN("foo")', $statements[0]); + $this->blueprint->gin('foo'); + $this->assertRegExpSql('/CREATE INDEX test_table_foo_gin ON (public.)?"test_table" USING GIN\("foo"\)/'); } /** @test */ public function addingGistIndex() { - $blueprint = new Blueprint('test'); - $blueprint->gist('foo'); - $statements = $blueprint->toSql($this->getConnectionMock(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertStringContainsString('CREATE INDEX', $statements[0]); - $this->assertStringContainsString('GIST("foo")', $statements[0]); - } - - protected function getConnectionMock() - { - return Mockery::mock(PostgresConnection::class); - } - - protected function getGrammar() - { - return new PostgresGrammar(); + $this->blueprint->gist('foo'); + $this->assertRegExpSql('/CREATE INDEX test_table_foo_gist ON (public.)?"test_table" USING GIST\("foo"\)/'); } } diff --git a/tests/Unit/Schema/IndexTest.php b/tests/Unit/Schema/IndexTest.php deleted file mode 100644 index 1b05de6..0000000 --- a/tests/Unit/Schema/IndexTest.php +++ /dev/null @@ -1,42 +0,0 @@ -blueprint = Mockery::mock(Blueprint::class) - ->makePartial() - ->shouldAllowMockingProtectedMethods(); - } - - /** @test */ - public function ginIndex() - { - $this->blueprint - ->shouldReceive('indexCommand') - ->with('gin', 'col', 'myName'); - $this->blueprint->gin('col', 'myName'); - } - - /** @test */ - public function gistIndex() - { - $this->blueprint - ->shouldReceive('indexCommand') - ->with('gist', 'col', 'myName'); - $this->blueprint->gist('col', 'myName'); - } -} From ea99b3c817315467671a041b0e2a329bd7eef525 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 13:56:22 +0300 Subject: [PATCH 18/38] travis fixes --- .gitignore | 1 - .travis.yml | 14 +++ README.md | 19 +++ ecs.yml | 1 - phpunit.travis.xml | 3 +- phpunit.xml.dist | 14 ++- src/Schema/Grammars/PostgresGrammar.php | 6 - .../SchemaAlterTableChangeColumnListener.php | 14 +-- .../Traits/AlterTableChangeColumnTrait.php | 36 +++--- tests.sh | 4 + tests/FunctionalTestCase.php | 36 +++++- tests/TestCase.php | 15 --- tests/TestUtil.php | 109 ------------------ tests/travis/create-database.sh | 3 + 14 files changed, 108 insertions(+), 167 deletions(-) create mode 100755 tests.sh delete mode 100644 tests/TestUtil.php create mode 100755 tests/travis/create-database.sh diff --git a/.gitignore b/.gitignore index 332dcde..5ed27c6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ phpunit.xml .phpunit.result.cache /build -phpunit.xml diff --git a/.travis.yml b/.travis.yml index 4979026..c767de7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,7 @@ matrix: - docker before_script: - bash ./tests/travis/install-postgres-11.sh + - bash ./tests/travis/create-database.sh - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.2 COVERAGE=yes @@ -53,6 +54,8 @@ matrix: - postgresql addons: postgresql: "9.2" + before_script: + - bash ./tests/travis/create-database.sh - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes @@ -60,6 +63,8 @@ matrix: - postgresql addons: postgresql: "9.3" + before_script: + - bash ./tests/travis/create-database.sh - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.4 COVERAGE=yes @@ -67,6 +72,8 @@ matrix: - postgresql addons: postgresql: "9.4" + before_script: + - bash ./tests/travis/create-database.sh - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.5 COVERAGE=yes @@ -74,6 +81,8 @@ matrix: - postgresql addons: postgresql: "9.5" + before_script: + - bash ./tests/travis/create-database.sh - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.6 COVERAGE=yes @@ -81,6 +90,8 @@ matrix: - postgresql addons: postgresql: "9.6" + before_script: + - bash ./tests/travis/create-database.sh - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes @@ -91,6 +102,7 @@ matrix: postgresql: "9.6" before_script: - bash ./tests/travis/install-postgres-10.sh + - bash ./tests/travis/create-database.sh - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=11.0 COVERAGE=yes @@ -99,6 +111,7 @@ matrix: - docker before_script: - bash ./tests/travis/install-postgres-11.sh + - bash ./tests/travis/create-database.sh - stage: Test php: "7.4snapshot" env: DB=pgsql POSTGRESQL_VERSION=11.0 @@ -107,3 +120,4 @@ matrix: - docker before_script: - bash ./tests/travis/install-postgres-11.sh + - bash ./tests/travis/create-database.sh diff --git a/README.md b/README.md index 0712d0f..b33eb8b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ php composer.phar require umbrellio/laravel-pg-extensions - [Extended `Schema::create()`](#extended-table-creation) - [Extended `Schema` with GIST/GIN indexes](#create-gist/gin-indexes) + - [Extended `Schema` with USING](#extended-schema-using) - [Working with unique indexes](#extended-unique-indexes-creation) - [Working with partitions](#partitions) - [Check existing index before manipulation](#check-existing-index) @@ -30,6 +31,24 @@ Schema::create('table', function (Blueprint $table) { }); ``` +### Extended Schema USING + +Example: +```php +Schema::create('table', function (Blueprint $table) { + $table->integer('number'); +}); + +//modifications with data... + +Schema::table('table', function (Blueprint $table) { + $table + ->string('number') + ->using("('[' || number || ']')::character varyiing") + ->change(); +}); +``` + ### Create gist/gin indexes ```php diff --git a/ecs.yml b/ecs.yml index dc913e1..5028a93 100644 --- a/ecs.yml +++ b/ecs.yml @@ -10,7 +10,6 @@ parameters: cache_directory: .ecs_cache exclude_files: - vendor/* - - src/Schema/Drivers/Traits/* skip: Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff: diff --git a/phpunit.travis.xml b/phpunit.travis.xml index a83988c..2418e78 100644 --- a/phpunit.travis.xml +++ b/phpunit.travis.xml @@ -5,8 +5,9 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1dc3aef..ba1eceb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,19 @@ - + stopOnFailure="true"> + + + + + + + + ./src diff --git a/src/Schema/Grammars/PostgresGrammar.php b/src/Schema/Grammars/PostgresGrammar.php index 3a321ed..088a14e 100644 --- a/src/Schema/Grammars/PostgresGrammar.php +++ b/src/Schema/Grammars/PostgresGrammar.php @@ -4,7 +4,6 @@ namespace Umbrellio\Postgres\Schema\Grammars; -use Illuminate\Database\Connection; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Grammars\PostgresGrammar as BasePostgresGrammar; use Illuminate\Support\Fluent; @@ -29,11 +28,6 @@ public function compileCreate(Blueprint $blueprint, Fluent $command): string ); } - public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) - { - return parent::compileChange($blueprint, $command, $connection); // TODO: Change the autogenerated stub - } - public function compileAttachPartition(Blueprint $blueprint, Fluent $command): string { return AttachPartitionCompiler::compile($this, $blueprint, $command); diff --git a/src/Schema/Listeners/SchemaAlterTableChangeColumnListener.php b/src/Schema/Listeners/SchemaAlterTableChangeColumnListener.php index bce9820..d65fed9 100644 --- a/src/Schema/Listeners/SchemaAlterTableChangeColumnListener.php +++ b/src/Schema/Listeners/SchemaAlterTableChangeColumnListener.php @@ -15,12 +15,12 @@ public function onSchemaAlterTableChangeColumn(SchemaAlterTableChangeColumnEvent { $event->preventDefault(); - $sql = array_unique(array_merge($event->getSql(), $this->getAlterTableChangeColumnSQL( - $event->getPlatform(), - $event->getTableDiff(), - $event->getColumnDiff() - ))); - - $event->addSql($sql); + $event->addSql( + $this->getAlterTableChangeColumnSQL( + $event->getPlatform(), + $event->getTableDiff(), + $event->getColumnDiff() + ) + ); } } diff --git a/src/Schema/Traits/AlterTableChangeColumnTrait.php b/src/Schema/Traits/AlterTableChangeColumnTrait.php index 475e734..fde94e5 100644 --- a/src/Schema/Traits/AlterTableChangeColumnTrait.php +++ b/src/Schema/Traits/AlterTableChangeColumnTrait.php @@ -110,27 +110,20 @@ private function compileAlterColumnSequence( string $oldColumnName, &$sql ): void { - if ($columnDiff->hasChanged('autoincrement')) { - if ($column->getAutoincrement()) { - $seqName = $platform->getIdentitySequenceName($diff->name, $oldColumnName); - - $sql[] = sprintf('CREATE SEQUENCE %s', $seqName); - $sql[] = sprintf( - "SELECT setval('%s', (SELECT MAX(%s) FROM %s))", - $seqName, - $oldColumnName, - $quoteName - ); - $sql[] = sprintf( - "ALTER TABLE %s ALTER %s SET DEFAULT nextval('%s')", - $quoteName, - $oldColumnName, - $seqName - ); - } else { - $sql[] = sprintf('ALTER TABLE %s ALTER %s DROP DEFAULT', $quoteName, $oldColumnName); - } + if (!$columnDiff->hasChanged('autoincrement')) { + return; + } + + if (!$column->getAutoincrement()) { + $sql[] = sprintf('ALTER TABLE %s ALTER %s DROP DEFAULT', $quoteName, $oldColumnName); + return; } + + $seqName = $platform->getIdentitySequenceName($diff->name, $oldColumnName); + + $sql[] = sprintf('CREATE SEQUENCE %s', $seqName); + $sql[] = sprintf("SELECT setval('%s', (SELECT MAX(%s) FROM %s))", $seqName, $oldColumnName, $quoteName); + $sql[] = sprintf("ALTER TABLE %s ALTER %s SET DEFAULT nextval('%s')", $quoteName, $oldColumnName, $seqName); } private function compileAlterColumnType( @@ -157,7 +150,6 @@ private function compileAlterColumnType( $typeName = $type->getSQLDeclaration($columnDefinition, $platform); - $using = ''; if ($columnDiff->hasChanged('type')) { $using = sprintf('USING %s::%s', $oldColumnName, $typeName); @@ -171,7 +163,7 @@ private function compileAlterColumnType( $quoteName, $oldColumnName, $typeName, - $using + $using ?? '' )); } } diff --git a/tests.sh b/tests.sh new file mode 100755 index 0000000..a881d1d --- /dev/null +++ b/tests.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +#psql -c 'CREATE DATABASE testing;' -U postgres +php -d pcov.directory='.' vendor/bin/phpunit --coverage-html build diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index bb157ac..d767da7 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -5,15 +5,27 @@ namespace Umbrellio\Postgres\Tests; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Facade; abstract class FunctionalTestCase extends TestCase { + protected function setUp(): void + { + if (!$this->app) { + putenv('APP_ENV=testing'); + $this->app = $this->createApplication(); + } + + parent::setUp(); + + Facade::clearResolvedInstances(); + } protected function getEnvironmentSetUp($app) { parent::getEnvironmentSetUp($app); $app['config']->set('database.default', 'main'); - $this->setConnectionConfig($app, 'main', TestUtil::getParamsForMainConnection()); + $this->setConnectionConfig($app, 'main', $this->getParamsForConnection()); } protected function assertCommentOnColumn(string $table, string $column, ?string $expected = null): void @@ -51,6 +63,28 @@ private function setConnectionConfig($app, $name, $params): void ]); } + private function getParamsForConnection(): array + { + $connectionParams = [ + 'driver' => $GLOBALS['db_type'] ?? 'pdo_pgsql', + 'user' => $GLOBALS['db_username'], + 'password' => $GLOBALS['db_password'], + 'host' => $GLOBALS['db_host'], + 'dbname' => $GLOBALS['db_name'], + 'port' => $GLOBALS['db_port'], + ]; + + if (isset($GLOBALS['db_server'])) { + $connectionParams['server'] = $GLOBALS['db_server']; + } + + if (isset($GLOBALS['db_unix_socket'])) { + $connectionParams['unix_socket'] = $GLOBALS['db_unix_socket']; + } + + return $connectionParams; + } + private function getCommentListing(string $table, string $column) { $definition = DB::selectOne( diff --git a/tests/TestCase.php b/tests/TestCase.php index d851b9e..2467719 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,26 +4,11 @@ namespace Umbrellio\Postgres\Tests; -use Illuminate\Support\Facades\Facade; use Orchestra\Testbench\TestCase as BaseTestCase; use Umbrellio\Postgres\UmbrellioPostgresProvider; abstract class TestCase extends BaseTestCase { - protected function setUp(): void - { - TestUtil::createDatabase(); - - if (!$this->app) { - putenv('APP_ENV=testing'); - $this->app = $this->createApplication(); - } - - parent::setUp(); - - Facade::clearResolvedInstances(); - } - protected function getPackageProviders($app) { return [UmbrellioPostgresProvider::class]; diff --git a/tests/TestUtil.php b/tests/TestUtil.php deleted file mode 100644 index 737c7da..0000000 --- a/tests/TestUtil.php +++ /dev/null @@ -1,109 +0,0 @@ - $GLOBALS['db_type'] ?? 'pdo_pgsql', - 'user' => $GLOBALS['db_username'], - 'password' => $GLOBALS['db_password'], - 'host' => $GLOBALS['db_host'], - 'dbname' => $GLOBALS['db_name'], - 'port' => $GLOBALS['db_port'], - ]; - - if (isset($GLOBALS['db_server'])) { - $connectionParams['server'] = $GLOBALS['db_server']; - } - - if (isset($GLOBALS['db_unix_socket'])) { - $connectionParams['unix_socket'] = $GLOBALS['db_unix_socket']; - } - - return $connectionParams; - } - - private static function hasRequiredConnectionParams(): bool - { - return isset( - $GLOBALS['db_type'], - $GLOBALS['db_username'], - $GLOBALS['db_password'], - $GLOBALS['db_host'], - $GLOBALS['db_name'], - $GLOBALS['db_port'] - ); - } - - private static function initializeDatabase(): void - { - $realConn = static::createDoctrineConnection(self::getParamsForMainConnection()); - - $platform = $realConn->getDatabasePlatform(); - - if ($platform->supportsCreateDropDatabase()) { - $realConn->getDatabase(); - $realConn->close(); - } else { - $sm = $realConn->getSchemaManager(); - $schema = $sm->createSchema(); - $stmts = $schema->toDropSql($realConn->getDatabasePlatform()); - - foreach ($stmts as $stmt) { - $realConn->exec($stmt); - } - } - } - - private static function addDbEventSubscribers(Connection $conn): void - { - if (!isset($GLOBALS['db_event_subscribers'])) { - return; - } - - $evm = $conn->getEventManager(); - foreach (explode(',', $GLOBALS['db_event_subscribers']) as $subscriberClass) { - $subscriberInstance = new $subscriberClass(); - $evm->addEventSubscriber($subscriberInstance); - } - } - - private static function getDoctrineConnection(string $name = null): Connection - { - return DB::connection($name)->getDoctrineConnection(); - } - - private static function createDoctrineConnection($params): Connection - { - return DriverManager::getConnection($params); - } -} diff --git a/tests/travis/create-database.sh b/tests/travis/create-database.sh new file mode 100755 index 0000000..d9d07c1 --- /dev/null +++ b/tests/travis/create-database.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +psql -c 'CREATE DATABASE testing;' -U postgres From 1171a222277a8f065107f3f08bb9d2a93516426b Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 14:04:08 +0300 Subject: [PATCH 19/38] travis create database fix --- .travis.yml | 15 +++++++-------- tests/travis/create-database.sh | 3 --- 2 files changed, 7 insertions(+), 11 deletions(-) delete mode 100755 tests/travis/create-database.sh diff --git a/.travis.yml b/.travis.yml index c767de7..c5ac0ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,8 +45,7 @@ matrix: services: - docker before_script: - - bash ./tests/travis/install-postgres-11.sh - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.2 COVERAGE=yes @@ -55,7 +54,7 @@ matrix: addons: postgresql: "9.2" before_script: - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes @@ -73,7 +72,7 @@ matrix: addons: postgresql: "9.4" before_script: - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.5 COVERAGE=yes @@ -82,7 +81,7 @@ matrix: addons: postgresql: "9.5" before_script: - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.6 COVERAGE=yes @@ -91,7 +90,7 @@ matrix: addons: postgresql: "9.6" before_script: - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes @@ -111,7 +110,7 @@ matrix: - docker before_script: - bash ./tests/travis/install-postgres-11.sh - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.4snapshot" env: DB=pgsql POSTGRESQL_VERSION=11.0 @@ -120,4 +119,4 @@ matrix: - docker before_script: - bash ./tests/travis/install-postgres-11.sh - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres diff --git a/tests/travis/create-database.sh b/tests/travis/create-database.sh deleted file mode 100755 index d9d07c1..0000000 --- a/tests/travis/create-database.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -psql -c 'CREATE DATABASE testing;' -U postgres From fc0373631ea9cb49b9d6b8267204f9874cab43bc Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 14:13:52 +0300 Subject: [PATCH 20/38] some fixes in travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c5ac0ed..0152b8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,7 +63,7 @@ matrix: addons: postgresql: "9.3" before_script: - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.4 COVERAGE=yes @@ -101,7 +101,7 @@ matrix: postgresql: "9.6" before_script: - bash ./tests/travis/install-postgres-10.sh - - bash ./tests/travis/create-database.sh + - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=11.0 COVERAGE=yes From 090d48971ae51b4267ff54a9735333716ebd8e61 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 14:22:55 +0300 Subject: [PATCH 21/38] fix travis 11 postgres php 7.3 --- .travis.yml | 2 +- tests/travis/install-postgres-11.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0152b8b..a5f7caa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,7 +110,7 @@ matrix: - docker before_script: - bash ./tests/travis/install-postgres-11.sh - - psql -c 'CREATE DATABASE testing;' -U postgres + - psql -c 'CREATE DATABASE testing;' -U postgres -p 5432:5432 - stage: Test php: "7.4snapshot" env: DB=pgsql POSTGRESQL_VERSION=11.0 diff --git a/tests/travis/install-postgres-11.sh b/tests/travis/install-postgres-11.sh index 2ef1aab..375a897 100755 --- a/tests/travis/install-postgres-11.sh +++ b/tests/travis/install-postgres-11.sh @@ -10,3 +10,4 @@ sudo docker run -d --name postgres11 -p 5432:5432 postgres:11.1 sudo docker exec -i postgres11 bash <<< 'until pg_isready -U postgres > /dev/null 2>&1 ; do sleep 1; done' echo "Postgres 11 ready" +sudo service postgresql restart From 3f3890d4b029421b7b12abb823fc1c0d8d97d5b0 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 14:31:30 +0300 Subject: [PATCH 22/38] fix travis --- tests/travis/install-postgres-11.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/travis/install-postgres-11.sh b/tests/travis/install-postgres-11.sh index 375a897..2ef1aab 100755 --- a/tests/travis/install-postgres-11.sh +++ b/tests/travis/install-postgres-11.sh @@ -10,4 +10,3 @@ sudo docker run -d --name postgres11 -p 5432:5432 postgres:11.1 sudo docker exec -i postgres11 bash <<< 'until pg_isready -U postgres > /dev/null 2>&1 ; do sleep 1; done' echo "Postgres 11 ready" -sudo service postgresql restart From 51432ce35685238894e106efb79b1e006b31b32b Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 17:31:01 +0300 Subject: [PATCH 23/38] fix travis --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a5f7caa..b108dc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -108,15 +108,17 @@ matrix: sudo: required services: - docker + addons: + postgresql: "9.6" before_script: - bash ./tests/travis/install-postgres-11.sh - - psql -c 'CREATE DATABASE testing;' -U postgres -p 5432:5432 - stage: Test php: "7.4snapshot" env: DB=pgsql POSTGRESQL_VERSION=11.0 sudo: required services: - docker + addons: + postgresql: "9.6" before_script: - bash ./tests/travis/install-postgres-11.sh - - psql -c 'CREATE DATABASE testing;' -U postgres From 6366d6c5b620bb96ffe36b1a6005099c87414f23 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 17:36:44 +0300 Subject: [PATCH 24/38] add unit test for indexes --- tests/Unit/Schema/Blueprint/IndexTest.php | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/Unit/Schema/Blueprint/IndexTest.php diff --git a/tests/Unit/Schema/Blueprint/IndexTest.php b/tests/Unit/Schema/Blueprint/IndexTest.php new file mode 100644 index 0000000..0df52b3 --- /dev/null +++ b/tests/Unit/Schema/Blueprint/IndexTest.php @@ -0,0 +1,42 @@ +blueprint = Mockery::mock(Blueprint::class) + ->makePartial() + ->shouldAllowMockingProtectedMethods(); + } + + /** @test */ + public function ginIndex() + { + $this->blueprint + ->shouldReceive('indexCommand') + ->with('gin', 'col', 'myName'); + $this->blueprint->gin('col', 'myName'); + } + + /** @test */ + public function gistIndex() + { + $this->blueprint + ->shouldReceive('indexCommand') + ->with('gist', 'col', 'myName'); + $this->blueprint->gist('col', 'myName'); + } +} From 99d78d7d762827373c0879ff012c79b21e707c5c Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 17:44:07 +0300 Subject: [PATCH 25/38] some travis fixes --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b108dc3..898870e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,10 @@ install: script: - vendor/bin/ecs check --config=ecs.yml . + - | + if [ "v$POSTGRESQL_VERSION" == "v11.0"]; then + psql -c 'CREATE DATABASE testing;' -U postgres + fi - | if [ "x$COVERAGE" == "xyes" ]; then ./vendor/bin/phpunit --configuration phpunit.travis.xml --coverage-clover build/logs/clover.xml @@ -44,8 +48,6 @@ matrix: sudo: required services: - docker - before_script: - - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.2 COVERAGE=yes From 170ddce9446eaa0802df0f43bcdc2d74f0044dca Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 17:52:08 +0300 Subject: [PATCH 26/38] add travis fix --- .travis.yml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 898870e..4170a6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,10 +20,7 @@ install: script: - vendor/bin/ecs check --config=ecs.yml . - - | - if [ "v$POSTGRESQL_VERSION" == "v11.0"]; then - psql -c 'CREATE DATABASE testing;' -U postgres - fi + - psql -c 'CREATE DATABASE testing;' -U postgres - | if [ "x$COVERAGE" == "xyes" ]; then ./vendor/bin/phpunit --configuration phpunit.travis.xml --coverage-clover build/logs/clover.xml @@ -55,8 +52,6 @@ matrix: - postgresql addons: postgresql: "9.2" - before_script: - - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes @@ -64,8 +59,6 @@ matrix: - postgresql addons: postgresql: "9.3" - before_script: - - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.4 COVERAGE=yes @@ -73,8 +66,6 @@ matrix: - postgresql addons: postgresql: "9.4" - before_script: - - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.5 COVERAGE=yes @@ -91,8 +82,6 @@ matrix: - postgresql addons: postgresql: "9.6" - before_script: - - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes @@ -103,7 +92,6 @@ matrix: postgresql: "9.6" before_script: - bash ./tests/travis/install-postgres-10.sh - - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=11.0 COVERAGE=yes @@ -120,7 +108,5 @@ matrix: sudo: required services: - docker - addons: - postgresql: "9.6" before_script: - bash ./tests/travis/install-postgres-11.sh From 12596100e82457dfb436e87adaf32c95c0589c50 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 20:56:16 +0300 Subject: [PATCH 27/38] test travis --- phpunit.travis.xml | 7 +++---- phpunit.xml.dist | 3 +-- tests.sh | 2 +- tests/FunctionalTestCase.php | 4 ++-- tests/travis/install-postgres-11.sh | 10 ++++++++++ 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/phpunit.travis.xml b/phpunit.travis.xml index 2418e78..22ab00f 100644 --- a/phpunit.travis.xml +++ b/phpunit.travis.xml @@ -2,8 +2,8 @@ @@ -12,7 +12,7 @@ - + @@ -25,7 +25,6 @@ ./src ./src/.meta.php - ./src/Commands diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ba1eceb..1e2c087 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,7 +11,7 @@ - + @@ -19,7 +19,6 @@ ./src ./src/.meta.php - ./src/Commands diff --git a/tests.sh b/tests.sh index a881d1d..47337bc 100755 --- a/tests.sh +++ b/tests.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash -#psql -c 'CREATE DATABASE testing;' -U postgres +psql postgres -U user -tc "SELECT 1 FROM pg_database WHERE datname = 'testing'" | grep -q 1 || psql postgres -U user -c "CREATE DATABASE testing" php -d pcov.directory='.' vendor/bin/phpunit --coverage-html build diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index d767da7..ed31265 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -54,7 +54,7 @@ private function setConnectionConfig($app, $name, $params): void 'driver' => 'pgsql', 'host' => $params['host'], 'port' => (int) $params['port'], - 'database' => $params['dbname'], + 'database' => $params['database'], 'username' => $params['user'], 'password' => $params['password'], 'charset' => 'utf8', @@ -70,7 +70,7 @@ private function getParamsForConnection(): array 'user' => $GLOBALS['db_username'], 'password' => $GLOBALS['db_password'], 'host' => $GLOBALS['db_host'], - 'dbname' => $GLOBALS['db_name'], + 'database' => $GLOBALS['db_database'], 'port' => $GLOBALS['db_port'], ]; diff --git a/tests/travis/install-postgres-11.sh b/tests/travis/install-postgres-11.sh index 2ef1aab..e03b27f 100755 --- a/tests/travis/install-postgres-11.sh +++ b/tests/travis/install-postgres-11.sh @@ -10,3 +10,13 @@ sudo docker run -d --name postgres11 -p 5432:5432 postgres:11.1 sudo docker exec -i postgres11 bash <<< 'until pg_isready -U postgres > /dev/null 2>&1 ; do sleep 1; done' echo "Postgres 11 ready" + +ps ax | grep postgres + +sudo netstat -plunt | grep postgres + +sudo service postgresql start || true + +sudo service postgres11 start + +sudo service postgres start From ec70595e3f9c8cf7014689b52f479369e470cdef Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 20:57:33 +0300 Subject: [PATCH 28/38] fix --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4170a6b..d12a9d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,8 +73,6 @@ matrix: - postgresql addons: postgresql: "9.5" - before_script: - - psql -c 'CREATE DATABASE testing;' -U postgres - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=9.6 COVERAGE=yes From dae17c91438dca0081e0c00aed85bdaf3bc33aa7 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 21:05:07 +0300 Subject: [PATCH 29/38] travis --- .travis.yml | 2 ++ tests/travis/install-postgres-11.sh | 10 +--------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index d12a9d0..3cc2c09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -96,6 +96,7 @@ matrix: sudo: required services: - docker + - postgresql addons: postgresql: "9.6" before_script: @@ -106,5 +107,6 @@ matrix: sudo: required services: - docker + - postgresql before_script: - bash ./tests/travis/install-postgres-11.sh diff --git a/tests/travis/install-postgres-11.sh b/tests/travis/install-postgres-11.sh index e03b27f..d338f36 100755 --- a/tests/travis/install-postgres-11.sh +++ b/tests/travis/install-postgres-11.sh @@ -11,12 +11,4 @@ sudo docker exec -i postgres11 bash <<< 'until pg_isready -U postgres > /dev/nul echo "Postgres 11 ready" -ps ax | grep postgres - -sudo netstat -plunt | grep postgres - -sudo service postgresql start || true - -sudo service postgres11 start - -sudo service postgres start +#sudo service postgres restart From e29515790b7d3599098e3a6b165b784915cdab26 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 21:14:41 +0300 Subject: [PATCH 30/38] fix travis --- tests/travis/install-postgres-11.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/travis/install-postgres-11.sh b/tests/travis/install-postgres-11.sh index d338f36..b01bd02 100755 --- a/tests/travis/install-postgres-11.sh +++ b/tests/travis/install-postgres-11.sh @@ -11,4 +11,6 @@ sudo docker exec -i postgres11 bash <<< 'until pg_isready -U postgres > /dev/nul echo "Postgres 11 ready" -#sudo service postgres restart +rm /usr/local/var/postgres/postmaster.pid + +pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start From 3752a4462f3018a0da3794b86a4616a36af44341 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 21:15:40 +0300 Subject: [PATCH 31/38] fix travis (maybe) --- .travis.yml | 102 ++++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3cc2c09..a7b1932 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,57 +39,57 @@ matrix: allow_failures: - php: "7.4snapshot" include: - - stage: Test - php: "7.2" - env: DB=pgsql POSTGRESQL_VERSION=11.0 - sudo: required - services: - - docker - - stage: Test - php: "7.3" - env: DB=pgsql POSTGRESQL_VERSION=9.2 COVERAGE=yes - services: - - postgresql - addons: - postgresql: "9.2" - - stage: Test - php: "7.3" - env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes - services: - - postgresql - addons: - postgresql: "9.3" - - stage: Test - php: "7.3" - env: DB=pgsql POSTGRESQL_VERSION=9.4 COVERAGE=yes - services: - - postgresql - addons: - postgresql: "9.4" - - stage: Test - php: "7.3" - env: DB=pgsql POSTGRESQL_VERSION=9.5 COVERAGE=yes - services: - - postgresql - addons: - postgresql: "9.5" - - stage: Test - php: "7.3" - env: DB=pgsql POSTGRESQL_VERSION=9.6 COVERAGE=yes - services: - - postgresql - addons: - postgresql: "9.6" - - stage: Test - php: "7.3" - env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes - sudo: required - services: - - postgresql - addons: - postgresql: "9.6" - before_script: - - bash ./tests/travis/install-postgres-10.sh +# - stage: Test +# php: "7.2" +# env: DB=pgsql POSTGRESQL_VERSION=11.0 +# sudo: required +# services: +# - docker +# - stage: Test +# php: "7.3" +# env: DB=pgsql POSTGRESQL_VERSION=9.2 COVERAGE=yes +# services: +# - postgresql +# addons: +# postgresql: "9.2" +# - stage: Test +# php: "7.3" +# env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes +# services: +# - postgresql +# addons: +# postgresql: "9.3" +# - stage: Test +# php: "7.3" +# env: DB=pgsql POSTGRESQL_VERSION=9.4 COVERAGE=yes +# services: +# - postgresql +# addons: +# postgresql: "9.4" +# - stage: Test +# php: "7.3" +# env: DB=pgsql POSTGRESQL_VERSION=9.5 COVERAGE=yes +# services: +# - postgresql +# addons: +# postgresql: "9.5" +# - stage: Test +# php: "7.3" +# env: DB=pgsql POSTGRESQL_VERSION=9.6 COVERAGE=yes +# services: +# - postgresql +# addons: +# postgresql: "9.6" +# - stage: Test +# php: "7.3" +# env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes +# sudo: required +# services: +# - postgresql +# addons: +# postgresql: "9.6" +# before_script: +# - bash ./tests/travis/install-postgres-10.sh - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=11.0 COVERAGE=yes From 2a34c44eacbe2914060d28b7b7d9fd89e20f534a Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 21:18:21 +0300 Subject: [PATCH 32/38] fix travis --- tests/travis/install-postgres-11.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/travis/install-postgres-11.sh b/tests/travis/install-postgres-11.sh index b01bd02..fa7ef80 100755 --- a/tests/travis/install-postgres-11.sh +++ b/tests/travis/install-postgres-11.sh @@ -11,6 +11,4 @@ sudo docker exec -i postgres11 bash <<< 'until pg_isready -U postgres > /dev/nul echo "Postgres 11 ready" -rm /usr/local/var/postgres/postmaster.pid - pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start From 02e61cbfda14f93e17175aeeb1a9793d5c06531e Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 21:29:52 +0300 Subject: [PATCH 33/38] testing travis --- .travis.yml | 57 ++++++++++++++++------------- tests/travis/install-postgres-11.sh | 2 - 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index a7b1932..257ac77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,12 @@ install: script: - vendor/bin/ecs check --config=ecs.yml . - - psql -c 'CREATE DATABASE testing;' -U postgres + - | + if [ "x$PG11" == "xyes" ]; then + sudo docker exec -i postgres11 bash <<< 'psql -c 'CREATE DATABASE testing;' -U postgres' + else + psql -c 'CREATE DATABASE testing;' -U postgres + fi - | if [ "x$COVERAGE" == "xyes" ]; then ./vendor/bin/phpunit --configuration phpunit.travis.xml --coverage-clover build/logs/clover.xml @@ -39,19 +44,19 @@ matrix: allow_failures: - php: "7.4snapshot" include: -# - stage: Test -# php: "7.2" -# env: DB=pgsql POSTGRESQL_VERSION=11.0 -# sudo: required -# services: -# - docker -# - stage: Test -# php: "7.3" -# env: DB=pgsql POSTGRESQL_VERSION=9.2 COVERAGE=yes -# services: -# - postgresql -# addons: -# postgresql: "9.2" + - stage: Test + php: "7.2" + env: DB=pgsql POSTGRESQL_VERSION=11.0 + sudo: required + services: + - docker + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.2 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.2" # - stage: Test # php: "7.3" # env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes @@ -80,19 +85,19 @@ matrix: # - postgresql # addons: # postgresql: "9.6" -# - stage: Test -# php: "7.3" -# env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes -# sudo: required -# services: -# - postgresql -# addons: -# postgresql: "9.6" -# before_script: -# - bash ./tests/travis/install-postgres-10.sh - stage: Test php: "7.3" - env: DB=pgsql POSTGRESQL_VERSION=11.0 COVERAGE=yes + env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes + sudo: required + services: + - postgresql + addons: + postgresql: "9.6" + before_script: + - bash ./tests/travis/install-postgres-10.sh + - stage: Test + php: "7.3" + env: DB=pgsql PG11=yes POSTGRESQL_VERSION=11.0 COVERAGE=yes sudo: required services: - docker @@ -103,7 +108,7 @@ matrix: - bash ./tests/travis/install-postgres-11.sh - stage: Test php: "7.4snapshot" - env: DB=pgsql POSTGRESQL_VERSION=11.0 + env: DB=pgsql PG11=yes POSTGRESQL_VERSION=11.0 sudo: required services: - docker diff --git a/tests/travis/install-postgres-11.sh b/tests/travis/install-postgres-11.sh index fa7ef80..2ef1aab 100755 --- a/tests/travis/install-postgres-11.sh +++ b/tests/travis/install-postgres-11.sh @@ -10,5 +10,3 @@ sudo docker run -d --name postgres11 -p 5432:5432 postgres:11.1 sudo docker exec -i postgres11 bash <<< 'until pg_isready -U postgres > /dev/null 2>&1 ; do sleep 1; done' echo "Postgres 11 ready" - -pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start From 04efe574efe7b1e67d59bec86194cca0552aa436 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 21:36:31 +0300 Subject: [PATCH 34/38] docker travis test --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 257ac77..6d374df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,8 @@ install: script: - vendor/bin/ecs check --config=ecs.yml . - | - if [ "x$PG11" == "xyes" ]; then - sudo docker exec -i postgres11 bash <<< 'psql -c 'CREATE DATABASE testing;' -U postgres' + if [ "x$DOCKER_POSTGRES" == "xyes" ]; then + sudo docker exec -ti postgres11 psql postgres -U postgres -c "CREATE DATABASE testing" else psql -c 'CREATE DATABASE testing;' -U postgres fi @@ -97,7 +97,7 @@ matrix: - bash ./tests/travis/install-postgres-10.sh - stage: Test php: "7.3" - env: DB=pgsql PG11=yes POSTGRESQL_VERSION=11.0 COVERAGE=yes + env: DB=pgsql DOCKER_POSTGRES=yes POSTGRESQL_VERSION=11.0 COVERAGE=yes sudo: required services: - docker @@ -108,7 +108,7 @@ matrix: - bash ./tests/travis/install-postgres-11.sh - stage: Test php: "7.4snapshot" - env: DB=pgsql PG11=yes POSTGRESQL_VERSION=11.0 + env: DB=pgsql DOCKER_POSTGRES=yes POSTGRESQL_VERSION=11.0 sudo: required services: - docker From f5d1c85b9a83f9b660120b8cdfa91dc61f40b354 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Tue, 23 Jul 2019 23:50:06 +0300 Subject: [PATCH 35/38] add some tests & fix errors --- .travis.yml | 56 +++++++++---------- src/.meta.php | 3 + src/Schema/Blueprint.php | 9 +++ src/Schema/Grammars/PostgresGrammar.php | 6 +- tests.sh | 1 + tests/Functional/Schema/CreateIndexTest.php | 62 +++++++++++++++------ tests/FunctionalTestCase.php | 28 ++++++++++ tests/Unit/Schema/Blueprint/IndexTest.php | 42 -------------- 8 files changed, 119 insertions(+), 88 deletions(-) delete mode 100644 tests/Unit/Schema/Blueprint/IndexTest.php diff --git a/.travis.yml b/.travis.yml index 6d374df..49767e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,34 +57,34 @@ matrix: - postgresql addons: postgresql: "9.2" -# - stage: Test -# php: "7.3" -# env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes -# services: -# - postgresql -# addons: -# postgresql: "9.3" -# - stage: Test -# php: "7.3" -# env: DB=pgsql POSTGRESQL_VERSION=9.4 COVERAGE=yes -# services: -# - postgresql -# addons: -# postgresql: "9.4" -# - stage: Test -# php: "7.3" -# env: DB=pgsql POSTGRESQL_VERSION=9.5 COVERAGE=yes -# services: -# - postgresql -# addons: -# postgresql: "9.5" -# - stage: Test -# php: "7.3" -# env: DB=pgsql POSTGRESQL_VERSION=9.6 COVERAGE=yes -# services: -# - postgresql -# addons: -# postgresql: "9.6" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.3 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.3" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.4 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.4" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.5 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.5" + - stage: Test + php: "7.3" + env: DB=pgsql POSTGRESQL_VERSION=9.6 COVERAGE=yes + services: + - postgresql + addons: + postgresql: "9.6" - stage: Test php: "7.3" env: DB=pgsql POSTGRESQL_VERSION=10.0 COVERAGE=yes diff --git a/src/.meta.php b/src/.meta.php index 6b40c8c..0659ee0 100644 --- a/src/.meta.php +++ b/src/.meta.php @@ -15,6 +15,7 @@ * @method UniqueDefinition uniquePartial($columns, ?string $index = null, ?string $algorithm = null) * @method Fluent gin($columns, ?string $name = null) * @method Fluent gist($columns, ?string $name = null) + * @method ColumnDefinition range(string $name, string $format = 'tsrange') */ class Blueprint { @@ -22,6 +23,8 @@ class Blueprint /** * @method ColumnDefinition using($expression) + * @method ColumnDefinition gist() + * @method ColumnDefinition gin() */ class ColumnDefinition { diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index f2d37ed..d2ce4ea 100644 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -5,6 +5,7 @@ namespace Umbrellio\Postgres\Schema; use Illuminate\Database\Schema\Blueprint as BaseBlueprint; +use Illuminate\Database\Schema\ColumnDefinition; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Fluent; use Umbrellio\Postgres\Schema\Builders\UniquePartialBuilder; @@ -86,6 +87,14 @@ public function hasIndex($index, bool $unique = false): bool return array_key_exists($index, $this->getSchemaManager()->listTableIndexes($this->getTable())); } + /** + * @return Fluent|ColumnDefinition + */ + public function range(string $column, string $format = 'tsrange') + { + return $this->addColumn($format, $column); + } + protected function addFluentIndexes(): void { foreach ($this->columns as $column) { diff --git a/src/Schema/Grammars/PostgresGrammar.php b/src/Schema/Grammars/PostgresGrammar.php index 088a14e..cff3bd0 100644 --- a/src/Schema/Grammars/PostgresGrammar.php +++ b/src/Schema/Grammars/PostgresGrammar.php @@ -60,7 +60,6 @@ public function compileGin(Blueprint $blueprint, Fluent $command): string ); } - public function compileGist(Blueprint $blueprint, Fluent $command): string { return sprintf( @@ -70,4 +69,9 @@ public function compileGist(Blueprint $blueprint, Fluent $command): string $this->columnize($command->columns) ); } + + protected function typeTsrange() + { + return 'tsrange'; + } } diff --git a/tests.sh b/tests.sh index 47337bc..c4ce18c 100755 --- a/tests.sh +++ b/tests.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash psql postgres -U user -tc "SELECT 1 FROM pg_database WHERE datname = 'testing'" | grep -q 1 || psql postgres -U user -c "CREATE DATABASE testing" +composer lint php -d pcov.directory='.' vendor/bin/phpunit --coverage-html build diff --git a/tests/Functional/Schema/CreateIndexTest.php b/tests/Functional/Schema/CreateIndexTest.php index a56baab..9517ec5 100644 --- a/tests/Functional/Schema/CreateIndexTest.php +++ b/tests/Functional/Schema/CreateIndexTest.php @@ -6,7 +6,6 @@ use Generator; use Illuminate\Foundation\Testing\DatabaseTransactions; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; use Umbrellio\Postgres\Tests\FunctionalTestCase; @@ -15,6 +14,46 @@ class CreateIndexTest extends FunctionalTestCase { use DatabaseTransactions; + /** @test */ + public function createGistIndex(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->range('code')->gist(); + }); + + $this->seeIndex('test_table_code_gist'); + + Schema::table('test_table', function (Blueprint $table) { + $table->range('some_id'); + $table->range('some_key'); + $table->gist('some_key', 'specify_gist_key'); + $table->gist('some_id'); + }); + + $this->seeIndex('specify_gist_key'); + $this->seeIndex('test_table_some_id_gist'); + } + + /** @test */ + public function createGinIndex(): void + { + Schema::create('test_table', function (Blueprint $table) { + $table->jsonb('id')->gin(); + }); + + $this->seeIndex('test_table_id_gin'); + + Schema::table('test_table', function (Blueprint $table) { + $table->jsonb('some_id'); + $table->jsonb('some_key'); + $table->gin('some_key', 'specify_gin_key'); + $table->gin('some_id'); + }); + + $this->seeIndex('specify_gin_key'); + $this->seeIndex('test_table_some_id_gin'); + } + /** @test */ public function createIndexIfNotExists(): void { @@ -29,15 +68,13 @@ public function createIndexIfNotExists(): void $this->assertTrue(Schema::hasTable('test_table')); - $indexes = $this->getIndexByName('test_table_name_unique'); - Schema::table('test_table', function (Blueprint $table) { if (!$table->hasIndex(['name'], true)) { $table->unique(['name']); } }); - $this->assertTrue(isset($indexes->indexdef)); + $this->seeIndex('test_table_name_unique'); } /** @@ -58,11 +95,7 @@ public function createPartialUniqueWithNull($expected, $callback): void }); $this->assertTrue(Schema::hasTable('test_table')); - - $indexes = $this->getIndexByName('test_table_name_unique'); - - $this->assertTrue(isset($indexes->indexdef)); - $this->assertRegExp('/' . $this->getDummyIndex() . $expected . '/', $indexes->indexdef); + $this->assertRegExpIndex('test_table_name_unique', '/' . $this->getDummyIndex() . $expected . '/'); } /** @test */ @@ -74,9 +107,9 @@ public function createSpecifyIndex(): void $this->assertTrue(Schema::hasTable('test_table')); - $this->assertRegExp( - '/CREATE INDEX specify_index_name ON (public.)?test_table USING btree \(name\)/', - $this->getIndexByName('specify_index_name')->indexdef + $this->assertRegExpIndex( + 'specify_index_name', + '/CREATE INDEX specify_index_name ON (public.)?test_table USING btree \(name\)/' ); } @@ -163,9 +196,4 @@ protected function getDummyIndex() { return 'CREATE UNIQUE INDEX test_table_name_unique ON (public.)?test_table USING btree \(name\)'; } - - protected function getIndexByName($name) - { - return collect(DB::select("SELECT indexdef FROM pg_indexes WHERE indexname = '{$name}'"))->first(); - } } diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index ed31265..7824929 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -48,6 +48,27 @@ protected function assertDefaultOnColumn(string $table, string $column, ?string $this->assertSame($expected, $defaultValue); } + protected function seeIndex(string $index): void + { + $this->assertNotNull($this->getIndexListing($index)); + } + + protected function assertSameIndex(string $index, string $expectedDef): void + { + $definition = $this->getIndexListing($index); + + $this->seeIndex($index); + $this->assertSame($expectedDef, $definition); + } + + protected function assertRegExpIndex(string $index, string $expectedDef): void + { + $definition = $this->getIndexListing($index); + + $this->seeIndex($index); + $this->assertRegExp($expectedDef, $definition); + } + private function setConnectionConfig($app, $name, $params): void { $app['config']->set('database.connections.' . $name, [ @@ -109,4 +130,11 @@ private function getDefaultListing(string $table, string $column) return $definition ? $definition->column_default : null; } + + private function getIndexListing($index): ?string + { + $definition = DB::selectOne('SELECT indexdef FROM pg_indexes WHERE indexname = ?', [$index]); + + return $definition ? $definition->indexdef : null; + } } diff --git a/tests/Unit/Schema/Blueprint/IndexTest.php b/tests/Unit/Schema/Blueprint/IndexTest.php deleted file mode 100644 index 0df52b3..0000000 --- a/tests/Unit/Schema/Blueprint/IndexTest.php +++ /dev/null @@ -1,42 +0,0 @@ -blueprint = Mockery::mock(Blueprint::class) - ->makePartial() - ->shouldAllowMockingProtectedMethods(); - } - - /** @test */ - public function ginIndex() - { - $this->blueprint - ->shouldReceive('indexCommand') - ->with('gin', 'col', 'myName'); - $this->blueprint->gin('col', 'myName'); - } - - /** @test */ - public function gistIndex() - { - $this->blueprint - ->shouldReceive('indexCommand') - ->with('gist', 'col', 'myName'); - $this->blueprint->gist('col', 'myName'); - } -} From 5ca5c16ef0a6bef01b1f2f3cc6ef695f9ec2c133 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Wed, 24 Jul 2019 01:58:30 +0300 Subject: [PATCH 36/38] add coverage (some tests & fix errors on travis jobs) --- src/.meta.php | 3 +- src/Doctrine/RangeExtension.php | 37 +++++++++ src/Doctrine/Schema/Grammars/RangeGrammar.php | 18 +++++ .../Schema/Grammars/VectorGrammar.php | 18 +++++ src/Doctrine/Schema/RangeBlueprint.php | 19 +++++ src/Doctrine/Schema/VectorBlueprint.php | 19 +++++ src/Doctrine/Types/TsRangeType.php | 41 ++++++++++ src/Doctrine/Types/TsVectorType.php | 41 ++++++++++ src/Doctrine/VectorExtension.php | 37 +++++++++ src/PostgresConnection.php | 9 +-- src/Schema/Blueprint.php | 9 --- src/Schema/Grammars/PostgresGrammar.php | 5 -- .../Traits/AlterTableChangeColumnTrait.php | 18 ++--- src/UmbrellioPostgresProvider.php | 9 +++ tests/Functional/Schema/AddColumnsTest.php | 40 ++++++++++ tests/Functional/Schema/AlterColumnsTest.php | 19 ++++- tests/Functional/Schema/CreateIndexTest.php | 12 +-- tests/Unit/Doctrine/Types/TsRangeTypeTest.php | 75 +++++++++++++++++++ .../Unit/Doctrine/Types/TsVectorTypeTest.php | 75 +++++++++++++++++++ 19 files changed, 463 insertions(+), 41 deletions(-) create mode 100644 src/Doctrine/RangeExtension.php create mode 100644 src/Doctrine/Schema/Grammars/RangeGrammar.php create mode 100644 src/Doctrine/Schema/Grammars/VectorGrammar.php create mode 100644 src/Doctrine/Schema/RangeBlueprint.php create mode 100644 src/Doctrine/Schema/VectorBlueprint.php create mode 100644 src/Doctrine/Types/TsRangeType.php create mode 100644 src/Doctrine/Types/TsVectorType.php create mode 100644 src/Doctrine/VectorExtension.php create mode 100644 tests/Functional/Schema/AddColumnsTest.php create mode 100644 tests/Unit/Doctrine/Types/TsRangeTypeTest.php create mode 100644 tests/Unit/Doctrine/Types/TsVectorTypeTest.php diff --git a/src/.meta.php b/src/.meta.php index 0659ee0..9f6ba6d 100644 --- a/src/.meta.php +++ b/src/.meta.php @@ -15,7 +15,8 @@ * @method UniqueDefinition uniquePartial($columns, ?string $index = null, ?string $algorithm = null) * @method Fluent gin($columns, ?string $name = null) * @method Fluent gist($columns, ?string $name = null) - * @method ColumnDefinition range(string $name, string $format = 'tsrange') + * @method ColumnDefinition tsRange(string $name) + * @method ColumnDefinition tsVector(string $name) */ class Blueprint { diff --git a/src/Doctrine/RangeExtension.php b/src/Doctrine/RangeExtension.php new file mode 100644 index 0000000..0dfffbe --- /dev/null +++ b/src/Doctrine/RangeExtension.php @@ -0,0 +1,37 @@ + RangeBlueprint::class, + PostgresGrammar::class => RangeGrammar::class, + ]; + } + + public static function getName(): string + { + return static::NAME; + } + + public static function getTypes(): array + { + return [ + TsRangeType::TYPE_NAME => TsRangeType::class, + ]; + } +} diff --git a/src/Doctrine/Schema/Grammars/RangeGrammar.php b/src/Doctrine/Schema/Grammars/RangeGrammar.php new file mode 100644 index 0000000..b43ceab --- /dev/null +++ b/src/Doctrine/Schema/Grammars/RangeGrammar.php @@ -0,0 +1,18 @@ +addColumn(TsRangeType::TYPE_NAME, $column); + }; + } +} diff --git a/src/Doctrine/Schema/VectorBlueprint.php b/src/Doctrine/Schema/VectorBlueprint.php new file mode 100644 index 0000000..f3477a4 --- /dev/null +++ b/src/Doctrine/Schema/VectorBlueprint.php @@ -0,0 +1,19 @@ +addColumn(TsVectorType::TYPE_NAME, $column); + }; + } +} diff --git a/src/Doctrine/Types/TsRangeType.php b/src/Doctrine/Types/TsRangeType.php new file mode 100644 index 0000000..d572a45 --- /dev/null +++ b/src/Doctrine/Types/TsRangeType.php @@ -0,0 +1,41 @@ + VectorBlueprint::class, + PostgresGrammar::class => VectorGrammar::class, + ]; + } + + public static function getName(): string + { + return static::NAME; + } + + public static function getTypes(): array + { + return [ + TsVectorType::TYPE_NAME => TsVectorType::class, + ]; + } +} diff --git a/src/PostgresConnection.php b/src/PostgresConnection.php index 2f6845b..eddfacf 100644 --- a/src/PostgresConnection.php +++ b/src/PostgresConnection.php @@ -53,7 +53,9 @@ public function useDefaultPostProcessor(): void public function getDoctrineConnection(): Connection { - return $this->fixDoctrineConnection(parent::getDoctrineConnection()); + $doctrineConnection = parent::getDoctrineConnection(); + $this->overrideDoctrineBehavior($doctrineConnection); + return $doctrineConnection; } protected function getDefaultSchemaGrammar() @@ -61,9 +63,6 @@ protected function getDefaultSchemaGrammar() return $this->withTablePrefix(new PostgresGrammar()); } - /** - * @codeCoverageIgnore - */ private function registerExtensions(): void { collect(self::$extensions)->each(function ($extension) { @@ -75,7 +74,7 @@ private function registerExtensions(): void }); } - private function fixDoctrineConnection(Connection $connection): Connection + private function overrideDoctrineBehavior(Connection $connection): Connection { $eventManager = $connection->getEventManager(); if (!$eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index d2ce4ea..f2d37ed 100644 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -5,7 +5,6 @@ namespace Umbrellio\Postgres\Schema; use Illuminate\Database\Schema\Blueprint as BaseBlueprint; -use Illuminate\Database\Schema\ColumnDefinition; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Fluent; use Umbrellio\Postgres\Schema\Builders\UniquePartialBuilder; @@ -87,14 +86,6 @@ public function hasIndex($index, bool $unique = false): bool return array_key_exists($index, $this->getSchemaManager()->listTableIndexes($this->getTable())); } - /** - * @return Fluent|ColumnDefinition - */ - public function range(string $column, string $format = 'tsrange') - { - return $this->addColumn($format, $column); - } - protected function addFluentIndexes(): void { foreach ($this->columns as $column) { diff --git a/src/Schema/Grammars/PostgresGrammar.php b/src/Schema/Grammars/PostgresGrammar.php index cff3bd0..6236d6e 100644 --- a/src/Schema/Grammars/PostgresGrammar.php +++ b/src/Schema/Grammars/PostgresGrammar.php @@ -69,9 +69,4 @@ public function compileGist(Blueprint $blueprint, Fluent $command): string $this->columnize($command->columns) ); } - - protected function typeTsrange() - { - return 'tsrange'; - } } diff --git a/src/Schema/Traits/AlterTableChangeColumnTrait.php b/src/Schema/Traits/AlterTableChangeColumnTrait.php index fde94e5..245ec9b 100644 --- a/src/Schema/Traits/AlterTableChangeColumnTrait.php +++ b/src/Schema/Traits/AlterTableChangeColumnTrait.php @@ -58,8 +58,8 @@ private function compileAlterColumnComment( string $quoteName, &$sql ): void { - $newComment = $this->getColumnComment($platform, $column); - $oldComment = $this->getOldColumnComment($platform, $columnDiff); + $newComment = $this->getColumnComment($column); + $oldComment = $this->getOldColumnComment($columnDiff); if (($columnDiff->fromColumn !== null && $oldComment !== $newComment) || $columnDiff->hasChanged('comment') @@ -196,19 +196,13 @@ private function quoteName(AbstractPlatform $platform, TableDiff $diff): string return $diff->getName($platform)->getQuotedName($platform); } - private function getOldColumnComment(AbstractPlatform $platform, ColumnDiff $columnDiff): ?string + private function getOldColumnComment(ColumnDiff $columnDiff): ?string { - return $columnDiff->fromColumn ? $this->getColumnComment($platform, $columnDiff->fromColumn) : null; + return $columnDiff->fromColumn ? $this->getColumnComment($columnDiff->fromColumn) : null; } - private function getColumnComment(AbstractPlatform $platform, Column $column): ?string + private function getColumnComment(Column $column): ?string { - $comment = $column->getComment(); - - if ($platform->isCommentedDoctrineType($column->getType())) { - $comment .= $platform->getDoctrineTypeComment($column->getType()); - } - - return $comment; + return $column->getComment(); } } diff --git a/src/UmbrellioPostgresProvider.php b/src/UmbrellioPostgresProvider.php index 2da00c4..1e4f57e 100644 --- a/src/UmbrellioPostgresProvider.php +++ b/src/UmbrellioPostgresProvider.php @@ -7,9 +7,18 @@ use Illuminate\Database\DatabaseManager; use Illuminate\Database\DatabaseServiceProvider; use Umbrellio\Postgres\Connectors\ConnectionFactory; +use Umbrellio\Postgres\Doctrine\RangeExtension; +use Umbrellio\Postgres\Doctrine\VectorExtension; class UmbrellioPostgresProvider extends DatabaseServiceProvider { + public function register() + { + parent::register(); + PostgresConnection::registerExtension(RangeExtension::class); + PostgresConnection::registerExtension(VectorExtension::class); + } + /** * @codeCoverageIgnore */ diff --git a/tests/Functional/Schema/AddColumnsTest.php b/tests/Functional/Schema/AddColumnsTest.php new file mode 100644 index 0000000..f294a41 --- /dev/null +++ b/tests/Functional/Schema/AddColumnsTest.php @@ -0,0 +1,40 @@ +assertSame($expectedFormat, Schema::getColumnType('test_table', 'field_range')); + } + + public function provideRangeTypes(): Generator + { + yield [TsRangeType::TYPE_NAME, function (Blueprint $table, string $column) { + $table->tsRange($column); + }]; + yield ['text', function (Blueprint $table, string $column) { + $table->tsVector($column); + }]; + } +} diff --git a/tests/Functional/Schema/AlterColumnsTest.php b/tests/Functional/Schema/AlterColumnsTest.php index d819c84..9fcb7cf 100644 --- a/tests/Functional/Schema/AlterColumnsTest.php +++ b/tests/Functional/Schema/AlterColumnsTest.php @@ -21,22 +21,25 @@ public function alterTableSetSimpleComment(): void Schema::create('test_table', function (Blueprint $table) { $table->string('code')->default('1'); }); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); Schema::table('test_table', function (Blueprint $table) { $table->string('code')->comment('some comment')->change(); }); $this->assertCommentOnColumn('test_table', 'code', 'some comment'); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); } /** @test */ public function alterTableJsonSetComment(): void { Schema::create('test_table', function (Blueprint $table) { - $table->json('myjson')->comment('(DC2Type:json_array)'); + $table->string('json_field'); }); + $this->assertCommentOnColumn('test_table', 'json_field'); Schema::table('test_table', function (Blueprint $table) { - $table->json('myjson')->comment('(DC2Type:json_array)')->change(); + $table->json('json_field')->comment('(DC2Type:json_array)')->change(); }); - $this->assertCommentOnColumn('test_table', 'myjson', '(DC2Type:json_array)'); + $this->assertCommentOnColumn('test_table', 'json_field', '(DC2Type:json_array)'); } /** @test */ @@ -45,9 +48,12 @@ public function alterTableSetDCComment(): void Schema::create('test_table', function (Blueprint $table) { $table->string('code')->default('1'); }); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); + $this->assertCommentOnColumn('test_table', 'code'); Schema::table('test_table', function (Blueprint $table) { $table->string('code')->comment('(DC2Type:string)')->change(); }); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); $this->assertCommentOnColumn('test_table', 'code', '(DC2Type:string)'); } @@ -72,10 +78,13 @@ public function alterTableChangeSimpleComment(): void Schema::create('test_table', function (Blueprint $table) { $table->integer('number')->comment('(DC2Type:integer)')->default(1); }); + $this->assertDefaultOnColumn('test_table', 'number', '1'); + Schema::table('test_table', function (Blueprint $table) { $table->string('number')->comment('some comment')->change(); }); $this->assertCommentOnColumn('test_table', 'number', 'some comment'); + $this->assertDefaultOnColumn('test_table', 'number', "'1'::character varying"); } /** @test */ @@ -84,6 +93,7 @@ public function alterTableUsingByDefault(): void Schema::create('test_table', function (Blueprint $table) { $table->string('code')->default('1'); }); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); Schema::table('test_table', function (Blueprint $table) { $table->integer('code')->default(null)->change(); }); @@ -100,6 +110,8 @@ public function alterTableUsingWithExpression(): void $table->integer('number')->default('1')->nullable(); }); + $this->assertDefaultOnColumn('test_table', 'number', '1'); + DB::table('test_table')->insert([['id' => 1]]); $this->assertDatabaseHas('test_table', ['id' => 1]); $this->assertSame('integer', Schema::getColumnType('test_table', 'number')); @@ -110,6 +122,7 @@ public function alterTableUsingWithExpression(): void ->change(); }); + $this->assertDefaultOnColumn('test_table', 'number', "'1'::character varying"); $this->assertSame('string', Schema::getColumnType('test_table', 'number')); $this->assertDatabaseHas('test_table', [ 'id' => 1, diff --git a/tests/Functional/Schema/CreateIndexTest.php b/tests/Functional/Schema/CreateIndexTest.php index 9517ec5..64e7e53 100644 --- a/tests/Functional/Schema/CreateIndexTest.php +++ b/tests/Functional/Schema/CreateIndexTest.php @@ -18,14 +18,14 @@ class CreateIndexTest extends FunctionalTestCase public function createGistIndex(): void { Schema::create('test_table', function (Blueprint $table) { - $table->range('code')->gist(); + $table->tsRange('code')->gist(); }); $this->seeIndex('test_table_code_gist'); Schema::table('test_table', function (Blueprint $table) { - $table->range('some_id'); - $table->range('some_key'); + $table->tsRange('some_id'); + $table->tsRange('some_key'); $table->gist('some_key', 'specify_gist_key'); $table->gist('some_id'); }); @@ -38,14 +38,14 @@ public function createGistIndex(): void public function createGinIndex(): void { Schema::create('test_table', function (Blueprint $table) { - $table->jsonb('id')->gin(); + $table->tsVector('id')->gin(); }); $this->seeIndex('test_table_id_gin'); Schema::table('test_table', function (Blueprint $table) { - $table->jsonb('some_id'); - $table->jsonb('some_key'); + $table->tsVector('some_id'); + $table->tsVector('some_key'); $table->gin('some_key', 'specify_gin_key'); $table->gin('some_id'); }); diff --git a/tests/Unit/Doctrine/Types/TsRangeTypeTest.php b/tests/Unit/Doctrine/Types/TsRangeTypeTest.php new file mode 100644 index 0000000..4de405c --- /dev/null +++ b/tests/Unit/Doctrine/Types/TsRangeTypeTest.php @@ -0,0 +1,75 @@ +type = $this + ->getMockBuilder(TsRangeType::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->abstractPlatform = $this->getMockForAbstractClass(AbstractPlatform::class); + } + + /** + * @test + */ + public function getSQLDeclaration(): void + { + $this->assertSame(TsRangeType::TYPE_NAME, $this->type->getSQLDeclaration([], $this->abstractPlatform)); + } + + /** + * @dataProvider providePHPValues + * @test + */ + public function convertToPHPValue($value, $expected): void + { + $this->assertSame($expected, $this->type->convertToDatabaseValue($value, $this->abstractPlatform)); + } + + public function provideDatabaseValues(): Generator + { + yield [null, null]; + yield ['[1352302322,1352302356]', '[1352302322,1352302356]']; + } + + /** + * @dataProvider provideDatabaseValues + * @test + */ + public function convertToDatabaseValue($value, $expected): void + { + $this->assertSame($expected, $this->type->convertToPHPValue($value, $this->abstractPlatform)); + } + + public function providePHPValues(): Generator + { + yield [null, null]; + yield ['[1352302322,1352302356]', '[1352302322,1352302356]']; + } + + /** @test */ + public function getTypeName(): void + { + $this->assertSame(TsRangeType::TYPE_NAME, $this->type->getName()); + } +} diff --git a/tests/Unit/Doctrine/Types/TsVectorTypeTest.php b/tests/Unit/Doctrine/Types/TsVectorTypeTest.php new file mode 100644 index 0000000..9dccd05 --- /dev/null +++ b/tests/Unit/Doctrine/Types/TsVectorTypeTest.php @@ -0,0 +1,75 @@ +type = $this + ->getMockBuilder(TsVectorType::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->abstractPlatform = $this->getMockForAbstractClass(AbstractPlatform::class); + } + + /** + * @test + */ + public function getSQLDeclaration(): void + { + $this->assertSame(TsVectorType::TYPE_NAME, $this->type->getSQLDeclaration([], $this->abstractPlatform)); + } + + /** + * @dataProvider providePHPValues + * @test + */ + public function convertToPHPValue($value, $expected): void + { + $this->assertSame($expected, $this->type->convertToDatabaseValue($value, $this->abstractPlatform)); + } + + public function provideDatabaseValues(): Generator + { + yield [null, null]; + yield ['key:2, key:2,3', 'key:2, key:2,3']; + } + + /** + * @dataProvider provideDatabaseValues + * @test + */ + public function convertToDatabaseValue($value, $expected): void + { + $this->assertSame($expected, $this->type->convertToPHPValue($value, $this->abstractPlatform)); + } + + public function providePHPValues(): Generator + { + yield [null, null]; + yield ['key:2, key:2,3', 'key:2, key:2,3']; + } + + /** @test */ + public function getTypeName(): void + { + $this->assertSame(TsVectorType::TYPE_NAME, $this->type->getName()); + } +} From 70db5fcc9e6a3471cfb5d1779f48bdec05545371 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Wed, 24 Jul 2019 20:43:25 +0300 Subject: [PATCH 37/38] some fixes --- src/Doctrine/RangeExtension.php | 4 +- src/Doctrine/VectorExtension.php | 4 +- src/Extensions/AbstractComponent.php | 3 - src/Extensions/AbstractExtension.php | 3 - tests/Functional/Helpers/ColumnAssertions.php | 72 ++++++++++++ tests/Functional/Helpers/IndexAssertions.php | 41 +++++++ tests/Functional/Helpers/TableAssertions.php | 36 ++++++ tests/Functional/Schema/AddColumnsTest.php | 10 +- tests/Functional/Schema/AlterColumnsTest.php | 30 +++-- tests/Functional/Schema/CreateIndexTest.php | 9 +- tests/Functional/Schema/CreateTableTest.php | 20 ++-- tests/FunctionalTestCase.php | 105 ++---------------- tests/TestCase.php | 2 +- tests/Unit/Doctrine/Types/TsRangeTypeTest.php | 11 +- .../Unit/Doctrine/Types/TsVectorTypeTest.php | 11 +- .../BlueprintAssertions.php} | 29 +++-- tests/Unit/Schema/Blueprint/PartitionTest.php | 15 ++- tests/Unit/Schema/Grammars/GrammarTest.php | 20 +++- 18 files changed, 258 insertions(+), 167 deletions(-) create mode 100644 tests/Functional/Helpers/ColumnAssertions.php create mode 100644 tests/Functional/Helpers/IndexAssertions.php create mode 100644 tests/Functional/Helpers/TableAssertions.php rename tests/Unit/{BlueprintTestCase.php => Helpers/BlueprintAssertions.php} (62%) diff --git a/src/Doctrine/RangeExtension.php b/src/Doctrine/RangeExtension.php index 0dfffbe..9fc6c45 100644 --- a/src/Doctrine/RangeExtension.php +++ b/src/Doctrine/RangeExtension.php @@ -30,8 +30,8 @@ public static function getName(): string public static function getTypes(): array { - return [ + return array_merge(parent::getTypes(), [ TsRangeType::TYPE_NAME => TsRangeType::class, - ]; + ]); } } diff --git a/src/Doctrine/VectorExtension.php b/src/Doctrine/VectorExtension.php index e2dcf22..0107c90 100644 --- a/src/Doctrine/VectorExtension.php +++ b/src/Doctrine/VectorExtension.php @@ -30,8 +30,8 @@ public static function getName(): string public static function getTypes(): array { - return [ + return array_merge(parent::getTypes(), [ TsVectorType::TYPE_NAME => TsVectorType::class, - ]; + ]); } } diff --git a/src/Extensions/AbstractComponent.php b/src/Extensions/AbstractComponent.php index 3eb63a7..b03f5a4 100644 --- a/src/Extensions/AbstractComponent.php +++ b/src/Extensions/AbstractComponent.php @@ -4,9 +4,6 @@ namespace Umbrellio\Postgres\Extensions; -/** - * @codeCoverageIgnore - */ abstract class AbstractComponent { final public function __construct() diff --git a/src/Extensions/AbstractExtension.php b/src/Extensions/AbstractExtension.php index 04acb44..b66b9d1 100644 --- a/src/Extensions/AbstractExtension.php +++ b/src/Extensions/AbstractExtension.php @@ -8,9 +8,6 @@ use Umbrellio\Postgres\Extensions\Exceptions\MacroableMissedException; use Umbrellio\Postgres\Extensions\Exceptions\MixinInvalidException; -/** - * @codeCoverageIgnore - */ abstract class AbstractExtension extends AbstractComponent { abstract public static function getMixins(): array; diff --git a/tests/Functional/Helpers/ColumnAssertions.php b/tests/Functional/Helpers/ColumnAssertions.php new file mode 100644 index 0000000..dc7e68a --- /dev/null +++ b/tests/Functional/Helpers/ColumnAssertions.php @@ -0,0 +1,72 @@ +getCommentListing($table, $column); + + if ($expected === null) { + $this->assertNull($comment); + } + + $this->assertSame($expected, $comment); + } + + protected function assertDefaultOnColumn(string $table, string $column, ?string $expected = null): void + { + $defaultValue = $this->getDefaultListing($table, $column); + + if ($expected === null) { + $this->assertNull($defaultValue); + } + + $this->assertSame($expected, $defaultValue); + } + + protected function assertTypeColumn(string $table, string $column, string $expected): void + { + $this->assertSame($expected, Schema::getColumnType($table, $column)); + } + private function getCommentListing(string $table, string $column) + { + $definition = DB::selectOne( + ' + SELECT pgd.description + FROM pg_catalog.pg_statio_all_tables AS st + INNER JOIN pg_catalog.pg_description pgd ON (pgd.objoid = st.relid) + INNER JOIN information_schema.columns c ON pgd.objsubid = c.ordinal_position + AND c.table_schema = st.schemaname AND c.table_name = st.relname + WHERE c.table_name = ? AND c.column_name = ? + ', + [$table, $column] + ); + + return $definition ? $definition->description : null; + } + + private function getDefaultListing(string $table, string $column) + { + $definition = DB::selectOne( + ' + SELECT column_default + FROM information_schema.columns c + WHERE c.table_name = ? and c.column_name = ? + ', + [$table, $column] + ); + + return $definition ? $definition->column_default : null; + } +} diff --git a/tests/Functional/Helpers/IndexAssertions.php b/tests/Functional/Helpers/IndexAssertions.php new file mode 100644 index 0000000..07d23d0 --- /dev/null +++ b/tests/Functional/Helpers/IndexAssertions.php @@ -0,0 +1,41 @@ +assertNotNull($this->getIndexListing($index)); + } + + protected function assertSameIndex(string $index, string $expectedDef): void + { + $definition = $this->getIndexListing($index); + + $this->seeIndex($index); + $this->assertSame($expectedDef, $definition); + } + + protected function assertRegExpIndex(string $index, string $expectedDef): void + { + $definition = $this->getIndexListing($index); + + $this->seeIndex($index); + $this->assertRegExp($expectedDef, $definition); + } + private function getIndexListing($index): ?string + { + $definition = DB::selectOne('SELECT indexdef FROM pg_indexes WHERE indexname = ?', [$index]); + + return $definition ? $definition->indexdef : null; + } +} diff --git a/tests/Functional/Helpers/TableAssertions.php b/tests/Functional/Helpers/TableAssertions.php new file mode 100644 index 0000000..c3a2c90 --- /dev/null +++ b/tests/Functional/Helpers/TableAssertions.php @@ -0,0 +1,36 @@ +assertSame($this->getTableDefinition($sourceTable), $this->getTableDefinition($destinationTable)); + } + + protected function assertSameTable(array $expectedDef, string $table): void + { + $definition = $this->getTableDefinition($table); + + $this->assertSame($expectedDef, $definition); + } + + protected function seeTable(string $table): void + { + $this->assertTrue(Schema::hasTable($table)); + } + + private function getTableDefinition(string $table): array + { + return Schema::getColumnListing($table); + } +} diff --git a/tests/Functional/Schema/AddColumnsTest.php b/tests/Functional/Schema/AddColumnsTest.php index f294a41..c58b326 100644 --- a/tests/Functional/Schema/AddColumnsTest.php +++ b/tests/Functional/Schema/AddColumnsTest.php @@ -8,29 +8,29 @@ use Generator; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Schema; -use Umbrellio\Postgres\Doctrine\Types\TsRangeType; use Umbrellio\Postgres\Schema\Blueprint; +use Umbrellio\Postgres\Tests\Functional\Helpers\ColumnAssertions; use Umbrellio\Postgres\Tests\FunctionalTestCase; class AddColumnsTest extends FunctionalTestCase { - use DatabaseTransactions; + use DatabaseTransactions, ColumnAssertions; /** * @test * @dataProvider provideRangeTypes */ - public function addColumnRangeFormat(string $expectedFormat, Closure $callback): void + public function addColumnRangeFormat(string $type, Closure $callback): void { Schema::create('test_table', function (Blueprint $table) use ($callback) { $callback($table, 'field_range'); }); - $this->assertSame($expectedFormat, Schema::getColumnType('test_table', 'field_range')); + $this->assertTypeColumn('test_table', 'field_range', $type); } public function provideRangeTypes(): Generator { - yield [TsRangeType::TYPE_NAME, function (Blueprint $table, string $column) { + yield ['tsrange', function (Blueprint $table, string $column) { $table->tsRange($column); }]; yield ['text', function (Blueprint $table, string $column) { diff --git a/tests/Functional/Schema/AlterColumnsTest.php b/tests/Functional/Schema/AlterColumnsTest.php index 9fcb7cf..dcb7deb 100644 --- a/tests/Functional/Schema/AlterColumnsTest.php +++ b/tests/Functional/Schema/AlterColumnsTest.php @@ -9,11 +9,12 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; +use Umbrellio\Postgres\Tests\Functional\Helpers\ColumnAssertions; use Umbrellio\Postgres\Tests\FunctionalTestCase; class AlterColumnsTest extends FunctionalTestCase { - use DatabaseTransactions; + use DatabaseTransactions, ColumnAssertions; /** @test */ public function alterTableSetSimpleComment(): void @@ -21,10 +22,13 @@ public function alterTableSetSimpleComment(): void Schema::create('test_table', function (Blueprint $table) { $table->string('code')->default('1'); }); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); + Schema::table('test_table', function (Blueprint $table) { $table->string('code')->comment('some comment')->change(); }); + $this->assertCommentOnColumn('test_table', 'code', 'some comment'); $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); } @@ -35,10 +39,13 @@ public function alterTableJsonSetComment(): void Schema::create('test_table', function (Blueprint $table) { $table->string('json_field'); }); + $this->assertCommentOnColumn('test_table', 'json_field'); + Schema::table('test_table', function (Blueprint $table) { $table->json('json_field')->comment('(DC2Type:json_array)')->change(); }); + $this->assertCommentOnColumn('test_table', 'json_field', '(DC2Type:json_array)'); } @@ -48,11 +55,14 @@ public function alterTableSetDCComment(): void Schema::create('test_table', function (Blueprint $table) { $table->string('code')->default('1'); }); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); $this->assertCommentOnColumn('test_table', 'code'); + Schema::table('test_table', function (Blueprint $table) { $table->string('code')->comment('(DC2Type:string)')->change(); }); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); $this->assertCommentOnColumn('test_table', 'code', '(DC2Type:string)'); } @@ -63,6 +73,7 @@ public function alterTableDropDCComment(): void Schema::create('test_table', function (Blueprint $table) { $table->integer('number')->comment('(DC2Type:integer)')->default(1); }); + $this->assertCommentOnColumn('test_table', 'number', '(DC2Type:integer)'); Schema::table('test_table', function (Blueprint $table) { @@ -78,11 +89,13 @@ public function alterTableChangeSimpleComment(): void Schema::create('test_table', function (Blueprint $table) { $table->integer('number')->comment('(DC2Type:integer)')->default(1); }); + $this->assertDefaultOnColumn('test_table', 'number', '1'); Schema::table('test_table', function (Blueprint $table) { $table->string('number')->comment('some comment')->change(); }); + $this->assertCommentOnColumn('test_table', 'number', 'some comment'); $this->assertDefaultOnColumn('test_table', 'number', "'1'::character varying"); } @@ -93,12 +106,14 @@ public function alterTableUsingByDefault(): void Schema::create('test_table', function (Blueprint $table) { $table->string('code')->default('1'); }); + $this->assertDefaultOnColumn('test_table', 'code', "'1'::character varying"); + Schema::table('test_table', function (Blueprint $table) { $table->integer('code')->default(null)->change(); }); - $this->assertSame('integer', Schema::getColumnType('test_table', 'code')); + $this->assertTypeColumn('test_table', 'code', 'integer'); $this->assertDefaultOnColumn('test_table', 'code'); } @@ -113,8 +128,9 @@ public function alterTableUsingWithExpression(): void $this->assertDefaultOnColumn('test_table', 'number', '1'); DB::table('test_table')->insert([['id' => 1]]); + $this->assertDatabaseHas('test_table', ['id' => 1]); - $this->assertSame('integer', Schema::getColumnType('test_table', 'number')); + $this->assertTypeColumn('test_table', 'number', 'integer'); Schema::table('test_table', function (Blueprint $table) { $table->string('number') @@ -123,7 +139,7 @@ public function alterTableUsingWithExpression(): void }); $this->assertDefaultOnColumn('test_table', 'number', "'1'::character varying"); - $this->assertSame('string', Schema::getColumnType('test_table', 'number')); + $this->assertTypeColumn('test_table', 'number', 'string'); $this->assertDatabaseHas('test_table', [ 'id' => 1, 'number' => '[1]', @@ -137,14 +153,14 @@ public function alterTableSetDefault(): void $table->integer('code')->nullable(); }); - $this->assertSame('integer', Schema::getColumnType('test_table', 'code')); + $this->assertTypeColumn('test_table', 'code', 'integer'); $this->assertDefaultOnColumn('test_table', 'code'); Schema::table('test_table', function (Blueprint $table) { $table->string('code')->default('test_string')->change(); }); - $this->assertSame('string', Schema::getColumnType('test_table', 'code')); + $this->assertTypeColumn('test_table', 'code', 'string'); $this->assertDefaultOnColumn('test_table', 'code', "'test_string'::character varying"); } @@ -158,7 +174,7 @@ public function alterTableChangeDefault(): void $this->assertDefaultOnColumn('test_table', 'description', "'default1'::character varying"); Schema::table('test_table', function (Blueprint $table) { - $table->text('description', 25)->default('default2')->change(); + $table->text('description')->default('default2')->change(); }); $this->assertDefaultOnColumn('test_table', 'description', "'default2'::text"); diff --git a/tests/Functional/Schema/CreateIndexTest.php b/tests/Functional/Schema/CreateIndexTest.php index 64e7e53..938dff0 100644 --- a/tests/Functional/Schema/CreateIndexTest.php +++ b/tests/Functional/Schema/CreateIndexTest.php @@ -4,15 +4,17 @@ namespace Umbrellio\Postgres\Tests\Functional\Schema; +use Closure; use Generator; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; +use Umbrellio\Postgres\Tests\Functional\Helpers\IndexAssertions; use Umbrellio\Postgres\Tests\FunctionalTestCase; class CreateIndexTest extends FunctionalTestCase { - use DatabaseTransactions; + use DatabaseTransactions, IndexAssertions; /** @test */ public function createGistIndex(): void @@ -81,7 +83,7 @@ public function createIndexIfNotExists(): void * @test * @dataProvider provideIndexes */ - public function createPartialUniqueWithNull($expected, $callback): void + public function createPartialUniqueWithNull(string $expected, Closure $callback): void { Schema::create('test_table', function (Blueprint $table) use ($callback) { $table->increments('id'); @@ -91,6 +93,7 @@ public function createPartialUniqueWithNull($expected, $callback): void $table->boolean('enabled'); $table->integer('icq'); $table->softDeletes(); + $callback($table); }); @@ -192,7 +195,7 @@ function (Blueprint $table) { ]; } - protected function getDummyIndex() + protected function getDummyIndex(): string { return 'CREATE UNIQUE INDEX test_table_name_unique ON (public.)?test_table USING btree \(name\)'; } diff --git a/tests/Functional/Schema/CreateTableTest.php b/tests/Functional/Schema/CreateTableTest.php index 955eee5..ce234a0 100644 --- a/tests/Functional/Schema/CreateTableTest.php +++ b/tests/Functional/Schema/CreateTableTest.php @@ -7,11 +7,12 @@ use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Schema; use Umbrellio\Postgres\Schema\Blueprint; +use Umbrellio\Postgres\Tests\Functional\Helpers\TableAssertions; use Umbrellio\Postgres\Tests\FunctionalTestCase; class CreateTableTest extends FunctionalTestCase { - use DatabaseTransactions; + use DatabaseTransactions, TableAssertions; /** @test */ public function createSimple(): void @@ -21,8 +22,8 @@ public function createSimple(): void $table->string('name'); }); - $this->assertTrue(Schema::hasTable('test_table')); - $this->assertSame(['id', 'name'], Schema::getColumnListing('test_table')); + $this->seeTable('test_table'); + $this->assertSameTable(['id', 'name'], 'test_table'); } /** @test */ @@ -37,10 +38,9 @@ public function createViaLike(): void $table->like('test_table'); }); - $this->assertTrue(Schema::hasTable('test_table')); - $this->assertTrue(Schema::hasTable('test_table2')); - - $this->assertSame(Schema::getColumnListing('test_table'), Schema::getColumnListing('test_table2')); + $this->seeTable('test_table'); + $this->seeTable('test_table2'); + $this->assertCompareTables('test_table', 'test_table2'); } /** @test */ @@ -56,8 +56,8 @@ public function createViaLikeIncludingAll(): void $table->ifNotExists(); }); - $this->assertTrue(Schema::hasTable('test_table')); - $this->assertTrue(Schema::hasTable('test_table2')); - $this->assertSame(Schema::getColumnListing('test_table'), Schema::getColumnListing('test_table2')); + $this->seeTable('test_table'); + $this->seeTable('test_table2'); + $this->assertCompareTables('test_table', 'test_table2'); } } diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index 7824929..ecf2691 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -4,74 +4,25 @@ namespace Umbrellio\Postgres\Tests; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Facade; abstract class FunctionalTestCase extends TestCase { protected function setUp(): void { - if (!$this->app) { - putenv('APP_ENV=testing'); - $this->app = $this->createApplication(); - } - parent::setUp(); Facade::clearResolvedInstances(); } - protected function getEnvironmentSetUp($app) - { - parent::getEnvironmentSetUp($app); - - $app['config']->set('database.default', 'main'); - $this->setConnectionConfig($app, 'main', $this->getParamsForConnection()); - } - - protected function assertCommentOnColumn(string $table, string $column, ?string $expected = null): void - { - $comment = $this->getCommentListing($table, $column); - - if ($expected === null) { - $this->assertNull($comment); - } - $this->assertSame($expected, $comment); - } - - protected function assertDefaultOnColumn(string $table, string $column, ?string $expected = null): void - { - $defaultValue = $this->getDefaultListing($table, $column); - - if ($expected === null) { - $this->assertNull($defaultValue); - } - $this->assertSame($expected, $defaultValue); - } - - protected function seeIndex(string $index): void - { - $this->assertNotNull($this->getIndexListing($index)); - } - - protected function assertSameIndex(string $index, string $expectedDef): void - { - $definition = $this->getIndexListing($index); - - $this->seeIndex($index); - $this->assertSame($expectedDef, $definition); - } - protected function assertRegExpIndex(string $index, string $expectedDef): void + protected function getEnvironmentSetUp($app): void { - $definition = $this->getIndexListing($index); + parent::getEnvironmentSetUp($app); - $this->seeIndex($index); - $this->assertRegExp($expectedDef, $definition); - } + $params = $this->getConnectionParams(); - private function setConnectionConfig($app, $name, $params): void - { - $app['config']->set('database.connections.' . $name, [ + $app['config']->set('database.default', 'main'); + $app['config']->set('database.connections.main', [ 'driver' => 'pgsql', 'host' => $params['host'], 'port' => (int) $params['port'], @@ -84,9 +35,9 @@ private function setConnectionConfig($app, $name, $params): void ]); } - private function getParamsForConnection(): array + private function getConnectionParams(): array { - $connectionParams = [ + return [ 'driver' => $GLOBALS['db_type'] ?? 'pdo_pgsql', 'user' => $GLOBALS['db_username'], 'password' => $GLOBALS['db_password'], @@ -94,47 +45,5 @@ private function getParamsForConnection(): array 'database' => $GLOBALS['db_database'], 'port' => $GLOBALS['db_port'], ]; - - if (isset($GLOBALS['db_server'])) { - $connectionParams['server'] = $GLOBALS['db_server']; - } - - if (isset($GLOBALS['db_unix_socket'])) { - $connectionParams['unix_socket'] = $GLOBALS['db_unix_socket']; - } - - return $connectionParams; - } - - private function getCommentListing(string $table, string $column) - { - $definition = DB::selectOne( - ' - SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st - INNER JOIN pg_catalog.pg_description pgd ON (pgd.objoid = st.relid) - INNER JOIN information_schema.columns c ON pgd.objsubid = c.ordinal_position AND c.table_schema = st.schemaname AND c.table_name = st.relname - WHERE c.table_name = ? AND c.column_name = ? - ', - [$table, $column] - ); - - return $definition ? $definition->description : null; - } - - private function getDefaultListing(string $table, string $column) - { - $definition = DB::selectOne( - 'SELECT column_default FROM information_schema.columns WHERE table_name = ? and column_name = ?', - [$table, $column] - ); - - return $definition ? $definition->column_default : null; - } - - private function getIndexListing($index): ?string - { - $definition = DB::selectOne('SELECT indexdef FROM pg_indexes WHERE indexname = ?', [$index]); - - return $definition ? $definition->indexdef : null; } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 2467719..67c8ab0 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,7 +9,7 @@ abstract class TestCase extends BaseTestCase { - protected function getPackageProviders($app) + protected function getPackageProviders($app): array { return [UmbrellioPostgresProvider::class]; } diff --git a/tests/Unit/Doctrine/Types/TsRangeTypeTest.php b/tests/Unit/Doctrine/Types/TsRangeTypeTest.php index 4de405c..375908d 100644 --- a/tests/Unit/Doctrine/Types/TsRangeTypeTest.php +++ b/tests/Unit/Doctrine/Types/TsRangeTypeTest.php @@ -9,12 +9,13 @@ use Umbrellio\Postgres\Doctrine\Types\TsRangeType; use Umbrellio\Postgres\Tests\TestCase; +/** + * @property AbstractPlatform $abstractPlatform + * @property TsRangeType $type + */ class TsRangeTypeTest extends TestCase { - /** @var AbstractPlatform */ private $abstractPlatform; - - /** @var TsRangeType */ private $type; protected function setUp(): void @@ -29,9 +30,7 @@ protected function setUp(): void $this->abstractPlatform = $this->getMockForAbstractClass(AbstractPlatform::class); } - /** - * @test - */ + /** @test */ public function getSQLDeclaration(): void { $this->assertSame(TsRangeType::TYPE_NAME, $this->type->getSQLDeclaration([], $this->abstractPlatform)); diff --git a/tests/Unit/Doctrine/Types/TsVectorTypeTest.php b/tests/Unit/Doctrine/Types/TsVectorTypeTest.php index 9dccd05..10ba52b 100644 --- a/tests/Unit/Doctrine/Types/TsVectorTypeTest.php +++ b/tests/Unit/Doctrine/Types/TsVectorTypeTest.php @@ -9,12 +9,13 @@ use Umbrellio\Postgres\Doctrine\Types\TsVectorType; use Umbrellio\Postgres\Tests\TestCase; +/** + * @property AbstractPlatform $abstractPlatform + * @property TsVectorType $type + */ class TsVectorTypeTest extends TestCase { - /** @var AbstractPlatform */ private $abstractPlatform; - - /** @var TsVectorType */ private $type; protected function setUp(): void @@ -29,9 +30,7 @@ protected function setUp(): void $this->abstractPlatform = $this->getMockForAbstractClass(AbstractPlatform::class); } - /** - * @test - */ + /** @test */ public function getSQLDeclaration(): void { $this->assertSame(TsVectorType::TYPE_NAME, $this->type->getSQLDeclaration([], $this->abstractPlatform)); diff --git a/tests/Unit/BlueprintTestCase.php b/tests/Unit/Helpers/BlueprintAssertions.php similarity index 62% rename from tests/Unit/BlueprintTestCase.php rename to tests/Unit/Helpers/BlueprintAssertions.php index 45cd371..be7de4b 100644 --- a/tests/Unit/BlueprintTestCase.php +++ b/tests/Unit/Helpers/BlueprintAssertions.php @@ -2,27 +2,29 @@ declare(strict_types=1); -namespace Umbrellio\Postgres\Tests\Unit; +namespace Umbrellio\Postgres\Tests\Unit\Helpers; +use PHPUnit\Framework\TestCase; use Umbrellio\Postgres\PostgresConnection; use Umbrellio\Postgres\Schema\Blueprint; use Umbrellio\Postgres\Schema\Grammars\PostgresGrammar; -use Umbrellio\Postgres\Tests\TestCase; -class BlueprintTestCase extends TestCase +/** + * @mixin TestCase + * + * @property Blueprint $blueprint + * @property PostgresConnection $postgresConnection + * @property PostgresGrammar $postgresGrammar + */ +trait BlueprintAssertions { - /** @var Blueprint */ protected $blueprint; - /** @var PostgresConnection */ protected $postgresConnection; - /** @var PostgresGrammar */ protected $postgresGrammar; - protected function setUp(): void + public function initializeMock(string $table) { - parent::setUp(); - - $this->blueprint = new Blueprint('test_table'); + $this->blueprint = new Blueprint($table); $this->postgresConnection = $this->createMock(PostgresConnection::class); $this->postgresGrammar = new PostgresGrammar(); } @@ -35,17 +37,14 @@ protected function assertSameSql($sql): void $this->assertSame((array) $sql, $this->runToSql()); } - /** - * @param string|array $regexpExpected - */ - protected function assertRegExpSql($regexpExpected): void + protected function assertRegExpSql(string $regexpExpected): void { foreach ($this->runToSql() as $sql) { $this->assertRegExp($regexpExpected, $sql); } } - protected function runToSql(): array + private function runToSql(): array { return $this->blueprint->toSql($this->postgresConnection, $this->postgresGrammar); } diff --git a/tests/Unit/Schema/Blueprint/PartitionTest.php b/tests/Unit/Schema/Blueprint/PartitionTest.php index 64f8f45..e748882 100644 --- a/tests/Unit/Schema/Blueprint/PartitionTest.php +++ b/tests/Unit/Schema/Blueprint/PartitionTest.php @@ -6,10 +6,21 @@ use Illuminate\Support\Carbon; use InvalidArgumentException; -use Umbrellio\Postgres\Tests\Unit\BlueprintTestCase; +use Umbrellio\Postgres\Tests\TestCase; +use Umbrellio\Postgres\Tests\Unit\Helpers\BlueprintAssertions; -class PartitionTest extends BlueprintTestCase +class PartitionTest extends TestCase { + use BlueprintAssertions; + + private const TABLE = 'test_table'; + + protected function setUp(): void + { + parent::setUp(); + $this->initializeMock(static::TABLE); + } + /** @test */ public function detachPartition(): void { diff --git a/tests/Unit/Schema/Grammars/GrammarTest.php b/tests/Unit/Schema/Grammars/GrammarTest.php index 7626f76..2a87d54 100644 --- a/tests/Unit/Schema/Grammars/GrammarTest.php +++ b/tests/Unit/Schema/Grammars/GrammarTest.php @@ -4,19 +4,31 @@ namespace Umbrellio\Postgres\Tests\Unit\Schema\Grammars; -use Umbrellio\Postgres\Tests\Unit\BlueprintTestCase; +use Umbrellio\Postgres\Tests\TestCase; +use Umbrellio\Postgres\Tests\Unit\Helpers\BlueprintAssertions; -class GrammarTest extends BlueprintTestCase +class GrammarTest extends TestCase { + use BlueprintAssertions; + + private const TABLE = 'test_table'; + + protected function setUp(): void + { + parent::setUp(); + + $this->initializeMock(static::TABLE); + } + /** @test */ - public function addingGinIndex() + public function addingGinIndex(): void { $this->blueprint->gin('foo'); $this->assertRegExpSql('/CREATE INDEX test_table_foo_gin ON (public.)?"test_table" USING GIN\("foo"\)/'); } /** @test */ - public function addingGistIndex() + public function addingGistIndex(): void { $this->blueprint->gist('foo'); $this->assertRegExpSql('/CREATE INDEX test_table_foo_gist ON (public.)?"test_table" USING GIST\("foo"\)/'); From f086b4e18e3d8f17f4e434dee557b6b22a97a938 Mon Sep 17 00:00:00 2001 From: Veselov Pavel Date: Wed, 24 Jul 2019 20:54:25 +0300 Subject: [PATCH 38/38] add some test for fix coverage --- .../Unit/Extensions/AbstractExtensionTest.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/Unit/Extensions/AbstractExtensionTest.php diff --git a/tests/Unit/Extensions/AbstractExtensionTest.php b/tests/Unit/Extensions/AbstractExtensionTest.php new file mode 100644 index 0000000..24c1ee9 --- /dev/null +++ b/tests/Unit/Extensions/AbstractExtensionTest.php @@ -0,0 +1,65 @@ + new class() extends Model { + }, + ]; + } + }; + + $this->expectException(MixinInvalidException::class); + + /** @var AbstractExtension $abstractExtension */ + $abstractExtension::register(); + } + + /** @test */ + public function registerWithInvalidMixin(): void + { + $abstractExtension = new class() extends AbstractExtension { + public static function getName(): string + { + return 'extension'; + } + + public static function getMixins(): array + { + return [ + ServiceProvider::class => new class() extends AbstractComponent { + }, + ]; + } + }; + + $this->expectException(MacroableMissedException::class); + + /** @var AbstractExtension $abstractExtension */ + $abstractExtension::register(); + } +}