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