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 +7 -0
- data/README.md +160 -0
- data/Rakefile +9 -0
- data/bin/rigrate +69 -0
- data/lib/rigrate.rb +97 -0
- data/lib/rigrate/data_source.rb +43 -0
- data/lib/rigrate/error.rb +11 -0
- data/lib/rigrate/interface.rb +20 -0
- data/lib/rigrate/interface/driver.rb +41 -0
- data/lib/rigrate/interface/mysql.rb +148 -0
- data/lib/rigrate/interface/oracle.rb +172 -0
- data/lib/rigrate/interface/result_set.rb +389 -0
- data/lib/rigrate/interface/row.rb +69 -0
- data/lib/rigrate/interface/sqlite.rb +106 -0
- data/lib/rigrate/migration.rb +89 -0
- data/lib/rigrate/parser.rb +270 -0
- data/rigrate.gemspec +21 -0
- data/test/test_datasource.rb +66 -0
- data/test/test_driver.rb +45 -0
- data/test/test_driver_mysql.rb +87 -0
- data/test/test_driver_oracle.rb +109 -0
- data/test/test_driver_sqlite.rb +85 -0
- data/test/test_helper.rb +9 -0
- data/test/test_migration.rb +108 -0
- data/test/test_parser.rb +262 -0
- data/test/test_resultset.rb +300 -0
- data/test/test_rigrate.rb +6 -0
- data/test/test_rigrate_row.rb +51 -0
- metadata +124 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
# encoding : utf-8
|
2
|
+
|
3
|
+
require 'mysql'
|
4
|
+
|
5
|
+
module Rigrate
|
6
|
+
class Mysql < Driver
|
7
|
+
attr_accessor :transaction_active
|
8
|
+
|
9
|
+
def initialize(uri)
|
10
|
+
default_opts = {
|
11
|
+
host: '127.0.0.1',
|
12
|
+
user: nil,
|
13
|
+
passwd: nil,
|
14
|
+
db: nil,
|
15
|
+
port: 3306,
|
16
|
+
socket: nil,
|
17
|
+
flag: 0
|
18
|
+
}
|
19
|
+
|
20
|
+
extract_from_db_path(uri).each do |k, v|
|
21
|
+
default_opts[k.to_sym] = v if default_opts.keys.include? k.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
@db = ::Mysql.connect(*default_opts.values)
|
25
|
+
@transaction_active = false
|
26
|
+
end
|
27
|
+
|
28
|
+
def select(sql, *args)
|
29
|
+
target_tbl_name = extract_tbl_from_sql(sql)
|
30
|
+
|
31
|
+
ResultSet.new.tap do |rs|
|
32
|
+
stm = @db.prepare(sql)
|
33
|
+
rs.db = self
|
34
|
+
rs.target_tbl_name = target_tbl_name
|
35
|
+
rs.column_info = statement_fields(stm)
|
36
|
+
result = stm.execute(*args)
|
37
|
+
rs.rows = []
|
38
|
+
while row = result.fetch
|
39
|
+
new_row = Row.new(to_rb_row(row))
|
40
|
+
yield new_row if block_given?
|
41
|
+
rs.rows << new_row
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def save(resultset)
|
47
|
+
resultset.db = self
|
48
|
+
|
49
|
+
resultset.save!
|
50
|
+
end
|
51
|
+
|
52
|
+
def update(sql, *args)
|
53
|
+
begin
|
54
|
+
stm = @db.prepare sql
|
55
|
+
args.each do |row|
|
56
|
+
if Rigrate::Row === row
|
57
|
+
row = row.data
|
58
|
+
end
|
59
|
+
stm.execute(*row)
|
60
|
+
end
|
61
|
+
rescue Exception => e
|
62
|
+
Rigrate.logger.error("execute SQL [#{sql}] ARGS [#{args.size}] -> #{e.backtrace.join('\n')}")
|
63
|
+
raise DriverError.new("execute error #{e.message}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
alias :insert :update
|
67
|
+
alias :delete :update
|
68
|
+
|
69
|
+
def primary_key(tbl_name)
|
70
|
+
tbl_name = tbl_name.to_s
|
71
|
+
|
72
|
+
db.list_fields(tbl_name).fetch_fields.select do |field|
|
73
|
+
field.is_pri_key?
|
74
|
+
end.map(&:name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def statement_fields(stm)
|
78
|
+
cols = []
|
79
|
+
|
80
|
+
stm.result_metadata.fetch_fields.each do |field|
|
81
|
+
cols << Column.new(field.name, get_field_type(field.type))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_rb_row(mysql_row)
|
86
|
+
mysql_row.map do |field|
|
87
|
+
if ::Mysql::Time === field
|
88
|
+
field.to_s
|
89
|
+
else
|
90
|
+
field
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def format_sql_args(args)
|
96
|
+
args.map do |arg|
|
97
|
+
if String === arg
|
98
|
+
"'#{arg}'"
|
99
|
+
elsif DateTime === arg
|
100
|
+
arg.strptime('%Y-%m-%d %H:%M:%S')
|
101
|
+
else
|
102
|
+
arg
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_field_type(num)
|
108
|
+
::Mysql::Field.constants.select do |cons|
|
109
|
+
cons if ::Mysql::Field.const_get(cons) == num
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def extract_from_db_path(uri)
|
114
|
+
uri = URI.parse(uri)
|
115
|
+
args = {}
|
116
|
+
|
117
|
+
args[:host] = uri.host if uri.host
|
118
|
+
args[:user] = uri.user if uri.user
|
119
|
+
args[:passwd] = uri.password if uri.password
|
120
|
+
args[:port] = uri.port if uri.port
|
121
|
+
args[:scheme] = uri.scheme if uri.scheme
|
122
|
+
args[:db] = uri.path.sub('/','') if uri.path.size > 1
|
123
|
+
|
124
|
+
args
|
125
|
+
end
|
126
|
+
|
127
|
+
def transaction_active?
|
128
|
+
@transaction_active
|
129
|
+
end
|
130
|
+
|
131
|
+
def transaction
|
132
|
+
@db.autocommit false
|
133
|
+
@transaction_active = true
|
134
|
+
end
|
135
|
+
|
136
|
+
def commit
|
137
|
+
@db.commit
|
138
|
+
@db.autocommit true
|
139
|
+
@transaction_active = false
|
140
|
+
end
|
141
|
+
|
142
|
+
def rollback
|
143
|
+
@db.rollback
|
144
|
+
@db.autocommit true
|
145
|
+
@transaction_active = false
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# encoding : utf-8
|
2
|
+
|
3
|
+
require 'oci8'
|
4
|
+
|
5
|
+
module Rigrate
|
6
|
+
class Oracle < Driver
|
7
|
+
def initialize(uri)
|
8
|
+
key_params = [:username, :password, :sid, :privilege]
|
9
|
+
opts = params_pack(extract_from_db_path(uri), key_params)
|
10
|
+
@db = OCI8.new(*opts.values)
|
11
|
+
@db.autocommit = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def select(sql, *args)
|
15
|
+
target_tbl_name = extract_tbl_from_sql(sql)
|
16
|
+
sql = convert_question_mark_to_symbol(sql, args.size)
|
17
|
+
|
18
|
+
ResultSet.new.tap do |rs|
|
19
|
+
cursor = @db.parse(sql)
|
20
|
+
rs.db = self
|
21
|
+
rs.target_tbl_name = target_tbl_name
|
22
|
+
rs.rows = []
|
23
|
+
cursor.exec(*args)
|
24
|
+
while row = cursor.fetch
|
25
|
+
new_row = Row.new(to_rb_row(row))
|
26
|
+
yield new_row if block_given?
|
27
|
+
rs.rows << new_row
|
28
|
+
end
|
29
|
+
rs.column_info = statement_fields(cursor)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute(sql, *args)
|
34
|
+
sql = convert_question_mark_to_symbol(sql, args.first.size)
|
35
|
+
begin
|
36
|
+
cursor = @db.parse(sql)
|
37
|
+
args.each do |row|
|
38
|
+
if Rigrate::Row === row
|
39
|
+
row = row.data
|
40
|
+
end
|
41
|
+
cursor.exec(*row)
|
42
|
+
end
|
43
|
+
rescue Exception => e
|
44
|
+
Rigrate.logger.error("execute SQL [#{sql}] ARGS [#{args.size}] -> #{e.backtrace.join('\n')}")
|
45
|
+
raise DriverError.new("execute error #{e.message}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
alias :insert :execute
|
49
|
+
alias :update :execute
|
50
|
+
alias :delete :execute
|
51
|
+
|
52
|
+
def statement_fields(cursor)
|
53
|
+
cols = []
|
54
|
+
|
55
|
+
cursor.column_metadata.each do |field|
|
56
|
+
cols << Column.new(field.name, field.data_type)
|
57
|
+
end
|
58
|
+
|
59
|
+
cols
|
60
|
+
end
|
61
|
+
|
62
|
+
def convert_question_mark_to_symbol(sql, param_size)
|
63
|
+
param_size.times do |idx|
|
64
|
+
sql.sub!('?', ":#{idx + 1}")
|
65
|
+
end
|
66
|
+
|
67
|
+
sql
|
68
|
+
end
|
69
|
+
|
70
|
+
# TODO add blob/clob support
|
71
|
+
def to_native_row(row, column_info)
|
72
|
+
column_info.each_with_index do |col_info, idx|
|
73
|
+
case col_info.type.to_s.downcase.to_sym
|
74
|
+
when :blob
|
75
|
+
new_val = OCI8::BLOB.new(@db, row[idx])
|
76
|
+
when :clob
|
77
|
+
new_val = OCI8::CLOB.new(@db, row[idx])
|
78
|
+
when :bclob
|
79
|
+
new_val = OCI8::NCLOB.new(@db, row[idx])
|
80
|
+
when :date
|
81
|
+
if row[idx]
|
82
|
+
if Time === row[idx]
|
83
|
+
new_val = row[idx]
|
84
|
+
elsif String === row[idx] && row[idx].size > 0
|
85
|
+
new_val = Time.parse(row[idx])
|
86
|
+
end
|
87
|
+
else
|
88
|
+
new_val = ''
|
89
|
+
end
|
90
|
+
else
|
91
|
+
new_val = row[idx]
|
92
|
+
end
|
93
|
+
|
94
|
+
if new_val.nil?
|
95
|
+
new_val = ''
|
96
|
+
end
|
97
|
+
|
98
|
+
row.[]=(idx, new_val, false)
|
99
|
+
end
|
100
|
+
|
101
|
+
row
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_rb_row(row)
|
105
|
+
row.map do |field|
|
106
|
+
type = field.class
|
107
|
+
if [OCI8::BLOB, OCI8::CLOB, OCI8::NCLOB].include? type
|
108
|
+
field.read
|
109
|
+
elsif Time == type
|
110
|
+
field.to_s
|
111
|
+
else
|
112
|
+
field
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# oracle://scott:tiger@foctest?privilege=:SYSOPER
|
118
|
+
def extract_from_db_path(uri)
|
119
|
+
uri = URI.parse(uri)
|
120
|
+
args = {}
|
121
|
+
|
122
|
+
args[:username] = uri.user if uri.user
|
123
|
+
args[:password] = uri.password if uri.password
|
124
|
+
args[:sid] = uri.host if uri.host
|
125
|
+
URI::decode_www_form(uri.query.to_s).to_h.each do |key, val|
|
126
|
+
args[key] = val
|
127
|
+
end
|
128
|
+
args
|
129
|
+
end
|
130
|
+
|
131
|
+
def primary_key(tbl_name)
|
132
|
+
str =<<SQL
|
133
|
+
select a.column_name
|
134
|
+
from user_cons_columns a, user_constraints b
|
135
|
+
where a.constraint_name = b.constraint_name
|
136
|
+
and b.constraint_type = 'P'
|
137
|
+
and a.table_name = '#{tbl_name}'
|
138
|
+
SQL
|
139
|
+
|
140
|
+
result = []
|
141
|
+
@db.exec(str) do |row|
|
142
|
+
result << row.first
|
143
|
+
end
|
144
|
+
|
145
|
+
result
|
146
|
+
end
|
147
|
+
|
148
|
+
def params_pack(hash, keys)
|
149
|
+
keys.each do |key|
|
150
|
+
hash.delete(key) unless hash.keys.include? key
|
151
|
+
end
|
152
|
+
|
153
|
+
hash
|
154
|
+
end
|
155
|
+
|
156
|
+
def transaction_active?
|
157
|
+
! @db.autocommit?
|
158
|
+
end
|
159
|
+
|
160
|
+
def transaction
|
161
|
+
@db.autocommit = false
|
162
|
+
end
|
163
|
+
|
164
|
+
def commit
|
165
|
+
@db.commit
|
166
|
+
end
|
167
|
+
|
168
|
+
def rollback
|
169
|
+
@db.rollbak
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,389 @@
|
|
1
|
+
# encoding : utf-8
|
2
|
+
|
3
|
+
module Rigrate
|
4
|
+
class ResultSet
|
5
|
+
attr_accessor :db, :target_tbl_name
|
6
|
+
attr_accessor :rows
|
7
|
+
attr_accessor :column_info
|
8
|
+
|
9
|
+
# join two table by given field { :jc => :job_code }
|
10
|
+
def join(source_rs, key_fields = {})
|
11
|
+
if key_fields.size <= 0
|
12
|
+
raise ResultSetError.new("must specify the join condition.")
|
13
|
+
end
|
14
|
+
|
15
|
+
# convert condition key and value to string
|
16
|
+
key_fields = key_fields.inject({}) do |h, (k, v)|
|
17
|
+
h[k.to_s.upcase] = v.to_s.upcase
|
18
|
+
h
|
19
|
+
end
|
20
|
+
|
21
|
+
origin_rs_idx = column_idx(*key_fields.keys)
|
22
|
+
source_rs_idx = source_rs.column_idx(*key_fields.values)
|
23
|
+
|
24
|
+
ResultSet.new.tap do |rs|
|
25
|
+
# remove duplicate column header, base on column name
|
26
|
+
addtion_column_info = source_rs.column_info.dup.delete_if do |col|
|
27
|
+
key_fields.values.include? col.name.upcase
|
28
|
+
end
|
29
|
+
|
30
|
+
rs.column_info = @column_info + addtion_column_info
|
31
|
+
|
32
|
+
rs.rows = @rows.inject([]) do |new_rows, row|
|
33
|
+
origin_rs_key_values = row.values(*origin_rs_idx)
|
34
|
+
|
35
|
+
selected_source_rs_row = source_rs.rows.select do |r|
|
36
|
+
r.values(*source_rs_idx) == origin_rs_key_values
|
37
|
+
end
|
38
|
+
|
39
|
+
# remove duplicate data columns
|
40
|
+
selected_source_rs_row.map! do |r|
|
41
|
+
data = []
|
42
|
+
r.data.each_with_index do |value, idx|
|
43
|
+
data << value unless source_rs_idx.include? idx
|
44
|
+
end
|
45
|
+
|
46
|
+
Row.new data, RowStatus::NEW
|
47
|
+
end
|
48
|
+
# this is a left join.
|
49
|
+
if selected_source_rs_row.size > 0
|
50
|
+
selected_source_rs_row.each do |t_row|
|
51
|
+
new_rows << Row.new(row.data + t_row.data, RowStatus::NEW)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
new_rows << row.dup.fill_with_nil(addtion_column_info.size)
|
55
|
+
end
|
56
|
+
|
57
|
+
new_rows
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# union two result set , columns defination will not change
|
63
|
+
# default is union all style
|
64
|
+
def union(target, opts = {})
|
65
|
+
src_col_size = column_info.size
|
66
|
+
target_col_size = target.column_info.size
|
67
|
+
|
68
|
+
# TODO need type checking?
|
69
|
+
|
70
|
+
if src_col_size > target_col_size
|
71
|
+
target.rows = target.rows.map do |row|
|
72
|
+
row.fill_with_nil(src_col_size - target_col_size)
|
73
|
+
end
|
74
|
+
elsif src_col_size < target_col_size
|
75
|
+
target.rows = target.rows.map { |row| row[0...src_col_size] }
|
76
|
+
end
|
77
|
+
|
78
|
+
@rows += target.rows
|
79
|
+
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def -(target, opts = {})
|
84
|
+
src_col_size = column_info.size
|
85
|
+
target_col_size = target.column_info.size
|
86
|
+
|
87
|
+
# checking type?
|
88
|
+
#columns size must equal
|
89
|
+
if src_col_size != target_col_size
|
90
|
+
raise ResultSetError.new('minus must be used between column size equaled.')
|
91
|
+
end
|
92
|
+
@rows.reject! do |row|
|
93
|
+
target.include? row
|
94
|
+
end
|
95
|
+
|
96
|
+
self
|
97
|
+
end
|
98
|
+
alias :minus :-
|
99
|
+
|
100
|
+
#
|
101
|
+
# There are three modes
|
102
|
+
# :echo is left to right ResultSet, and delete right which is not in left
|
103
|
+
# +When condition is nil+
|
104
|
+
# find the primary key as condition
|
105
|
+
# 1. if primary key can't find in ResultSet then delete all record in right side, and insert
|
106
|
+
# all rows to left side
|
107
|
+
# +When condition is not nil+
|
108
|
+
# 1. using condition to update existing row in right side
|
109
|
+
# 2. and insert rows which not included in right side
|
110
|
+
# 3. then delete rows not in left side
|
111
|
+
#
|
112
|
+
# :contribute is left to right, and KEEP the file even it not in left
|
113
|
+
# +When condition is nil+
|
114
|
+
# find the primary key as condition
|
115
|
+
# 1. if primary key can't find in Result then insert all rows to left side
|
116
|
+
# +When condition is not nil+
|
117
|
+
# 1. using condition to update existing row in right side
|
118
|
+
# 2. and insert rows not included in right side
|
119
|
+
#
|
120
|
+
# :sync will keep left and right the same +TODO+
|
121
|
+
# just keep two side the same
|
122
|
+
#
|
123
|
+
def migrate_from(src_rs, condition = nil, opts = {})
|
124
|
+
Rigrate.logger.info("start migration : source rs [#{src_rs.size}] ->target rs [#{rows.size}]")
|
125
|
+
mode = opts[:mode]
|
126
|
+
condition = eval "{#{condition.to_s.upcase}}" unless condition.nil?
|
127
|
+
|
128
|
+
if condition
|
129
|
+
src_cols_idx = src_rs.column_idx(*condition.keys)
|
130
|
+
tg_cols_idx = column_idx(*condition.values)
|
131
|
+
else
|
132
|
+
# delete line -> src_cols_idx = src_rs.column_idx(*src_rs.default_migration_condition)
|
133
|
+
# suppose all primary key of target resultset can be found in src result, and in same column idx
|
134
|
+
tg_cols_idx = column_idx(*default_migration_condition)
|
135
|
+
src_cols_idx = tg_cols_idx
|
136
|
+
end
|
137
|
+
@rows = handle_rows(src_rs.rows, mode, src_cols_idx, tg_cols_idx)
|
138
|
+
save!(condition)
|
139
|
+
end
|
140
|
+
|
141
|
+
# condition {:name => :c_name, :age => :age}
|
142
|
+
# insert or update or delete
|
143
|
+
def handle_rows (src_rows_data, mode, src_cols_idx = nil, tg_cols_idx = nil)
|
144
|
+
new_rows_data = []
|
145
|
+
|
146
|
+
# condition parameter is null , so delete all ROWS. and then copy the source rs
|
147
|
+
if src_cols_idx.to_a.size <= 0 && tg_cols_idx.to_a.size <= 0
|
148
|
+
# TODO check the size
|
149
|
+
if mode == :echo
|
150
|
+
# delete all the dest rs data
|
151
|
+
@rows.each do |row|
|
152
|
+
new_rows_data << Row.new(row.data, RowStatus::DELETE)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
# :echo and :contribute mode
|
156
|
+
format_rows(src_rows_data, width).map do |row|
|
157
|
+
new_rows_data << Row.new(row.data, RowStatus::NEW)
|
158
|
+
end
|
159
|
+
else
|
160
|
+
if mode == :echo
|
161
|
+
new_rows_data += delete_not_exist_rows(@rows, src_rows_data, tg_cols_idx, src_cols_idx)
|
162
|
+
end
|
163
|
+
# :echo and :contribute
|
164
|
+
new_rows_data += op_two_rows(@rows, tg_cols_idx, src_rows_data, src_cols_idx)
|
165
|
+
end
|
166
|
+
|
167
|
+
new_rows_data
|
168
|
+
end
|
169
|
+
|
170
|
+
def op_two_rows(target_rows, target_cols_idx, source_rows, src_cols_idx)
|
171
|
+
result = []
|
172
|
+
source_rows.each do |s_row|
|
173
|
+
s_values = s_row.values(*src_cols_idx)
|
174
|
+
fetched = false
|
175
|
+
|
176
|
+
target_rows.each do |t_row|
|
177
|
+
if s_values == t_row.values(*target_cols_idx)
|
178
|
+
fetched = true
|
179
|
+
if s_row.data != t_row.data
|
180
|
+
result << Row.new(s_row.data, RowStatus::UPDATED)
|
181
|
+
else
|
182
|
+
result << Row.new(s_row.data, RowStatus::ORIGIN)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
result << Row.new(s_row.data, RowStatus::NEW) unless fetched
|
187
|
+
end
|
188
|
+
|
189
|
+
result
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete_not_exist_rows(target_rows, source_rows, target_cols_idx, src_cols_idx)
|
193
|
+
result = []
|
194
|
+
target_rows.each do |t_row|
|
195
|
+
t_values = t_row.values(*target_cols_idx)
|
196
|
+
fetched = false
|
197
|
+
|
198
|
+
source_rows.each do |s_row|
|
199
|
+
break fetched = true if s_row.values(*src_cols_idx) == t_values
|
200
|
+
end
|
201
|
+
result << Row.new(t_row.data, RowStatus::DELETE) unless fetched
|
202
|
+
end
|
203
|
+
|
204
|
+
result
|
205
|
+
end
|
206
|
+
|
207
|
+
def save!(condition = nil)
|
208
|
+
begin
|
209
|
+
# convert all to native row
|
210
|
+
@rows.map do |row|
|
211
|
+
convert_to_native_row(row)
|
212
|
+
end
|
213
|
+
|
214
|
+
@db.transaction if Rigrate.config[:strict]
|
215
|
+
condition = condition.values if condition
|
216
|
+
handle_delete!(condition)
|
217
|
+
handle_insert!
|
218
|
+
handle_update!(condition)
|
219
|
+
@db.commit if @db.transaction_active?
|
220
|
+
rescue Exception => e
|
221
|
+
Rigrate.logger.error("saving resultset [#{rows.size}] error: #{e.message} #{e.backtrace}")
|
222
|
+
raise e
|
223
|
+
@db.rollback if @db.transaction_active?
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def handle_insert!()
|
228
|
+
sql = get_sql(:insert)
|
229
|
+
|
230
|
+
op_rows = @rows.select do |row|
|
231
|
+
row.status == RowStatus::NEW
|
232
|
+
end
|
233
|
+
|
234
|
+
Rigrate.logger.info("start insert [#{op_rows.size}] rows using sql [#{sql}]")
|
235
|
+
@db.insert sql, *op_rows if op_rows.size > 0
|
236
|
+
end
|
237
|
+
|
238
|
+
def handle_update!(condition = nil)
|
239
|
+
key_fields = (condition || primary_key)
|
240
|
+
sql = get_sql(:update, key_fields)
|
241
|
+
param_fields = column_info.reject do |col|
|
242
|
+
key_fields.include? col.name
|
243
|
+
end.map { |col| col.name }
|
244
|
+
|
245
|
+
op_rows = rows.select do |row|
|
246
|
+
row.status == RowStatus::UPDATED
|
247
|
+
end
|
248
|
+
|
249
|
+
key_idx = column_idx(*key_fields)
|
250
|
+
params_idx = column_idx(*param_fields)
|
251
|
+
formated_rows = op_rows.map do |row|
|
252
|
+
key_values = row.values(*key_idx)
|
253
|
+
params_values = row.values(*params_idx)
|
254
|
+
|
255
|
+
params_values + key_values
|
256
|
+
end
|
257
|
+
Rigrate.logger.info("start update [#{op_rows.size}] rows using sql [#{sql}]")
|
258
|
+
@db.update sql, *formated_rows if formated_rows.size > 0
|
259
|
+
end
|
260
|
+
|
261
|
+
def handle_delete!(condition = nil)
|
262
|
+
temp_primary_key = nil
|
263
|
+
temp_primary_key = primary_key if primary_key.size > 0
|
264
|
+
condi_fields = condition || temp_primary_key || column_info.map{ |col| col.name }
|
265
|
+
|
266
|
+
sql = get_sql(:delete, condi_fields)
|
267
|
+
|
268
|
+
op_rows = @rows.select do |row|
|
269
|
+
row.status == RowStatus::DELETE
|
270
|
+
end
|
271
|
+
|
272
|
+
params_idx = column_idx(*condi_fields)
|
273
|
+
formated_rows = op_rows.map do |row|
|
274
|
+
row.values(*params_idx)
|
275
|
+
end
|
276
|
+
|
277
|
+
Rigrate.logger.info("start delete [#{op_rows.size}] rows using sql [#{sql}]")
|
278
|
+
@db.delete sql, *formated_rows if formated_rows.size > 0
|
279
|
+
end
|
280
|
+
|
281
|
+
def get_sql(type, condition = nil)
|
282
|
+
case type
|
283
|
+
when :insert
|
284
|
+
params_str = column_info.map(&:name).join(',')
|
285
|
+
values_str = Array.new(column_info.size){'?'}.join(',')
|
286
|
+
|
287
|
+
"insert into #{target_tbl_name} (#{params_str}) values (#{values_str})"
|
288
|
+
when :update
|
289
|
+
condi_fields = condition || primary_key
|
290
|
+
params_str = condi_fields.map do |col|
|
291
|
+
"#{col}=?"
|
292
|
+
end.join(' and ')
|
293
|
+
|
294
|
+
upd_fields = column_info.reject do |col|
|
295
|
+
condi_fields.include? col.name
|
296
|
+
end
|
297
|
+
setting_str = upd_fields.map do |col|
|
298
|
+
"#{col.name}=?"
|
299
|
+
end.join(',')
|
300
|
+
|
301
|
+
"update #{target_tbl_name} set #{setting_str} where #{params_str}"
|
302
|
+
when :delete
|
303
|
+
params_str = condition.map do |col|
|
304
|
+
"#{col}=?"
|
305
|
+
end.join(' and ')
|
306
|
+
raise ResultSetError.new("can't get the delete sql") if params_str.to_s.size <= 0
|
307
|
+
|
308
|
+
"delete from #{target_tbl_name} where #{params_str}"
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# convert source resulset rows to specify width
|
313
|
+
def format_rows(src_rows, tg_width, filled = nil)
|
314
|
+
r_length = src_rows.first.size
|
315
|
+
|
316
|
+
if r_length > tg_width
|
317
|
+
src_rows.map! do |row|
|
318
|
+
row.data = row[0..tg_width]
|
319
|
+
end
|
320
|
+
elsif r_length < tg_width
|
321
|
+
src_rows.map! do |row|
|
322
|
+
row.fill_with_nil(tg_width - r_length)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
src_rows
|
327
|
+
end
|
328
|
+
|
329
|
+
def column_values(row, cols)
|
330
|
+
cols.map do |col|
|
331
|
+
row[col]
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def column_idx(*names)
|
336
|
+
names.inject([]) do |idxes, name|
|
337
|
+
column_info.each_with_index do |col, idx|
|
338
|
+
idxes << idx if col.name.to_s.downcase == name.to_s.downcase
|
339
|
+
end
|
340
|
+
|
341
|
+
idxes
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def include?(p_row)
|
346
|
+
@rows.each do |row|
|
347
|
+
return true if row.data == p_row.data
|
348
|
+
end
|
349
|
+
|
350
|
+
false
|
351
|
+
end
|
352
|
+
|
353
|
+
def default_migration_condition
|
354
|
+
flag = true
|
355
|
+
cols = @column_info.map { |col| col.name }
|
356
|
+
primary_key.each do |key|
|
357
|
+
flag = false unless cols.include? key
|
358
|
+
end
|
359
|
+
|
360
|
+
primary_key if flag
|
361
|
+
end
|
362
|
+
|
363
|
+
def convert_to_native_row(row)
|
364
|
+
@db.to_native_row(row, @column_info)
|
365
|
+
end
|
366
|
+
|
367
|
+
def size
|
368
|
+
@rows.size
|
369
|
+
end
|
370
|
+
|
371
|
+
def primary_key
|
372
|
+
@primary_key ||= @db.primary_key(@target_tbl_name)
|
373
|
+
end
|
374
|
+
|
375
|
+
private
|
376
|
+
|
377
|
+
def fill_with_nil(rows, num)
|
378
|
+
fill_row = Array.new(num)
|
379
|
+
|
380
|
+
rows.map do |row|
|
381
|
+
row + fill_row
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def width
|
386
|
+
@column_info.size
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|