og 0.20.0 → 0.21.0

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