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