diff --git a/src/Sql/Ddl/AlterTable.php b/src/Sql/Ddl/AlterTable.php index 3d7be7cc..f85849f4 100644 --- a/src/Sql/Ddl/AlterTable.php +++ b/src/Sql/Ddl/AlterTable.php @@ -6,9 +6,13 @@ use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Sql\AbstractSql; +use PhpDb\Sql\Literal; use PhpDb\Sql\TableIdentifier; use function array_key_exists; +use function is_bool; +use function is_int; +use function strtoupper; class AlterTable extends AbstractSql { @@ -26,6 +30,8 @@ class AlterTable extends AbstractSql final public const TABLE = 'table'; + final public const TABLE_OPTIONS = 'tableOptions'; + protected array $addColumns = []; protected array $addConstraints = []; @@ -38,6 +44,8 @@ class AlterTable extends AbstractSql protected array $dropIndexes = []; + protected array $options = []; + /** * Specifications for Sql String generation */ @@ -73,6 +81,11 @@ class AlterTable extends AbstractSql [1 => "DROP INDEX %1\$s,\n", 'combinedby' => ''], ], ], + self::TABLE_OPTIONS => [ + "%1\$s" => [ + [1 => "%1\$s,\n", 'combinedby' => ''], + ], + ], ]; protected string|TableIdentifier $table = ''; @@ -136,6 +149,23 @@ public function dropIndex(string $name): static return $this; } + public function setOption(string $name, Literal|bool|int|string $value): static + { + $this->options[$name] = $value; + return $this; + } + + public function setOptions(array $options): static + { + $this->options = $options; + return $this; + } + + public function getOptions(): array + { + return $this->options; + } + public function getRawState(?string $key = null): array|string { $rawState = [ @@ -146,6 +176,7 @@ public function getRawState(?string $key = null): array|string self::ADD_CONSTRAINTS => $this->addConstraints, self::DROP_CONSTRAINTS => $this->dropConstraints, self::DROP_INDEXES => $this->dropIndexes, + self::TABLE_OPTIONS => $this->options, ]; return isset($key) && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; @@ -243,4 +274,31 @@ protected function processDropIndexes(?PlatformInterface $adapterPlatform = null return [$sqls]; } + + /** + * @return string[][]|null + */ + protected function processTableOptions(?PlatformInterface $adapterPlatform = null): ?array + { + if (! $this->options) { + return null; + } + + $sqls = []; + foreach ($this->options as $key => $value) { + $key = strtoupper($key); + if ($value instanceof Literal) { + $value = $value->getLiteral(); + } elseif (is_bool($value)) { + $value = $value ? '1' : '0'; + } elseif (is_int($value)) { + $value = (string) $value; + } else { + $value = $adapterPlatform->quoteTrustedValue($value); + } + $sqls[] = $key . ' = ' . $value; + } + + return [$sqls]; + } } diff --git a/src/Sql/Ddl/CreateTable.php b/src/Sql/Ddl/CreateTable.php index e068c388..6d0ddb2b 100644 --- a/src/Sql/Ddl/CreateTable.php +++ b/src/Sql/Ddl/CreateTable.php @@ -6,9 +6,14 @@ use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Sql\AbstractSql; +use PhpDb\Sql\Literal; use PhpDb\Sql\TableIdentifier; use function array_key_exists; +use function implode; +use function is_bool; +use function is_int; +use function strtoupper; class CreateTable extends AbstractSql { @@ -18,6 +23,8 @@ class CreateTable extends AbstractSql final public const TABLE = 'table'; + final public const TABLE_OPTIONS = 'tableOptions'; + protected array $columns = []; protected array $constraints = []; @@ -26,23 +33,26 @@ class CreateTable extends AbstractSql protected bool $isTemporary = false; + protected array $options = []; + /** * {@inheritDoc} */ protected array $specifications = [ - self::TABLE => 'CREATE %1$sTABLE %2$s%3$s (', - self::COLUMNS => [ + self::TABLE => 'CREATE %1$sTABLE %2$s%3$s (', + self::COLUMNS => [ "\n %1\$s" => [ [1 => '%1$s', 'combinedby' => ",\n "], ], ], - 'combinedBy' => ',', - self::CONSTRAINTS => [ + 'combinedBy' => ',', + self::CONSTRAINTS => [ "\n %1\$s" => [ [1 => '%1$s', 'combinedby' => ",\n "], ], ], - 'statementEnd' => '%1$s', + 'statementEnd' => '%1$s', + self::TABLE_OPTIONS => '%1$s', ]; protected string|TableIdentifier $table = ''; @@ -93,6 +103,23 @@ public function addConstraint(Constraint\ConstraintInterface $constraint): stati return $this; } + public function setOption(string $name, Literal|bool|int|string $value): static + { + $this->options[$name] = $value; + return $this; + } + + public function setOptions(array $options): static + { + $this->options = $options; + return $this; + } + + public function getOptions(): array + { + return $this->options; + } + /** * @return ((Column\ColumnInterface|string)[]|Column\ColumnInterface|string)[]|string * @psalm-return array|string>|string @@ -100,9 +127,10 @@ public function addConstraint(Constraint\ConstraintInterface $constraint): stati public function getRawState(?string $key = null): array|string { $rawState = [ - self::COLUMNS => $this->columns, - self::CONSTRAINTS => $this->constraints, - self::TABLE => $this->table, + self::COLUMNS => $this->columns, + self::CONSTRAINTS => $this->constraints, + self::TABLE => $this->table, + self::TABLE_OPTIONS => $this->options, ]; return isset($key) && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; @@ -172,4 +200,31 @@ protected function processStatementEnd(?PlatformInterface $adapterPlatform = nul { return ["\n)"]; } + + /** + * @return string[]|null + */ + protected function processTableOptions(?PlatformInterface $adapterPlatform = null): ?array + { + if (! $this->options) { + return null; + } + + $parts = []; + foreach ($this->options as $key => $value) { + $key = strtoupper($key); + if ($value instanceof Literal) { + $value = $value->getLiteral(); + } elseif (is_bool($value)) { + $value = $value ? '1' : '0'; + } elseif (is_int($value)) { + $value = (string) $value; + } else { + $value = $adapterPlatform->quoteTrustedValue($value); + } + $parts[] = $key . ' = ' . $value; + } + + return [implode(' ', $parts)]; + } } diff --git a/test/unit/Sql/Ddl/AlterTableTest.php b/test/unit/Sql/Ddl/AlterTableTest.php index 215349f4..a5bf50a4 100644 --- a/test/unit/Sql/Ddl/AlterTableTest.php +++ b/test/unit/Sql/Ddl/AlterTableTest.php @@ -9,6 +9,7 @@ use PhpDb\Sql\Ddl\Column\ColumnInterface; use PhpDb\Sql\Ddl\Constraint; use PhpDb\Sql\Ddl\Constraint\ConstraintInterface; +use PhpDb\Sql\Literal; use PhpDb\Sql\TableIdentifier; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\TestCase; @@ -31,6 +32,10 @@ #[CoversMethod(AlterTable::class, 'processAddConstraints')] #[CoversMethod(AlterTable::class, 'processDropConstraints')] #[CoversMethod(AlterTable::class, 'processDropIndexes')] +#[CoversMethod(AlterTable::class, 'setOption')] +#[CoversMethod(AlterTable::class, 'setOptions')] +#[CoversMethod(AlterTable::class, 'getOptions')] +#[CoversMethod(AlterTable::class, 'processTableOptions')] class AlterTableTest extends TestCase { public function testSetTable(): void @@ -346,4 +351,99 @@ public function testTableIdentifierInChangeColumn(): void self::assertStringContainsString('"schema"."table"', $sql); self::assertStringContainsString('CHANGE COLUMN "col1" "col1_new"', $sql); } + + public function testSetOptionFluentInterface(): void + { + $at = new AlterTable('foo'); + $result = $at->setOption('engine', new Literal('InnoDB')); + + self::assertSame($at, $result); + self::assertEquals(['engine' => new Literal('InnoDB')], $at->getOptions()); + } + + public function testSetOptionsReplacesAll(): void + { + $at = new AlterTable('foo'); + $at->setOption('engine', new Literal('InnoDB')); + + $at->setOptions(['charset' => new Literal('utf8mb4')]); + + self::assertEquals(['charset' => new Literal('utf8mb4')], $at->getOptions()); + } + + public function testGetOptionsReturnsEmpty(): void + { + $at = new AlterTable('foo'); + self::assertEquals([], $at->getOptions()); + } + + public function testGetRawStateIncludesTableOptions(): void + { + $at = new AlterTable('foo'); + $at->setOption('engine', new Literal('InnoDB')); + + $rawState = $at->getRawState(); + + self::assertArrayHasKey(AlterTable::TABLE_OPTIONS, $rawState); + self::assertEquals(['engine' => new Literal('InnoDB')], $rawState[AlterTable::TABLE_OPTIONS]); + } + + public function testGetSqlStringWithEngineOption(): void + { + $at = new AlterTable('foo'); + $at->setOption('engine', new Literal('InnoDB')); + + $sql = $at->getSqlString(); + self::assertStringContainsString('ALTER TABLE "foo"', $sql); + self::assertStringContainsString('ENGINE = InnoDB', $sql); + } + + public function testGetSqlStringWithColumnAndEngineOption(): void + { + $at = new AlterTable('foo'); + $at->addColumn(new Column\Column('bar')); + $at->setOption('engine', new Literal('InnoDB')); + + $sql = $at->getSqlString(); + self::assertStringContainsString('ADD COLUMN "bar" INTEGER NOT NULL', $sql); + self::assertStringContainsString('ENGINE = InnoDB', $sql); + } + + public function testGetSqlStringWithStringOption(): void + { + $at = new AlterTable('foo'); + $at->setOption('comment', 'My table'); + + $sql = $at->getSqlString(); + self::assertStringContainsString('COMMENT = \'My table\'', $sql); + } + + public function testGetSqlStringWithIntOption(): void + { + $at = new AlterTable('foo'); + $at->setOption('auto_increment', 100); + + $sql = $at->getSqlString(); + self::assertStringContainsString('AUTO_INCREMENT = 100', $sql); + } + + public function testGetSqlStringWithBoolOption(): void + { + $at = new AlterTable('foo'); + $at->setOption('pack_keys', true); + + $sql = $at->getSqlString(); + self::assertStringContainsString('PACK_KEYS = 1', $sql); + } + + public function testGetSqlStringWithMultipleOptions(): void + { + $at = new AlterTable('foo'); + $at->setOption('engine', new Literal('InnoDB')); + $at->setOption('auto_increment', 100); + + $sql = $at->getSqlString(); + self::assertStringContainsString('ENGINE = InnoDB', $sql); + self::assertStringContainsString('AUTO_INCREMENT = 100', $sql); + } } diff --git a/test/unit/Sql/Ddl/CreateTableTest.php b/test/unit/Sql/Ddl/CreateTableTest.php index a31547d9..34c5a75a 100644 --- a/test/unit/Sql/Ddl/CreateTableTest.php +++ b/test/unit/Sql/Ddl/CreateTableTest.php @@ -9,6 +9,7 @@ use PhpDb\Sql\Ddl\Constraint; use PhpDb\Sql\Ddl\Constraint\ConstraintInterface; use PhpDb\Sql\Ddl\CreateTable; +use PhpDb\Sql\Literal; use PhpDb\Sql\TableIdentifier; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\TestCase; @@ -28,6 +29,10 @@ #[CoversMethod(CreateTable::class, 'processCombinedby')] #[CoversMethod(CreateTable::class, 'processConstraints')] #[CoversMethod(CreateTable::class, 'processStatementEnd')] +#[CoversMethod(CreateTable::class, 'setOption')] +#[CoversMethod(CreateTable::class, 'setOptions')] +#[CoversMethod(CreateTable::class, 'getOptions')] +#[CoversMethod(CreateTable::class, 'processTableOptions')] class CreateTableTest extends TestCase { /** @@ -311,6 +316,114 @@ public function testSetTableAfterConstruction(): void self::assertEquals('another_table', $ct->getRawState(CreateTable::TABLE)); } + public function testSetOptionFluentInterface(): void + { + $ct = new CreateTable('foo'); + $result = $ct->setOption('engine', new Literal('InnoDB')); + + self::assertSame($ct, $result); + self::assertEquals(['engine' => new Literal('InnoDB')], $ct->getOptions()); + } + + public function testSetOptionsReplacesAll(): void + { + $ct = new CreateTable('foo'); + $ct->setOption('engine', new Literal('InnoDB')); + + $ct->setOptions(['charset' => new Literal('utf8mb4')]); + + self::assertEquals(['charset' => new Literal('utf8mb4')], $ct->getOptions()); + } + + public function testGetOptionsReturnsEmpty(): void + { + $ct = new CreateTable('foo'); + self::assertEquals([], $ct->getOptions()); + } + + public function testGetRawStateIncludesTableOptions(): void + { + $ct = new CreateTable('foo'); + $ct->setOption('engine', new Literal('InnoDB')); + + $rawState = $ct->getRawState(); + + self::assertArrayHasKey(CreateTable::TABLE_OPTIONS, $rawState); + self::assertEquals(['engine' => new Literal('InnoDB')], $rawState[CreateTable::TABLE_OPTIONS]); + } + + public function testGetSqlStringWithLiteralOption(): void + { + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + $ct->setOption('engine', new Literal('InnoDB')); + + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) ENGINE = InnoDB", + $ct->getSqlString() + ); + } + + public function testGetSqlStringWithStringOption(): void + { + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + $ct->setOption('comment', 'My table'); + + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) COMMENT = 'My table'", + $ct->getSqlString() + ); + } + + public function testGetSqlStringWithIntOption(): void + { + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + $ct->setOption('auto_increment', 100); + + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) AUTO_INCREMENT = 100", + $ct->getSqlString() + ); + } + + public function testGetSqlStringWithBoolOption(): void + { + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + $ct->setOption('pack_keys', true); + + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) PACK_KEYS = 1", + $ct->getSqlString() + ); + } + + public function testGetSqlStringWithMultipleOptions(): void + { + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + $ct->setOption('engine', new Literal('InnoDB')); + $ct->setOption('auto_increment', 100); + + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) ENGINE = InnoDB AUTO_INCREMENT = 100", + $ct->getSqlString() + ); + } + + public function testGetSqlStringWithNoOptionsUnchanged(): void + { + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", + $ct->getSqlString() + ); + } + public function testIfNotExists(): void { $ct = new CreateTable('foo');