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.
@@ -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