Skip to content

Commit 82378ea

Browse files
fix(MySQL): unsigned boolean Columns comparison (#234)
Co-authored-by: Adam Dyson <[email protected]>
1 parent f8b3a87 commit 82378ea

File tree

2 files changed

+108
-4
lines changed

2 files changed

+108
-4
lines changed

src/Driver/MySQL/Schema/MySQLColumn.php

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class MySQLColumn extends AbstractColumn
4242
*/
4343
public const DATETIME_NOW = 'CURRENT_TIMESTAMP';
4444

45-
public const EXCLUDE_FROM_COMPARE = ['size', 'timezone', 'userType', 'attributes', 'first', 'after'];
45+
public const EXCLUDE_FROM_COMPARE = ['size', 'timezone', 'userType', 'attributes', 'first', 'after', 'unknownSize'];
4646
protected const INTEGER_TYPES = ['tinyint', 'smallint', 'mediumint', 'int', 'bigint'];
4747

4848
protected array $mapping = [
@@ -162,6 +162,11 @@ class MySQLColumn extends AbstractColumn
162162
)]
163163
protected int $size = 0;
164164

165+
/**
166+
* True if size is not defined in DB schema.
167+
*/
168+
protected bool $unknownSize = false;
169+
165170
/**
166171
* Column is auto incremental.
167172
*/
@@ -251,6 +256,7 @@ public static function createInstance(string $table, array $schema, ?\DateTimeZo
251256

252257
// since 8.0 database does not provide size for some columns
253258
if ($column->size === 0) {
259+
$column->unknownSize = true;
254260
switch ($column->type) {
255261
case 'int':
256262
$column->size = 11;
@@ -291,6 +297,12 @@ public static function createInstance(string $table, array $schema, ?\DateTimeZo
291297
return $column;
292298
}
293299

300+
public function size(int $value): self
301+
{
302+
$this->unknownSize = false;
303+
return parent::__call('size', [$value]);
304+
}
305+
294306
/**
295307
* @psalm-return non-empty-string
296308
*/
@@ -327,10 +339,27 @@ public function sqlStatement(DriverInterface $driver): string
327339

328340
public function compare(AbstractColumn $initial): bool
329341
{
330-
$result = parent::compare($initial);
342+
\assert($initial instanceof self);
343+
$self = $this;
344+
345+
// MySQL 8.0 does not provide size for unsigned integers without zerofill
346+
// so we can get wrong results in comparison of boolean columns
347+
if ($self->unknownSize || $initial->unknownSize) {
348+
// if one of the columns is boolean, we can safely assume that size is 1
349+
if (\in_array($self->userType, ['bool', 'boolean'], true)) {
350+
$initial = clone $initial;
351+
$initial->size = 1;
352+
} elseif (\in_array($initial->userType, ['bool', 'boolean'], true)) {
353+
$self = clone $self;
354+
$self->size = 1;
355+
}
356+
}
357+
358+
$result = \Closure::fromCallable([parent::class, 'compare'])->bindTo($self)($initial);
359+
331360

332-
if ($this->type === 'varchar' || $this->type === 'varbinary') {
333-
return $result && $this->size === $initial->size;
361+
if ($self->type === 'varchar' || $self->type === 'varbinary') {
362+
return $result && $self->size === $initial->size;
334363
}
335364

336365
return $result;

tests/Database/Functional/Driver/MySQL/Schema/BooleanColumnTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,79 @@
1414
class BooleanColumnTest extends CommonClass
1515
{
1616
public const DRIVER = 'mysql';
17+
18+
public function testBooleanDefaultSize(): void
19+
{
20+
$schema = $this->schema('table');
21+
$schema->boolean('column');
22+
$schema->save();
23+
24+
$column = $this->fetchSchema($schema)->column('column');
25+
26+
$this->assertSame('boolean', $column->getAbstractType());
27+
$this->assertSame(1, $column->getSize());
28+
}
29+
30+
public function testBooleanComparisonWithSize(): void
31+
{
32+
$schema = $this->schema('table');
33+
$foo = $schema->boolean('foo')->defaultValue(false)->nullable(false)->unsigned(true)->size(1)->zerofill(true);
34+
$bar = $schema->boolean('bar', nullable: true, unsigned: true, size: 1, zerofill: true);
35+
$baz = $schema->boolean('baz', nullable: false, unsigned: true);
36+
$mux = $schema->boolean('mux', nullable: false);
37+
$schema->save();
38+
39+
$schema = $this->schema('table');
40+
$this->assertTrue($schema->exists());
41+
$this->assertSame(1, $foo->getSize());
42+
$this->assertSame(1, $bar->getSize());
43+
$this->assertSame(1, $baz->getSize());
44+
$this->assertSame(1, $mux->getSize());
45+
$this->assertSame(1, $schema->column('foo')->getSize());
46+
$this->assertSame(1, $schema->column('bar')->getSize());
47+
$this->assertTrue(\in_array($schema->column('baz')->getSize(), [1, 4], true));
48+
$this->assertSame(1, $schema->column('mux')->getSize());
49+
$this->assertTrue($foo->compare($schema->column('foo')));
50+
$this->assertTrue($bar->compare($schema->column('bar')));
51+
$this->assertTrue($baz->compare($schema->column('baz')));
52+
$this->assertTrue($mux->compare($schema->column('mux')));
53+
}
54+
55+
public function testBooleanWithProblematicValues(): void
56+
{
57+
$schema = $this->schema('table');
58+
59+
$column = $schema->boolean('target')
60+
->defaultValue(false)
61+
->nullable(false)
62+
->unsigned(true)
63+
->comment('Target comment');
64+
65+
$schema->save();
66+
67+
$this->assertTrue($schema->exists());
68+
69+
$schema = $this->schema('table');
70+
$target = $schema->column('target');
71+
72+
$this->assertSame(1, $column->getSize());
73+
$this->assertSame(4, $target->getSize());
74+
$this->assertFalse($column->isNullable());
75+
$this->assertFalse($target->isNullable());
76+
$this->assertTrue($column->isUnsigned());
77+
$this->assertTrue($target->isUnsigned());
78+
79+
$object = new \ReflectionObject($target);
80+
$property = $object->getProperty('defaultValue');
81+
$property->setAccessible(true);
82+
$defaultValue = $property->getValue($target);
83+
84+
$this->assertSame(false, $column->getDefaultValue());
85+
$this->assertSame(0, $target->getDefaultValue());
86+
$this->assertSame('0', $defaultValue);
87+
$this->assertTrue($column->compare($target));
88+
$this->assertTrue($target->compare($column));
89+
// The size was not changed
90+
$this->assertSame(4, $target->getSize());
91+
}
1792
}

0 commit comments

Comments
 (0)