diff --git a/src/Driver/MySQL/Schema/MySQLColumn.php b/src/Driver/MySQL/Schema/MySQLColumn.php index ad823afa..28ee7321 100644 --- a/src/Driver/MySQL/Schema/MySQLColumn.php +++ b/src/Driver/MySQL/Schema/MySQLColumn.php @@ -42,7 +42,7 @@ class MySQLColumn extends AbstractColumn */ public const DATETIME_NOW = 'CURRENT_TIMESTAMP'; - public const EXCLUDE_FROM_COMPARE = ['size', 'timezone', 'userType', 'attributes', 'first', 'after']; + public const EXCLUDE_FROM_COMPARE = ['size', 'timezone', 'userType', 'attributes', 'first', 'after', 'unknownSize']; protected const INTEGER_TYPES = ['tinyint', 'smallint', 'mediumint', 'int', 'bigint']; protected array $mapping = [ @@ -162,6 +162,11 @@ class MySQLColumn extends AbstractColumn )] protected int $size = 0; + /** + * True if size is not defined in DB schema. + */ + protected bool $unknownSize = false; + /** * Column is auto incremental. */ @@ -251,6 +256,7 @@ public static function createInstance(string $table, array $schema, ?\DateTimeZo // since 8.0 database does not provide size for some columns if ($column->size === 0) { + $column->unknownSize = true; switch ($column->type) { case 'int': $column->size = 11; @@ -291,6 +297,12 @@ public static function createInstance(string $table, array $schema, ?\DateTimeZo return $column; } + public function size(int $value): self + { + $this->unknownSize = false; + return parent::__call('size', [$value]); + } + /** * @psalm-return non-empty-string */ @@ -327,10 +339,27 @@ public function sqlStatement(DriverInterface $driver): string public function compare(AbstractColumn $initial): bool { - $result = parent::compare($initial); + \assert($initial instanceof self); + $self = $this; + + // MySQL 8.0 does not provide size for unsigned integers without zerofill + // so we can get wrong results in comparison of boolean columns + if ($self->unknownSize || $initial->unknownSize) { + // if one of the columns is boolean, we can safely assume that size is 1 + if (\in_array($self->userType, ['bool', 'boolean'], true)) { + $initial = clone $initial; + $initial->size = 1; + } elseif (\in_array($initial->userType, ['bool', 'boolean'], true)) { + $self = clone $self; + $self->size = 1; + } + } + + $result = \Closure::fromCallable([parent::class, 'compare'])->bindTo($self)($initial); + - if ($this->type === 'varchar' || $this->type === 'varbinary') { - return $result && $this->size === $initial->size; + if ($self->type === 'varchar' || $self->type === 'varbinary') { + return $result && $self->size === $initial->size; } return $result; diff --git a/tests/Database/Functional/Driver/MySQL/Schema/BooleanColumnTest.php b/tests/Database/Functional/Driver/MySQL/Schema/BooleanColumnTest.php index c999ddf3..8eeb7096 100644 --- a/tests/Database/Functional/Driver/MySQL/Schema/BooleanColumnTest.php +++ b/tests/Database/Functional/Driver/MySQL/Schema/BooleanColumnTest.php @@ -14,4 +14,79 @@ class BooleanColumnTest extends CommonClass { public const DRIVER = 'mysql'; + + public function testBooleanDefaultSize(): void + { + $schema = $this->schema('table'); + $schema->boolean('column'); + $schema->save(); + + $column = $this->fetchSchema($schema)->column('column'); + + $this->assertSame('boolean', $column->getAbstractType()); + $this->assertSame(1, $column->getSize()); + } + + public function testBooleanComparisonWithSize(): void + { + $schema = $this->schema('table'); + $foo = $schema->boolean('foo')->defaultValue(false)->nullable(false)->unsigned(true)->size(1)->zerofill(true); + $bar = $schema->boolean('bar', nullable: true, unsigned: true, size: 1, zerofill: true); + $baz = $schema->boolean('baz', nullable: false, unsigned: true); + $mux = $schema->boolean('mux', nullable: false); + $schema->save(); + + $schema = $this->schema('table'); + $this->assertTrue($schema->exists()); + $this->assertSame(1, $foo->getSize()); + $this->assertSame(1, $bar->getSize()); + $this->assertSame(1, $baz->getSize()); + $this->assertSame(1, $mux->getSize()); + $this->assertSame(1, $schema->column('foo')->getSize()); + $this->assertSame(1, $schema->column('bar')->getSize()); + $this->assertTrue(\in_array($schema->column('baz')->getSize(), [1, 4], true)); + $this->assertSame(1, $schema->column('mux')->getSize()); + $this->assertTrue($foo->compare($schema->column('foo'))); + $this->assertTrue($bar->compare($schema->column('bar'))); + $this->assertTrue($baz->compare($schema->column('baz'))); + $this->assertTrue($mux->compare($schema->column('mux'))); + } + + public function testBooleanWithProblematicValues(): void + { + $schema = $this->schema('table'); + + $column = $schema->boolean('target') + ->defaultValue(false) + ->nullable(false) + ->unsigned(true) + ->comment('Target comment'); + + $schema->save(); + + $this->assertTrue($schema->exists()); + + $schema = $this->schema('table'); + $target = $schema->column('target'); + + $this->assertSame(1, $column->getSize()); + $this->assertSame(4, $target->getSize()); + $this->assertFalse($column->isNullable()); + $this->assertFalse($target->isNullable()); + $this->assertTrue($column->isUnsigned()); + $this->assertTrue($target->isUnsigned()); + + $object = new \ReflectionObject($target); + $property = $object->getProperty('defaultValue'); + $property->setAccessible(true); + $defaultValue = $property->getValue($target); + + $this->assertSame(false, $column->getDefaultValue()); + $this->assertSame(0, $target->getDefaultValue()); + $this->assertSame('0', $defaultValue); + $this->assertTrue($column->compare($target)); + $this->assertTrue($target->compare($column)); + // The size was not changed + $this->assertSame(4, $target->getSize()); + } }