og 0.20.0 → 0.21.0
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/CHANGELOG +796 -664
- data/INSTALL +24 -24
- data/README +39 -32
- data/Rakefile +41 -42
- data/benchmark/bench.rb +36 -36
- data/doc/AUTHORS +15 -12
- data/doc/LICENSE +3 -3
- data/doc/RELEASES +311 -243
- data/doc/config.txt +1 -1
- data/examples/mysql_to_psql.rb +15 -15
- data/examples/run.rb +92 -92
- data/install.rb +7 -17
- data/lib/og.rb +76 -75
- data/lib/og/collection.rb +203 -160
- data/lib/og/entity.rb +168 -169
- data/lib/og/errors.rb +5 -5
- data/lib/og/manager.rb +179 -178
- data/lib/og/mixin/hierarchical.rb +107 -107
- data/lib/og/mixin/optimistic_locking.rb +36 -36
- data/lib/og/mixin/orderable.rb +148 -148
- data/lib/og/mixin/timestamped.rb +8 -8
- data/lib/og/mixin/tree.rb +124 -124
- data/lib/og/relation.rb +237 -213
- data/lib/og/relation/belongs_to.rb +5 -5
- data/lib/og/relation/has_many.rb +60 -58
- data/lib/og/relation/joins_many.rb +93 -47
- data/lib/og/relation/refers_to.rb +25 -21
- data/lib/og/store.rb +210 -207
- data/lib/og/store/filesys.rb +79 -79
- data/lib/og/store/kirby.rb +263 -258
- data/lib/og/store/memory.rb +261 -261
- data/lib/og/store/mysql.rb +288 -284
- data/lib/og/store/psql.rb +261 -244
- data/lib/og/store/sql.rb +873 -720
- data/lib/og/store/sqlite.rb +177 -175
- data/lib/og/store/sqlserver.rb +204 -214
- data/lib/og/types.rb +1 -1
- data/lib/og/validation.rb +57 -57
- data/lib/vendor/mysql.rb +376 -376
- data/lib/vendor/mysql411.rb +10 -10
- data/test/og/mixin/tc_hierarchical.rb +59 -59
- data/test/og/mixin/tc_optimistic_locking.rb +40 -40
- data/test/og/mixin/tc_orderable.rb +67 -67
- data/test/og/mixin/tc_timestamped.rb +19 -19
- data/test/og/store/tc_filesys.rb +46 -46
- data/test/og/tc_inheritance.rb +81 -81
- data/test/og/tc_join.rb +67 -0
- data/test/og/tc_polymorphic.rb +49 -49
- data/test/og/tc_relation.rb +57 -30
- data/test/og/tc_select.rb +49 -0
- data/test/og/tc_store.rb +345 -337
- data/test/og/tc_types.rb +11 -11
- metadata +11 -18
data/lib/og/store/mysql.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
begin
|
2
|
-
|
2
|
+
require 'mysql'
|
3
3
|
rescue Object => ex
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
Logger.error 'Ruby-Mysql bindings are not installed!'
|
5
|
+
Logger.error 'Trying to uses the pure-Ruby binding included in Og'
|
6
|
+
begin
|
7
|
+
# Attempt to use the included pure ruby version.
|
8
|
+
require 'vendor/mysql'
|
9
|
+
require 'vendor/mysql411'
|
10
|
+
rescue Object => ex
|
11
|
+
Logger.error ex
|
12
|
+
end
|
13
13
|
end
|
14
14
|
|
15
15
|
require 'og/store/sql'
|
@@ -18,54 +18,58 @@ require 'og/store/sql'
|
|
18
18
|
# more compatible with Og.
|
19
19
|
|
20
20
|
class Mysql::Result
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
21
|
+
def blank?
|
22
|
+
0 == num_rows
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :next, :fetch_row
|
26
|
+
|
27
|
+
def each_row
|
28
|
+
each do |row|
|
29
|
+
yield(row, 0)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def first_value
|
34
|
+
val = fetch_row[0]
|
35
|
+
free
|
36
|
+
return val
|
37
|
+
end
|
38
|
+
|
39
|
+
alias_method :close, :free
|
40
|
+
|
41
|
+
def fields
|
42
|
+
fetch_fields.map { |f| f.name }
|
43
|
+
end
|
40
44
|
end
|
41
45
|
|
42
46
|
module Og
|
43
47
|
|
44
48
|
module MysqlUtils
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
49
|
+
include SqlUtils
|
50
|
+
|
51
|
+
def escape(str)
|
52
|
+
return nil unless str
|
53
|
+
return Mysql.quote(str)
|
54
|
+
end
|
55
|
+
|
56
|
+
def quote(val)
|
57
|
+
case val
|
58
|
+
when Fixnum, Integer, Float
|
59
|
+
val ? val.to_s : 'NULL'
|
60
|
+
when String
|
61
|
+
val ? "'#{escape(val)}'" : 'NULL'
|
62
|
+
when Time
|
63
|
+
val ? "'#{timestamp(val)}'" : 'NULL'
|
64
|
+
when Date
|
65
|
+
val ? "'#{date(val)}'" : 'NULL'
|
66
|
+
when TrueClass
|
67
|
+
val ? "'1'" : 'NULL'
|
68
|
+
else
|
69
|
+
# gmosx: keep the '' for nil symbols.
|
70
|
+
val ? escape(val.to_yaml) : ''
|
71
|
+
end
|
72
|
+
end
|
69
73
|
end
|
70
74
|
|
71
75
|
# A Store that persists objects into a MySQL database.
|
@@ -73,240 +77,240 @@ end
|
|
73
77
|
# for SqlStore and Store.
|
74
78
|
|
75
79
|
class MysqlStore < SqlStore
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
80
|
+
extend MysqlUtils
|
81
|
+
include MysqlUtils
|
82
|
+
|
83
|
+
def self.create(options)
|
84
|
+
# gmosx: system is used to avoid shell expansion.
|
85
|
+
system 'mysqladmin', '-f', "--user=#{options[:user]}",
|
86
|
+
"--password=#{options[:password]}",
|
87
|
+
'create', options[:name]
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.destroy(options)
|
92
|
+
system 'mysqladmin', '-f', "--user=#{options[:user]}",
|
93
|
+
"--password=#{options[:password]}", 'drop',
|
94
|
+
options[:name]
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def initialize(options)
|
99
|
+
super
|
100
|
+
|
101
|
+
@typemap.update(TrueClass => 'tinyint')
|
102
|
+
|
103
|
+
@conn = Mysql.connect(
|
104
|
+
options[:address] || 'localhost',
|
105
|
+
options[:user],
|
106
|
+
options[:password],
|
107
|
+
options[:name]
|
108
|
+
)
|
109
|
+
|
110
|
+
# You should set recconect to true to avoid MySQL has
|
111
|
+
# gone away errors.
|
112
|
+
|
113
|
+
if @conn.respond_to? :reconnect
|
114
|
+
options[:reconnect] = true unless options.has_key?(:reconnect)
|
115
|
+
@conn.reconnect = options[:reconnect]
|
116
|
+
end
|
117
|
+
|
118
|
+
rescue => ex
|
119
|
+
if ex.errno == 1049 # database does not exist.
|
120
|
+
Logger.info "Database '#{options[:name]}' not found!"
|
121
|
+
self.class.create(options)
|
122
|
+
retry
|
123
|
+
end
|
124
|
+
raise
|
125
|
+
end
|
126
|
+
|
127
|
+
def close
|
128
|
+
@conn.close
|
129
|
+
super
|
130
|
+
end
|
131
|
+
|
132
|
+
def enchant(klass, manager)
|
133
|
+
klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
|
134
|
+
super
|
135
|
+
end
|
136
|
+
|
137
|
+
def query(sql)
|
138
|
+
Logger.debug sql if $DBG
|
139
|
+
@conn.query_with_result = true
|
140
|
+
return @conn.query(sql)
|
141
|
+
rescue => ex
|
142
|
+
handle_sql_exception(ex, sql)
|
143
|
+
end
|
144
|
+
|
145
|
+
def exec(sql)
|
146
|
+
Logger.debug sql if $DBG
|
147
|
+
@conn.query_with_result = false
|
148
|
+
@conn.query(sql)
|
149
|
+
rescue => ex
|
150
|
+
handle_sql_exception(ex, sql)
|
151
|
+
end
|
152
|
+
|
153
|
+
def start
|
154
|
+
# nop
|
155
|
+
# FIXME: InnoDB supports transactions.
|
156
|
+
end
|
157
|
+
|
158
|
+
# Commit a transaction.
|
159
|
+
|
160
|
+
def commit
|
161
|
+
# nop, not supported?
|
162
|
+
# FIXME: InnoDB supports transactions.
|
163
|
+
end
|
164
|
+
|
165
|
+
# Rollback a transaction.
|
166
|
+
|
167
|
+
def rollback
|
168
|
+
# nop, not supported?
|
169
|
+
# FIXME: InnoDB supports transactions.
|
170
|
+
end
|
171
|
+
|
172
|
+
def sql_update(sql)
|
173
|
+
exec(sql)
|
174
|
+
@conn.affected_rows
|
175
|
+
end
|
172
176
|
|
173
177
|
private
|
174
178
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
179
|
+
def create_table(klass)
|
180
|
+
fields = fields_for_class(klass)
|
181
|
+
|
182
|
+
sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
|
183
|
+
|
184
|
+
# Create table constrains.
|
185
|
+
|
186
|
+
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
187
|
+
sql << ", #{constrains.join(', ')}"
|
188
|
+
end
|
189
|
+
|
190
|
+
if table_type = @options[:table_type]
|
191
|
+
sql << ") TYPE = #{table_type};"
|
192
|
+
else
|
193
|
+
sql << ");"
|
194
|
+
end
|
195
|
+
|
196
|
+
# Create indices.
|
197
|
+
|
198
|
+
if klass.__meta and indices = klass.__meta[:index]
|
199
|
+
for data in indices
|
200
|
+
idx, options = *data
|
201
|
+
idx = idx.to_s
|
202
|
+
pre_sql, post_sql = options[:pre], options[:post]
|
203
|
+
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
204
|
+
sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
@conn.query_with_result = false
|
209
|
+
|
210
|
+
begin
|
211
|
+
@conn.query(sql)
|
212
|
+
Logger.info "Created table '#{klass::OGTABLE}'."
|
213
|
+
rescue => ex
|
214
|
+
if ex.errno == 1050 # table already exists.
|
215
|
+
Logger.debug 'Table already exists' if $DBG
|
216
|
+
return
|
217
|
+
else
|
218
|
+
raise
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Create join tables if needed. Join tables are used in
|
223
|
+
# 'many_to_many' relations.
|
224
|
+
|
225
|
+
if klass.__meta and join_tables = klass.__meta[:join_tables]
|
226
|
+
for info in join_tables
|
227
|
+
begin
|
228
|
+
create_join_table_sql(info).each do |sql|
|
229
|
+
@conn.query sql
|
230
|
+
end
|
231
|
+
rescue => ex
|
232
|
+
if ex.respond_to?(:errno) and ex.errno == 1050 # table already exists.
|
233
|
+
Logger.debug 'Join table already exists' if $DBG
|
234
|
+
else
|
235
|
+
raise
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def create_field_map(klass)
|
243
|
+
conn.query_with_result = true
|
244
|
+
res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
|
245
|
+
map = {}
|
246
|
+
|
247
|
+
res.num_fields.times do |i|
|
248
|
+
map[res.fetch_field.name.intern] = i
|
249
|
+
end
|
250
|
+
|
251
|
+
return map
|
252
|
+
ensure
|
253
|
+
res.close if res
|
254
|
+
end
|
255
|
+
|
256
|
+
def write_prop(p)
|
257
|
+
if p.klass.ancestors.include?(Integer)
|
258
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
259
|
+
elsif p.klass.ancestors.include?(Float)
|
260
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
261
|
+
elsif p.klass.ancestors.include?(String)
|
262
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
|
263
|
+
elsif p.klass.ancestors.include?(Time)
|
264
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
265
|
+
elsif p.klass.ancestors.include?(Date)
|
266
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
|
267
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
268
|
+
return "#\{@#{p.symbol} ? \"'1'\" : 'NULL' \}"
|
269
|
+
else
|
270
|
+
# gmosx: keep the '' for nil symbols.
|
271
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def read_prop(p, col)
|
276
|
+
if p.klass.ancestors.include?(Integer)
|
277
|
+
return "#{self.class}.parse_int(res[#{col} + offset])"
|
278
|
+
elsif p.klass.ancestors.include?(Float)
|
279
|
+
return "#{self.class}.parse_float(res[#{col} + offset])"
|
280
|
+
elsif p.klass.ancestors.include?(String)
|
281
|
+
return "res[#{col} + offset]"
|
282
|
+
elsif p.klass.ancestors.include?(Time)
|
283
|
+
return "#{self.class}.parse_timestamp(res[#{col} + offset])"
|
284
|
+
elsif p.klass.ancestors.include?(Date)
|
285
|
+
return "#{self.class}.parse_date(res[#{col} + offset])"
|
286
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
287
|
+
return "('0' != res[#{col} + offset])"
|
288
|
+
else
|
289
|
+
return "YAML.load(res[#{col} + offset])"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def eval_og_insert(klass)
|
294
|
+
props = klass.properties.dup
|
295
|
+
values = props.collect { |p| write_prop(p) }.join(',')
|
296
|
+
|
297
|
+
if klass.metadata.superclass or klass.metadata.subclasses
|
298
|
+
props << Property.new(:ogtype, String)
|
299
|
+
values << ", '#{klass}'"
|
300
|
+
end
|
301
|
+
|
302
|
+
sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
|
303
|
+
|
304
|
+
klass.class_eval %{
|
305
|
+
def og_insert(store)
|
306
|
+
#{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
307
|
+
store.conn.query_with_result = false
|
308
|
+
store.conn.query "#{sql}"
|
309
|
+
@#{klass.pk_symbol} = store.conn.insert_id
|
310
|
+
#{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
311
|
+
end
|
312
|
+
}
|
313
|
+
end
|
310
314
|
|
311
315
|
end
|
312
316
|
|