diff --git a/.gitignore b/.gitignore index 27a3df6..041cd14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea /vendor/ composer.lock /tests/coverage.html diff --git a/composer.json b/composer.json index 191e5e4..b485479 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,10 @@ "classmap": ["src/"] }, "require-dev": { - "nette/tester": "^2.5" - } + "nette/tester": "^2.5", + "phpstan/phpstan": "^2.1" + }, + "suggest": { + "ext-sqlite3": "For SQLite driver" + } } diff --git a/src/Drivers/DateParserTrait.php b/src/Drivers/DateParserTrait.php new file mode 100644 index 0000000..5c4f7dc --- /dev/null +++ b/src/Drivers/DateParserTrait.php @@ -0,0 +1,20 @@ +format($format); + } catch (Exception $e) { + return ''; + } + } + } \ No newline at end of file diff --git a/src/Drivers/MysqlDriver.php b/src/Drivers/MysqlDriver.php index 5e21111..729763d 100644 --- a/src/Drivers/MysqlDriver.php +++ b/src/Drivers/MysqlDriver.php @@ -5,19 +5,31 @@ namespace CzProject\SqlGenerator\Drivers; use CzProject\SqlGenerator\IDriver; + use DateTimeInterface; - - class MysqlDriver implements IDriver + class MysqlDriver implements IDriver { - public function escapeIdentifier($value) - { + use DateParserTrait; + + /** + * @var string[] + */ + private const TRANSACTION = [ + 'start' => 'BEGIN;', + 'commit' => 'COMMIT;', + 'rollback' => 'ROLLBACK;', + ]; + + private const LAST_ID = 'LAST_INSERT_ID()'; + + public function escapeIdentifier(string $value): string + { // @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html // @see http://api.dibiphp.com/2.3.2/source-drivers.DibiMySqlDriver.php.html#307 return '`' . str_replace('`', '``', $value) . '`'; } - - public function escapeText($value) + public function escapeText(string $value): string { // https://dev.mysql.com/doc/refman/5.5/en/string-literals.html // http://us3.php.net/manual/en/function.mysql-real-escape-string.php#101248 @@ -28,27 +40,28 @@ public function escapeText($value) ) . '\''; } - - public function escapeBool($value) + public function escapeBool(bool $value): string { return $value ? '1' : '0'; } - - public function escapeDate($value) + public function escapeDate(DateTimeInterface|string $value): string { - if (!($value instanceof \DateTime) && !($value instanceof \DateTimeInterface)) { - $value = new \DateTime($value); - } - return $value->format("'Y-m-d'"); + return $this->dateFormat($value, "'Y-m-d'"); } - - public function escapeDateTime($value) + public function escapeDateTime(DateTimeInterface|string $value): string { - if (!($value instanceof \DateTime) && !($value instanceof \DateTimeInterface)) { - $value = new \DateTime($value); - } - return $value->format("'Y-m-d H:i:s'"); + return $this->dateFormat($value, "'Y-m-d H:i:s'"); } + + public function transaction(string $action): string + { + return self::TRANSACTION[$action]; + } + + public function lastId(): string + { + return self::LAST_ID; + } } diff --git a/src/Drivers/SqliteDriver.php b/src/Drivers/SqliteDriver.php new file mode 100644 index 0000000..47b8b82 --- /dev/null +++ b/src/Drivers/SqliteDriver.php @@ -0,0 +1,65 @@ + 'BEGIN TRANSACTION;', + 'commit' => 'COMMIT TRANSACTION;', + 'rollback' => 'ROLLBACK TRANSACTION;', + ]; + + private const LAST_ID = 'LAST_INSERT_ROWID()'; + + public function escapeIdentifier(string $value): string + { + return '"'.str_replace('"', '""', $value).'"'; + } + + public function escapeText(string $value): string + { + return "'".SQLite3::escapeString($value)."'"; + } + + public function escapeBool(bool $value): string + { + return strval($value ? 1 : 0); + } + + public function escapeDate(DateTimeInterface|string $value): string + { + return $this->escapeText($this->dateFormat($value, 'Y-m-d')); + } + + public function escapeDateTime(DateTimeInterface|string $value): string + { + return $this->escapeText($this->dateFormat($value, 'Y-m-d H:i:s')); + } + + public function transaction(string $action): string + { + return self::TRANSACTION[$action]; + } + + public function lastId(): string + { + return self::LAST_ID; + } + } diff --git a/src/Helpers.php b/src/Helpers.php index 29db07d..09f03c5 100644 --- a/src/Helpers.php +++ b/src/Helpers.php @@ -4,52 +4,48 @@ namespace CzProject\SqlGenerator; + use DateTimeInterface; class Helpers { - public function __construct() + /** + * @throws StaticClassException + */ + public function __construct() { throw new StaticClassException('This is static class.'); } - - /** - * @param mixed $value - * @return string - * @throws InvalidArgumentException - * @see https://api.dibiphp.com/3.0/source-Dibi.Translator.php.html#174 - */ - public static function formatValue($value, IDriver $driver) - { + /** + * @throws InvalidArgumentException + * @see https://api.dibiphp.com/3.0/source-Dibi.Translator.php.html#174 + */ + public static function formatValue(mixed $value, IDriver $driver): string + { if (is_string($value)) { return $driver->escapeText($value); - - } elseif (is_int($value)) { - return (string) $value; - - } elseif (is_float($value)) { + } + elseif (is_int($value)) { + return (string)$value; + } + elseif (is_float($value)) { return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'); - - } elseif (is_bool($value)) { + } + elseif (is_bool($value)) { return $driver->escapeBool($value); - - } elseif ($value === NULL) { + } + elseif ($value === NULL) { return 'NULL'; - - } elseif ($value instanceof \DateTime || $value instanceof \DateTimeInterface) { + } + elseif ($value instanceof DateTimeInterface) { return $driver->escapeDateTime($value); } throw new InvalidArgumentException("Unsupported value type."); } - - /** - * @param string|TableName $tableName - * @return string - */ - public static function escapeTableName($tableName, IDriver $driver) - { + public static function escapeTableName(TableName|string $tableName, IDriver $driver): string + { if ($tableName instanceof TableName) { return $tableName->toString($driver); } @@ -57,13 +53,8 @@ public static function escapeTableName($tableName, IDriver $driver) return $driver->escapeIdentifier($tableName); } - - /** - * @param string|TableName $tableName - * @return string|TableName - */ - public static function createTableName($tableName) - { + public static function createTableName(TableName|string $tableName): TableName|string + { if (is_string($tableName) && strpos($tableName, '.')) { return TableName::create($tableName); } @@ -71,13 +62,8 @@ public static function createTableName($tableName) return $tableName; } - - /** - * @param string $s - * @return string - */ - public static function normalizeNewLines($s) - { + public static function normalizeNewLines(string $s): string + { return str_replace(["\r\n", "\r"], "\n", $s); } } diff --git a/src/IDriver.php b/src/IDriver.php index d257d64..b304882 100644 --- a/src/IDriver.php +++ b/src/IDriver.php @@ -4,36 +4,30 @@ namespace CzProject\SqlGenerator; - - interface IDriver + use DateTimeInterface; + + /** + * A few additional properties can be set to control the driver's capabilities: + * - `public bool $renameTable` + * - `public bool $renameColumn` + * - `public bool $modifyColumn` + */ + interface IDriver { - /** - * @param string $value - * @return string - */ - function escapeIdentifier($value); - - /** - * @param string $value - * @return string - */ - function escapeText($value); - - /** - * @param bool $value - * @return string - */ - function escapeBool($value); - - /** - * @param string|\DateTime|\DateTimeInterface $value - * @return string - */ - function escapeDate($value); - - /** - * @param string|\DateTime|\DateTimeInterface $value - * @return string - */ - function escapeDateTime($value); + public function escapeIdentifier(string $value): string; + + public function escapeText(string $value): string; + + public function escapeBool(bool $value): string; + + public function escapeDate(DateTimeInterface|string $value): string; + + public function escapeDateTime(DateTimeInterface|string $value): string; + + /** + * @param 'start'|'commit'|'rollback' $action + */ + public function transaction(string $action): string; + + public function lastId(): string; } diff --git a/src/IStatement.php b/src/IStatement.php index 900a413..7c23c87 100644 --- a/src/IStatement.php +++ b/src/IStatement.php @@ -4,11 +4,7 @@ namespace CzProject\SqlGenerator; - interface IStatement { - /** - * @return string - */ - function toSql(IDriver $driver); + public function toSql(IDriver $driver): string; } diff --git a/src/SqlDocument.php b/src/SqlDocument.php index 9df7352..d80ef69 100644 --- a/src/SqlDocument.php +++ b/src/SqlDocument.php @@ -4,37 +4,29 @@ namespace CzProject\SqlGenerator; + use CzProject\SqlGenerator\Statements\Transaction; - class SqlDocument + class SqlDocument { /** @var IStatement[] */ - private $statements = []; + private array $statements = []; - - /** - * @return static - */ - public function addStatement(IStatement $statement) - { + public function addStatement(IStatement $statement): static + { $this->statements[] = $statement; return $this; } - - /** - * @return bool - */ - public function isEmpty() - { + public function isEmpty(): bool + { return empty($this->statements); } - /** * @return string[] */ - public function getSqlQueries(IDriver $driver) - { + public function getSqlQueries(IDriver $driver): array + { $output = []; foreach ($this->statements as $statement) { @@ -44,12 +36,8 @@ public function getSqlQueries(IDriver $driver) return $output; } - - /** - * @return string - */ - public function toSql(IDriver $driver) - { + public function toSql(IDriver $driver): string + { $output = ''; $first = TRUE; @@ -68,14 +56,11 @@ public function toSql(IDriver $driver) return $output; } - /** - * @param string $file - * @return void * @throws IOException */ - public function save($file, IDriver $driver) - { + public function save(string $file, IDriver $driver): void + { // create directory $dir = dirname($file); @@ -91,87 +76,75 @@ public function save($file, IDriver $driver) } } - - /** - * @param string|TableName $tableName - * @param array $data - * @return Statements\Insert - */ - public function insert($tableName, array $data) - { + /** + * @param 'start'|'commit'|'rollback' $action + * @return Transaction + */ + public function transaction(string $action): Statements\Transaction + { + $statement = new Statements\Transaction($action); + $this->addStatement($statement); + return $statement; + } + + /** + * @param array $data + */ + public function insert(string|TableName $tableName, array $data): Statements\Insert + { $statement = new Statements\Insert($tableName, $data); $this->addStatement($statement); return $statement; } - - /** - * @param string|TableName $tableName - * @return Statements\CreateTable - */ - public function createTable($tableName) - { + /** + * @param array $data + * @param array $where + */ + public function update(string|TableName $tableName, array $data, array $where): Statements\Update + { + $statement = new Statements\Update($tableName, $data, $where); + $this->addStatement($statement); + return $statement; + } + + public function createTable(string|TableName $tableName): Statements\CreateTable + { $statement = new Statements\CreateTable($tableName); $this->addStatement($statement); return $statement; } - - /** - * @param string|TableName $tableName - * @return Statements\DropTable - */ - public function dropTable($tableName) - { + public function dropTable(string|TableName $tableName): Statements\DropTable + { $statement = new Statements\DropTable($tableName); $this->addStatement($statement); return $statement; } - - /** - * @param string|TableName $oldTable - * @param string|TableName $newTable - * @return Statements\RenameTable - */ - public function renameTable($oldTable, $newTable) - { + public function renameTable(string|TableName $oldTable, string|TableName $newTable): Statements\RenameTable + { $statement = new Statements\RenameTable($oldTable, $newTable); $this->addStatement($statement); return $statement; } - - /** - * @param string|TableName $tableName - * @return Statements\AlterTable - */ - public function alterTable($tableName) - { + public function alterTable(string|TableName $tableName): Statements\AlterTable + { $statement = new Statements\AlterTable($tableName); $this->addStatement($statement); return $statement; } - - /** - * @param string $command - * @return Statements\SqlCommand - */ - public function command($command) - { + public function command(string $command): Statements\SqlCommand + { $statement = new Statements\SqlCommand($command); $this->addStatement($statement); return $statement; } - - /** - * @param string $comment - * @return Statements\Comment - */ - public function comment($comment) - { + public function comment(string $comment): Statements\Comment + { $statement = new Statements\Comment($comment); $this->addStatement($statement); return $statement; diff --git a/src/Statements/AddColumn.php b/src/Statements/AddColumn.php deleted file mode 100644 index 89d33da..0000000 --- a/src/Statements/AddColumn.php +++ /dev/null @@ -1,124 +0,0 @@ - $parameters - * @param array $options [name => value] - */ - public function __construct($name, $type, ?array $parameters = NULL, array $options = []) - { - $this->definition = new ColumnDefinition($name, $type, $parameters, $options); - } - - - /** - * @return static - */ - public function moveToFirstPosition() - { - $this->position = self::POSITION_FIRST; - return $this; - } - - - /** - * @param string $column - * @return static - */ - public function moveAfterColumn($column) - { - $this->position = $column; - return $this; - } - - - /** - * @return static - */ - public function moveToLastPosition() - { - $this->position = self::POSITION_LAST; - return $this; - } - - - /** - * @param bool $nullable - * @return static - */ - public function setNullable($nullable = TRUE) - { - $this->definition->setNullable($nullable); - return $this; - } - - - /** - * @param mixed|NULL $defaultValue - * @return static - */ - public function setDefaultValue($defaultValue) - { - $this->definition->setDefaultValue($defaultValue); - return $this; - } - - - /** - * @param bool $autoIncrement - * @return static - */ - public function setAutoIncrement($autoIncrement = TRUE) - { - $this->definition->setAutoIncrement($autoIncrement); - return $this; - } - - - /** - * @param string|NULL $comment - * @return static - */ - public function setComment($comment) - { - $this->definition->setComment($comment); - return $this; - } - - - public function toSql(IDriver $driver) - { - $output = 'ADD COLUMN ' . $this->definition->toSql($driver); - - if ($this->position === self::POSITION_FIRST) { - $output .= ' FIRST'; - - } elseif ($this->position !== self::POSITION_LAST) { - $output .= ' AFTER ' . $driver->escapeIdentifier($this->position); - } - - return $output; - } - } diff --git a/src/Statements/AddForeignKey.php b/src/Statements/AddForeignKey.php deleted file mode 100644 index fddda28..0000000 --- a/src/Statements/AddForeignKey.php +++ /dev/null @@ -1,56 +0,0 @@ -definition = new ForeignKeyDefinition($name, $columns, $targetTable, $targetColumns); - } - - - /** - * @param string $onUpdateAction - * @return static - */ - public function setOnUpdateAction($onUpdateAction) - { - $this->definition->setOnUpdateAction($onUpdateAction); - return $this; - } - - - /** - * @param string $onDeleteAction - * @return static - */ - public function setOnDeleteAction($onDeleteAction) - { - $this->definition->setOnDeleteAction($onDeleteAction); - return $this; - } - - - public function toSql(IDriver $driver) - { - return 'ADD ' . $this->definition->toSql($driver); - } - } diff --git a/src/Statements/AddIndex.php b/src/Statements/AddIndex.php deleted file mode 100644 index a7480bc..0000000 --- a/src/Statements/AddIndex.php +++ /dev/null @@ -1,44 +0,0 @@ -definition = new IndexDefinition($name, $type); - } - - - /** - * @param string $column - * @param string $order - * @param int|NULL $length - * @return static - */ - public function addColumn($column, $order = IndexColumnDefinition::ASC, $length = NULL) - { - $this->definition->addColumn($column, $order, $length); - return $this; - } - - - public function toSql(IDriver $driver) - { - return 'ADD ' . $this->definition->toSql($driver); - } - } diff --git a/src/Statements/Alter/AddColumn.php b/src/Statements/Alter/AddColumn.php new file mode 100644 index 0000000..f5f05c9 --- /dev/null +++ b/src/Statements/Alter/AddColumn.php @@ -0,0 +1,92 @@ + $parameters + * @param array $options [name => value] + */ + public function __construct(string $name, string $type, ?array $parameters = NULL, array $options = []) + { + $this->definition = new ColumnDefinition($name, $type, $parameters, $options); + } + + public function moveToFirstPosition(): static + { + $this->position = self::POSITION_FIRST; + return $this; + } + + public function moveAfterColumn(string $column): static + { + $this->position = $column; + return $this; + } + + public function moveToLastPosition(): static + { + $this->position = self::POSITION_LAST; + return $this; + } + + public function setNullable(bool $nullable = TRUE): static + { + $this->definition->setNullable($nullable); + return $this; + } + + public function setDefaultValue(mixed $defaultValue): static + { + $this->definition->setDefaultValue($defaultValue); + return $this; + } + + public function setAutoIncrement(bool $autoIncrement = TRUE): static + { + $this->definition->setAutoIncrement($autoIncrement); + return $this; + } + + public function setComment(?string $comment): static + { + $this->definition->setComment($comment); + return $this; + } + + + /** + * @throws InvalidArgumentException + */ + public function toSql(IDriver $driver): string + { + $output = 'ADD COLUMN ' . $this->definition->toSql($driver); + + if ($driver->modifyColumn ?? true) { + if ($this->position === self::POSITION_FIRST) { + $output .= ' FIRST'; + } + elseif ($this->position !== self::POSITION_LAST) { + $output .= ' AFTER ' . $driver->escapeIdentifier($this->position); + } + } + + return $output; + } + } diff --git a/src/Statements/Alter/AddForeignKey.php b/src/Statements/Alter/AddForeignKey.php new file mode 100644 index 0000000..2dae0fe --- /dev/null +++ b/src/Statements/Alter/AddForeignKey.php @@ -0,0 +1,60 @@ +definition = new ForeignKeyDefinition($name, $columns, $targetTable, $targetColumns); + } + + + /** + * @throws OutOfRangeException + */ + public function setOnUpdateAction(string $onUpdateAction): static + { + $this->definition->setOnUpdateAction($onUpdateAction); + return $this; + } + + + /** + * @throws OutOfRangeException + */ + public function setOnDeleteAction(string $onDeleteAction): static + { + $this->definition->setOnDeleteAction($onDeleteAction); + return $this; + } + + + /** + * @throws NotImplementedException + */ + public function toSql(IDriver $driver): string + { + if ($driver->modifyColumn ?? true) { + return 'ADD ' . $this->definition->toSql($driver); + } + else { + throw new NotImplementedException('Add key is not implemented for driver ' . get_class($driver) . '.'); + } + } + } diff --git a/src/Statements/Alter/AddIndex.php b/src/Statements/Alter/AddIndex.php new file mode 100644 index 0000000..bc348ef --- /dev/null +++ b/src/Statements/Alter/AddIndex.php @@ -0,0 +1,42 @@ +definition = new IndexDefinition($name, $type); + } + + public function addColumn(string $column, string $order = IndexColumnDefinition::ASC, ?int $length = NULL): static + { + $this->definition->addColumn($column, $order, $length); + return $this; + } + + /** + * @throws NotImplementedException + */ + public function toSql(IDriver $driver): string + { + if ($driver->modifyColumn ?? true) { + return 'ADD ' . $this->definition->toSql($driver); + } + else { + throw new NotImplementedException('Add key is not implemented for driver ' . get_class($driver) . '.'); + } + } + } diff --git a/src/Statements/DropColumn.php b/src/Statements/Alter/DropColumn.php similarity index 65% rename from src/Statements/DropColumn.php rename to src/Statements/Alter/DropColumn.php index 4146a22..3479c5e 100644 --- a/src/Statements/DropColumn.php +++ b/src/Statements/Alter/DropColumn.php @@ -7,24 +7,17 @@ use CzProject\SqlGenerator\IDriver; use CzProject\SqlGenerator\IStatement; - class DropColumn implements IStatement { - /** @var string */ - private $column; - + private string $column; - /** - * @param string $column - */ - public function __construct($column) + public function __construct(string $column) { $this->column = $column; } - - public function toSql(IDriver $driver) - { + public function toSql(IDriver $driver): string + { return 'DROP COLUMN ' . $driver->escapeIdentifier($this->column); } } diff --git a/src/Statements/Alter/DropForeignKey.php b/src/Statements/Alter/DropForeignKey.php new file mode 100644 index 0000000..e14674d --- /dev/null +++ b/src/Statements/Alter/DropForeignKey.php @@ -0,0 +1,33 @@ +foreignKey = $foreignKey; + } + + + /** + * @throws NotImplementedException + */ + public function toSql(IDriver $driver): string + { + if ($driver->modifyColumn ?? true) { + return 'DROP FOREIGN KEY ' . $driver->escapeIdentifier($this->foreignKey); + } + else { + throw new NotImplementedException('Drop key is not implemented for driver ' . get_class($driver) . '.'); + } + } + } diff --git a/src/Statements/Alter/DropIndex.php b/src/Statements/Alter/DropIndex.php new file mode 100644 index 0000000..b80acd0 --- /dev/null +++ b/src/Statements/Alter/DropIndex.php @@ -0,0 +1,43 @@ +index = $index; + } + + + /** + * @throws NotImplementedException + */ + public function toSql(IDriver $driver): string + { + if ($driver->modifyColumn ?? true) { + if ($this->index === NULL) { // PRIMARY KEY + if ($driver instanceof Drivers\MysqlDriver) { + return 'DROP PRIMARY KEY'; + + } else { + throw new NotImplementedException('Drop of primary key is not implemented for driver ' . get_class($driver) . '.'); + } + } + + return 'DROP INDEX ' . $driver->escapeIdentifier($this->index); + } + else { + throw new NotImplementedException('Drop key is not implemented for driver ' . get_class($driver) . '.'); + } + } + } diff --git a/src/Statements/Alter/ModifyColumn.php b/src/Statements/Alter/ModifyColumn.php new file mode 100644 index 0000000..1c6a8fd --- /dev/null +++ b/src/Statements/Alter/ModifyColumn.php @@ -0,0 +1,94 @@ + $parameters + * @param array $options [name => value] + */ + public function __construct(string $name, string $type, ?array $parameters = NULL, array $options = []) + { + $this->definition = new ColumnDefinition($name, $type, $parameters, $options); + } + + public function moveToFirstPosition(): static + { + $this->position = self::POSITION_FIRST; + return $this; + } + + public function moveAfterColumn(string $column): static + { + $this->position = $column; + return $this; + } + + public function moveToLastPosition(): static + { + $this->position = self::POSITION_LAST; + return $this; + } + + public function setNullable(bool $nullable = TRUE): static + { + $this->definition->setNullable($nullable); + return $this; + } + + public function setDefaultValue(mixed $defaultValue): static + { + $this->definition->setDefaultValue($defaultValue); + return $this; + } + + public function setAutoIncrement(bool $autoIncrement = TRUE): static + { + $this->definition->setAutoIncrement($autoIncrement); + return $this; + } + + public function setComment(?string $comment): static + { + $this->definition->setComment($comment); + return $this; + } + + /** + * @throws NotImplementedException|InvalidArgumentException + */ + public function toSql(IDriver $driver): string + { + if ($driver->modifyColumn ?? true) { + $output = 'MODIFY COLUMN ' . $this->definition->toSql($driver); + + if ($this->position === self::POSITION_FIRST) { + $output .= ' FIRST'; + + } elseif ($this->position !== self::POSITION_LAST) { + $output .= ' AFTER ' . $driver->escapeIdentifier($this->position); + } + + return $output . ';'; + } + else { + throw new NotImplementedException('Modify column is not implemented for driver ' . get_class($driver) . '.'); + } + } + } diff --git a/src/Statements/Alter/RenameColumnTo.php b/src/Statements/Alter/RenameColumnTo.php new file mode 100644 index 0000000..4d8058c --- /dev/null +++ b/src/Statements/Alter/RenameColumnTo.php @@ -0,0 +1,31 @@ +oldName = $oldName; + $this->newName = $newName; + } + + public function toSql(IDriver $driver): string + { + $oldName = $driver->escapeIdentifier($this->oldName); + $newName = $driver->escapeIdentifier($this->newName); + + return "RENAME COLUMN $oldName TO $newName"; + } + } diff --git a/src/Statements/Alter/RenameTo.php b/src/Statements/Alter/RenameTo.php new file mode 100644 index 0000000..140d348 --- /dev/null +++ b/src/Statements/Alter/RenameTo.php @@ -0,0 +1,25 @@ +tableName = Helpers::createTableName($tableName); + } + + public function toSql(IDriver $driver): string + { + return 'RENAME TO ' . Helpers::escapeTableName($this->tableName, $driver); + } + } diff --git a/src/Statements/AlterTable.php b/src/Statements/AlterTable.php index 3e4fca3..d60e719 100644 --- a/src/Statements/AlterTable.php +++ b/src/Statements/AlterTable.php @@ -6,140 +6,106 @@ use CzProject\SqlGenerator\Helpers; use CzProject\SqlGenerator\IDriver; - use CzProject\SqlGenerator\IStatement; - use CzProject\SqlGenerator\TableName; + use CzProject\SqlGenerator\InvalidArgumentException; + use CzProject\SqlGenerator\IStatement; + use CzProject\SqlGenerator\OutOfRangeException; + use CzProject\SqlGenerator\TableName; use CzProject\SqlGenerator\Value; - class AlterTable implements IStatement { - /** @var string|TableName */ - private $tableName; + private TableName|string $tableName; /** @var IStatement[] */ - private $statements = []; - - /** @var string|NULL */ - private $comment; + private array $statements = []; - /** @var array [name => value] */ - private $options = []; + private ?string $comment; + /** @var array [name => value] */ + private array $options = []; - /** - * @param string|TableName $tableName - */ - public function __construct($tableName) + public function __construct(string|TableName $tableName) { $this->tableName = Helpers::createTableName($tableName); } + public function rename(string|TableName $newName): RenameTo + { + return $this->statements[] = new RenameTo($newName); + } + + public function renameColumn(string $oldName, string $newName): RenameColumnTo + { + return $this->statements[] = new RenameColumnTo($oldName, $newName); + } /** - * @param string $name - * @param string $type - * @param array $parameters - * @param array $options [name => value] - * @return AddColumn + * @param array $parameters + * @param array $options [name => value] */ - public function addColumn($name, $type, ?array $parameters = NULL, array $options = []) - { + public function addColumn(string $name, string $type, ?array $parameters = NULL, array $options = []): AddColumn + { return $this->statements[] = new AddColumn($name, $type, $parameters, $options); } - - /** - * @param string $column - * @return DropColumn - */ - public function dropColumn($column) - { + public function dropColumn(string $column): DropColumn + { return $this->statements[] = new DropColumn($column); } - /** - * @param string $name - * @param string $type - * @param array $parameters - * @param array $options [name => value] - * @return ModifyColumn + * @param array $parameters + * @param array $options [name => value] */ - public function modifyColumn($name, $type, ?array $parameters = NULL, array $options = []) - { + public function modifyColumn(string $name, string $type, ?array $parameters = NULL, array $options = []): ModifyColumn + { return $this->statements[] = new ModifyColumn($name, $type, $parameters, $options); } - - /** - * @param string|NULL $name - * @param string $type - * @return AddIndex - */ - public function addIndex($name, $type) - { + /** + * @throws OutOfRangeException + */ + public function addIndex(?string $name, string $type): AddIndex + { return $this->statements[] = new AddIndex($name, $type); } - - /** - * @param string|NULL $index - * @return DropIndex - */ - public function dropIndex($index) - { + public function dropIndex(?string $index): DropIndex + { return $this->statements[] = new DropIndex($index); } - /** - * @param string $name - * @param string[]|string $columns - * @param string|TableName $targetTable - * @param string[]|string $targetColumns - * @return AddForeignKey + * @param string|string[] $columns + * @param string|string[] $targetColumns */ - public function addForeignKey($name, $columns, $targetTable, $targetColumns) - { + public function addForeignKey(string $name, array|string $columns, string|TableName $targetTable, array|string $targetColumns): AddForeignKey + { return $this->statements[] = new AddForeignKey($name, $columns, $targetTable, $targetColumns); } - - /** - * @param string $foreignKey - * @return DropForeignKey - */ - public function dropForeignKey($foreignKey) - { + public function dropForeignKey(string $foreignKey): DropForeignKey + { return $this->statements[] = new DropForeignKey($foreignKey); } - - /** - * @param string|NULL $comment - * @return static - */ - public function setComment($comment) - { + public function setComment(?string $comment): static + { $this->comment = $comment; return $this; } - - /** - * @param string $name - * @param string|Value $value - * @return static - */ - public function setOption($name, $value) - { + public function setOption(string $name, string|Value $value): static + { $this->options[$name] = $value; return $this; } - - public function toSql(IDriver $driver) - { + /** + * @throws InvalidArgumentException + */ + public function toSql(IDriver $driver): string + { if (empty($this->statements) && empty($this->options) && !isset($this->comment)) { return ''; } diff --git a/src/Statements/Comment.php b/src/Statements/Comment.php index 9ef2528..d5cd712 100644 --- a/src/Statements/Comment.php +++ b/src/Statements/Comment.php @@ -8,24 +8,17 @@ use CzProject\SqlGenerator\IDriver; use CzProject\SqlGenerator\IStatement; - class Comment implements IStatement { - /** @var string */ - private $comment; - + private string $comment; - /** - * @param string $comment - */ - public function __construct($comment) + public function __construct(string $comment) { $this->comment = $comment; } - - public function toSql(IDriver $driver) - { + public function toSql(IDriver $driver): string + { return '-- ' . str_replace("\n", "\n-- ", Helpers::normalizeNewLines(trim($this->comment))); } } diff --git a/src/Statements/CreateTable.php b/src/Statements/CreateTable.php index d7b2c61..2a262f0 100644 --- a/src/Statements/CreateTable.php +++ b/src/Statements/CreateTable.php @@ -7,50 +7,46 @@ use CzProject\SqlGenerator\DuplicateException; use CzProject\SqlGenerator\Helpers; use CzProject\SqlGenerator\IDriver; - use CzProject\SqlGenerator\IStatement; - use CzProject\SqlGenerator\TableName; + use CzProject\SqlGenerator\InvalidArgumentException; + use CzProject\SqlGenerator\IStatement; + use CzProject\SqlGenerator\OutOfRangeException; + use CzProject\SqlGenerator\TableName; use CzProject\SqlGenerator\Value; class CreateTable implements IStatement { - /** @var string|TableName */ - private $tableName; + private TableName|string $tableName; /** @var array [name => ColumnDefinition] */ - private $columns = []; + private array $columns = []; /** @var array [name => IndexDefinition] */ - private $indexes = []; + private array $indexes = []; /** @var array [name => ForeignKeyDefinition] */ - private $foreignKeys = []; + private array $foreignKeys = []; - /** @var string|NULL */ - private $comment; + private ?string $comment; /** @var array [name => value] */ - private $options = []; - + private array $options = []; /** - * @param string|TableName $tableName + * @param string|TableName $tableName */ - public function __construct($tableName) + public function __construct(string|TableName $tableName) { $this->tableName = Helpers::createTableName($tableName); } - - /** - * @param string $name - * @param string $type - * @param array|NULL $parameters - * @param array $options - * @return ColumnDefinition - */ - public function addColumn($name, $type, ?array $parameters = NULL, array $options = []) - { + /** + * @param array|NULL $parameters + * @param array $options + * @throws DuplicateException + */ + public function addColumn(string $name, string $type, ?array $parameters = NULL, array $options = []): ColumnDefinition + { if (isset($this->columns[$name])) { throw new DuplicateException("Column '$name' already exists."); } @@ -58,14 +54,11 @@ public function addColumn($name, $type, ?array $parameters = NULL, array $option return $this->columns[$name] = new ColumnDefinition($name, $type, $parameters, $options); } - - /** - * @param string|NULL $name - * @param string $type - * @return IndexDefinition - */ - public function addIndex($name, $type) - { + /** + * @throws DuplicateException|OutOfRangeException + */ + public function addIndex(?string $name, string $type): IndexDefinition + { if (isset($this->indexes[$name])) { throw new DuplicateException("Index '$name' already exists."); } @@ -73,16 +66,13 @@ public function addIndex($name, $type) return $this->indexes[$name] = new IndexDefinition($name, $type); } - - /** - * @param string $name - * @param string[]|string $columns - * @param string|TableName $targetTable - * @param string[]|string $targetColumns - * @return ForeignKeyDefinition - */ - public function addForeignKey($name, $columns, $targetTable, $targetColumns) - { + /** + * @param string|string[] $columns + * @param string|string[] $targetColumns + * @throws DuplicateException + */ + public function addForeignKey(string $name, array|string $columns, string|TableName $targetTable, array|string $targetColumns): ForeignKeyDefinition + { if (isset($this->foreignKeys[$name])) { throw new DuplicateException("Foreign key '$name' already exists."); } @@ -90,32 +80,24 @@ public function addForeignKey($name, $columns, $targetTable, $targetColumns) return $this->foreignKeys[$name] = new ForeignKeyDefinition($name, $columns, $targetTable, $targetColumns); } - - /** - * @param string|NULL $comment - * @return static - */ - public function setComment($comment) - { + public function setComment(?string $comment): static + { $this->comment = $comment; return $this; } - - /** - * @param string $name - * @param string|Value $value - * @return static - */ - public function setOption($name, $value) - { + public function setOption(string $name, string|Value $value): static + { $this->options[$name] = $value; return $this; } - public function toSql(IDriver $driver) - { + /** + * @throws InvalidArgumentException + */ + public function toSql(IDriver $driver): string + { $output = 'CREATE TABLE ' . Helpers::escapeTableName($this->tableName, $driver) . " (\n"; // columns diff --git a/src/Statements/ColumnDefinition.php b/src/Statements/Defs/ColumnDefinition.php similarity index 61% rename from src/Statements/ColumnDefinition.php rename to src/Statements/Defs/ColumnDefinition.php index cd7e1d8..dc4c968 100644 --- a/src/Statements/ColumnDefinition.php +++ b/src/Statements/Defs/ColumnDefinition.php @@ -7,44 +7,35 @@ use CzProject\SqlGenerator\Drivers; use CzProject\SqlGenerator\Helpers; use CzProject\SqlGenerator\IDriver; - use CzProject\SqlGenerator\IStatement; + use CzProject\SqlGenerator\InvalidArgumentException; + use CzProject\SqlGenerator\IStatement; use CzProject\SqlGenerator\Value; - class ColumnDefinition implements IStatement { - /** @var string */ - private $name; + private string $name; - /** @var string */ - private $type; + private string $type; /** @var array */ - private $parameters = []; + private array $parameters = []; /** @var array [name => value] */ - private $options = []; - - /** @var bool */ - private $nullable = FALSE; + private array $options = []; - /** @var mixed|NULL */ - private $defaultValue; + private bool $nullable = FALSE; - /** @var bool */ - private $autoIncrement = FALSE; + private mixed $defaultValue; - /** @var string|NULL */ - private $comment; + private bool $autoIncrement = FALSE; + private ?string $comment; /** - * @param string $name - * @param string $type - * @param array|NULL $parameters - * @param array $options [name => value] + * @param array|NULL $parameters + * @param array $options [name => value] */ - public function __construct($name, $type, ?array $parameters = NULL, array $options = []) + public function __construct(string $name, string $type, ?array $parameters = NULL, array $options = []) { $this->name = $name; $this->type = $type; @@ -52,52 +43,34 @@ public function __construct($name, $type, ?array $parameters = NULL, array $opti $this->options = $options; } - - /** - * @param bool $nullable - * @return static - */ - public function setNullable($nullable = TRUE) - { + public function setNullable(bool $nullable = TRUE): static + { $this->nullable = $nullable; return $this; } - - /** - * @param mixed|NULL $defaultValue - * @return static - */ - public function setDefaultValue($defaultValue) - { + public function setDefaultValue(mixed $defaultValue): static + { $this->defaultValue = $defaultValue; return $this; } - - /** - * @param bool $autoIncrement - * @return static - */ - public function setAutoIncrement($autoIncrement = TRUE) - { + public function setAutoIncrement(bool $autoIncrement = TRUE): static + { $this->autoIncrement = $autoIncrement; return $this; } - - /** - * @param string|NULL $comment - * @return static - */ - public function setComment($comment) - { + public function setComment(?string $comment): static + { $this->comment = $comment; return $this; } - - public function toSql(IDriver $driver) + /** + * @throws InvalidArgumentException + */ + public function toSql(IDriver $driver): string { $output = $driver->escapeIdentifier($this->name) . ' ' . $this->type; @@ -147,14 +120,11 @@ public function toSql(IDriver $driver) return $output; } - - /** - * @param string $name - * @param string|Value|NULL $value - * @return string - */ - private static function formatOption($name, $value, IDriver $driver) - { + /** + * @throws InvalidArgumentException + */ + private static function formatOption(string $name, string|Value|null $value, IDriver $driver): string + { if ($value instanceof Value) { $value = $value->toString($driver); } diff --git a/src/Statements/ForeignKeyDefinition.php b/src/Statements/Defs/ForeignKeyDefinition.php similarity index 69% rename from src/Statements/ForeignKeyDefinition.php rename to src/Statements/Defs/ForeignKeyDefinition.php index 7db5355..f9d46f0 100644 --- a/src/Statements/ForeignKeyDefinition.php +++ b/src/Statements/Defs/ForeignKeyDefinition.php @@ -18,32 +18,25 @@ class ForeignKeyDefinition implements IStatement const ACTION_CASCADE = 'CASCADE'; const ACTION_SET_NULL = 'SET NULL'; - /** @var string */ - private $name; + private string $name; /** @var string[] */ - private $columns; + private array $columns; - /** @var string|TableName */ - private $targetTable; + private TableName|string $targetTable; /** @var string[] */ - private $targetColumns; + private array $targetColumns; - /** @var string */ - private $onUpdateAction = self::ACTION_RESTRICT; - - /** @var string */ - private $onDeleteAction = self::ACTION_RESTRICT; + private string $onUpdateAction = self::ACTION_RESTRICT; + private string $onDeleteAction = self::ACTION_RESTRICT; /** - * @param string $name - * @param string[]|string $columns - * @param string|TableName $targetTable - * @param string[]|string $targetColumns + * @param string|string[] $columns + * @param string|string[] $targetColumns */ - public function __construct($name, $columns, $targetTable, $targetColumns) + public function __construct(string $name, array|string $columns, string|TableName $targetTable, array|string $targetColumns) { $this->name = $name; $this->targetTable = Helpers::createTableName($targetTable); @@ -66,12 +59,11 @@ public function __construct($name, $columns, $targetTable, $targetColumns) } - /** - * @param string $onUpdateAction - * @return static - */ - public function setOnUpdateAction($onUpdateAction) - { + /** + * @throws OutOfRangeException + */ + public function setOnUpdateAction(string $onUpdateAction): static + { if (!$this->validateAction($onUpdateAction)) { throw new OutOfRangeException("Action '$onUpdateAction' is invalid."); } @@ -81,12 +73,11 @@ public function setOnUpdateAction($onUpdateAction) } - /** - * @param string $onDeleteAction - * @return static - */ - public function setOnDeleteAction($onDeleteAction) - { + /** + * @throws OutOfRangeException + */ + public function setOnDeleteAction(string $onDeleteAction): static + { if (!$this->validateAction($onDeleteAction)) { throw new OutOfRangeException("Action '$onDeleteAction' is invalid."); } @@ -96,8 +87,8 @@ public function setOnDeleteAction($onDeleteAction) } - public function toSql(IDriver $driver) - { + public function toSql(IDriver $driver): string + { $output = 'CONSTRAINT ' . $driver->escapeIdentifier($this->name); $output .= ' FOREIGN KEY ('; $output .= implode(', ', array_map([$driver, 'escapeIdentifier'], $this->columns)); @@ -109,13 +100,8 @@ public function toSql(IDriver $driver) return $output; } - - /** - * @param string $action - * @return bool - */ - private function validateAction($action) - { + private function validateAction(string $action): bool + { return $action === self::ACTION_RESTRICT || $action === self::ACTION_NO_ACTION || $action === self::ACTION_CASCADE diff --git a/src/Statements/IndexColumnDefinition.php b/src/Statements/Defs/IndexColumnDefinition.php similarity index 62% rename from src/Statements/IndexColumnDefinition.php rename to src/Statements/Defs/IndexColumnDefinition.php index a8448e0..9ee93c5 100644 --- a/src/Statements/IndexColumnDefinition.php +++ b/src/Statements/Defs/IndexColumnDefinition.php @@ -14,48 +14,37 @@ class IndexColumnDefinition implements IStatement const ASC = 'ASC'; const DESC = 'DESC'; - /** @var string */ - private $name; + private string $name; - /** @var string */ - private $order; + private string $order; - /** @var int|NULL */ - private $length; + private ?int $length; - /** - * @param string $name - * @param string $order - * @param int|NULL $length - */ - public function __construct($name, $order = self::ASC, $length = NULL) + /** + * @throws OutOfRangeException + */ + public function __construct(string $name, string $order = self::ASC, ?int $length = NULL) { $this->name = $name; $this->setOrder($order); $this->length = $length; } - - /** - * @param string $order - * @return static - */ - private function setOrder($order) - { - $order = (string) $order; - + /** + * @throws OutOfRangeException + */ + private function setOrder(string $order): void + { if ($order !== self::ASC && $order !== self::DESC) { throw new OutOfRangeException("Order type '$order' not found."); } $this->order = $order; - return $this; - } - + } - public function toSql(IDriver $driver) - { + public function toSql(IDriver $driver): string + { $output = $driver->escapeIdentifier($this->name); if ($this->length !== NULL) { diff --git a/src/Statements/IndexDefinition.php b/src/Statements/Defs/IndexDefinition.php similarity index 68% rename from src/Statements/IndexDefinition.php rename to src/Statements/Defs/IndexDefinition.php index 989693c..662475a 100644 --- a/src/Statements/IndexDefinition.php +++ b/src/Statements/Defs/IndexDefinition.php @@ -16,47 +16,36 @@ class IndexDefinition implements IStatement const TYPE_UNIQUE = 'UNIQUE'; const TYPE_FULLTEXT = 'FULLTEXT'; - /** @var string|NULL */ - private $name; + private ?string $name; - /** @var string */ - private $type; + private string $type; /** @var IndexColumnDefinition[] */ - private $columns = []; + private array $columns = []; - - /** - * @param string|NULL $name - * @param string $type - */ - public function __construct($name, $type) + /** + * @throws OutOfRangeException + */ + public function __construct(?string $name, string $type) { $this->name = $name; $this->setType($type); } - - /** - * @param string $column - * @param string $order - * @param int|NULL $length - * @return static - */ - public function addColumn($column, $order = IndexColumnDefinition::ASC, $length = NULL) - { + /** + * @throws OutOfRangeException + */ + public function addColumn(string $column, string $order = IndexColumnDefinition::ASC, ?int $length = NULL): static + { $this->columns[] = new IndexColumnDefinition($column, $order, $length); return $this; } - - /** - * @param string $type - * @return void - */ - private function setType($type) - { - $type = (string) $type; + /** + * @throws OutOfRangeException + */ + private function setType(string $type): void + { $exists = $type === self::TYPE_INDEX || $type === self::TYPE_PRIMARY || $type === self::TYPE_UNIQUE @@ -69,9 +58,8 @@ private function setType($type) $this->type = $type; } - - public function toSql(IDriver $driver) - { + public function toSql(IDriver $driver): string + { $output = $this->type !== self::TYPE_INDEX ? ($this->type . ' ') : ''; $output .= 'KEY'; diff --git a/src/Statements/DropForeignKey.php b/src/Statements/DropForeignKey.php deleted file mode 100644 index f69040e..0000000 --- a/src/Statements/DropForeignKey.php +++ /dev/null @@ -1,30 +0,0 @@ -foreignKey = $foreignKey; - } - - - public function toSql(IDriver $driver) - { - return 'DROP FOREIGN KEY ' . $driver->escapeIdentifier($this->foreignKey); - } - } diff --git a/src/Statements/DropIndex.php b/src/Statements/DropIndex.php deleted file mode 100644 index fe5388e..0000000 --- a/src/Statements/DropIndex.php +++ /dev/null @@ -1,40 +0,0 @@ -index = $index; - } - - - public function toSql(IDriver $driver) - { - if ($this->index === NULL) { // PRIMARY KEY - if ($driver instanceof Drivers\MysqlDriver) { - return 'DROP PRIMARY KEY'; - - } else { - throw new \CzProject\SqlGenerator\NotImplementedException('Drop of primary key is not implemented for driver ' . get_class($driver) . '.'); - } - } - - return 'DROP INDEX ' . $driver->escapeIdentifier($this->index); - } - } diff --git a/src/Statements/DropTable.php b/src/Statements/DropTable.php index 13ba3a5..591197d 100644 --- a/src/Statements/DropTable.php +++ b/src/Statements/DropTable.php @@ -9,24 +9,17 @@ use CzProject\SqlGenerator\IStatement; use CzProject\SqlGenerator\TableName; - class DropTable implements IStatement { - /** @var string|TableName */ - private $tableName; - + private TableName|string $tableName; - /** - * @param string|TableName $tableName - */ - public function __construct($tableName) + public function __construct(string|TableName $tableName) { $this->tableName = Helpers::createTableName($tableName); } - - public function toSql(IDriver $driver) - { + public function toSql(IDriver $driver): string + { return 'DROP TABLE ' . Helpers::escapeTableName($this->tableName, $driver) . ';'; } } diff --git a/src/Statements/Insert.php b/src/Statements/Insert.php index 8114e5f..ed60562 100644 --- a/src/Statements/Insert.php +++ b/src/Statements/Insert.php @@ -6,52 +6,39 @@ use CzProject\SqlGenerator\Helpers; use CzProject\SqlGenerator\IDriver; - use CzProject\SqlGenerator\IStatement; + use CzProject\SqlGenerator\InvalidArgumentException; + use CzProject\SqlGenerator\IStatement; use CzProject\SqlGenerator\TableName; - class Insert implements IStatement { - /** @var string|TableName */ - private $tableName; + private TableName|string $tableName; /** @var array */ - private $data; - + private array $data; /** - * @param string|TableName $tableName - * @param array $data + * @param array $data */ - public function __construct($tableName, array $data) + public function __construct(string|TableName $tableName, array $data) { $this->tableName = Helpers::createTableName($tableName); $this->data = $data; } - - public function toSql(IDriver $driver) - { - $output = 'INSERT INTO ' . Helpers::escapeTableName($this->tableName, $driver); - - // columns - $output .= ' ('; - $output .= implode(', ', array_map([$driver, 'escapeIdentifier'], array_keys($this->data))); - $output .= ")\nVALUES ("; - - // data - $fields = count($this->data); - - foreach ($this->data as $value) { - $output .= Helpers::formatValue($value, $driver); - $fields--; - - if ($fields > 0) { - $output .= ', '; - } - } - - $output .= ');'; - return $output; + /** + * @throws InvalidArgumentException + */ + public function toSql(IDriver $driver): string + { + try { + $tableName = Helpers::escapeTableName($this->tableName, $driver); + $fields = implode(',', array_map(static fn (string $field) => $driver->escapeIdentifier($field), array_keys($this->data))); + $values = implode(',', array_map(static fn (mixed $value) => Helpers::formatValue($value, $driver), array_values($this->data))); + + return "INSERT INTO $tableName($fields) VALUES ($values);"; + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException('Error creating UPDATE query.', previous: $e); + } } } diff --git a/src/Statements/ModifyColumn.php b/src/Statements/ModifyColumn.php deleted file mode 100644 index 2268f41..0000000 --- a/src/Statements/ModifyColumn.php +++ /dev/null @@ -1,124 +0,0 @@ - $parameters - * @param array $options [name => value] - */ - public function __construct($name, $type, ?array $parameters = NULL, array $options = []) - { - $this->definition = new ColumnDefinition($name, $type, $parameters, $options); - } - - - /** - * @return static - */ - public function moveToFirstPosition() - { - $this->position = self::POSITION_FIRST; - return $this; - } - - - /** - * @param string $column - * @return static - */ - public function moveAfterColumn($column) - { - $this->position = $column; - return $this; - } - - - /** - * @return static - */ - public function moveToLastPosition() - { - $this->position = self::POSITION_LAST; - return $this; - } - - - /** - * @param bool $nullable - * @return static - */ - public function setNullable($nullable = TRUE) - { - $this->definition->setNullable($nullable); - return $this; - } - - - /** - * @param mixed|NULL $defaultValue - * @return static - */ - public function setDefaultValue($defaultValue) - { - $this->definition->setDefaultValue($defaultValue); - return $this; - } - - - /** - * @param bool $autoIncrement - * @return static - */ - public function setAutoIncrement($autoIncrement = TRUE) - { - $this->definition->setAutoIncrement($autoIncrement); - return $this; - } - - - /** - * @param string|NULL $comment - * @return static - */ - public function setComment($comment) - { - $this->definition->setComment($comment); - return $this; - } - - - public function toSql(IDriver $driver) - { - $output = 'MODIFY COLUMN ' . $this->definition->toSql($driver); - - if ($this->position === self::POSITION_FIRST) { - $output .= ' FIRST'; - - } elseif ($this->position !== self::POSITION_LAST) { - $output .= ' AFTER ' . $driver->escapeIdentifier($this->position); - } - - return $output; - } - } diff --git a/src/Statements/RenameColumn.php b/src/Statements/RenameColumn.php new file mode 100644 index 0000000..40bc18b --- /dev/null +++ b/src/Statements/RenameColumn.php @@ -0,0 +1,40 @@ +oldColumn = $oldColumn; + $this->newColumn = $newColumn; + } + + /** + * @throws NotImplementedException|InvalidArgumentException + */ + public function toSql(IDriver $driver): string + { + if ($driver->renameColumn ?? true) { + $this->renameColumn($this->oldColumn, $this->newColumn); + + return parent::toSql($driver); + } + else { + throw new NotImplementedException('Column rename is not implemented for driver ' . get_class($driver) . '.'); + } + } + } diff --git a/src/Statements/RenameTable.php b/src/Statements/RenameTable.php index bbc5d7d..61f1390 100644 --- a/src/Statements/RenameTable.php +++ b/src/Statements/RenameTable.php @@ -4,44 +4,36 @@ namespace CzProject\SqlGenerator\Statements; - use CzProject\SqlGenerator\Drivers; use CzProject\SqlGenerator\IDriver; use CzProject\SqlGenerator\Helpers; use CzProject\SqlGenerator\NotImplementedException; - use CzProject\SqlGenerator\IStatement; + use CzProject\SqlGenerator\InvalidArgumentException; use CzProject\SqlGenerator\TableName; - - class RenameTable implements IStatement + class RenameTable extends AlterTable { - /** @var string|TableName */ - private $oldTable; - - /** @var string|TableName */ - private $newTable; - + private TableName|string $newTable; - /** - * @param string|TableName $oldTable - * @param string|TableName $newTable - */ - public function __construct($oldTable, $newTable) + public function __construct(TableName|string $oldTable, TableName|string $newTable) { - $this->oldTable = Helpers::createTableName($oldTable); + parent::__construct($oldTable); + $this->newTable = Helpers::createTableName($newTable); } - - public function toSql(IDriver $driver) - { - if ($driver instanceof Drivers\MysqlDriver) { - return 'RENAME TABLE ' . Helpers::escapeTableName($this->oldTable, $driver) - . ' TO ' - . Helpers::escapeTableName($this->newTable, $driver) - . ';'; - } - - // see http://stackoverflow.com/questions/886786/how-do-i-rename-the-table-name-using-sql-query - throw new NotImplementedException('Table rename is not implemented for driver ' . get_class($driver) . '.'); + /** + * @throws NotImplementedException|InvalidArgumentException + */ + public function toSql(IDriver $driver): string + { + if ($driver->renameTable ?? true) { + $this->rename($this->newTable); + + return parent::toSql($driver); + } + else { + // @see http://stackoverflow.com/questions/886786/how-do-i-rename-the-table-name-using-sql-query + throw new NotImplementedException('Table rename is not implemented for driver ' . get_class($driver) . '.'); + } } } diff --git a/src/Statements/SqlCommand.php b/src/Statements/SqlCommand.php index 87ca0c2..2fb8a1a 100644 --- a/src/Statements/SqlCommand.php +++ b/src/Statements/SqlCommand.php @@ -7,24 +7,17 @@ use CzProject\SqlGenerator\IDriver; use CzProject\SqlGenerator\IStatement; - class SqlCommand implements IStatement { - /** @var string */ - private $command; - + private string $command; - /** - * @param string $command - */ - public function __construct($command) + public function __construct(string $command) { $this->command = $command; } - - public function toSql(IDriver $driver) - { + public function toSql(IDriver $driver): string + { return rtrim($this->command, ';') . ';'; } } diff --git a/src/Statements/Transaction.php b/src/Statements/Transaction.php new file mode 100644 index 0000000..a42b949 --- /dev/null +++ b/src/Statements/Transaction.php @@ -0,0 +1,29 @@ +action = $action; + } + + public function toSql(IDriver $driver): string + { + return $driver->transaction($this->action); + } + } \ No newline at end of file diff --git a/src/Statements/Update.php b/src/Statements/Update.php new file mode 100644 index 0000000..555825c --- /dev/null +++ b/src/Statements/Update.php @@ -0,0 +1,67 @@ + */ + private array $data; + + /** @var array */ + private array $where; + + /** + * @param array $data + * @param array $where + */ + public function __construct(string|TableName $tableName, array $data, array $where) + { + $this->tableName = Helpers::createTableName($tableName); + $this->data = $data; + $this->where = $where; + } + + /** + * @throws InvalidArgumentException + */ + public function toSql(IDriver $driver): string + { + try { + $tableName = Helpers::escapeTableName($this->tableName, $driver); + + $set = implode(',', array_map( + static fn (string $field, mixed $value): string => sprintf( + '%s=%s', + $driver->escapeIdentifier($field), + Helpers::formatValue($value, $driver), + ), + array_keys($this->data), + array_values($this->data), + )); + + $where = implode(' AND ', array_map( + static fn (string $field, string $value): string => sprintf( + '%s=%s', + $driver->escapeIdentifier($field), + Helpers::formatValue($value, $driver), + ), + array_keys($this->where), + array_values($this->where), + )); + + return "UPDATE $tableName SET $set WHERE $where;"; + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException('Error creating UPDATE query.', previous: $e); + } + } + } diff --git a/src/TableName.php b/src/TableName.php index 4559c7d..b02b1b3 100644 --- a/src/TableName.php +++ b/src/TableName.php @@ -4,12 +4,10 @@ namespace CzProject\SqlGenerator; - class TableName { /** @var string[] */ - private $parts; - + private array $parts; /** * @param string ...$parts @@ -19,12 +17,8 @@ public function __construct(...$parts) $this->parts = $parts; } - - /** - * @return string - */ - public function toString(IDriver $driver) - { + public function toString(IDriver $driver): string + { $res = []; foreach ($this->parts as $part) { @@ -34,13 +28,8 @@ public function toString(IDriver $driver) return implode('.', $res); } - - /** - * @param string $name - * @return self - */ - public static function create($name) - { + public static function create(string $name): self + { $parts = explode('.', $name); return new self(...$parts); } diff --git a/src/Value.php b/src/Value.php index 5fc523b..c012fc5 100644 --- a/src/Value.php +++ b/src/Value.php @@ -4,37 +4,38 @@ namespace CzProject\SqlGenerator; + use DateTimeInterface; + use Stringable; class Value { - /** @var scalar|\Stringable|\DateTimeInterface */ - private $value; + /** @var scalar|Stringable|DateTimeInterface */ + private int|float|bool|string|Stringable|DateTimeInterface $value; /** - * @param scalar|\Stringable|\DateTimeInterface $value + * @param scalar|Stringable|DateTimeInterface $value */ - public function __construct($value) + public function __construct(int|float|bool|string|Stringable|DateTimeInterface $value) { $this->value = $value; } - /** - * @return string - */ - public function toString(IDriver $driver) - { + /** + * @throws InvalidArgumentException + */ + public function toString(IDriver $driver): string + { return Helpers::formatValue($this->value, $driver); } /** - * @param scalar|\Stringable|\DateTimeInterface $value - * @return self + * @param scalar|Stringable|DateTimeInterface $value */ - public static function create($value) - { + public static function create(int|float|bool|string|Stringable|DateTimeInterface $value): self + { return new self($value); } } diff --git a/tests/libs/DummyDriver.php b/tests/libs/DummyDriver.php index 636a6a9..a9d9d14 100644 --- a/tests/libs/DummyDriver.php +++ b/tests/libs/DummyDriver.php @@ -4,20 +4,40 @@ namespace Tests; + use CzProject\SqlGenerator\Drivers\DateParserTrait; use CzProject\SqlGenerator\IDriver; + use CzProject\SqlGenerator\NotImplementedException; + use DateTimeInterface; - - class DummyDriver implements IDriver + class DummyDriver implements IDriver { - public function escapeIdentifier($value) + use DateParserTrait; + + public bool $renameTable = false; + + public bool $renameColumn = false; + + public bool $modifyColumn = false; + + /** + * @var string[] + */ + private const TRANSACTION = [ + 'start' => 'BEGIN;', + 'commit' => 'COMMIT;', + 'rollback' => 'ROLLBACK', + ]; + + private const LAST_ID = 'LAST_INSERT_ID()'; + + public function escapeIdentifier(string $value): string { // @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html // @see http://api.dibiphp.com/2.3.2/source-drivers.DibiMySqlDriver.php.html#307 return '`' . str_replace('`', '``', $value) . '`'; } - - public function escapeText($value) + public function escapeText(string $value): string { // https://dev.mysql.com/doc/refman/5.5/en/string-literals.html // http://us3.php.net/manual/en/function.mysql-real-escape-string.php#101248 @@ -28,27 +48,27 @@ public function escapeText($value) ) . '\''; } - - public function escapeBool($value) + public function escapeBool(bool $value): string { return $value ? 'TRUE' : 'FALSE'; } - - public function escapeDate($value) + public function escapeDate(DateTimeInterface|string $value): string { - if (!($value instanceof \DateTime) && !($value instanceof \DateTimeInterface)) { - $value = new \DateTime($value); - } - return $value->format("'Y-m-d'"); + return $this->dateFormat($value, "'Y-m-d'"); } - - public function escapeDateTime($value) + public function escapeDateTime(DateTimeInterface|string $value): string { - if (!($value instanceof \DateTime) && !($value instanceof \DateTimeInterface)) { - $value = new \DateTime($value); - } - return $value->format("'Y-m-d H:i:s'"); + return $this->dateFormat($value, "'Y-m-d H:i:s'"); } + + public function transaction(string $action): string + { + return self::TRANSACTION[$action]; + } + + public function lastId(): string { + return self::LAST_ID; + } }