Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce no notnull for boolean to store false #24055

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions lib/private/DB/MigrationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ public function migrateSchemaOnly($to = 'latest') {
$targetSchema = $toSchema->getWrappedSchema();
if ($this->checkOracle) {
$beforeSchema = $this->connection->createSchema();
$this->ensureOracleIdentifierLengthLimit($beforeSchema, $targetSchema, strlen($this->connection->getPrefix()));
$this->ensureOracleConstraints($beforeSchema, $targetSchema, strlen($this->connection->getPrefix()));
}
$this->connection->migrateToSchema($targetSchema);
$toSchema->performDropTableCalls();
Expand Down Expand Up @@ -536,7 +536,7 @@ public function executeStep($version, $schemaOnly = false) {
$targetSchema = $toSchema->getWrappedSchema();
if ($this->checkOracle) {
$sourceSchema = $this->connection->createSchema();
$this->ensureOracleIdentifierLengthLimit($sourceSchema, $targetSchema, strlen($this->connection->getPrefix()));
$this->ensureOracleConstraints($sourceSchema, $targetSchema, strlen($this->connection->getPrefix()));
}
$this->connection->migrateToSchema($targetSchema);
$toSchema->performDropTableCalls();
Expand All @@ -551,7 +551,25 @@ public function executeStep($version, $schemaOnly = false) {
$this->markAsExecuted($version);
}

public function ensureOracleIdentifierLengthLimit(Schema $sourceSchema, Schema $targetSchema, int $prefixLength) {
/**
* Naming constraints:
* - Tables names must be 30 chars or shorter (27 + oc_ prefix)
* - Column names must be 30 chars or shorter
* - Index names must be 30 chars or shorter
* - Sequence names must be 30 chars or shorter
* - Primary key names must be set or the table name 23 chars or shorter
*
* Data constraints:
* - Columns with "NotNull" can not have empty string as default value
* - Columns with "NotNull" can not have number 0 as default value
* - Columns with type "bool" (which is in fact integer of length 1) can not be "NotNull" as it can not store 0/false
*
* @param Schema $sourceSchema
* @param Schema $targetSchema
* @param int $prefixLength
* @throws \Doctrine\DBAL\Exception
*/
public function ensureOracleConstraints(Schema $sourceSchema, Schema $targetSchema, int $prefixLength) {
$sequences = $targetSchema->getSequences();

foreach ($targetSchema->getTables() as $table) {
Expand All @@ -571,7 +589,11 @@ public function ensureOracleIdentifierLengthLimit(Schema $sourceSchema, Schema $

if ($thing->getNotnull() && $thing->getDefault() === ''
&& $sourceTable instanceof Table && !$sourceTable->hasColumn($thing->getName())) {
throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.');
throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.');
}

if ($thing->getNotnull() && $thing->getType()->getName() === Types::BOOLEAN) {
throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type Bool and also NotNull, so it can not store "false".');
}
}

Expand All @@ -583,7 +605,7 @@ public function ensureOracleIdentifierLengthLimit(Schema $sourceSchema, Schema $

foreach ($table->getForeignKeys() as $thing) {
if ((!$sourceTable instanceof Table || !$sourceTable->hasForeignKey($thing->getName())) && \strlen($thing->getName()) > 30) {
throw new \InvalidArgumentException('Foreign key name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
throw new \InvalidArgumentException('Foreign key name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
}
}

Expand Down
81 changes: 61 additions & 20 deletions tests/lib/DB/MigrationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type;
use OC\DB\Connection;
use OC\DB\MigrationService;
use OC\DB\SchemaWrapper;
Expand Down Expand Up @@ -219,7 +220,7 @@ public function testMigrate() {
$this->migrationService->migrate();
}

public function testEnsureOracleIdentifierLengthLimitValid() {
public function testEnsureOracleConstraintsValid() {
$column = $this->createMock(Column::class);
$column->expects($this->once())
->method('getName')
Expand Down Expand Up @@ -274,10 +275,10 @@ public function testEnsureOracleIdentifierLengthLimitValid() {
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}

public function testEnsureOracleIdentifierLengthLimitValidWithPrimaryKey() {
public function testEnsureOracleConstraintsValidWithPrimaryKey() {
$index = $this->createMock(Index::class);
$index->expects($this->any())
->method('getName')
Expand Down Expand Up @@ -317,10 +318,10 @@ public function testEnsureOracleIdentifierLengthLimitValidWithPrimaryKey() {
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}

public function testEnsureOracleIdentifierLengthLimitValidWithPrimaryKeyDefault() {
public function testEnsureOracleConstraintsValidWithPrimaryKeyDefault() {
$defaultName = 'PRIMARY';
if ($this->db->getDatabasePlatform() instanceof PostgreSqlPlatform) {
$defaultName = \str_repeat('a', 26) . '_' . \str_repeat('b', 30) . '_seq';
Expand Down Expand Up @@ -370,11 +371,11 @@ public function testEnsureOracleIdentifierLengthLimitValidWithPrimaryKeyDefault(
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}


public function testEnsureOracleIdentifierLengthLimitTooLongTableName() {
public function testEnsureOracleConstraintsTooLongTableName() {
$this->expectException(\InvalidArgumentException::class);

$table = $this->createMock(Table::class);
Expand All @@ -395,11 +396,11 @@ public function testEnsureOracleIdentifierLengthLimitTooLongTableName() {
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}


public function testEnsureOracleIdentifierLengthLimitTooLongPrimaryWithDefault() {
public function testEnsureOracleConstraintsTooLongPrimaryWithDefault() {
$this->expectException(\InvalidArgumentException::class);

$defaultName = 'PRIMARY';
Expand Down Expand Up @@ -448,11 +449,11 @@ public function testEnsureOracleIdentifierLengthLimitTooLongPrimaryWithDefault()
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}


public function testEnsureOracleIdentifierLengthLimitTooLongPrimaryWithName() {
public function testEnsureOracleConstraintsTooLongPrimaryWithName() {
$this->expectException(\InvalidArgumentException::class);

$index = $this->createMock(Index::class);
Expand Down Expand Up @@ -491,11 +492,11 @@ public function testEnsureOracleIdentifierLengthLimitTooLongPrimaryWithName() {
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}


public function testEnsureOracleIdentifierLengthLimitTooLongColumnName() {
public function testEnsureOracleConstraintsTooLongColumnName() {
$this->expectException(\InvalidArgumentException::class);

$column = $this->createMock(Column::class);
Expand Down Expand Up @@ -525,11 +526,11 @@ public function testEnsureOracleIdentifierLengthLimitTooLongColumnName() {
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}


public function testEnsureOracleIdentifierLengthLimitTooLongIndexName() {
public function testEnsureOracleConstraintsTooLongIndexName() {
$this->expectException(\InvalidArgumentException::class);

$index = $this->createMock(Index::class);
Expand Down Expand Up @@ -562,11 +563,11 @@ public function testEnsureOracleIdentifierLengthLimitTooLongIndexName() {
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}


public function testEnsureOracleIdentifierLengthLimitTooLongForeignKeyName() {
public function testEnsureOracleConstraintsTooLongForeignKeyName() {
$this->expectException(\InvalidArgumentException::class);

$foreignKey = $this->createMock(ForeignKeyConstraint::class);
Expand Down Expand Up @@ -602,11 +603,11 @@ public function testEnsureOracleIdentifierLengthLimitTooLongForeignKeyName() {
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}


public function testEnsureOracleIdentifierLengthLimitTooLongSequenceName() {
public function testEnsureOracleConstraintsTooLongSequenceName() {
$this->expectException(\InvalidArgumentException::class);

$sequence = $this->createMock(Sequence::class);
Expand All @@ -630,6 +631,46 @@ public function testEnsureOracleIdentifierLengthLimitTooLongSequenceName() {
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$sourceSchema, $schema, 3]);
self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}


public function testEnsureOracleConstraintsBooleanNotNull() {
$this->expectException(\InvalidArgumentException::class);

$column = $this->createMock(Column::class);
$column->expects($this->any())
->method('getName')
->willReturn('aaaa');
$column->expects($this->any())
->method('getType')
->willReturn(Type::getType('boolean'));
$column->expects($this->any())
->method('getNotnull')
->willReturn(true);

$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 30));

$table->expects($this->once())
->method('getColumns')
->willReturn([$column]);

$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);

$sourceSchema = $this->createMock(Schema::class);
$sourceSchema->expects($this->any())
->method('getTable')
->willThrowException(new SchemaException());
$sourceSchema->expects($this->any())
->method('hasSequence')
->willReturn(false);

self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
}
}