Skip to content

Commit 7ad2df1

Browse files
feat(MySQL): add first and after into column declaration (#226)
Co-authored-by: roxblnfk <[email protected]>
1 parent 13490d3 commit 7ad2df1

File tree

3 files changed

+166
-14
lines changed

3 files changed

+166
-14
lines changed

src/Driver/MySQL/Schema/MySQLColumn.php

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* @method $this|AbstractColumn unsigned(bool $value)
3434
* @method $this|AbstractColumn zerofill(bool $value)
3535
* @method $this|AbstractColumn comment(string $value)
36+
* @method $this|AbstractColumn after(string $column)
3637
*/
3738
class MySQLColumn extends AbstractColumn
3839
{
@@ -41,7 +42,7 @@ class MySQLColumn extends AbstractColumn
4142
*/
4243
public const DATETIME_NOW = 'CURRENT_TIMESTAMP';
4344

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

4748
protected array $mapping = [
@@ -184,6 +185,18 @@ class MySQLColumn extends AbstractColumn
184185
#[ColumnAttribute]
185186
protected string $comment = '';
186187

188+
/**
189+
* Column name to position after.
190+
*/
191+
#[ColumnAttribute]
192+
protected string $after = '';
193+
194+
/**
195+
* Whether the column should be positioned first.
196+
*/
197+
#[ColumnAttribute]
198+
protected bool $first = false;
199+
187200
/**
188201
* @psalm-param non-empty-string $table
189202
*/
@@ -283,23 +296,30 @@ public static function createInstance(string $table, array $schema, ?\DateTimeZo
283296
public function sqlStatement(DriverInterface $driver): string
284297
{
285298
if (\in_array($this->type, self::INTEGER_TYPES, true)) {
286-
return $this->sqlStatementInteger($driver);
287-
}
299+
$statement = $this->sqlStatementInteger($driver);
300+
} else {
301+
$defaultValue = $this->defaultValue;
288302

289-
$defaultValue = $this->defaultValue;
303+
if (\in_array($this->type, $this->forbiddenDefaults, true)) {
304+
// Flushing default value for forbidden types
305+
$this->defaultValue = null;
306+
}
307+
308+
$statement = parent::sqlStatement($driver);
290309

291-
if (\in_array($this->type, $this->forbiddenDefaults, true)) {
292-
//Flushing default value for forbidden types
293-
$this->defaultValue = null;
310+
$this->defaultValue = $defaultValue;
294311
}
295312

296-
$statement = parent::sqlStatement($driver);
313+
$this->comment === '' or $statement .= " COMMENT {$driver->quote($this->comment)}";
297314

298-
$this->defaultValue = $defaultValue;
315+
$first = $this->first;
316+
$after = $first ? '' : $this->after;
299317

300-
if ($this->comment !== '') {
301-
return "{$statement} COMMENT {$driver->quote($this->comment)}";
302-
}
318+
$statement .= match (true) {
319+
$first => ' FIRST',
320+
$after !== '' => " AFTER {$driver->identifier($after)}",
321+
default => '',
322+
};
303323

304324
return $statement;
305325
}
@@ -325,6 +345,13 @@ public function isZerofill(): bool
325345
return $this->zerofill;
326346
}
327347

348+
public function first(bool $value = true): self
349+
{
350+
$this->first = $value;
351+
352+
return $this;
353+
}
354+
328355
public function set(string|array $values): self
329356
{
330357
$this->type('set');
@@ -395,12 +422,11 @@ protected function formatDatetime(
395422
private function sqlStatementInteger(DriverInterface $driver): string
396423
{
397424
return \sprintf(
398-
'%s %s(%s)%s%s%s%s%s%s',
425+
'%s %s(%s)%s%s%s%s%s',
399426
$driver->identifier($this->name),
400427
$this->type,
401428
$this->size,
402429
$this->unsigned ? ' UNSIGNED' : '',
403-
$this->comment !== '' ? " COMMENT {$driver->quote($this->comment)}" : '',
404430
$this->zerofill ? ' ZEROFILL' : '',
405431
$this->nullable ? ' NULL' : ' NOT NULL',
406432
$this->defaultValue !== null ? " DEFAULT {$this->quoteDefault($driver)}" : '',
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Database\Tests\Functional\Driver\Common\Schema;
6+
7+
use Cycle\Database\Driver\Handler;
8+
use Cycle\Database\Exception\DBALException;
9+
use Cycle\Database\Schema\AbstractColumn;
10+
use Cycle\Database\Schema\AbstractTable;
11+
use Cycle\Database\Tests\Functional\Driver\Common\BaseTest;
12+
use Cycle\Database\Tests\Utils\DontGenerateAttribute;
13+
14+
#[DontGenerateAttribute]
15+
abstract class PositionColumnTest extends BaseTest
16+
{
17+
public function testPositionFirst(): void
18+
{
19+
$schema = $this->sampleSchema('table');
20+
21+
$this->assertTrue($schema->exists());
22+
$this->assertSameAsInDB($schema);
23+
24+
$schema->string('identifier')->nullable(false)->first();
25+
$schema->save();
26+
27+
$this->assertSameAsInDB($schema);
28+
29+
$updatedSchema = $this->fetchSchema($schema);
30+
$updatedColumnNames = \array_map(static fn(AbstractColumn $column) => $column->getName(), $updatedSchema->getColumns());
31+
32+
$expectedColumnNames = [
33+
'identifier' => 'identifier',
34+
'id' => 'id',
35+
'first_name' => 'first_name',
36+
'last_name' => 'last_name',
37+
'email' => 'email',
38+
'status' => 'status',
39+
'balance' => 'balance',
40+
'created_at' => 'created_at',
41+
'updated_at' => 'updated_at',
42+
];
43+
44+
$this->assertSame($expectedColumnNames, $updatedColumnNames);
45+
}
46+
47+
public function testPositionAfter(): void
48+
{
49+
$schema = $this->sampleSchema('table');
50+
51+
$this->assertTrue($schema->exists());
52+
$this->assertSameAsInDB($schema);
53+
54+
$schema->string('identifier')->nullable(false)->after('email');
55+
$schema->save();
56+
57+
$this->assertSameAsInDB($schema);
58+
59+
$updatedSchema = $this->fetchSchema($schema);
60+
$updatedColumnNames = \array_map(static fn(AbstractColumn $column) => $column->getName(), $updatedSchema->getColumns());
61+
62+
$expectedColumnNames = [
63+
'id' => 'id',
64+
'first_name' => 'first_name',
65+
'last_name' => 'last_name',
66+
'email' => 'email',
67+
'identifier' => 'identifier',
68+
'status' => 'status',
69+
'balance' => 'balance',
70+
'created_at' => 'created_at',
71+
'updated_at' => 'updated_at',
72+
];
73+
74+
$this->assertSame($expectedColumnNames, $updatedColumnNames);
75+
}
76+
77+
public function testPositionAfterThrowsException(): void
78+
{
79+
$schema = $this->sampleSchema('table');
80+
81+
$this->assertTrue($schema->exists());
82+
$this->assertSameAsInDB($schema);
83+
84+
$this->expectException(DBALException::class);
85+
$this->expectExceptionMessage("Unknown column 'nonexistent'");
86+
87+
$schema->string('identifier')->nullable(false)->after('nonexistent');
88+
$schema->save();
89+
}
90+
91+
private function sampleSchema(string $table): AbstractTable
92+
{
93+
$schema = $this->schema($table);
94+
95+
if (! $schema->exists()) {
96+
$schema->primary('id');
97+
$schema->string('first_name')->nullable(false);
98+
$schema->string('last_name')->nullable(false);
99+
$schema->string('email', 64)->nullable(false);
100+
$schema->enum('status', ['active', 'disabled'])->defaultValue('active');
101+
$schema->double('balance')->defaultValue(0);
102+
$schema->datetime('created_at')->defaultValue(AbstractColumn::DATETIME_NOW);
103+
$schema->datetime('updated_at')->nullable(true);
104+
105+
$schema->save(Handler::DO_ALL);
106+
}
107+
108+
return $schema;
109+
}
110+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Database\Tests\Functional\Driver\MySQL\Schema;
6+
7+
use Cycle\Database\Tests\Functional\Driver\Common\Schema\PositionColumnTest as BaseTest;
8+
9+
/**
10+
* @group driver
11+
* @group driver-mysql
12+
*/
13+
class PositionColumnTest extends BaseTest
14+
{
15+
public const DRIVER = 'mysql';
16+
}

0 commit comments

Comments
 (0)