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,8 +1,8 @@
1
1
  begin
2
- require 'postgres'
2
+ require 'postgres'
3
3
  rescue Object => ex
4
- Logger.error 'Ruby-PostgreSQL bindings are not installed!'
5
- Logger.error ex
4
+ Logger.error 'Ruby-PostgreSQL bindings are not installed!'
5
+ Logger.error ex
6
6
  end
7
7
 
8
8
  require 'og/store/sql'
@@ -11,58 +11,58 @@ require 'og/store/sql'
11
11
  # more compatible with Og.
12
12
 
13
13
  class PGresult
14
- def blank?
15
- 0 == num_tuples
16
- end
17
-
18
- def next
19
- self
20
- end
21
-
22
- def each_row
23
- for row in (0...num_tuples)
24
- yield(self, row)
25
- end
26
- end
27
-
28
- def first_value
29
- val = getvalue(0, 0)
30
- clear
31
- return val
32
- end
33
-
34
- alias_method :close, :clear
14
+ def blank?
15
+ 0 == num_tuples
16
+ end
17
+
18
+ def next
19
+ self
20
+ end
21
+
22
+ def each_row
23
+ for row in (0...num_tuples)
24
+ yield(self, row)
25
+ end
26
+ end
27
+
28
+ def first_value
29
+ val = getvalue(0, 0)
30
+ clear
31
+ return val
32
+ end
33
+
34
+ alias_method :close, :clear
35
35
  end
36
36
 
37
37
  module Og
38
38
 
39
39
  module PsqlUtils
