diff --git a/src/Database/Migration.php b/src/Database/Migration.php index d6283623..967f8c0e 100644 --- a/src/Database/Migration.php +++ b/src/Database/Migration.php @@ -4,9 +4,16 @@ namespace Phenix\Database; +use Phenix\Database\Migrations\Table; use Phinx\Migration\AbstractMigration; abstract class Migration extends AbstractMigration { - // .. + public function table(string $tableName, array $options = []): Table + { + $table = new Table($tableName, $options, $this->getAdapter()); + $this->tables[] = $table; + + return $table; + } } diff --git a/src/Database/Migrations/Columns/BigInteger.php b/src/Database/Migrations/Columns/BigInteger.php new file mode 100644 index 00000000..78cd4078 --- /dev/null +++ b/src/Database/Migrations/Columns/BigInteger.php @@ -0,0 +1,26 @@ +options['signed'] = true; + } + + public function getType(): string + { + return 'biginteger'; + } +} diff --git a/src/Database/Migrations/Columns/Binary.php b/src/Database/Migrations/Columns/Binary.php new file mode 100644 index 00000000..931ea861 --- /dev/null +++ b/src/Database/Migrations/Columns/Binary.php @@ -0,0 +1,31 @@ +options['limit'] = $limit; + } + } + + public function getType(): string + { + return 'binary'; + } + + public function default(string $value): static + { + $this->options['default'] = $value; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Bit.php b/src/Database/Migrations/Columns/Bit.php new file mode 100644 index 00000000..59349c11 --- /dev/null +++ b/src/Database/Migrations/Columns/Bit.php @@ -0,0 +1,41 @@ +options['limit'] = $limit; + } + + public function getType(): string + { + return 'bit'; + } + + public function limit(int $limit): static + { + if ($limit < 1 || $limit > 64) { + throw new InvalidArgumentException('Bit limit must be between 1 and 64'); + } + + $this->options['limit'] = $limit; + + return $this; + } + + public function default(int $default): static + { + $this->options['default'] = $default; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Blob.php b/src/Database/Migrations/Columns/Blob.php new file mode 100644 index 00000000..48c8b073 --- /dev/null +++ b/src/Database/Migrations/Columns/Blob.php @@ -0,0 +1,69 @@ +options['limit'] = $limit; + } + } + + public function getType(): string + { + return 'blob'; + } + + public function limit(int $limit): static + { + $this->options['limit'] = $limit; + + return $this; + } + + public function tiny(): static + { + if ($this->isMysql()) { + $this->options['limit'] = MysqlAdapter::BLOB_TINY; + } + + return $this; + } + + public function regular(): static + { + if ($this->isMysql()) { + $this->options['limit'] = MysqlAdapter::BLOB_REGULAR; + } + + return $this; + } + + public function medium(): static + { + if ($this->isMysql()) { + $this->options['limit'] = MysqlAdapter::BLOB_MEDIUM; + } + + return $this; + } + + public function long(): static + { + if ($this->isMysql()) { + $this->options['limit'] = MysqlAdapter::BLOB_LONG; + } + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Boolean.php b/src/Database/Migrations/Columns/Boolean.php new file mode 100644 index 00000000..c2606bec --- /dev/null +++ b/src/Database/Migrations/Columns/Boolean.php @@ -0,0 +1,20 @@ +options['default'] = $value; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Char.php b/src/Database/Migrations/Columns/Char.php new file mode 100644 index 00000000..332bd5be --- /dev/null +++ b/src/Database/Migrations/Columns/Char.php @@ -0,0 +1,53 @@ +options['limit'] = $limit; + } + + public function getType(): string + { + return 'char'; + } + + public function limit(int $limit): static + { + $this->options['limit'] = $limit; + + return $this; + } + + public function collation(string $collation): static + { + if ($this->isMysql()) { + $this->options['collation'] = $collation; + } + + return $this; + } + + public function encoding(string $encoding): static + { + if ($this->isMysql()) { + $this->options['encoding'] = $encoding; + } + + return $this; + } + + public function default(string $default): static + { + $this->options['default'] = $default; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Cidr.php b/src/Database/Migrations/Columns/Cidr.php new file mode 100644 index 00000000..130a1228 --- /dev/null +++ b/src/Database/Migrations/Columns/Cidr.php @@ -0,0 +1,20 @@ +options['default'] = $default; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Column.php b/src/Database/Migrations/Columns/Column.php new file mode 100644 index 00000000..441f4067 --- /dev/null +++ b/src/Database/Migrations/Columns/Column.php @@ -0,0 +1,144 @@ +options['null'] = false; + } + + public function getName(): string + { + return $this->name; + } + + public function getOptions(): array + { + return $this->options; + } + + abstract public function getType(): string; + + public function nullable(): static + { + $this->options['null'] = true; + + return $this; + } + + public function comment(string $comment): static + { + $this->options['comment'] = $comment; + + return $this; + } + + public function after(string $column): static + { + $this->options['after'] = $column; + + return $this; + } + + public function first(): static + { + $this->options['after'] = MysqlAdapter::FIRST; + + return $this; + } + + public function collation(string $collation): static + { + if ($this->isMysql()) { + $this->options['collation'] = $collation; + } + + return $this; + } + + public function encoding(string $encoding): static + { + if ($this->isMysql()) { + $this->options['encoding'] = $encoding; + } + + return $this; + } + + public function timezone(bool $timezone = true): static + { + if ($this->isPostgres()) { + $this->options['timezone'] = $timezone; + } + + return $this; + } + + public function update(string $update): static + { + if ($this->isMysql()) { + $this->options['update'] = $update; + } + + return $this; + } + + public function length(int $length): static + { + return $this->limit($length); + } + + public function limit(int $limit): static + { + $this->options['limit'] = $limit; + + return $this; + } + + public function setAdapter(AdapterInterface $adapter): static + { + $this->adapter = $adapter; + + return $this; + } + + public function getAdapter(): ?AdapterInterface + { + return $this->adapter; + } + + public function isMysql(): bool + { + return $this->adapter instanceof MysqlAdapter; + } + + public function isPostgres(): bool + { + return $this->adapter instanceof PostgresAdapter; + } + + public function isSQLite(): bool + { + return $this->adapter instanceof SQLiteAdapter; + } + + public function isSqlServer(): bool + { + return $this->adapter instanceof SqlServerAdapter; + } +} diff --git a/src/Database/Migrations/Columns/Concerns/HasSign.php b/src/Database/Migrations/Columns/Concerns/HasSign.php new file mode 100644 index 00000000..188f5488 --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/HasSign.php @@ -0,0 +1,22 @@ +options['signed'] = false; + + return $this; + } + + public function signed(): static + { + $this->options['signed'] = true; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Concerns/WithBinary.php b/src/Database/Migrations/Columns/Concerns/WithBinary.php new file mode 100644 index 00000000..46102210 --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/WithBinary.php @@ -0,0 +1,27 @@ +addColumnWithAdapter(new Binary($name, $limit)); + } + + public function blob(string $name, int|null $limit = null): Blob + { + return $this->addColumnWithAdapter(new Blob($name, $limit)); + } + + public function bit(string $name, int $limit = 1): Bit + { + return $this->addColumnWithAdapter(new Bit($name, $limit)); + } +} diff --git a/src/Database/Migrations/Columns/Concerns/WithConvenience.php b/src/Database/Migrations/Columns/Concerns/WithConvenience.php new file mode 100644 index 00000000..c76ed3da --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/WithConvenience.php @@ -0,0 +1,27 @@ +addColumnWithAdapter(new UnsignedInteger($name, null, true)); + } + + public function timestamps(bool $timezone = false): self + { + $createdAt = $this->addColumnWithAdapter(new Timestamp('created_at', $timezone)); + $createdAt->nullable()->currentTimestamp(); + + $updatedAt = $this->addColumnWithAdapter(new Timestamp('updated_at', $timezone)); + $updatedAt->nullable()->onUpdateCurrentTimestamp(); + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Concerns/WithDateTime.php b/src/Database/Migrations/Columns/Concerns/WithDateTime.php new file mode 100644 index 00000000..a18a0ff5 --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/WithDateTime.php @@ -0,0 +1,39 @@ +addColumnWithAdapter(new DateTime($name)); + } + + public function date(string $name): Date + { + return $this->addColumnWithAdapter(new Date($name)); + } + + public function time(string $name, bool $timezone = false): Time + { + return $this->addColumnWithAdapter(new Time($name, $timezone)); + } + + public function timestamp(string $name, bool $timezone = false): Timestamp + { + return $this->addColumnWithAdapter(new Timestamp($name, $timezone)); + } + + public function interval(string $name): Interval + { + return $this->addColumnWithAdapter(new Interval($name)); + } +} diff --git a/src/Database/Migrations/Columns/Concerns/WithJson.php b/src/Database/Migrations/Columns/Concerns/WithJson.php new file mode 100644 index 00000000..d31892d0 --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/WithJson.php @@ -0,0 +1,21 @@ +addColumnWithAdapter(new Json($name)); + } + + public function jsonb(string $name): JsonB + { + return $this->addColumnWithAdapter(new JsonB($name)); + } +} diff --git a/src/Database/Migrations/Columns/Concerns/WithNetwork.php b/src/Database/Migrations/Columns/Concerns/WithNetwork.php new file mode 100644 index 00000000..3138aaf0 --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/WithNetwork.php @@ -0,0 +1,27 @@ +addColumnWithAdapter(new Inet($name)); + } + + public function cidr(string $name): Cidr + { + return $this->addColumnWithAdapter(new Cidr($name)); + } + + public function macaddr(string $name): MacAddr + { + return $this->addColumnWithAdapter(new MacAddr($name)); + } +} diff --git a/src/Database/Migrations/Columns/Concerns/WithNumeric.php b/src/Database/Migrations/Columns/Concerns/WithNumeric.php new file mode 100644 index 00000000..3cdc1c7a --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/WithNumeric.php @@ -0,0 +1,75 @@ +addColumnWithAdapter(new Integer($name, $limit, $identity)); + } + + public function bigInteger(string $name, bool $identity = false): BigInteger + { + return $this->addColumnWithAdapter(new BigInteger($name, $identity)); + } + + public function smallInteger(string $name, bool $identity = false): SmallInteger + { + return $this->addColumnWithAdapter(new SmallInteger($name, $identity)); + } + + public function unsignedInteger(string $name, int|null $limit = null, bool $identity = false): UnsignedInteger + { + return $this->addColumnWithAdapter(new UnsignedInteger($name, $limit, $identity)); + } + + public function unsignedBigInteger(string $name, bool $identity = false): UnsignedBigInteger + { + return $this->addColumnWithAdapter(new UnsignedBigInteger($name, $identity)); + } + + public function unsignedSmallInteger(string $name, bool $identity = false): UnsignedSmallInteger + { + return $this->addColumnWithAdapter(new UnsignedSmallInteger($name, $identity)); + } + + public function decimal(string $name, int $precision = 10, int $scale = 2): Decimal + { + return $this->addColumnWithAdapter(new Decimal($name, $precision, $scale)); + } + + public function unsignedDecimal(string $name, int $precision = 10, int $scale = 2): UnsignedDecimal + { + return $this->addColumnWithAdapter(new UnsignedDecimal($name, $precision, $scale)); + } + + public function float(string $name): Floating + { + return $this->addColumnWithAdapter(new Floating($name)); + } + + public function unsignedFloat(string $name): UnsignedFloat + { + return $this->addColumnWithAdapter(new UnsignedFloat($name)); + } + + public function double(string $name, bool $signed = true): Double + { + return $this->addColumnWithAdapter(new Double($name, $signed)); + } +} diff --git a/src/Database/Migrations/Columns/Concerns/WithSpecial.php b/src/Database/Migrations/Columns/Concerns/WithSpecial.php new file mode 100644 index 00000000..106a3e2e --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/WithSpecial.php @@ -0,0 +1,39 @@ +addColumnWithAdapter(new Boolean($name)); + } + + public function uuid(string $name): Uuid + { + return $this->addColumnWithAdapter(new Uuid($name)); + } + + public function ulid(string $name): Ulid + { + return $this->addColumnWithAdapter(new Ulid($name)); + } + + public function enum(string $name, array $values): Enum + { + return $this->addColumnWithAdapter(new Enum($name, $values)); + } + + public function set(string $name, array $values): Set + { + return $this->addColumnWithAdapter(new Set($name, $values)); + } +} diff --git a/src/Database/Migrations/Columns/Concerns/WithText.php b/src/Database/Migrations/Columns/Concerns/WithText.php new file mode 100644 index 00000000..55abd51d --- /dev/null +++ b/src/Database/Migrations/Columns/Concerns/WithText.php @@ -0,0 +1,27 @@ +addColumnWithAdapter(new Str($name, $limit)); + } + + public function text(string $name, int|null $limit = null): Text + { + return $this->addColumnWithAdapter(new Text($name, $limit)); + } + + public function char(string $name, int $limit = 255): Char + { + return $this->addColumnWithAdapter(new Char($name, $limit)); + } +} diff --git a/src/Database/Migrations/Columns/Date.php b/src/Database/Migrations/Columns/Date.php new file mode 100644 index 00000000..60fe6b4c --- /dev/null +++ b/src/Database/Migrations/Columns/Date.php @@ -0,0 +1,20 @@ +options['default'] = $value; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/DateTime.php b/src/Database/Migrations/Columns/DateTime.php new file mode 100644 index 00000000..5b4f7ba2 --- /dev/null +++ b/src/Database/Migrations/Columns/DateTime.php @@ -0,0 +1,20 @@ +options['default'] = $value; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Decimal.php b/src/Database/Migrations/Columns/Decimal.php new file mode 100644 index 00000000..67e8257c --- /dev/null +++ b/src/Database/Migrations/Columns/Decimal.php @@ -0,0 +1,49 @@ +options['precision'] = $precision; + $this->options['scale'] = $scale; + $this->options['signed'] = true; + } + + public function getType(): string + { + return 'decimal'; + } + + public function default(float $value): static + { + $this->options['default'] = $value; + + return $this; + } + + public function precision(int $precision): static + { + $this->options['precision'] = $precision; + + return $this; + } + + public function scale(int $scale): static + { + $this->options['scale'] = $scale; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Double.php b/src/Database/Migrations/Columns/Double.php new file mode 100644 index 00000000..39ee0554 --- /dev/null +++ b/src/Database/Migrations/Columns/Double.php @@ -0,0 +1,46 @@ +options['signed'] = $signed; + } + + public function getType(): string + { + return 'double'; + } + + public function default(float|int $default): static + { + $this->options['default'] = $default; + + return $this; + } + + public function unsigned(): static + { + if ($this->isMysql()) { + $this->options['signed'] = false; + } + + return $this; + } + + public function signed(): static + { + if ($this->isMysql()) { + $this->options['signed'] = true; + } + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Enum.php b/src/Database/Migrations/Columns/Enum.php new file mode 100644 index 00000000..bb3283dd --- /dev/null +++ b/src/Database/Migrations/Columns/Enum.php @@ -0,0 +1,35 @@ +options['values'] = $values; + } + + public function getType(): string + { + return 'enum'; + } + + public function default(string $value): static + { + $this->options['default'] = $value; + + return $this; + } + + public function values(array $values): static + { + $this->options['values'] = $values; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Floating.php b/src/Database/Migrations/Columns/Floating.php new file mode 100644 index 00000000..cb509a46 --- /dev/null +++ b/src/Database/Migrations/Columns/Floating.php @@ -0,0 +1,20 @@ +options['default'] = $value; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Inet.php b/src/Database/Migrations/Columns/Inet.php new file mode 100644 index 00000000..ce56a00f --- /dev/null +++ b/src/Database/Migrations/Columns/Inet.php @@ -0,0 +1,20 @@ +options['default'] = $default; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Integer.php b/src/Database/Migrations/Columns/Integer.php new file mode 100644 index 00000000..d654a230 --- /dev/null +++ b/src/Database/Migrations/Columns/Integer.php @@ -0,0 +1,22 @@ +options['signed'] = true; + } +} diff --git a/src/Database/Migrations/Columns/Interval.php b/src/Database/Migrations/Columns/Interval.php new file mode 100644 index 00000000..c903827e --- /dev/null +++ b/src/Database/Migrations/Columns/Interval.php @@ -0,0 +1,20 @@ +options['default'] = $default; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Json.php b/src/Database/Migrations/Columns/Json.php new file mode 100644 index 00000000..b043cc98 --- /dev/null +++ b/src/Database/Migrations/Columns/Json.php @@ -0,0 +1,20 @@ +options['default'] = $value; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/JsonB.php b/src/Database/Migrations/Columns/JsonB.php new file mode 100644 index 00000000..0f2ceb95 --- /dev/null +++ b/src/Database/Migrations/Columns/JsonB.php @@ -0,0 +1,20 @@ +options['default'] = $default; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/MacAddr.php b/src/Database/Migrations/Columns/MacAddr.php new file mode 100644 index 00000000..7b7d3ada --- /dev/null +++ b/src/Database/Migrations/Columns/MacAddr.php @@ -0,0 +1,20 @@ +options['default'] = $default; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Number.php b/src/Database/Migrations/Columns/Number.php new file mode 100644 index 00000000..019926d2 --- /dev/null +++ b/src/Database/Migrations/Columns/Number.php @@ -0,0 +1,22 @@ +options['default'] = $value; + + return $this; + } + + public function identity(): static + { + $this->options['identity'] = true; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Set.php b/src/Database/Migrations/Columns/Set.php new file mode 100644 index 00000000..f408ba2a --- /dev/null +++ b/src/Database/Migrations/Columns/Set.php @@ -0,0 +1,53 @@ +options['values'] = $values; + } + + public function getType(): string + { + return 'set'; + } + + public function values(array $values): static + { + $this->options['values'] = $values; + + return $this; + } + + public function default(string|array $default): static + { + $this->options['default'] = $default; + + return $this; + } + + public function collation(string $collation): static + { + if ($this->isMysql()) { + $this->options['collation'] = $collation; + } + + return $this; + } + + public function encoding(string $encoding): static + { + if ($this->isMysql()) { + $this->options['encoding'] = $encoding; + } + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/SmallInteger.php b/src/Database/Migrations/Columns/SmallInteger.php new file mode 100644 index 00000000..4c4d6db6 --- /dev/null +++ b/src/Database/Migrations/Columns/SmallInteger.php @@ -0,0 +1,48 @@ +options['identity'] = true; + } + + if (! $signed) { + $this->options['signed'] = false; + } + } + + public function getType(): string + { + return 'smallinteger'; + } + + public function default(int $value): static + { + $this->options['default'] = $value; + + return $this; + } + + public function identity(): static + { + $this->options['identity'] = true; + $this->options['null'] = false; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Str.php b/src/Database/Migrations/Columns/Str.php new file mode 100644 index 00000000..89f6ae52 --- /dev/null +++ b/src/Database/Migrations/Columns/Str.php @@ -0,0 +1,42 @@ +options['limit'] = $limit; + } + + public function getType(): string + { + return 'string'; + } + + public function default(string $value): static + { + $this->options['default'] = $value; + + return $this; + } + + public function collation(string $collation): static + { + $this->options['collation'] = $collation; + + return $this; + } + + public function encoding(string $encoding): static + { + $this->options['encoding'] = $encoding; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Text.php b/src/Database/Migrations/Columns/Text.php new file mode 100644 index 00000000..37304a5f --- /dev/null +++ b/src/Database/Migrations/Columns/Text.php @@ -0,0 +1,44 @@ +options['limit'] = $limit; + } + } + + public function getType(): string + { + return 'text'; + } + + public function default(string $value): static + { + $this->options['default'] = $value; + + return $this; + } + + public function collation(string $collation): static + { + $this->options['collation'] = $collation; + + return $this; + } + + public function encoding(string $encoding): static + { + $this->options['encoding'] = $encoding; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Time.php b/src/Database/Migrations/Columns/Time.php new file mode 100644 index 00000000..3392a100 --- /dev/null +++ b/src/Database/Migrations/Columns/Time.php @@ -0,0 +1,40 @@ +isPostgres()) { + $this->options['timezone'] = true; + } + } + + public function getType(): string + { + return 'time'; + } + + public function withTimezone(bool $timezone = true): static + { + if ($this->isPostgres()) { + $this->options['timezone'] = $timezone; + } + + return $this; + } + + public function default(string $default): static + { + $this->options['default'] = $default; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Timestamp.php b/src/Database/Migrations/Columns/Timestamp.php new file mode 100644 index 00000000..ef6dbe6d --- /dev/null +++ b/src/Database/Migrations/Columns/Timestamp.php @@ -0,0 +1,58 @@ +options['timezone'] = true; + } + } + + public function getType(): string + { + return 'timestamp'; + } + + public function default(string $value): static + { + $this->options['default'] = $value; + + return $this; + } + + public function timezone(bool $timezone = true): static + { + $this->options['timezone'] = $timezone; + + return $this; + } + + public function update(string $action): static + { + $this->options['update'] = $action; + + return $this; + } + + public function currentTimestamp(): static + { + $this->options['default'] = 'CURRENT_TIMESTAMP'; + + return $this; + } + + public function onUpdateCurrentTimestamp(): static + { + $this->options['update'] = 'CURRENT_TIMESTAMP'; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Ulid.php b/src/Database/Migrations/Columns/Ulid.php new file mode 100644 index 00000000..102bf7f6 --- /dev/null +++ b/src/Database/Migrations/Columns/Ulid.php @@ -0,0 +1,37 @@ +options['limit'] = 26; + } + + public function getType(): string + { + return 'string'; + } + + public function default(string $value): static + { + $this->options['default'] = $value; + + return $this; + } + + public function limit(int $limit): static + { + return $this; + } + + public function length(int $length): static + { + return $this; + } +} diff --git a/src/Database/Migrations/Columns/UnsignedBigInteger.php b/src/Database/Migrations/Columns/UnsignedBigInteger.php new file mode 100644 index 00000000..0ac04ebe --- /dev/null +++ b/src/Database/Migrations/Columns/UnsignedBigInteger.php @@ -0,0 +1,30 @@ +options['signed'] = false; + + if ($identity) { + $this->options['identity'] = true; + } + } + + public function getType(): string + { + return 'biginteger'; + } +} diff --git a/src/Database/Migrations/Columns/UnsignedDecimal.php b/src/Database/Migrations/Columns/UnsignedDecimal.php new file mode 100644 index 00000000..780e01ef --- /dev/null +++ b/src/Database/Migrations/Columns/UnsignedDecimal.php @@ -0,0 +1,45 @@ +options['precision'] = $precision; + $this->options['scale'] = $scale; + $this->options['signed'] = false; + } + + public function getType(): string + { + return 'decimal'; + } + + public function default(float $value): static + { + $this->options['default'] = $value; + + return $this; + } + + public function precision(int $precision): static + { + $this->options['precision'] = $precision; + + return $this; + } + + public function scale(int $scale): static + { + $this->options['scale'] = $scale; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/UnsignedFloat.php b/src/Database/Migrations/Columns/UnsignedFloat.php new file mode 100644 index 00000000..36831afe --- /dev/null +++ b/src/Database/Migrations/Columns/UnsignedFloat.php @@ -0,0 +1,27 @@ +options['signed'] = false; + } + + public function getType(): string + { + return 'float'; + } + + public function default(float $value): static + { + $this->options['default'] = $value; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/UnsignedInteger.php b/src/Database/Migrations/Columns/UnsignedInteger.php new file mode 100644 index 00000000..f414f6e4 --- /dev/null +++ b/src/Database/Migrations/Columns/UnsignedInteger.php @@ -0,0 +1,31 @@ +options['signed'] = false; + + if ($limit) { + $this->options['limit'] = $limit; + } + + if ($identity) { + $this->options['identity'] = true; + } + } + + public function getType(): string + { + return 'integer'; + } +} diff --git a/src/Database/Migrations/Columns/UnsignedSmallInteger.php b/src/Database/Migrations/Columns/UnsignedSmallInteger.php new file mode 100644 index 00000000..1a844102 --- /dev/null +++ b/src/Database/Migrations/Columns/UnsignedSmallInteger.php @@ -0,0 +1,33 @@ +options['signed'] = false; + + if ($identity) { + $this->options['identity'] = true; + } + } + + public function getType(): string + { + return 'smallinteger'; + } + + public function identity(): static + { + $this->options['identity'] = true; + $this->options['null'] = false; + + return $this; + } +} diff --git a/src/Database/Migrations/Columns/Uuid.php b/src/Database/Migrations/Columns/Uuid.php new file mode 100644 index 00000000..f311eb70 --- /dev/null +++ b/src/Database/Migrations/Columns/Uuid.php @@ -0,0 +1,20 @@ +options['default'] = $value; + + return $this; + } +} diff --git a/src/Database/Migrations/Table.php b/src/Database/Migrations/Table.php new file mode 100644 index 00000000..cf3024c3 --- /dev/null +++ b/src/Database/Migrations/Table.php @@ -0,0 +1,52 @@ + + */ + protected array $columns = []; + + public function getColumnBuilders(): array + { + return $this->columns; + } + + /** + * @template T of Column + * @param T $column + * @return T + */ + protected function addColumnWithAdapter(Column $column): Column + { + $column->setAdapter($this->getAdapter()); + + $this->columns[] = $column; + + return $column; + } +} diff --git a/tests/Unit/Database/Migrations/Columns/BitTest.php b/tests/Unit/Database/Migrations/Columns/BitTest.php new file mode 100644 index 00000000..0712cf2b --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/BitTest.php @@ -0,0 +1,72 @@ +getName())->toBe('flags'); + expect($column->getType())->toBe('bit'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 1, + ]); +}); + +it('can create bit column with custom limit', function (): void { + $column = new Bit('permissions', 8); + + expect($column->getOptions()['limit'])->toBe(8); +}); + +it('can set limit after creation', function (): void { + $column = new Bit('flags'); + $column->limit(16); + + expect($column->getOptions()['limit'])->toBe(16); +}); + +it('throws exception when limit is less than 1', function (): void { + $column = new Bit('flags'); + + try { + $column->limit(0); + $this->fail('Expected InvalidArgumentException was not thrown'); + } catch (\InvalidArgumentException $e) { + expect($e->getMessage())->toBe('Bit limit must be between 1 and 64'); + } +}); + +it('throws exception when limit is greater than 64', function (): void { + $column = new Bit('flags'); + + try { + $column->limit(65); + $this->fail('Expected InvalidArgumentException was not thrown'); + } catch (\InvalidArgumentException $e) { + expect($e->getMessage())->toBe('Bit limit must be between 1 and 64'); + } +}); + +it('can set default value', function (): void { + $column = new Bit('flags'); + $column->default(1); + + expect($column->getOptions()['default'])->toBe(1); +}); + +it('can be nullable', function (): void { + $column = new Bit('flags'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Bit('flags'); + $column->comment('Status flags'); + + expect($column->getOptions()['comment'])->toBe('Status flags'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/BlobTest.php b/tests/Unit/Database/Migrations/Columns/BlobTest.php new file mode 100644 index 00000000..35ed48ca --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/BlobTest.php @@ -0,0 +1,121 @@ +mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + + $this->mockMysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->mockPostgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); +}); + +it('can create blob column without limit', function (): void { + $column = new Blob('data'); + + expect($column->getName())->toBe('data'); + expect($column->getType())->toBe('blob'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can create blob column with limit', function (): void { + $column = new Blob('file_data', 1024); + + expect($column->getOptions()['limit'])->toBe(1024); +}); + +it('can set limit after creation', function (): void { + $column = new Blob('data'); + $column->limit(2048); + + expect($column->getOptions()['limit'])->toBe(2048); +}); + +it('can set tiny blob for mysql', function (): void { + $column = new Blob('data'); + $column->setAdapter($this->mockMysqlAdapter); + $column->tiny(); + + expect($column->getOptions()['limit'])->toBe(MysqlAdapter::BLOB_TINY); +}); + +it('ignores tiny blob for non-mysql adapters', function (): void { + $column = new Blob('data'); + $column->setAdapter($this->mockPostgresAdapter); + $column->tiny(); + + expect($column->getOptions())->not->toHaveKey('limit'); +}); + +it('can set regular blob for mysql', function (): void { + $column = new Blob('data'); + $column->setAdapter($this->mockMysqlAdapter); + $column->regular(); + + expect($column->getOptions()['limit'])->toBe(MysqlAdapter::BLOB_REGULAR); +}); + +it('ignores regular blob for non-mysql adapters', function (): void { + $column = new Blob('data'); + $column->setAdapter($this->mockPostgresAdapter); + $column->regular(); + + expect($column->getOptions())->not->toHaveKey('limit'); +}); + +it('can set medium blob for mysql', function (): void { + $column = new Blob('data'); + $column->setAdapter($this->mockMysqlAdapter); + $column->medium(); + + expect($column->getOptions()['limit'])->toBe(MysqlAdapter::BLOB_MEDIUM); +}); + +it('ignores medium blob for non-mysql adapters', function (): void { + $column = new Blob('data'); + $column->setAdapter($this->mockPostgresAdapter); + $column->medium(); + + expect($column->getOptions())->not->toHaveKey('limit'); +}); + +it('can set long blob for mysql', function (): void { + $column = new Blob('data'); + $column->setAdapter($this->mockMysqlAdapter); + $column->long(); + + expect($column->getOptions()['limit'])->toBe(MysqlAdapter::BLOB_LONG); +}); + +it('ignores long blob for non-mysql adapters', function (): void { + $column = new Blob('data'); + $column->setAdapter($this->mockPostgresAdapter); + $column->long(); + + expect($column->getOptions())->not->toHaveKey('limit'); +}); + +it('can be nullable', function (): void { + $column = new Blob('data'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Blob('data'); + $column->comment('Binary data'); + + expect($column->getOptions()['comment'])->toBe('Binary data'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/CharTest.php b/tests/Unit/Database/Migrations/Columns/CharTest.php new file mode 100644 index 00000000..5e0c4c02 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/CharTest.php @@ -0,0 +1,97 @@ +mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + + $this->mockMysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->mockPostgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); +}); + +it('can create char column with default limit', function (): void { + $column = new Char('code'); + + expect($column->getName())->toBe('code'); + expect($column->getType())->toBe('char'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 255, + ]); +}); + +it('can create char column with custom limit', function (): void { + $column = new Char('status', 10); + + expect($column->getOptions()['limit'])->toBe(10); +}); + +it('can set limit after creation', function (): void { + $column = new Char('code'); + $column->limit(50); + + expect($column->getOptions()['limit'])->toBe(50); +}); + +it('can set default value', function (): void { + $column = new Char('status'); + $column->default('A'); + + expect($column->getOptions()['default'])->toBe('A'); +}); + +it('can set collation for mysql', function (): void { + $column = new Char('code'); + $column->setAdapter($this->mockMysqlAdapter); + $column->collation('utf8mb4_unicode_ci'); + + expect($column->getOptions()['collation'])->toBe('utf8mb4_unicode_ci'); +}); + +it('ignores collation for non-mysql adapters', function (): void { + $column = new Char('code'); + $column->setAdapter($this->mockPostgresAdapter); + $column->collation('utf8mb4_unicode_ci'); + + expect($column->getOptions())->not->toHaveKey('collation'); +}); + +it('can set encoding for mysql', function (): void { + $column = new Char('code'); + $column->setAdapter($this->mockMysqlAdapter); + $column->encoding('utf8mb4'); + + expect($column->getOptions()['encoding'])->toBe('utf8mb4'); +}); + +it('ignores encoding for non-mysql adapters', function (): void { + $column = new Char('code'); + $column->setAdapter($this->mockPostgresAdapter); + $column->encoding('utf8mb4'); + + expect($column->getOptions())->not->toHaveKey('encoding'); +}); + +it('can be nullable', function (): void { + $column = new Char('code'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Char('code'); + $column->comment('Status code'); + + expect($column->getOptions()['comment'])->toBe('Status code'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/CidrTest.php b/tests/Unit/Database/Migrations/Columns/CidrTest.php new file mode 100644 index 00000000..9f4b98b4 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/CidrTest.php @@ -0,0 +1,36 @@ +getName())->toBe('network'); + expect($column->getType())->toBe('cidr'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new Cidr('network'); + $column->default('192.168.0.0/24'); + + expect($column->getOptions()['default'])->toBe('192.168.0.0/24'); +}); + +it('can be nullable', function (): void { + $column = new Cidr('subnet'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Cidr('network'); + $column->comment('Network CIDR block'); + + expect($column->getOptions()['comment'])->toBe('Network CIDR block'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/DateTest.php b/tests/Unit/Database/Migrations/Columns/DateTest.php new file mode 100644 index 00000000..37539eb4 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/DateTest.php @@ -0,0 +1,36 @@ +getName())->toBe('birth_date'); + expect($column->getType())->toBe('date'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new Date('created_date'); + $column->default('2023-01-01'); + + expect($column->getOptions()['default'])->toBe('2023-01-01'); +}); + +it('can be nullable', function (): void { + $column = new Date('deleted_at'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Date('birth_date'); + $column->comment('User birth date'); + + expect($column->getOptions()['comment'])->toBe('User birth date'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/DateTimeTest.php b/tests/Unit/Database/Migrations/Columns/DateTimeTest.php new file mode 100644 index 00000000..aedc8075 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/DateTimeTest.php @@ -0,0 +1,36 @@ +getName())->toBe('created_at'); + expect($column->getType())->toBe('datetime'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new DateTime('published_at'); + $column->default('2023-01-01 12:00:00'); + + expect($column->getOptions()['default'])->toBe('2023-01-01 12:00:00'); +}); + +it('can be nullable', function (): void { + $column = new DateTime('deleted_at'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new DateTime('created_at'); + $column->comment('Creation timestamp'); + + expect($column->getOptions()['comment'])->toBe('Creation timestamp'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/DecimalTest.php b/tests/Unit/Database/Migrations/Columns/DecimalTest.php new file mode 100644 index 00000000..40f27630 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/DecimalTest.php @@ -0,0 +1,74 @@ +getName())->toBe('price'); + expect($column->getType())->toBe('decimal'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'precision' => 10, + 'scale' => 2, + 'signed' => true, + ]); +}); + +it('can create decimal column with custom precision and scale', function (): void { + $column = new Decimal('amount', 15, 4); + + expect($column->getOptions()['precision'])->toBe(15); + expect($column->getOptions()['scale'])->toBe(4); +}); + +it('can set default value', function (): void { + $column = new Decimal('price'); + $column->default(99.99); + + expect($column->getOptions()['default'])->toBe(99.99); +}); + +it('can set precision', function (): void { + $column = new Decimal('price'); + $column->precision(12); + + expect($column->getOptions()['precision'])->toBe(12); +}); + +it('can set scale', function (): void { + $column = new Decimal('price'); + $column->scale(4); + + expect($column->getOptions()['scale'])->toBe(4); +}); + +it('can be unsigned', function (): void { + $column = new Decimal('price'); + $column->unsigned(); + + expect($column->getOptions()['signed'])->toBeFalse(); +}); + +it('can be signed', function (): void { + $column = new Decimal('balance'); + $column->signed(); + + expect($column->getOptions()['signed'])->toBeTrue(); +}); + +it('can be nullable', function (): void { + $column = new Decimal('discount'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Decimal('price'); + $column->comment('Product price'); + + expect($column->getOptions()['comment'])->toBe('Product price'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/DoubleTest.php b/tests/Unit/Database/Migrations/Columns/DoubleTest.php new file mode 100644 index 00000000..0de51eac --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/DoubleTest.php @@ -0,0 +1,94 @@ +mockMysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->mockPostgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); +}); + +it('can create double column with default signed', function (): void { + $column = new Double('value'); + + expect($column->getName())->toBe('value'); + expect($column->getType())->toBe('double'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => true, + ]); +}); + +it('can create double column as unsigned', function (): void { + $column = new Double('value', false); + + expect($column->getOptions()['signed'])->toBeFalse(); +}); + +it('can set default value as float', function (): void { + $column = new Double('temperature'); + $column->default(98.6); + + expect($column->getOptions()['default'])->toBe(98.6); +}); + +it('can set default value as integer', function (): void { + $column = new Double('count'); + $column->default(100); + + expect($column->getOptions()['default'])->toBe(100); +}); + +it('can set unsigned for mysql', function (): void { + $column = new Double('value'); + $column->setAdapter($this->mockMysqlAdapter); + $column->unsigned(); + + expect($column->getOptions()['signed'])->toBeFalse(); +}); + +it('ignores unsigned for non-mysql adapters', function (): void { + $column = new Double('value'); + $column->setAdapter($this->mockPostgresAdapter); + $column->unsigned(); + + expect($column->getOptions()['signed'])->toBeTrue(); +}); + +it('can set signed for mysql', function (): void { + $column = new Double('value', false); + $column->setAdapter($this->mockMysqlAdapter); + $column->signed(); + + expect($column->getOptions()['signed'])->toBeTrue(); +}); + +it('ignores signed for non-mysql adapters', function (): void { + $column = new Double('value', false); + $column->setAdapter($this->mockPostgresAdapter); + $column->signed(); + + expect($column->getOptions()['signed'])->toBeFalse(); +}); + +it('can be nullable', function (): void { + $column = new Double('measurement'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Double('value'); + $column->comment('Measurement value'); + + expect($column->getOptions()['comment'])->toBe('Measurement value'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/EnumTest.php b/tests/Unit/Database/Migrations/Columns/EnumTest.php new file mode 100644 index 00000000..c6cc6841 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/EnumTest.php @@ -0,0 +1,44 @@ +getName())->toBe('status'); + expect($column->getType())->toBe('enum'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'values' => ['active', 'inactive', 'pending'], + ]); +}); + +it('can set default value', function (): void { + $column = new Enum('status', ['active', 'inactive']); + $column->default('active'); + + expect($column->getOptions()['default'])->toBe('active'); +}); + +it('can update values', function (): void { + $column = new Enum('role', ['user', 'admin']); + $column->values(['user', 'admin', 'moderator']); + + expect($column->getOptions()['values'])->toBe(['user', 'admin', 'moderator']); +}); + +it('can be nullable', function (): void { + $column = new Enum('status', ['active', 'inactive']); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Enum('status', ['active', 'inactive']); + $column->comment('User status'); + + expect($column->getOptions()['comment'])->toBe('User status'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/InetTest.php b/tests/Unit/Database/Migrations/Columns/InetTest.php new file mode 100644 index 00000000..9b5a7248 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/InetTest.php @@ -0,0 +1,36 @@ +getName())->toBe('ip_address'); + expect($column->getType())->toBe('inet'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new Inet('ip_address'); + $column->default('192.168.1.1'); + + expect($column->getOptions()['default'])->toBe('192.168.1.1'); +}); + +it('can be nullable', function (): void { + $column = new Inet('client_ip'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Inet('ip_address'); + $column->comment('Client IP address'); + + expect($column->getOptions()['comment'])->toBe('Client IP address'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/IntervalTest.php b/tests/Unit/Database/Migrations/Columns/IntervalTest.php new file mode 100644 index 00000000..5e09ed57 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/IntervalTest.php @@ -0,0 +1,36 @@ +getName())->toBe('duration'); + expect($column->getType())->toBe('interval'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new Interval('duration'); + $column->default('1 hour'); + + expect($column->getOptions()['default'])->toBe('1 hour'); +}); + +it('can be nullable', function (): void { + $column = new Interval('processing_time'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Interval('duration'); + $column->comment('Event duration'); + + expect($column->getOptions()['comment'])->toBe('Event duration'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/JsonBTest.php b/tests/Unit/Database/Migrations/Columns/JsonBTest.php new file mode 100644 index 00000000..16b6ad12 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/JsonBTest.php @@ -0,0 +1,43 @@ +getName())->toBe('metadata'); + expect($column->getType())->toBe('jsonb'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value as string', function (): void { + $column = new JsonB('settings'); + $column->default('{}'); + + expect($column->getOptions()['default'])->toBe('{}'); +}); + +it('can set default value as array', function (): void { + $column = new JsonB('config'); + $column->default(['key' => 'value']); + + expect($column->getOptions()['default'])->toBe(['key' => 'value']); +}); + +it('can be nullable', function (): void { + $column = new JsonB('data'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new JsonB('metadata'); + $column->comment('JSONB metadata'); + + expect($column->getOptions()['comment'])->toBe('JSONB metadata'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/JsonTest.php b/tests/Unit/Database/Migrations/Columns/JsonTest.php new file mode 100644 index 00000000..aaa750c7 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/JsonTest.php @@ -0,0 +1,36 @@ +getName())->toBe('metadata'); + expect($column->getType())->toBe('json'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new Json('settings'); + $column->default('{}'); + + expect($column->getOptions()['default'])->toBe('{}'); +}); + +it('can be nullable', function (): void { + $column = new Json('config'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Json('metadata'); + $column->comment('JSON metadata'); + + expect($column->getOptions()['comment'])->toBe('JSON metadata'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/MacAddrTest.php b/tests/Unit/Database/Migrations/Columns/MacAddrTest.php new file mode 100644 index 00000000..1e8502ba --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/MacAddrTest.php @@ -0,0 +1,36 @@ +getName())->toBe('mac_address'); + expect($column->getType())->toBe('macaddr'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new MacAddr('mac_address'); + $column->default('08:00:2b:01:02:03'); + + expect($column->getOptions()['default'])->toBe('08:00:2b:01:02:03'); +}); + +it('can be nullable', function (): void { + $column = new MacAddr('device_mac'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new MacAddr('mac_address'); + $column->comment('Device MAC address'); + + expect($column->getOptions()['comment'])->toBe('Device MAC address'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/NumberTest.php b/tests/Unit/Database/Migrations/Columns/NumberTest.php new file mode 100644 index 00000000..272d6b10 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/NumberTest.php @@ -0,0 +1,42 @@ +default(42); + + expect($column->getOptions()['default'])->toBe(42); +}); + +it('can set identity', function (): void { + $column = new TestNumber('test'); + $column->identity(); + + expect($column->getOptions()['identity'])->toBeTrue(); +}); + +it('can be nullable', function (): void { + $column = new TestNumber('test'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new TestNumber('test'); + $column->comment('Test number'); + + expect($column->getOptions()['comment'])->toBe('Test number'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/SetTest.php b/tests/Unit/Database/Migrations/Columns/SetTest.php new file mode 100644 index 00000000..87f396a1 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/SetTest.php @@ -0,0 +1,98 @@ +mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + + $this->mockMysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->mockPostgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); +}); + +it('can create set column with values', function (): void { + $column = new Set('permissions', ['read', 'write', 'execute']); + + expect($column->getName())->toBe('permissions'); + expect($column->getType())->toBe('set'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'values' => ['read', 'write', 'execute'], + ]); +}); + +it('can set default value as string', function (): void { + $column = new Set('status', ['active', 'inactive']); + $column->default('active'); + + expect($column->getOptions()['default'])->toBe('active'); +}); + +it('can set default value as array', function (): void { + $column = new Set('permissions', ['read', 'write', 'execute']); + $column->default(['read', 'write']); + + expect($column->getOptions()['default'])->toBe(['read', 'write']); +}); + +it('can update values', function (): void { + $column = new Set('permissions', ['read', 'write']); + $column->values(['read', 'write', 'execute', 'admin']); + + expect($column->getOptions()['values'])->toBe(['read', 'write', 'execute', 'admin']); +}); + +it('can set collation for mysql', function (): void { + $column = new Set('status', ['active', 'inactive']); + $column->setAdapter($this->mockMysqlAdapter); + $column->collation('utf8mb4_unicode_ci'); + + expect($column->getOptions()['collation'])->toBe('utf8mb4_unicode_ci'); +}); + +it('ignores collation for non-mysql adapters', function (): void { + $column = new Set('status', ['active', 'inactive']); + $column->setAdapter($this->mockPostgresAdapter); + $column->collation('utf8mb4_unicode_ci'); + + expect($column->getOptions())->not->toHaveKey('collation'); +}); + +it('can set encoding for mysql', function (): void { + $column = new Set('status', ['active', 'inactive']); + $column->setAdapter($this->mockMysqlAdapter); + $column->encoding('utf8mb4'); + + expect($column->getOptions()['encoding'])->toBe('utf8mb4'); +}); + +it('ignores encoding for non-mysql adapters', function (): void { + $column = new Set('status', ['active', 'inactive']); + $column->setAdapter($this->mockPostgresAdapter); + $column->encoding('utf8mb4'); + + expect($column->getOptions())->not->toHaveKey('encoding'); +}); + +it('can be nullable', function (): void { + $column = new Set('permissions', ['read', 'write']); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Set('permissions', ['read', 'write']); + $column->comment('User permissions'); + + expect($column->getOptions()['comment'])->toBe('User permissions'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/SmallIntegerTest.php b/tests/Unit/Database/Migrations/Columns/SmallIntegerTest.php new file mode 100644 index 00000000..0a8c05f4 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/SmallIntegerTest.php @@ -0,0 +1,76 @@ +getName())->toBe('status'); + expect($column->getType())->toBe('smallinteger'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can create small integer column with identity', function (): void { + $column = new SmallInteger('id', true); + + expect($column->getOptions())->toBe([ + 'null' => false, + 'identity' => true, + ]); +}); + +it('can create small integer column as unsigned', function (): void { + $column = new SmallInteger('count', false, false); + + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new SmallInteger('status'); + $column->default(1); + + expect($column->getOptions()['default'])->toBe(1); +}); + +it('can set identity', function (): void { + $column = new SmallInteger('id'); + $column->identity(); + + expect($column->getOptions()['identity'])->toBeTrue(); + expect($column->getOptions()['null'])->toBeFalse(); +}); + +it('can be unsigned', function (): void { + $column = new SmallInteger('count'); + $column->unsigned(); + + expect($column->getOptions()['signed'])->toBeFalse(); +}); + +it('can be signed', function (): void { + $column = new SmallInteger('balance'); + $column->signed(); + + expect($column->getOptions()['signed'])->toBeTrue(); +}); + +it('can be nullable', function (): void { + $column = new SmallInteger('priority'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new SmallInteger('status'); + $column->comment('Status code'); + + expect($column->getOptions()['comment'])->toBe('Status code'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/StrTest.php b/tests/Unit/Database/Migrations/Columns/StrTest.php new file mode 100644 index 00000000..88d29398 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/StrTest.php @@ -0,0 +1,86 @@ +mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + + $this->mockMysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->mockPostgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); +}); + +it('can create string column with default limit', function (): void { + $column = new Str('name'); + + expect($column->getName())->toBe('name'); + expect($column->getType())->toBe('string'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 255, + ]); +}); + +it('can create string column with custom limit', function (): void { + $column = new Str('username', 100); + + expect($column->getOptions()['limit'])->toBe(100); +}); + +it('can set default value', function (): void { + $column = new Str('status'); + $column->default('active'); + + expect($column->getOptions()['default'])->toBe('active'); +}); + +it('can set collation', function (): void { + $column = new Str('name'); + $column->collation('utf8mb4_unicode_ci'); + + expect($column->getOptions()['collation'])->toBe('utf8mb4_unicode_ci'); +}); + +it('can set encoding', function (): void { + $column = new Str('name'); + $column->encoding('utf8mb4'); + + expect($column->getOptions()['encoding'])->toBe('utf8mb4'); +}); + +it('can be nullable', function (): void { + $column = new Str('description'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Str('name'); + $column->comment('User name'); + + expect($column->getOptions()['comment'])->toBe('User name'); +}); + +it('can set limit after creation', function (): void { + $column = new Str('name'); + $column->limit(150); + + expect($column->getOptions()['limit'])->toBe(150); +}); + +it('can set length after creation', function (): void { + $column = new Str('name'); + $column->length(200); + + expect($column->getOptions()['limit'])->toBe(200); +}); diff --git a/tests/Unit/Database/Migrations/Columns/TextTest.php b/tests/Unit/Database/Migrations/Columns/TextTest.php new file mode 100644 index 00000000..72f38c7b --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/TextTest.php @@ -0,0 +1,63 @@ +getName())->toBe('content'); + expect($column->getType())->toBe('text'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can create text column with limit', function (): void { + $column = new Text('description', 1000); + + expect($column->getOptions()['limit'])->toBe(1000); +}); + +it('can set default value', function (): void { + $column = new Text('content'); + $column->default('Default content'); + + expect($column->getOptions()['default'])->toBe('Default content'); +}); + +it('can set collation', function (): void { + $column = new Text('content'); + $column->collation('utf8mb4_unicode_ci'); + + expect($column->getOptions()['collation'])->toBe('utf8mb4_unicode_ci'); +}); + +it('can set encoding', function (): void { + $column = new Text('content'); + $column->encoding('utf8mb4'); + + expect($column->getOptions()['encoding'])->toBe('utf8mb4'); +}); + +it('can be nullable', function (): void { + $column = new Text('notes'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Text('content'); + $column->comment('Post content'); + + expect($column->getOptions()['comment'])->toBe('Post content'); +}); + +it('can set limit after creation', function (): void { + $column = new Text('content'); + $column->limit(2000); + + expect($column->getOptions()['limit'])->toBe(2000); +}); diff --git a/tests/Unit/Database/Migrations/Columns/TimeTest.php b/tests/Unit/Database/Migrations/Columns/TimeTest.php new file mode 100644 index 00000000..b9594ac0 --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/TimeTest.php @@ -0,0 +1,73 @@ +mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + + $this->mockPostgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); +}); + +it('can create time column without timezone', function (): void { + $column = new Time('start_time'); + + expect($column->getName())->toBe('start_time'); + expect($column->getType())->toBe('time'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can create time column with timezone for postgres after setting adapter', function (): void { + $column = new Time('start_time', true); + $column->setAdapter($this->mockPostgresAdapter); + $column->withTimezone(true); + + expect($column->getOptions())->toBe([ + 'null' => false, + 'timezone' => true, + ]); +}); + +it('can set default value', function (): void { + $column = new Time('start_time'); + $column->default('09:00:00'); + + expect($column->getOptions()['default'])->toBe('09:00:00'); +}); + +it('can set timezone for postgres', function (): void { + $column = new Time('start_time'); + $column->setAdapter($this->mockPostgresAdapter); + $column->withTimezone(true); + + expect($column->getOptions()['timezone'])->toBeTrue(); +}); + +it('ignores timezone for non-postgres adapters', function (): void { + $column = new Time('start_time'); + $column->setAdapter($this->mockAdapter); + $column->withTimezone(true); + + expect($column->getOptions())->not->toHaveKey('timezone'); +}); + +it('can be nullable', function (): void { + $column = new Time('end_time'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Time('start_time'); + $column->comment('Event start time'); + + expect($column->getOptions()['comment'])->toBe('Event start time'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/TimestampTest.php b/tests/Unit/Database/Migrations/Columns/TimestampTest.php new file mode 100644 index 00000000..46adc96b --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/TimestampTest.php @@ -0,0 +1,80 @@ +getName())->toBe('created_at'); + expect($column->getType())->toBe('timestamp'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can create timestamp column with timezone', function (): void { + $column = new Timestamp('created_at', true); + + expect($column->getOptions())->toBe([ + 'null' => false, + 'timezone' => true, + ]); +}); + +it('can set default value', function (): void { + $column = new Timestamp('created_at'); + $column->default('2023-01-01 12:00:00'); + + expect($column->getOptions()['default'])->toBe('2023-01-01 12:00:00'); +}); + +it('can set timezone', function (): void { + $column = new Timestamp('created_at'); + $column->timezone(true); + + expect($column->getOptions()['timezone'])->toBeTrue(); +}); + +it('can disable timezone', function (): void { + $column = new Timestamp('created_at', true); + $column->timezone(false); + + expect($column->getOptions()['timezone'])->toBeFalse(); +}); + +it('can set update action', function (): void { + $column = new Timestamp('updated_at'); + $column->update('CURRENT_TIMESTAMP'); + + expect($column->getOptions()['update'])->toBe('CURRENT_TIMESTAMP'); +}); + +it('can use current timestamp as default', function (): void { + $column = new Timestamp('created_at'); + $column->currentTimestamp(); + + expect($column->getOptions()['default'])->toBe('CURRENT_TIMESTAMP'); +}); + +it('can use on update current timestamp', function (): void { + $column = new Timestamp('updated_at'); + $column->onUpdateCurrentTimestamp(); + + expect($column->getOptions()['update'])->toBe('CURRENT_TIMESTAMP'); +}); + +it('can be nullable', function (): void { + $column = new Timestamp('deleted_at'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Timestamp('created_at'); + $column->comment('Creation timestamp'); + + expect($column->getOptions()['comment'])->toBe('Creation timestamp'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/UlidTest.php b/tests/Unit/Database/Migrations/Columns/UlidTest.php new file mode 100644 index 00000000..2d4c38eb --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/UlidTest.php @@ -0,0 +1,52 @@ +getName())->toBe('ulid_field'); + expect($column->getType())->toBe('string'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 26, + ]); +}); + +it('can be nullable', function (): void { + $column = new Ulid('ulid_field'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can set default value', function (): void { + $defaultUlid = '01ARZ3NDEKTSV4RRFFQ69G5FAV'; + $column = new Ulid('ulid_field'); + $column->default($defaultUlid); + + expect($column->getOptions()['default'])->toBe($defaultUlid); +}); + +it('can have comment', function (): void { + $column = new Ulid('ulid_field'); + $column->comment('User identifier'); + + expect($column->getOptions()['comment'])->toBe('User identifier'); +}); + +it('maintains fixed length of 26 characters when limit is called', function (): void { + $column = new Ulid('ulid_field'); + $column->limit(50); + + expect($column->getOptions()['limit'])->toBe(26); +}); + +it('maintains fixed length of 26 characters when length is called', function (): void { + $column = new Ulid('ulid_field'); + $column->length(100); + + expect($column->getOptions()['limit'])->toBe(26); +}); diff --git a/tests/Unit/Database/Migrations/Columns/UnsignedDecimalTest.php b/tests/Unit/Database/Migrations/Columns/UnsignedDecimalTest.php new file mode 100644 index 00000000..5ea39e3e --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/UnsignedDecimalTest.php @@ -0,0 +1,60 @@ +getName())->toBe('price'); + expect($column->getType())->toBe('decimal'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'precision' => 10, + 'scale' => 2, + 'signed' => false, + ]); +}); + +it('can create unsigned decimal column with custom precision and scale', function (): void { + $column = new UnsignedDecimal('amount', 15, 4); + + expect($column->getOptions()['precision'])->toBe(15); + expect($column->getOptions()['scale'])->toBe(4); +}); + +it('can set default value', function (): void { + $column = new UnsignedDecimal('price'); + $column->default(99.99); + + expect($column->getOptions()['default'])->toBe(99.99); +}); + +it('can set precision', function (): void { + $column = new UnsignedDecimal('price'); + $column->precision(12); + + expect($column->getOptions()['precision'])->toBe(12); +}); + +it('can set scale', function (): void { + $column = new UnsignedDecimal('price'); + $column->scale(4); + + expect($column->getOptions()['scale'])->toBe(4); +}); + +it('can be nullable', function (): void { + $column = new UnsignedDecimal('discount'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new UnsignedDecimal('price'); + $column->comment('Product price'); + + expect($column->getOptions()['comment'])->toBe('Product price'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/UnsignedSmallIntegerTest.php b/tests/Unit/Database/Migrations/Columns/UnsignedSmallIntegerTest.php new file mode 100644 index 00000000..48a5a0ee --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/UnsignedSmallIntegerTest.php @@ -0,0 +1,55 @@ +getName())->toBe('count'); + expect($column->getType())->toBe('smallinteger'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + ]); +}); + +it('can create unsigned small integer column with identity', function (): void { + $column = new UnsignedSmallInteger('id', true); + + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + 'identity' => true, + ]); +}); + +it('can set default value', function (): void { + $column = new UnsignedSmallInteger('status'); + $column->default(1); + + expect($column->getOptions()['default'])->toBe(1); +}); + +it('can set identity', function (): void { + $column = new UnsignedSmallInteger('id'); + $column->identity(); + + expect($column->getOptions()['identity'])->toBeTrue(); + expect($column->getOptions()['null'])->toBeFalse(); +}); + +it('can be nullable', function (): void { + $column = new UnsignedSmallInteger('priority'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new UnsignedSmallInteger('count'); + $column->comment('Item count'); + + expect($column->getOptions()['comment'])->toBe('Item count'); +}); diff --git a/tests/Unit/Database/Migrations/Columns/UuidTest.php b/tests/Unit/Database/Migrations/Columns/UuidTest.php new file mode 100644 index 00000000..fa7f758c --- /dev/null +++ b/tests/Unit/Database/Migrations/Columns/UuidTest.php @@ -0,0 +1,36 @@ +getName())->toBe('uuid'); + expect($column->getType())->toBe('uuid'); + expect($column->getOptions())->toBe([ + 'null' => false, + ]); +}); + +it('can set default value', function (): void { + $column = new Uuid('identifier'); + $column->default('550e8400-e29b-41d4-a716-446655440000'); + + expect($column->getOptions()['default'])->toBe('550e8400-e29b-41d4-a716-446655440000'); +}); + +it('can be nullable', function (): void { + $column = new Uuid('external_id'); + $column->nullable(); + + expect($column->getOptions()['null'])->toBeTrue(); +}); + +it('can have comment', function (): void { + $column = new Uuid('uuid'); + $column->comment('Unique identifier'); + + expect($column->getOptions()['comment'])->toBe('Unique identifier'); +}); diff --git a/tests/Unit/Database/Migrations/TableTest.php b/tests/Unit/Database/Migrations/TableTest.php new file mode 100644 index 00000000..51a739d1 --- /dev/null +++ b/tests/Unit/Database/Migrations/TableTest.php @@ -0,0 +1,1032 @@ +mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + + $this->mockAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $this->mockAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $this->mockAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); +}); + +it('can add string column with options', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $table->string('username', 50)->comment('User name'); + + $columns = $table->getColumnBuilders(); + + expect(count($columns))->toBe(1); + + $column = $columns[0]; + + expect($column)->toBeInstanceOf(Str::class); + expect($column->getName())->toBe('username'); + expect($column->getType())->toBe('string'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 50, + 'comment' => 'User name', + ]); +}); + +it('can add integer column with options', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->integer('age', 10, false)->default(0)->comment('User age'); + + expect($column)->toBeInstanceOf(Integer::class); + expect($column->getName())->toBe('age'); + expect($column->getType())->toBe('integer'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => true, + 'limit' => 10, + 'default' => 0, + 'comment' => 'User age', + ]); +}); + +it('can add big integer column with identity', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->bigInteger('id', true)->comment('Primary key'); + + expect($column)->toBeInstanceOf(BigInteger::class); + expect($column->getName())->toBe('id'); + expect($column->getType())->toBe('biginteger'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => true, + 'identity' => true, + 'comment' => 'Primary key', + ]); +}); + +it('can add unsigned integer column with options', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->unsignedInteger('count', 10, false)->default(0)->comment('Item count'); + + expect($column)->toBeInstanceOf(UnsignedInteger::class); + expect($column->getName())->toBe('count'); + expect($column->getType())->toBe('integer'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + 'limit' => 10, + 'default' => 0, + 'comment' => 'Item count', + ]); +}); + +it('can add unsigned big integer column with identity', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->unsignedBigInteger('id', true)->comment('Primary key'); + + expect($column)->toBeInstanceOf(UnsignedBigInteger::class); + expect($column->getName())->toBe('id'); + expect($column->getType())->toBe('biginteger'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + 'identity' => true, + 'comment' => 'Primary key', + ]); +}); + +it('can add small integer column', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->smallInteger('status', false)->default(1); + + expect($column)->toBeInstanceOf(SmallInteger::class); + expect($column->getName())->toBe('status'); + expect($column->getType())->toBe('smallinteger'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'default' => 1, + ]); +}); + +it('can add text column with limit', function (): void { + $table = new Table('posts', adapter: $this->mockAdapter); + + $column = $table->text('content', 1000)->nullable()->comment('Post content'); + + expect($column)->toBeInstanceOf(Text::class); + expect($column->getName())->toBe('content'); + expect($column->getType())->toBe('text'); + expect($column->getOptions())->toBe([ + 'null' => true, + 'limit' => 1000, + 'comment' => 'Post content', + ]); +}); + +it('can add boolean column', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->boolean('is_active')->default(true)->comment('User status'); + + expect($column)->toBeInstanceOf(Boolean::class); + expect($column->getName())->toBe('is_active'); + expect($column->getType())->toBe('boolean'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'default' => true, + 'comment' => 'User status', + ]); +}); + +it('can add decimal column with precision and scale', function (): void { + $table = new Table('products', adapter: $this->mockAdapter); + + $column = $table->decimal('price', 8, 2)->default(0.00)->comment('Product price'); + + expect($column)->toBeInstanceOf(Decimal::class); + expect($column->getName())->toBe('price'); + expect($column->getType())->toBe('decimal'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'precision' => 8, + 'scale' => 2, + 'signed' => true, + 'default' => 0.00, + 'comment' => 'Product price', + ]); +}); + +it('can add datetime column', function (): void { + $table = new Table('posts', adapter: $this->mockAdapter); + + $column = $table->dateTime('published_at')->nullable()->comment('Publication date'); + + expect($column)->toBeInstanceOf(DateTime::class); + expect($column->getName())->toBe('published_at'); + expect($column->getType())->toBe('datetime'); + expect($column->getOptions())->toBe([ + 'null' => true, + 'comment' => 'Publication date', + ]); +}); + +it('can add timestamp column with timezone', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->timestamp('created_at', true)->currentTimestamp(); + + expect($column)->toBeInstanceOf(Timestamp::class); + expect($column->getName())->toBe('created_at'); + expect($column->getType())->toBe('timestamp'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'timezone' => true, + 'default' => 'CURRENT_TIMESTAMP', + ]); +}); + +it('can add json column', function (): void { + $table = new Table('settings', adapter: $this->mockAdapter); + + $column = $table->json('data')->nullable()->comment('JSON data'); + + expect($column)->toBeInstanceOf(Json::class); + expect($column->getName())->toBe('data'); + expect($column->getType())->toBe('json'); + expect($column->getOptions())->toBe([ + 'null' => true, + 'comment' => 'JSON data', + ]); +}); + +it('can add uuid column', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->uuid('uuid')->comment('Unique identifier'); + + expect($column)->toBeInstanceOf(Uuid::class); + expect($column->getName())->toBe('uuid'); + expect($column->getType())->toBe('uuid'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'comment' => 'Unique identifier', + ]); +}); + +it('can add ulid column', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->ulid('ulid')->comment('ULID identifier'); + + expect($column)->toBeInstanceOf(Ulid::class); + expect($column->getName())->toBe('ulid'); + expect($column->getType())->toBe('string'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 26, + 'comment' => 'ULID identifier', + ]); +}); + +it('can add enum column with values', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->enum('role', ['admin', 'user', 'guest'])->default('user')->comment('User role'); + + expect($column)->toBeInstanceOf(Enum::class); + expect($column->getName())->toBe('role'); + expect($column->getType())->toBe('enum'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'values' => ['admin', 'user', 'guest'], + 'default' => 'user', + 'comment' => 'User role', + ]); +}); + +it('can add float column', function (): void { + $table = new Table('measurements', adapter: $this->mockAdapter); + + $column = $table->float('temperature')->default(0.0)->comment('Temperature value'); + + expect($column)->toBeInstanceOf(Floating::class); + expect($column->getName())->toBe('temperature'); + expect($column->getType())->toBe('float'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'default' => 0.0, + 'comment' => 'Temperature value', + ]); +}); + +it('can add date column', function (): void { + $table = new Table('events', adapter: $this->mockAdapter); + + $column = $table->date('event_date')->comment('Event date'); + + expect($column)->toBeInstanceOf(Date::class); + expect($column->getName())->toBe('event_date'); + expect($column->getType())->toBe('date'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'comment' => 'Event date', + ]); +}); + +it('can add binary column with limit', function (): void { + $table = new Table('files', adapter: $this->mockAdapter); + + $column = $table->binary('file_data', 1024)->nullable()->comment('Binary file data'); + + expect($column)->toBeInstanceOf(Binary::class); + expect($column->getName())->toBe('file_data'); + expect($column->getType())->toBe('binary'); + expect($column->getOptions())->toBe([ + 'null' => true, + 'limit' => 1024, + 'comment' => 'Binary file data', + ]); +}); + +it('can add id column with auto increment', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->id('user_id'); + + expect($column)->toBeInstanceOf(UnsignedInteger::class); + expect($column->getName())->toBe('user_id'); + expect($column->getType())->toBe('integer'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + 'identity' => true, + ]); +}); + +it('can add timestamps columns', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $table->timestamps(true); + + $columns = $table->getColumnBuilders(); + + expect(count($columns))->toBe(2); + + $createdAt = $columns[0]; + expect($createdAt)->toBeInstanceOf(Timestamp::class); + expect($createdAt->getName())->toBe('created_at'); + expect($createdAt->getType())->toBe('timestamp'); + expect($createdAt->getOptions())->toBe([ + 'null' => true, + 'timezone' => true, + 'default' => 'CURRENT_TIMESTAMP', + ]); + + $updatedAt = $columns[1]; + expect($updatedAt)->toBeInstanceOf(Timestamp::class); + expect($updatedAt->getName())->toBe('updated_at'); + expect($updatedAt->getType())->toBe('timestamp'); + expect($updatedAt->getOptions())->toBe([ + 'null' => true, + 'timezone' => true, + 'update' => 'CURRENT_TIMESTAMP', + ]); +}); + +it('can add unsigned decimal column with precision and scale', function (): void { + $table = new Table('products', adapter: $this->mockAdapter); + + $column = $table->unsignedDecimal('price', 8, 2)->default(0.00)->comment('Product price'); + + expect($column)->toBeInstanceOf(UnsignedDecimal::class); + expect($column->getName())->toBe('price'); + expect($column->getType())->toBe('decimal'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'precision' => 8, + 'scale' => 2, + 'signed' => false, + 'default' => 0.00, + 'comment' => 'Product price', + ]); +}); + +it('can add unsigned small integer column', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->unsignedSmallInteger('status', false)->default(1)->comment('User status'); + + expect($column)->toBeInstanceOf(UnsignedSmallInteger::class); + expect($column->getName())->toBe('status'); + expect($column->getType())->toBe('smallinteger'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + 'default' => 1, + 'comment' => 'User status', + ]); +}); + +it('can add unsigned small integer column with identity', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->unsignedSmallInteger('id', true)->comment('Primary key'); + + expect($column)->toBeInstanceOf(UnsignedSmallInteger::class); + expect($column->getName())->toBe('id'); + expect($column->getType())->toBe('smallinteger'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + 'identity' => true, + 'comment' => 'Primary key', + ]); +}); + +it('can add unsigned float column', function (): void { + $table = new Table('measurements', adapter: $this->mockAdapter); + + $column = $table->unsignedFloat('temperature')->default(0.0)->comment('Temperature value'); + + expect($column)->toBeInstanceOf(UnsignedFloat::class); + expect($column->getName())->toBe('temperature'); + expect($column->getType())->toBe('float'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => false, + 'default' => 0.0, + 'comment' => 'Temperature value', + ]); +}); + +it('can change adapter for columns', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->string('name', 100); + + expect($column->getAdapter())->toBe($this->mockAdapter); + + $mysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $column->setAdapter($mysqlAdapter); + expect($column->isMysql())->toBeTrue(); + expect($column->isPostgres())->toBeFalse(); + expect($column->isSQLite())->toBeFalse(); + expect($column->isSqlServer())->toBeFalse(); + + $postgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $column->setAdapter($postgresAdapter); + + expect($column->isPostgres())->toBeTrue(); + expect($column->isMysql())->toBeFalse(); +}); + +it('can add char column with limit', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->char('code', 10)->comment('Product code'); + + expect($column)->toBeInstanceOf(Char::class); + expect($column->getName())->toBe('code'); + expect($column->getType())->toBe('char'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 10, + 'comment' => 'Product code', + ]); +}); + +it('can add time column', function (): void { + $table = new Table('events', adapter: $this->mockAdapter); + + $column = $table->time('start_time')->comment('Event start time'); + + expect($column)->toBeInstanceOf(Time::class); + expect($column->getName())->toBe('start_time'); + expect($column->getType())->toBe('time'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'comment' => 'Event start time', + ]); +}); + +it('can add double column', function (): void { + $table = new Table('measurements', adapter: $this->mockAdapter); + + $column = $table->double('value')->default(0.0)->comment('Measurement value'); + + expect($column)->toBeInstanceOf(Double::class); + expect($column->getName())->toBe('value'); + expect($column->getType())->toBe('double'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'signed' => true, + 'default' => 0.0, + 'comment' => 'Measurement value', + ]); +}); + +it('can add blob column', function (): void { + $table = new Table('files', adapter: $this->mockAdapter); + + $column = $table->blob('data')->comment('File data'); + + expect($column)->toBeInstanceOf(Blob::class); + expect($column->getName())->toBe('data'); + expect($column->getType())->toBe('blob'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'comment' => 'File data', + ]); +}); + +it('can add set column with values', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->set('permissions', ['read', 'write', 'execute'])->comment('User permissions'); + + expect($column)->toBeInstanceOf(Set::class); + expect($column->getName())->toBe('permissions'); + expect($column->getType())->toBe('set'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'values' => ['read', 'write', 'execute'], + 'comment' => 'User permissions', + ]); +}); + +it('can add bit column', function (): void { + $table = new Table('flags', adapter: $this->mockAdapter); + + $column = $table->bit('flags', 8)->comment('Status flags'); + + expect($column)->toBeInstanceOf(Bit::class); + expect($column->getName())->toBe('flags'); + expect($column->getType())->toBe('bit'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 8, + 'comment' => 'Status flags', + ]); +}); + +it('can add jsonb column (PostgreSQL)', function (): void { + $table = new Table('data', adapter: $this->mockAdapter); + + $column = $table->jsonb('metadata')->nullable()->comment('JSON metadata'); + + expect($column)->toBeInstanceOf(JsonB::class); + expect($column->getName())->toBe('metadata'); + expect($column->getType())->toBe('jsonb'); + expect($column->getOptions())->toBe([ + 'null' => true, + 'comment' => 'JSON metadata', + ]); +}); + +it('can add inet column (PostgreSQL)', function (): void { + $table = new Table('connections', adapter: $this->mockAdapter); + + $column = $table->inet('ip_address')->comment('IP address'); + + expect($column)->toBeInstanceOf(Inet::class); + expect($column->getName())->toBe('ip_address'); + expect($column->getType())->toBe('inet'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'comment' => 'IP address', + ]); +}); + +it('can add cidr column (PostgreSQL)', function (): void { + $table = new Table('networks', adapter: $this->mockAdapter); + + $column = $table->cidr('network')->comment('Network CIDR'); + + expect($column)->toBeInstanceOf(Cidr::class); + expect($column->getName())->toBe('network'); + expect($column->getType())->toBe('cidr'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'comment' => 'Network CIDR', + ]); +}); + +it('can add macaddr column (PostgreSQL)', function (): void { + $table = new Table('devices', adapter: $this->mockAdapter); + + $column = $table->macaddr('mac_address')->comment('MAC address'); + + expect($column)->toBeInstanceOf(MacAddr::class); + expect($column->getName())->toBe('mac_address'); + expect($column->getType())->toBe('macaddr'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'comment' => 'MAC address', + ]); +}); + +it('can add interval column (PostgreSQL)', function (): void { + $table = new Table('events', adapter: $this->mockAdapter); + + $column = $table->interval('duration')->comment('Event duration'); + + expect($column)->toBeInstanceOf(Interval::class); + expect($column->getName())->toBe('duration'); + expect($column->getType())->toBe('interval'); + expect($column->getOptions())->toBe([ + 'null' => false, + 'comment' => 'Event duration', + ]); +}); + +it('can use after method to position column', function (): void { + $table = new Table('users', adapter: $this->mockAdapter); + + $column = $table->string('email')->after('username'); + + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 255, + 'after' => 'username', + ]); +}); + +it('can use first method to position column at beginning', function (): void { + $mysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $mysqlAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('users', adapter: $mysqlAdapter); + + $column = $table->string('id')->setAdapter($mysqlAdapter)->first(); + + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 255, + 'after' => MysqlAdapter::FIRST, + ]); +}); + +it('can set collation for MySQL columns', function (): void { + $mysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $mysqlAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('users', adapter: $mysqlAdapter); + + $column = $table->string('name')->setAdapter($mysqlAdapter)->collation('utf8mb4_unicode_ci'); + + expect($column->isMysql())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 255, + 'collation' => 'utf8mb4_unicode_ci', + ]); +}); + +it('sets collation for non-MySQL adapters (Str class behavior)', function (): void { + $postgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $postgresAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $postgresAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $postgresAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('users', adapter: $postgresAdapter); + + $column = $table->string('name'); + $column->setAdapter($postgresAdapter); + $column->collation('utf8mb4_unicode_ci'); + + expect($column->isPostgres())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 255, + 'collation' => 'utf8mb4_unicode_ci', + ]); +}); + +it('can set encoding for MySQL columns', function (): void { + $mysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $mysqlAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('users', adapter: $mysqlAdapter); + + $column = $table->string('name'); + $column->setAdapter($mysqlAdapter); + $column->encoding('utf8mb4'); + + expect($column->isMysql())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 255, + 'encoding' => 'utf8mb4', + ]); +}); + +it('sets encoding for non-MySQL adapters (Str class behavior)', function (): void { + $postgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $postgresAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $postgresAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $postgresAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('users', adapter: $postgresAdapter); + + $column = $table->string('name'); + $column->setAdapter($postgresAdapter); + $column->encoding('utf8mb4'); + + expect($column->isPostgres())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'limit' => 255, + 'encoding' => 'utf8mb4', + ]); +}); + +it('can set timezone for PostgreSQL columns', function (): void { + $postgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $postgresAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $postgresAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $postgresAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('events', adapter: $postgresAdapter); + + $column = $table->timestamp('created_at'); + $column->setAdapter($postgresAdapter); + $column->timezone(true); + + expect($column->isPostgres())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'timezone' => true, + ]); +}); + +it('can set timezone to false for PostgreSQL columns', function (): void { + $postgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $postgresAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $postgresAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $postgresAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('events', adapter: $postgresAdapter); + + $column = $table->timestamp('created_at'); + $column->setAdapter($postgresAdapter); + $column->timezone(false); + + expect($column->isPostgres())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'timezone' => false, + ]); +}); + +it('sets timezone for non-PostgreSQL adapters (Timestamp class behavior)', function (): void { + $mysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $mysqlAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('events', adapter: $mysqlAdapter); + + $column = $table->timestamp('created_at'); + $column->setAdapter($mysqlAdapter); + $column->timezone(true); + + expect($column->isMysql())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'timezone' => true, + ]); +}); + +it('can set update trigger for MySQL columns', function (): void { + $mysqlAdapter = $this->getMockBuilder(MysqlAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $mysqlAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $mysqlAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('users', adapter: $mysqlAdapter); + + $column = $table->timestamp('updated_at'); + $column->setAdapter($mysqlAdapter); + $column->update('CURRENT_TIMESTAMP'); + + expect($column->isMysql())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'update' => 'CURRENT_TIMESTAMP', + ]); +}); + +it('sets update trigger for non-MySQL adapters (Timestamp class behavior)', function (): void { + $postgresAdapter = $this->getMockBuilder(PostgresAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $postgresAdapter->expects($this->any()) + ->method('isValidColumnType') + ->willReturn(true); + + $postgresAdapter->expects($this->any()) + ->method('getColumnTypes') + ->willReturn(['string', 'integer', 'boolean', 'text', 'datetime', 'timestamp']); + + $postgresAdapter->expects($this->any()) + ->method('getColumnForType') + ->willReturnCallback(function (string $columnName, string $type, array $options): Column { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); + + return $column; + }); + + $table = new Table('users', adapter: $postgresAdapter); + + $column = $table->timestamp('updated_at'); + $column->setAdapter($postgresAdapter); + $column->update('CURRENT_TIMESTAMP'); + + expect($column->isPostgres())->toBeTrue(); + expect($column->getOptions())->toBe([ + 'null' => false, + 'update' => 'CURRENT_TIMESTAMP', + ]); +}); + +it('returns new table for migrations', function (): void { + $migration = new class ('local', 1) extends Migration {}; + $migration->setAdapter($this->mockAdapter); + + expect($migration->table('users'))->toBeInstanceOf(Table::class); +});