mysql-inspector 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ current
2
+ target
3
+ pkg
data/README ADDED
@@ -0,0 +1,48 @@
1
+ mysql-inspector is a simple tool for diffing and searching mysql dumps.
2
+
3
+ Why do I need that?
4
+
5
+ It helps you migrate your schema from one version to another.
6
+
7
+ Ok, how?
8
+
9
+ Say you have a project called zippers. Your development database is called
10
+ zippers_development. You're working on a branch called smoother, based on
11
+ master, which adds a new column metal_grade.
12
+
13
+ Start by storing the new state of your database.
14
+
15
+ % mysql-inspector --target zippers_development --write
16
+
17
+ This command says "store the state of the zippers_development database as my
18
+ target version".
19
+
20
+ Now, go back to your master branch and load its database into
21
+ zippers_development, then dump the contents.
22
+
23
+ % mysql-inspector --current zippers_development --write
24
+
25
+ Now, in order to write database migrations for master to smoother let's see
26
+ what changes occurred.
27
+
28
+ % mysql-inspector --diff
29
+
30
+ [[ show sample output ]]
31
+
32
+ Here we see that our target version contains one column that the current
33
+ version does not. It's easy to write an alter statement, in fact most of the
34
+ information is right here.
35
+
36
+ mysql% alter table zippers add column metal_grade int(11) NOT NULL DEFAULT '0';
37
+
38
+ Now we can compare the two again. This time we need to add the --force argument
39
+ to say that it's ok to overwrite the previous dump.
40
+
41
+ % mysql-inspector --current zippers_development --write --force
42
+ % mysql-inspector --diff
43
+
44
+ No differences!
45
+
46
+ That was pretty simple. Is there more?
47
+
48
+ Sure.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "mysql-inspector"
7
+ gemspec.summary = "Tools for identifying changes to a MySQL schema"
8
+ gemspec.email = "ryan@fivesevensix.com"
9
+ gemspec.homepage = "http://github.com/rcarver/mysql-inspector"
10
+ gemspec.authors = ["Ryan Carver"]
11
+ end
12
+ Jeweler::GemcutterTasks.new
13
+ rescue LoadError
14
+ puts "Jeweler not available. Install it with: gem install jeweler"
15
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require "lib/mysql-inspector"
5
+
6
+ options = {
7
+ :databases => [],
8
+ :versions => [],
9
+ :base_dir => ".",
10
+ :diff => false,
11
+ :grep => false,
12
+ :dump => false,
13
+ :clean => false
14
+ }
15
+
16
+ OptionParser.new do |opts|
17
+ opts.banner = "Usage: ????"
18
+
19
+ opts.on("-c", "--current [db]") do |db_name|
20
+ options[:versions] << "current"
21
+ options[:databases] << db_name
22
+ end
23
+
24
+ opts.on("-t", "--target [db]") do |db_name|
25
+ options[:versions] << "target"
26
+ options[:databases] << db_name
27
+ end
28
+
29
+ opts.on("-w", "--write") do |yes|
30
+ options[:dump] = true
31
+ end
32
+
33
+ opts.on("-f", "--fresh") do |ok|
34
+ options[:clean] = true
35
+ end
36
+
37
+ opts.on("-g", "--grep foo_id,bar_id") do |columns|
38
+ options[:grep] = columns.split(",").collect { |c| c.strip }
39
+ end
40
+
41
+ opts.on("-d", "--diff") do |info|
42
+ options[:diff] = true
43
+ end
44
+
45
+ end.parse!
46
+
47
+ options[:versions].compact!
48
+ options[:databases].compact!
49
+
50
+ options[:versions].collect! do |version|
51
+ MysqlInspector::Dump.new(version, options[:base_dir])
52
+ end
53
+
54
+ if options[:clean]
55
+ options[:versions].each { |v| v.clean! }
56
+ end
57
+
58
+ if options[:dump]
59
+ raise "Missing database names" unless options[:versions].size == options[:databases].size
60
+ options[:versions].zip(options[:databases]).each do |v, d|
61
+ v.dump!(d)
62
+ end
63
+ end
64
+
65
+ if options[:grep]
66
+ options[:versions].each do |v|
67
+ grep = MysqlInspector::Grep.new(v)
68
+ grep.find(STDOUT, *options[:grep])
69
+ end
70
+ end
71
+
72
+ if options[:diff]
73
+ inputs = ["current", "target"].collect { |version| MysqlInspector::Dump.new(version, options[:base_dir]) }
74
+ comparison = MysqlInspector::Comparison.new(*inputs)
75
+ comparison.compare(STDOUT)
76
+ end
@@ -0,0 +1,210 @@
1
+ require "fileutils"
2
+
3
+ module MysqlInspector
4
+
5
+ module Config
6
+ extend self
7
+
8
+ def mysqldump(*args)
9
+ all_args = ["-u #{mysql_user}"] + args
10
+ Command.new(mysqldump_path, *all_args)
11
+ end
12
+
13
+ def mysql_user
14
+ @mysql_user ||= "root"
15
+ end
16
+
17
+ def mysqldump_path
18
+ @mysqldump_path ||= begin
19
+ path = `which mysqldump`.chomp
20
+ raise "mysqldump was not in your path" if path.empty?
21
+ path
22
+ end
23
+ end
24
+ end
25
+
26
+ class Command
27
+ def initialize(path, *args)
28
+ @path = path
29
+ @args = args
30
+ end
31
+ def to_s
32
+ "#{@path} #{@args * " "}"
33
+ end
34
+ def run!
35
+ system to_s
36
+ end
37
+ end
38
+
39
+ class Dump
40
+ def initialize(version, base_dir)
41
+ @version = version
42
+ @base_dir = base_dir
43
+ # TODO: sanity check base_dir for either a relative dir or /tmp/...
44
+ end
45
+
46
+ attr_reader :version, :base_dir
47
+
48
+ def db_name
49
+ @db_name ||= read_db_name
50
+ end
51
+
52
+ def dir
53
+ File.join(base_dir, version)
54
+ end
55
+
56
+ def clean!
57
+ FileUtils.rm_rf(dir)
58
+ end
59
+
60
+ def dump!(db_name)
61
+ raise "Destination exists! (#{dir.inspect})" if File.exist?(dir)
62
+ @db_name = db_name
63
+ FileUtils.mkdir_p(dir)
64
+ Config.mysqldump("--no-data", "-T #{dir}", "--skip-opt", db_name).run!
65
+ File.open(info_file, "w") { |f| f.puts(db_name) }
66
+ end
67
+
68
+ protected
69
+
70
+ def info_file
71
+ File.join(dir, ".info")
72
+ end
73
+
74
+ def read_db_name
75
+ raise "No dump exists at #{dir.inspect}" unless File.exist?(info_file)
76
+ File.read(info_file).strip
77
+ end
78
+ end
79
+
80
+ module Utils
81
+
82
+ def file_to_table(file)
83
+ file[/(.*)\.sql/, 1]
84
+ end
85
+
86
+ def sanitize_schema!(schema)
87
+ schema.collect! { |line| line.rstrip[/(.*?),?$/, 1] }
88
+ schema.delete_if { |line| line =~ /(\/\*|--|CREATE TABLE)/ or line == ");" or line.strip.empty? }
89
+ schema.sort!
90
+ schema
91
+ end
92
+ end
93
+
94
+ class Grep
95
+ include Utils
96
+
97
+ def initialize(dump)
98
+ @dump = dump
99
+ end
100
+
101
+ attr_reader :dump
102
+
103
+ def find(writer, *matchers)
104
+ writer.puts
105
+ writer.puts "Searching #{dump.version} (#{dump.db_name}) for #{matchers.inspect}"
106
+ writer.puts
107
+ files = Dir[File.join(dump.dir, "*.sql")].collect { |f| File.basename(f) }.sort
108
+ files.each do |f|
109
+ schema = File.read(File.join(dump.dir, f)).split("\n")
110
+ sanitize_schema!(schema)
111
+
112
+ matches = schema.select do |line|
113
+ matchers.all? do |matcher|
114
+ col, *items = matcher.split(/\s+/)
115
+ col = "`#{col}`"
116
+ [col, items].flatten.all? { |item| line.downcase =~ /#{Regexp.escape item.downcase}/ }
117
+ end
118
+ end
119
+
120
+ if matches.any?
121
+ writer.puts
122
+ writer.puts file_to_table(f)
123
+ writer.puts "*" * file_to_table(f).size
124
+ writer.puts
125
+ writer.puts "Found matching:"
126
+ writer.puts matches.join("\n")
127
+ writer.puts
128
+ writer.puts "Full schema:"
129
+ writer.puts schema.join("\n")
130
+ writer.puts
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ class Comparison
137
+ include Utils
138
+
139
+ def initialize(current, target)
140
+ @current = current
141
+ @target = target
142
+ end
143
+
144
+ attr_reader :current, :target
145
+
146
+ def ignore_files
147
+ ["migration_info.sql"]
148
+ end
149
+
150
+ def compare(writer=STDOUT)
151
+ writer.puts
152
+ writer.puts "Current: #{current.version} (#{current.db_name})"
153
+ writer.puts "Target: #{target.version} (#{target.db_name})"
154
+
155
+ current_files = Dir[File.join(current.dir, "*.sql")].collect { |f| File.basename(f) }.sort
156
+ target_files = Dir[File.join(target.dir, "*.sql")].collect { |f| File.basename(f) }.sort
157
+
158
+ # Ignore some tables
159
+ current_files -= ignore_files
160
+ target_files -= ignore_files
161
+
162
+ files_only_in_target = target_files - current_files
163
+ files_only_in_current = current_files - target_files
164
+ common_files = target_files & current_files
165
+
166
+ if files_only_in_current.any?
167
+ writer.puts
168
+ writer.puts "Tables only in current"
169
+ writer.puts files_only_in_current.collect { |f| file_to_table(f) }.join(", ")
170
+ end
171
+
172
+ if files_only_in_target.any?
173
+ writer.puts
174
+ writer.puts "Tables in target but not in current"
175
+ writer.puts files_only_in_target.collect { |f| file_to_table(f) }.join(", ")
176
+ end
177
+
178
+ common_files.each do |f|
179
+ current_schema = File.read(File.join(current.dir, f)).split("\n")
180
+ target_schema = File.read(File.join(target.dir, f)).split("\n")
181
+
182
+ sanitize_schema!(current_schema)
183
+ sanitize_schema!(target_schema)
184
+
185
+ next if current_schema == target_schema
186
+
187
+ writer.puts
188
+ writer.puts file_to_table(f)
189
+ writer.puts "*" * file_to_table(f).size
190
+ writer.puts
191
+
192
+ only_in_target = target_schema - current_schema
193
+ only_in_current = current_schema - target_schema
194
+
195
+ if only_in_current.any?
196
+ writer.puts "only in current"
197
+ writer.puts only_in_current.join("\n")
198
+ writer.puts
199
+ end
200
+ if only_in_target.any?
201
+ writer.puts "only in target"
202
+ writer.puts only_in_target.join("\n")
203
+ writer.puts
204
+ end
205
+ end
206
+ end
207
+
208
+ end
209
+
210
+ end
@@ -0,0 +1,44 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{mysql-inspector}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ryan Carver"]
12
+ s.date = %q{2010-03-03}
13
+ s.default_executable = %q{mysql-inspector}
14
+ s.email = %q{ryan@fivesevensix.com}
15
+ s.executables = ["mysql-inspector"]
16
+ s.extra_rdoc_files = [
17
+ "README"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "README",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "bin/mysql-inspector",
25
+ "lib/mysql-inspector.rb",
26
+ "mysql-inspector.gemspec"
27
+ ]
28
+ s.homepage = %q{http://github.com/rcarver/mysql-inspector}
29
+ s.rdoc_options = ["--charset=UTF-8"]
30
+ s.require_paths = ["lib"]
31
+ s.rubygems_version = %q{1.3.6}
32
+ s.summary = %q{Tools for identifying changes to a MySQL schema}
33
+
34
+ if s.respond_to? :specification_version then
35
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
36
+ s.specification_version = 3
37
+
38
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
39
+ else
40
+ end
41
+ else
42
+ end
43
+ end
44
+
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysql-inspector
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ version: 0.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Ryan Carver
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-03 00:00:00 -08:00
18
+ default_executable: mysql-inspector
19
+ dependencies: []
20
+
21
+ description:
22
+ email: ryan@fivesevensix.com
23
+ executables:
24
+ - mysql-inspector
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README
29
+ files:
30
+ - .gitignore
31
+ - README
32
+ - Rakefile
33
+ - VERSION
34
+ - bin/mysql-inspector
35
+ - lib/mysql-inspector.rb
36
+ - mysql-inspector.gemspec
37
+ has_rdoc: true
38
+ homepage: http://github.com/rcarver/mysql-inspector
39
+ licenses: []
40
+
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --charset=UTF-8
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ segments:
51
+ - 0
52
+ version: "0"
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.6
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Tools for identifying changes to a MySQL schema
67
+ test_files: []
68
+