40
- include SqlUtils
41
-
42
- def escape(str)
43
- return nil unless str
44
- return PGconn.escape(str)
45
- end
46
-
47
- # TODO, mneumann:
48
- #
49
- # Blobs are actually a lot faster (and uses up less storage) for large data I
50
- # think, as they need not to be encoded and decoded. I'd like to have both ;-)
51
- # BYTEA is easier to handle than BLOBs, but if you implement BLOBs in a way
52
- # that they are transparent to the user (as I did in Ruby/DBI), I'd prefer that
53
- # way.
54
-
55
- def blob(val)
56
- val.gsub(/[\000-\037\047\134\177-\377]/) do |b|
57
- "\\#{ b[0].to_s(8).rjust(3, '0') }"
58
- end
59
- end
60
-
61
- def parse_blob(val)
62
- val.gsub(/\\(\\|'|[0-3][0-7][0-7])/) do |s|
63
- if s.size == 2 then s[1,1] else s[1,3].oct.chr end
64
- end
65
- end
40
+ include SqlUtils
41
+
42
+ def escape(str)
43
+ return nil unless str
44
+ return PGconn.escape(str)
45
+ end
46
+
47
+ # TODO, mneumann:
48
+ #
49
+ # Blobs are actually a lot faster (and uses up less storage) for large data I
50
+ # think, as they need not to be encoded and decoded. I'd like to have both ;-)
51
+ # BYTEA is easier to handle than BLOBs, but if you implement BLOBs in a way
52
+ # that they are transparent to the user (as I did in Ruby/DBI), I'd prefer that
53
+ # way.
54
+
55
+ def blob(val)
56
+ val.gsub(/[\000-\037\047\134\177-\377]/) do |b|
57
+ "\\#{ b[0].to_s(8).rjust(3, '0') }"
58
+ end
59
+ end
60
+
61
+ def parse_blob(val)
62
+ val.gsub(/\\(\\|'|[0-3][0-7][0-7])/) do |s|
63
+ if s.size == 2 then s[1,1] else s[1,3].oct.chr end
64
+ end
65
+ end
66
66
 
67
67
  end
68
68
 
@@ -76,206 +76,222 @@ end
76
76
  # performance.
77
77
 
78
78
  class PsqlStore < SqlStore
79
- extend PsqlUtils
80
- include PsqlUtils
81
-
82
- def self.create(options)
83
- # gmosx: system is used to avoid shell expansion.
84
- system 'createdb', options[:name], '-U', options[:user]
85
- super
86
- end
87
-
88
- def self.destroy(options)
89
- system 'dropdb', options[:name], '-U', options[:user]
90
- super
91
- end
92
-
93
- def initialize(options)
94
- super
95
-
96
- @conn = PGconn.connect(
97
- options[:address],
98
- options[:port], nil, nil,
99
- options[:name],
100
- options[:user].to_s,
101
- options[:password].to_s
102
- )
103
-
104
- schema_order = options[:schema_order]
105
- encoding = options[:encoding]
106
- min_messages = options[:min_messages]
107
-
108
- @conn.exec("SET search_path TO #{schema_order}") if schema_order
79
+ extend PsqlUtils
80
+ include PsqlUtils
81
+
82
+ def self.create(options)
83
+ # gmosx: system is used to avoid shell expansion.
84
+ system 'createdb', options[:name], '-U', options[:user]
85
+ super
86
+ end
87
+
88
+ def self.destroy(options)
89
+ system 'dropdb', options[:name], '-U', options[:user]
90
+ super
91
+ end
92
+
93
+ def initialize(options)
94
+ super
95
+
96
+ @typemap.update(Og::Blob => 'bytea')
97
+
98
+ @conn = PGconn.connect(
99
+ options[:address],
100
+ options[:port], nil, nil,
101
+ options[:name],
102
+ options[:user].to_s,
103
+ options[:password].to_s
104
+ )
105
+
106
+ schema_order = options[:schema_order]
107
+ encoding = options[:encoding]
108
+ min_messages = options[:min_messages]
109
+
110
+ @conn.exec("SET search_path TO #{schema_order}") if schema_order
109
111
  @conn.exec("SET client_encoding TO '#{encoding}'") if encoding
110
112
  @conn.exec("SET client_min_messages TO '#{min_messages}'") if min_messages
111
- rescue => ex
112
- # gmosx: any idea how to better test this?
113
- if ex.to_s =~ /database .* does not exist/i
114
- Logger.info "Database '#{options[:name]}' not found!"
115
- self.class.create(options)
116
- retry
117
- end
118
- raise
119
- end
120
-
121
- def close
122
- @conn.close
123
- super
124
- end
125
-
126
- def enchant(klass, manager)
127
- klass.const_set 'OGSEQ', "#{table(klass)}_oid_seq"
128
- klass.property :oid, Fixnum, :sql => 'serial PRIMARY KEY'
129
- super
130
- end
131
-
132
- def query(sql)
133
- Logger.debug sql if $DBG
134
- return @conn.exec(sql)
135
- rescue => ex
136
- handle_sql_exception(ex, sql)
137
- end
138
-
139
- def exec(sql)
140
- Logger.debug sql if $DBG
141
- @conn.exec(sql).clear
142
- rescue => ex
143
- handle_sql_exception(ex, sql)
144
- end
145
-
146
- def sql_update(sql)
147
- Logger.debug sql if $DBG
148
- res = @conn.exec(sql)
149
- changed = res.cmdtuples
150
- res.clear
151
- changed
152
- end
113
+ rescue => ex
114
+ # gmosx: any idea how to better test this?
115
+ if ex.to_s =~ /database .* does not exist/i
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.const_set 'OGSEQ', "#{table(klass)}_oid_seq"
130
+ klass.property :oid, Fixnum, :sql => 'serial PRIMARY KEY'
131
+ super
132
+ end
133
+
134
+ def query(sql)
135
+ Logger.debug sql if $DBG
136
+ return @conn.exec(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.exec(sql).clear
144
+ rescue => ex
145
+ handle_sql_exception(ex, sql)
146
+ end
147
+
148
+ def sql_update(sql)
149
+ Logger.debug sql if $DBG
150
+ res = @conn.exec(sql)
151
+ changed = res.cmdtuples
152
+ res.clear
153
+ changed
154
+ end
155
+
156
+ # Start a new transaction.
157
+
158
+ def start
159
+ # neumann: works with earlier PSQL databases too.
160
+ exec('BEGIN TRANSACTION') if @transaction_nesting < 1
161
+ @transaction_nesting += 1
162
+ end
153
163
 
154
164
  private
155
165
 
156
- def create_table(klass)
157
- fields = fields_for_class(klass)
158
-
159
- sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
160
-
161
- # Create table constrains.
162
-
163
- if klass.__meta and constrains = klass.__meta[:sql_constrain]
164
- sql << ", #{constrains.join(', ')}"
165
- end
166
-
167
- sql << ") WITHOUT OIDS;"
168
-
169
- # Create indices.
170
-
171
- if klass.__meta and indices = klass.__meta[:index]
172
- for data in indices
173
- idx, options = *data
174
- idx = idx.to_s
175
- pre_sql, post_sql = options[:pre], options[:post]
176
- idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
177
- sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
178
- end
179
- end
180
-
181
- begin
182
- @conn.exec(sql).clear
183
- Logger.info "Created table '#{klass::OGTABLE}'."
184
- rescue Object => ex
185
- # gmosx: any idea how to better test this?
186
- if ex.to_s =~ /relation .* already exists/i
187
- Logger.debug 'Table already exists' if $DBG
188
- return
189
- else
190
- raise
191
- end
192
- end
193
-
194
- # Create join tables if needed. Join tables are used in
195
- # 'many_to_many' relations.
196
-
197
- if klass.__meta and join_tables = klass.__meta[:join_tables]
198
- for join_table in join_tables
199
- begin
200
- @conn.exec("CREATE TABLE #{join_table} (key1 integer NOT NULL, key2 integer NOT NULL)").clear
201
- @conn.exec("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").clear
202
- @conn.exec("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").clear
203
- rescue Object => ex
204
- # gmosx: any idea how to better test this?
205
- if ex.to_s =~ /relation .* already exists/i
206
- Logger.debug 'Join table already exists'
207
- else
208
- raise
209
- end
210
- end
211
- end
212
- end
213
- end
214
-
215
- def drop_table(klass)
216
- super
217
- exec "DROP SEQUENCE #{klass::OGSEQ}"
218
- end
219
-
220
- def create_field_map(klass)
221
- res = @conn.exec "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
222
- map = {}
223
-
224
- for field in res.fields
225
- map[field.intern] = res.fieldnum(field)
226
- end
227
-
228
- return map
229
- ensure
230
- res.clear if res
231
- end
232
-
233
- def read_prop(p, col)
234
- if p.klass.ancestors.include?(Integer)
235
- return "#{self.class}.parse_int(res.getvalue(row, #{col} + offset))"
236
- elsif p.klass.ancestors.include?(Float)
237
- return "#{self.class}.parse_float(res.getvalue(row, #{col} + offset))"
238
- elsif p.klass.ancestors.include?(String)
239
- return "res.getvalue(row, #{col} + offset)"
240
- elsif p.klass.ancestors.include?(Time)
241
- return "#{self.class}.parse_timestamp(res.getvalue(row, #{col} + offset))"
242
- elsif p.klass.ancestors.include?(Date)
243
- return "#{self.class}.parse_date(res.getvalue(row, #{col} + offset))"
244
- elsif p.klass.ancestors.include?(TrueClass)
245
- return %|('t' == res.getvalue(row, #{col} + offset))|
166
+ def create_table(klass)
167
+ fields = fields_for_class(klass)
168
+
169
+ sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
170
+
171
+ # Create table constrains.
172
+
173
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
174
+ sql << ", #{constrains.join(', ')}"
175
+ end
176
+
177
+ sql << ") WITHOUT OIDS;"
178
+
179
+ # Create indices.
180
+
181
+ if klass.__meta and indices = klass.__meta[:index]
182
+ for data in indices
183
+ idx, options = *data
184
+ idx = idx.to_s
185
+ pre_sql, post_sql = options[:pre], options[:post]
186
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
187
+ sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
188
+ end
189
+ end
190
+
191
+ begin
192
+ @conn.exec(sql).clear
193
+ Logger.info "Created table '#{klass::OGTABLE}'."
194
+ rescue Object => ex
195
+ # gmosx: any idea how to better test this?
196
+ if ex.to_s =~ /relation .* already exists/i
197
+ Logger.debug 'Table already exists' if $DBG
198
+ return
199
+ else
200
+ raise
201
+ end
202
+ end
203
+
204
+ # Create join tables if needed. Join tables are used in
205
+ # 'many_to_many' relations.
206
+
207
+ if klass.__meta and join_tables = klass.__meta[:join_tables]
208
+ for info in join_tables
209
+ begin
210
+ create_join_table_sql(info).each do |sql|
211
+ @conn.exec(sql).clear
212
+ end
213
+ rescue Object => ex
214
+ # gmosx: any idea how to better test this?
215
+ if ex.to_s =~ /relation .* already exists/i
216
+ Logger.debug 'Join table already exists' if $DBG
217
+ else
218
+ raise
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ def drop_table(klass)
226
+ super
227
+ exec "DROP SEQUENCE #{klass::OGSEQ}"
228
+ end
229
+
230
+ def create_field_map(klass)
231
+ res = @conn.exec "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
232
+ map = {}
233
+
234
+ for field in res.fields
235
+ map[field.intern] = res.fieldnum(field)
236
+ end
237
+
238
+ return map
239
+ ensure
240
+ res.clear if res
241
+ end
242
+
243
+ def read_prop(p, col)
244
+ if p.klass.ancestors.include?(Integer)
245
+ return "#{self.class}.parse_int(res.getvalue(row, #{col} + offset))"
246
+ elsif p.klass.ancestors.include?(Float)
247
+ return "#{self.class}.parse_float(res.getvalue(row, #{col} + offset))"
248
+ elsif p.klass.ancestors.include?(String)
249
+ return "res.getvalue(row, #{col} + offset)"
250
+ elsif p.klass.ancestors.include?(Time)
251
+ return "#{self.class}.parse_timestamp(res.getvalue(row, #{col} + offset))"
252
+ elsif p.klass.ancestors.include?(Date)
253
+ return "#{self.class}.parse_date(res.getvalue(row, #{col} + offset))"
254
+ elsif p.klass.ancestors.include?(TrueClass)
255
+ return %|('t' == res.getvalue(row, #{col} + offset))|
246
256
  elsif p.klass.ancestors.include?(Og::Blob)
247
- return "#{self.class}.parse_blob(res.getvalue(row, #{col} + offset))"
248
- else
249
- return "YAML.load(res.getvalue(row, #{col} + offset))"
250
- end
251
- end
252
-
253
- #--
254
- # TODO: create stored procedure.
255
- #++
256
-
257
- def eval_og_insert(klass)
258
- props = klass.properties
259
- values = props.collect { |p| write_prop(p) }.join(',')
260
-
261
- if klass.metadata.superclass or klass.metadata.subclasses
262
- props << Property.new(:ogtype, String)
263
- values << ", '#{klass}'"
264
- end
265
-
266
- sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
267
-
268
- klass.class_eval %{
269
- def og_insert(store)
270
- #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
271
- res = store.conn.exec "SELECT nextval('#{klass::OGSEQ}')"
272
- @#{klass.pk_symbol} = res.getvalue(0, 0).to_i
273
- res.clear
274
- store.conn.exec("#{sql}").clear
275
- #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
276
- end
277
- }
278
- end
257
+ return "#{self.class}.parse_blob(res.getvalue(row, #{col} + offset))"
258
+ else
259
+ return "YAML.load(res.getvalue(row, #{col} + offset))"
260
+ end
261
+ end
262
+
263
+ #--
264
+ # TODO: create stored procedure.
265
+ #++
266
+
267
+ def eval_og_insert(klass)
268
+ props = klass.properties
269
+ values = props.collect { |p| write_prop(p) }.join(',')
270
+
271
+ if klass.metadata.superclass or klass.metadata.subclasses
272
+ props << Property.new(:ogtype, String)
273
+ values << ", '#{klass}'"
274
+ end
275
+
276
+ sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
277
+
278
+ klass.class_eval %{
279
+ def og_insert(store)
280
+ #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
281
+ res = store.conn.exec "SELECT nextval('#{klass::OGSEQ}')"
282
+ @#{klass.pk_symbol} = res.getvalue(0, 0).to_i
283
+ res.clear
284
+ store.conn.exec("#{sql}").clear
285
+ #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
286
+ end
287
+ }
288
+ end
289
+
290
+ def read_row(obj, res, res_row, row)
291
+ res.fields.each_with_index do |field, idx|
292
+ obj.instance_variable_set "@#{field}", res.getvalue(row, idx)
293
+ end
294
+ end
279
295
 
280
296
  end
281
297
 
@@ -283,3 +299,4 @@ end
283
299
 
284
300
  # * George Moschovitis <gm@navel.gr>
285
301
  # * Michael Neumann <mneumann@ntecs.de>
302
+ # * Ysabel <deb@ysabel.org>