mysql-inspector 0.0.6 → 0.1.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.
- data/.gitignore +5 -3
- data/CHANGELOG.md +8 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.md +82 -0
- data/Rakefile +36 -14
- data/bin/mysql-inspector +9 -85
- data/lib/mysql-inspector.rb +1 -226
- data/lib/mysql_inspector.rb +30 -0
- data/lib/mysql_inspector/access.rb +64 -0
- data/lib/mysql_inspector/ar/access.rb +55 -0
- data/lib/mysql_inspector/cli.rb +291 -0
- data/lib/mysql_inspector/column.rb +21 -0
- data/lib/mysql_inspector/config.rb +82 -0
- data/lib/mysql_inspector/constraint.rb +28 -0
- data/lib/mysql_inspector/diff.rb +82 -0
- data/lib/mysql_inspector/dump.rb +70 -0
- data/lib/mysql_inspector/grep.rb +65 -0
- data/lib/mysql_inspector/index.rb +25 -0
- data/lib/mysql_inspector/migrations.rb +37 -0
- data/lib/mysql_inspector/railtie.rb +17 -0
- data/lib/mysql_inspector/railties/databases.rake +92 -0
- data/lib/mysql_inspector/table.rb +147 -0
- data/lib/mysql_inspector/table_part.rb +21 -0
- data/lib/mysql_inspector/version.rb +3 -0
- data/mysql-inspector.gemspec +17 -36
- data/test/fixtures/migrate/111_create_users.rb +7 -0
- data/test/fixtures/migrate/222_create_things.rb +9 -0
- data/test/helper.rb +125 -0
- data/test/helper_ar.rb +37 -0
- data/test/helpers/mysql_schemas.rb +82 -0
- data/test/helpers/mysql_utils.rb +35 -0
- data/test/helpers/string_unindented.rb +13 -0
- data/test/mysql_inspector/cli_basics_test.rb +77 -0
- data/test/mysql_inspector/cli_diff_test.rb +60 -0
- data/test/mysql_inspector/cli_grep_test.rb +74 -0
- data/test/mysql_inspector/cli_load_test.rb +43 -0
- data/test/mysql_inspector/cli_write_test.rb +58 -0
- data/test/mysql_inspector/config_test.rb +14 -0
- data/test/mysql_inspector/diff_test.rb +82 -0
- data/test/mysql_inspector/dump_test.rb +81 -0
- data/test/mysql_inspector/grep_test.rb +61 -0
- data/test/mysql_inspector/table_test.rb +123 -0
- data/test/mysql_inspector_ar/ar_dump_test.rb +29 -0
- data/test/mysql_inspector_ar/ar_migrations_test.rb +47 -0
- metadata +123 -49
- data/README +0 -48
- data/VERSION +0 -1
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module MysqlUtils
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def create_mysql_database(database_name, schema)
|
8
|
+
drop_mysql_database(database_name)
|
9
|
+
syscall "echo 'CREATE DATABASE #{database_name}' | #{mysql_command}"
|
10
|
+
Tempfile.open('schema') do |file|
|
11
|
+
file.puts(schema)
|
12
|
+
file.flush
|
13
|
+
syscall "cat #{file.path} | #{mysql_command} #{database_name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def drop_mysql_database(database_name)
|
18
|
+
syscall "echo 'DROP DATABASE IF EXISTS #{database_name}' | #{mysql_command}"
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def mysql_command
|
24
|
+
@mysql_command ||= begin
|
25
|
+
path = `which mysql`.chomp
|
26
|
+
raise "mysql is not in your PATH" if path.empty?
|
27
|
+
"#{path} -uroot"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def syscall(command)
|
32
|
+
out, err, status = Open3.capture3(command)
|
33
|
+
raise "FAILED:\n\nstdout:\n#{out}\n\nstderr:\n#{err}" unless status.exitstatus == 0
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class String
|
2
|
+
# Strip left indentation from a string. Call this on a HEREDOC
|
3
|
+
# string to unindent it.
|
4
|
+
def unindented
|
5
|
+
lines = self.split("\n")
|
6
|
+
indent_level = (lines[0][/^(\s*)/, 1] || "").size
|
7
|
+
lines.map { |line|
|
8
|
+
line.sub(/^\s{#{indent_level}}/, '')
|
9
|
+
}.join("\n") + "\n"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe "mysql-inspector -v" do
|
4
|
+
|
5
|
+
subject { mysql_inspector "-v" }
|
6
|
+
|
7
|
+
it "shows the version" do
|
8
|
+
stderr.must_equal ""
|
9
|
+
stdout.must_equal MysqlInspector::VERSION
|
10
|
+
status.must_equal 0
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "mysql-inspector -h" do
|
15
|
+
|
16
|
+
subject { mysql_inspector "-h" }
|
17
|
+
|
18
|
+
it "shows the help" do
|
19
|
+
stderr.must_equal ""
|
20
|
+
stdout.must_equal <<-EOL.unindented
|
21
|
+
Usage: mysql-inspector [options] command [command args]
|
22
|
+
|
23
|
+
Options
|
24
|
+
|
25
|
+
--out DIR Where to store schemas. Defaults to '.'
|
26
|
+
--rails Configure for a Rails project
|
27
|
+
-h, --help What you're looking at
|
28
|
+
-v, --version Show the version of mysql-inspector
|
29
|
+
|
30
|
+
Commands
|
31
|
+
|
32
|
+
write DATABASE [VERSION]
|
33
|
+
load DATABASE [VERSION]
|
34
|
+
diff
|
35
|
+
diff TO
|
36
|
+
diff FROM TO
|
37
|
+
grep PATTERN [PATTERN]
|
38
|
+
|
39
|
+
EOL
|
40
|
+
status.must_equal 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "mysql-inspector an unknown command" do
|
45
|
+
|
46
|
+
subject { mysql_inspector "unknown_command" }
|
47
|
+
|
48
|
+
it "fails" do
|
49
|
+
stderr.must_equal "Unknown command \"unknown_command\""
|
50
|
+
stdout.must_equal ""
|
51
|
+
status.must_equal 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "mysql-inspector error cases" do
|
56
|
+
|
57
|
+
describe "when the database does not exist" do
|
58
|
+
subject { inspect_database "write #{database_name}" }
|
59
|
+
it "fails" do
|
60
|
+
stdout.must_equal ""
|
61
|
+
stderr.must_equal "The database #{database_name} does not exist"
|
62
|
+
status.must_equal 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "when the dump does not exist" do
|
67
|
+
subject { inspect_database "load #{database_name}" }
|
68
|
+
before do
|
69
|
+
create_mysql_database ""
|
70
|
+
end
|
71
|
+
it "fails" do
|
72
|
+
stdout.must_equal ""
|
73
|
+
stderr.must_equal "Dump \"current\" does not exist"
|
74
|
+
status.must_equal 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe "mysql-inspector diff" do
|
4
|
+
|
5
|
+
describe "parsing arguments" do
|
6
|
+
|
7
|
+
subject { parse_command(MysqlInspector::CLI::DiffCommand, args) }
|
8
|
+
let(:args) { [] }
|
9
|
+
|
10
|
+
specify "it compares current to target" do
|
11
|
+
subject.ivar(:version1).must_equal "current"
|
12
|
+
subject.ivar(:version2).must_equal "target"
|
13
|
+
end
|
14
|
+
|
15
|
+
specify "it compares current to something else" do
|
16
|
+
args << "other"
|
17
|
+
subject.ivar(:version1).must_equal "current"
|
18
|
+
subject.ivar(:version2).must_equal "other"
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "it compares two arbitrary versions" do
|
22
|
+
args << "other1"
|
23
|
+
args << "other2"
|
24
|
+
subject.ivar(:version1).must_equal "other1"
|
25
|
+
subject.ivar(:version2).must_equal "other2"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "running" do
|
30
|
+
|
31
|
+
subject { inspect_database "diff" }
|
32
|
+
|
33
|
+
before do
|
34
|
+
create_mysql_database schema_a
|
35
|
+
inspect_database "write #{database_name} current"
|
36
|
+
create_mysql_database schema_b
|
37
|
+
inspect_database "write #{database_name} target"
|
38
|
+
end
|
39
|
+
|
40
|
+
specify do
|
41
|
+
stderr.must_equal ""
|
42
|
+
stdout.must_equal <<-EOL.unindented
|
43
|
+
diff current target
|
44
|
+
|
45
|
+
- colors
|
46
|
+
= things
|
47
|
+
COL - `color` varchar(255) NOT NULL
|
48
|
+
+ `first_name` varchar(255) NOT NULL
|
49
|
+
+ `last_name` varchar(255) NOT NULL
|
50
|
+
IDX - KEY `color` (`color`)
|
51
|
+
+ KEY `name` (`first_name`,`last_name`)
|
52
|
+
CST - CONSTRAINT `belongs_to_color` FOREIGN KEY (`color`) REFERENCES `colors` (`name`) ON DELETE NO ACTION ON UPDATE CASCADE
|
53
|
+
+ CONSTRAINT `belongs_to_user` FOREIGN KEY (`first_name`,`last_name`) REFERENCES `users` (`first_name`,`last_name`) ON DELETE NO ACTION ON UPDATE CASCADE
|
54
|
+
+ users
|
55
|
+
|
56
|
+
EOL
|
57
|
+
status.must_equal 0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe "mysql-inspector grep" do
|
4
|
+
|
5
|
+
describe "parsing arguments" do
|
6
|
+
|
7
|
+
subject { parse_command(MysqlInspector::CLI::GrepCommand, args) }
|
8
|
+
let(:args) { [] }
|
9
|
+
|
10
|
+
specify "it searches current" do
|
11
|
+
args.concat ["a", "^b"]
|
12
|
+
subject.ivar(:version).must_equal "current"
|
13
|
+
subject.ivar(:matchers).must_equal [/a/, /^b/]
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "it searches another version" do
|
17
|
+
skip "not supported. how would the args work?"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "running" do
|
22
|
+
before do
|
23
|
+
create_mysql_database [users_schema, things_schema] * ";"
|
24
|
+
inspect_database "write #{database_name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
subject { inspect_database "grep #{matchers * ' '}" }
|
28
|
+
let(:matchers) { [] }
|
29
|
+
|
30
|
+
specify "searching for a single term" do
|
31
|
+
matchers << "name"
|
32
|
+
|
33
|
+
stderr.must_equal ""
|
34
|
+
stdout.must_equal <<-EOL.unindented
|
35
|
+
grep /name/
|
36
|
+
|
37
|
+
things
|
38
|
+
COL `first_name` varchar(255) NOT NULL
|
39
|
+
`last_name` varchar(255) NOT NULL
|
40
|
+
`name` varchar(255) NOT NULL DEFAULT 'toy'
|
41
|
+
IDX KEY `name` (`first_name`,`last_name`)
|
42
|
+
CST CONSTRAINT `belongs_to_user` FOREIGN KEY (`first_name`,`last_name`) REFERENCES `users` (`first_name`,`last_name`) ON DELETE NO ACTION ON UPDATE CASCADE
|
43
|
+
|
44
|
+
users
|
45
|
+
COL `first_name` varchar(255) NOT NULL
|
46
|
+
`last_name` varchar(255) NOT NULL
|
47
|
+
IDX KEY `name` (`first_name`,`last_name`)
|
48
|
+
|
49
|
+
EOL
|
50
|
+
status.must_equal 0
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "searching for multiple terms" do
|
54
|
+
matchers << "name"
|
55
|
+
matchers << "first"
|
56
|
+
|
57
|
+
stderr.must_equal ""
|
58
|
+
stdout.must_equal <<-EOL.unindented
|
59
|
+
grep /name/ AND /first/
|
60
|
+
|
61
|
+
things
|
62
|
+
COL `first_name` varchar(255) NOT NULL
|
63
|
+
IDX KEY `name` (`first_name`,`last_name`)
|
64
|
+
CST CONSTRAINT `belongs_to_user` FOREIGN KEY (`first_name`,`last_name`) REFERENCES `users` (`first_name`,`last_name`) ON DELETE NO ACTION ON UPDATE CASCADE
|
65
|
+
|
66
|
+
users
|
67
|
+
COL `first_name` varchar(255) NOT NULL
|
68
|
+
IDX KEY `name` (`first_name`,`last_name`)
|
69
|
+
|
70
|
+
EOL
|
71
|
+
status.must_equal 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
describe "mysql-inspector load" do
|
2
|
+
|
3
|
+
describe "parsing arguments" do
|
4
|
+
|
5
|
+
subject { parse_command(MysqlInspector::CLI::LoadCommand, args) }
|
6
|
+
let(:args) { [] }
|
7
|
+
|
8
|
+
specify "it fails when you don't specify a database" do
|
9
|
+
stderr.must_equal "Usage: mysql-inspector load DATABASE [VERSION]"
|
10
|
+
stdout.must_equal ""
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "it loads from current" do
|
14
|
+
args.concat ["my_database"]
|
15
|
+
subject.ivar(:database).must_equal "my_database"
|
16
|
+
subject.ivar(:version).must_equal "current"
|
17
|
+
end
|
18
|
+
|
19
|
+
specify "it loads another version" do
|
20
|
+
args.concat ["my_database", "other"]
|
21
|
+
subject.ivar(:database).must_equal "my_database"
|
22
|
+
subject.ivar(:version).must_equal "other"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "running" do
|
27
|
+
subject { inspect_database "load #{database_name}" }
|
28
|
+
specify do
|
29
|
+
create_mysql_database schema_b
|
30
|
+
inspect_database "write #{database_name}"
|
31
|
+
create_mysql_database ideas_schema
|
32
|
+
access.table_names.size.must_equal 1
|
33
|
+
|
34
|
+
it "outputs nothing"
|
35
|
+
stdout.must_equal ""
|
36
|
+
stderr.must_equal ""
|
37
|
+
status.must_equal 0
|
38
|
+
|
39
|
+
it "creates all tables"
|
40
|
+
access.table_names.size.must_equal 3
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe "mysql-inspector write" do
|
4
|
+
|
5
|
+
describe "parsing arguments" do
|
6
|
+
|
7
|
+
subject { parse_command(MysqlInspector::CLI::WriteCommand, args) }
|
8
|
+
let(:args) { [] }
|
9
|
+
|
10
|
+
specify "it fails when you don't specify a database" do
|
11
|
+
stderr.must_equal "Usage: mysql-inspector write DATABASE [VERSION]"
|
12
|
+
stdout.must_equal ""
|
13
|
+
end
|
14
|
+
|
15
|
+
specify "it writes to current" do
|
16
|
+
args.concat ["my_database"]
|
17
|
+
subject.ivar(:database).must_equal "my_database"
|
18
|
+
subject.ivar(:version).must_equal "current"
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "it writes to another version" do
|
22
|
+
args.concat ["my_database", "other"]
|
23
|
+
subject.ivar(:database).must_equal "my_database"
|
24
|
+
subject.ivar(:version).must_equal "other"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "running" do
|
29
|
+
|
30
|
+
subject { run_command(MysqlInspector::CLI::WriteCommand, args) }
|
31
|
+
let(:args) { [database_name] }
|
32
|
+
|
33
|
+
let(:dirname) { "#{tmpdir}/current" }
|
34
|
+
|
35
|
+
specify "when the database does not exist" do
|
36
|
+
it "fails"
|
37
|
+
stdout.must_equal ""
|
38
|
+
stderr.must_equal "The database mysql_inspector_test does not exist"
|
39
|
+
status.must_equal 1
|
40
|
+
|
41
|
+
it "does not create a directory"
|
42
|
+
File.directory?(dirname).must_equal false
|
43
|
+
end
|
44
|
+
|
45
|
+
specify "when the database exists" do
|
46
|
+
create_mysql_database schema_a
|
47
|
+
|
48
|
+
it "succeeds"
|
49
|
+
stdout.must_equal ""
|
50
|
+
stderr.must_equal ""
|
51
|
+
status.must_equal 0
|
52
|
+
|
53
|
+
it "creates a directory and files"
|
54
|
+
File.directory?(dirname).must_equal true
|
55
|
+
Dir.glob(dirname + "/*.table").size.must_equal 3
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe MysqlInspector::Config do
|
4
|
+
|
5
|
+
it "uses Access" do
|
6
|
+
config.database_name = "test"
|
7
|
+
config.access.must_be_instance_of MysqlInspector::Access
|
8
|
+
end
|
9
|
+
|
10
|
+
it "uses Dump" do
|
11
|
+
config.create_dump("test").must_be_instance_of MysqlInspector::Dump
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe MysqlInspector::Diff do
|
4
|
+
|
5
|
+
let(:current_dump) do
|
6
|
+
mock = MiniTest::Mock.new
|
7
|
+
mock.expect :tables, [
|
8
|
+
MysqlInspector::Table.new(ideas_schema),
|
9
|
+
MysqlInspector::Table.new(colors_schema),
|
10
|
+
MysqlInspector::Table.new(things_schema_1)
|
11
|
+
]
|
12
|
+
mock
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:target_dump) do
|
16
|
+
mock = MiniTest::Mock.new
|
17
|
+
mock.expect :tables, [
|
18
|
+
MysqlInspector::Table.new(users_schema),
|
19
|
+
MysqlInspector::Table.new(ideas_schema),
|
20
|
+
MysqlInspector::Table.new(things_schema_2)
|
21
|
+
]
|
22
|
+
mock
|
23
|
+
end
|
24
|
+
|
25
|
+
subject do
|
26
|
+
MysqlInspector::Diff.new(current_dump, target_dump)
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
subject.execute
|
31
|
+
end
|
32
|
+
|
33
|
+
def table_names(tables)
|
34
|
+
tables.map { |t| t.table_name }
|
35
|
+
end
|
36
|
+
|
37
|
+
def names(items)
|
38
|
+
items.map { |t| t.name }
|
39
|
+
end
|
40
|
+
|
41
|
+
specify "the types of tables" do
|
42
|
+
|
43
|
+
it "finds tables that were added"
|
44
|
+
table_names(subject.added_tables).must_equal ["users"]
|
45
|
+
|
46
|
+
it "finds tables that are missing"
|
47
|
+
table_names(subject.missing_tables).must_equal ["colors"]
|
48
|
+
|
49
|
+
it "finds tables that are equal"
|
50
|
+
table_names(subject.equal_tables).must_equal ["ideas"]
|
51
|
+
|
52
|
+
it "finds tables that differ"
|
53
|
+
table_names(subject.different_tables).must_equal ["things"]
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "a table that differs" do
|
57
|
+
|
58
|
+
let(:table) { subject.different_tables.first }
|
59
|
+
|
60
|
+
specify "the parts of the table" do
|
61
|
+
|
62
|
+
it "finds the columns that are added"
|
63
|
+
names(table.added_columns).must_equal ["first_name", "last_name"]
|
64
|
+
|
65
|
+
it "finds the columns that are missing"
|
66
|
+
names(table.missing_columns).must_equal ["color"]
|
67
|
+
|
68
|
+
it "finds the indices that are added"
|
69
|
+
names(table.added_indices).must_equal ["name"]
|
70
|
+
|
71
|
+
it "finds the indices that are missing"
|
72
|
+
names(table.missing_indices).must_equal ["color"]
|
73
|
+
|
74
|
+
it "finds the constraints that are added"
|
75
|
+
names(table.added_constraints).must_equal ["belongs_to_user"]
|
76
|
+
|
77
|
+
it "finds the constraints that are missing"
|
78
|
+
names(table.missing_constraints).must_equal ["belongs_to_color"]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|