diff --git a/src/Database/Migrations/Columns/Column.php b/src/Database/Migrations/Columns/Column.php index 26f8d95c..da15d19d 100644 --- a/src/Database/Migrations/Columns/Column.php +++ b/src/Database/Migrations/Columns/Column.php @@ -9,6 +9,8 @@ abstract class Column extends TableColumn { + protected bool $isUnique = false; + public function __construct( protected string $name ) { @@ -29,6 +31,11 @@ public function comment(string $comment): static return $this; } + public function isUnique(): bool + { + return $this->isUnique; + } + public function after(string $column): static { $this->options['after'] = $column; @@ -90,4 +97,11 @@ public function limit(int $limit): static return $this; } + + public function unique(): static + { + $this->isUnique = true; + + return $this; + } } diff --git a/src/Database/Migrations/Table.php b/src/Database/Migrations/Table.php index 5bdf579e..7ea574fc 100644 --- a/src/Database/Migrations/Table.php +++ b/src/Database/Migrations/Table.php @@ -112,6 +112,11 @@ protected function addForeignKeyWithAdapter(ForeignKey $foreignKey): ForeignKey return $foreignKey; } + public function getUniqueColumns(): array + { + return array_filter($this->columns, fn ($column): bool => $column->isUnique()); + } + protected function addColumnFromBuilders(): void { foreach ($this->columns as $column) { @@ -126,5 +131,10 @@ protected function addColumnFromBuilders(): void $foreignKey->getOptions() ); } + + foreach ($this->getUniqueColumns() as $column) { + $this->addIndex([$column->getName()], ['unique' => true]); + } + } } diff --git a/tests/Unit/Database/Migrations/TableTest.php b/tests/Unit/Database/Migrations/TableTest.php index f0b18a5b..730815f8 100644 --- a/tests/Unit/Database/Migrations/TableTest.php +++ b/tests/Unit/Database/Migrations/TableTest.php @@ -1068,3 +1068,76 @@ expect($foreignKey->getOptions()['delete'])->toBe('SET_NULL'); expect($foreignKey->getOptions()['constraint'])->toBe('fk_post_author'); }); + +it('can mark a column as unique', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->string('email')->unique(); + + expect($column->isUnique())->toBeTrue(); + + $uniqueColumns = $table->getUniqueColumns(); + + expect(count($uniqueColumns))->toBe(1); + expect($uniqueColumns[0]->getName())->toBe('email'); +}); + +it('can mark multiple columns as unique', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $table->string('username')->unique(); + $table->string('email')->unique(); + $table->integer('user_id')->unique(); + + $uniqueColumns = $table->getUniqueColumns(); + + expect(count($uniqueColumns))->toBe(3); + + $columnNames = array_map(fn ($column) => $column->getName(), $uniqueColumns); + + expect($columnNames)->toContain('username'); + expect($columnNames)->toContain('email'); + expect($columnNames)->toContain('user_id'); +}); + +it('can identify non-unique columns', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $uniqueColumn = $table->string('email')->unique(); + $regularColumn = $table->string('name'); + + expect($uniqueColumn->isUnique())->toBeTrue(); + expect($regularColumn->isUnique())->toBeFalse(); + + $uniqueColumns = $table->getUniqueColumns(); + + expect(count($uniqueColumns))->toBe(1); + expect($uniqueColumns[0]->getName())->toBe('email'); +}); + +it('creates unique indexes when building columns', function (): void { + $table = $this->getMockBuilder(Table::class) + ->setConstructorArgs(['users', [], $this->mockAdapter]) + ->onlyMethods(['addIndex', 'addColumn']) + ->getMock(); + + $table->expects($this->exactly(3)) + ->method('addColumn') + ->willReturn($table); + + $table->expects($this->exactly(2)) + ->method('addIndex') + ->withConsecutive( + [['username'], ['unique' => true]], + [['email'], ['unique' => true]] + ); + + $table->string('username')->unique(); + $table->string('email')->unique(); + $table->string('name'); + + $reflection = new ReflectionClass($table); + $method = $reflection->getMethod('addColumnFromBuilders'); + $method->setAccessible(true); + $method->invoke($table); +}); diff --git a/tests/Unit/Queue/ParallelQueueTest.php b/tests/Unit/Queue/ParallelQueueTest.php index b4f75bd1..15c49544 100644 --- a/tests/Unit/Queue/ParallelQueueTest.php +++ b/tests/Unit/Queue/ParallelQueueTest.php @@ -381,7 +381,7 @@ } // Wait for all tasks to complete - delay(5.0); + delay(6.0); // Eventually all tasks should be processed $this->assertSame(0, $parallelQueue->size());