mysql-inspector 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +5 -3
  2. data/CHANGELOG.md +8 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE +20 -0
  5. data/README.md +82 -0
  6. data/Rakefile +36 -14
  7. data/bin/mysql-inspector +9 -85
  8. data/lib/mysql-inspector.rb +1 -226
  9. data/lib/mysql_inspector.rb +30 -0
  10. data/lib/mysql_inspector/access.rb +64 -0
  11. data/lib/mysql_inspector/ar/access.rb +55 -0
  12. data/lib/mysql_inspector/cli.rb +291 -0
  13. data/lib/mysql_inspector/column.rb +21 -0
  14. data/lib/mysql_inspector/config.rb +82 -0
  15. data/lib/mysql_inspector/constraint.rb +28 -0
  16. data/lib/mysql_inspector/diff.rb +82 -0
  17. data/lib/mysql_inspector/dump.rb +70 -0
  18. data/lib/mysql_inspector/grep.rb +65 -0
  19. data/lib/mysql_inspector/index.rb +25 -0
  20. data/lib/mysql_inspector/migrations.rb +37 -0
  21. data/lib/mysql_inspector/railtie.rb +17 -0
  22. data/lib/mysql_inspector/railties/databases.rake +92 -0
  23. data/lib/mysql_inspector/table.rb +147 -0
  24. data/lib/mysql_inspector/table_part.rb +21 -0
  25. data/lib/mysql_inspector/version.rb +3 -0
  26. data/mysql-inspector.gemspec +17 -36
  27. data/test/fixtures/migrate/111_create_users.rb +7 -0
  28. data/test/fixtures/migrate/222_create_things.rb +9 -0
  29. data/test/helper.rb +125 -0
  30. data/test/helper_ar.rb +37 -0
  31. data/test/helpers/mysql_schemas.rb +82 -0
  32. data/test/helpers/mysql_utils.rb +35 -0
  33. data/test/helpers/string_unindented.rb +13 -0
  34. data/test/mysql_inspector/cli_basics_test.rb +77 -0
  35. data/test/mysql_inspector/cli_diff_test.rb +60 -0
  36. data/test/mysql_inspector/cli_grep_test.rb +74 -0
  37. data/test/mysql_inspector/cli_load_test.rb +43 -0
  38. data/test/mysql_inspector/cli_write_test.rb +58 -0
  39. data/test/mysql_inspector/config_test.rb +14 -0
  40. data/test/mysql_inspector/diff_test.rb +82 -0
  41. data/test/mysql_inspector/dump_test.rb +81 -0
  42. data/test/mysql_inspector/grep_test.rb +61 -0
  43. data/test/mysql_inspector/table_test.rb +123 -0
  44. data/test/mysql_inspector_ar/ar_dump_test.rb +29 -0
  45. data/test/mysql_inspector_ar/ar_migrations_test.rb +47 -0
  46. metadata +123 -49
  47. data/README +0 -48
  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