db-migrate 0.0.1
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/.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,77 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
|
3
|
+
module Migrate
|
4
|
+
module Storage
|
5
|
+
class Mysql < DB
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
@conn = Mysql2::Client.new(
|
9
|
+
:database => @config.database,
|
10
|
+
:host => @config.host,
|
11
|
+
:port => @config.port,
|
12
|
+
:username => @config.user,
|
13
|
+
:password => @config.password,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_tables
|
18
|
+
Log.info("Creating version table")
|
19
|
+
self.exec_sql <<-eos
|
20
|
+
CREATE TABLE #{@config.version_info}
|
21
|
+
(
|
22
|
+
version INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
23
|
+
description TEXT,
|
24
|
+
created_date TIMESTAMP NOT NULL,
|
25
|
+
last_up TIMESTAMP,
|
26
|
+
last_down TIMESTAMP
|
27
|
+
);
|
28
|
+
eos
|
29
|
+
|
30
|
+
self.exec_sql <<-eos
|
31
|
+
CREATE TABLE #{@config.version_number} (
|
32
|
+
version int(11) not null,
|
33
|
+
PRIMARY KEY (version)
|
34
|
+
);
|
35
|
+
eos
|
36
|
+
|
37
|
+
self.exec_sql <<-eos
|
38
|
+
INSERT INTO #{@config.version_number} VALUES(0);
|
39
|
+
eos
|
40
|
+
Log.success("Version table created")
|
41
|
+
end
|
42
|
+
|
43
|
+
def exec_sql(sql)
|
44
|
+
results = []
|
45
|
+
result = @tx.query sql
|
46
|
+
return [] if result == nil
|
47
|
+
|
48
|
+
result.each do |row|
|
49
|
+
results << row
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def has_tx
|
54
|
+
@tx != nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def tx
|
58
|
+
if has_tx
|
59
|
+
yield
|
60
|
+
else
|
61
|
+
begin
|
62
|
+
@conn.query "BEGIN;"
|
63
|
+
@tx = @conn
|
64
|
+
yield
|
65
|
+
@conn.query "COMMIT;"
|
66
|
+
rescue Exception => e
|
67
|
+
@conn.query "ROLLBACK;"
|
68
|
+
raise e
|
69
|
+
ensure
|
70
|
+
@tx = nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "pg"
|
2
|
+
require "pry"
|
3
|
+
|
4
|
+
module Migrate
|
5
|
+
module Storage
|
6
|
+
class Postgres < DB
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
@conn = PG.connect({
|
10
|
+
dbname: @config.database,
|
11
|
+
host: @config.host,
|
12
|
+
user: @config.user,
|
13
|
+
password: @config.password,
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_tables
|
18
|
+
Log.info("Creating version table")
|
19
|
+
self.exec_sql <<-eos
|
20
|
+
CREATE TABLE #{@config.version_info}
|
21
|
+
(
|
22
|
+
version SERIAL PRIMARY KEY NOT NULL,
|
23
|
+
description TEXT,
|
24
|
+
created_date TIMESTAMP WITH TIME ZONE NOT NULL,
|
25
|
+
last_up TIMESTAMP WITH TIME ZONE,
|
26
|
+
last_down TIMESTAMP WITH TIME ZONE
|
27
|
+
);
|
28
|
+
CREATE UNIQUE INDEX #{@config.version_info}_version_uindex ON #{@config.version_info} (version);
|
29
|
+
|
30
|
+
CREATE TABLE #{@config.version_number}
|
31
|
+
(
|
32
|
+
version INT PRIMARY KEY NOT NULL
|
33
|
+
);
|
34
|
+
|
35
|
+
INSERT INTO #{@config.version_number} VALUES(0);
|
36
|
+
eos
|
37
|
+
Log.success("Version table created")
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def extract_version(results)
|
42
|
+
if results && results.count > 0
|
43
|
+
results[0]["version"]
|
44
|
+
else
|
45
|
+
raise VersionNotFound
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def exec_sql(sql)
|
50
|
+
@tx.exec sql
|
51
|
+
end
|
52
|
+
|
53
|
+
def has_tx
|
54
|
+
@tx != nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def tx
|
58
|
+
if has_tx
|
59
|
+
yield
|
60
|
+
else
|
61
|
+
begin
|
62
|
+
@conn.transaction do |tx|
|
63
|
+
@tx = tx
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
ensure
|
67
|
+
@tx = nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/migrate.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'db-migrate'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.licenses = ['MIT']
|
5
|
+
s.summary = "Tool for managing and executing your database migrations."
|
6
|
+
s.description = "#{s.summary} It supports multiple databases and multiple languages for writing migration scripts."
|
7
|
+
s.authors = ["Ivan Pusic"]
|
8
|
+
s.email = 'pusic007@gmail.com'
|
9
|
+
s.files = `git ls-files -z`.split("\x0")
|
10
|
+
s.homepage = 'https://github.com/ivpusic/migrate'
|
11
|
+
s.executables << 'migrate'
|
12
|
+
|
13
|
+
# deps
|
14
|
+
s.add_runtime_dependency 'thor', ['0.19.1']
|
15
|
+
s.add_runtime_dependency 'highline', ['1.7.8']
|
16
|
+
s.add_runtime_dependency 'json', ['1.8.3']
|
17
|
+
s.add_runtime_dependency 'mysql2', ['0.4.2']
|
18
|
+
s.add_runtime_dependency 'pg', ['0.18.4']
|
19
|
+
s.add_runtime_dependency 'parseconfig', ['1.0.6']
|
20
|
+
s.add_runtime_dependency 'colorize', ['0.7.7']
|
21
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
describe "Config" do
|
2
|
+
let(:fixtures) { "spec/lib/fixtures" }
|
3
|
+
let(:config) { Config.new(fixtures, "example.config") }
|
4
|
+
let(:config_hash) {
|
5
|
+
{
|
6
|
+
host: "localhost",
|
7
|
+
port: ("5432").to_i,
|
8
|
+
database: "mydb",
|
9
|
+
user: "postgres",
|
10
|
+
password: "password",
|
11
|
+
version_info: "version_info",
|
12
|
+
version_number: "version_number"
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
it "should create new instance" do
|
17
|
+
expect(config).not_to eq(nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should save root" do
|
21
|
+
expect(config.root).to eq("spec/lib/fixtures")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not find file" do
|
25
|
+
config = Config.new(".", "custom_file.conf")
|
26
|
+
expect(config.exists?).to eq(false)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should find file" do
|
30
|
+
expect(config.exists?).to eq(true)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should create config file" do
|
34
|
+
config_path = "#{fixtures}/test.config"
|
35
|
+
begin
|
36
|
+
expect(File.exist? config_path).to be false
|
37
|
+
|
38
|
+
config = Config.new(fixtures, "test.config")
|
39
|
+
config.init(config_hash)
|
40
|
+
expect(File.exist? config_path). to be true
|
41
|
+
ensure
|
42
|
+
if File.exist? config_path
|
43
|
+
File.delete config_path
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should load configuration" do
|
49
|
+
config.init(config_hash)
|
50
|
+
config.load!
|
51
|
+
expect(config.host).to eq("localhost")
|
52
|
+
expect(config.port).to eq("5432")
|
53
|
+
expect(config.database).to eq("mydb")
|
54
|
+
expect(config.user).to eq("postgres")
|
55
|
+
expect(config.password).to eq("password")
|
56
|
+
expect(config.version_info).to eq("version_info")
|
57
|
+
expect(config.version_number).to eq("version_number")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should remove configuration" do
|
61
|
+
config_path = "#{fixtures}/test.config"
|
62
|
+
begin
|
63
|
+
expect(File.exist? config_path).to be false
|
64
|
+
|
65
|
+
config = Config.new(fixtures, "test.config")
|
66
|
+
config.init(config_hash)
|
67
|
+
expect(File.exist? config_path).to be true
|
68
|
+
config.delete
|
69
|
+
expect(File.exist? config_path).to be false
|
70
|
+
ensure
|
71
|
+
if File.exist? config_path
|
72
|
+
File.delete config_path
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
[{type: "pg", cls: Storage::Postgres, conf: $pg_config_hash},
|
78
|
+
{type: "mysql", cls: Storage::Mysql, conf: $mysql_config_hash}].each do |storage|
|
79
|
+
context storage[:type] do
|
80
|
+
it "should be able to get database instance" do
|
81
|
+
config.init(storage[:conf])
|
82
|
+
config.load!
|
83
|
+
expect(config.get_db).to be_kind_of(storage[:cls])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
[{type: "go", cls: Lang::Go}, {type: "sql", cls: Lang::Sql},
|
89
|
+
{type: "ruby", cls: Lang::Ruby}, {type: "javascript", cls: Lang::Javascript},
|
90
|
+
{type: "python", cls: Lang::Python}].each do |lang|
|
91
|
+
context lang[:type] do
|
92
|
+
it "should be able to get language instance" do
|
93
|
+
config_hash = $pg_config_hash.merge({
|
94
|
+
lang: lang[:type]
|
95
|
+
})
|
96
|
+
|
97
|
+
config.init(config_hash)
|
98
|
+
config.load!
|
99
|
+
expect(config.get_lang).to be_kind_of(lang[:cls])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
console.log("works");
|
@@ -0,0 +1 @@
|
|
1
|
+
console.log("works");
|
@@ -0,0 +1 @@
|
|
1
|
+
print "works"
|
@@ -0,0 +1 @@
|
|
1
|
+
print "works"
|
@@ -0,0 +1 @@
|
|
1
|
+
puts "works"
|
@@ -0,0 +1 @@
|
|
1
|
+
puts "works"
|
@@ -0,0 +1 @@
|
|
1
|
+
-- todo;
|
@@ -0,0 +1 @@
|
|
1
|
+
-- todo;
|
@@ -0,0 +1,46 @@
|
|
1
|
+
describe "Lang" do
|
2
|
+
langs = [
|
3
|
+
Lang::Go.new,
|
4
|
+
Lang::Javascript.new,
|
5
|
+
Lang::Ruby.new,
|
6
|
+
Lang::Sql.new($dbs[0]),
|
7
|
+
Lang::Python.new
|
8
|
+
]
|
9
|
+
|
10
|
+
langs.each do |lang|
|
11
|
+
dir = "spec/lib/fixtures/v01"
|
12
|
+
|
13
|
+
after(:each) do
|
14
|
+
if Dir.exist? dir
|
15
|
+
Dir.glob("#{dir}/{up.*,down.*}").each do |file|
|
16
|
+
File.delete(file)
|
17
|
+
end
|
18
|
+
Dir.rmdir dir
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "#{lang.ext} should be able to create new migration" do
|
23
|
+
Dir.mkdir dir
|
24
|
+
lang.create_migration(dir)
|
25
|
+
|
26
|
+
up = "#{dir}/up.#{lang.ext}"
|
27
|
+
down = "#{dir}/down.#{lang.ext}"
|
28
|
+
|
29
|
+
expect(File.exist? up).to be true
|
30
|
+
expect(File.exist? down).to be true
|
31
|
+
end
|
32
|
+
|
33
|
+
context "#{lang.ext} when migrating up" do
|
34
|
+
[true, false].each do |is_up|
|
35
|
+
it "should be able to execute migration script" do
|
36
|
+
lang_dir = "spec/lib/fixtures/#{lang.ext}"
|
37
|
+
|
38
|
+
if lang.ext != "sql"
|
39
|
+
out = lang.exec_migration(lang_dir, is_up)
|
40
|
+
expect(out.strip).to eq("works")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
describe "Migrator" do
|
2
|
+
$dbs.each do |db|
|
3
|
+
config = db.config
|
4
|
+
migrator = Migrator.new(config)
|
5
|
+
|
6
|
+
around(:each) do |test|
|
7
|
+
db.tx do
|
8
|
+
test.run
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context db.type do
|
13
|
+
it "should be able to initialize" do
|
14
|
+
expect{Migrator.new(config)}.not_to raise_error(Exception)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should make new migration" do
|
18
|
+
migration_dir = nil
|
19
|
+
|
20
|
+
begin
|
21
|
+
result = db.exec_sql("SELECT * FROM #{config.version_info} ORDER BY version DESC")
|
22
|
+
version = db.extract_version result
|
23
|
+
|
24
|
+
migration_dir = migrator.new("description")
|
25
|
+
|
26
|
+
result = db.exec_sql("SELECT * FROM #{config.version_info} ORDER BY version DESC")
|
27
|
+
new_version = db.extract_version result
|
28
|
+
expect(new_version.to_i).to eq(version.to_i + 1)
|
29
|
+
expect(Dir.exist? migration_dir).to be true
|
30
|
+
ensure
|
31
|
+
if migration_dir != nil
|
32
|
+
Dir.glob("#{migration_dir}/{up.*,down.*}").each do |file|
|
33
|
+
File.delete file
|
34
|
+
end
|
35
|
+
|
36
|
+
Dir.rmdir migration_dir
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
create_migration_dir = lambda do |version|
|
42
|
+
migration = db.get_migration(version)
|
43
|
+
migration_dir = migrator.migration_dir(migration)
|
44
|
+
|
45
|
+
if not Dir.exist? migration_dir
|
46
|
+
Dir.mkdir migration_dir
|
47
|
+
config.get_lang().create_migration(migration_dir)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should execute one up migration" do
|
52
|
+
current = db.current_version().to_i
|
53
|
+
create_migration_dir.call(current + 1)
|
54
|
+
migrator.up
|
55
|
+
|
56
|
+
expect(current.to_i + 1).to eq(db.current_version().to_i)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should execute multiple up migration" do
|
60
|
+
current = db.current_version().to_i
|
61
|
+
create_migration_dir.call(current + 1)
|
62
|
+
create_migration_dir.call(current + 2)
|
63
|
+
|
64
|
+
migrator.up(current + 2)
|
65
|
+
expect(current.to_i + 2).to eq(db.current_version().to_i)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should execute one down migration" do
|
69
|
+
current = db.current_version().to_i
|
70
|
+
create_migration_dir.call(current)
|
71
|
+
|
72
|
+
migrator.down
|
73
|
+
expect(current.to_i - 1).to eq(db.current_version().to_i)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should execute multiple up migration" do
|
77
|
+
current = db.current_version().to_i
|
78
|
+
create_migration_dir.call(current)
|
79
|
+
create_migration_dir.call(current - 1)
|
80
|
+
|
81
|
+
migrator.down(current - 2)
|
82
|
+
expect(current.to_i - 2).to eq(db.current_version().to_i)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should get current version" do
|
86
|
+
current = db.current_version().to_i
|
87
|
+
expect(migrator.current_version().to_i).to eq(current)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should delete one version" do
|
91
|
+
delete = db.current_version().to_i + 1
|
92
|
+
migrator.delete(delete)
|
93
|
+
expect{db.get_migration(delete)}.to raise_error(VersionNotFound)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should not delete current version" do
|
97
|
+
current = db.current_version().to_i
|
98
|
+
migrator.delete(current)
|
99
|
+
expect(db.get_migration(current)["version"].to_i).to eq(current)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|