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