rigrate 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 187fa5716dd1625fe30f8a5d1fcab945cf1b8293
4
+ data.tar.gz: 0bdffca0c4724b9d1a36b36c3a8f46dc54fd0b52
5
+ SHA512:
6
+ metadata.gz: 5a5af45d6cade3df5147184238d8ad3ae41b0afdd0c399d4789fe4b6820a345205c742ba9f8ba4b9b4e1d94573d9844f0c058d1d9647c70322a420de539a8c48
7
+ data.tar.gz: e281b403611bef6f7fa738ad3cf64a153b2a57a91c1e35027582c47435cfb9d6003a6e801e6edc79362a33446a485f91a779d6d4f4f8a4fd7fd42efb8805169f
data/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # Rigrate
2
+
3
+ ## Overview
4
+ This gem is used to migrate data between different data source in an easy way. SQLite3、MySQL、Oracle supported in current.
5
+
6
+ ## Installation
7
+ ```ruby
8
+ gem install rigrate
9
+ ```
10
+
11
+ if you are using oracle, you need install instant oracle clent at least.
12
+
13
+ ## Basic Usage
14
+
15
+ An easy example, migrate entries hrdb users table to accounts table in oadb.
16
+
17
+ Firstly, Write the migrate script
18
+
19
+ ```ruby
20
+ # file : hr_users_to_oa_accounts.rigrate
21
+
22
+ # define the data sources, use URI pattern like DB://USR:PWD@HOST/DBNAME
23
+ ds :hr, "oracle://scott:tiger@hrdb"
24
+ ds :oa, "mysql://root:testpwd@127.0.0.1:oadb"
25
+
26
+ # migrate snippet code
27
+ from hr.users to oa.accounts
28
+ ```
29
+
30
+ Then, you can run as following:
31
+
32
+ ```shell
33
+ $ rigrate execute -f hr_users_to_oa_accounts.rigrate
34
+ ```
35
+
36
+ ## More Examples
37
+
38
+ 1. Using native SQL. Just migrate DepNO is D00001 users to target data source.
39
+
40
+ ```ruby
41
+ from
42
+ hr.sql("select * from users where deptno = 'D00001'")
43
+ to
44
+ oa.accounts
45
+ ```
46
+
47
+ 2. Specify the sync condition columns. When the migration has condition columns, it will update target data instead of delete the records directly.
48
+
49
+ ```ruby
50
+ from
51
+ hr.sql("select * from users")
52
+ to
53
+ oa.accounts
54
+ on :user_code => :job_code
55
+ ```
56
+
57
+ 3. Just migrate a part of entry source columns. In this situation migration with one condition is advised.
58
+
59
+ ```ruby
60
+ from
61
+ hr.users(:user_code, :name, :age, :passwd)
62
+ to
63
+ oa.account(:job_code, :name, :age, :password)
64
+ on :user_code => :job_code
65
+ ```
66
+ 4. Union two data source.
67
+
68
+ ```ruby
69
+ from
70
+ hr.users union oa.accounts
71
+ to
72
+ erp.users
73
+ ```
74
+ 5. Minus two data source
75
+
76
+ ```ruby
77
+ from
78
+ hr.users minus oa.accounts
79
+ to
80
+ erp.users
81
+ ```
82
+
83
+ 6. Join two data source. JOIN must with condition.
84
+
85
+ ```ruby
86
+ from
87
+ hr.users join oa.account on :user_code => :job_code
88
+ to
89
+ erp.users
90
+ ```
91
+
92
+ 7. Data source select with after hooks
93
+
94
+ ```ruby
95
+ from
96
+ hr.users(:user_code, :user_name, :password, :age) do |row|
97
+ row[2] = "useless_password" # make password useless
98
+ row[3] = 16 # make everyone 16 forever :-)
99
+ end
100
+ to
101
+ oa.accounts(:job_code, :name, :pwd, :age)
102
+ on :user_code => :job_code
103
+ ```
104
+
105
+ ## Advance Tips
106
+
107
+ 1. Sync Mode
108
+ Rigrate support support three mode, inspired by `SyncToy` a file sync tool by Microsoft.
109
+
110
+ **:echo** is the default mode, will delete all records of target ds which not exists in source ds
111
+ **:contrbiute** is same as :echo, but keep the records even it deleted in source ds
112
+ **:sync** mode will make two side of migration the same
113
+
114
+ use :contribute mode in migration:
115
+ ```ruby
116
+ from hr.users to oa.users mode :contribute
117
+ ```
118
+
119
+ 2. Using Transaction
120
+ Rigrate will do not use transaction as default. Switch it on, you can do it througt command line `--strict`.
121
+
122
+ ```shell
123
+ $ rigrate -f script.rigrate --strict
124
+ ```
125
+
126
+ 3. Using Rigrate in other ruby script
127
+
128
+ ```ruby
129
+ require 'rigrate'
130
+
131
+ str =<<EOS
132
+ ds :hr, "oracle://scott:tiger@hrdb"
133
+ ds :oa, "mysql://root:testpwd@127.0.0.1:oadb"
134
+
135
+ from hr.users to oa.accounts
136
+ EOS
137
+
138
+ parser = Rigrate::Parser.new
139
+ parser.lex(str).parsing
140
+ ```
141
+ 4. QuickScript. a fast way to operate data, Rigrate will load a predefined data sources file in {home}/.rigrate/ds.
142
+ you can define some frequently used data source, and run in migration throught command line. like blow:
143
+
144
+ ```shell
145
+ $ rigrate execute -c "from hr.users to oa.accounts"
146
+ ```
147
+
148
+ for more usage to check `Rigrate help`
149
+
150
+ ## TODO Lists
151
+
152
+ 1. Integrate with schedule framework
153
+ 2. Add pg/sql sever/access/csv support
154
+ 3. Row data type and length checking
155
+ 4. Rigrate file checking
156
+ 5. More logger details
157
+
158
+ ## Others
159
+
160
+ None :-)
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ gem "minitest"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc 'Run tests'
9
+ task :default => :test
data/bin/rigrate ADDED
@@ -0,0 +1,69 @@
1
+ #! ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && ! $LOAD_PATH.include?(lib)
5
+
6
+ require 'thor'
7
+ require 'rigrate'
8
+
9
+ class RigrateApp < Thor
10
+ desc 'execute', 'execute a task.'
11
+ method_option :file, :aliases => '-f',
12
+ :type => :string,
13
+ :desc => 'script file path.'
14
+ method_option :mode, :aliases => '-m',
15
+ :type => :string,
16
+ :default => :echo,
17
+ :desc => "migration mode, :echo and :contribute support in current"
18
+ method_option :loglevel, :aliases => '-l',
19
+ :type => :string,
20
+ :default => "999",
21
+ :desc => "Do you want to log the actions? using number 1~5 or info/warn/debug... to this option"
22
+ method_option :stdout, :aliases => '-o',
23
+ :type => :boolean,
24
+ :default => false,
25
+ :desc => "switch on to using STDOUT in prompt."
26
+ method_option :strict, :aliases => '-s',
27
+ :type => :boolean,
28
+ :default => false,
29
+ :desc => "switch the option on will using db transaction to commit"
30
+ method_option :script, :aliases => '-c',
31
+ :type => :string,
32
+ :desc => "eval migration script in command line. using predefined DataSource in default ds file"
33
+ method_option :dir, :aliases => '-d',
34
+ :type => :string,
35
+ :desc => "directory of rigrate script"
36
+ def execute
37
+ # format all key from symbol to string
38
+ opts = options.inject({}) do |hash, (k, v)|
39
+ hash[k.to_sym] = v
40
+ hash
41
+ end
42
+
43
+ opts[:loglevel] = format_loglevel(opts[:loglevel])
44
+ Rigrate.run(opts)
45
+ end
46
+
47
+ desc "verify", "verify a file is right or not"
48
+ method_option :file, :aliases => '-f',
49
+ :desc => "the file to be verified"
50
+ def verify
51
+
52
+ end
53
+
54
+ no_commands {
55
+ def format_loglevel(level)
56
+ level = level.to_s.downcase
57
+ return level.to_i if level =~ /[0-5]+/
58
+
59
+ ['debug', 'info', 'warn', 'error', 'fatal', 'unknown'].each_with_index do |ele, idx|
60
+ return idx if ele == level.to_s.downcase
61
+ end
62
+
63
+ 999
64
+ end
65
+ }
66
+
67
+ end
68
+
69
+ RigrateApp.start
data/lib/rigrate.rb ADDED
@@ -0,0 +1,97 @@
1
+ # encoding : utf-8
2
+
3
+ require 'logger'
4
+
5
+ require 'rigrate/data_source'
6
+ require 'rigrate/error'
7
+ require 'rigrate/interface'
8
+ require 'rigrate/migration'
9
+ require 'rigrate/parser'
10
+
11
+ module Rigrate
12
+
13
+ ##
14
+ # used to output logger to mutli place
15
+ #
16
+ class MutiOutput
17
+ def initialize(file, prompt)
18
+ @target = []
19
+ @target << file
20
+ @target << STDOUT if prompt
21
+ end
22
+
23
+ def write(*args)
24
+ @target.each { |dest| dest.write(*args) }
25
+ end
26
+
27
+ def close
28
+ @target.each(&:close)
29
+ end
30
+ end
31
+
32
+ # create rigrate main path
33
+ path = File.join(Dir.home, ".rigrate")
34
+ Dir.mkdir path unless (Dir.exist? path)
35
+
36
+ @config = {
37
+ :loglevel => 999, # default logger not show
38
+ :logpath => path,
39
+ :mode => :echo,
40
+ :strict => false,
41
+ :stdout => false,
42
+ :ds => File.join(Dir.home, ".rigrate/ds"),
43
+ :script => nil,
44
+ :file => nil,
45
+ }
46
+
47
+ def self.run(opts = {})
48
+ scripts = []
49
+ configure(opts)
50
+
51
+ # read all scripts <- from command line , file script , rigrate dir
52
+ scripts << opts[:script] if opts[:script]
53
+ scripts << File.read(opts[:file]) if opts[:file]
54
+ if Dir.exist? opts[:dir].to_s
55
+ Dir["#{File.join(opts[:dir], '*')}"].each do |item|
56
+ scripts << File.read(item)
57
+ end
58
+ end
59
+
60
+ raise RigrateError.new("your should specify one script at least.") if scripts.size <= 0
61
+
62
+ parser = Parser.new
63
+ # loading data source
64
+ if File.exist? config[:ds]
65
+ parser.lex(File.read(config[:ds])).parsing
66
+ end
67
+
68
+ scripts.each do |script|
69
+ parser.lex(script).parsing
70
+ end
71
+ end
72
+
73
+ def self.logger
74
+ logger_file = File.open(File.join(config[:logpath],logger_name), 'a')
75
+
76
+ @log = Logger.new MutiOutput.new(logger_file, config[:stdout])
77
+ @log.level = config[:loglevel]
78
+
79
+ @log
80
+ end
81
+
82
+ def self.configure(opts)
83
+ opts.each do |k, v|
84
+ raise ParserError.new("arguments #{k} not valid.") unless config.keys.include? k.to_sym
85
+ end
86
+
87
+ config.merge! opts
88
+ end
89
+
90
+ def self.config
91
+ @config
92
+ end
93
+
94
+ def self.logger_name
95
+ "#{Time.now.strftime('%Y-%m-%d')}.log"
96
+ end
97
+ end
@@ -0,0 +1,43 @@
1
+ # encoding : utf-8
2
+
3
+ require 'uri'
4
+
5
+ module Rigrate
6
+ class DataSource
7
+ attr_accessor :dbh
8
+
9
+ def initialize(conn_uri)
10
+ uri = URI.parse(conn_uri)
11
+ @dbh = eval(uri.scheme.capitalize).new conn_uri
12
+ end
13
+
14
+ def sql(str, *args)
15
+ begin
16
+ @dbh.select(str, *args)
17
+ rescue SQLite3::SQLException => e
18
+ puts "DB: #{@dbh.inspect} SQL: #{str}"
19
+ raise e
20
+ end
21
+ end
22
+
23
+ def method_missing(mth, *args, &block)
24
+ begin
25
+ table_name = mth
26
+ columns = args
27
+
28
+ str_sql = build_sql(table_name, *columns)
29
+ @dbh.select(str_sql, &block)
30
+ rescue Exception => e
31
+ raise e
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def build_sql(table, *columns)
38
+ columns = ['*'] if columns.size == 0
39
+
40
+ "select #{columns.map(&:to_s).join(',')} from #{table}"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ # encoding : utf-8
2
+
3
+ module Rigrate
4
+ class BasicError < ::StandardError; end
5
+
6
+ class RigrateError < BasicError; end
7
+ class ParserError < BasicError; end
8
+ class InterfaceError < BasicError; end
9
+ class ResultSetError < BasicError; end
10
+ class DirverError < BasicError; end
11
+ end
@@ -0,0 +1,20 @@
1
+ # encoding : utf-8
2
+
3
+ require 'uri'
4
+ require 'rigrate/interface/driver'
5
+ require 'rigrate/interface/result_set'
6
+ require 'rigrate/interface/row'
7
+ require 'rigrate/interface/sqlite'
8
+ require 'rigrate/interface/mysql'
9
+ require 'rigrate/interface/oracle'
10
+
11
+ module Rigrate
12
+ def self.lazy_load_driver(driver_name)
13
+ driver_path = "./interface/#{driver_name}"
14
+ if File.exists? driver_path
15
+ raise InterfaceError.new('Driver not found.')
16
+ end
17
+
18
+ require driver_path
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ # encoding : utf-8
2
+
3
+ module Rigrate
4
+ # defination for column include name and type
5
+ # column name is a string
6
+ Column = Struct.new(:name, :type)
7
+
8
+ class Driver
9
+ attr_accessor :db
10
+
11
+ def connect(db)
12
+ self.db = db
13
+ end
14
+
15
+ def method_missing(mth, *args, &block)
16
+ @db.send(mth, *args, &block)
17
+ end
18
+
19
+ def extract_conn_param(uri)
20
+ opts = {}
21
+ opts['db_type'] = uri.scheme if uri.scheme
22
+ opts['host'] = uri.host if uri.host
23
+ opts['username'] = uri.user if uri.user
24
+ opts['password'] = uri.password if uri.password
25
+ opts['port'] = uri.port if uri.port
26
+ opts['db_name'] = uri.path.tr('/','') if uri.path.tr('/','').size > 0
27
+
28
+ opts
29
+ end
30
+
31
+ def to_native_row(row, column_info)
32
+ row
33
+ end
34
+
35
+ def extract_tbl_from_sql(sql_str)
36
+ return $1 if sql_str =~ /from\s+(\w*)\s*/
37
+
38
+ raise Exception.new('a lastest one table name must specify.')
39
+ end
40
+ end
41
+ end