aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/DB/MigratorTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/DB/MigratorTest.php')
-rw-r--r--tests/lib/DB/MigratorTest.php314
1 files changed, 314 insertions, 0 deletions
diff --git a/tests/lib/DB/MigratorTest.php b/tests/lib/DB/MigratorTest.php
new file mode 100644
index 00000000000..9d8ee6791a9
--- /dev/null
+++ b/tests/lib/DB/MigratorTest.php
@@ -0,0 +1,314 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\DB;
+
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\ParameterType;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\SchemaConfig;
+use OC\DB\Migrator;
+use OC\DB\OracleMigrator;
+use OC\DB\SQLiteMigrator;
+use OCP\DB\Types;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Server;
+
+/**
+ * Class MigratorTest
+ *
+ * @group DB
+ *
+ * @package Test\DB
+ */
+class MigratorTest extends \Test\TestCase {
+ /**
+ * @var \Doctrine\DBAL\Connection $connection
+ */
+ private $connection;
+
+ /**
+ * @var IConfig
+ **/
+ private $config;
+
+ /** @var string */
+ private $tableName;
+
+ /** @var string */
+ private $tableNameTmp;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->config = Server::get(IConfig::class);
+ $this->connection = Server::get(\OC\DB\Connection::class);
+
+ $this->tableName = $this->getUniqueTableName();
+ $this->tableNameTmp = $this->getUniqueTableName();
+ }
+
+ private function getMigrator(): Migrator {
+ $dispatcher = Server::get(IEventDispatcher::class);
+ if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE) {
+ return new SQLiteMigrator($this->connection, $this->config, $dispatcher);
+ } elseif ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) {
+ return new OracleMigrator($this->connection, $this->config, $dispatcher);
+ }
+ return new Migrator($this->connection, $this->config, $dispatcher);
+ }
+
+ private function getUniqueTableName() {
+ return strtolower($this->getUniqueID($this->config->getSystemValueString('dbtableprefix', 'oc_') . 'test_'));
+ }
+
+ protected function tearDown(): void {
+ // Try to delete if exists (IF EXISTS NOT SUPPORTED IN ORACLE)
+ try {
+ $this->connection->exec('DROP TABLE ' . $this->connection->quoteIdentifier($this->tableNameTmp));
+ } catch (Exception $e) {
+ }
+
+ try {
+ $this->connection->exec('DROP TABLE ' . $this->connection->quoteIdentifier($this->tableName));
+ } catch (Exception $e) {
+ }
+ parent::tearDown();
+ }
+
+ /**
+ * @return \Doctrine\DBAL\Schema\Schema[]
+ */
+ private function getDuplicateKeySchemas() {
+ $startSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $startSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer');
+ $table->addColumn('name', 'string');
+ $table->addIndex(['id'], $this->tableName . '_id');
+
+ $endSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $endSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer');
+ $table->addColumn('name', 'string');
+ $table->addUniqueIndex(['id'], $this->tableName . '_id');
+
+ return [$startSchema, $endSchema];
+ }
+
+ /**
+ * @return \Doctrine\DBAL\Schema\Schema[]
+ */
+ private function getChangedTypeSchema($from, $to) {
+ $startSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $startSchema->createTable($this->tableName);
+ $table->addColumn('id', $from);
+ $table->addColumn('name', 'string');
+ $table->addIndex(['id'], $this->tableName . '_id');
+
+ $endSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $endSchema->createTable($this->tableName);
+ $table->addColumn('id', $to);
+ $table->addColumn('name', 'string');
+ $table->addIndex(['id'], $this->tableName . '_id');
+
+ return [$startSchema, $endSchema];
+ }
+
+
+ private function getSchemaConfig() {
+ $config = new SchemaConfig();
+ $config->setName($this->connection->getDatabase());
+ return $config;
+ }
+
+ public function testUpgrade(): void {
+ [$startSchema, $endSchema] = $this->getDuplicateKeySchemas();
+ $migrator = $this->getMigrator();
+ $migrator->migrate($startSchema);
+
+ $this->connection->insert($this->tableName, ['id' => 1, 'name' => 'foo']);
+ $this->connection->insert($this->tableName, ['id' => 2, 'name' => 'bar']);
+ $this->connection->insert($this->tableName, ['id' => 3, 'name' => 'qwerty']);
+
+ $migrator->migrate($endSchema);
+ $this->addToAssertionCount(1);
+ }
+
+ public function testUpgradeDifferentPrefix(): void {
+ $oldTablePrefix = $this->config->getSystemValueString('dbtableprefix', 'oc_');
+
+ $this->config->setSystemValue('dbtableprefix', 'ownc_');
+ $this->tableName = strtolower($this->getUniqueID($this->config->getSystemValueString('dbtableprefix') . 'test_'));
+
+ [$startSchema, $endSchema] = $this->getDuplicateKeySchemas();
+ $migrator = $this->getMigrator();
+ $migrator->migrate($startSchema);
+
+ $this->connection->insert($this->tableName, ['id' => 1, 'name' => 'foo']);
+ $this->connection->insert($this->tableName, ['id' => 2, 'name' => 'bar']);
+ $this->connection->insert($this->tableName, ['id' => 3, 'name' => 'qwerty']);
+
+ $migrator->migrate($endSchema);
+ $this->addToAssertionCount(1);
+
+ $this->config->setSystemValue('dbtableprefix', $oldTablePrefix);
+ }
+
+ public function testInsertAfterUpgrade(): void {
+ [$startSchema, $endSchema] = $this->getDuplicateKeySchemas();
+ $migrator = $this->getMigrator();
+ $migrator->migrate($startSchema);
+
+ $migrator->migrate($endSchema);
+
+ $this->connection->insert($this->tableName, ['id' => 1, 'name' => 'foo']);
+ $this->connection->insert($this->tableName, ['id' => 2, 'name' => 'bar']);
+ try {
+ $this->connection->insert($this->tableName, ['id' => 2, 'name' => 'qwerty']);
+ $this->fail('Expected duplicate key insert to fail');
+ } catch (Exception $e) {
+ $this->addToAssertionCount(1);
+ }
+ }
+
+ public function testAddingPrimaryKeyWithAutoIncrement(): void {
+ $startSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $startSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer');
+ $table->addColumn('name', 'string');
+
+ $endSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $endSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer', ['autoincrement' => true]);
+ $table->addColumn('name', 'string');
+ $table->setPrimaryKey(['id']);
+
+ $migrator = $this->getMigrator();
+ $migrator->migrate($startSchema);
+
+ $migrator->migrate($endSchema);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testReservedKeywords(): void {
+ $startSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $startSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer', ['autoincrement' => true]);
+ $table->addColumn('user', 'string', ['length' => 255]);
+ $table->setPrimaryKey(['id']);
+
+ $endSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $endSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer', ['autoincrement' => true]);
+ $table->addColumn('user', 'string', ['length' => 64]);
+ $table->setPrimaryKey(['id']);
+
+ $migrator = $this->getMigrator();
+ $migrator->migrate($startSchema);
+
+ $migrator->migrate($endSchema);
+
+ $this->addToAssertionCount(1);
+ }
+
+ /**
+ * Test for nextcloud/server#36803
+ */
+ public function testColumnCommentsInUpdate(): void {
+ $startSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $startSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer', ['autoincrement' => true, 'comment' => 'foo']);
+ $table->setPrimaryKey(['id']);
+
+ $endSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $endSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer', ['autoincrement' => true, 'comment' => 'foo']);
+ // Assert adding comments on existing tables work (or at least does not throw)
+ $table->addColumn('time', 'integer', ['comment' => 'unix-timestamp', 'notnull' => false]);
+ $table->setPrimaryKey(['id']);
+
+ $migrator = $this->getMigrator();
+ $migrator->migrate($startSchema);
+
+ $migrator->migrate($endSchema);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testAddingForeignKey(): void {
+ $startSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $startSchema->createTable($this->tableName);
+ $table->addColumn('id', 'integer', ['autoincrement' => true]);
+ $table->addColumn('name', 'string');
+ $table->setPrimaryKey(['id']);
+
+ $fkName = 'fkc';
+ $tableFk = $startSchema->createTable($this->tableNameTmp);
+ $tableFk->addColumn('fk_id', 'integer');
+ $tableFk->addColumn('name', 'string');
+ $tableFk->addForeignKeyConstraint($this->tableName, ['fk_id'], ['id'], [], $fkName);
+
+ $migrator = $this->getMigrator();
+ $migrator->migrate($startSchema);
+
+
+ $this->assertTrue($startSchema->getTable($this->tableNameTmp)->hasForeignKey($fkName));
+ }
+
+ public static function dataNotNullEmptyValuesFailOracle(): array {
+ return [
+ [ParameterType::BOOLEAN, true, Types::BOOLEAN, false],
+ [ParameterType::BOOLEAN, false, Types::BOOLEAN, true],
+
+ [ParameterType::STRING, 'foo', Types::STRING, false],
+ [ParameterType::STRING, '', Types::STRING, true],
+
+ [ParameterType::INTEGER, 1234, Types::INTEGER, false],
+ [ParameterType::INTEGER, 0, Types::INTEGER, false], // Integer 0 is not stored as Null and therefor works
+
+ [ParameterType::STRING, '{"a": 2}', Types::JSON, false],
+ ];
+ }
+
+ /**
+ *
+ * @param int $parameterType
+ * @param bool|int|string $value
+ * @param string $columnType
+ * @param bool $oracleThrows
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataNotNullEmptyValuesFailOracle')]
+ public function testNotNullEmptyValuesFailOracle(int $parameterType, $value, string $columnType, bool $oracleThrows): void {
+ $startSchema = new Schema([], [], $this->getSchemaConfig());
+ $table = $startSchema->createTable($this->tableName);
+ $table->addColumn('id', Types::BIGINT);
+ $table->addColumn('will_it_blend', $columnType, [
+ 'notnull' => true,
+ ]);
+ $table->addIndex(['id'], $this->tableName . '_id');
+
+ $migrator = $this->getMigrator();
+ $migrator->migrate($startSchema);
+
+ if ($oracleThrows && $this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) {
+ // Oracle can not store false|empty string in notnull columns
+ $this->expectException(\Doctrine\DBAL\Exception\NotNullConstraintViolationException::class);
+ }
+
+ $this->connection->insert(
+ $this->tableName,
+ ['id' => 1, 'will_it_blend' => $value],
+ ['id' => ParameterType::INTEGER, 'will_it_blend' => $parameterType],
+ );
+
+ $this->addToAssertionCount(1);
+ }
+}