db-migrate 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +46 -0
- data/LICENSE +20 -0
- data/README.md +60 -0
- data/bin/migrate +136 -0
- data/lib/migrate.rb +9 -0
- data/lib/migrate/config.rb +104 -0
- data/lib/migrate/errors.rb +4 -0
- data/lib/migrate/lang.rb +10 -0
- data/lib/migrate/lang/go.rb +37 -0
- data/lib/migrate/lang/javascript.rb +25 -0
- data/lib/migrate/lang/lang.rb +15 -0
- data/lib/migrate/lang/python.rb +25 -0
- data/lib/migrate/lang/ruby.rb +25 -0
- data/lib/migrate/lang/sql.rb +26 -0
- data/lib/migrate/logger.rb +39 -0
- data/lib/migrate/migrator.rb +144 -0
- data/lib/migrate/storage.rb +7 -0
- data/lib/migrate/storage/db.rb +150 -0
- data/lib/migrate/storage/mysql.rb +77 -0
- data/lib/migrate/storage/postgres.rb +74 -0
- data/migrate.gemspec +21 -0
- data/spec/lib/config_spec.rb +103 -0
- data/spec/lib/fixtures/go/down.go +7 -0
- data/spec/lib/fixtures/go/up.go +7 -0
- data/spec/lib/fixtures/js/down.js +1 -0
- data/spec/lib/fixtures/js/up.js +1 -0
- data/spec/lib/fixtures/py/down.py +1 -0
- data/spec/lib/fixtures/py/up.py +1 -0
- data/spec/lib/fixtures/rb/down.rb +1 -0
- data/spec/lib/fixtures/rb/up.rb +1 -0
- data/spec/lib/fixtures/sql/down.sql +1 -0
- data/spec/lib/fixtures/sql/up.sql +1 -0
- data/spec/lib/lang/lang_spec.rb +46 -0
- data/spec/lib/migrator_spec.rb +103 -0
- data/spec/lib/storage/db_spec.rb +104 -0
- data/spec/spec_helper.rb +109 -0
- metadata +184 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Migrate
|
2
|
+
module Lang
|
3
|
+
class Javascript < Lang
|
4
|
+
def initialize
|
5
|
+
@ext = "js"
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_migration(dir)
|
9
|
+
File.open("#{dir}/up.#{@ext}", "w") do |f|
|
10
|
+
f.puts "// Here goes JS for migration forward\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
File.open("#{dir}/down.#{@ext}", "w") do |f|
|
14
|
+
f.puts "// Here goes JS for migration backward\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def exec_migration(dir, is_up)
|
19
|
+
script = "#{dir}/#{is_up ? "up" : "down"}.#{@ext}"
|
20
|
+
Log.info("Executing #{script}...")
|
21
|
+
`node #{script}`
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Migrate
|
2
|
+
module Lang
|
3
|
+
class Lang
|
4
|
+
attr_reader :ext
|
5
|
+
|
6
|
+
def create_migration(dir)
|
7
|
+
raise "Implementation for creating new migration not found."
|
8
|
+
end
|
9
|
+
|
10
|
+
def exec_migration(dir, is_up)
|
11
|
+
raise "Implementation for executing migration not found."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Migrate
|
2
|
+
module Lang
|
3
|
+
class Python < Lang
|
4
|
+
def initialize
|
5
|
+
@ext = "py"
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_migration(dir)
|
9
|
+
File.open("#{dir}/up.#{@ext}", "w") do |f|
|
10
|
+
f.puts "# Here goes Python code for migration forward\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
File.open("#{dir}/down.#{@ext}", "w") do |f|
|
14
|
+
f.puts "# Here goes Python code for migration backward\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def exec_migration(dir, is_up)
|
19
|
+
script = "#{dir}/#{is_up ? "up" : "down"}.#{@ext}"
|
20
|
+
Log.info("Executing #{script}...")
|
21
|
+
`python #{script}`
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Migrate
|
2
|
+
module Lang
|
3
|
+
class Ruby < Lang
|
4
|
+
def initialize
|
5
|
+
@ext = "rb"
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_migration(dir)
|
9
|
+
File.open("#{dir}/up.#{@ext}", "w") do |f|
|
10
|
+
f.puts "# Here goes Ruby code for migration forward\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
File.open("#{dir}/down.#{@ext}", "w") do |f|
|
14
|
+
f.puts "# Here goes Ruby code for migration backward\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def exec_migration(dir, is_up)
|
19
|
+
script = "#{dir}/#{is_up ? "up" : "down"}.#{@ext}"
|
20
|
+
Log.info("Executing #{script}...")
|
21
|
+
`ruby #{script}`
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Migrate
|
2
|
+
module Lang
|
3
|
+
class Sql < Lang
|
4
|
+
def initialize(db)
|
5
|
+
@db = db
|
6
|
+
@ext = "sql"
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_migration(dir)
|
10
|
+
File.open("#{dir}/up.#{@ext}", "w") do |f|
|
11
|
+
f.puts "-- Here goes SQL for migration forward\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
File.open("#{dir}/down.#{@ext}", "w") do |f|
|
15
|
+
f.puts "-- Here goes SQL for migration backward\n"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def exec_migration(dir, is_up)
|
20
|
+
script = "#{dir}/#{is_up ? "up" : "down"}.#{@ext}"
|
21
|
+
Log.info("Executing #{script}...")
|
22
|
+
@db.exec_sql(File.read(script))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "colorize"
|
2
|
+
|
3
|
+
module Migrate
|
4
|
+
class Log
|
5
|
+
@@debug = true
|
6
|
+
|
7
|
+
def self.verbose(verbose)
|
8
|
+
@@debug = verbose
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.info(msg)
|
12
|
+
return if not @@debug
|
13
|
+
puts ("[INFO] " + msg).green
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.warn(msg)
|
17
|
+
return if not @@debug
|
18
|
+
puts ("[WARN] " + msg).yellow
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.error(msg, e=nil)
|
22
|
+
return if not @@debug
|
23
|
+
puts ("[ERRPR] " + msg + (e != nil ? " #{e.message}" : "")).red
|
24
|
+
|
25
|
+
if e != nil
|
26
|
+
puts e.backtrace
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.success(msg)
|
31
|
+
return if not @@debug
|
32
|
+
puts ("[SUCCESS] " + msg).blue
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.version(msg)
|
36
|
+
puts (" [VERSION] #{msg} ").colorize(:color => :white, :background => :blue)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Migrate
|
2
|
+
class Migrator
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
@db = config.get_db
|
6
|
+
@lang = config.get_lang
|
7
|
+
|
8
|
+
if @db == nil
|
9
|
+
throw "Database connection not found."
|
10
|
+
exit
|
11
|
+
end
|
12
|
+
|
13
|
+
if @lang == nil
|
14
|
+
throw "Language not found."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def init
|
19
|
+
@db.tx do
|
20
|
+
@db.create_tables
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def migration_dir(migration)
|
25
|
+
date = DateTime.parse(migration["created_date"].to_s)
|
26
|
+
"#{@config.root}/v#{migration["version"]}-#{date.strftime("%Y-%m-%d")}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def new(desc)
|
30
|
+
@db.tx do
|
31
|
+
Log.info("Creating new migration...")
|
32
|
+
|
33
|
+
migration = @db.new_migration(desc)
|
34
|
+
migration_dir = self.migration_dir(migration)
|
35
|
+
Dir.mkdir migration_dir
|
36
|
+
@lang.create_migration(migration_dir)
|
37
|
+
|
38
|
+
Log.success("Migration for version #{migration["version"]} created.")
|
39
|
+
migration_dir
|
40
|
+
end
|
41
|
+
rescue Exception => e
|
42
|
+
Log.error("Error while creating new migration.", e)
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
# will execute single migration by running up or down script
|
47
|
+
def exec_migration(migration, is_up)
|
48
|
+
migration_dir = self.migration_dir(migration)
|
49
|
+
result = @lang.exec_migration(migration_dir, is_up)
|
50
|
+
if @lang.ext != "sql"
|
51
|
+
puts result
|
52
|
+
end
|
53
|
+
|
54
|
+
Log.info("Updating current version number...")
|
55
|
+
version = migration["version"]
|
56
|
+
is_up ? @db.log_up(version) : @db.log_down(version)
|
57
|
+
end
|
58
|
+
|
59
|
+
# will execute range of migrations
|
60
|
+
def exec_migrations(is_up=true)
|
61
|
+
Log.info("Executing migrations...")
|
62
|
+
migrations = yield @db.current_version
|
63
|
+
|
64
|
+
if migrations.count == 0
|
65
|
+
Log.warn("Migrations not found")
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
migrations.each do |migration|
|
70
|
+
self.exec_migration(migration, is_up)
|
71
|
+
end
|
72
|
+
Log.success("Migrations executed. Current version: #{@db.current_version}")
|
73
|
+
end
|
74
|
+
|
75
|
+
def up(to_version=nil)
|
76
|
+
@db.tx do
|
77
|
+
self.exec_migrations do |last_version|
|
78
|
+
new_version = @db.next_version
|
79
|
+
if to_version == nil
|
80
|
+
to_version = new_version
|
81
|
+
end
|
82
|
+
|
83
|
+
@db.migrations_range(new_version, to_version, true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def down(to_version=nil)
|
89
|
+
@db.tx do
|
90
|
+
self.exec_migrations(false) do |current_version|
|
91
|
+
if current_version == 0
|
92
|
+
raise VersionNotFound
|
93
|
+
end
|
94
|
+
|
95
|
+
if to_version == nil
|
96
|
+
to_version = current_version
|
97
|
+
else
|
98
|
+
to_version = to_version.to_i + 1
|
99
|
+
end
|
100
|
+
|
101
|
+
@db.migrations_range(to_version, current_version, false)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def current_version
|
107
|
+
@db.tx do
|
108
|
+
return @db.current_version
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete(version)
|
113
|
+
@db.tx do
|
114
|
+
Log.info("Removing migration data...")
|
115
|
+
|
116
|
+
if @db.current_version.to_i == version
|
117
|
+
return Log.error("Cannot remove current version.")
|
118
|
+
end
|
119
|
+
|
120
|
+
dir = self.migration_dir(@db.get_migration(version))
|
121
|
+
@db.delete version
|
122
|
+
|
123
|
+
if Dir.exist? dir
|
124
|
+
File.delete "#{dir}/up.sql"
|
125
|
+
File.delete "#{dir}/down.sql"
|
126
|
+
Dir.rmdir dir
|
127
|
+
end
|
128
|
+
|
129
|
+
Log.success("Migration data removed.")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def list(select, limit)
|
134
|
+
@db.tx do
|
135
|
+
migrations = @db.list_migrations(select, limit)
|
136
|
+
if not migrations.any?
|
137
|
+
return Log.info("Migrations not found")
|
138
|
+
end
|
139
|
+
|
140
|
+
@db.print(migrations, "Migrations")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
|
3
|
+
module Migrate
|
4
|
+
module Storage
|
5
|
+
class DB
|
6
|
+
attr_reader :config
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def type
|
13
|
+
@config.storage
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_migration(description="")
|
17
|
+
self.exec_sql <<-eos
|
18
|
+
INSERT INTO #{@config.version_info} (description, created_date)
|
19
|
+
VALUES('#{description}', now())
|
20
|
+
eos
|
21
|
+
|
22
|
+
res = self.exec_sql <<-eos
|
23
|
+
SELECT * FROM #{@config.version_info} ORDER BY version DESC LIMIT 1
|
24
|
+
eos
|
25
|
+
res[0]
|
26
|
+
end
|
27
|
+
|
28
|
+
def list_migrations(selects, limit)
|
29
|
+
self.exec_sql <<-eos
|
30
|
+
SELECT #{(selects == nil ? "*" : selects)} FROM #{@config.version_info}
|
31
|
+
ORDER BY last_up, version
|
32
|
+
#{limit != nil ? "LIMIT #{limit}" : ""}
|
33
|
+
eos
|
34
|
+
end
|
35
|
+
|
36
|
+
def migrations_range(from, to, is_up)
|
37
|
+
self.exec_sql <<-eos
|
38
|
+
SELECT * FROM #{@config.version_info}
|
39
|
+
WHERE version >= #{from} AND version <= #{to}
|
40
|
+
ORDER BY version #{!is_up ? "DESC" : ""}
|
41
|
+
eos
|
42
|
+
end
|
43
|
+
|
44
|
+
def extract_version(results)
|
45
|
+
if results && results.count > 0
|
46
|
+
results[0]["version"]
|
47
|
+
else
|
48
|
+
raise VersionNotFound
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def lowest_version
|
53
|
+
self.extract_version self.exec_sql <<-eos
|
54
|
+
SELECT version FROM #{@config.version_info}
|
55
|
+
ORDER BY version
|
56
|
+
LIMIT 1
|
57
|
+
eos
|
58
|
+
end
|
59
|
+
|
60
|
+
def next_version
|
61
|
+
self.extract_version self.exec_sql <<-eos
|
62
|
+
SELECT version FROM #{@config.version_info}
|
63
|
+
WHERE version > (SELECT version FROM #{@config.version_number} LIMIT 1)
|
64
|
+
ORDER BY version
|
65
|
+
LIMIT 1
|
66
|
+
eos
|
67
|
+
end
|
68
|
+
|
69
|
+
def current_version
|
70
|
+
self.extract_version self.exec_sql <<-eos
|
71
|
+
SELECT * FROM #{config.version_number}
|
72
|
+
LIMIT 1
|
73
|
+
eos
|
74
|
+
end
|
75
|
+
|
76
|
+
def prev_version
|
77
|
+
self.extract_version self.exec_sql <<-eos
|
78
|
+
SELECT version FROM #{@config.version_info}
|
79
|
+
WHERE version < (SELECT version FROM #{@config.version_number} LIMIT 1)
|
80
|
+
ORDER BY version DESC
|
81
|
+
LIMIT 1
|
82
|
+
eos
|
83
|
+
end
|
84
|
+
|
85
|
+
def log_up(version)
|
86
|
+
self.exec_sql "UPDATE #{@config.version_info} SET last_up=now() WHERE version=#{version}"
|
87
|
+
self.exec_sql "UPDATE #{@config.version_number} SET version=#{version}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def log_down(version)
|
91
|
+
self.exec_sql "UPDATE #{@config.version_info} SET last_down=now() WHERE version=#{version}"
|
92
|
+
|
93
|
+
lowest_version = self.lowest_version
|
94
|
+
version_to_save = lowest_version.to_i < version.to_i ? self.prev_version().to_i : 0
|
95
|
+
self.exec_sql "UPDATE #{@config.version_number} SET version=#{version_to_save}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_migration(version)
|
99
|
+
res = self.exec_sql "SELECT * FROM #{@config.version_info} WHERE version=#{version}"
|
100
|
+
if res && res.count > 0
|
101
|
+
res[0]
|
102
|
+
else
|
103
|
+
raise VersionNotFound
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def delete(version)
|
108
|
+
self.exec_sql "DELETE FROM #{@config.version_info} WHERE version=#{version}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def print(results, title="")
|
112
|
+
rows = []
|
113
|
+
headings = results[0].keys
|
114
|
+
|
115
|
+
results.each do |result|
|
116
|
+
row = []
|
117
|
+
result.each do |column, value|
|
118
|
+
if column == "description"
|
119
|
+
if value.length > 70
|
120
|
+
value = value.scan(/.{1,70}/).join("\n")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
row << value
|
125
|
+
end
|
126
|
+
rows << row
|
127
|
+
end
|
128
|
+
|
129
|
+
table = Terminal::Table.new :headings => headings, :rows => rows
|
130
|
+
table.title = title
|
131
|
+
puts table
|
132
|
+
end
|
133
|
+
|
134
|
+
# Will create database model used by tool
|
135
|
+
def create_tables
|
136
|
+
raise "Implementation for creating tables not found"
|
137
|
+
end
|
138
|
+
|
139
|
+
# Executes SQL
|
140
|
+
def exec_sql(sql)
|
141
|
+
raise "Implementation for executing SQL script not found"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Creates new transaction. Should accept block.
|
145
|
+
def tx
|
146
|
+
raise "Implementation for starting new transaction not found"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|