Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
54 changes: 40 additions & 14 deletions src/Driver/MySQL/Schema/MySQLColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* @method $this|AbstractColumn unsigned(bool $value)
* @method $this|AbstractColumn zerofill(bool $value)
* @method $this|AbstractColumn comment(string $value)
* @method $this|AbstractColumn after(string $column)
*/
class MySQLColumn extends AbstractColumn
{
Expand All @@ -41,7 +42,7 @@ class MySQLColumn extends AbstractColumn
*/
public const DATETIME_NOW = 'CURRENT_TIMESTAMP';

public const EXCLUDE_FROM_COMPARE = ['size', 'timezone', 'userType', 'attributes'];
public const EXCLUDE_FROM_COMPARE = ['size', 'timezone', 'userType', 'attributes', 'first', 'after'];
protected const INTEGER_TYPES = ['tinyint', 'smallint', 'mediumint', 'int', 'bigint'];

protected array $mapping = [
Expand Down Expand Up @@ -184,6 +185,18 @@ class MySQLColumn extends AbstractColumn
#[ColumnAttribute]
protected string $comment = '';

/**
* Column name to position after.
*/
#[ColumnAttribute]
protected string $after = '';

/**
* Whether the column should be positioned first.
*/
#[ColumnAttribute]
protected bool $first = false;

/**
* @psalm-param non-empty-string $table
*/
Expand Down Expand Up @@ -283,23 +296,30 @@ public static function createInstance(string $table, array $schema, ?\DateTimeZo
public function sqlStatement(DriverInterface $driver): string
{
if (\in_array($this->type, self::INTEGER_TYPES, true)) {
return $this->sqlStatementInteger($driver);
}
$statement = $this->sqlStatementInteger($driver);
} else {
$defaultValue = $this->defaultValue;

$defaultValue = $this->defaultValue;
if (\in_array($this->type, $this->forbiddenDefaults, true)) {
// Flushing default value for forbidden types
$this->defaultValue = null;
}

$statement = parent::sqlStatement($driver);

if (\in_array($this->type, $this->forbiddenDefaults, true)) {
//Flushing default value for forbidden types
$this->defaultValue = null;
$this->defaultValue = $defaultValue;
}

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

$this->defaultValue = $defaultValue;
$first = $this->first;
$after = $first ? '' : $this->after;

if ($this->comment !== '') {
return "{$statement} COMMENT {$driver->quote($this->comment)}";
}
$statement .= match (true) {
$first => ' FIRST',
$after !== '' => " AFTER {$driver->identifier($after)}",
default => '',
};

return $statement;
}
Expand All @@ -325,6 +345,13 @@ public function isZerofill(): bool
return $this->zerofill;
}

public function first(bool $value = true): self
{
$this->first = $value;

return $this;
}

public function set(string|array $values): self
{
$this->type('set');
Expand Down Expand Up @@ -395,12 +422,11 @@ protected function formatDatetime(
private function sqlStatementInteger(DriverInterface $driver): string
{
return \sprintf(
'%s %s(%s)%s%s%s%s%s%s',
'%s %s(%s)%s%s%s%s%s',
$driver->identifier($this->name),
$this->type,
$this->size,
$this->unsigned ? ' UNSIGNED' : '',
$this->comment !== '' ? " COMMENT {$driver->quote($this->comment)}" : '',
$this->zerofill ? ' ZEROFILL' : '',
$this->nullable ? ' NULL' : ' NOT NULL',
$this->defaultValue !== null ? " DEFAULT {$this->quoteDefault($driver)}" : '',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

namespace Cycle\Database\Tests\Functional\Driver\Common\Schema;

use Cycle\Database\Driver\Handler;
use Cycle\Database\Exception\DBALException;
use Cycle\Database\Schema\AbstractColumn;
use Cycle\Database\Schema\AbstractTable;
use Cycle\Database\Tests\Functional\Driver\Common\BaseTest;
use Cycle\Database\Tests\Utils\DontGenerateAttribute;

#[DontGenerateAttribute]
abstract class PositionColumnTest extends BaseTest
{
public function testPositionFirst(): void
{
$schema = $this->sampleSchema('table');

$this->assertTrue($schema->exists());
$this->assertSameAsInDB($schema);

$schema->string('identifier')->nullable(false)->first();
$schema->save();

$this->assertSameAsInDB($schema);

$updatedSchema = $this->fetchSchema($schema);
$updatedColumnNames = \array_map(static fn(AbstractColumn $column) => $column->getName(), $updatedSchema->getColumns());

$expectedColumnNames = [
'identifier' => 'identifier',
'id' => 'id',
'first_name' => 'first_name',
'last_name' => 'last_name',
'email' => 'email',
'status' => 'status',
'balance' => 'balance',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
];

$this->assertSame($expectedColumnNames, $updatedColumnNames);
}

public function testPositionAfter(): void
{
$schema = $this->sampleSchema('table');

$this->assertTrue($schema->exists());
$this->assertSameAsInDB($schema);

$schema->string('identifier')->nullable(false)->after('email');
$schema->save();

$this->assertSameAsInDB($schema);

$updatedSchema = $this->fetchSchema($schema);
$updatedColumnNames = \array_map(static fn(AbstractColumn $column) => $column->getName(), $updatedSchema->getColumns());

$expectedColumnNames = [
'id' => 'id',
'first_name' => 'first_name',
'last_name' => 'last_name',
'email' => 'email',
'identifier' => 'identifier',
'status' => 'status',
'balance' => 'balance',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
];

$this->assertSame($expectedColumnNames, $updatedColumnNames);
}

public function testPositionAfterThrowsException(): void
{
$schema = $this->sampleSchema('table');

$this->assertTrue($schema->exists());
$this->assertSameAsInDB($schema);

$this->expectException(DBALException::class);
$this->expectExceptionMessage("Unknown column 'nonexistent'");

$schema->string('identifier')->nullable(false)->after('nonexistent');
$schema->save();
}

private function sampleSchema(string $table): AbstractTable
{
$schema = $this->schema($table);

if (! $schema->exists()) {
$schema->primary('id');
$schema->string('first_name')->nullable(false);
$schema->string('last_name')->nullable(false);
$schema->string('email', 64)->nullable(false);
$schema->enum('status', ['active', 'disabled'])->defaultValue('active');
$schema->double('balance')->defaultValue(0);
$schema->datetime('created_at')->defaultValue(AbstractColumn::DATETIME_NOW);
$schema->datetime('updated_at')->nullable(true);

$schema->save(Handler::DO_ALL);
}

return $schema;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Cycle\Database\Tests\Functional\Driver\MySQL\Schema;

use Cycle\Database\Tests\Functional\Driver\Common\Schema\PositionColumnTest as BaseTest;

/**
* @group driver
* @group driver-mysql
*/
class PositionColumnTest extends BaseTest
{
public const DRIVER = 'mysql';
}
Loading