migration_bundler 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.gitignore +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +122 -0
- data/Guardfile +8 -0
- data/LICENSE +202 -0
- data/README.md +219 -0
- data/Rakefile +16 -0
- data/bin/mb +6 -0
- data/lib/migration_bundler/actions.rb +38 -0
- data/lib/migration_bundler/cli.rb +355 -0
- data/lib/migration_bundler/databases/abstract_database.rb +54 -0
- data/lib/migration_bundler/databases/cassandra_database.rb +88 -0
- data/lib/migration_bundler/databases/sqlite_database.rb +116 -0
- data/lib/migration_bundler/migrations.rb +52 -0
- data/lib/migration_bundler/project.rb +90 -0
- data/lib/migration_bundler/targets/base.rb +85 -0
- data/lib/migration_bundler/targets/cassandra/cassandra_target.rb +73 -0
- data/lib/migration_bundler/targets/cassandra/create_schema_migrations.cql.erb +16 -0
- data/lib/migration_bundler/targets/cassandra/migration.cql.erb +0 -0
- data/lib/migration_bundler/targets/cocoapods/cocoapods_target.rb +43 -0
- data/lib/migration_bundler/targets/cocoapods/podspec.erb +12 -0
- data/lib/migration_bundler/targets/maven/maven_target.rb +62 -0
- data/lib/migration_bundler/targets/maven/project/.gitignore +6 -0
- data/lib/migration_bundler/targets/maven/project/MonkeyButler.iml +19 -0
- data/lib/migration_bundler/targets/maven/project/build.gradle +54 -0
- data/lib/migration_bundler/targets/sqlite/create_migration_bundler_tables.sql.erb +15 -0
- data/lib/migration_bundler/targets/sqlite/migration.sql.erb +11 -0
- data/lib/migration_bundler/targets/sqlite/sqlite_target.rb +92 -0
- data/lib/migration_bundler/templates/Gemfile.erb +4 -0
- data/lib/migration_bundler/templates/gitignore.erb +1 -0
- data/lib/migration_bundler/util.rb +71 -0
- data/lib/migration_bundler/version.rb +3 -0
- data/lib/migration_bundler.rb +1 -0
- data/migration_bundler.gemspec +33 -0
- data/spec/cli_spec.rb +700 -0
- data/spec/databases/cassandra_database_spec.rb +260 -0
- data/spec/databases/sqlite_database_spec.rb +198 -0
- data/spec/migrations_spec.rb +4 -0
- data/spec/project_spec.rb +128 -0
- data/spec/sandbox/cassandra/.gitignore +2 -0
- data/spec/sandbox/cassandra/.migration_bundler.yml +9 -0
- data/spec/sandbox/cassandra/migrations/20140523123443021_create_sandbox.cql.sql +14 -0
- data/spec/sandbox/cassandra/sandbox.cql +0 -0
- data/spec/sandbox/sqlite/.gitignore +2 -0
- data/spec/sandbox/sqlite/.migration_bundler.yml +9 -0
- data/spec/sandbox/sqlite/migrations/20140523123443021_create_sandbox.sql +14 -0
- data/spec/sandbox/sqlite/sandbox.sql +0 -0
- data/spec/spec_helper.rb +103 -0
- data/spec/targets/cassandra_target_spec.rb +191 -0
- data/spec/targets/cocoapods_target_spec.rb +197 -0
- data/spec/targets/maven_target_spec.rb +156 -0
- data/spec/targets/sqlite_target_spec.rb +103 -0
- data/spec/util_spec.rb +13 -0
- metadata +260 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module MigrationBundler
|
5
|
+
class Project
|
6
|
+
class << self
|
7
|
+
def load(path = Dir.pwd)
|
8
|
+
@project ||= proc do
|
9
|
+
project_path = File.join(path, '.migration_bundler.yml')
|
10
|
+
raise "fatal: Not a migration_bundler repository: no .migration_bundler.yml" unless File.exists?(project_path)
|
11
|
+
options = YAML.load(File.read(project_path))
|
12
|
+
new(options)
|
13
|
+
end.call
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(options)
|
17
|
+
@project = new(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear
|
21
|
+
@project = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :name, :config, :database_url, :targets
|
26
|
+
|
27
|
+
def initialize(options = {})
|
28
|
+
options.each { |k,v| send("#{k}=", v) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def database_url=(database_url)
|
32
|
+
@database_url = database_url ? URI(database_url) : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def database
|
36
|
+
database_url.scheme || 'sqlite'
|
37
|
+
end
|
38
|
+
|
39
|
+
def schema_path
|
40
|
+
"#{name}" + database_class.migration_ext
|
41
|
+
end
|
42
|
+
|
43
|
+
def migrations_path
|
44
|
+
"migrations"
|
45
|
+
end
|
46
|
+
|
47
|
+
def git_url
|
48
|
+
`git config remote.origin.url`.chomp
|
49
|
+
end
|
50
|
+
|
51
|
+
def git_latest_tag
|
52
|
+
git_tag_for_version(nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
def git_current_branch
|
56
|
+
`git symbolic-ref --short HEAD`.chomp
|
57
|
+
end
|
58
|
+
|
59
|
+
def git_tag_for_version(version)
|
60
|
+
pattern = version && "#{version}*"
|
61
|
+
tag = `git tag -l --sort=-v:refname #{pattern} | head -n 1`.chomp
|
62
|
+
tag.empty? ? nil : tag
|
63
|
+
end
|
64
|
+
|
65
|
+
def git_user_email
|
66
|
+
`git config user.email`.chomp
|
67
|
+
end
|
68
|
+
|
69
|
+
def git_user_name
|
70
|
+
`git config user.name`.chomp
|
71
|
+
end
|
72
|
+
|
73
|
+
def save!(path)
|
74
|
+
project_path = File.join(path, '.migration_bundler.yml')
|
75
|
+
File.open(project_path, 'w') { |f| f << YAML.dump(self.to_hash) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def database_class
|
79
|
+
MigrationBundler::Util.database_named(database)
|
80
|
+
end
|
81
|
+
|
82
|
+
def database_target_class
|
83
|
+
MigrationBundler::Util.target_classes_named(database)[0]
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_hash
|
87
|
+
{ "name" => name, "config" => config, "database_url" => database_url.to_s, "targets" => targets }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module MigrationBundler
|
2
|
+
module Targets
|
3
|
+
class Base < Thor
|
4
|
+
include Thor::Actions
|
5
|
+
include MigrationBundler::Actions
|
6
|
+
add_runtime_options!
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def source_root
|
10
|
+
File.join File.dirname(__FILE__), name
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
"#{self}".split('::').last.gsub(/Target$/, '').downcase
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_with_cli(cli)
|
18
|
+
# Allows targets a chance to configure the CLI
|
19
|
+
# This is an ideal place to register any options, tweak description, etc.
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :project, :database, :migrations
|
24
|
+
|
25
|
+
# Target Command
|
26
|
+
|
27
|
+
desc "init", "Initializes the target."
|
28
|
+
def init
|
29
|
+
# Default implementation does nothing
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "new NAME", "Create a new migration"
|
33
|
+
def new(path)
|
34
|
+
# Default implementation does nothing
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "generate", "Generates a platform specific package."
|
38
|
+
def generate
|
39
|
+
# Default implementation does nothing
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "push", "Pushes a built package."
|
43
|
+
def push
|
44
|
+
# Default implementation does nothing
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "push", "Validates the environment."
|
48
|
+
def validate
|
49
|
+
# Default implementation does nothing
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "dump", "Dumps the schema"
|
53
|
+
def dump
|
54
|
+
# Default implementation does nothing
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "load", "Loads the schema"
|
58
|
+
def load
|
59
|
+
# Default implementation does nothing
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "drop", "Drops all tables and records"
|
63
|
+
def drop
|
64
|
+
# Default implementation does nothing
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
def project
|
69
|
+
@project ||= MigrationBundler::Project.load(destination_root)
|
70
|
+
end
|
71
|
+
|
72
|
+
def database
|
73
|
+
@database ||= project.database_class.new(database_url)
|
74
|
+
end
|
75
|
+
|
76
|
+
def database_url
|
77
|
+
(options[:database] && URI(options[:database])) || project.database_url
|
78
|
+
end
|
79
|
+
|
80
|
+
def migrations
|
81
|
+
@migrations ||= MigrationBundler::Migrations.new(project.migrations_path, database)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'migration_bundler/databases/cassandra_database'
|
2
|
+
|
3
|
+
module MigrationBundler
|
4
|
+
module Targets
|
5
|
+
class CassandraTarget < Base
|
6
|
+
def init
|
7
|
+
migration_path = "migrations/" + MigrationBundler::Util.migration_named('create_' + options[:name]) + '.cql'
|
8
|
+
template('create_schema_migrations.cql.erb', migration_path)
|
9
|
+
git_add migration_path
|
10
|
+
end
|
11
|
+
|
12
|
+
def new(name)
|
13
|
+
migration_path = "migrations/" + MigrationBundler::Util.migration_named(name) + '.cql'
|
14
|
+
template('migration.cql.erb', migration_path)
|
15
|
+
git_add migration_path
|
16
|
+
end
|
17
|
+
|
18
|
+
def dump
|
19
|
+
database_url = (options[:database] && URI(options[:database])) || project.database_url
|
20
|
+
@database = MigrationBundler::Databases::CassandraDatabase.new(database_url)
|
21
|
+
fail Error, "Cannot dump database: the database at '#{database_url}' does not have a `schema_migrations` table." unless database.migrations_table?
|
22
|
+
say "Dumping schema from database '#{database_url}'"
|
23
|
+
|
24
|
+
say "Dumping keyspaces '#{keyspaces.join(', ')}'..."
|
25
|
+
describe_statements = keyspaces.map { |keyspace| "describe keyspace #{keyspace};" }
|
26
|
+
run "cqlsh -e '#{describe_statements.join(' ')}' #{database_url.host} > #{project.schema_path}"
|
27
|
+
|
28
|
+
File.open(project.schema_path, 'a') do |f|
|
29
|
+
f.puts "USE #{keyspace};"
|
30
|
+
project.config['db.dump_tables'].each do |table_name|
|
31
|
+
say "Dumping rows from '#{table_name}'..."
|
32
|
+
with_padding do
|
33
|
+
row_statements = database.dump_rows(table_name)
|
34
|
+
f.puts row_statements.join("\n")
|
35
|
+
say "wrote #{row_statements.size} rows.", :green
|
36
|
+
end
|
37
|
+
end
|
38
|
+
say
|
39
|
+
end
|
40
|
+
|
41
|
+
say "Dump complete. Schema written to #{project.schema_path}."
|
42
|
+
end
|
43
|
+
|
44
|
+
def load
|
45
|
+
project = MigrationBundler::Project.load
|
46
|
+
unless File.size?(project.schema_path)
|
47
|
+
raise Error, "Cannot load database: empty schema found at #{project.schema_path}. Maybe you need to `mb migrate`?"
|
48
|
+
end
|
49
|
+
|
50
|
+
@database = MigrationBundler::Databases::CassandraDatabase.new(database_url)
|
51
|
+
drop
|
52
|
+
run "cqlsh #{database_url.host} -f #{project.schema_path}"
|
53
|
+
|
54
|
+
say "Loaded schema at version #{database.current_version}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def drop
|
58
|
+
say_status :drop, database_url, :yellow
|
59
|
+
database.drop(keyspaces)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def keyspace
|
64
|
+
database_url.path[1..-1]
|
65
|
+
end
|
66
|
+
|
67
|
+
def keyspaces
|
68
|
+
keyspaces = (project.config['cassandra.keyspaces'] || []).dup
|
69
|
+
keyspaces.unshift(keyspace)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/* Create Monkey Butler Tables */
|
2
|
+
|
3
|
+
-- Maintains list of applied migrations
|
4
|
+
USE KEYSPACE <%= keyspace -%>;
|
5
|
+
CREATE TABLE schema_migrations(
|
6
|
+
version INTEGER UNIQUE NOT NULL
|
7
|
+
);
|
8
|
+
|
9
|
+
/* Create application tables */
|
10
|
+
|
11
|
+
/*
|
12
|
+
CREATE TABLE table_name (
|
13
|
+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
14
|
+
...
|
15
|
+
);
|
16
|
+
*/
|
File without changes
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'migration_bundler/targets/base'
|
2
|
+
|
3
|
+
module MigrationBundler
|
4
|
+
module Targets
|
5
|
+
class CocoapodsTarget < MigrationBundler::Targets::Base
|
6
|
+
def init
|
7
|
+
unless project.config['cocoapods.repo']
|
8
|
+
project.config['cocoapods.repo'] = ask("What is the name of your Cocoapods specs repo? ")
|
9
|
+
end
|
10
|
+
if options['bundler']
|
11
|
+
append_to_file 'Gemfile', "gem 'cocoapods'\n"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate
|
16
|
+
invoke :validate
|
17
|
+
template('podspec.erb', podspec_name, force: true)
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate
|
21
|
+
fail Error, "Invalid configuration: cocoapods.repo is not configured." unless cocoapods_repo
|
22
|
+
end
|
23
|
+
|
24
|
+
def push
|
25
|
+
invoke :validate
|
26
|
+
run "pod repo push #{options['quiet'] && '--silent '}--allow-warnings #{cocoapods_repo} #{podspec_name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def cocoapods_repo
|
31
|
+
project.config['cocoapods.repo']
|
32
|
+
end
|
33
|
+
|
34
|
+
def podspec_name
|
35
|
+
"#{project.name}.podspec"
|
36
|
+
end
|
37
|
+
|
38
|
+
def pod_version
|
39
|
+
unique_tag_for_version(migrations.latest_version)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Pod::Spec.new do |s|
|
2
|
+
s.name = "<%= project.name %>"
|
3
|
+
s.version = "<%= pod_version %>"
|
4
|
+
s.summary = "Packages the database schema and migrations for <%= project.name -%>"
|
5
|
+
s.homepage = "<%= project.config['homepage'] || "http://github.com/layerhq" %>"
|
6
|
+
s.author = { "<%= project.git_user_name -%>" => "<%= project.git_user_email -%>" }
|
7
|
+
s.source = { :git => "<%= project.git_url -%>", :tag => "<%= pod_version -%>" }
|
8
|
+
s.license = 'Commercial'
|
9
|
+
s.requires_arc = true
|
10
|
+
|
11
|
+
s.resource_bundles = { '<%= project.name -%>' => ['migrations/*', '<%= project.schema_path -%>'] }
|
12
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'migration_bundler/targets/base'
|
2
|
+
|
3
|
+
module MigrationBundler
|
4
|
+
module Targets
|
5
|
+
class MavenTarget < MigrationBundler::Targets::Base
|
6
|
+
def init
|
7
|
+
unless project.config['maven.url']
|
8
|
+
project.config['maven.url'] = ask("What is the URL of your Java Maven repo? ")
|
9
|
+
end
|
10
|
+
unless project.config['maven.username']
|
11
|
+
project.config['maven.username'] = ask("What is the username for your Java Maven repo? ")
|
12
|
+
end
|
13
|
+
unless project.config['maven.password']
|
14
|
+
project.config['maven.password'] = ask("What is the password for your Java Maven repo? ")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate
|
19
|
+
invoke :validate
|
20
|
+
remove_file "project"
|
21
|
+
empty_directory "project"
|
22
|
+
empty_directory "project/src"
|
23
|
+
empty_directory "project/src/main"
|
24
|
+
empty_directory "project/src/main/resources"
|
25
|
+
empty_directory "project/src/main/resources/schema"
|
26
|
+
|
27
|
+
copy_file "project/build.gradle", "project/build.gradle"
|
28
|
+
FileUtils.cp_r project.schema_path, "project/src/main/resources/schema/schema.sql"
|
29
|
+
FileUtils.cp_r project.migrations_path, "project/src/main/resources"
|
30
|
+
|
31
|
+
version = unique_tag_for_version(migrations.latest_version)
|
32
|
+
run "cd project && gradle#{options['quiet'] && ' -q '} -Pversion=#{version} clean jar"
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate
|
36
|
+
fail Error, "Invalid configuration: maven.repo is not configured." unless maven_url
|
37
|
+
fail Error, "Invalid configuration: maven.username is not configured." unless maven_username
|
38
|
+
fail Error, "Invalid configuration: maven.password is not configured." unless maven_password
|
39
|
+
end
|
40
|
+
|
41
|
+
def push
|
42
|
+
invoke :validate
|
43
|
+
version = project.git_latest_tag
|
44
|
+
run "cd project && gradle#{options['quiet'] && ' -q'} -Pversion=#{version} -Purl=#{maven_url} -Pusername=#{maven_username} -Ppassword=#{maven_password} publish"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def maven_url
|
50
|
+
project.config['maven.url']
|
51
|
+
end
|
52
|
+
|
53
|
+
def maven_username
|
54
|
+
project.config['maven.username']
|
55
|
+
end
|
56
|
+
|
57
|
+
def maven_password
|
58
|
+
project.config['maven.password']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="0.1.1" type="JAVA_MODULE" version="4">
|
3
|
+
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
4
|
+
<output url="file://$MODULE_DIR$/build/classes/main" />
|
5
|
+
<output-test url="file://$MODULE_DIR$/build/classes/test" />
|
6
|
+
<exclude-output />
|
7
|
+
<content url="file://$MODULE_DIR$">
|
8
|
+
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
9
|
+
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
10
|
+
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
11
|
+
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
12
|
+
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
13
|
+
<excludeFolder url="file://$MODULE_DIR$/build" />
|
14
|
+
</content>
|
15
|
+
<orderEntry type="inheritedJdk" />
|
16
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
17
|
+
</component>
|
18
|
+
</module>
|
19
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
apply plugin: 'java'
|
2
|
+
apply plugin: 'maven'
|
3
|
+
apply plugin: 'maven-publish'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Specify:
|
7
|
+
* -Pversion=[VERSION]
|
8
|
+
* -Purl=[MAVEN URL TO PUBLISH TO] e.g. "http://nexus.dev.layer.com:8081/nexus/content/repositories/releases" or "${System.env.HOME}/.m2/repository"
|
9
|
+
* -Pusername=[USERNAME FOR MAVEN PUBLISH]
|
10
|
+
* -Ppassword=[PASSWORD FOR MAVEN PUBLISH]
|
11
|
+
*/
|
12
|
+
|
13
|
+
ext {
|
14
|
+
mavenUrl = rootProject.hasProperty("url") ? rootProject.property("url") : "${System.env.HOME}/.m2/repository"
|
15
|
+
mavenUsername = rootProject.hasProperty("username") ? rootProject.property("username") : null;
|
16
|
+
mavenPassword = rootProject.hasProperty("password") ? rootProject.property("password") : null;
|
17
|
+
}
|
18
|
+
|
19
|
+
compileJava {
|
20
|
+
sourceCompatibility = 1.6
|
21
|
+
targetCompatibility = 1.6
|
22
|
+
}
|
23
|
+
|
24
|
+
repositories {
|
25
|
+
mavenCentral()
|
26
|
+
}
|
27
|
+
|
28
|
+
/*************************************************
|
29
|
+
* Uploading
|
30
|
+
************************************************/
|
31
|
+
task Jar(type: Jar) {
|
32
|
+
jar.archiveName = "MigrationBundler-" + version + ".jar"
|
33
|
+
}
|
34
|
+
|
35
|
+
publishing {
|
36
|
+
publications {
|
37
|
+
mavenJava(MavenPublication) {
|
38
|
+
groupId "com.layer"
|
39
|
+
artifactId "MigrationBundler"
|
40
|
+
from components.java
|
41
|
+
}
|
42
|
+
}
|
43
|
+
repositories {
|
44
|
+
maven {
|
45
|
+
credentials {
|
46
|
+
username = mavenUsername;
|
47
|
+
password = mavenPassword;
|
48
|
+
}
|
49
|
+
url = mavenUrl;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/* Create Monkey Butler Tables */
|
2
|
+
|
3
|
+
-- Maintains list of applied migrations
|
4
|
+
CREATE TABLE schema_migrations(
|
5
|
+
version INTEGER UNIQUE NOT NULL
|
6
|
+
);
|
7
|
+
|
8
|
+
/* Create application tables */
|
9
|
+
|
10
|
+
/*
|
11
|
+
CREATE TABLE table_name (
|
12
|
+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
13
|
+
...
|
14
|
+
);
|
15
|
+
*/
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'migration_bundler/databases/sqlite_database'
|
2
|
+
|
3
|
+
module MigrationBundler
|
4
|
+
module Targets
|
5
|
+
class SqliteTarget < Base
|
6
|
+
# TODO: Need a way to do this for self elegantly...
|
7
|
+
def self.register_with_cli(cli)
|
8
|
+
cli.method_option :database, type: :string, aliases: '-d', desc: "Set target DATABASE", for: :dump
|
9
|
+
cli.method_option :database, type: :string, aliases: '-d', desc: "Set target DATABASE", for: :load
|
10
|
+
end
|
11
|
+
|
12
|
+
def init
|
13
|
+
migration_name = MigrationBundler::Util.migration_named('create_' + options['name'])
|
14
|
+
template('create_migration_bundler_tables.sql.erb', "migrations/#{migration_name}.sql")
|
15
|
+
git_add "migrations/#{migration_name}.sql"
|
16
|
+
create_file(database_path)
|
17
|
+
append_to_file '.gitignore', database_path
|
18
|
+
end
|
19
|
+
|
20
|
+
no_commands do
|
21
|
+
# NOTE: Used instead of database.url to avoid creation of the database in pretend mode
|
22
|
+
def database_path
|
23
|
+
database_url.path || database_url.opaque
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def new(name)
|
28
|
+
migration_ext = project.database_class.migration_ext
|
29
|
+
migration_name = MigrationBundler::Util.migration_named(name) + migration_ext
|
30
|
+
template('migration.sql.erb', "migrations/#{migration_name}")
|
31
|
+
git_add "migrations/#{migration_name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def dump
|
35
|
+
database_url = (options[:database] && URI(options[:database])) || project.database_url
|
36
|
+
database_path = database_url.path || database_url.opaque
|
37
|
+
fail Error, "Cannot dump database: no file at path '#{database_path}'." unless File.exists?(database_path)
|
38
|
+
|
39
|
+
@database = MigrationBundler::Databases::SqliteDatabase.new(database_url)
|
40
|
+
fail Error, "Cannot dump database: the database at path '#{database_path}' does not have a `schema_migrations` table." unless database.migrations_table?
|
41
|
+
say "Dumping schema from database '#{database_path}'"
|
42
|
+
|
43
|
+
File.truncate(project.schema_path, 0)
|
44
|
+
|
45
|
+
types = { table: 'tables', index: 'indexes', trigger: 'triggers', view: 'views'}
|
46
|
+
types.each do |type, name|
|
47
|
+
say "Dumping #{name}..."
|
48
|
+
with_padding do
|
49
|
+
database.dump_to_schema(type, project.schema_path) do |name|
|
50
|
+
say "wrote #{type}: #{name}", :green
|
51
|
+
end
|
52
|
+
end
|
53
|
+
say
|
54
|
+
end
|
55
|
+
|
56
|
+
File.open(project.schema_path, 'a') do |f|
|
57
|
+
project.config['db.dump_tables'].each do |table_name|
|
58
|
+
say "Dumping rows from '#{table_name}'..."
|
59
|
+
with_padding do
|
60
|
+
row_statements = database.dump_rows(table_name)
|
61
|
+
f.puts row_statements.join("\n")
|
62
|
+
say "wrote #{row_statements.size} rows.", :green
|
63
|
+
end
|
64
|
+
end
|
65
|
+
say
|
66
|
+
end
|
67
|
+
|
68
|
+
say "Dump complete. Schema written to #{project.schema_path}."
|
69
|
+
end
|
70
|
+
|
71
|
+
def load
|
72
|
+
project = MigrationBundler::Project.load
|
73
|
+
unless File.size?(project.schema_path)
|
74
|
+
raise Error, "Cannot load database: empty schema found at #{project.schema_path}. Maybe you need to `mb migrate`?"
|
75
|
+
end
|
76
|
+
|
77
|
+
drop
|
78
|
+
command = "sqlite3 #{database.path} < #{project.schema_path}"
|
79
|
+
say_status :executing, command
|
80
|
+
stdout_str, stderr_str, status = Open3.capture3(command)
|
81
|
+
fail Error, "Failed loading schema: #{stderr_str}" unless stderr_str.empty?
|
82
|
+
|
83
|
+
say "Loaded schema at version #{database.current_version}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def drop
|
87
|
+
say_status :truncate, database.path, :yellow
|
88
|
+
database.drop
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module MigrationBundler
|
2
|
+
class Util
|
3
|
+
class << self
|
4
|
+
def migration_timestamp
|
5
|
+
Time.now.strftime('%Y%m%d%H%M%S%3N').to_i
|
6
|
+
end
|
7
|
+
|
8
|
+
def migration_named(name, timestamp = migration_timestamp)
|
9
|
+
migration_name = [timestamp, Thor::Util.snake_case(name)].join('_')
|
10
|
+
end
|
11
|
+
|
12
|
+
def strip_leading_whitespace(string)
|
13
|
+
string.gsub(/^\s+/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
def migration_version_from_path(path)
|
17
|
+
path.match(/(\d{17})_/)[1].to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def migration_versions_from_paths(paths)
|
21
|
+
paths.map { |path| migration_version_from_path(path) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def migrations_by_version(paths)
|
25
|
+
paths.inject({}) do |hash, path|
|
26
|
+
version = migration_version_from_path(path)
|
27
|
+
hash[version] = path
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def camelize(term, uppercase_first_letter = true)
|
33
|
+
string = term.to_s
|
34
|
+
string = string.sub(/^[a-z\d]*/) { $&.capitalize }
|
35
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
|
36
|
+
string.gsub!('/', '::')
|
37
|
+
string
|
38
|
+
end
|
39
|
+
|
40
|
+
def database_named(name)
|
41
|
+
raise ArgumentError, "Database name cannot be nil." if name.nil?
|
42
|
+
require "migration_bundler/databases/#{name}_database"
|
43
|
+
klass_name = "MigrationBundler::Databases::#{Util.camelize(name)}Database"
|
44
|
+
Object.const_get(klass_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def target_classes_named(*names)
|
48
|
+
raise ArgumentError, "Database name cannot be nil." if names.nil?
|
49
|
+
names.flatten.map do |name|
|
50
|
+
require "migration_bundler/targets/#{name}/#{name}_target"
|
51
|
+
klass_name = "MigrationBundler::Targets::#{Util.camelize(name)}Target"
|
52
|
+
Object.const_get(klass_name).tap do |klass|
|
53
|
+
yield klass if block_given?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def unique_tag_for_version(version)
|
59
|
+
revision = nil
|
60
|
+
tag = nil
|
61
|
+
begin
|
62
|
+
tag = [version, revision].compact.join('.')
|
63
|
+
existing_tag = `git tag -l #{tag}`
|
64
|
+
break if existing_tag == ""
|
65
|
+
revision = revision.to_i + 1
|
66
|
+
end while true
|
67
|
+
tag
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'migration_bundler/cli'
|