squealer 1.2.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +5 -2
- data/README.md +14 -0
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/bin/skewer +165 -0
- data/lib/.example_squeal.rb.swp +0 -0
- data/lib/example_squeal.rb +13 -5
- data/lib/squealer/database.rb +47 -1
- data/lib/squealer/progress_bar.rb +112 -0
- data/lib/squealer/target.rb +30 -8
- data/lib/squealer.rb +1 -0
- data/spec/spec_helper.rb +106 -0
- data/spec/squealer/database_spec.rb +116 -24
- data/spec/squealer/progress_bar_spec.rb +191 -0
- data/spec/squealer/target_spec.rb +29 -5
- data/squealer.gemspec +15 -5
- metadata +28 -16
data/.rvmrc
CHANGED
data/README.md
CHANGED
@@ -10,6 +10,20 @@ To run standalone, simply make your data squeal thusly:
|
|
10
10
|
where the squeal script includes a `require 'squealer'`.
|
11
11
|
|
12
12
|
## Release Notes
|
13
|
+
### v2.1
|
14
|
+
* Ruby 1.8.6 back-compatibility added. Using `eval "", binding, __FILE__, __LINE__` instead of `binding.eval`
|
15
|
+
* Target SQL script using backtick-quoted (mySQL) identifiers to avoid column-name / keyword conflict
|
16
|
+
* Automatically typecast Ruby `Boolean` (to integer), `Symbol` (to string), `Array` (to comma-seperated string)
|
17
|
+
* Improved handling and reporting of Target SQL errors
|
18
|
+
* Schaefer's Special "skewer" Script to reflect on Mongoid models and generate an initial squeal script and SQL schema DDL script. This tool is intended to build the _initial_ scripts only. It is extremely useful to get you going, but do think about the needs of the consumer of the export database, and adjust the scripts to suit. [How do you make something squeal? You skewer it!]
|
19
|
+
|
20
|
+
### v2.0
|
21
|
+
* `Object#import` now wraps a MongoDB cursor to provide counters and timings. Only `each` is supported for now, however `source` takes optional conditions.
|
22
|
+
* Progress bar and summary.
|
23
|
+
|
24
|
+
### v1.2.1
|
25
|
+
* `Object#import` syntax has changed. Now `import.source(collection).each` rather than `import.collection(collection).find({}).each`. `source` returns a MongoDB cursor like `find` does. See lib/example_squeal.rb for options.
|
26
|
+
|
13
27
|
### v1.2
|
14
28
|
* `Object#target` verifies there is a variable in scope with the same name as the `table_name` being targetted, it must be a `Hash` and must have an `_id` key
|
15
29
|
* Block to `Object#assign` not required, infers value from source scope
|
data/Rakefile
CHANGED
@@ -13,9 +13,14 @@ begin
|
|
13
13
|
gemspec.description = "Exports mongodb to mysql. More later."
|
14
14
|
gemspec.email = "joshua.graham@grahamis.com"
|
15
15
|
gemspec.homepage = "http://github.com/delitescere/squealer/"
|
16
|
-
gemspec.authors = ["Josh Graham", "Durran Jordan"]
|
16
|
+
gemspec.authors = ["Josh Graham", "Durran Jordan", "Matt Yoho", "Bernerd Schaefer"]
|
17
|
+
|
18
|
+
gemspec.default_executable = "skewer"
|
19
|
+
gemspec.executables = ["skewer"]
|
20
|
+
|
17
21
|
gemspec.add_dependency('mysql', '>= 2.8.1')
|
18
22
|
gemspec.add_dependency('mongo', '>= 0.18.3')
|
23
|
+
gemspec.add_dependency('bson_ext', '>= 1.0.1')
|
19
24
|
end
|
20
25
|
Jeweler::GemcutterTasks.new
|
21
26
|
rescue LoadError
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
2.1.0
|
data/bin/skewer
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
model = ARGV[0]
|
4
|
+
unless model
|
5
|
+
$stderr.puts "usage: skewer ModelName"
|
6
|
+
exit 1
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'config/boot'
|
10
|
+
require 'config/environment'
|
11
|
+
|
12
|
+
model = Object.const_get(model)
|
13
|
+
|
14
|
+
unless defined?(Mongoid) and Mongoid::Document > model
|
15
|
+
$stderr.puts "#{model} must be a Mongoid::Document"
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
write_schema = true
|
20
|
+
write_squeal = true
|
21
|
+
|
22
|
+
schema_filename = "#{model.name.underscore}_schema.sql"
|
23
|
+
squeal_filename = "#{model.name.underscore}_squeal.rb"
|
24
|
+
|
25
|
+
schema_exists = File.exists?(schema_filename)
|
26
|
+
squeal_exists = File.exists?(squeal_filename)
|
27
|
+
|
28
|
+
if schema_exists || squeal_exists
|
29
|
+
$stdout.print "#{schema_filename} already exists, overwrite? [Y/n] "
|
30
|
+
write_schema = $stdin.gets.chomp != "n"
|
31
|
+
|
32
|
+
$stdout.print "#{squeal_filename} already exists, overwrite? [Y/n] "
|
33
|
+
write_squeal = $stdin.gets.chomp != "n"
|
34
|
+
end
|
35
|
+
|
36
|
+
fields = model.fields.values.sort_by(&:name)
|
37
|
+
associations = model.associations
|
38
|
+
|
39
|
+
# SQL #
|
40
|
+
def create_table(model, parent = nil)
|
41
|
+
fields = model.fields.values.sort_by(&:name)
|
42
|
+
columns = []
|
43
|
+
columns << "`#{parent.name.underscore}_id` CHAR(24)" if parent
|
44
|
+
|
45
|
+
fields.each do |field|
|
46
|
+
mysql_type = case field.type.name
|
47
|
+
when "Boolean"
|
48
|
+
"BOOLEAN"
|
49
|
+
when "Time"
|
50
|
+
"TIMESTAMP NULL DEFAULT NULL"
|
51
|
+
when "Date"
|
52
|
+
"DATE"
|
53
|
+
when "Float"
|
54
|
+
"FLOAT"
|
55
|
+
when "Integer"
|
56
|
+
"INT"
|
57
|
+
else
|
58
|
+
"TEXT"
|
59
|
+
end
|
60
|
+
columns << "`#{field.name[0..63]}` #{mysql_type}"
|
61
|
+
end
|
62
|
+
|
63
|
+
table_name = if parent
|
64
|
+
"#{parent.name.underscore}_#{model.name.underscore}"
|
65
|
+
else
|
66
|
+
"#{model.name.underscore}"
|
67
|
+
end
|
68
|
+
|
69
|
+
table_sql = []
|
70
|
+
table_sql << "DROP TABLE IF EXISTS `#{table_name}`;"
|
71
|
+
table_sql << "CREATE TABLE `#{table_name}` (`id` CHAR(24) PRIMARY KEY);"
|
72
|
+
columns.each do |column|
|
73
|
+
table_sql << "ALTER TABLE `#{table_name}` ADD COLUMN #{column};"
|
74
|
+
end
|
75
|
+
|
76
|
+
table_sql.join("\n") + "\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
# SQUEAL #
|
80
|
+
def create_squeal(model, indent=false, parents = [])
|
81
|
+
fields = model.fields.values.sort_by(&:name)
|
82
|
+
|
83
|
+
parent = parents.last
|
84
|
+
table_name = if parent
|
85
|
+
"#{parent.name.underscore}_#{model.name.underscore}"
|
86
|
+
else
|
87
|
+
"#{model.name.underscore}"
|
88
|
+
end
|
89
|
+
|
90
|
+
squeal = if parent
|
91
|
+
"#{parent.name.underscore}.#{model.name.tableize}.each do |#{model.name.underscore}|\n" \
|
92
|
+
" #{table_name} = #{model.name.underscore}"
|
93
|
+
else
|
94
|
+
"import.source(\"#{model.name.tableize}\").each do |#{model.name.underscore}|"
|
95
|
+
end
|
96
|
+
|
97
|
+
schemas = [create_table(model, parent)]
|
98
|
+
|
99
|
+
squeal << <<-EOS
|
100
|
+
|
101
|
+
target(:#{table_name}) do
|
102
|
+
EOS
|
103
|
+
if parent
|
104
|
+
squeal << <<-EOS
|
105
|
+
assign(:#{parent.name.underscore}_id)
|
106
|
+
EOS
|
107
|
+
end
|
108
|
+
|
109
|
+
fields.each do |field|
|
110
|
+
field_name = field.name
|
111
|
+
case
|
112
|
+
when %w(type target).include?(field.name)
|
113
|
+
value = " { #{table_name}['#{field.name}'] }"
|
114
|
+
when field.name.size > 64
|
115
|
+
field_name = field.name[0..63]
|
116
|
+
value = " { #{table_name}.#{field.name} }"
|
117
|
+
when field.name =~ /(.*)_id$/
|
118
|
+
value = " { #{table_name}.#{$1} }"
|
119
|
+
end
|
120
|
+
squeal << <<-EOS
|
121
|
+
assign(:#{field_name})#{value}
|
122
|
+
EOS
|
123
|
+
end
|
124
|
+
|
125
|
+
model.associations.values.each do |association|
|
126
|
+
begin
|
127
|
+
if [Mongoid::Associations::HasMany, Mongoid::Associations::HasOne].include?(association.association)
|
128
|
+
unless parents.include?(association.klass)
|
129
|
+
ruby, sql = create_squeal(association.klass, true, parents | [model])
|
130
|
+
squeal << "\n" + ruby + "\n"
|
131
|
+
schemas |= sql
|
132
|
+
end
|
133
|
+
end
|
134
|
+
rescue NameError
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
squeal << <<-EOS
|
139
|
+
end
|
140
|
+
end # #{table_name}
|
141
|
+
EOS
|
142
|
+
squeal.gsub!(/^/, " ") if indent
|
143
|
+
return squeal, schemas
|
144
|
+
end
|
145
|
+
|
146
|
+
squeal, schema = create_squeal(model)
|
147
|
+
|
148
|
+
if write_squeal
|
149
|
+
File.open(squeal_filename, "w") do |file|
|
150
|
+
file.write <<-EOS
|
151
|
+
require 'squealer'
|
152
|
+
|
153
|
+
import('localhost', 27017, 'development') # <--- Change this as needed
|
154
|
+
export('localhost', 'root', '', 'export') # <--- Change this as needed
|
155
|
+
|
156
|
+
EOS
|
157
|
+
file.write(squeal)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
if write_schema
|
162
|
+
File.open(schema_filename, "w") do |file|
|
163
|
+
file.write(schema.join("\n"))
|
164
|
+
end
|
165
|
+
end
|
Binary file
|
data/lib/example_squeal.rb
CHANGED
@@ -7,7 +7,16 @@ import('localhost', 27017, 'development')
|
|
7
7
|
export('localhost', 'root', '', 'reporting_export')
|
8
8
|
|
9
9
|
# Here we extract, transform and load all documents in a collection...
|
10
|
-
|
10
|
+
|
11
|
+
#
|
12
|
+
# You don't want to use a find() on the MongoDB collection...
|
13
|
+
# import.source("users") { |users| users.find_one() }.each do |user|
|
14
|
+
#
|
15
|
+
# Also accepts optional conditions...
|
16
|
+
# import.source("users", "{disabled: 'false'}").each do |user|
|
17
|
+
#
|
18
|
+
# Defaults to find all...
|
19
|
+
import.source('users').each do |user|
|
11
20
|
# Insert or Update on table 'user' where 'id' is the column name of the primary key.
|
12
21
|
#
|
13
22
|
# The primary key value is taken from the '_id' field of the source document,
|
@@ -54,8 +63,7 @@ import.collection("users").find({}).each do |user|
|
|
54
63
|
#
|
55
64
|
# You can use an empty block to infer the value from the '_id' field
|
56
65
|
# of a parent document where the name of the parent collection matches
|
57
|
-
# a variable that is in scope
|
58
|
-
#
|
66
|
+
# a variable that is in scope...
|
59
67
|
assign(:user_id) #or# assign(:user_id) { user._id }
|
60
68
|
assign(:name) #or# assign(:name) { activity.name }
|
61
69
|
assign(:due_date) #or# assign(:due_date) { activity.due_date }
|
@@ -73,8 +81,8 @@ import.collection("users").find({}).each do |user|
|
|
73
81
|
end #collection("users")
|
74
82
|
|
75
83
|
# Here we use a procedural "join" on related collections to update a target...
|
76
|
-
import.
|
77
|
-
import.
|
84
|
+
import.source('organizations', {'disabled_date' => {'exists' => true}}).each do |organization|
|
85
|
+
import.source('users', {'organization_id' => organization.id}) do |user|
|
78
86
|
target(:user) do
|
79
87
|
#
|
80
88
|
# Source boolean values are converted to integer (0 or 1)...
|
data/lib/squealer/database.rb
CHANGED
@@ -8,6 +8,7 @@ module Squealer
|
|
8
8
|
|
9
9
|
def import_from(host, port, name)
|
10
10
|
@import_dbc = Mongo::Connection.new(host, port, :slave_ok => true).db(name)
|
11
|
+
@import_connection = Connection.new(@import_dbc)
|
11
12
|
end
|
12
13
|
|
13
14
|
def export_to(host, username, password, name)
|
@@ -15,12 +16,57 @@ module Squealer
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def import
|
18
|
-
@
|
19
|
+
@import_connection
|
19
20
|
end
|
20
21
|
|
21
22
|
def export
|
22
23
|
@export_dbc
|
23
24
|
end
|
24
25
|
|
26
|
+
class Connection
|
27
|
+
attr_reader :collections
|
28
|
+
|
29
|
+
def initialize(dbc)
|
30
|
+
@dbc = dbc
|
31
|
+
@collections = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def source(collection, conditions = {}, &block)
|
35
|
+
source = Source.new(@dbc, collection)
|
36
|
+
@collections[collection] = source
|
37
|
+
source.source(conditions, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def eval(string)
|
41
|
+
@dbc.eval(string)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Source
|
46
|
+
attr_reader :counts, :cursor
|
47
|
+
|
48
|
+
def initialize(dbc, collection)
|
49
|
+
@counts = {:exported => 0, :imported => 0}
|
50
|
+
@collection = dbc.collection(collection)
|
51
|
+
end
|
52
|
+
|
53
|
+
def source(conditions)
|
54
|
+
@cursor = block_given? ? yield(@collection) : @collection.find(conditions)
|
55
|
+
@counts[:total] = cursor.count
|
56
|
+
@progress_bar = Squealer::ProgressBar.new(cursor.count)
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def each
|
61
|
+
@progress_bar.start if @progress_bar
|
62
|
+
@cursor.each do |row|
|
63
|
+
@counts[:imported] += 1
|
64
|
+
yield row
|
65
|
+
@progress_bar.tick if @progress_bar
|
66
|
+
@counts[:exported] += 1
|
67
|
+
end
|
68
|
+
@progress_bar.finish if @progress_bar
|
69
|
+
end
|
70
|
+
end
|
25
71
|
end
|
26
72
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Squealer
|
2
|
+
class ProgressBar
|
3
|
+
|
4
|
+
@@progress_bar = nil
|
5
|
+
|
6
|
+
def self.new(*args)
|
7
|
+
if @@progress_bar
|
8
|
+
nil
|
9
|
+
else
|
10
|
+
@@progress_bar = super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(total)
|
15
|
+
@total = total
|
16
|
+
@ticks = 0
|
17
|
+
|
18
|
+
@progress_bar_width = 50
|
19
|
+
@count_width = total.to_s.size
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
@start_time = Time.new
|
25
|
+
@emitter = start_emitter if total > 0
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def finish
|
30
|
+
@end_time = Time.new
|
31
|
+
@emitter.wakeup.join if @emitter
|
32
|
+
@@progress_bar = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def tick
|
36
|
+
@ticks += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def start_emitter
|
42
|
+
Thread.new do
|
43
|
+
emit
|
44
|
+
sleep(1) and emit until done?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def emit
|
49
|
+
format = "\r[%-#{progress_bar_width}s] %#{count_width}i/%i (%i%%)"
|
50
|
+
console.print format % [progress_markers, ticks, total, percentage]
|
51
|
+
emit_final if done?
|
52
|
+
end
|
53
|
+
|
54
|
+
def emit_final
|
55
|
+
console.puts
|
56
|
+
|
57
|
+
console.puts "Start: #{start_time}"
|
58
|
+
console.puts "End: #{end_time}"
|
59
|
+
console.puts "Duration: #{duration}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def done?
|
63
|
+
ticks >= total || end_time
|
64
|
+
end
|
65
|
+
|
66
|
+
def start_time
|
67
|
+
@start_time
|
68
|
+
end
|
69
|
+
|
70
|
+
def end_time
|
71
|
+
@end_time
|
72
|
+
end
|
73
|
+
|
74
|
+
def ticks
|
75
|
+
@ticks
|
76
|
+
end
|
77
|
+
|
78
|
+
def total
|
79
|
+
@total
|
80
|
+
end
|
81
|
+
|
82
|
+
def percentage
|
83
|
+
((ticks.to_f / total) * 100).floor
|
84
|
+
end
|
85
|
+
|
86
|
+
def progress_markers
|
87
|
+
"=" * ((ticks.to_f / total) * progress_bar_width).floor
|
88
|
+
end
|
89
|
+
|
90
|
+
def console
|
91
|
+
$stderr
|
92
|
+
end
|
93
|
+
|
94
|
+
def progress_bar_width
|
95
|
+
@progress_bar_width
|
96
|
+
end
|
97
|
+
|
98
|
+
def count_width
|
99
|
+
@count_width
|
100
|
+
end
|
101
|
+
|
102
|
+
def total_time
|
103
|
+
@end_time - @start_time
|
104
|
+
end
|
105
|
+
|
106
|
+
def duration
|
107
|
+
duration = Time.at(total_time).utc
|
108
|
+
duration.strftime("%H:%M:%S.#{duration.usec}")
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
data/lib/squealer/target.rb
CHANGED
@@ -55,11 +55,11 @@ module Squealer
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def infer_row_id
|
58
|
-
|
58
|
+
eval "#{@table_name}._id", @binding, __FILE__, __LINE__
|
59
59
|
end
|
60
60
|
|
61
61
|
def verify_table_name_in_scope
|
62
|
-
table =
|
62
|
+
table = eval "#{@table_name}", @binding, __FILE__, __LINE__
|
63
63
|
raise ArgumentError, "The variable '#{@table_name}' is not a hashmap" unless table.is_a? Hash
|
64
64
|
raise ArgumentError, "The hashmap '#{@table_name}' must have an '_id' key" unless table.has_key? '_id'
|
65
65
|
rescue NameError
|
@@ -68,12 +68,12 @@ module Squealer
|
|
68
68
|
|
69
69
|
|
70
70
|
def infer_value(column_name, binding)
|
71
|
-
value =
|
71
|
+
value = eval "#{@table_name}.#{column_name}", binding, __FILE__, __LINE__
|
72
72
|
unless value
|
73
73
|
name = column_name.to_s
|
74
|
-
if name
|
74
|
+
if name =~ /_id$/
|
75
75
|
related = name[0..-4] #strip "_id"
|
76
|
-
value =
|
76
|
+
value = eval "#{related}._id", binding, __FILE__, __LINE__
|
77
77
|
end
|
78
78
|
end
|
79
79
|
value
|
@@ -103,8 +103,11 @@ module Squealer
|
|
103
103
|
|
104
104
|
def execute_sql(sql)
|
105
105
|
statement = Database.instance.export.prepare(sql)
|
106
|
-
values =
|
106
|
+
values = typecast_values * 2
|
107
|
+
|
107
108
|
statement.send(:execute, @row_id, *values) #expand values into distinct arguments
|
109
|
+
rescue Mysql::Error, TypeError
|
110
|
+
raise "Failed to execute statement: #{sql} with #{values.inspect}.\nOriginal Exception was: #{$!.to_s}"
|
108
111
|
end
|
109
112
|
|
110
113
|
def pk_name
|
@@ -113,7 +116,7 @@ module Squealer
|
|
113
116
|
|
114
117
|
def column_names
|
115
118
|
return if @column_names.size == 0
|
116
|
-
",#{@column_names.join(',')}"
|
119
|
+
",#{@column_names.map { |name| quote_identifier(name) }.join(',')}"
|
117
120
|
end
|
118
121
|
|
119
122
|
def column_values
|
@@ -130,10 +133,29 @@ module Squealer
|
|
130
133
|
def column_markers
|
131
134
|
return if @column_names.size == 0
|
132
135
|
result = ""
|
133
|
-
@column_names.each {|k| result << "#{k}=?," }
|
136
|
+
@column_names.each {|k| result << "#{quote_identifier(k)}=?," }
|
134
137
|
result.chop
|
135
138
|
end
|
136
139
|
|
140
|
+
def typecast_values
|
141
|
+
column_values.map do |value|
|
142
|
+
case value
|
143
|
+
when true, false
|
144
|
+
value.to_i
|
145
|
+
when Symbol
|
146
|
+
value.to_s
|
147
|
+
when Array
|
148
|
+
value.join(",")
|
149
|
+
else
|
150
|
+
value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def quote_identifier(name)
|
156
|
+
"`#{name}`"
|
157
|
+
end
|
158
|
+
|
137
159
|
class Queue < DelegateClass(Array)
|
138
160
|
include Singleton
|
139
161
|
|
data/lib/squealer.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,112 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
1
3
|
require 'rubygems'
|
2
4
|
|
3
5
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
6
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
7
|
|
6
8
|
require 'squealer'
|
9
|
+
|
10
|
+
Spec::Runner.configure do |config|
|
11
|
+
config.before(:suite) do
|
12
|
+
$db_name = "test_export_#{object_id}"
|
13
|
+
create_test_db($db_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
config.after(:suite) do
|
17
|
+
drop_test_db($db_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_test_db(name)
|
21
|
+
@my = Mysql.connect('localhost', 'root')
|
22
|
+
@my.query("DROP DATABASE IF EXISTS #{name}")
|
23
|
+
@my.query("CREATE DATABASE #{name}")
|
24
|
+
@my.query("USE #{name}")
|
25
|
+
@my.query("SET sql_mode='ANSI_QUOTES'")
|
26
|
+
|
27
|
+
create_export_tables
|
28
|
+
|
29
|
+
Squealer::Database.instance.import_from('localhost', 27017, $db_name)
|
30
|
+
@mongo = Squealer::Database.instance.import.send(:instance_variable_get, '@dbc')
|
31
|
+
drop_mongo
|
32
|
+
seed_import
|
33
|
+
end
|
34
|
+
|
35
|
+
def drop_test_db(name)
|
36
|
+
@my.query("DROP DATABASE IF EXISTS #{name}")
|
37
|
+
@my.close
|
38
|
+
|
39
|
+
drop_mongo
|
40
|
+
end
|
41
|
+
|
42
|
+
def drop_mongo
|
43
|
+
@mongo.eval('db.dropDatabase()') if @mongo
|
44
|
+
end
|
45
|
+
|
46
|
+
def seed_import
|
47
|
+
hashrocket = @mongo.collection('organizations').save({ :name => 'Hashrocket' })
|
48
|
+
zorganization = @mongo.collection('organizations').save({ :name => 'Zorganization', :disabled_date => as_time(Date.today) })
|
49
|
+
|
50
|
+
users = [
|
51
|
+
{ :name => 'Josh Graham', :dob => as_time(Date.parse('01-Jan-1971')), :gender => 'M',
|
52
|
+
:organization_id => hashrocket,
|
53
|
+
:activities => [
|
54
|
+
{ :name => 'Develop squealer', :due_date => as_time(Date.today + 1) },
|
55
|
+
{ :name => 'Organize speakerconf.com', :due_date => as_time(Date.today + 30) },
|
56
|
+
{ :name => 'Hashrocket party', :due_date => as_time(Date.today + 7) }
|
57
|
+
]
|
58
|
+
},
|
59
|
+
{ :name => 'Bernerd Schaefer', :dob => as_time(Date.parse('31-Dec-1985')), :gender => 'M',
|
60
|
+
:organization_id => hashrocket,
|
61
|
+
:activities => [
|
62
|
+
{ :name => 'Retype all of the code Josh wrote in squealer', :due_date => as_time(Date.today + 2) },
|
63
|
+
{ :name => 'Listen to rare Thelonius Monk EP', :due_date => as_time(Date.today) },
|
64
|
+
{ :name => 'Practice karaoke', :due_date => as_time(Date.today + 7) }
|
65
|
+
]
|
66
|
+
},
|
67
|
+
{ :name => 'Your momma', :dob => as_time(Date.parse('15-Jun-1955')), :gender => 'F',
|
68
|
+
:organization_id => zorganization,
|
69
|
+
:activities => [
|
70
|
+
{ :name => 'Cook me some pie', :due_date => as_time(Date.today) },
|
71
|
+
{ :name => 'Make me a sammich', :due_date => as_time(Date.today) }
|
72
|
+
]
|
73
|
+
}
|
74
|
+
]
|
75
|
+
|
76
|
+
users.each { |user| @mongo.collection('users').save user }
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_export_tables
|
80
|
+
command = <<-COMMAND.gsub(/\n\s*/, " ")
|
81
|
+
CREATE TABLE "users" (
|
82
|
+
"id" INT NOT NULL AUTO_INCREMENT ,
|
83
|
+
"name" VARCHAR(255) NULL ,
|
84
|
+
"gender" CHAR(1) NULL ,
|
85
|
+
"dob" DATE NULL ,
|
86
|
+
PRIMARY KEY ("id") )
|
87
|
+
COMMAND
|
88
|
+
@my.query(command)
|
89
|
+
|
90
|
+
command = <<-COMMAND.gsub(/\n\s*/, " ")
|
91
|
+
CREATE TABLE "activity" (
|
92
|
+
"id" INT NOT NULL AUTO_INCREMENT ,
|
93
|
+
"user_id" INT NULL ,
|
94
|
+
"name" VARCHAR(255) NULL ,
|
95
|
+
"due_date" DATE NULL ,
|
96
|
+
PRIMARY KEY ("id") )
|
97
|
+
COMMAND
|
98
|
+
@my.query(command)
|
99
|
+
|
100
|
+
command = <<-COMMAND.gsub(/\n\s*/, " ")
|
101
|
+
CREATE TABLE "organizations" (
|
102
|
+
"id" INT NOT NULL AUTO_INCREMENT ,
|
103
|
+
"disabed_date" DATE NULL ,
|
104
|
+
PRIMARY KEY ("id") )
|
105
|
+
COMMAND
|
106
|
+
@my.query(command)
|
107
|
+
end
|
108
|
+
|
109
|
+
def as_time(date)
|
110
|
+
Time.parse(date.to_s)
|
111
|
+
end
|
112
|
+
end
|
@@ -3,39 +3,131 @@ require 'mongo'
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
describe Squealer::Database do
|
6
|
-
|
7
|
-
before(:all) do
|
8
|
-
@db_name = "test_export_#{object_id}"
|
9
|
-
create_test_db(@db_name)
|
10
|
-
end
|
11
|
-
|
12
|
-
after(:all) do
|
13
|
-
drop_test_db(@db_name)
|
14
|
-
end
|
15
|
-
|
16
6
|
it "is a singleton" do
|
17
7
|
Squealer::Database.respond_to?(:instance).should be_true
|
18
8
|
end
|
19
9
|
|
20
|
-
|
21
|
-
Squealer::Database.instance
|
22
|
-
|
23
|
-
|
10
|
+
describe "import" do
|
11
|
+
let(:databases) { Squealer::Database.instance }
|
12
|
+
|
13
|
+
|
14
|
+
it "takes an import database" do
|
15
|
+
databases.send(:instance_variable_get, '@import_dbc').should be_a_kind_of(Mongo::DB)
|
16
|
+
end
|
24
17
|
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
it "returns a squealer connection object" do
|
19
|
+
databases.import.should be_a_kind_of(Squealer::Database::Connection)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "delegates eval to Mongo" do
|
23
|
+
databases.send(:instance_variable_get, '@import_dbc').eval('db.getName()').should == $db_name
|
24
|
+
databases.import.eval('db.getName()').should == $db_name
|
25
|
+
end
|
28
26
|
end
|
29
27
|
|
30
|
-
|
28
|
+
describe "source" do
|
29
|
+
let(:databases) { Squealer::Database.instance }
|
30
|
+
|
31
|
+
before { databases.import_from('localhost', 27017, $db_name) }
|
32
|
+
|
33
|
+
it "returns a Source" do
|
34
|
+
databases.import.source('foo').should be_a_kind_of(Squealer::Database::Source)
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "Source::cursor" do
|
38
|
+
it "returns a databases cursor" do
|
39
|
+
databases.import.source('foo').cursor.should be_a_kind_of(Mongo::Cursor)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "an empty collection" do
|
44
|
+
subject { databases.import.source('foo') }
|
45
|
+
|
46
|
+
it "counts a total of zero" do
|
47
|
+
subject.counts[:total].should == 0
|
48
|
+
end
|
49
|
+
|
50
|
+
it "counts zero imported" do
|
51
|
+
subject.counts[:imported].should == 0
|
52
|
+
end
|
53
|
+
|
54
|
+
it "counts zero exported" do
|
55
|
+
subject.counts[:exported].should == 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "a collection with two documents" do
|
60
|
+
let(:mongo) { Squealer::Database.instance.import.send(:instance_variable_get, '@dbc') }
|
61
|
+
|
62
|
+
subject do
|
63
|
+
mongo.collection('foo').save({'name' => 'Bar'});
|
64
|
+
mongo.collection('foo').save({'name' => 'Baz'});
|
65
|
+
source = databases.import.source('foo') # activate the counter
|
66
|
+
source.send(:instance_variable_set, :@progress_bar, nil)
|
67
|
+
Squealer::ProgressBar.send(:class_variable_set, :@@progress_bar, nil)
|
68
|
+
source
|
69
|
+
end
|
70
|
+
|
71
|
+
after do
|
72
|
+
mongo.collection('foo').drop
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns a Source" do
|
76
|
+
subject.should be_a_kind_of(Squealer::Database::Source)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "counts a total of two" do
|
80
|
+
subject.counts[:total].should == 2
|
81
|
+
end
|
82
|
+
|
83
|
+
context "before iterating" do
|
84
|
+
it "counts zero imported" do
|
85
|
+
subject.counts[:imported].should == 0
|
86
|
+
end
|
87
|
+
|
88
|
+
it "counts zero exported" do
|
89
|
+
subject.counts[:exported].should == 0
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "after iterating" do
|
94
|
+
before do
|
95
|
+
subject.each {}
|
96
|
+
end
|
97
|
+
|
98
|
+
it "counts two imported" do
|
99
|
+
subject.counts[:imported].should == 2
|
100
|
+
end
|
101
|
+
|
102
|
+
it "counts two exported" do
|
103
|
+
subject.counts[:exported].should == 2
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "real squeal" do
|
108
|
+
before { pending "interactive_view" }
|
109
|
+
subject do
|
110
|
+
source = databases.import.source("users")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "^^^ you saw that progress bar right there" do
|
114
|
+
subject.each do
|
115
|
+
sleep (rand(2) + 1)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
31
121
|
|
32
|
-
def create_test_db(name)
|
33
|
-
@my = Mysql.connect('localhost', 'root')
|
34
|
-
@my.query("create database #{name}")
|
35
122
|
end
|
36
123
|
|
37
|
-
|
38
|
-
|
39
|
-
|
124
|
+
describe "export" do
|
125
|
+
let(:databases) { Squealer::Database.instance }
|
126
|
+
|
127
|
+
it "takes an export database" do
|
128
|
+
databases.export_to('localhost', 'root', '', $db_name)
|
129
|
+
databases.send(:instance_variable_get, '@export_dbc').should be_a_kind_of(Mysql)
|
130
|
+
end
|
40
131
|
end
|
132
|
+
|
41
133
|
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Squealer::ProgressBar do
|
4
|
+
let(:total) { 200 }
|
5
|
+
let(:progress_bar) do
|
6
|
+
testable_progress_bar = Class.new(Squealer::ProgressBar) do
|
7
|
+
attr_reader :emitter
|
8
|
+
public :total, :ticks, :percentage, :progress_markers, :emit,
|
9
|
+
:duration, :start_time, :end_time, :progress_bar_width
|
10
|
+
|
11
|
+
def console
|
12
|
+
@console ||= StringIO.new
|
13
|
+
end
|
14
|
+
|
15
|
+
alias real_start_emitter start_emitter
|
16
|
+
def start_emitter; end
|
17
|
+
public :real_start_emitter
|
18
|
+
|
19
|
+
end
|
20
|
+
testable_progress_bar.new(total).start
|
21
|
+
end
|
22
|
+
let(:console) { progress_bar.console }
|
23
|
+
let(:progress_bar_width) { progress_bar.progress_bar_width }
|
24
|
+
|
25
|
+
before { progress_bar.start }
|
26
|
+
after { progress_bar.finish }
|
27
|
+
|
28
|
+
it "allows only one progress bar at a time" do
|
29
|
+
Squealer::ProgressBar.new(0).should be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "records the starting time" do
|
33
|
+
progress_bar.start_time.should be_an_instance_of(Time)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "records the starting time" do
|
37
|
+
progress_bar.start_time.should be_an_instance_of(Time)
|
38
|
+
end
|
39
|
+
|
40
|
+
context "threaded" do
|
41
|
+
before { progress_bar.stub(:emitter).and_return(progress_bar.real_start_emitter) }
|
42
|
+
after { progress_bar.emitter.kill }
|
43
|
+
|
44
|
+
it "has an emitter" do
|
45
|
+
progress_bar.tick
|
46
|
+
progress_bar.emitter.should_not be_nil
|
47
|
+
end
|
48
|
+
|
49
|
+
it "emits at least once" do
|
50
|
+
progress_bar.tick
|
51
|
+
progress_bar.emitter.wakeup
|
52
|
+
sleep 0.1
|
53
|
+
console.string.split("\r").length.should > 0
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "no items completed" do
|
58
|
+
it "emits the total number provided" do
|
59
|
+
progress_bar.total.should == total
|
60
|
+
end
|
61
|
+
|
62
|
+
it "emits the number of ticks" do
|
63
|
+
progress_bar.ticks.should == 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it "displays an empty bar" do
|
67
|
+
progress_bar.progress_markers.size.should == 0
|
68
|
+
end
|
69
|
+
|
70
|
+
it "prints a progress bar to the console" do
|
71
|
+
progress_bar.emit
|
72
|
+
console.string.should == "\r[#{' ' * progress_bar_width}] #{0}/#{total} (0%)"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "one item completed" do
|
77
|
+
before { progress_bar.tick }
|
78
|
+
it "emits the number of ticks" do
|
79
|
+
progress_bar.ticks.should == 1
|
80
|
+
end
|
81
|
+
|
82
|
+
it "emits the number of ticks as a percentage of the total number (rounded down)" do
|
83
|
+
progress_bar.percentage.should == 0
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "1/nth complete (where n is the width of the progress bar)" do
|
88
|
+
let(:ticks) { (total / progress_bar_width) }
|
89
|
+
before { ticks.times { progress_bar.tick } }
|
90
|
+
it "emits the number of ticks" do
|
91
|
+
progress_bar.ticks.should == ticks
|
92
|
+
end
|
93
|
+
|
94
|
+
it "emits the number of ticks as a percentage of the total number (rounded down)" do
|
95
|
+
progress_bar.percentage.should == (ticks.to_f * 100 / total).floor
|
96
|
+
end
|
97
|
+
|
98
|
+
it "displays the first progress marker" do
|
99
|
+
progress_bar.progress_markers.size.should == 1
|
100
|
+
end
|
101
|
+
|
102
|
+
it "prints a progress bar to the console" do
|
103
|
+
progress_bar.emit
|
104
|
+
percentag = (ticks.to_f * 100 / total).floor
|
105
|
+
console.string.should == "\r[=#{' ' * (progress_bar_width - 1)}] #{ticks}/#{total} (#{percentag}%)"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "all but one item completed" do
|
110
|
+
let(:ticks) { total - 1 }
|
111
|
+
before { ticks.times { progress_bar.tick } }
|
112
|
+
|
113
|
+
it "emits the number of ticks" do
|
114
|
+
progress_bar.ticks.should == ticks
|
115
|
+
end
|
116
|
+
|
117
|
+
it "emits the number of ticks as a percentage of the total number (rounded down)" do
|
118
|
+
progress_bar.percentage.should == 99
|
119
|
+
end
|
120
|
+
|
121
|
+
it "has not yet displayed the final progress marker" do
|
122
|
+
progress_bar.progress_markers.size.should == (progress_bar_width - 1)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "all items completed" do
|
127
|
+
let(:ticks) { total }
|
128
|
+
before { ticks.times { progress_bar.tick } }
|
129
|
+
|
130
|
+
it "emits the number of ticks" do
|
131
|
+
progress_bar.ticks.should == ticks
|
132
|
+
end
|
133
|
+
|
134
|
+
it "emits 100%" do
|
135
|
+
progress_bar.percentage.should == 100
|
136
|
+
end
|
137
|
+
|
138
|
+
it "fills the progress bar with progress markers" do
|
139
|
+
progress_bar.progress_markers.size.should == progress_bar_width
|
140
|
+
end
|
141
|
+
|
142
|
+
it "records the ending time when finished" do
|
143
|
+
progress_bar.finish
|
144
|
+
progress_bar.end_time.should be_an_instance_of(Time)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "prints a progress bar to the console" do
|
148
|
+
progress_bar.finish
|
149
|
+
progress_bar.emit
|
150
|
+
console.string.split("\n").first.should == "\r[#{'=' * progress_bar_width}] #{ticks}/#{total} (100%)"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "multiple emits" do
|
155
|
+
let(:ticks) { total }
|
156
|
+
subject { console.string }
|
157
|
+
before do
|
158
|
+
progress_bar.emit
|
159
|
+
end
|
160
|
+
|
161
|
+
context "not done" do
|
162
|
+
it "emitted two lines with no final newline" do
|
163
|
+
progress_bar.emit
|
164
|
+
|
165
|
+
subject.split("\r").size.should == 3
|
166
|
+
subject[-1, 1].should_not == "\n"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "done" do
|
171
|
+
it "emitted two lines with a final newline" do
|
172
|
+
ticks.times { progress_bar.tick }
|
173
|
+
progress_bar.finish
|
174
|
+
progress_bar.emit
|
175
|
+
|
176
|
+
subject.split("\r").size.should == 3
|
177
|
+
subject[-1, 1].should == "\n"
|
178
|
+
end
|
179
|
+
|
180
|
+
it "emitted final timings" do
|
181
|
+
ticks.times { progress_bar.tick }
|
182
|
+
progress_bar.finish
|
183
|
+
progress_bar.emit
|
184
|
+
|
185
|
+
subject.should include("Start: #{progress_bar.start_time}\n")
|
186
|
+
subject.should include("End: #{progress_bar.end_time}\n")
|
187
|
+
subject.should include("Duration: #{progress_bar.duration}\n")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -196,7 +196,6 @@ describe Squealer::Target do
|
|
196
196
|
end
|
197
197
|
end
|
198
198
|
end
|
199
|
-
|
200
199
|
end
|
201
200
|
end
|
202
201
|
end
|
@@ -210,6 +209,31 @@ describe Squealer::Target do
|
|
210
209
|
end
|
211
210
|
|
212
211
|
describe "#target" do
|
212
|
+
describe "#typecast_values" do
|
213
|
+
subject { target.send(:typecast_values) }
|
214
|
+
let(:target) { Squealer::Target.new(export_dbc, table_name) {} }
|
215
|
+
|
216
|
+
it "casts array to comma-separated string" do
|
217
|
+
target.assign(:colA) { ['1', '2'] }
|
218
|
+
subject.should == ['1,2']
|
219
|
+
end
|
220
|
+
|
221
|
+
it "casts false to 0 (for mysql TINYINT)" do
|
222
|
+
target.assign(:colA) { false }
|
223
|
+
subject.should == [0]
|
224
|
+
end
|
225
|
+
|
226
|
+
it "casts true to 1 (for mysql TINYINT)" do
|
227
|
+
target.assign(:colA) { true }
|
228
|
+
subject.should == [1]
|
229
|
+
end
|
230
|
+
|
231
|
+
it "casts symbol to string" do
|
232
|
+
target.assign(:colA) { :open }
|
233
|
+
subject.should == ['open']
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
213
237
|
context "generates SQL command strings" do
|
214
238
|
let(:target) { Squealer::Target.new(export_dbc, table_name) { nil } }
|
215
239
|
|
@@ -239,7 +263,7 @@ describe Squealer::Target do
|
|
239
263
|
end
|
240
264
|
|
241
265
|
it "includes the column name in the INSERT" do
|
242
|
-
target.sql.should =~ /\(id
|
266
|
+
target.sql.should =~ /\(id,`colA`\) VALUES/
|
243
267
|
end
|
244
268
|
|
245
269
|
it "includes the column value in the INSERT" do
|
@@ -249,7 +273,7 @@ describe Squealer::Target do
|
|
249
273
|
|
250
274
|
it "includes the column name and value in the UPDATE" do
|
251
275
|
# target.sql.should =~ /UPDATE colA='#{value_1}'/
|
252
|
-
target.sql.should =~ /UPDATE colA
|
276
|
+
target.sql.should =~ /UPDATE `colA`=\?/
|
253
277
|
end
|
254
278
|
|
255
279
|
end
|
@@ -265,7 +289,7 @@ describe Squealer::Target do
|
|
265
289
|
end
|
266
290
|
|
267
291
|
it "includes the column names in the INSERT" do
|
268
|
-
target.sql.should =~ /\(id
|
292
|
+
target.sql.should =~ /\(id,`colA`,`colB`\) VALUES/
|
269
293
|
end
|
270
294
|
|
271
295
|
it "includes the column values in the INSERT" do
|
@@ -275,7 +299,7 @@ describe Squealer::Target do
|
|
275
299
|
|
276
300
|
it "includes the column names and values in the UPDATE" do
|
277
301
|
# target.sql.should =~ /UPDATE colA='#{value_1}',colB='#{value_2}'/
|
278
|
-
target.sql.should =~ /UPDATE colA
|
302
|
+
target.sql.should =~ /UPDATE `colA`=\?,`colB`=\?/
|
279
303
|
end
|
280
304
|
end
|
281
305
|
end
|
data/squealer.gemspec
CHANGED
@@ -5,13 +5,15 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{squealer}
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "2.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Josh Graham", "Durran Jordan"]
|
12
|
-
s.date = %q{2010-05-
|
11
|
+
s.authors = ["Josh Graham", "Durran Jordan", "Matt Yoho", "Bernerd Schaefer"]
|
12
|
+
s.date = %q{2010-05-20}
|
13
|
+
s.default_executable = %q{skewer}
|
13
14
|
s.description = %q{Exports mongodb to mysql. More later.}
|
14
15
|
s.email = %q{joshua.graham@grahamis.com}
|
16
|
+
s.executables = ["skewer"]
|
15
17
|
s.extra_rdoc_files = [
|
16
18
|
"README.md"
|
17
19
|
]
|
@@ -22,12 +24,15 @@ Gem::Specification.new do |s|
|
|
22
24
|
"README.md",
|
23
25
|
"Rakefile",
|
24
26
|
"VERSION",
|
27
|
+
"bin/skewer",
|
28
|
+
"lib/.example_squeal.rb.swp",
|
25
29
|
"lib/example_squeal.rb",
|
26
30
|
"lib/squealer.rb",
|
27
31
|
"lib/squealer/boolean.rb",
|
28
32
|
"lib/squealer/database.rb",
|
29
33
|
"lib/squealer/hash.rb",
|
30
34
|
"lib/squealer/object.rb",
|
35
|
+
"lib/squealer/progress_bar.rb",
|
31
36
|
"lib/squealer/target.rb",
|
32
37
|
"lib/squealer/time.rb",
|
33
38
|
"lib/tasks/jeweler.rake",
|
@@ -37,6 +42,7 @@ Gem::Specification.new do |s|
|
|
37
42
|
"spec/squealer/database_spec.rb",
|
38
43
|
"spec/squealer/hash_spec.rb",
|
39
44
|
"spec/squealer/object_spec.rb",
|
45
|
+
"spec/squealer/progress_bar_spec.rb",
|
40
46
|
"spec/squealer/target_spec.rb",
|
41
47
|
"spec/squealer/time_spec.rb",
|
42
48
|
"squealer.gemspec"
|
@@ -44,7 +50,7 @@ Gem::Specification.new do |s|
|
|
44
50
|
s.homepage = %q{http://github.com/delitescere/squealer/}
|
45
51
|
s.rdoc_options = ["--charset=UTF-8"]
|
46
52
|
s.require_paths = ["lib"]
|
47
|
-
s.rubygems_version = %q{1.3.
|
53
|
+
s.rubygems_version = %q{1.3.6}
|
48
54
|
s.summary = %q{Document-oriented to Relational database exporter}
|
49
55
|
s.test_files = [
|
50
56
|
"spec/spec_helper.rb",
|
@@ -52,6 +58,7 @@ Gem::Specification.new do |s|
|
|
52
58
|
"spec/squealer/database_spec.rb",
|
53
59
|
"spec/squealer/hash_spec.rb",
|
54
60
|
"spec/squealer/object_spec.rb",
|
61
|
+
"spec/squealer/progress_bar_spec.rb",
|
55
62
|
"spec/squealer/target_spec.rb",
|
56
63
|
"spec/squealer/time_spec.rb"
|
57
64
|
]
|
@@ -60,16 +67,19 @@ Gem::Specification.new do |s|
|
|
60
67
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
61
68
|
s.specification_version = 3
|
62
69
|
|
63
|
-
if Gem::Version.new(Gem::
|
70
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
64
71
|
s.add_runtime_dependency(%q<mysql>, [">= 2.8.1"])
|
65
72
|
s.add_runtime_dependency(%q<mongo>, [">= 0.18.3"])
|
73
|
+
s.add_runtime_dependency(%q<bson_ext>, [">= 1.0.1"])
|
66
74
|
else
|
67
75
|
s.add_dependency(%q<mysql>, [">= 2.8.1"])
|
68
76
|
s.add_dependency(%q<mongo>, [">= 0.18.3"])
|
77
|
+
s.add_dependency(%q<bson_ext>, [">= 1.0.1"])
|
69
78
|
end
|
70
79
|
else
|
71
80
|
s.add_dependency(%q<mysql>, [">= 2.8.1"])
|
72
81
|
s.add_dependency(%q<mongo>, [">= 0.18.3"])
|
82
|
+
s.add_dependency(%q<bson_ext>, [">= 1.0.1"])
|
73
83
|
end
|
74
84
|
end
|
75
85
|
|
metadata
CHANGED
@@ -1,33 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: squealer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 31
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
|
-
- 1
|
8
6
|
- 2
|
7
|
+
- 1
|
9
8
|
- 0
|
10
|
-
version: 1.
|
9
|
+
version: 2.1.0
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Josh Graham
|
14
13
|
- Durran Jordan
|
14
|
+
- Matt Yoho
|
15
|
+
- Bernerd Schaefer
|
15
16
|
autorequire:
|
16
17
|
bindir: bin
|
17
18
|
cert_chain: []
|
18
19
|
|
19
|
-
date: 2010-05-
|
20
|
-
default_executable:
|
20
|
+
date: 2010-05-20 00:00:00 -05:00
|
21
|
+
default_executable: skewer
|
21
22
|
dependencies:
|
22
23
|
- !ruby/object:Gem::Dependency
|
23
24
|
name: mysql
|
24
25
|
prerelease: false
|
25
26
|
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
27
|
requirements:
|
28
28
|
- - ">="
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
hash: 45
|
31
30
|
segments:
|
32
31
|
- 2
|
33
32
|
- 8
|
@@ -39,11 +38,9 @@ dependencies:
|
|
39
38
|
name: mongo
|
40
39
|
prerelease: false
|
41
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
41
|
requirements:
|
44
42
|
- - ">="
|
45
43
|
- !ruby/object:Gem::Version
|
46
|
-
hash: 81
|
47
44
|
segments:
|
48
45
|
- 0
|
49
46
|
- 18
|
@@ -51,10 +48,24 @@ dependencies:
|
|
51
48
|
version: 0.18.3
|
52
49
|
type: :runtime
|
53
50
|
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: bson_ext
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 1
|
60
|
+
- 0
|
61
|
+
- 1
|
62
|
+
version: 1.0.1
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
54
65
|
description: Exports mongodb to mysql. More later.
|
55
66
|
email: joshua.graham@grahamis.com
|
56
|
-
executables:
|
57
|
-
|
67
|
+
executables:
|
68
|
+
- skewer
|
58
69
|
extensions: []
|
59
70
|
|
60
71
|
extra_rdoc_files:
|
@@ -66,12 +77,15 @@ files:
|
|
66
77
|
- README.md
|
67
78
|
- Rakefile
|
68
79
|
- VERSION
|
80
|
+
- bin/skewer
|
81
|
+
- lib/.example_squeal.rb.swp
|
69
82
|
- lib/example_squeal.rb
|
70
83
|
- lib/squealer.rb
|
71
84
|
- lib/squealer/boolean.rb
|
72
85
|
- lib/squealer/database.rb
|
73
86
|
- lib/squealer/hash.rb
|
74
87
|
- lib/squealer/object.rb
|
88
|
+
- lib/squealer/progress_bar.rb
|
75
89
|
- lib/squealer/target.rb
|
76
90
|
- lib/squealer/time.rb
|
77
91
|
- lib/tasks/jeweler.rake
|
@@ -81,6 +95,7 @@ files:
|
|
81
95
|
- spec/squealer/database_spec.rb
|
82
96
|
- spec/squealer/hash_spec.rb
|
83
97
|
- spec/squealer/object_spec.rb
|
98
|
+
- spec/squealer/progress_bar_spec.rb
|
84
99
|
- spec/squealer/target_spec.rb
|
85
100
|
- spec/squealer/time_spec.rb
|
86
101
|
- squealer.gemspec
|
@@ -94,27 +109,23 @@ rdoc_options:
|
|
94
109
|
require_paths:
|
95
110
|
- lib
|
96
111
|
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
112
|
requirements:
|
99
113
|
- - ">="
|
100
114
|
- !ruby/object:Gem::Version
|
101
|
-
hash: 3
|
102
115
|
segments:
|
103
116
|
- 0
|
104
117
|
version: "0"
|
105
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
119
|
requirements:
|
108
120
|
- - ">="
|
109
121
|
- !ruby/object:Gem::Version
|
110
|
-
hash: 3
|
111
122
|
segments:
|
112
123
|
- 0
|
113
124
|
version: "0"
|
114
125
|
requirements: []
|
115
126
|
|
116
127
|
rubyforge_project:
|
117
|
-
rubygems_version: 1.3.
|
128
|
+
rubygems_version: 1.3.6
|
118
129
|
signing_key:
|
119
130
|
specification_version: 3
|
120
131
|
summary: Document-oriented to Relational database exporter
|
@@ -124,5 +135,6 @@ test_files:
|
|
124
135
|
- spec/squealer/database_spec.rb
|
125
136
|
- spec/squealer/hash_spec.rb
|
126
137
|
- spec/squealer/object_spec.rb
|
138
|
+
- spec/squealer/progress_bar_spec.rb
|
127
139
|
- spec/squealer/target_spec.rb
|
128
140
|
- spec/squealer/time_spec.rb
|