rigrate 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ # encoding : utf-8
2
+
3
+ module Rigrate
4
+ # start
5
+ module RowStatus
6
+ NEW = :NEW
7
+ UPDATED = :UPDATED
8
+ DELETE = :DELETE
9
+ ORIGIN = :ORIGIN
10
+
11
+ def test; end
12
+ end
13
+ # end
14
+
15
+ class Row
16
+ attr_accessor :data
17
+ attr_accessor :status
18
+ attr_accessor :fields
19
+
20
+ def initialize(data = [], status = RowStatus::ORIGIN)
21
+ self.data = data
22
+ self.status = status
23
+ end
24
+
25
+ def values(*idxes)
26
+ idxes.map do |idx|
27
+ self[idx]
28
+ end
29
+ end
30
+
31
+ # why need in this way?
32
+ # TODO need rework
33
+ def +(t_row)
34
+ t_row.data.each { |item| @data << item }
35
+ @status = RowStatus::UPDATED
36
+
37
+ self
38
+ end
39
+
40
+ def ==(t_row)
41
+ @data == t_row.data && @status == t_row.status
42
+ end
43
+
44
+ def fill_with_nil(num)
45
+ @data += Array.new(num)
46
+
47
+ self
48
+ end
49
+
50
+ def [](idx)
51
+ @data[idx]
52
+ end
53
+
54
+ def []=(idx, val, status_force = true)
55
+ @data[idx] = val
56
+ @status = RowStatus::UPDATED if status_force
57
+ end
58
+
59
+ def size
60
+ @data.size
61
+ end
62
+
63
+ def <<(item)
64
+ @data << item
65
+
66
+ self
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,106 @@
1
+ # encoding : utf-8
2
+
3
+ require 'sqlite3'
4
+
5
+ module Rigrate
6
+ class Sqlite < Driver
7
+ def initialize(url = nil)
8
+ url ||= "sqlite://memory"
9
+
10
+ opts = extract_conn_param(URI.parse(url))
11
+ file = extract_db_path(url)
12
+
13
+ @db = ::SQLite3::Database.new(file, opts)
14
+ end
15
+
16
+ def select(sql, *args)
17
+ target_tbl_name = extract_tbl_from_sql(sql)
18
+
19
+ ResultSet.new.tap do |rs|
20
+ stm = @db.prepare sql, *args
21
+
22
+ rs.db = self
23
+ rs.target_tbl_name = target_tbl_name
24
+ rs.column_info = statement_fields(stm.columns, stm.types)
25
+ rs.rows = []
26
+ stm.execute.each do |row|
27
+ new_row = Row.new(to_rb_row(row.to_a))
28
+ yield new_row if block_given?
29
+ rs.rows << new_row
30
+ end
31
+ end
32
+ end
33
+
34
+ def save(resultset)
35
+ resultset.db = self
36
+
37
+ resultset.save!
38
+ end
39
+
40
+ def update(sql, *args)
41
+ begin
42
+ stm = @db.prepare sql
43
+ args.each do |row|
44
+ if Rigrate::Row === row
45
+ row = row.data
46
+ end
47
+ stm.execute(*row)
48
+ end
49
+ rescue Exception => e
50
+ Rigrate.logger.error("execute SQL [#{sql}] ARGS [#{args.size}] -> #{e.backtrace.join('\n')}")
51
+ raise DriverError.new("execute error #{e.message}")
52
+ end
53
+ end
54
+ alias :insert :update
55
+ alias :delete :update
56
+
57
+ def primary_key(tbl_name)
58
+ (@db.table_info(tbl_name).select do |col_hash|
59
+ col_hash["pk"] == 1
60
+ end).map do |col_hash|
61
+ col_hash["name"]
62
+ end
63
+ end
64
+
65
+ def to_rb_row(row)
66
+ row.map do |field|
67
+ if field.nil?
68
+ field.to_s
69
+ else
70
+ field
71
+ end
72
+ end
73
+ end
74
+
75
+ def statement_fields(names, types)
76
+ cols = []
77
+ names.each_with_index do |name, idx|
78
+ cols << Column.new(name, types[idx])
79
+ end
80
+
81
+ cols
82
+ end
83
+
84
+ def extract_db_path(path)
85
+ result = ":memory:"
86
+
87
+ if path =~ /sqlite:\/\/(.*)/
88
+ if $1 == 'memory'
89
+ result = ":memory:"
90
+ else
91
+ result = $1
92
+ end
93
+ end
94
+
95
+ result
96
+ end
97
+
98
+ def transaction
99
+ @db.transaction
100
+ end
101
+
102
+ def commit
103
+ @db.commit
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,89 @@
1
+ # encoding : utf-8
2
+
3
+ module Rigrate
4
+ module Migration
5
+ def union(rs_first_str, rs_second_str)
6
+ if String === rs_first_str
7
+ rs_first = instance_eval rs_first_str
8
+ else
9
+ rs_first = rs_first_str
10
+ end
11
+ rs_second = instance_eval rs_second_str
12
+
13
+ if ResultSet === rs_first && ResultSet === rs_second
14
+ Rigrate.logger.info("start union two ResultSet rs [#{rs_first.size}] <---> rs [#{rs_second.size}]")
15
+ return rs_first.union rs_second
16
+ else
17
+ raise Exception.new('rs_first or rs_second is not a resultset')
18
+ end
19
+ end
20
+
21
+ def minus(rs_first_str, rs_second_str)
22
+ if String === rs_first_str
23
+ rs_first = instance_eval rs_first_str
24
+ else
25
+ rs_first = rs_first_str
26
+ end
27
+ rs_second = instance_eval rs_second_str
28
+
29
+ if ResultSet === rs_first && ResultSet === rs_second
30
+ Rigrate.logger.info("start minus two ResultSet rs [#{rs_first.size}] <---> rs [#{rs_second.size}]")
31
+ return rs_first.minus rs_second
32
+ else
33
+ raise Exception.new('rs_first or rs_second is not a resultset')
34
+ end
35
+ end
36
+
37
+ def join(rs_first_str, rs_second_str, condition = nil)
38
+ if String === rs_first_str
39
+ rs_first = instance_eval rs_first_str
40
+ else
41
+ rs_first = rs_first_str
42
+ end
43
+ condition = eval("{#{condition}}") unless condition.nil?
44
+ rs_second = instance_eval rs_second_str
45
+
46
+ if ResultSet === rs_first && ResultSet === rs_second
47
+ Rigrate.logger.info("start join two ResultSet rs [#{rs_first.size}] <---> rs [#{rs_second.size}]")
48
+ return rs_first.join(rs_second, condition)
49
+ else
50
+ raise Exception.new('rs_first or rs_second is not a resultset')
51
+ end
52
+ end
53
+
54
+ # migration mode
55
+ def migrate(rs_first_str, rs_second_str, condition = nil, mode = nil)
56
+ if String === rs_first_str
57
+ rs_source = instance_eval rs_first_str
58
+ else
59
+ rs_source = rs_first_str
60
+ end
61
+ rs_target = instance_eval rs_second_str
62
+
63
+ default_opts = { :mode => Rigrate.config[:mode] }
64
+ if mode
65
+ default_opts[:mode] = instance_eval("#{mode}")
66
+ end
67
+
68
+ if ResultSet === rs_source && ResultSet === rs_target
69
+ return rs_target.migrate_from(rs_source, condition, default_opts)
70
+ else
71
+ raise Exception.new('rs_target or rs_source is not a resultset.')
72
+ end
73
+ end
74
+
75
+ def self_eval(rb_str)
76
+ instance_eval(rb_str)
77
+ end
78
+
79
+ def data_source(name, conn_str)
80
+ name = name.to_s
81
+ ds = DataSource.new(conn_str)
82
+ variable_name = "@#{name}".to_sym unless name.start_with? "@"
83
+ instance_variable_set variable_name, ds
84
+ instance_eval("def #{name}=(x); #{variable_name}=x; end;\
85
+ def #{name}; #{variable_name}; end")
86
+ end
87
+ alias :ds :data_source
88
+ end
89
+ end
@@ -0,0 +1,270 @@
1
+ # encoding : utf-8
2
+
3
+ module Rigrate
4
+ class Parser
5
+ include Migration
6
+
7
+ module TokenType
8
+ FROM = :FROM_TAG
9
+ TO = :TO_TAG
10
+ UNION = :UNION_TAG
11
+ JOIN = :JOIN_TAG
12
+ MINUS = :MINUS_TAG
13
+ ON = :ON_TAG
14
+ USING = :USING_TAG
15
+ RUBY_STR = :RUBY_STR_TAG
16
+ end
17
+
18
+ module StringType
19
+ DOUBLE_QUOTE = :DQ_STR
20
+ SINGLE_QUOTE = :SQ_STR
21
+ end
22
+
23
+ module LexStatus
24
+ INIT = :INIT_STATUS
25
+ IN_KEYWORD = :IN_KEYWORD_STATUS
26
+ IN_RUBY_CODE= :IN_RUBY_CODE_STATUS
27
+ IN_RUBY_STR = :IN_RUBY_STR_STATUS
28
+ IN_COMMENT = :IN_COMMENT_STATUS
29
+ end
30
+
31
+ Token = Struct.new(:type, :value)
32
+ attr_accessor :tokens
33
+
34
+ ##
35
+ # load script str -> tokens
36
+ # TODO there is a bug need to fixed, when comment str include FROM key word will praser error.
37
+ def lex(str)
38
+ status = LexStatus::INIT
39
+ @tokens = []
40
+ t_token = ''
41
+ t_sub_token = ''
42
+ string_type = StringType::DOUBLE_QUOTE
43
+ char_arr = str.chars
44
+ loop do
45
+ c = char_arr.shift
46
+
47
+ if c == nil
48
+ token = Token.new TokenType::RUBY_STR, t_token
49
+ @tokens << token
50
+ break
51
+ end
52
+
53
+ if status == LexStatus::IN_KEYWORD && c =~ /\s/
54
+ if is_a_token?(t_token)
55
+ @tokens << (Token.new get_token_type(t_token), t_token)
56
+ t_token = ''
57
+ t_sub_token = ''
58
+ status = LexStatus::INIT
59
+ next
60
+ else
61
+ status = LexStatus::IN_RUBY_CODE
62
+ end
63
+ end
64
+
65
+ if status != LexStatus::IN_RUBY_CODE && status != LexStatus::IN_RUBY_STR
66
+ (status = LexStatus::INIT && next) if c =~ /\s/
67
+ end
68
+
69
+ t_token << c
70
+ t_sub_token << c
71
+
72
+ if status == LexStatus::IN_RUBY_CODE ||
73
+ status == LexStatus::IN_KEYWORD
74
+ if c == '"'
75
+ string_type = StringType::DOUBLE_QUOTE
76
+ status = LexStatus::IN_RUBY_STR
77
+ next
78
+ elsif c == "'"
79
+ string_type = StringType::SINGLE_QUOTE
80
+ status = LexStatus::IN_RUBY_STR
81
+ next
82
+ end
83
+ end
84
+
85
+ if status == LexStatus::IN_RUBY_STR
86
+ is_matched = false
87
+ if (string_type == StringType::DOUBLE_QUOTE && c == '"') ||
88
+ (string_type == StringType::SINGLE_QUOTE && c == "'")
89
+ is_matched = true
90
+ end
91
+
92
+ if is_matched && t_token[-1] != "\\"
93
+ status = LexStatus::IN_RUBY_CODE
94
+ elsif c =~ /\s/
95
+ t_sub_token = ''
96
+ end
97
+ end
98
+
99
+ if status == LexStatus::IN_RUBY_CODE && c =~ /\s/
100
+ if is_a_token? t_sub_token
101
+ token = Token.new TokenType::RUBY_STR, t_token.sub(/#{t_sub_token}$/, '')
102
+ @tokens << token
103
+ token = Token.new get_token_type(t_sub_token), t_sub_token
104
+ @tokens << token
105
+
106
+ status = LexStatus::INIT
107
+ t_token = ''
108
+ end
109
+
110
+ t_sub_token = ''
111
+ end
112
+
113
+ if status == LexStatus::INIT
114
+ status = LexStatus::IN_KEYWORD
115
+ end
116
+ end
117
+
118
+ self
119
+ end
120
+
121
+ def parsing
122
+ full_parse tokens.dup
123
+ end
124
+
125
+ #private
126
+
127
+ def full_parse(tks)
128
+ while tks.size > 0
129
+ parse_rs_or_migrate_exp(tks)
130
+ end
131
+ end
132
+
133
+ def parse_rs_or_migrate_exp(tks)
134
+ token = tks.shift
135
+
136
+ if token.type == TokenType::RUBY_STR
137
+ v1 = self_eval token.value
138
+ else
139
+ tks.unshift token
140
+ v1 = parse_migrate_exp(tks)
141
+ end
142
+
143
+ v1
144
+ end
145
+
146
+ def parse_migrate_exp(tks)
147
+ token = tks.shift
148
+
149
+ if token.type == TokenType::FROM
150
+ v1 = parse_operate_exp(tks)
151
+
152
+ sub_token = tks.shift
153
+ if sub_token.type == TokenType::TO
154
+ v2 = parse_rs_exp(tks)
155
+ v3 = parse_condition_exp(tks)
156
+ v4 = parse_using_exp(tks)
157
+ migrate(v1, v2, v3, v4)
158
+
159
+ # sub_token_1 = tks.shift
160
+ # if not sub_token_1.nil?
161
+ # if sub_token_1.type == TokenType::ON
162
+ # cond = tks.shift
163
+ # migrate(v1, v2, cond)
164
+ # else
165
+ # tks.unshift sub_token_1
166
+ # migrate(v1, v2)
167
+ # end
168
+ # else
169
+ # migrate(v1, v2)
170
+ # end
171
+ else
172
+ raise ParserError.new("Syntax Error: need TO expression")
173
+ end
174
+ end
175
+ end
176
+
177
+ def parse_operate_exp(tks)
178
+ v1 = parse_rs_exp(tks)
179
+
180
+ while true
181
+ token = tks.shift
182
+ if token.type != TokenType::UNION &&
183
+ token.type != TokenType::JOIN &&
184
+ token.type != TokenType::MINUS
185
+ tks.unshift token
186
+ break
187
+ end
188
+
189
+ v2 = parse_rs_exp(tks)
190
+ if token.type == TokenType::UNION
191
+ v1 = union(v1, v2)
192
+ elsif token.type == TokenType::MINUS
193
+ v1 = minus(v1, v2)
194
+ elsif token.type == TokenType::JOIN
195
+ sub_token = tks.shift
196
+
197
+ if not sub_token.nil?
198
+ if sub_token.type == TokenType::ON
199
+ cond = tks.shift
200
+ v1 = join(v1, v2, cond.value)
201
+ else
202
+ tks.unshift sub_token
203
+ v1 = join(v1, v2)
204
+ end
205
+ else
206
+ v1 = join(v1, v2)
207
+ end
208
+ end
209
+ end
210
+
211
+ v1
212
+ end
213
+
214
+ def parse_condition_exp(tks)
215
+ token = tks.shift
216
+ return if token.nil?
217
+
218
+ if token.type == TokenType::ON
219
+ v1 = parse_rs_exp(tks)
220
+
221
+ if v1.nil?
222
+ raise ParserError.new("ON expression should end with a [RUBY_STR]")
223
+ end
224
+ else
225
+ tks.unshift token
226
+ return
227
+ end
228
+
229
+ v1
230
+ end
231
+
232
+ def parse_using_exp(tks)
233
+ token = tks.shift
234
+ return if token.nil?
235
+
236
+ if token.type == TokenType::USING
237
+ v1 = parse_rs_exp(tks)
238
+
239
+ if v1.nil?
240
+ raise ParserError.new("USING expression should end with a [RUBY_STR]")
241
+ end
242
+ else
243
+ tks.unshift token
244
+ return
245
+ end
246
+
247
+ v1
248
+ end
249
+
250
+ # TODO return value should change
251
+ def parse_rs_exp(tks)
252
+ token = tks.shift
253
+ return if token.nil?
254
+
255
+ if token.type == TokenType::RUBY_STR
256
+ return token.value
257
+ else
258
+ raise ParserError.new("Invalid Syntax")
259
+ end
260
+ end
261
+
262
+ def is_a_token?(str)
263
+ TokenType.constants.include? str.strip.upcase.to_sym
264
+ end
265
+
266
+ def get_token_type(str)
267
+ TokenType.const_get(str.strip.upcase)
268
+ end
269
+ end
270
+ end