mongify 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/CHANGELOG.rdoc +22 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +68 -0
- data/LICENSE +20 -0
- data/README.rdoc +86 -0
- data/Rakefile +73 -0
- data/bin/mongify +14 -0
- data/features/options.feature +46 -0
- data/features/print.feature +10 -0
- data/features/process.feature +19 -0
- data/features/step_definitions/mongify_steps.rb +42 -0
- data/features/step_definitions/mongo_steps.rb +7 -0
- data/features/support/env.rb +28 -0
- data/lib/mongify.rb +22 -0
- data/lib/mongify/cli.rb +8 -0
- data/lib/mongify/cli/application.rb +43 -0
- data/lib/mongify/cli/help_command.rb +16 -0
- data/lib/mongify/cli/options.rb +94 -0
- data/lib/mongify/cli/report.rb +11 -0
- data/lib/mongify/cli/version_command.rb +18 -0
- data/lib/mongify/cli/worker_command.rb +93 -0
- data/lib/mongify/configuration.rb +46 -0
- data/lib/mongify/database.rb +5 -0
- data/lib/mongify/database/base_connection.rb +78 -0
- data/lib/mongify/database/column.rb +81 -0
- data/lib/mongify/database/no_sql_connection.rb +62 -0
- data/lib/mongify/database/sql_connection.rb +61 -0
- data/lib/mongify/database/table.rb +74 -0
- data/lib/mongify/exceptions.rb +13 -0
- data/lib/mongify/translation.rb +55 -0
- data/lib/mongify/translation/printer.rb +20 -0
- data/lib/mongify/translation/process.rb +61 -0
- data/lib/mongify/ui.rb +45 -0
- data/lib/mongify/version.rb +3 -0
- data/mongify.gemspec +42 -0
- data/spec/default.watch +199 -0
- data/spec/files/base_configuration.rb +9 -0
- data/spec/files/empty_translation.rb +0 -0
- data/spec/files/simple_translation.rb +26 -0
- data/spec/mongify/cli/application_spec.rb +19 -0
- data/spec/mongify/cli/help_command_spec.rb +18 -0
- data/spec/mongify/cli/options_spec.rb +62 -0
- data/spec/mongify/cli/version_command_spec.rb +24 -0
- data/spec/mongify/cli/worker_command_spec.rb +115 -0
- data/spec/mongify/configuration_spec.rb +25 -0
- data/spec/mongify/database/base_connection_spec.rb +59 -0
- data/spec/mongify/database/column_spec.rb +103 -0
- data/spec/mongify/database/no_sql_connection_spec.rb +131 -0
- data/spec/mongify/database/sql_connection_spec.rb +91 -0
- data/spec/mongify/database/table_spec.rb +120 -0
- data/spec/mongify/translation/printer_spec.rb +34 -0
- data/spec/mongify/translation/process_spec.rb +68 -0
- data/spec/mongify/translation_spec.rb +59 -0
- data/spec/mongify/ui_spec.rb +73 -0
- data/spec/mongify_spec.rb +15 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/config_reader.rb +21 -0
- data/spec/support/database.example +17 -0
- data/spec/support/database_output.txt +27 -0
- data/spec/support/generate_database.rb +91 -0
- metadata +370 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
module Mongify
|
2
|
+
module Database
|
3
|
+
#
|
4
|
+
# A column in the sql table
|
5
|
+
#
|
6
|
+
class Column
|
7
|
+
attr_reader :name, :type, :options
|
8
|
+
|
9
|
+
AVAILABLE_OPTIONS = ['references', 'default']
|
10
|
+
|
11
|
+
def initialize(name, type=:string, *args)
|
12
|
+
@name = name
|
13
|
+
type = :string if type.nil?
|
14
|
+
@type = type.is_a?(Symbol) ? type : type.to_sym
|
15
|
+
@options = args.extract_options!.stringify_keys
|
16
|
+
|
17
|
+
auto_detect!
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def translate(value)
|
23
|
+
case type
|
24
|
+
when :key
|
25
|
+
{"pre_mongified_#{name}" => value}
|
26
|
+
when :datetime
|
27
|
+
{"#{name}" => value.to_time}
|
28
|
+
else
|
29
|
+
{"#{self.name}" => value}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_print
|
34
|
+
"column \"#{name}\", :#{type}".tap do |output|
|
35
|
+
output_options = options.map{|k, v| (v == nil) ? nil : ":#{k} => \"#{v}\""}.compact
|
36
|
+
output << ", #{output_options.join(', ')}" unless output_options.blank?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(meth, *args, &blk)
|
41
|
+
method_name = meth.to_s.gsub("=", '')
|
42
|
+
if AVAILABLE_OPTIONS.include?(method_name)
|
43
|
+
class_eval <<-EOF
|
44
|
+
def #{method_name}=(value)
|
45
|
+
options['#{method_name}'] = value
|
46
|
+
end
|
47
|
+
def #{method_name}
|
48
|
+
options['#{method_name}']
|
49
|
+
end
|
50
|
+
EOF
|
51
|
+
send(meth, *args, &blk)
|
52
|
+
else
|
53
|
+
super(meth, *args, &blk)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def reference?
|
58
|
+
!self.options['references'].nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
#######
|
62
|
+
private
|
63
|
+
#######
|
64
|
+
|
65
|
+
def key?
|
66
|
+
self.type == :key
|
67
|
+
end
|
68
|
+
|
69
|
+
def auto_detect!
|
70
|
+
case name.downcase
|
71
|
+
when 'id'
|
72
|
+
@type = :key if self.type == :integer
|
73
|
+
when /(.*)_id/
|
74
|
+
self.references = $1.to_s.pluralize unless self.references
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'mongo'
|
2
|
+
module Mongify
|
3
|
+
module Database
|
4
|
+
#
|
5
|
+
# No sql connection configuration
|
6
|
+
#
|
7
|
+
class NoSqlConnection < Mongify::Database::BaseConnection
|
8
|
+
include Mongo
|
9
|
+
REQUIRED_FIELDS = %w{host database}
|
10
|
+
|
11
|
+
def initialize(options=nil)
|
12
|
+
super options
|
13
|
+
adapter 'mongodb' if adapter.nil? || adapter == 'mongo'
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection_string
|
17
|
+
"#{@adapter}://#{@host}#{":#{@port}" if @port}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
@database.present? && @host.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def connection
|
25
|
+
return @connection if @connection
|
26
|
+
@connection = Connection.new(host, port)
|
27
|
+
@connection.add_auth(database, username, password) if username && password
|
28
|
+
@connection
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_connection?
|
32
|
+
connection.connected?
|
33
|
+
end
|
34
|
+
|
35
|
+
def db
|
36
|
+
@db ||= connection[database]
|
37
|
+
end
|
38
|
+
|
39
|
+
def select_rows(collection)
|
40
|
+
db[collection].find
|
41
|
+
end
|
42
|
+
|
43
|
+
def insert_into(colleciton_name, row)
|
44
|
+
db[colleciton_name].insert(row, :safe => true)
|
45
|
+
end
|
46
|
+
|
47
|
+
def update(colleciton_name, id, attributes)
|
48
|
+
db[colleciton_name].update({"_id" => id}, attributes)
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_id_using_pre_mongified_id(colleciton_name, pre_mongified_id)
|
52
|
+
db[colleciton_name].find_one('pre_mongified_id' => pre_mongified_id).try(:[], '_id')
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def reset!
|
57
|
+
@connection = nil
|
58
|
+
@db = nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Mongify
|
2
|
+
module Database
|
3
|
+
#
|
4
|
+
# Sql connection configuration
|
5
|
+
#
|
6
|
+
class SqlConnection < Mongify::Database::BaseConnection
|
7
|
+
|
8
|
+
REQUIRED_FIELDS = %w{host adapter database}
|
9
|
+
|
10
|
+
def initialize(options=nil)
|
11
|
+
@prefixed_db = false
|
12
|
+
super(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup_connection_adapter
|
16
|
+
ActiveRecord::Base.establish_connection(self.to_hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid?
|
20
|
+
return false unless @adapter
|
21
|
+
if sqlite_adapter?
|
22
|
+
return true if @database
|
23
|
+
else
|
24
|
+
return super
|
25
|
+
end
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_connection?
|
30
|
+
setup_connection_adapter
|
31
|
+
connection.send(:connect) if ActiveRecord::Base.connection.respond_to?(:connect)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def connection
|
36
|
+
return nil unless has_connection?
|
37
|
+
ActiveRecord::Base.connection
|
38
|
+
end
|
39
|
+
|
40
|
+
def tables
|
41
|
+
return nil unless has_connection?
|
42
|
+
self.connection.tables
|
43
|
+
end
|
44
|
+
|
45
|
+
def columns_for(table_name)
|
46
|
+
self.connection.columns(table_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def select_rows(table_name)
|
50
|
+
self.connection.select_all("SELECT * FROM #{table_name}")
|
51
|
+
end
|
52
|
+
|
53
|
+
#######
|
54
|
+
private
|
55
|
+
#######
|
56
|
+
def sqlite_adapter?
|
57
|
+
@adapter && (@adapter.downcase == 'sqlite' || @adapter.downcase == 'sqlite3')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Mongify
|
2
|
+
module Database
|
3
|
+
#
|
4
|
+
# A representation of a sql table and how it should map to a no_sql system
|
5
|
+
#
|
6
|
+
class Table
|
7
|
+
|
8
|
+
attr_accessor :name
|
9
|
+
attr_reader :options, :columns
|
10
|
+
|
11
|
+
def initialize(name, *args, &block)
|
12
|
+
@columns = []
|
13
|
+
@column_lookup = {}
|
14
|
+
@options = args.extract_options!.stringify_keys
|
15
|
+
self.name = name
|
16
|
+
|
17
|
+
self.instance_exec(&block) if block_given?
|
18
|
+
|
19
|
+
import_columns
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
#Add a Database Column
|
25
|
+
def add_column(column)
|
26
|
+
raise Mongify::DatabaseColumnExpected, "Expected a Mongify::Database::Column" unless column.is_a?(Mongify::Database::Column)
|
27
|
+
add_column_index(column.name, @columns.size)
|
28
|
+
@columns << column
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def column(name, type=nil, options={})
|
33
|
+
options = type and type = nil if type.is_a?(Hash)
|
34
|
+
type = type.to_sym if type
|
35
|
+
add_column_index(name.to_s.downcase, @columns.size)
|
36
|
+
@columns << (col = Mongify::Database::Column.new(name, type, options))
|
37
|
+
col
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_column(name)
|
41
|
+
return nil unless (index = @column_lookup[name.to_s.downcase])
|
42
|
+
@columns[index]
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def reference_columns
|
47
|
+
@columns.reject{ |c| !c.reference? }
|
48
|
+
end
|
49
|
+
|
50
|
+
def translate(row)
|
51
|
+
new_row = {}
|
52
|
+
row.each do |key, value|
|
53
|
+
c = find_column(key)
|
54
|
+
new_row.merge!(c.present? ? c.translate(value) : {"#{key}" => value})
|
55
|
+
end
|
56
|
+
new_row
|
57
|
+
end
|
58
|
+
|
59
|
+
#######
|
60
|
+
private
|
61
|
+
#######
|
62
|
+
|
63
|
+
def add_column_index(name, index)
|
64
|
+
@column_lookup[name] = index
|
65
|
+
end
|
66
|
+
|
67
|
+
def import_columns
|
68
|
+
return unless import_columns = @options.delete('columns')
|
69
|
+
import_columns.each { |c| add_column(c) }
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Mongify
|
2
|
+
# File Not Found Exception
|
3
|
+
class FileNotFound < RuntimeError; end
|
4
|
+
class ConfigurationFileNotFound < FileNotFound; end
|
5
|
+
class TranslationFileNotFound < FileNotFound; end
|
6
|
+
|
7
|
+
class ConfigurationError < RuntimeError; end
|
8
|
+
class SqlConnectionRequired < ConfigurationError; end
|
9
|
+
class NoSqlConnectionRequired < ConfigurationError; end
|
10
|
+
class DatabaseColumnExpected < ConfigurationError; end
|
11
|
+
|
12
|
+
class RootMissing < RuntimeError; end
|
13
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'mongify/translation/printer'
|
2
|
+
require 'mongify/translation/process'
|
3
|
+
module Mongify
|
4
|
+
#
|
5
|
+
# Actually runs the translation from sql to no sql
|
6
|
+
#
|
7
|
+
class Translation
|
8
|
+
attr_reader :tables, :sql_connection, :no_sql_connection
|
9
|
+
|
10
|
+
include Printer
|
11
|
+
include Process
|
12
|
+
class << self
|
13
|
+
def parse(file_name)
|
14
|
+
translation = self.new
|
15
|
+
translation.instance_eval(File.read(file_name))
|
16
|
+
translation
|
17
|
+
end
|
18
|
+
|
19
|
+
def load(connection)
|
20
|
+
raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless connection.is_a?(Mongify::Database::SqlConnection)
|
21
|
+
return unless connection.has_connection?
|
22
|
+
translation = self.new
|
23
|
+
connection.tables.each do |t|
|
24
|
+
columns = []
|
25
|
+
connection.columns_for(t).each do |ar_col|
|
26
|
+
columns << Mongify::Database::Column.new(ar_col.name, ar_col.type, :default => ar_col.default)
|
27
|
+
end
|
28
|
+
translation.table(t, :columns => columns)
|
29
|
+
end
|
30
|
+
translation
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@tables = []
|
37
|
+
end
|
38
|
+
|
39
|
+
def table(table_name, options={}, &block)
|
40
|
+
table = Mongify::Database::Table.new(table_name, options, &block)
|
41
|
+
@tables << table
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_table(table)
|
45
|
+
@tables << table
|
46
|
+
end
|
47
|
+
|
48
|
+
#######
|
49
|
+
private
|
50
|
+
#######
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Mongify
|
2
|
+
#
|
3
|
+
# Actually runs the translation from sql to no sql
|
4
|
+
#
|
5
|
+
class Translation
|
6
|
+
module Printer
|
7
|
+
def print
|
8
|
+
''.tap do |output|
|
9
|
+
@tables.each do |t|
|
10
|
+
output << %Q[table "#{t.name}" do\n]
|
11
|
+
t.columns.each do |c|
|
12
|
+
output << "\t#{c.to_print}\n"
|
13
|
+
end
|
14
|
+
output << "end\n\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Mongify
|
2
|
+
#
|
3
|
+
# This will take the Translation and do the processing on it
|
4
|
+
#
|
5
|
+
class Translation
|
6
|
+
module Process
|
7
|
+
def sql_connection=(value)
|
8
|
+
@sql_connection=value
|
9
|
+
end
|
10
|
+
def no_sql_connection=(value)
|
11
|
+
@no_sql_connection=value
|
12
|
+
end
|
13
|
+
def process(sql_connection, no_sql_connection)
|
14
|
+
raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless sql_connection.is_a?(Mongify::Database::SqlConnection)
|
15
|
+
raise Mongify::NoSqlConnectionRequired, "Can only write to Mongify::Database::NoSqlConnection" unless no_sql_connection.is_a?(Mongify::Database::NoSqlConnection)
|
16
|
+
|
17
|
+
self.sql_connection = sql_connection
|
18
|
+
raise "SQL Connection is not valid" unless self.sql_connection.valid?
|
19
|
+
self.no_sql_connection = no_sql_connection
|
20
|
+
raise "noSql Connection is not valid" unless self.no_sql_connection.valid?
|
21
|
+
|
22
|
+
copy
|
23
|
+
update_reference_ids
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
#######
|
28
|
+
private
|
29
|
+
#######
|
30
|
+
|
31
|
+
def copy
|
32
|
+
self.tables.each do |t|
|
33
|
+
sql_connection.select_rows(t.name).each do |row|
|
34
|
+
no_sql_connection.insert_into(t.name, t.translate(row))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_reference_ids
|
40
|
+
self.tables.each do |t|
|
41
|
+
no_sql_connection.select_rows(t.name).each do |row|
|
42
|
+
id = row["_id"]
|
43
|
+
attributes = {}
|
44
|
+
t.reference_columns.each do |c|
|
45
|
+
new_id = no_sql_connection.get_id_using_pre_mongified_id(c.references.to_s, row[c.name])
|
46
|
+
attributes.merge!(c.name => new_id) unless new_id.nil?
|
47
|
+
end
|
48
|
+
no_sql_connection.update(t.name, id, {"$set" => attributes}) unless attributes.blank?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Process that needs to take place
|
59
|
+
# import the data (moving the id to premongified_id)
|
60
|
+
# fix all the references to the new ids
|
61
|
+
#
|
data/lib/mongify/ui.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Mongify
|
2
|
+
#
|
3
|
+
# Used to output messages to the UI
|
4
|
+
#
|
5
|
+
class UI
|
6
|
+
class << self
|
7
|
+
def puts(msg)
|
8
|
+
out_stream.puts(msg) if out_stream
|
9
|
+
end
|
10
|
+
|
11
|
+
def print(msg)
|
12
|
+
out_stream.print(msg) if out_stream
|
13
|
+
end
|
14
|
+
|
15
|
+
def gets
|
16
|
+
in_stream ? in_stream.gets : ''
|
17
|
+
end
|
18
|
+
|
19
|
+
def request(msg)
|
20
|
+
print(msg)
|
21
|
+
gets.chomp
|
22
|
+
end
|
23
|
+
|
24
|
+
def ask(msg)
|
25
|
+
request("#{msg} [yn] ") == 'y'
|
26
|
+
end
|
27
|
+
|
28
|
+
def warn(msg)
|
29
|
+
puts "WARNING: #{msg}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def abort(message)
|
33
|
+
UI.puts "PROGRAM HALT: #{message}"
|
34
|
+
Kernel.abort message
|
35
|
+
end
|
36
|
+
|
37
|
+
def in_stream
|
38
|
+
Configuration.in_stream
|
39
|
+
end
|
40
|
+
def out_stream
|
41
|
+
Configuration.out_stream
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|