├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── migrator ├── phpunit.xml.dist ├── src ├── Command │ ├── BaseCommand.php │ ├── MigrateCommand.php │ └── StatusCommand.php ├── Console │ └── Application.php ├── Factory │ ├── Config │ │ ├── BaseFileConfigProvider.php │ │ ├── JSONConfigProvider.php │ │ ├── PHPConfigProvider.php │ │ ├── ProviderInterface.php │ │ └── YAMLConfigProvider.php │ ├── ConfigurableFactory.php │ └── FactoryInterface.php ├── MigrationReader │ └── SingleFolder.php ├── MigrationReaderInterface.php ├── Migrator.php ├── VersionLog │ ├── DatabaseLog.php │ └── DatabaseLogAdapter │ │ ├── AbstractAdapter.php │ │ ├── Factory.php │ │ ├── FactoryInterface.php │ │ ├── MySQL.php │ │ ├── PostgreSQL.php │ │ └── SQLite.php └── VersionLogInterface.php └── test ├── Factory └── Config │ ├── JSONConfigProviderTest.php │ ├── PHPConfigProviderTest.php │ └── YAMLConfigProviderTest.php ├── FunctionalTest.php ├── MigrationReader └── SingleFolderTest.php ├── MigratorTest.php ├── VersionLog └── DatabaseLogAdapter │ ├── AbstractAdapterTest.php │ ├── FactoryTest.php │ └── MySQLTest.php └── stubs ├── config ├── stub.json ├── stub.php └── yaml │ ├── invalid.yaml │ └── stub.yaml └── migrations ├── 1.down.sql ├── 1.up.sql ├── 2.dn.comment.sql ├── 2.up.comment.sql ├── 3.up.sql ├── 4.down.sql ├── 4.up.sql └── 5.invalid-direction.sql /.gitignore: -------------------------------------------------------------------------------- 1 | /phpunit.xml 2 | /composer.lock 3 | /vendor 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '5.5' 4 | - '5.6' 5 | - '7.0' 6 | - hhvm 7 | - nightly 8 | 9 | services: 10 | - mysql 11 | 12 | before_script: 13 | - composer install 14 | 15 | script: 16 | - vendor/bin/phpcs --standard=PSR2 src/ test/ 17 | - mkdir -p build/logs 18 | - mysql -e 'create database mysql_test;' 19 | - phpunit --coverage-clover build/logs/clover.xml 20 | 21 | after_script: 22 | - php vendor/bin/coveralls -v 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alexey Karapetov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Migrator is a simple database migration tool for PHP 2 | [![Total Downloads](https://img.shields.io/packagist/dt/lazypdo/migrator.svg)](https://packagist.org/packages/lazypdo/migrator) 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/lazypdo/migrator.svg)](https://packagist.org/packages/lazypdo/migrator) 4 | [![Travis Build](https://travis-ci.org/lazypdo/migrator.svg?branch=master)](https://travis-ci.org/lazypdo/migrator) 5 | [![SensioLabs Insight](https://img.shields.io/sensiolabs/i/c44976d6-9726-4aae-a423-865211cbc5b2.svg)](https://insight.sensiolabs.com/projects/c44976d6-9726-4aae-a423-865211cbc5b2) 6 | [![Coverage Status](https://coveralls.io/repos/github/lazypdo/migrator/badge.svg?branch=master)](https://coveralls.io/github/lazypdo/migrator?branch=master) 7 | 8 | The goal of this project is to create a simple yet modular database schema migration tool 9 | which would be easy to integrate with your project or to use standalone. 10 | 11 | ## Installation 12 | Use composer: `composer require lazypdo/migrator` 13 | 14 | ## Migrations 15 | Migrations is a set of SQL files residing in a dedicated directory. 16 | 17 | ### Naming 18 | Every migration is a SQL file. The naming convention is the following: 19 | `...sql` 20 | 21 | - _version_: a natural (positive integer) number 22 | - _direction_: either "up" or "down" 23 | - _memo_: optional text 24 | 25 | Examples: 26 | - `0004.up.create_foo_table.sql` 27 | - `042.down.sql` 28 | 29 | It is recommended to put a few leading zeros to make the migrations appear nicely sorted in file managers. 30 | 31 | ### Versions 32 | * File `.up.sql` defines the migration **from N-1 to N**. 33 | * File `.down.sql` defines the migration **from N back to N-1**. 34 | 35 | Versioning starts with 1. Every consequential upward migration must take the next natural number. 36 | When it is not possible to create a corresponding downward migration, the entire file must be omitted. 37 | E.g. if "42.up.sql" exists, but "42.down.sql" does not, Migrator will only be able to go from 41 to 42 but never back. 38 | 39 | ## Using Migrator standalone 40 | 41 | Configuration is pretty straightforward. 42 | Create a configuration file "migrator.php": 43 | 44 | ```php 45 | [ 48 | 'dsn' => 'pgsql:host=localhost;port=5432;dbname=testdb', 49 | 'user' => 'my-database_user', 50 | 'password' => 'my_database_password', 51 | 'options' => [ 52 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 53 | ], 54 | 'migrations' => '/path/to/migrations' 55 | ] 56 | ]; 57 | ``` 58 | 59 | Parameters _user_, _password_, and _options_ are optional. 60 | 61 | You can add as many databases as you want. The configuration file must 62 | either be in the current directory or specified using `--config` option: 63 | 64 | `./migrator --config=/path/to/migrator.php status` 65 | 66 | It is also possible to use json and yaml configs. 67 | 68 | Migrator has just two commands: _status_ and _migrate_. 69 | 70 | ### Status 71 | Shows the current status of the given database. The default database 72 | name is "default". It shows the current versions and possible migration 73 | range: the minimal and maximum version possible to migrate to. 74 | 75 | `./migrator my_database status` 76 | 77 | ### Migrate 78 | Migrates the database to the given target version. 79 | 80 | `./migrator my_database migrate [version]` 81 | 82 | * _version_: target version. If omitted, the highest possible version will be used. 83 | 84 | ## Using Migrator in your project 85 | Your project might already have its configuration infrastructure. 86 | You can tailor Migrator to your needs in just two steps. 87 | 88 | ### 1. Implement \Migrator\Factory\FactoryInterface 89 | This is what gives the console application an instance of Migrator for 90 | a given database name. 91 | 92 | ```php 93 | class MyMigratorFactory implements \Migrator\Factory\FactoryInterface 94 | { 95 | /** 96 | * Get an instance of Migrator for the given config 97 | * @param string $name 98 | * @return \Migrator\Migrator 99 | */ 100 | public function getMigrator($name) 101 | { 102 | // Here you will need 3 components 103 | 104 | /* @var PDO */ 105 | $pdo = ...;// Get it from your config according to the $name 106 | 107 | /* @var \Migrator\MigrationReaderInterface */ 108 | $migrationReader = ...; // Use \Migrator\MigrationReader\SingleFolderCallbackMigrationReader or create your own 109 | 110 | /* @var \Migrator\VersionLogInterface */ 111 | $log = ...; // Use \Migrator\VersionLog\DatabaseLog or create your own 112 | 113 | return new \Migrator\Migrator($pdo, $migrationReader, $log); 114 | } 115 | } 116 | ``` 117 | 118 | ### 2. Create your executable 119 | Create a file called `my_migrator` in your project's bin directory. 120 | 121 | ```php 122 | #!/usr/bin/env php 123 | run(); 129 | ``` 130 | 131 | Make it executable: `chmod +x my_migrator`. 132 | 133 | Done. 134 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazypdo/migrator", 3 | "type": "library", 4 | "description": "A Simple Database Schema Migration Tool For PHP", 5 | "keywords": [ 6 | "pdo", 7 | "database", 8 | "migration", 9 | "sql", 10 | "schema", 11 | "db" 12 | ], 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Alexey Karapetov", 17 | "email": "karapetov@gmail.com", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=5.5", 23 | "symfony/console": "^2.8 || ^3.0" 24 | }, 25 | "bin": [ 26 | "migrator" 27 | ], 28 | "require-dev": { 29 | "phpunit/phpunit": "^4.8 ||^5.0", 30 | "squizlabs/php_codesniffer": "^2.0", 31 | "satooshi/php-coveralls": "dev-master", 32 | "symfony/yaml": "^3.1" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Migrator\\": "src/" 37 | } 38 | }, 39 | "suggest": { 40 | "symfony/yaml": "Allows using YAML config files" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /migrator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 16 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | test/ 15 | 16 | 17 | 18 | 19 | ./src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Command/BaseCommand.php: -------------------------------------------------------------------------------- 1 | getApplication(); 21 | if ($app instanceof Application) { 22 | return $app->getFactory()->getMigrator($database); 23 | } 24 | throw new LogicException('Application is expected to be an instance of \\Migrator\\Console\\Application'); 25 | } 26 | 27 | /** 28 | * @param OutputInterface $output 29 | * @param string $name 30 | * @param int $lowest 31 | * @param int $current 32 | * @param int $highest 33 | */ 34 | protected function printStatus(OutputInterface $output, $name, $lowest, $current, $highest) 35 | { 36 | $output->writeln("Database: $name"); 37 | $output->writeln("At version: $current"); 38 | $output->writeln(""); 39 | if ($highest > $current) { 40 | $output->writeln("Upgradable to: $highest"); 41 | } 42 | if ($lowest < $current) { 43 | $output->writeln("Downgradable to: $lowest"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Command/MigrateCommand.php: -------------------------------------------------------------------------------- 1 | setName('migrate') 19 | ->setDescription('Migrates the database to the given version') 20 | ->addArgument( 21 | 'database', 22 | InputArgument::OPTIONAL, 23 | 'Database name', 24 | 'default' 25 | ) 26 | ->addArgument( 27 | 'target', 28 | InputArgument::OPTIONAL, 29 | 'Target version', 30 | self::HIGHEST 31 | ); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $database = $input->getArgument('database'); 37 | $migrator = $this->getMigrator($database); 38 | list($lowest, $current, $highest) = $migrator->getVersionRange(); 39 | $target = $input->getArgument('target'); 40 | if ($target == self::HIGHEST) { 41 | $target = $highest; 42 | } 43 | if ($target < $lowest || $target > $highest) { 44 | throw new OutOfBoundsException("Target range is $lowest:$highest"); 45 | } 46 | if ($target < $current) { 47 | $helper = $this->getHelper('question'); 48 | $question = new ConfirmationQuestion( 49 | 'Downgrade requested! Retype the target version to confirm: ', 50 | false, 51 | "/^$target$/" 52 | ); 53 | if (!$helper->ask($input, $output, $question)) { 54 | throw new RuntimeException('Incorrect answer provided. Aborting.'); 55 | } 56 | } 57 | $migrator->migrateTo($target); 58 | list($lowest, $current, $highest) = $migrator->getVersionRange(); 59 | $this->printStatus($output, $database, $lowest, $current, $highest); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Command/StatusCommand.php: -------------------------------------------------------------------------------- 1 | setName('status') 18 | ->setDescription('Database status') 19 | ->addArgument( 20 | 'database', 21 | InputArgument::OPTIONAL, 22 | 'Database name', 23 | 'default' 24 | ); 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output) 28 | { 29 | $database = $input->getArgument('database'); 30 | $migrator = $this->getMigrator($database); 31 | list($lowest, $current, $highest) = $migrator->getVersionRange(); 32 | $this->printStatus($output, $database, $lowest, $current, $highest); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Console/Application.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 35 | } 36 | parent::__construct('Migrator', self::VERSION); 37 | $this->addCommands([ 38 | new StatusCommand(), 39 | new MigrateCommand(), 40 | ]); 41 | } 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public function doRun(InputInterface $input, OutputInterface $output) 47 | { 48 | if (!$this->factory) { 49 | $config_file = $input->getParameterOption(['--config', '-c'], self::DEFAULT_CONFIG_FILE); 50 | switch (pathinfo($config_file, PATHINFO_EXTENSION)) { 51 | case 'php': 52 | $provider = new PHPConfigProvider($config_file); 53 | break; 54 | case 'yaml': 55 | case 'yml': 56 | $provider = new YAMLConfigProvider($config_file); 57 | break; 58 | default: 59 | $provider = new JSONConfigProvider($config_file); 60 | } 61 | $this->factory = new ConfigurableFactory($provider); 62 | } 63 | 64 | return parent::doRun($input, $output); 65 | } 66 | 67 | /** 68 | * @return FactoryInterface 69 | */ 70 | public function getFactory() 71 | { 72 | return $this->factory; 73 | } 74 | 75 | /** 76 | * @inheritdoc 77 | */ 78 | protected function getDefaultInputDefinition() 79 | { 80 | $def = parent::getDefaultInputDefinition(); 81 | 82 | if (!$this->factory) { 83 | $def->addOption( 84 | new InputOption( 85 | '--config', 86 | '-c', 87 | InputOption::VALUE_OPTIONAL, 88 | 'Configuration file', 89 | self::DEFAULT_CONFIG_FILE 90 | ) 91 | ); 92 | } 93 | return $def; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Factory/Config/BaseFileConfigProvider.php: -------------------------------------------------------------------------------- 1 | file = $file; 25 | } 26 | 27 | /** 28 | * @return bool 29 | */ 30 | protected function isValidFilePath() 31 | { 32 | return (is_file($this->file) && is_readable($this->file)); 33 | } 34 | 35 | /** 36 | * @throws RuntimeException 37 | */ 38 | protected function validateFilePath() 39 | { 40 | if (!$this->isValidFilePath()) { 41 | throw new RuntimeException("Can not find config file '{$this->file}'"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Factory/Config/JSONConfigProvider.php: -------------------------------------------------------------------------------- 1 | validateFilePath(); 16 | 17 | $config = json_decode(file_get_contents($this->file), true); 18 | if (isset($config[$db_name]) && is_array($config[$db_name])) { 19 | return $config[$db_name]; 20 | } 21 | 22 | throw new RuntimeException("No configuration found for database '{$db_name}'"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Factory/Config/PHPConfigProvider.php: -------------------------------------------------------------------------------- 1 | validateFilePath(); 16 | 17 | $config = $this->readConfig(); 18 | if (is_array($config) && isset($config[$db_name]) && is_array($config[$db_name])) { 19 | return $config[$db_name]; 20 | } 21 | 22 | throw new RuntimeException("No configuration found for database '{$db_name}'"); 23 | } 24 | 25 | /** 26 | * @return mixed 27 | */ 28 | private function readConfig() 29 | { 30 | return require $this->file; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Factory/Config/ProviderInterface.php: -------------------------------------------------------------------------------- 1 | (string) PDO DSN 11 | * 'user' => (string) database username 12 | * 'password' => (string) database password 13 | * 'options' => (array) PDO connection options 14 | * 'migrations' => (string) folder containing migrations 15 | * 'version_log_class' => (sting) class implementing \Migrator\VersionLogInterface 16 | * ] 17 | * @param string $db_name 18 | * @return array 19 | */ 20 | public function getConfig($db_name); 21 | } 22 | -------------------------------------------------------------------------------- /src/Factory/Config/YAMLConfigProvider.php: -------------------------------------------------------------------------------- 1 | validateFilePath(); 21 | 22 | $config = Yaml::parse(file_get_contents($this->file)); 23 | if (isset($config[$db_name]) && is_array($config[$db_name])) { 24 | return $config[$db_name]; 25 | } 26 | 27 | throw new RuntimeException("No configuration found for database '{$db_name}'"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Factory/ConfigurableFactory.php: -------------------------------------------------------------------------------- 1 | provider = $provider; 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function getMigrator($database) 31 | { 32 | $config = array_merge( 33 | [ 34 | 'options' => [], 35 | 'user' => null, 36 | 'password' => null, 37 | 'migrations' => 'migrations', 38 | ], 39 | $this->provider->getConfig($database) 40 | ); 41 | if (empty($config['dsn'])) { 42 | throw new RuntimeException("DSN in not configured for database $database"); 43 | } 44 | $pdo = new PDO( 45 | $config['dsn'], 46 | $config['user'], 47 | $config['password'], 48 | $config['options'] 49 | ); 50 | $reader = new SingleFolder($config['migrations']); 51 | $log = new DatabaseLog(); 52 | return new Migrator($pdo, $reader, $log); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Factory/FactoryInterface.php: -------------------------------------------------------------------------------- 1 | isDot()) { 28 | continue; 29 | } 30 | if ($this->match($file->getFilename(), $version, $is_upgrade)) { 31 | $pathname = $file->getPathname(); 32 | if ($is_upgrade) { 33 | $this->up[(int) $version] = $pathname; 34 | } else { 35 | $this->down[(int) $version] = $pathname; 36 | } 37 | } 38 | } 39 | } 40 | 41 | /** 42 | * Checks if the filename matches the naming convention. 43 | * If matches, sets $version and $direction variables 44 | * @param string $filename 45 | * @param int $version Matched version number 46 | * @param bool $is_upgrade True if upgrade, false if downgrade 47 | * @return bool 48 | */ 49 | public function match($filename, &$version, &$is_upgrade) 50 | { 51 | if (preg_match('/^(?\d+)\.(?up|dn|down)(\..+)?\.sql$/', $filename, $match)) { 52 | $version = (int) $match['version']; 53 | $is_upgrade = ($match['dir'] === 'up'); 54 | return true; 55 | } 56 | return false; 57 | } 58 | 59 | /** 60 | * Does upgrade to the version exist? 61 | * @param int $version 62 | * @return bool 63 | */ 64 | public function upgradeExistsTo($version) 65 | { 66 | return array_key_exists($version, $this->up); 67 | } 68 | 69 | /** 70 | * Does downgrade from the version exist? 71 | * @param int $version 72 | * @return bool 73 | */ 74 | public function downgradeExistsFrom($version) 75 | { 76 | return array_key_exists($version, $this->down); 77 | } 78 | 79 | /** 80 | * Get SQL upgrading from ($version - 1) to $version 81 | * @param int $version 82 | * @return string 83 | * @throws OutOfBoundsException if upgrade to version is not possible 84 | */ 85 | public function getUpgradeTo($version) 86 | { 87 | return $this->getContents($this->up, $version); 88 | } 89 | 90 | /** 91 | * Get SQL downgrading from ($version) to ($version - 1) 92 | * @param int $version 93 | * @return string 94 | * @throws OutOfBoundsException if downgrade from version is not possible 95 | */ 96 | public function getDowngradeFrom($version) 97 | { 98 | return $this->getContents($this->down, $version); 99 | } 100 | 101 | private function getContents(array $files, $key) 102 | { 103 | if (array_key_exists($key, $files)) { 104 | return file_get_contents($files[$key]); 105 | } 106 | throw new OutOfBoundsException('Version not found'); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/MigrationReaderInterface.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 35 | $this->version_log = $version_log; 36 | $this->migration_reader = $migration_reader; 37 | } 38 | 39 | /** 40 | * Get the possible version range. The lowest, current, and highest version 41 | * @return int[] [$lowest, $current, $highest] 42 | */ 43 | public function getVersionRange() 44 | { 45 | $current = $this->version_log->getCurrentVersion($this->pdo); 46 | $highest = $current; 47 | while ($this->migration_reader->upgradeExistsTo($highest + 1)) { 48 | $highest++; 49 | }; 50 | $lowest = $current + 1; 51 | while ($this->migration_reader->downgradeExistsFrom($lowest - 1)) { 52 | $lowest--; 53 | }; 54 | return [$lowest - 1, $current, $highest]; 55 | } 56 | 57 | /** 58 | * Migrate to version 59 | * @param int $to_version 60 | * @throws Exception 61 | */ 62 | public function migrateTo($to_version) 63 | { 64 | list($lowest, $current, $highest) = $this->getVersionRange(); 65 | if ($to_version < $lowest || $to_version > $highest) { 66 | throw new OutOfRangeException('Target version out of range'); 67 | } 68 | if ($to_version == $current) { 69 | return; 70 | } 71 | $this->pdo->beginTransaction(); 72 | 73 | try { 74 | if ($to_version > $current) { 75 | $this->applyUpgradesTo(range($current + 1, $to_version, 1)); 76 | } else { 77 | $this->applyDowngradesFrom(range($current, $to_version + 1, -1)); 78 | } 79 | $this->version_log->updateVersion($this->pdo, $to_version); 80 | $this->pdo->commit(); 81 | } catch (Exception $e) { 82 | $this->pdo->rollBack(); 83 | throw $e; 84 | } 85 | } 86 | 87 | /** 88 | * Apply upgrades to the given versions 89 | * @param int[] $versions 90 | */ 91 | protected function applyUpgradesTo(array $versions) 92 | { 93 | foreach ($versions as $version) { 94 | $migration = $this->migration_reader->getUpgradeTo($version); 95 | $this->exec($migration); 96 | } 97 | } 98 | 99 | /** 100 | * Apply downgrades from the given versions 101 | * @param int[] $versions 102 | */ 103 | protected function applyDowngradesFrom(array $versions) 104 | { 105 | foreach ($versions as $version) { 106 | $migration = $this->migration_reader->getDowngradeFrom($version); 107 | $this->exec($migration); 108 | } 109 | } 110 | 111 | /** 112 | * @param string $sql 113 | */ 114 | protected function exec($sql) 115 | { 116 | $result = $this->pdo->exec($sql); 117 | if ($result === false) { 118 | throw new RuntimeException("Query failed:\n{$sql}"); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/VersionLog/DatabaseLog.php: -------------------------------------------------------------------------------- 1 | factory = $factory ?: new Factory(); 23 | } 24 | 25 | /** 26 | * Get current version 27 | * @param PDO $pdo 28 | * @return int 29 | */ 30 | public function getCurrentVersion(PDO $pdo) 31 | { 32 | $adapter = $this->factory->getAdapter($pdo); 33 | $adapter->init($pdo); 34 | return $adapter->getCurrentVersion($pdo); 35 | } 36 | 37 | /** 38 | * Set version to the new value 39 | * @param PDO $pdo 40 | * @param int $new_version 41 | * @return void 42 | * @internal param int $version 43 | */ 44 | public function updateVersion(PDO $pdo, $new_version) 45 | { 46 | $adapter = $this->factory->getAdapter($pdo); 47 | $adapter->init($pdo); 48 | $adapter->updateVersion($pdo, $new_version); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/VersionLog/DatabaseLogAdapter/AbstractAdapter.php: -------------------------------------------------------------------------------- 1 | table = $table; 25 | } 26 | 27 | /** 28 | * Initialize table 29 | * @param PDO $pdo 30 | * @return void 31 | */ 32 | abstract public function init(PDO $pdo); 33 | 34 | /** 35 | * Get current version 36 | * @param PDO $pdo 37 | * @return int 38 | */ 39 | abstract public function getCurrentVersion(PDO $pdo); 40 | 41 | /** 42 | * Set version to the new value 43 | * @param PDO $pdo 44 | * @param int $new_version 45 | * @return void 46 | */ 47 | abstract public function updateVersion(PDO $pdo, $new_version); 48 | } 49 | -------------------------------------------------------------------------------- /src/VersionLog/DatabaseLogAdapter/Factory.php: -------------------------------------------------------------------------------- 1 | table_name = $table_name; 26 | } 27 | 28 | /** 29 | * @param PDO $pdo 30 | * @return AbstractAdapter 31 | */ 32 | public function getAdapter(PDO $pdo) 33 | { 34 | $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); 35 | if (isset($this->adapters[$driver])) { 36 | return $this->adapters[$driver]; 37 | } 38 | switch ($driver) { 39 | case 'sqlite': 40 | case 'sqlite2': 41 | $adapter = new SQLite($this->table_name); 42 | break; 43 | case 'pgsql': 44 | $adapter = new PostgreSQL($this->table_name); 45 | break; 46 | case 'mysql': 47 | $adapter = new MySQL($this->table_name); 48 | break; 49 | default: 50 | throw new RuntimeException("Adapter for $driver is not yet implemented. Mind opening a pull request?"); 51 | } 52 | return $this->adapters[$driver] = $adapter; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/VersionLog/DatabaseLogAdapter/FactoryInterface.php: -------------------------------------------------------------------------------- 1 | exec(" 16 | CREATE TABLE IF NOT EXISTS {$this->table} ( 17 | id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, 18 | version INT NOT NULL, 19 | ts TIMESTAMP NOT NULL DEFAULT now() 20 | ) 21 | "); 22 | } 23 | 24 | /** 25 | * Get current version 26 | * @param PDO $pdo 27 | * @return int 28 | */ 29 | public function getCurrentVersion(PDO $pdo) 30 | { 31 | $select = $pdo->prepare("SELECT version FROM {$this->table} ORDER BY id DESC LIMIT 1"); 32 | $select->execute(); 33 | return (int) $select->fetchColumn(); // returns 0 if the result set is empty 34 | } 35 | 36 | /** 37 | * Set version to the new value 38 | * @param PDO $pdo 39 | * @param int $new_version 40 | */ 41 | public function updateVersion(PDO $pdo, $new_version) 42 | { 43 | $insert = $pdo->prepare("INSERT INTO {$this->table} (version) VALUES (:ver)"); 44 | $insert->execute([ 45 | ':ver' => $new_version, 46 | ]); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/VersionLog/DatabaseLogAdapter/PostgreSQL.php: -------------------------------------------------------------------------------- 1 | exec(" 16 | CREATE TABLE IF NOT EXISTS {$this->table} ( 17 | id SERIAL NOT NULL PRIMARY KEY, 18 | version INTEGER NOT NULL, 19 | ts TIMESTAMP NOT NULL DEFAULT now() 20 | ) 21 | "); 22 | } 23 | 24 | /** 25 | * Get current version 26 | * @param PDO $pdo 27 | * @return int 28 | */ 29 | public function getCurrentVersion(PDO $pdo) 30 | { 31 | $select = $pdo->prepare("SELECT version FROM {$this->table} ORDER BY id DESC LIMIT 1"); 32 | $select->execute(); 33 | return (int) $select->fetchColumn(); // returns 0 if the result set is empty 34 | } 35 | 36 | /** 37 | * Set version to the new value 38 | * @param PDO $pdo 39 | * @param int $new_version 40 | */ 41 | public function updateVersion(PDO $pdo, $new_version) 42 | { 43 | $insert = $pdo->prepare("INSERT INTO {$this->table} (version) VALUES (:ver)"); 44 | $insert->execute([ 45 | ':ver' => $new_version, 46 | ]); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/VersionLog/DatabaseLogAdapter/SQLite.php: -------------------------------------------------------------------------------- 1 | exec(" 16 | CREATE TABLE IF NOT EXISTS {$this->table} 17 | (id INTEGER PRIMARY KEY, version INTEGER NOT NULL, ts TEXT NOT NULL) 18 | "); 19 | } 20 | 21 | /** 22 | * Get current version 23 | * @param PDO $pdo 24 | * @return int 25 | */ 26 | public function getCurrentVersion(PDO $pdo) 27 | { 28 | $select = $pdo->prepare("SELECT version FROM {$this->table} ORDER BY id DESC LIMIT 1"); 29 | $select->execute(); 30 | return (int) $select->fetchColumn(); 31 | } 32 | 33 | /** 34 | * Set version to the new value 35 | * @param PDO $pdo 36 | * @param int $new_version 37 | * @return void 38 | */ 39 | public function updateVersion(PDO $pdo, $new_version) 40 | { 41 | $insert = $pdo->prepare("INSERT INTO {$this->table} (version, ts) VALUES (:ver, datetime('now'))"); 42 | $insert->execute([ 43 | ':ver' => $new_version, 44 | ]); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/VersionLogInterface.php: -------------------------------------------------------------------------------- 1 | getConfig('none'); 22 | } 23 | 24 | /** 25 | * @expectedException RuntimeException 26 | * @expectedExceptionMessage No configuration found for database 'invalid database name' 27 | */ 28 | public function testNoConfigurationForDatabase() 29 | { 30 | $config_provider = new JSONConfigProvider($this->getStubPath()); 31 | $config_provider->getConfig("invalid database name"); 32 | } 33 | 34 | public function testReadConfig() 35 | { 36 | $config_stub_path = $this->getStubPath(); 37 | $config_stub = json_decode(file_get_contents($config_stub_path), true); 38 | 39 | foreach ($config_stub as $db_name => $stub) { 40 | $config_provider = new JSONConfigProvider($config_stub_path); 41 | $config = $config_provider->getConfig($db_name); 42 | 43 | $this->assertTrue(is_array($config)); 44 | foreach ($stub as $k => $v) { 45 | $this->assertEquals($v, $config[$k]); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | private function getStubPath() 54 | { 55 | return __DIR__ . '/../../stubs/config/stub.json'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/Factory/Config/PHPConfigProviderTest.php: -------------------------------------------------------------------------------- 1 | getConfig('none'); 22 | } 23 | 24 | /** 25 | * @expectedException RuntimeException 26 | * @expectedExceptionMessage No configuration found for database 'invalid database name' 27 | */ 28 | public function testNoConfigurationForDatabase() 29 | { 30 | $config_provider = new PHPConfigProvider($this->getStubPath()); 31 | $config_provider->getConfig("invalid database name"); 32 | } 33 | 34 | public function testReadConfig() 35 | { 36 | $config_stub_path = $this->getStubPath(); 37 | $config_stub = require $config_stub_path; 38 | 39 | foreach ($config_stub as $db_name => $stub) { 40 | $config_provider = new PHPConfigProvider($config_stub_path); 41 | $config = $config_provider->getConfig($db_name); 42 | 43 | $this->assertTrue(is_array($config)); 44 | foreach ($stub as $k => $v) { 45 | $this->assertEquals($v, $config[$k]); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | private function getStubPath() 54 | { 55 | return __DIR__ . '/../../stubs/config/stub.php'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/Factory/Config/YAMLConfigProviderTest.php: -------------------------------------------------------------------------------- 1 | getConfig('none'); 23 | } 24 | 25 | /** 26 | * @expectedException RuntimeException 27 | * @expectedExceptionMessage No configuration found for database 'invalid database name' 28 | */ 29 | public function testNoConfigurationForDatabase() 30 | { 31 | $config_provider = new YAMLConfigProvider($this->getStubPath()); 32 | $config_provider->getConfig("invalid database name"); 33 | } 34 | 35 | /** 36 | * @expectedException RuntimeException 37 | * @expectedExceptionMessage Unable to parse at line 1 (near "Hey"). 38 | */ 39 | public function testInvalidYAMLFile() 40 | { 41 | $config_provider = new YAMLConfigProvider($this->getInvalidStubPath()); 42 | $config_provider->getConfig("something"); 43 | } 44 | 45 | public function testReadConfig() 46 | { 47 | $config_stub_path = $this->getStubPath(); 48 | $config_stub = Yaml::parse(file_get_contents($config_stub_path)); 49 | 50 | foreach ($config_stub as $db_name => $stub) { 51 | $config_provider = new YAMLConfigProvider($config_stub_path); 52 | $config = $config_provider->getConfig($db_name); 53 | 54 | $this->assertTrue(is_array($config)); 55 | foreach ($stub as $k => $v) { 56 | $this->assertEquals($v, $config[$k]); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * @return string 63 | */ 64 | private function getStubPath() 65 | { 66 | return __DIR__ . '/../../stubs/config/yaml/stub.yaml'; 67 | } 68 | 69 | /** 70 | * @return string 71 | */ 72 | private function getInvalidStubPath() 73 | { 74 | return __DIR__ . '/../../stubs/config/yaml/invalid.yaml'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/FunctionalTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('pdo_sqlite extension is needed to run functional tests'); 27 | } 28 | $this->pdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); 29 | $this->migrator = new Migrator( 30 | $this->pdo, 31 | new SingleFolder(__DIR__ . '/stubs/migrations'), 32 | new DatabaseLog() 33 | ); 34 | } 35 | 36 | public function testInitialState() 37 | { 38 | $this->assertCount(0, $this->getTables()); 39 | $this->assertEquals([0, 0, 4], $this->migrator->getVersionRange()); 40 | $this->assertArrayHasKey('__version_log', $this->getTables()); 41 | } 42 | 43 | public function versionsAndRanges() 44 | { 45 | return [ 46 | [[ // gap in upgrades 47 | [1, [0, 1, 4], ['table_v1' => 'CREATE TABLE "table_v1"(id INT PRIMARY KEY)']], 48 | [2, [0, 2, 4], ['table_v2' => 'CREATE TABLE "table_v2"(id INT PRIMARY KEY)']], 49 | [3, [3, 3, 4], ['table_v2' => 'CREATE TABLE "table_v2"(id INT PRIMARY KEY, a TEXT)']], 50 | [4, [3, 4, 4], ['table_v4' => 'CREATE TABLE "table_v4"(id INT PRIMARY KEY, a TEXT)']], 51 | [3, [3, 3, 4], ['table_v2' => 'CREATE TABLE "table_v2"(id INT PRIMARY KEY, a TEXT)']], 52 | ]], 53 | [[ // there and back 54 | [1, [0, 1, 4], ['table_v1' => 'CREATE TABLE "table_v1"(id INT PRIMARY KEY)']], 55 | [2, [0, 2, 4], ['table_v2' => 'CREATE TABLE "table_v2"(id INT PRIMARY KEY)']], 56 | [1, [0, 1, 4], ['table_v1' => 'CREATE TABLE "table_v1"(id INT PRIMARY KEY)']], 57 | [0, [0, 0, 4], []], 58 | ]], 59 | [[ // same version 60 | [0, [0, 0, 4], []], 61 | [0, [0, 0, 4], []], 62 | ]], 63 | ]; 64 | } 65 | 66 | /** 67 | * @dataProvider versionsAndRanges 68 | * @param array $data_set 69 | * @throws Exception 70 | */ 71 | public function testMigrationAndRange(array $data_set) 72 | { 73 | foreach ($data_set as $parameters) { 74 | list($version, $range, $expectedTables) = $parameters; 75 | $this->migrator->migrateTo($version); 76 | $this->assertEquals($range, $this->migrator->getVersionRange()); 77 | $tables = $this->getTables(); 78 | unset($tables['__version_log']); 79 | $this->assertEquals($expectedTables, $tables); 80 | } 81 | } 82 | 83 | /** 84 | * @return array 85 | */ 86 | private function getTables() 87 | { 88 | $tables = []; 89 | $select = $this->pdo->prepare("SELECT * FROM sqlite_master WHERE type='table'"); 90 | $select->execute(); 91 | while ($row = $select->fetch(PDO::FETCH_ASSOC)) { 92 | $tables[$row['name']] = $row['sql']; 93 | } 94 | return $tables; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/MigrationReader/SingleFolderTest.php: -------------------------------------------------------------------------------- 1 | getUpgradeTo(42); 18 | } 19 | 20 | /** 21 | * @expectedException OutOfBoundsException 22 | * @expectedExceptionMessage Version not found 23 | */ 24 | public function testInvalidDowngradeVersion() 25 | { 26 | $reader = new SingleFolder(__DIR__ . '/../stubs/migrations'); 27 | $reader->getDowngradeFrom(42); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/MigratorTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('PDO mocking is not possible'); 42 | } 43 | $this->pdo = $this->getMockBuilder('PDO') 44 | ->disableOriginalConstructor() 45 | ->getMock(); 46 | 47 | $this->log = $this->getMockForAbstractClass('Migrator\\VersionLogInterface'); 48 | $this->reader = $this->getMockForAbstractClass('Migrator\\MigrationReaderInterface'); 49 | $this->migratorReal = new Migrator($this->pdo, $this->reader, $this->log); 50 | $this->migratorMock = $this->getMockBuilder('Migrator\\Migrator') 51 | ->setConstructorArgs([$this->pdo, $this->reader, $this->log]) 52 | ->setMethods(['getVersionRange']) 53 | ->getMock(); 54 | } 55 | 56 | public function versionRanges() 57 | { 58 | return [ 59 | [ 60 | [0, 0, 0], 0, [], [], 61 | [42, 42, 42], 42, [], [], 62 | [1, 2, 3], 2, [1, 2, 3], [3, 2], 63 | [42, 42, 42], 42, [1, 2, 3], [3, 2], 64 | ], 65 | ]; 66 | } 67 | 68 | /** 69 | * @param array $expected 70 | * @param int $version 71 | * @param array $upgrades 72 | * @param array $downgrades 73 | * @dataProvider versionRanges 74 | */ 75 | public function testGetVersionRange(array $expected, $version, array $upgrades, array $downgrades) 76 | { 77 | $this->log->method('getCurrentVersion') 78 | ->willReturn($version); 79 | 80 | $this->reader->method('upgradeExistsTo') 81 | ->willReturnCallback(function ($v) use ($upgrades) { 82 | return in_array($v, $upgrades); 83 | }); 84 | 85 | $this->reader->method('downgradeExistsFrom') 86 | ->willReturnCallback(function ($v) use ($downgrades) { 87 | return in_array($v, $downgrades); 88 | }); 89 | 90 | $this->assertEquals($expected, $this->migratorReal->getVersionRange()); 91 | } 92 | 93 | 94 | /** 95 | * @expectedException OutOfRangeException 96 | * @expectedExceptionMessage Target version out of range 97 | */ 98 | public function testMigrateThrowsException() 99 | { 100 | $this->migratorMock->method('getVersionRange') 101 | ->willReturn([1, 2, 3]); 102 | 103 | $this->migratorMock->migrateTo(5); 104 | } 105 | 106 | public function upDown() 107 | { 108 | return [ 109 | [true], 110 | [false], 111 | ]; 112 | } 113 | 114 | /** 115 | * @param $is_up 116 | * @expectedException Exception 117 | * @expectedExceptionMessage OMG 118 | * @dataProvider upDown 119 | */ 120 | public function testMigrateRollsBack($is_up) 121 | { 122 | $this->migratorMock->method('getVersionRange') 123 | ->willReturn([1, 2, 3]); 124 | 125 | $query = 'my test query'; 126 | if ($is_up) { 127 | $target = 3; 128 | $this->reader->method('getUpgradeTo') 129 | ->with(3) 130 | ->willReturn($query); 131 | } else { 132 | $target = 1; 133 | $this->reader->method('getDowngradeFrom') 134 | ->with(2) 135 | ->willReturn($query); 136 | } 137 | 138 | $this->pdo->expects($this->at(0)) 139 | ->method('beginTransaction'); 140 | $this->pdo->expects($this->at(1)) 141 | ->method('exec') 142 | ->with($query) 143 | ->willThrowException(new Exception('OMG')); 144 | 145 | $this->pdo->expects($this->at(2)) 146 | ->method('rollback'); 147 | 148 | $this->migratorMock->migrateTo($target); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /test/VersionLog/DatabaseLogAdapter/AbstractAdapterTest.php: -------------------------------------------------------------------------------- 1 | getMockForAbstractClass( 13 | 'Migrator\\VersionLog\\DatabaseLogAdapter\\AbstractAdapter', 14 | ['incorrect table name'] 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/VersionLog/DatabaseLogAdapter/FactoryTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('PDO mocking is not possible'); 15 | } 16 | $this->pdo = $this->getMockBuilder('PDO') 17 | ->disableOriginalConstructor() 18 | ->setMethods(['getAttribute']) 19 | ->getMock(); 20 | } 21 | 22 | public function driverDataProvider() 23 | { 24 | return [ 25 | ['sqlite', '\\Migrator\\VersionLog\\DatabaseLogAdapter\\SQLite'], 26 | ['sqlite2', '\\Migrator\\VersionLog\\DatabaseLogAdapter\\SQLite'], 27 | ['pgsql', '\\Migrator\\VersionLog\\DatabaseLogAdapter\\PostgreSQL'], 28 | ['mysql', '\\Migrator\\VersionLog\\DatabaseLogAdapter\\MySQL'], 29 | ]; 30 | } 31 | 32 | /** 33 | * @param string $driver_name 34 | * @param string $class 35 | * @dataProvider driverDataProvider 36 | */ 37 | public function testGetAdapter($driver_name, $class) 38 | { 39 | $this->pdo->expects($this->once()) 40 | ->method('getAttribute') 41 | ->with(\PDO::ATTR_DRIVER_NAME) 42 | ->willReturn($driver_name); 43 | 44 | $factory = new Factory(); 45 | $this->assertInstanceOf($class, $factory->getAdapter($this->pdo)); 46 | } 47 | 48 | /** 49 | * @expectedException \RuntimeException 50 | * @expectedExceptionMessage Adapter for foo is not yet implemented 51 | */ 52 | public function testGetAdapterUnknownDriver() 53 | { 54 | $this->pdo->expects($this->once()) 55 | ->method('getAttribute') 56 | ->with(\PDO::ATTR_DRIVER_NAME) 57 | ->willReturn('foo'); 58 | 59 | $factory = new Factory(); 60 | $factory->getAdapter($this->pdo); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/VersionLog/DatabaseLogAdapter/MySQLTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('pdo_mysql extension is not loaded'); 24 | } 25 | 26 | try { 27 | /** 28 | * MYSQL_TEST_DB_* constants are defined in phpunut.xml.dist 29 | */ 30 | $this->pdo = new PDO( 31 | sprintf('mysql:dbname=%s;host=%s', MYSQL_TEST_DB_NAME, MYSQL_TEST_DB_HOST), 32 | MYSQL_TEST_DB_USER, 33 | MYSQL_TEST_DB_PASSWORD, 34 | [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] 35 | ); 36 | } catch (PDOException $e) { 37 | $this->markTestSkipped( 38 | sprintf( 39 | 'Exception: %s. Set MYSQL_TEST_DB_* constants in phpunit.xml to connect to a local database', 40 | $e->getMessage() 41 | ) 42 | ); 43 | } 44 | 45 | $this->mysqlAdapter = new MySQL(); 46 | } 47 | 48 | public function versionDataProvider() 49 | { 50 | return [ 51 | [0, 0], 52 | [21, 21], 53 | [54, 54], 54 | [78, 78], 55 | ]; 56 | } 57 | 58 | /** 59 | * @param integer $version 60 | * @param integer $expected 61 | * @dataProvider versionDataProvider 62 | */ 63 | public function testMySQLAdapter($version, $expected) 64 | { 65 | $this->assertNull($this->mysqlAdapter->init($this->pdo)); 66 | $this->mysqlAdapter->updateVersion($this->pdo, $version); 67 | $this->assertEquals($expected, $this->mysqlAdapter->getCurrentVersion($this->pdo)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/stubs/config/stub.json: -------------------------------------------------------------------------------- 1 | { 2 | "my-database": { 3 | "dsn": "pgsql:host=localhost;port=5432;dbname=testdb", 4 | "user": "my-database_user", 5 | "password": "my_database_password", 6 | "options": [], 7 | "migrations": "/path/to/migrations" 8 | }, 9 | "another-database": { 10 | "dsn": "pgsql:host=localhost;port=5432;dbname=testdb", 11 | "user": "another_database_user", 12 | "password": "another_database_password", 13 | "options": [], 14 | "migrations": "/path/to/migrations" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/stubs/config/stub.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'dsn' => 'pgsql:host=localhost;port=5432;dbname=testdb', 6 | 'user' => 'my-database_user', 7 | 'password' => 'my_database_password', 8 | 'options' => [ 9 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 10 | ], 11 | 'migrations' => '/path/to/migrations' 12 | ], 13 | 'another-database' => [ 14 | 'dsn' => 'pgsql:host=localhost;port=5432;dbname=testdb', 15 | 'user' => 'another_database_user', 16 | 'password' => 'another_database_password', 17 | 'options' => [ 18 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 19 | ], 20 | 'migrations' => '/path/to/migrations' 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /test/stubs/config/yaml/invalid.yaml: -------------------------------------------------------------------------------- 1 | Hey 2 | I'm invalid 3 | YAML 4 | FILE 5 | -------------------------------------------------------------------------------- /test/stubs/config/yaml/stub.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | my-database: 3 | dsn: pgsql:host=localhost;port=5432;dbname=testdb 4 | user: my-database_user 5 | password: my_database_password 6 | options: [] 7 | migrations: "/path/to/migrations" 8 | another-database: 9 | dsn: pgsql:host=localhost;port=5432;dbname=testdb 10 | user: another_database_user 11 | password: another_database_password 12 | options: [] 13 | migrations: "/path/to/migrations" 14 | -------------------------------------------------------------------------------- /test/stubs/migrations/1.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE table_v1; 2 | -------------------------------------------------------------------------------- /test/stubs/migrations/1.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "table_v1"(id INT PRIMARY KEY); 2 | -------------------------------------------------------------------------------- /test/stubs/migrations/2.dn.comment.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE table_v2 RENAME TO table_v1; 2 | -------------------------------------------------------------------------------- /test/stubs/migrations/2.up.comment.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE table_v1 RENAME TO table_v2; 2 | -------------------------------------------------------------------------------- /test/stubs/migrations/3.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE table_v2 ADD COLUMN a TEXT; 2 | -------------------------------------------------------------------------------- /test/stubs/migrations/4.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE table_v4 RENAME TO table_v2; 2 | -------------------------------------------------------------------------------- /test/stubs/migrations/4.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE table_v2 RENAME TO table_v4; 2 | -------------------------------------------------------------------------------- /test/stubs/migrations/5.invalid-direction.sql: -------------------------------------------------------------------------------- 1 | -- invalid name 2 | --------------------------------------------------------------------------------