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.
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