diff --git a/.travis.yml b/.travis.yml index e11ab57..d972292 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,8 @@ sudo: required before_script: - travis_retry composer self-update - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source -- mysql -e 'CREATE DATABASE test_ms;' -- psql -c 'CREATE DATABASE test_ms;' -U postgres +- mysql -e 'CREATE DATABASE forge;' +- psql -c 'CREATE DATABASE forge;' -U postgres - psql -c 'CREATE ROLE root SUPERUSER LOGIN;' -U postgres script: diff --git a/README.md b/README.md index e5b4a88..d959b8d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Simplify and accelerate applying many migrations at once using a flattened dump of the database schema and migrations, similar in spirit to Rails' `schema.rb`. -Works with the `mysql` and `pgsql` database drivers. +Works with the `mysql`, `pgsql`, and `sqlite` database drivers. ## Installation diff --git a/phpunit.xml.travis b/phpunit.xml.travis index 77cc040..86d7767 100644 --- a/phpunit.xml.travis +++ b/phpunit.xml.travis @@ -28,7 +28,6 @@ - diff --git a/src/Commands/MigrateDumpCommand.php b/src/Commands/MigrateDumpCommand.php index 1a743be..8f1dbe5 100644 --- a/src/Commands/MigrateDumpCommand.php +++ b/src/Commands/MigrateDumpCommand.php @@ -5,7 +5,7 @@ final class MigrateDumpCommand extends \Illuminate\Console\Command { public const SCHEMA_SQL_PATH_SUFFIX = '/migrations/sql/schema.sql'; - public const SUPPORTED_DB_DRIVERS = ['mysql', 'pgsql']; + public const SUPPORTED_DB_DRIVERS = ['mysql', 'pgsql', 'sqlite']; protected $signature = 'migrate:dump {--database= : The database connection to use}'; @@ -78,6 +78,9 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i . ' --user=' . escapeshellarg($db_config['username']) . ' --password=' . escapeshellarg($db_config['password']) . ' ' . escapeshellarg($db_config['database']); + // TODO: Suppress warning about insecure password. + // CONSIDER: Intercepting stdout and stderr and converting to colorized + // console output with `$this->info` and `->error`. passthru( $command_prefix . ' --result-file=' . escapeshellarg($schema_sql_path) @@ -127,6 +130,7 @@ private static function pgsqlDump(array $db_config, string $schema_sql_path) : i . ' --port=' . escapeshellarg($db_config['port']) . ' --username=' . escapeshellarg($db_config['username']) . ' ' . escapeshellarg($db_config['database']); + // TODO: Suppress warning about insecure password. passthru( $command_prefix . ' --file=' . escapeshellarg($schema_sql_path) @@ -148,4 +152,41 @@ private static function pgsqlDump(array $db_config, string $schema_sql_path) : i return $exit_code; } + + /** + * @param array $db_config like ['host' => , 'port' => ]. + * @param string $schema_sql_path like '.../schema.sql' + * + * @return int containing exit code. + */ + private static function sqliteDump(array $db_config, string $schema_sql_path) : int + { + // CONSIDER: Accepting command name as option or from config. + $command_prefix = 'sqlite3 ' . escapeshellarg($db_config['database']); + + // Since Sqlite lacks Information Schema, and dumping everything may be + // too slow or memory intense, just query tables and dump them + // individually. + exec($command_prefix . ' .tables', $output, $exit_code); + if (0 !== $exit_code) { + return $exit_code; + } + $tables = preg_split('/\s+/', implode(' ', $output)); + + foreach ($tables as $table) { + // Only migrations should dump data with schema. + $sql_command = 'migrations' === $table ? '.dump' : '.schema'; + + passthru( + $command_prefix . ' ' . escapeshellarg("$sql_command $table") + . ' >> ' . escapeshellarg($schema_sql_path), + $exit_code + ); + if (0 !== $exit_code) { + return $exit_code; + } + } + + return $exit_code; + } } \ No newline at end of file diff --git a/src/Commands/MigrateLoadCommand.php b/src/Commands/MigrateLoadCommand.php index c7c5ce6..c0f4094 100644 --- a/src/Commands/MigrateLoadCommand.php +++ b/src/Commands/MigrateLoadCommand.php @@ -145,4 +145,17 @@ private static function pgsqlLoad(string $path, array $db_config, int $verbosity return $exit_code; } + + private static function sqliteLoad(string $path, array $db_config, int $verbosity = null) : int + { + // CONSIDER: Directly sending queries via Eloquent (requires parsing SQL + // or intermediate format). + // CONSIDER: Capturing Stderr and outputting with `$this->error()`. + + $command = 'sqlite3 ' . escapeshellarg($db_config['database']) . ' ' . escapeshellarg(".read $path"); + + passthru($command, $exit_code); + + return $exit_code; + } } \ No newline at end of file diff --git a/src/Handlers/MigrateStartingHandler.php b/src/Handlers/MigrateStartingHandler.php index bdff5ae..8482475 100644 --- a/src/Handlers/MigrateStartingHandler.php +++ b/src/Handlers/MigrateStartingHandler.php @@ -66,8 +66,8 @@ public function handle(CommandStarting $event) // Must pass along options or it may use wrong DB or have // inconsistent output. $options = self::inputToArtisanOptions($event->input); - $database = $options['--database'] ?? env('DB_CONNECTION'); - $db_driver = \DB::connection($database)->getDriverName(); + $database = $options['--database'] ?? \DB::getConfig('name'); + $db_driver = \DB::getDriverName(); if (! in_array($db_driver, MigrateDumpCommand::SUPPORTED_DB_DRIVERS, true)) { // CONSIDER: Logging or emitting console warning. return; @@ -79,13 +79,13 @@ public function handle(CommandStarting $event) // Try-catch instead of information_schema since not all have one. try { $has_migrated_any = ! is_null( - \DB::connection($database)->table('migrations')->value('id') + \DB::table('migrations')->value('id') ); } catch (\PDOException $e) { // No op. when table does not exist. if ( ! in_array($e->getCode(), ['42P01', '42S02'], true) - && ! preg_match("/\bdoes ?n[o']t exist\b/iu", $e->getMessage()) + && ! preg_match("/\b(does ?n[o']t exist|no such table)\b/iu", $e->getMessage()) ) { throw $e; } @@ -112,7 +112,7 @@ public static function inputToArtisanOptions(InputInterface $input) : array if (false !== $input->getParameterOption($option)) { $options[$option] = true; } - } else { + } elseif (false !== $input->getParameterOption($option)) { $options[$option] = $input->getParameterOption($option); } } diff --git a/tests/Sqlite/MigrateDumpTest.php b/tests/Sqlite/MigrateDumpTest.php new file mode 100644 index 0000000..bab66a0 --- /dev/null +++ b/tests/Sqlite/MigrateDumpTest.php @@ -0,0 +1,19 @@ +createTestTablesWithoutMigrate(); + $result = \Artisan::call('migrate:dump'); + $this->assertEquals(0, $result); + $this->assertDirectoryExists($this->schemaSqlDirectory); + $this->assertFileExists($this->schemaSqlPath); + $result_sql = file_get_contents($this->schemaSqlPath); + $this->assertRegExp('/CREATE TABLE( IF NOT EXISTS)? "test_ms" /', $result_sql); + $this->assertRegExp('/INSERT INTO "?migrations"? /', $result_sql); + } +} \ No newline at end of file diff --git a/tests/Sqlite/MigrateLoadTest.php b/tests/Sqlite/MigrateLoadTest.php new file mode 100644 index 0000000..d8509b4 --- /dev/null +++ b/tests/Sqlite/MigrateLoadTest.php @@ -0,0 +1,26 @@ +createTestTablesWithoutMigrate(); + $result = \Artisan::call('migrate:dump'); + $this->assertEquals(0, $result); + + $result = \Artisan::call('migrate:load'); + $this->assertEquals(0, $result); + + $this->assertEquals( + '0000_00_00_000000_create_test_tables', + \DB::table('migrations')->value('migration') + ); + + $this->assertNull(\DB::table('test_ms')->value('name')); + } +} \ No newline at end of file diff --git a/tests/Sqlite/SqliteTestCase.php b/tests/Sqlite/SqliteTestCase.php new file mode 100644 index 0000000..3e7445d --- /dev/null +++ b/tests/Sqlite/SqliteTestCase.php @@ -0,0 +1,17 @@ +