migration_bundler 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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'
|