diff --git a/packages/core/src/Exceptions/ExceptionTester.php b/packages/core/src/Exceptions/ExceptionTester.php index a8e72ad894..a27ee15dfa 100644 --- a/packages/core/src/Exceptions/ExceptionTester.php +++ b/packages/core/src/Exceptions/ExceptionTester.php @@ -92,7 +92,7 @@ private function findRecordedProcessings(string|object $exception): array return true; } - return class_exists($exception) && is_a($reported, $exception, allow_string: true); + return class_exists($exception) && $reported instanceof $exception; }); } diff --git a/packages/database/src/Columns/CharColumn.php b/packages/database/src/Columns/CharColumn.php new file mode 100644 index 0000000000..c7d00f5eeb --- /dev/null +++ b/packages/database/src/Columns/CharColumn.php @@ -0,0 +1,19 @@ + $length, + ]); + } +} diff --git a/packages/database/src/Columns/Column.php b/packages/database/src/Columns/Column.php new file mode 100644 index 0000000000..bc983085b8 --- /dev/null +++ b/packages/database/src/Columns/Column.php @@ -0,0 +1,18 @@ + $name, + ]); + } + + public function primaryKey(): static + { + return clone($this, [ + 'primaryKey' => true, + ]); + } + + public function nullable(bool $nullable = true): static + { + return clone($this, [ + 'nullable' => $nullable, + ]); + } + + public function default(mixed $default): static + { + return clone($this, [ + 'default' => $default, + ]); + } + + public function unique(bool $unique = true): self + { + return clone($this, [ + 'unique' => $unique, + ]); + } +} diff --git a/packages/database/src/Columns/DateColumn.php b/packages/database/src/Columns/DateColumn.php new file mode 100644 index 0000000000..02cca38b9d --- /dev/null +++ b/packages/database/src/Columns/DateColumn.php @@ -0,0 +1,19 @@ + true, + ]); + } +} diff --git a/packages/database/src/Columns/DateTimeColumn.php b/packages/database/src/Columns/DateTimeColumn.php new file mode 100644 index 0000000000..64583a0f5b --- /dev/null +++ b/packages/database/src/Columns/DateTimeColumn.php @@ -0,0 +1,42 @@ + 6) { + throw new InvalidArgumentException( + 'Precision must be between 0 and 6.', + ); + } + + return clone($this, [ + 'precision' => $precision, + ]); + } + + public function current(): self + { + return clone($this, [ + 'current' => true, + ]); + } + + public function currentOnUpdate(): self + { + return clone($this, [ + 'currentOnUpdate' => true, + ]); + } +} diff --git a/packages/database/src/Columns/FloatColumn.php b/packages/database/src/Columns/FloatColumn.php new file mode 100644 index 0000000000..9d15dc3ce5 --- /dev/null +++ b/packages/database/src/Columns/FloatColumn.php @@ -0,0 +1,19 @@ + true, + ]); + } +} diff --git a/packages/database/src/Columns/IntegerColumn.php b/packages/database/src/Columns/IntegerColumn.php new file mode 100644 index 0000000000..737695da25 --- /dev/null +++ b/packages/database/src/Columns/IntegerColumn.php @@ -0,0 +1,28 @@ + $autoIncrement, + ]); + } + + public function unsigned(bool $unsigned = true): self + { + return clone($this, [ + 'unsigned' => $unsigned, + ]); + } +} diff --git a/packages/database/src/Columns/TextColumn.php b/packages/database/src/Columns/TextColumn.php new file mode 100644 index 0000000000..5215bd6c00 --- /dev/null +++ b/packages/database/src/Columns/TextColumn.php @@ -0,0 +1,19 @@ + $length, + ]); + } +} diff --git a/packages/database/src/Columns/UuidColumn.php b/packages/database/src/Columns/UuidColumn.php new file mode 100644 index 0000000000..bc9e518f3f --- /dev/null +++ b/packages/database/src/Columns/UuidColumn.php @@ -0,0 +1,19 @@ + true, + ]); + } +} diff --git a/packages/database/src/Columns/VarcharColumn.php b/packages/database/src/Columns/VarcharColumn.php new file mode 100644 index 0000000000..574815db56 --- /dev/null +++ b/packages/database/src/Columns/VarcharColumn.php @@ -0,0 +1,19 @@ + $length, + ]); + } +} diff --git a/packages/database/src/Compilers/ColumnCompiler.php b/packages/database/src/Compilers/ColumnCompiler.php new file mode 100644 index 0000000000..c2ff7ca1f4 --- /dev/null +++ b/packages/database/src/Compilers/ColumnCompiler.php @@ -0,0 +1,10 @@ +quote($column->name), + $this->compileType($column), + $this->compileNullability($column), + $this->compileDefault($column), + $this->compileModifiers($column), + ]; + + return implode(' ', array_filter($parts)); + } + + private function compileType(Column $column): string + { + return match (true) { + $column instanceof CharColumn => "CHAR({$column->length})", + $column instanceof VarcharColumn => "VARCHAR({$column->length})", + $column instanceof TextColumn => $this->compileTextType($column), + $column instanceof IntegerColumn => 'INT' . ($column->unsigned ? ' UNSIGNED' : ''), + $column instanceof FloatColumn => 'FLOAT', + $column instanceof DateColumn => 'DATE', + $column instanceof DateTimeColumn => "DATETIME({$column->precision})", + $column instanceof UuidColumn => 'CHAR(36)', + default => throw new \LogicException( + 'Unsupported column: ' . $column::class, + ), + }; + } + + private function compileTextType(TextColumn $column): string + { + return match (true) { + $column->length <= 255 => 'TINYTEXT', + $column->length <= 65_535 => 'TEXT', + $column->length <= 16_777_215 => 'MEDIUMTEXT', + $column->length <= 4_294_967_295 => 'LONGTEXT', + default => new \LogicException('Unsupported text length'), + }; + } + + private function compileNullability(Column $column): string + { + return $column->nullable ? 'NULL' : 'NOT NULL'; + } + + private function compileDefault(Column $column): string + { + if ($column instanceof DateColumn && $column->current) { + return 'DEFAULT (CURRENT_DATE)'; + } + + if ($column instanceof DateTimeColumn && $column->current) { + $precision = $column->precision; + + return "DEFAULT CURRENT_TIMESTAMP($precision)"; + } + + if ($column instanceof UuidColumn && $column->generated) { + return 'DEFAULT (UUID())'; + } + + if (! isset($column->default)) { + return ''; + } + + $value = $this->literal($column->default); + + if ($column instanceof TextColumn) { + return "DEFAULT ($value)"; + } + + return "DEFAULT $value"; + } + + private function compileModifiers(Column $column): string + { + $parts = []; + + if ($column instanceof IntegerColumn && $column->autoIncrement) { + $parts[] = 'AUTO_INCREMENT'; + } + + if ($column instanceof DateTimeColumn && $column->currentOnUpdate) { + $precision = $column->precision; + + $parts[] = "ON UPDATE CURRENT_TIMESTAMP($precision)"; + } + + if ($column->unique) { + $parts[] = 'UNIQUE'; + } + + if ($column->primaryKey) { + $parts[] = 'PRIMARY KEY'; + } + + return implode(' ', $parts); + } + + private function quote(string $value): string + { + return '`' . str_replace('`', '``', $value) . '`'; + } + + private function literal(mixed $value): string + { + return match (true) { + $value === null => 'NULL', + is_bool($value) => $value ? '1' : '0', + is_int($value), is_float($value) => (string) $value, + is_string($value) => "'" . str_replace("'", "''", $value) . "'", + default => throw new InvalidArgumentException( + 'Unsupported default value.', + ), + }; + } +} diff --git a/packages/database/src/Compilers/Dialects/MySQL/MySQLTableCompiler.php b/packages/database/src/Compilers/Dialects/MySQL/MySQLTableCompiler.php new file mode 100644 index 0000000000..7194dcf34c --- /dev/null +++ b/packages/database/src/Compilers/Dialects/MySQL/MySQLTableCompiler.php @@ -0,0 +1,50 @@ + $this->compileCreateTable($tableDefinition), + }; + } + + private function compileCreateTable(CreateTable $createTable): string + { + if (empty($createTable->columns)) { + // @todo(aidan-casey): Real exceptions. + throw new \InvalidArgumentException('Table must have at least one column'); + } + + // @todo(aidan-casey): Guard against duplicate column names. + + $columns = array_map( + fn (Column $column) => $this->columnCompiler->compile($column), + $createTable->columns + ); + + $columns = implode(",\n", $columns); + $columns = ' ' . str_replace("\n", "\n ", $columns); + + return <<quote($createTable->name)} ( + {$columns} + ); + SQL; + } + + private function quote(string $value): string + { + return '`' . str_replace('`', '``', $value) . '`'; + } +} \ No newline at end of file diff --git a/packages/database/src/Compilers/Dialects/Postgres/PostgresColumnCompiler.php b/packages/database/src/Compilers/Dialects/Postgres/PostgresColumnCompiler.php new file mode 100644 index 0000000000..41644c8b45 --- /dev/null +++ b/packages/database/src/Compilers/Dialects/Postgres/PostgresColumnCompiler.php @@ -0,0 +1,133 @@ +quote($column->name), + $this->compileType($column), + $this->compileNullability($column), + $this->compileDefault($column), + $this->compileModifiers($column), + ]; + + return implode(' ', array_filter($parts)); + } + + private function compileType(Column $column): string + { + return match (true) { + $column instanceof CharColumn => "CHAR({$column->length})", + $column instanceof VarcharColumn => "VARCHAR({$column->length})", + $column instanceof TextColumn => 'TEXT', + $column instanceof IntegerColumn => 'INT', + $column instanceof FloatColumn => 'REAL', + $column instanceof DateColumn => 'DATE', + $column instanceof DateTimeColumn => "TIMESTAMP({$column->precision})", + $column instanceof UuidColumn => 'UUID', + default => throw new \LogicException( + 'Unsupported column: ' . $column::class, + ), + }; + } + + private function compileNullability(Column $column): string + { + return $column->nullable ? 'NULL' : 'NOT NULL'; + } + + private function compileDefault(Column $column): string + { + if ($column instanceof DateColumn && $column->current) { + return 'DEFAULT (CURRENT_DATE)'; + } + + if ($column instanceof DateTimeColumn && $column->current) { + $precision = $column->precision; + + return "DEFAULT CURRENT_TIMESTAMP($precision)"; + } + + if ($column instanceof UuidColumn && $column->generated) { + return 'DEFAULT gen_random_uuid()'; + } + + if (! isset($column->default)) { + return ''; + } + + $value = $this->literal($column->default); + + if ($column instanceof TextColumn) { + return "DEFAULT ($value)"; + } + + return "DEFAULT $value"; + } + + private function compileModifiers(Column $column): string + { + $parts = []; + + if ($column instanceof IntegerColumn && $column->autoIncrement) { + $parts[] = 'GENERATED BY DEFAULT AS IDENTITY'; + } + + if ($column instanceof IntegerColumn && $column->unsigned) { + $name = $this->quote($column->name); + + $parts[] = "CHECK ($name >= 0)"; + } + + // @todo(aidan-casey): Eventually we can move this to a trigger. + if ($column instanceof DateTimeColumn && $column->currentOnUpdate) { + throw new UnexpectedValueException( + 'The "currentOnUpdate" method is not supported by the Postgres dialect.' + ); + } + + if ($column->unique) { + $parts[] = 'UNIQUE'; + } + + if ($column->primaryKey) { + $parts[] = 'PRIMARY KEY'; + } + + return implode(' ', $parts); + } + + private function quote(string $value): string + { + return '"' . str_replace('"', '""', $value) . '"'; + } + + private function literal(mixed $value): string + { + return match (true) { + $value === null => 'NULL', + is_bool($value) => $value ? 'TRUE' : 'FALSE', + is_int($value), is_float($value) => (string) $value, + is_string($value) => "'" . str_replace("'", "''", $value) . "'", + default => throw new InvalidArgumentException( + 'Unsupported default value.', + ), + }; + } +} diff --git a/packages/database/src/Compilers/Dialects/Postgres/PostgresTableCompiler.php b/packages/database/src/Compilers/Dialects/Postgres/PostgresTableCompiler.php new file mode 100644 index 0000000000..c82f78de65 --- /dev/null +++ b/packages/database/src/Compilers/Dialects/Postgres/PostgresTableCompiler.php @@ -0,0 +1,50 @@ + $this->compileCreateTable($tableDefinition), + }; + } + + private function compileCreateTable(CreateTable $createTable): string + { + if (empty($createTable->columns)) { + // @todo(aidan-casey): Real exceptions. + throw new \InvalidArgumentException('Table must have at least one column'); + } + + // @todo(aidan-casey): Guard against duplicate column names. + + $columns = array_map( + fn (Column $column) => $this->columnCompiler->compile($column), + $createTable->columns + ); + + $columns = implode(",\n", $columns); + $columns = ' ' . str_replace("\n", "\n ", $columns); + + return <<quote($createTable->name)} ( + {$columns} + ); + SQL; + } + + private function quote(string $value): string + { + return '"' . str_replace('`', '``', $value) . '"'; + } +} \ No newline at end of file diff --git a/packages/database/src/Compilers/Dialects/Sqlite/SqliteColumnCompiler.php b/packages/database/src/Compilers/Dialects/Sqlite/SqliteColumnCompiler.php new file mode 100644 index 0000000000..14d8c71ec0 --- /dev/null +++ b/packages/database/src/Compilers/Dialects/Sqlite/SqliteColumnCompiler.php @@ -0,0 +1,14 @@ + $columns + */ + public function __construct( + public string $name, + public array $columns, + public bool $unique = false, + ) {} +} \ No newline at end of file diff --git a/packages/database/src/TableDefinitions/AlterTable.php b/packages/database/src/TableDefinitions/AlterTable.php new file mode 100644 index 0000000000..a372593a4a --- /dev/null +++ b/packages/database/src/TableDefinitions/AlterTable.php @@ -0,0 +1,8 @@ + $columns + * @param list $indexes + */ + public function __construct( + public string $name, + public array $columns = [], + public array $indexes = [], + ) {} + + public function addColumn(Column $column): self + { + return $this->addColumns($column); + } + + public function addColumns(Column ...$columns): self + { + return clone($this, [ + 'columns' => [...$this->columns, ...$columns], + ]); + } + + public function addIndex(Index $index): self + { + return $this->addIndexes($index); + } + + public function addIndexes(Index ...$indexes): self + { + return clone($this, [ + 'columns' => [...$this->columns, ...$indexes], + ]); + } +} \ No newline at end of file diff --git a/packages/database/src/TableDefinitions/DropTable.php b/packages/database/src/TableDefinitions/DropTable.php new file mode 100644 index 0000000000..956cf61e0d --- /dev/null +++ b/packages/database/src/TableDefinitions/DropTable.php @@ -0,0 +1,10 @@ +assertEquals('`test` CHAR(1) NOT NULL', $compiler->compile($column)); + } + + public function test_char_compilation_with_length() + { + $column = new CharColumn('test')->length(10); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`test` CHAR(10) NOT NULL', $compiler->compile($column)); + } + + public function test_date_compilation() + { + $column = new DateColumn('birthday'); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`birthday` DATE NOT NULL', $compiler->compile($column)); + } + + public function test_datetime_compilation() + { + $column = new DateTimeColumn('created_at'); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`created_at` DATETIME(0) NOT NULL', $compiler->compile($column)); + } + + public function test_datetime_compilation_with_precision() + { + $column = new DateTimeColumn('created_at')->precision(6); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`created_at` DATETIME(6) NOT NULL', $compiler->compile($column)); + } + + public function test_datetime_compilation_with_current() + { + $column = new DateTimeColumn('created_at')->current(); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`created_at` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0)', $compiler->compile($column)); + } + + public function test_datetime_compilation_with_current_on_update() + { + $column = new DateTimeColumn('created_at')->currentOnUpdate(); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`created_at` DATETIME(0) NOT NULL ON UPDATE CURRENT_TIMESTAMP(0)', $compiler->compile($column)); + } + + public function test_float_compilation() + { + $column = new FloatColumn('temperature'); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`temperature` FLOAT NOT NULL', $compiler->compile($column)); + } + + public function test_integer_compilation() + { + $column = new IntegerColumn('age'); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`age` INT NOT NULL', $compiler->compile($column)); + } + + public function test_tiny_text_compilation() + { + $column = new TextColumn('description')->length(255); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`description` TINYTEXT NOT NULL', $compiler->compile($column)); + } + + public function test_text_compilation() + { + $column = new TextColumn('description')->length(65_535); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`description` TEXT NOT NULL', $compiler->compile($column)); + } + + public function test_medium_text_compilation() + { + $column = new TextColumn('description')->length(16_777_215); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`description` MEDIUMTEXT NOT NULL', $compiler->compile($column)); + } + + public function test_long_text_compilation() + { + $column = new TextColumn('description')->length(4_294_967_295); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`description` LONGTEXT NOT NULL', $compiler->compile($column)); + } + + public function test_uuid_compilation() + { + $column = new UuidColumn('uuid'); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`uuid` CHAR(36) NOT NULL', $compiler->compile($column)); + } + + public function test_uuid_compilation_with_generated_uuid() + { + $column = new UuidColumn('uuid')->generated(); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`uuid` CHAR(36) NOT NULL DEFAULT (UUID())', $compiler->compile($column)); + } + + public function test_varchar_compilation() + { + $column = new VarcharColumn('note'); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`note` VARCHAR(255) NOT NULL', $compiler->compile($column)); + } + + public function test_varchar_compilation_with_length() + { + $column = new VarcharColumn('note')->length(100); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`note` VARCHAR(100) NOT NULL', $compiler->compile($column)); + } + + public function test_primary_key_constraint_compilation() + { + $column = new IntegerColumn('id')->primaryKey(); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`id` INT NOT NULL PRIMARY KEY', $compiler->compile($column)); + } + + public function test_unique_constraint_compilation() + { + $column = new VarcharColumn('name')->unique(); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`name` VARCHAR(255) NOT NULL UNIQUE', $compiler->compile($column)); + } + + public function test_nullability_constraint_compilation() + { + $default = new VarcharColumn('name'); + $column1 = new VarcharColumn('name')->nullable(); + $column2 = new VarcharColumn('name')->nullable(false); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`name` VARCHAR(255) NOT NULL', $compiler->compile($default)); + $this->assertEquals('`name` VARCHAR(255) NULL', $compiler->compile($column1)); + $this->assertEquals('`name` VARCHAR(255) NOT NULL', $compiler->compile($column2)); + } + + public function test_auto_increment_attribute_compilation() + { + $column = new IntegerColumn('id')->autoIncrement(); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`id` INT NOT NULL AUTO_INCREMENT', $compiler->compile($column)); + } + + public function test_default_value_compilation() + { + $column = new VarcharColumn('name')->default('Jim Halpert'); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals('`name` VARCHAR(255) NOT NULL DEFAULT \'Jim Halpert\'', $compiler->compile($column)); + } + + public function test_default_value_escaping() + { + $column = new VarcharColumn('name')->default("O'Reilly"); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals( + "`name` VARCHAR(255) NOT NULL DEFAULT 'O''Reilly'", + $compiler->compile($column), + ); + } + + public function test_column_name_escaping() + { + $column = new VarcharColumn('user`name'); + + $compiler = new MySQLColumnCompiler(); + + $this->assertEquals( + '`user``name` VARCHAR(255) NOT NULL', + $compiler->compile($column), + ); + } +} diff --git a/packages/database/tests/Compilers/Dialects/MySQL/MySQLTableCompilerTestCase.php b/packages/database/tests/Compilers/Dialects/MySQL/MySQLTableCompilerTestCase.php new file mode 100644 index 0000000000..4a8b2b46c8 --- /dev/null +++ b/packages/database/tests/Compilers/Dialects/MySQL/MySQLTableCompilerTestCase.php @@ -0,0 +1,39 @@ +addColumns( + new IntegerColumn('id') + ->unsigned() + ->autoIncrement() + ->primaryKey(), + new TextColumn('first_name'), + new TextColumn('middle_name')->nullable(), + new TextColumn('last_name'), + ); + + $this->assertEquals( + <<compile($createTable) + ); + } +} \ No newline at end of file diff --git a/packages/database/tests/Compilers/Dialects/Postgres/PostgresColumnCompilerTest.php b/packages/database/tests/Compilers/Dialects/Postgres/PostgresColumnCompilerTest.php new file mode 100644 index 0000000000..5cc8eccf96 --- /dev/null +++ b/packages/database/tests/Compilers/Dialects/Postgres/PostgresColumnCompilerTest.php @@ -0,0 +1,259 @@ +assertEquals('"test" CHAR(1) NOT NULL', $compiler->compile($column)); + } + + public function test_char_compilation_with_length() + { + $column = new CharColumn('test')->length(10); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"test" CHAR(10) NOT NULL', $compiler->compile($column)); + } + + public function test_date_compilation() + { + $column = new DateColumn('birthday'); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"birthday" DATE NOT NULL', $compiler->compile($column)); + } + + public function test_datetime_compilation() + { + $column = new DateTimeColumn('created_at'); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"created_at" TIMESTAMP(0) NOT NULL', $compiler->compile($column)); + } + + public function test_datetime_compilation_with_precision() + { + $column = new DateTimeColumn('created_at')->precision(6); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"created_at" TIMESTAMP(6) NOT NULL', $compiler->compile($column)); + } + + public function test_datetime_compilation_with_current() + { + $column = new DateTimeColumn('created_at')->current(); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"created_at" TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0)', $compiler->compile($column)); + } + + public function test_datetime_compilation_with_current_on_update() + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('The "currentOnUpdate" method is not supported by the Postgres dialect.'); + + $column = new DateTimeColumn('created_at')->currentOnUpdate(); + + new PostgresColumnCompiler()->compile($column); + } + + public function test_float_compilation() + { + $column = new FloatColumn('temperature'); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"temperature" REAL NOT NULL', $compiler->compile($column)); + } + + public function test_integer_compilation() + { + $column = new IntegerColumn('age'); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"age" INT NOT NULL', $compiler->compile($column)); + } + + public function test_unsigned_integer_compilation() + { + $column = new IntegerColumn('age')->unsigned(); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals( + '"age" INT NOT NULL CHECK ("age" >= 0)', + $compiler->compile($column), + ); + } + + public function test_tiny_text_compilation() + { + $column = new TextColumn('description')->length(255); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"description" TEXT NOT NULL', $compiler->compile($column)); + } + + public function test_text_compilation() + { + $column = new TextColumn('description')->length(65_535); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"description" TEXT NOT NULL', $compiler->compile($column)); + } + + public function test_medium_text_compilation() + { + $column = new TextColumn('description')->length(16_777_215); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"description" TEXT NOT NULL', $compiler->compile($column)); + } + + public function test_long_text_compilation() + { + $column = new TextColumn('description')->length(4_294_967_295); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"description" TEXT NOT NULL', $compiler->compile($column)); + } + + public function test_uuid_compilation() + { + $column = new UuidColumn('uuid'); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"uuid" UUID NOT NULL', $compiler->compile($column)); + } + + public function test_uuid_compilation_with_generated_uuid() + { + $column = new UuidColumn('uuid')->generated(); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"uuid" UUID NOT NULL DEFAULT gen_random_uuid()', $compiler->compile($column)); + } + + public function test_varchar_compilation() + { + $column = new VarcharColumn('note'); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"note" VARCHAR(255) NOT NULL', $compiler->compile($column)); + } + + public function test_varchar_compilation_with_length() + { + $column = new VarcharColumn('note')->length(100); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"note" VARCHAR(100) NOT NULL', $compiler->compile($column)); + } + + public function test_primary_key_constraint_compilation() + { + $column = new IntegerColumn('id')->primaryKey(); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals( + '"id" INT NOT NULL PRIMARY KEY', + $compiler->compile($column) + ); + } + + public function test_unique_constraint_compilation() + { + $column = new VarcharColumn('name')->unique(); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"name" VARCHAR(255) NOT NULL UNIQUE', $compiler->compile($column)); + } + + public function test_nullability_constraint_compilation() + { + $default = new VarcharColumn('name'); + $column1 = new VarcharColumn('name')->nullable(); + $column2 = new VarcharColumn('name')->nullable(false); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"name" VARCHAR(255) NOT NULL', $compiler->compile($default)); + $this->assertEquals('"name" VARCHAR(255) NULL', $compiler->compile($column1)); + $this->assertEquals('"name" VARCHAR(255) NOT NULL', $compiler->compile($column2)); + } + + public function test_auto_increment_attribute_compilation() + { + $column = new IntegerColumn('id')->autoIncrement(); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"id" INT NOT NULL GENERATED BY DEFAULT AS IDENTITY', $compiler->compile($column)); + } + + public function test_auto_increment_primary_key_compilation() + { + $column = new IntegerColumn('id')->autoIncrement()->primaryKey(); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals( + '"id" INT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY', + $compiler->compile($column), + ); + } + + public function test_default_value_compilation() + { + $column = new VarcharColumn('name')->default('Jim Halpert'); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals('"name" VARCHAR(255) NOT NULL DEFAULT \'Jim Halpert\'', $compiler->compile($column)); + } + + public function test_default_value_escaping() + { + $column = new VarcharColumn('name')->default("O'Reilly"); + + $compiler = new PostgresColumnCompiler(); + + $this->assertEquals( + "\"name\" VARCHAR(255) NOT NULL DEFAULT 'O''Reilly'", + $compiler->compile($column), + ); + } +} \ No newline at end of file diff --git a/packages/database/tests/Compilers/Dialects/Postgres/PostgresTableCompilerTestCase.php b/packages/database/tests/Compilers/Dialects/Postgres/PostgresTableCompilerTestCase.php new file mode 100644 index 0000000000..2e073b7170 --- /dev/null +++ b/packages/database/tests/Compilers/Dialects/Postgres/PostgresTableCompilerTestCase.php @@ -0,0 +1,39 @@ +addColumns( + new IntegerColumn('id') + ->unsigned() + ->autoIncrement() + ->primaryKey(), + new TextColumn('first_name'), + new TextColumn('middle_name')->nullable(), + new TextColumn('last_name'), + ); + + $this->assertEquals( + <<= 0) PRIMARY KEY, + "first_name" TEXT NOT NULL, + "middle_name" TEXT NULL, + "last_name" TEXT NOT NULL + ); + SQL, + $compiler->compile($createTable) + ); + } +} \ No newline at end of file