Skip to content

Commit

Permalink
Merge pull request #1 from orisintel/feature/sqlite
Browse files Browse the repository at this point in the history
feat(Sqlite): Initial support for Sqlite3 databases.
  • Loading branch information
paulrrogers authored Apr 5, 2019
2 parents fd7ef29 + 7438b0d commit 536211e
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 10 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion phpunit.xml.travis
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
</logging>

<php>
<env name="DB_DATABASE" value="test_ms"/>
<env name="DB_USERNAME" value="root"/>
</php>
</phpunit>
43 changes: 42 additions & 1 deletion src/Commands/MigrateDumpCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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}';
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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;
}
}
13 changes: 13 additions & 0 deletions src/Commands/MigrateLoadCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
10 changes: 5 additions & 5 deletions src/Handlers/MigrateStartingHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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);
}
}
Expand Down
19 changes: 19 additions & 0 deletions tests/Sqlite/MigrateDumpTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php


namespace OrisIntel\MigrationSnapshot\Tests\Sqlite;

class MigrateDumpTest extends SqliteTestCase
{
public function test_handle()
{
$this->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);
}
}
26 changes: 26 additions & 0 deletions tests/Sqlite/MigrateLoadTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php


namespace OrisIntel\MigrationSnapshot\Tests\Sqlite;


class MigrateLoadTest extends SqliteTestCase
{
public function test_handle()
{
// Make the dump file.
$this->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'));
}
}
17 changes: 17 additions & 0 deletions tests/Sqlite/SqliteTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php


namespace OrisIntel\MigrationSnapshot\Tests\Sqlite;

use OrisIntel\MigrationSnapshot\Tests\TestCase;

abstract class SqliteTestCase extends TestCase
{
protected $dbDefault = 'sqlite';

public static function setUpBeforeClass()
{
// File must exist before connection will initialize, even if empty.
touch(__DIR__ . '/../../vendor/orchestra/testbench-core/laravel/database/database.sqlite');
}
}
1 change: 1 addition & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ protected function createTestTablesWithoutMigrate() : void
{
// Executing without `loadMigrationsFrom` and without `Artisan::call` to
// avoid unnecessary runs through migration hooks.

require_once(__DIR__ . '/migrations/setup/0000_00_00_000000_create_test_tables.php');
\Schema::dropAllTables();
\Schema::dropAllViews();
Expand Down

0 comments on commit 536211e

Please sign in to comment.