diff --git a/.gitignore b/.gitignore index 3509d48..4ff1035 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: +.idea .idea/workspace.xml .idea/tasks.xml .idea/dictionaries diff --git a/.travis.yml b/.travis.yml index 5734df6..7f83832 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,12 @@ language: php php: + - "7.1" - "7.0" - "5.6" - - "5.5" - - "5.4" install: - composer install script: - - phpunit + - phpunit tests/SqliteCommandTest.php diff --git a/README.md b/README.md index a65ae15..746f1ca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Database Migrations [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/byjg/migration/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/byjg/migration/?branch=master) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/571cb412-7018-4938-a4e5-0f9ce44956d7/mini.png)](https://insight.sensiolabs.com/projects/571cb412-7018-4938-a4e5-0f9ce44956d7) +[![Build Status](https://travis-ci.org/byjg/migration.svg?branch=master)](https://travis-ci.org/byjg/migration) A micro framework in PHP for managing a set of database migrations using pure Sql. @@ -18,8 +19,8 @@ The basic usage is See an example: ```php -$connection = new ConnectionManagement('mysql://migrateuser:migratepwd@localhost/migratedatabase'); -$migration = new Migration($connection, '.'); +$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase'); +$migration = new Migration($connectionUri, '.'); // Restore the database using the "base.sql" script and run ALL existing scripts for up the database version // and run the up() method to maintain the database updated; @@ -90,9 +91,29 @@ Example: migrate down --up-to=3 --path=/somepath mysql://root:password@server/database ``` +## Suportted databases: + +* Sqlite +* Mysql / MariaDB +* Postgres +* SqlServer + ## Installing Globally ```bash -composer global require 'byjg/migration=1.0.*' +composer global require 'byjg/migration=1.1.*' sudo ln -s $HOME/.composer/vendor/bin/migrate /usr/local/bin ``` + +## Unit Tests + +This library has integrated tests and need to be setup for each database you want to test. + +Basiclly you have the follow tests: + +``` +phpunit tests/SqliteCommandTest.php +phpunit tests/MysqlCommandTest.php +phpunit tests/PostgresCommandTest.php +phpunit tests/SqlServerCommandTest.php +``` \ No newline at end of file diff --git a/composer.json b/composer.json index 67a7710..ae07d2f 100644 --- a/composer.json +++ b/composer.json @@ -7,8 +7,11 @@ "email": "joao@byjg.com.br" } ], + "minimum-stability": "dev", + "prefer-stable": true, "require": { - "byjg/anydataset": "2.1.*", + "byjg/anydataset": "3.0.*", + "byjg/uri": "1.0.*", "symfony/console": "3.1.*" }, "autoload": { diff --git a/example/mysql/test_mysql.php b/example/mysql/test_mysql.php index d5b065a..1c2bcc5 100644 --- a/example/mysql/test_mysql.php +++ b/example/mysql/test_mysql.php @@ -1,6 +1,6 @@ prepareEnvironment(); diff --git a/example/postgres/base.sql b/example/postgres/base.sql new file mode 100644 index 0000000..08dbd88 --- /dev/null +++ b/example/postgres/base.sql @@ -0,0 +1,18 @@ + +-- -------------------------------------------------------- +-- THIS IS THE BASE FILE . The version '0' +-- -------------------------------------------------------- + +-- Create the demo table USERS and populate it + +create table users ( + + id SERIAL NOT NULL PRIMARY KEY, + name varchar(50) NOT NULL, + createdate VARCHAR(8) + +); + +insert into users (name, createdate) values ('John Doe', '20160110'); +insert into users (name, createdate) values ('Jane Doe', '20151230'); + diff --git a/example/postgres/migrations/down/00000.sql b/example/postgres/migrations/down/00000.sql new file mode 100644 index 0000000..dacc43b --- /dev/null +++ b/example/postgres/migrations/down/00000.sql @@ -0,0 +1,20 @@ + +-- -------------------------------------------------------- +-- This is the script for migrate DOWN +-- from version '1' to version '0' +-- +-- This is the reverse operation of the script up/00001 +-- -------------------------------------------------------- + +ALTER TABLE users +ADD COLUMN createdate_old VARCHAR(8) NULL; + +update users + set createdate_old = TO_CHAR(createdate,'YYYYMMDD'); + +ALTER TABLE users + DROP COLUMN createdate; + +ALTER TABLE users + RENAME COLUMN createdate_old TO createdate; + diff --git a/example/postgres/migrations/down/00001.sql b/example/postgres/migrations/down/00001.sql new file mode 100644 index 0000000..2d5d1aa --- /dev/null +++ b/example/postgres/migrations/down/00001.sql @@ -0,0 +1,9 @@ + +-- -------------------------------------------------------- +-- This is the script for migrate DOWN +-- from version '2' to version '1' +-- +-- This is the reverse operation of the script up/00002 +-- -------------------------------------------------------- + +drop table roles; diff --git a/example/postgres/migrations/up/00001.sql b/example/postgres/migrations/up/00001.sql new file mode 100644 index 0000000..69b10c6 --- /dev/null +++ b/example/postgres/migrations/up/00001.sql @@ -0,0 +1,19 @@ + +-- -------------------------------------------------------- +-- This is the script for migrate up +-- from version '0' to version '1' +-- -------------------------------------------------------- + + +ALTER TABLE users +ADD COLUMN createdate_new DATE NULL; + +update users +set createdate_new = TO_DATE(createdate, 'YYYYMMDD'); + +ALTER TABLE users + DROP COLUMN createdate; + +ALTER TABLE users + RENAME COLUMN createdate_new TO createdate; + diff --git a/example/postgres/migrations/up/00002.sql b/example/postgres/migrations/up/00002.sql new file mode 100644 index 0000000..8235099 --- /dev/null +++ b/example/postgres/migrations/up/00002.sql @@ -0,0 +1,12 @@ + +-- -------------------------------------------------------- +-- This is the script for migrate up +-- from version '1' to version '2' +-- -------------------------------------------------------- + +create table roles +( + id SERIAL NOT NULL PRIMARY KEY, + rolename char(1) NOT NULL, + userid int not null +) diff --git a/example/postgres/test_postgres.php b/example/postgres/test_postgres.php new file mode 100644 index 0000000..81b0c62 --- /dev/null +++ b/example/postgres/test_postgres.php @@ -0,0 +1,18 @@ +prepareEnvironment(); + +$migration->reset(); + diff --git a/example/sql_server/base.sql b/example/sql_server/base.sql new file mode 100644 index 0000000..dab6783 --- /dev/null +++ b/example/sql_server/base.sql @@ -0,0 +1,18 @@ + +-- -------------------------------------------------------- +-- THIS IS THE BASE FILE . The version '0' +-- -------------------------------------------------------- + +-- Create the demo table USERS and populate it + +create table users ( + + ID int IDENTITY(1,1) PRIMARY KEY, + name varchar(50) NOT NULL, + createdate VARCHAR(8) + +); + +insert into users (name, createdate) values ('John Doe', '20160110'); +insert into users (name, createdate) values ('Jane Doe', '20151230'); + diff --git a/example/sql_server/migrations/down/00000.sql b/example/sql_server/migrations/down/00000.sql new file mode 100644 index 0000000..befe215 --- /dev/null +++ b/example/sql_server/migrations/down/00000.sql @@ -0,0 +1,22 @@ + +-- -------------------------------------------------------- +-- This is the script for migrate DOWN +-- from version '1' to version '0' +-- +-- This is the reverse operation of the script up/00001 +-- -------------------------------------------------------- + +ALTER TABLE users +ADD createdate_old VARCHAR(8) NULL ; + +update users +set createdate_old = concat(DATEPART(yyyy,createdate), + RIGHT('00' + CONVERT(NVARCHAR(2), DATEPART(MONTH, createdate)), 2), + RIGHT('00' + CONVERT(NVARCHAR(2), DATEPART(DAY, createdate)), 2) +); + +ALTER TABLE users + DROP COLUMN createdate; + +EXEC sp_RENAME 'users.createdate_old' , 'createdate', 'COLUMN' + diff --git a/example/sql_server/migrations/down/00001.sql b/example/sql_server/migrations/down/00001.sql new file mode 100644 index 0000000..2d5d1aa --- /dev/null +++ b/example/sql_server/migrations/down/00001.sql @@ -0,0 +1,9 @@ + +-- -------------------------------------------------------- +-- This is the script for migrate DOWN +-- from version '2' to version '1' +-- +-- This is the reverse operation of the script up/00002 +-- -------------------------------------------------------- + +drop table roles; diff --git a/example/sql_server/migrations/up/00001.sql b/example/sql_server/migrations/up/00001.sql new file mode 100644 index 0000000..87bb877 --- /dev/null +++ b/example/sql_server/migrations/up/00001.sql @@ -0,0 +1,22 @@ + +-- -------------------------------------------------------- +-- This is the script for migrate up +-- from version '0' to version '1' +-- -------------------------------------------------------- + + +ALTER TABLE users ADD createdate_new DATE NULL; + +update users set createdate_new = concat( + substring(createdate, 1, 4), + '-', + substring(createdate, 5, 2), + '-', + substring(createdate, 7, 2) +); + +ALTER TABLE users DROP COLUMN createdate; + +EXEC SP_RENAME 'users.createdate_new' , 'createdate', 'COLUMN'; + + diff --git a/example/sql_server/migrations/up/00002.sql b/example/sql_server/migrations/up/00002.sql new file mode 100644 index 0000000..05eefe5 --- /dev/null +++ b/example/sql_server/migrations/up/00002.sql @@ -0,0 +1,12 @@ + +-- -------------------------------------------------------- +-- This is the script for migrate up +-- from version '1' to version '2' +-- -------------------------------------------------------- + +create table roles +( + ID int IDENTITY(1,1) PRIMARY KEY, + rolename char(1) NOT NULL, + userid int not null +) diff --git a/example/sql_server/test_sqlserver.php b/example/sql_server/test_sqlserver.php new file mode 100644 index 0000000..ad14684 --- /dev/null +++ b/example/sql_server/test_sqlserver.php @@ -0,0 +1,18 @@ +prepareEnvironment(); + +$migration->reset(); + diff --git a/example/sqlite/base.sql b/example/sqlite/base.sql index 9171566..564eb70 100644 --- a/example/sqlite/base.sql +++ b/example/sqlite/base.sql @@ -13,6 +13,6 @@ create table users ( ); -insert into users (name, createdate) values ('John Doe 2', '20160110'); -insert into users (name, createdate) values ('Jane Doe 2', '20151230'); +insert into users (name, createdate) values ('John Doe', '20160110'); +insert into users (name, createdate) values ('Jane Doe', '20151230'); diff --git a/example/sqlite/migrations/down/00000.sql b/example/sqlite/migrations/down/00000.sql index 368f679..cde4893 100644 --- a/example/sqlite/migrations/down/00000.sql +++ b/example/sqlite/migrations/down/00000.sql @@ -6,15 +6,17 @@ -- This is the reverse operation of the script up/00001 -- -------------------------------------------------------- -ALTER TABLE `users` -ADD COLUMN `createdate_old` VARCHAR(8) NULL AFTER `createdate`; +CREATE table users_backup ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , + name varchar(50) NOT NULL, + createdate varchar(8) not NULL +); -update users -set createdate_old = DATE_FORMAT(createdate,'%Y%m%d'); +INSERT INTO users_backup +SELECT id, name, strftime('%Y%m%d', createdate) +FROM users; -ALTER TABLE `users` - DROP COLUMN `createdate`; +DROP TABLE users; -ALTER TABLE `users` - CHANGE COLUMN `createdate_old` `createdate` VARCHAR(8) NOT NULL ; +ALTER TABLE users_backup RENAME TO users; diff --git a/example/sqlite/test_sqlite.php b/example/sqlite/test_sqlite.php index bb1e230..a8bdf9a 100644 --- a/example/sqlite/test_sqlite.php +++ b/example/sqlite/test_sqlite.php @@ -1,10 +1,10 @@ reset(); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4f08dc8..32c9fc6 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,7 +21,8 @@ and open the template in the editor. - ./tests/ + + ./tests/SqliteCommandTest.php diff --git a/scripts/migrate b/scripts/migrate index c12bdf7..5e5b12d 100755 --- a/scripts/migrate +++ b/scripts/migrate @@ -13,8 +13,9 @@ require_once($autoload); use Symfony\Component\Console\Application; -$application = new Application('Migrate Script by JG', '1.0.2'); +$application = new Application('Migrate Script by JG', '1.1.0'); $application->add(new \ByJG\DbMigration\Console\ResetCommand()); $application->add(new \ByJG\DbMigration\Console\UpCommand()); $application->add(new \ByJG\DbMigration\Console\DownCommand()); +$application->add(new \ByJG\DbMigration\Console\CreateCommand()); $application->run(); diff --git a/src/Commands/AbstractCommand.php b/src/Commands/AbstractCommand.php index 1d41139..1a02759 100644 --- a/src/Commands/AbstractCommand.php +++ b/src/Commands/AbstractCommand.php @@ -2,49 +2,53 @@ namespace ByJG\DbMigration\Commands; -use ByJG\AnyDataset\Repository\DBDataset; +use ByJG\AnyDataset\DbDriverInterface; abstract class AbstractCommand implements CommandInterface { /** - * @var DBDataset + * @var DbDriverInterface */ - private $_dbDataset; + private $dbDriver; /** * Command constructor. * - * @param DBDataset $_dbDataset + * @param DbDriverInterface $dbDriver */ - public function __construct(DBDataset $_dbDataset) + public function __construct(DbDriverInterface $dbDriver) { - $this->_dbDataset = $_dbDataset; + $this->dbDriver = $dbDriver; } /** - * @return DBDataset + * @return DbDriverInterface */ - public function getDbDataset() + public function getDbDriver() { - return $this->_dbDataset; + return $this->dbDriver; } public function getVersion() { - return $this->getDbDataset()->getScalar('SELECT version FROM migration_version'); + try { + return $this->getDbDriver()->getScalar('SELECT version FROM migration_version'); + } catch (\Exception $ex) { + throw new \Exception('This database does not have a migration version. Please use "migrate reset" to create one.'); + } } public function setVersion($version) { - $this->getDbDataset()->execSQL('UPDATE migration_version SET version = :version', ['version' => $version]); + $this->getDbDriver()->execute('UPDATE migration_version SET version = :version', ['version' => $version]); } protected function checkExistsVersion() { // Get the version to check if exists $version = $this->getVersion(); - if ($version === false) { - $this->getDbDataset()->execSQL('insert into migration_version values(0)'); + if (empty($version)) { + $this->getDbDriver()->execute('insert into migration_version values(0)'); } } } diff --git a/src/Commands/CommandInterface.php b/src/Commands/CommandInterface.php index 1876758..40c3771 100644 --- a/src/Commands/CommandInterface.php +++ b/src/Commands/CommandInterface.php @@ -2,12 +2,11 @@ namespace ByJG\DbMigration\Commands; - -use ByJG\AnyDataset\ConnectionManagement; +use ByJG\Util\Uri; interface CommandInterface { - public static function prepareEnvironment(ConnectionManagement $connection); + public static function prepareEnvironment(Uri $dbDriver); public function createDatabase(); diff --git a/src/Commands/DblibCommand.php b/src/Commands/DblibCommand.php new file mode 100644 index 0000000..a4260f9 --- /dev/null +++ b/src/Commands/DblibCommand.php @@ -0,0 +1,74 @@ +getPath()); + + $customUri = new Uri($uri->__toString()); + + $dbDriver = Factory::getDbRelationalInstance($customUri->withPath('/')->__toString()); + $dbDriver->execute("IF NOT EXISTS(select * from sys.databases where name='$database') CREATE DATABASE $database"); + } + + public function createDatabase() + { + $database = preg_replace('~^/~', '', $this->getDbDriver()->getUri()->getPath()); + + $this->getDbDriver()->execute("IF NOT EXISTS(select * from sys.databases where name='$database') CREATE DATABASE $database"); + $this->getDbDriver()->execute("USE $database"); + } + + public function dropDatabase() + { + $database = preg_replace('~^/~', '', $this->getDbDriver()->getUri()->getPath()); + + $this->getDbDriver()->execute("use master"); + $this->getDbDriver()->execute("drop database $database"); + } + + protected function createTableIfNotExists($database, $table, $createTable) + { + $this->getDbDriver()->execute("use $database"); + + $sql = "IF (NOT EXISTS (SELECT * + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'dbo' + AND TABLE_NAME = '$table')) + BEGIN + $createTable + END"; + + $this->getDbDriver()->execute($sql); + } + + public function createVersion() + { + $database = preg_replace('~^/~', '', $this->getDbDriver()->getUri()->getPath()); + $table = 'migration_version'; + $createTable = 'CREATE TABLE migration_version (version int)'; + $this->createTableIfNotExists($database, $table, $createTable); + $this->checkExistsVersion(); + } + + public function executeSql($sql) + { + $statements = explode(";", $sql); + + foreach ($statements as $sql) { + $this->executeSqlInternal($sql); + } + } + + protected function executeSqlInternal($sql) + { + $this->getDbDriver()->execute($sql); + } +} diff --git a/src/Commands/MysqlCommand.php b/src/Commands/MysqlCommand.php index 8898f2e..5ed9e27 100644 --- a/src/Commands/MysqlCommand.php +++ b/src/Commands/MysqlCommand.php @@ -2,44 +2,45 @@ namespace ByJG\DbMigration\Commands; -use ByJG\AnyDataset\ConnectionManagement; -use ByJG\AnyDataset\Repository\DBDataset; +use ByJG\AnyDataset\Factory; +use ByJG\Util\Uri; class MySqlCommand extends AbstractCommand { - public static function prepareEnvironment(ConnectionManagement $connection) + public static function prepareEnvironment(Uri $uri) { - $database = $connection->getDatabase(); + $database = preg_replace('~^/~', '', $uri->getPath()); - $newConnection = new ConnectionManagement(str_replace("/$database", "/", $connection->getDbConnectionString())); - $dbDataset = new DBDataset($newConnection->getDbConnectionString()); - $dbDataset->execSQL("CREATE SCHEMA IF NOT EXISTS `$database` DEFAULT CHARACTER SET utf8 ;"); + $customUri = new Uri($uri->__toString()); + + $dbDriver = Factory::getDbRelationalInstance($customUri->withPath('/')->__toString()); + $dbDriver->execute("CREATE SCHEMA IF NOT EXISTS `$database` DEFAULT CHARACTER SET utf8 ;"); } public function createDatabase() { - $database = $this->getDbDataset()->getConnectionManagement()->getDatabase(); + $database = preg_replace('~^/~', '', $this->getDbDriver()->getUri()->getPath()); - $this->getDbDataset()->execSQL("CREATE SCHEMA IF NOT EXISTS `$database` DEFAULT CHARACTER SET utf8 ;"); - $this->getDbDataset()->execSQL("USE `$database`"); + $this->getDbDriver()->execute("CREATE SCHEMA IF NOT EXISTS `$database` DEFAULT CHARACTER SET utf8 ;"); + $this->getDbDriver()->execute("USE `$database`"); } public function dropDatabase() { - $database = $this->getDbDataset()->getConnectionManagement()->getDatabase(); + $database = preg_replace('~^/~', '', $this->getDbDriver()->getUri()->getPath()); - $this->getDbDataset()->execSQL("drop database `$database`"); + $this->getDbDriver()->execute("drop database `$database`"); } public function createVersion() { - $this->getDbDataset()->execSQL('CREATE TABLE IF NOT EXISTS migration_version (version int)'); + $this->getDbDriver()->execute('CREATE TABLE IF NOT EXISTS migration_version (version int)'); $this->checkExistsVersion(); } public function executeSql($sql) { - $this->getDbDataset()->execSQL($sql); + $this->getDbDriver()->execute($sql); } } diff --git a/src/Commands/PgsqlCommand.php b/src/Commands/PgsqlCommand.php new file mode 100644 index 0000000..460f76c --- /dev/null +++ b/src/Commands/PgsqlCommand.php @@ -0,0 +1,76 @@ +getPath()); + $dbDriver = self::getDbDriverWithoutDatabase($uri); + self::createDatabaseIfNotExists($dbDriver, $database); + } + + protected static function getDbDriverWithoutDatabase(Uri $uri) + { + $customUri = new Uri($uri->__toString()); + return Factory::getDbRelationalInstance($customUri->withPath('/')->__toString()); + } + + protected static function createDatabaseIfNotExists($dbDriver, $database) + { + $currentDbName = $dbDriver->getScalar( + "SELECT datname FROM pg_catalog.pg_database WHERE lower(datname) = lower(:dbname)", + ['dbname' => $database] + ); + + if (empty($currentDbName)) { + $dbDriver->execute("CREATE DATABASE $database WITH encoding=UTF8;"); + } + } + + public function createDatabase() + { + $database = preg_replace('~^/~', '', $this->getDbDriver()->getUri()->getPath()); + self::createDatabaseIfNotExists($this->getDbDriver(), $database); + } + + public function dropDatabase() + { + // $database = preg_replace('~^/~', '', $this->getDbDriver()->getUri()->getPath()); + + $iterator = $this->getDbDriver()->getIterator( + "select 'drop table if exists \"' || tablename || '\" cascade;' command from pg_tables where schemaname = 'public';" + ); + foreach ($iterator as $singleRow) { + $this->getDbDriver()->execute($singleRow->get('command')); + } + } + + public function createVersion() + { + $this->getDbDriver()->execute('CREATE TABLE IF NOT EXISTS migration_version (version int)'); + $this->checkExistsVersion(); + } + + public function executeSql($sql) + { + $statements = explode(";", $sql); + + foreach ($statements as $sql) { + $this->executeSqlInternal($sql); + } + } + + protected function executeSqlInternal($sql) + { + if (empty(trim($sql))) { + return; + } + $this->getDbDriver()->execute($sql); + } +} diff --git a/src/Commands/SqliteCommand.php b/src/Commands/SqliteCommand.php index 4275e06..f05ac7b 100644 --- a/src/Commands/SqliteCommand.php +++ b/src/Commands/SqliteCommand.php @@ -2,12 +2,12 @@ namespace ByJG\DbMigration\Commands; -use ByJG\AnyDataset\ConnectionManagement; +use ByJG\Util\Uri; class SqliteCommand extends AbstractCommand { - public static function prepareEnvironment(ConnectionManagement $connection) + public static function prepareEnvironment(Uri $uri) { } @@ -17,7 +17,7 @@ public function createDatabase() public function dropDatabase() { - $iterator = $this->getDbDataset()->getIterator(" + $iterator = $this->getDbDriver()->getIterator(" select 'drop table ' || name || ';' as command from sqlite_master @@ -26,13 +26,13 @@ public function dropDatabase() "); foreach ($iterator as $row) { - $this->getDbDataset()->execSQL($row->getField('command')); + $this->getDbDriver()->execute($row->get('command')); } } public function createVersion() { - $this->getDbDataset()->execSQL('CREATE TABLE IF NOT EXISTS migration_version (version int)'); + $this->getDbDriver()->execute('CREATE TABLE IF NOT EXISTS migration_version (version int)'); $this->checkExistsVersion(); } @@ -47,6 +47,6 @@ public function executeSql($sql) protected function executeSqlInternal($sql) { - $this->getDbDataset()->execSQL($sql); + $this->getDbDriver()->execute($sql); } } diff --git a/src/Console/ConsoleCommand.php b/src/Console/ConsoleCommand.php index 95df4f6..1b88295 100644 --- a/src/Console/ConsoleCommand.php +++ b/src/Console/ConsoleCommand.php @@ -4,6 +4,7 @@ use ByJG\AnyDataset\ConnectionManagement; use ByJG\DbMigration\Migration; +use ByJG\Util\Uri; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; @@ -70,8 +71,8 @@ protected function initialize(InputInterface $input, OutputInterface $output) $this->upTo = $input->getOption('up-to'); - $connectionObject = new ConnectionManagement($this->connection); - $this->migration = new Migration($connectionObject, $this->path); + $uri = new Uri($this->connection); + $this->migration = new Migration($uri, $this->path); } protected function execute(InputInterface $input, OutputInterface $output) diff --git a/src/Console/CreateCommand.php b/src/Console/CreateCommand.php new file mode 100644 index 0000000..bea94d0 --- /dev/null +++ b/src/Console/CreateCommand.php @@ -0,0 +1,83 @@ +setName('create') + ->setDescription('Create the directory structure FROM a pre-existing database') + ->addArgument( + 'path', + InputArgument::REQUIRED, + 'Define the path where the base.sql resides. If not set assumes the current folder' + ) + ->addOption( + 'migration', + 'm', + InputOption::VALUE_NONE, + 'Create the migration script (Up and Down)' + ) + ->addUsage('') + ->addUsage('Example: ') + ->addUsage(' migrate create --path /path/to/strcuture') + ->addUsage(' migrate create --path /path/to/strcuture --migration ') + ; + } + + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + protected function createMigrationSql($path, $startVersion) + { + $files = glob("$path/*.sql"); + $lastVersion = $startVersion; + foreach ($files as $file) { + $version = intval(basename($file)); + if ($version > $lastVersion) { + $lastVersion = $version; + } + } + + $lastVersion = $lastVersion + 1; + + file_put_contents( + "$path/" . str_pad($lastVersion, 5, '0', STR_PAD_LEFT) . ".sql", + "-- Migrate to Version $lastVersion \n\n" + ); + + return $lastVersion; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $path = $input->getArgument('path'); + if (!file_exists($path)) { + mkdir($path, 0777, true); + } + + if (!file_exists("$path/base.sql")) { + file_put_contents("$path/base.sql", "-- Put here your base SQL"); + } + + if (!file_exists("$path/migrations")) { + mkdir("$path/migrations", 0777, true); + mkdir("$path/migrations/up", 0777, true); + mkdir("$path/migrations/down", 0777, true); + } + + if ($input->hasOption('migration')) { + $output->writeln('Created UP version: ' . $this->createMigrationSql("$path/migrations/up", 0)); + $output->writeln('Created DOWN version: ' . $this->createMigrationSql("$path/migrations/down", -1)); + } + } +} diff --git a/src/Migration.php b/src/Migration.php index 6777489..81193c5 100644 --- a/src/Migration.php +++ b/src/Migration.php @@ -2,16 +2,17 @@ namespace ByJG\DbMigration; -use ByJG\AnyDataset\ConnectionManagement; -use ByJG\AnyDataset\Repository\DBDataset; +use ByJG\AnyDataset\DbDriverInterface; +use ByJG\AnyDataset\Factory; use ByJG\DbMigration\Commands\CommandInterface; +use ByJG\Util\Uri; class Migration { /** - * @var ConnectionManagement + * @var Uri */ - protected $_connection; + protected $uri; /** * @var string @@ -19,9 +20,9 @@ class Migration protected $_folder; /** - * @var DBDataset + * @var DbDriverInterface */ - protected $_dbDataset; + protected $dbDriver; /** * @var CommandInterface @@ -36,12 +37,12 @@ class Migration /** * Migration constructor. * - * @param ConnectionManagement $_connection + * @param Uri $uri * @param string $_folder */ - public function __construct(ConnectionManagement $_connection, $_folder) + public function __construct(Uri $uri, $_folder) { - $this->_connection = $_connection; + $this->uri = $uri; $this->_folder = $_folder; if (!file_exists($this->_folder . '/base.sql')) { @@ -50,14 +51,14 @@ public function __construct(ConnectionManagement $_connection, $_folder) } /** - * @return DBDataset + * @return DbDriverInterface */ - public function getDbDataset() + public function getDbDriver() { - if (is_null($this->_dbDataset)) { - $this->_dbDataset = new DBDataset($this->_connection->getDbConnectionString()); + if (is_null($this->dbDriver)) { + $this->dbDriver = Factory::getDbRelationalInstance($this->uri->__toString()); } - return $this->_dbDataset; + return $this->dbDriver; } /** @@ -67,14 +68,14 @@ public function getDbCommand() { if (is_null($this->_dbCommand)) { $class = $this->getCommandClassName(); - $this->_dbCommand = new $class($this->getDbDataset()); + $this->_dbCommand = new $class($this->getDbDriver()); } return $this->_dbCommand; } protected function getCommandClassName() { - return "\\ByJG\\DbMigration\\Commands\\" . ucfirst($this->_connection->getDriver()) . "Command"; + return "\\ByJG\\DbMigration\\Commands\\" . ucfirst($this->uri->getScheme()) . "Command"; } /** @@ -96,10 +97,18 @@ public function getBaseSql() */ public function getMigrationSql($version, $increment) { - return $this->_folder - . "/migrations" + $result = glob( + $this->_folder + . "/migrations" . "/" . ($increment < 0 ? "down" : "up") - . "/" . str_pad($version, 5, '0', STR_PAD_LEFT) . ".sql"; + . "/*$version.sql" + ); + + foreach ($result as $file) { + if (intval(basename($file)) == $version) { + return $file; + } + } } /** @@ -108,7 +117,7 @@ public function getMigrationSql($version, $increment) public function prepareEnvironment() { $class = $this->getCommandClassName(); - $class::prepareEnvironment($this->_connection); + $class::prepareEnvironment($this->uri); } /** diff --git a/tests/BaseCommand.php b/tests/BaseCommand.php new file mode 100644 index 0000000..434b96a --- /dev/null +++ b/tests/BaseCommand.php @@ -0,0 +1,168 @@ +migrate->prepareEnvironment(); + } + + public function tearDown() + { + $this->migrate->getDbCommand()->dropDatabase(); + } + + public function testVersion0() + { + $this->migrate->reset(0); + $this->assertVersion0(); + } + + public function testUpVersion1() + { + $this->migrate->reset(0); + $this->assertVersion0(); + $this->migrate->up(1); + $this->assertVersion1(); + } + + public function testUpVersion2() + { + $this->migrate->reset(0); + $this->assertVersion0(); + $this->migrate->up(2); + $this->assertVersion2(); + } + + public function testDownVersion1() + { + $this->migrate->reset(); + $this->assertVersion2(); + $this->migrate->down(1); + $this->assertVersion1(); + } + + public function testDownVersion0() + { + $this->migrate->reset(); + $this->assertVersion2(); + $this->migrate->down(0); + $this->assertVersion0(); + } + + protected function getExpectedUsersVersion0() + { + return [ + ["id" => 1, "name" => 'John Doe', 'createdate' => '20160110'], + ["id" => 2, "name" => 'Jane Doe', 'createdate' => '20151230'] + ]; + } + + protected function getExpectedUsersVersion1() + { + return [ + ["id" => 1, "name" => 'John Doe', 'createdate' => '2016-01-10'], + ["id" => 2, "name" => 'Jane Doe', 'createdate' => '2015-12-30'] + ]; + } + + protected function assertVersion0() + { + $version = $this->migrate->getDbDriver()->getScalar('select version from migration_version'); + $this->assertEquals(0, $version); + + $iterator = $this->migrate->getDbDriver()->getIterator('select * from users'); + + $this->assertTrue($iterator->hasNext()); + $row = $iterator->moveNext(); + $this->assertEquals( + $this->getExpectedUsersVersion0()[0], + $row->toArray() + ); + + $this->assertTrue($iterator->hasNext()); + $row = $iterator->moveNext(); + $this->assertEquals( + $this->getExpectedUsersVersion0()[1], + $row->toArray() + ); + + $this->assertFalse($iterator->hasNext()); + + try { + $this->migrate->getDbDriver()->getIterator('select * from roles'); + } catch (PDOException $ex) { + $this->assertTrue(true); + } + } + + protected function assertVersion1() + { + $version = $this->migrate->getDbDriver()->getScalar('select version from migration_version'); + $this->assertEquals(1, $version); + + $iterator = $this->migrate->getDbDriver()->getIterator('select * from users'); + + $this->assertTrue($iterator->hasNext()); + $row = $iterator->moveNext(); + $this->assertEquals( + $this->getExpectedUsersVersion1()[0], + $row->toArray() + ); + + $this->assertTrue($iterator->hasNext()); + $row = $iterator->moveNext(); + $this->assertEquals( + $this->getExpectedUsersVersion1()[1], + $row->toArray() + ); + + $this->assertFalse($iterator->hasNext()); + + try { + $this->migrate->getDbDriver()->getIterator('select * from roles'); + } catch (PDOException $ex) { + $this->assertTrue(true); + } + } + + protected function assertVersion2() + { + $version = $this->migrate->getDbDriver()->getScalar('select version from migration_version'); + $this->assertEquals(2, $version); + + $iterator = $this->migrate->getDbDriver()->getIterator('select * from users'); + + $this->assertTrue($iterator->hasNext()); + $row = $iterator->moveNext(); + $this->assertEquals( + $this->getExpectedUsersVersion1()[0], + $row->toArray() + ); + + $this->assertTrue($iterator->hasNext()); + $row = $iterator->moveNext(); + $this->assertEquals( + $this->getExpectedUsersVersion1()[1], + $row->toArray() + ); + + $this->assertFalse($iterator->hasNext()); + + $this->migrate->getDbDriver()->getIterator('select * from roles'); + } +} diff --git a/tests/MysqlCommandTest.php b/tests/MysqlCommandTest.php new file mode 100644 index 0000000..765d0aa --- /dev/null +++ b/tests/MysqlCommandTest.php @@ -0,0 +1,19 @@ +migrate = new \ByJG\DbMigration\Migration(new \ByJG\Util\Uri($this->uri), __DIR__ . '/../example/mysql'); + parent::setUp(); + } +} diff --git a/tests/PostgresCommandTest.php b/tests/PostgresCommandTest.php new file mode 100644 index 0000000..7be5cf8 --- /dev/null +++ b/tests/PostgresCommandTest.php @@ -0,0 +1,19 @@ +migrate = new \ByJG\DbMigration\Migration(new \ByJG\Util\Uri($this->uri), __DIR__ . '/../example/postgres'); + parent::setUp(); + } +} diff --git a/tests/SqlServerCommandTest.php b/tests/SqlServerCommandTest.php new file mode 100644 index 0000000..0c148b9 --- /dev/null +++ b/tests/SqlServerCommandTest.php @@ -0,0 +1,27 @@ + 1, "name" => 'John Doe', 'createdate' => 'Jan 10 2016 12:00:00:AM'], + ["id" => 2, "name" => 'Jane Doe', 'createdate' => 'Dec 30 2015 12:00:00:AM'] + ]; + } + + public function setUp() + { + $this->migrate = new \ByJG\DbMigration\Migration(new \ByJG\Util\Uri($this->uri), __DIR__ . '/../example/sql_server'); + parent::setUp(); + } +} diff --git a/tests/SqliteCommandTest.php b/tests/SqliteCommandTest.php new file mode 100644 index 0000000..33f5b95 --- /dev/null +++ b/tests/SqliteCommandTest.php @@ -0,0 +1,19 @@ +migrate = new \ByJG\DbMigration\Migration(new \ByJG\Util\Uri($this->uri), __DIR__ . '/../example/sqlite'); + parent::setUp(); + } +}