og 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ProjectInfo +2 -5
- data/README +2 -0
- data/doc/AUTHORS +4 -1
- data/doc/RELEASES +53 -0
- data/examples/run.rb +2 -2
- data/lib/{og/mixin → glue}/hierarchical.rb +19 -19
- data/lib/{og/mixin → glue}/optimistic_locking.rb +1 -1
- data/lib/glue/orderable.rb +235 -0
- data/lib/glue/revisable.rb +2 -0
- data/lib/glue/taggable.rb +176 -0
- data/lib/{og/mixin/taggable.rb → glue/taggable_old.rb} +6 -0
- data/lib/glue/timestamped.rb +37 -0
- data/lib/{og/mixin → glue}/tree.rb +3 -8
- data/lib/og.rb +21 -20
- data/lib/og/collection.rb +15 -1
- data/lib/og/entity.rb +256 -114
- data/lib/og/manager.rb +60 -27
- data/lib/og/{mixin/schema_inheritance_base.rb → markers.rb} +5 -2
- data/lib/og/relation.rb +70 -74
- data/lib/og/relation/belongs_to.rb +5 -3
- data/lib/og/relation/has_many.rb +1 -0
- data/lib/og/relation/joins_many.rb +5 -4
- data/lib/og/store.rb +25 -46
- data/lib/og/store/alpha/filesys.rb +1 -1
- data/lib/og/store/alpha/kirby.rb +30 -30
- data/lib/og/store/alpha/memory.rb +49 -49
- data/lib/og/store/alpha/sqlserver.rb +7 -7
- data/lib/og/store/kirby.rb +38 -38
- data/lib/og/store/mysql.rb +43 -43
- data/lib/og/store/psql.rb +222 -53
- data/lib/og/store/sql.rb +165 -105
- data/lib/og/store/sqlite.rb +29 -25
- data/lib/og/validation.rb +24 -14
- data/lib/{vendor → og/vendor}/README +0 -0
- data/lib/{vendor → og/vendor}/kbserver.rb +1 -1
- data/lib/{vendor → og/vendor}/kirbybase.rb +230 -79
- data/lib/{vendor → og/vendor}/mysql.rb +0 -0
- data/lib/{vendor → og/vendor}/mysql411.rb +0 -0
- data/test/og/mixin/tc_hierarchical.rb +1 -1
- data/test/og/mixin/tc_optimistic_locking.rb +1 -1
- data/test/og/mixin/tc_orderable.rb +1 -1
- data/test/og/mixin/tc_taggable.rb +2 -2
- data/test/og/mixin/tc_timestamped.rb +2 -2
- data/test/og/tc_finder.rb +33 -0
- data/test/og/tc_inheritance.rb +2 -2
- data/test/og/tc_scoped.rb +45 -0
- data/test/og/tc_store.rb +1 -7
- metadata +21 -18
- data/lib/og/mixin/orderable.rb +0 -174
- data/lib/og/mixin/revisable.rb +0 -0
- data/lib/og/mixin/timestamped.rb +0 -24
data/lib/og/store/psql.rb
CHANGED
@@ -5,6 +5,63 @@ rescue Object => ex
|
|
5
5
|
Logger.error ex
|
6
6
|
end
|
7
7
|
|
8
|
+
class PGconn
|
9
|
+
# Lists all the tables within the database.
|
10
|
+
|
11
|
+
def list_tables
|
12
|
+
r = self.exec "SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind='r' AND n.nspname NOT IN ('pg_catalog', 'pg_toast') AND pg_catalog.pg_table_is_visible(c.oid)"
|
13
|
+
ret = r.result.flatten
|
14
|
+
r.clear
|
15
|
+
ret
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns true if a table exists within the database, false
|
19
|
+
# otherwise.
|
20
|
+
|
21
|
+
def table_exists?(table) #rp: this should be abstracted to the sql abstractor
|
22
|
+
r = self.exec "SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind='r' AND n.nspname NOT IN ('pg_catalog', 'pg_toast') AND pg_catalog.pg_table_is_visible(c.oid) AND c.relname='#{self.class.escape(table.to_s)}'"
|
23
|
+
ret = r.result.size != 0
|
24
|
+
r.clear
|
25
|
+
ret
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the PostgreSQL OID of a table within the database or
|
29
|
+
# nil if it doesn't exist. Mostly for internal usage.
|
30
|
+
|
31
|
+
def table_oid(table)
|
32
|
+
r = self.exec "SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind='r' AND n.nspname NOT IN ('pg_catalog', 'pg_toast') AND pg_catalog.pg_table_is_visible(c.oid) AND c.relname='#{self.class.escape(table.to_s)}'"
|
33
|
+
ret = r.result.flatten.first
|
34
|
+
r.clear
|
35
|
+
ret
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns an array of arrays containing the list of fields within a
|
39
|
+
# table. Each element contains two elements, the first is the field
|
40
|
+
# name and the second is the field type. Returns nil if the table
|
41
|
+
# does not exist.
|
42
|
+
|
43
|
+
def table_field_list(table)
|
44
|
+
return nil unless pg_oid = table_oid(table)
|
45
|
+
r = self.exec "SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod) FROM pg_catalog.pg_attribute a WHERE a.attrelid = '#{pg_oid}' AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum"
|
46
|
+
ret = r.result
|
47
|
+
r.clear
|
48
|
+
ret
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns an array of arrays containing the PostgreSQL foreign keys
|
52
|
+
# within a table. The first element is the constraint name and the
|
53
|
+
# second element is the constraint definition.
|
54
|
+
|
55
|
+
def table_foreign_keys(table)
|
56
|
+
return nil unless pg_oid = table_oid(table)
|
57
|
+
r = self.exec "SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = '#{pg_oid}' AND r.contype = 'f'"
|
58
|
+
ret = r.result
|
59
|
+
r.clear
|
60
|
+
ret
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
8
65
|
require 'og/store/sql'
|
9
66
|
|
10
67
|
# Customize the standard postgres resultset to make
|
@@ -71,6 +128,8 @@ end
|
|
71
128
|
# A Store that persists objects into a PostgreSQL database.
|
72
129
|
# To read documentation about the methods, consult the documentation
|
73
130
|
# for SqlStore and Store.
|
131
|
+
#
|
132
|
+
# This is the reference Og store.
|
74
133
|
#
|
75
134
|
# === Design
|
76
135
|
#
|
@@ -92,6 +151,28 @@ class PsqlStore < SqlStore
|
|
92
151
|
super
|
93
152
|
end
|
94
153
|
|
154
|
+
# Purges all tables from the database.
|
155
|
+
|
156
|
+
def self.destroy_tables(options)
|
157
|
+
|
158
|
+
conn = PGconn.connect(
|
159
|
+
options[:address],
|
160
|
+
options[:port], nil, nil,
|
161
|
+
options[:name],
|
162
|
+
options[:user].to_s,
|
163
|
+
options[:password].to_s
|
164
|
+
)
|
165
|
+
|
166
|
+
conn.list_tables.each do |table|
|
167
|
+
sql = "DROP TABLE #{table} CASCADE"
|
168
|
+
conn.exec sql
|
169
|
+
Logger.debug "Dropped database table #{table}"
|
170
|
+
end
|
171
|
+
|
172
|
+
conn.close
|
173
|
+
end
|
174
|
+
|
175
|
+
|
95
176
|
def initialize(options)
|
96
177
|
super
|
97
178
|
|
@@ -134,7 +215,7 @@ class PsqlStore < SqlStore
|
|
134
215
|
klass.const_set 'OGSEQ', "#{table(klass)}_oid_seq"
|
135
216
|
end
|
136
217
|
|
137
|
-
if klass.ann.
|
218
|
+
if klass.ann.self.primary_key.symbol == :oid
|
138
219
|
unless klass.properties.include? :oid
|
139
220
|
klass.property :oid, Fixnum, :sql => 'serial PRIMARY KEY'
|
140
221
|
end
|
@@ -165,7 +246,7 @@ class PsqlStore < SqlStore
|
|
165
246
|
end
|
166
247
|
|
167
248
|
# Start a new transaction.
|
168
|
-
|
249
|
+
|
169
250
|
def start
|
170
251
|
# neumann: works with earlier PSQL databases too.
|
171
252
|
exec('BEGIN TRANSACTION') if @transaction_nesting < 1
|
@@ -174,79 +255,166 @@ class PsqlStore < SqlStore
|
|
174
255
|
|
175
256
|
private
|
176
257
|
|
177
|
-
|
178
|
-
|
258
|
+
# Adds foreign key constraints to a join table, replicating all
|
259
|
+
# modifications to OIDs to the join tables and also purging
|
260
|
+
# any left over data from deleted records (at the time of
|
261
|
+
# implementation, self-join cases left this data here).
|
179
262
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
263
|
+
def create_join_table_foreign_key_constraints(klass,info)
|
264
|
+
|
265
|
+
table_list = @conn.list_tables
|
266
|
+
|
267
|
+
needed_tables = [ info[:table], info[:first_table], info[:second_table] ]
|
268
|
+
missing_tables = Array.new
|
269
|
+
needed_tables.each do |table|
|
270
|
+
missing_tables << table unless table_list.include?(table)
|
186
271
|
end
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
for data in indices
|
194
|
-
idx, options = *data
|
195
|
-
idx = idx.to_s
|
196
|
-
pre_sql, post_sql = options[:pre], options[:post]
|
197
|
-
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
198
|
-
sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
|
272
|
+
if missing_tables.size > 0
|
273
|
+
msg = "Join table #{info[:table]} needs PostgreSQL foreign key constraints but the following table"
|
274
|
+
msg << (missing_tables.size > 1 ? "s were " : " was ")
|
275
|
+
msg << "missing: "
|
276
|
+
missing_tables.each do |table|
|
277
|
+
msg << "#{table}, "
|
199
278
|
end
|
279
|
+
Logger.debug msg[0..-3] + ". (Should be retried later)."
|
280
|
+
return false
|
200
281
|
end
|
201
282
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
283
|
+
#This info should maybe be in join metadata?
|
284
|
+
target_class = nil
|
285
|
+
klass.relations.each do |rel|
|
286
|
+
if rel.join_table == info[:table]
|
287
|
+
target_class = rel.target_class
|
288
|
+
break
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
if target_class
|
293
|
+
Logger.debug "Adding PostgreSQL foreign key constraints to #{info[:table]}"
|
294
|
+
|
295
|
+
# TODO: This should also interrograte the constraint definition
|
296
|
+
# incase people meddle with the database in an insane fashion
|
297
|
+
# (very, very low priority)
|
298
|
+
|
299
|
+
existing_constraints = @conn.table_foreign_keys(info[:table]).map {|fk| fk.first}
|
300
|
+
|
301
|
+
constraints = Array.new
|
302
|
+
constraints << { :table => info[:first_table], :join_table => info[:table], :primary_key => klass.primary_key.field || klass.primary_key.symbol, :foreign_key => info[:first_key] }
|
303
|
+
constraints << { :table => info[:second_table], :join_table => info[:table], :primary_key => target_class.primary_key.field || target_class.primary_key.symbol, :foreign_key => info[:second_key] }
|
304
|
+
|
305
|
+
constraints.each do |constraint|
|
306
|
+
constraint_name = "ogc_#{constraint[:table]}_#{constraint[:foreign_key]}"
|
307
|
+
if existing_constraints.include?(constraint_name)
|
308
|
+
Logger.debug "PostgreSQL foreign key constraint linking #{constraint[:foreign_key]} on #{constraint[:join_table]} to #{constraint[:primary_key]} on #{constraint[:table]} already exists (#{constraint_name})."
|
309
|
+
next
|
310
|
+
end
|
311
|
+
sql = "ALTER TABLE #{constraint[:join_table]} ADD CONSTRAINT #{constraint_name} FOREIGN KEY (#{constraint[:foreign_key]}) REFERENCES #{constraint[:table]} (#{constraint[:primary_key]}) ON UPDATE CASCADE ON DELETE CASCADE"
|
312
|
+
@conn.exec(sql).clear
|
313
|
+
Logger.debug "Added PostgreSQL foreign key constraint linking #{constraint[:foreign_key]} on #{constraint[:join_table]} to #{constraint[:primary_key]} on #{constraint[:table]} (#{constraint_name})."
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def create_table(klass)
|
319
|
+
fields = fields_for_class(klass)
|
320
|
+
|
321
|
+
unless @conn.table_exists? klass::OGTABLE
|
322
|
+
|
323
|
+
sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
|
324
|
+
|
325
|
+
# Create table constraints.
|
326
|
+
|
327
|
+
if constraints = klass.ann.self[:sql_constraint]
|
328
|
+
sql << ", #{constraints.join(', ')}"
|
329
|
+
end
|
330
|
+
|
331
|
+
sql << ") WITHOUT OIDS;"
|
332
|
+
|
333
|
+
# Create indices.
|
334
|
+
|
335
|
+
if indices = klass.ann.self[:index]
|
336
|
+
for data in indices
|
337
|
+
idx, options = *data
|
338
|
+
idx = idx.to_s
|
339
|
+
pre_sql, post_sql = options[:pre], options[:post]
|
340
|
+
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
341
|
+
sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
@conn.exec(sql).clear
|
346
|
+
Logger.info "Created table '#{klass::OGTABLE}'."
|
347
|
+
else
|
348
|
+
Logger.debug "Table #{klass::OGTABLE} already exists"
|
349
|
+
#rp: basic field interrogation
|
350
|
+
# TODO: Add type checking.
|
351
|
+
|
352
|
+
actual_fields = @conn.table_field_list(klass::OGTABLE).map {|pair| pair.first}
|
353
|
+
|
354
|
+
#Make new ones always - don't destroy by default because it might contain data you want back.
|
355
|
+
need_fields = fields.each do |needed_field|
|
356
|
+
field_name = needed_field[0..(needed_field.index(' ')-1)]
|
357
|
+
next if actual_fields.include?(field_name)
|
358
|
+
|
359
|
+
if @options[:evolve_schema] == true
|
360
|
+
Logger.debug "Adding field '#{needed_field}' to '#{klass::OGTABLE}'"
|
361
|
+
sql = "ALTER TABLE #{klass::OGTABLE} ADD COLUMN #{needed_field}"
|
362
|
+
@conn.exec(sql)
|
363
|
+
else
|
364
|
+
Logger.info "WARNING: Table '#{klass::OGTABLE}' is missing field '#{needed_field}' and :evolve_schema is not set to true!"
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
#Drop old ones
|
369
|
+
needed_fields = fields.map {|f| f =~ /^([^ ]+)/; $1}
|
370
|
+
actual_fields.each do |obsolete_field|
|
371
|
+
next if needed_fields.include?(obsolete_field)
|
372
|
+
if @options[:evolve_schema] == true and @options[:evolve_schema_cautious] == false
|
373
|
+
sql = "ALTER TABLE #{klass::OGTABLE} DROP COLUMN #{obsolete_field}"
|
374
|
+
Logger.debug "Removing obsolete field '#{obsolete_field}' from '#{klass::OGTABLE}'"
|
375
|
+
@conn.exec(sql)
|
376
|
+
else
|
377
|
+
Logger.info "WARNING: You have an obsolete field '#{obsolete_field}' on table '#{klass::OGTABLE}' and :evolve_schema is not set or is in cautious mode!"
|
378
|
+
end
|
212
379
|
end
|
213
380
|
end
|
214
381
|
|
215
382
|
# Create join tables if needed. Join tables are used in
|
216
383
|
# 'many_to_many' relations.
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
if ex.to_s =~ /relation .* already exists/i
|
228
|
-
Logger.debug 'Join table already exists' if $DBG
|
384
|
+
players = klass.resolve_remote_relations.map{|rel| rel.owner_class if rel.collection}.compact.uniq
|
385
|
+
players << klass
|
386
|
+
players.each do |player|
|
387
|
+
if join_tables = player.ann.self[:join_tables]
|
388
|
+
for info in join_tables
|
389
|
+
unless @conn.table_exists? info[:table]
|
390
|
+
create_join_table_sql(info).each do |sql|
|
391
|
+
@conn.exec(sql).clear
|
392
|
+
end
|
393
|
+
Logger.debug "Created jointable '#{info[:table]}'."
|
229
394
|
else
|
230
|
-
|
395
|
+
Logger.debug "Join table '#{info[:table]}' already exists."
|
231
396
|
end
|
397
|
+
create_join_table_foreign_key_constraints(player,info)
|
232
398
|
end
|
233
399
|
end
|
234
400
|
end
|
401
|
+
|
235
402
|
end
|
236
403
|
|
237
404
|
def drop_table(klass)
|
238
|
-
|
239
|
-
|
405
|
+
# foreign key constraints will remove the need to do manual cleanup on
|
406
|
+
# postgresql join tables.
|
407
|
+
exec "DROP TABLE #{klass.table} CASCADE"
|
240
408
|
end
|
241
409
|
|
242
410
|
def create_field_map(klass)
|
243
411
|
res = @conn.exec "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
|
244
412
|
map = {}
|
245
|
-
|
413
|
+
|
246
414
|
for field in res.fields
|
247
415
|
map[field.intern] = res.fieldnum(field)
|
248
416
|
end
|
249
|
-
|
417
|
+
|
250
418
|
return map
|
251
419
|
ensure
|
252
420
|
res.clear if res
|
@@ -284,23 +452,23 @@ private
|
|
284
452
|
props << Property.new(:symbol => :ogtype, :klass => String)
|
285
453
|
values << ", '#{klass}'"
|
286
454
|
end
|
287
|
-
|
455
|
+
|
288
456
|
sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
|
289
457
|
|
290
458
|
klass.class_eval %{
|
291
459
|
def og_insert(store)
|
292
|
-
#{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
460
|
+
#{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
293
461
|
res = store.conn.exec "SELECT nextval('#{klass::OGSEQ}')"
|
294
462
|
@#{klass.pk_symbol} = res.getvalue(0, 0).to_i
|
295
463
|
res.clear
|
296
464
|
store.conn.exec("#{sql}").clear
|
297
|
-
#{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
465
|
+
#{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
298
466
|
end
|
299
467
|
}
|
300
468
|
end
|
301
469
|
|
302
470
|
def eval_og_allocate(klass)
|
303
|
-
if klass.ann.
|
471
|
+
if klass.ann.self[:subclasses]
|
304
472
|
klass.module_eval %{
|
305
473
|
def self.og_allocate(res, row = 0)
|
306
474
|
Object.constant(res.getvalue(row, 0)).allocate
|
@@ -328,3 +496,4 @@ end
|
|
328
496
|
# * George Moschovitis <gm@navel.gr>
|
329
497
|
# * Michael Neumann <mneumann@ntecs.de>
|
330
498
|
# * Ysabel <deb@ysabel.org>
|
499
|
+
# * Rob Pitt <rob@motionpath.com>
|
data/lib/og/store/sql.rb
CHANGED
@@ -8,7 +8,7 @@ module Og
|
|
8
8
|
module SqlUtils
|
9
9
|
|
10
10
|
# Escape an SQL string
|
11
|
-
|
11
|
+
|
12
12
|
def escape(str)
|
13
13
|
return nil unless str
|
14
14
|
return str.gsub(/'/, "''")
|
@@ -16,19 +16,19 @@ module SqlUtils
|
|
16
16
|
|
17
17
|
# Convert a ruby time to an sql timestamp.
|
18
18
|
#--
|
19
|
-
# TODO: Optimize this
|
19
|
+
# TODO: Optimize this.
|
20
20
|
#++
|
21
|
-
|
21
|
+
|
22
22
|
def timestamp(time = Time.now)
|
23
23
|
return nil unless time
|
24
24
|
return time.strftime("%Y-%m-%d %H:%M:%S")
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
# Output YYY-mm-dd
|
28
28
|
#--
|
29
29
|
# TODO: Optimize this.
|
30
30
|
#++
|
31
|
-
|
31
|
+
|
32
32
|
def date(date)
|
33
33
|
return nil unless date
|
34
34
|
return "#{date.year}-#{date.month}-#{date.mday}"
|
@@ -37,40 +37,40 @@ module SqlUtils
|
|
37
37
|
#--
|
38
38
|
# TODO: implement me!
|
39
39
|
#++
|
40
|
-
|
40
|
+
|
41
41
|
def blob(val)
|
42
42
|
val
|
43
43
|
end
|
44
44
|
|
45
45
|
# Parse an integer.
|
46
|
-
|
46
|
+
|
47
47
|
def parse_int(int)
|
48
48
|
int = int.to_i if int
|
49
49
|
int
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
# Parse a float.
|
53
|
-
|
53
|
+
|
54
54
|
def parse_float(fl)
|
55
55
|
fl = fl.to_f if fl
|
56
56
|
fl
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
# Parse sql datetime
|
60
60
|
#--
|
61
61
|
# TODO: Optimize this.
|
62
62
|
#++
|
63
|
-
|
63
|
+
|
64
64
|
def parse_timestamp(str)
|
65
65
|
return nil unless str
|
66
66
|
return Time.parse(str)
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
# Input YYYY-mm-dd
|
70
70
|
#--
|
71
71
|
# TODO: Optimize this.
|
72
72
|
#++
|
73
|
-
|
73
|
+
|
74
74
|
def parse_date(str)
|
75
75
|
return nil unless str
|
76
76
|
return Date.strptime(str)
|
@@ -79,13 +79,13 @@ module SqlUtils
|
|
79
79
|
#--
|
80
80
|
# TODO: implement me!!
|
81
81
|
#++
|
82
|
-
|
82
|
+
|
83
83
|
def parse_blob(val)
|
84
84
|
val
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
# Escape the various Ruby types.
|
88
|
-
|
88
|
+
|
89
89
|
def quote(val)
|
90
90
|
case val
|
91
91
|
when Fixnum, Integer, Float
|
@@ -98,20 +98,20 @@ module SqlUtils
|
|
98
98
|
val ? "'#{date(val)}'" : 'NULL'
|
99
99
|
when TrueClass
|
100
100
|
val ? "'t'" : 'NULL'
|
101
|
-
else
|
101
|
+
else
|
102
102
|
# gmosx: keep the '' for nil symbols.
|
103
103
|
val ? escape(val.to_yaml) : ''
|
104
|
-
end
|
104
|
+
end
|
105
105
|
end
|
106
106
|
|
107
107
|
# Apply table name conventions to a class name.
|
108
|
-
|
108
|
+
|
109
109
|
def tableize(klass)
|
110
110
|
"#{klass.to_s.gsub(/::/, "_").downcase}"
|
111
111
|
end
|
112
112
|
|
113
113
|
def table(klass)
|
114
|
-
klass.ann.
|
114
|
+
klass.ann.self[:sql_table] || klass.ann.self[:table] || "#{Og.table_prefix}#{tableize(klass)}"
|
115
115
|
end
|
116
116
|
|
117
117
|
def join_object_ordering(obj1, obj2)
|
@@ -129,7 +129,7 @@ module SqlUtils
|
|
129
129
|
return class2, class1, true
|
130
130
|
end
|
131
131
|
end
|
132
|
-
|
132
|
+
|
133
133
|
def build_join_name(class1, class2, postfix = nil)
|
134
134
|
# Don't reorder arguments, as this is used in places that
|
135
135
|
# have already determined the order they want.
|
@@ -149,14 +149,14 @@ module SqlUtils
|
|
149
149
|
klass = klass.schema_inheritance_root_class if klass.schema_inheritance_child?
|
150
150
|
"#{klass.to_s.split('::').last.downcase}_oid"
|
151
151
|
end
|
152
|
-
|
152
|
+
|
153
153
|
def join_table_keys(class1, class2)
|
154
154
|
if class1 == class2
|
155
155
|
# Fix for the self-join case.
|
156
156
|
return join_table_key(class1), "#{join_table_key(class2)}2"
|
157
157
|
else
|
158
158
|
return join_table_key(class1), join_table_key(class2)
|
159
|
-
end
|
159
|
+
end
|
160
160
|
end
|
161
161
|
|
162
162
|
def ordered_join_table_keys(class1, class2)
|
@@ -165,7 +165,7 @@ module SqlUtils
|
|
165
165
|
end
|
166
166
|
|
167
167
|
def join_table_info(owner_class, target_class, postfix = nil)
|
168
|
-
|
168
|
+
|
169
169
|
# some fixes for schema inheritance.
|
170
170
|
|
171
171
|
owner_class = owner_class.schema_inheritance_root_class if owner_class.schema_inheritance_child?
|
@@ -173,13 +173,13 @@ module SqlUtils
|
|
173
173
|
|
174
174
|
owner_key, target_key = join_table_keys(owner_class, target_class)
|
175
175
|
first, second, changed = join_class_ordering(owner_class, target_class)
|
176
|
-
|
176
|
+
|
177
177
|
if changed
|
178
178
|
first_key, second_key = target_key, owner_key
|
179
179
|
else
|
180
180
|
first_key, second_key = owner_key, target_key
|
181
181
|
end
|
182
|
-
|
182
|
+
|
183
183
|
return {
|
184
184
|
:table => join_table(owner_class, target_class, postfix),
|
185
185
|
:owner_key => owner_key,
|
@@ -195,7 +195,7 @@ module SqlUtils
|
|
195
195
|
|
196
196
|
# Subclasses can override this if they need a different
|
197
197
|
# syntax.
|
198
|
-
|
198
|
+
|
199
199
|
def create_join_table_sql(join_table_info, suffix = 'NOT NULL', key_type = 'integer')
|
200
200
|
join_table = join_table_info[:table]
|
201
201
|
first_index = join_table_info[:first_index]
|
@@ -210,13 +210,13 @@ module SqlUtils
|
|
210
210
|
#{first_key} integer NOT NULL,
|
211
211
|
#{second_key} integer NOT NULL,
|
212
212
|
PRIMARY KEY(#{first_key}, #{second_key})
|
213
|
-
)
|
213
|
+
)
|
214
214
|
}
|
215
|
-
|
215
|
+
|
216
216
|
# gmosx: not that useful?
|
217
217
|
# sql << "CREATE INDEX #{first_index} ON #{join_table} (#{first_key})"
|
218
218
|
# sql << "CREATE INDEX #{second_index} ON #{join_table} (#{second_key})"
|
219
|
-
|
219
|
+
|
220
220
|
return sql
|
221
221
|
end
|
222
222
|
|
@@ -237,7 +237,7 @@ class SqlStore < Store
|
|
237
237
|
|
238
238
|
# The default Ruby <-> SQL type mappings, should be valid
|
239
239
|
# for most RDBM systems.
|
240
|
-
|
240
|
+
|
241
241
|
@typemap = {
|
242
242
|
Integer => 'integer',
|
243
243
|
Fixnum => 'integer',
|
@@ -265,12 +265,36 @@ class SqlStore < Store
|
|
265
265
|
Glue::Aspects.wrap(klass, [:exec, :query])
|
266
266
|
end
|
267
267
|
|
268
|
-
#
|
268
|
+
# Returns a list of tables that exist within the database but are
|
269
|
+
# not managed by the supplied manager.
|
270
|
+
|
271
|
+
def unmanaged_tables(manager)
|
272
|
+
ret = Array.new
|
273
|
+
mt = managed_tables(manager)
|
274
|
+
@conn.list_tables.each do |table|
|
275
|
+
ret << table unless mt.include?(table)
|
276
|
+
end
|
277
|
+
ret
|
278
|
+
end
|
279
|
+
|
280
|
+
# Returns a list of tables within the database that are there to
|
281
|
+
# support a class managed by the supplied manager.
|
282
|
+
|
283
|
+
def managed_tables(manager)
|
284
|
+
ret = Array.new
|
285
|
+
manager.managed_classes.each do |klass|
|
286
|
+
ret << klass::OGTABLE
|
287
|
+
ret.concat(klass.relations.reject{|rel| not rel.options[:join_table]}.map{|rel| rel.options[:join_table]})
|
288
|
+
end
|
289
|
+
ret
|
290
|
+
end
|
291
|
+
|
292
|
+
# Enchants a class.
|
269
293
|
|
270
294
|
def enchant(klass, manager)
|
271
295
|
|
272
296
|
# setup the table where this class is mapped.
|
273
|
-
|
297
|
+
|
274
298
|
if klass.schema_inheritance_child?
|
275
299
|
# farms: allow deeper inheritance (TODO: use annotation :superclass)
|
276
300
|
klass.const_set 'OGTABLE', table(klass.schema_inheritance_root_class)
|
@@ -279,13 +303,13 @@ class SqlStore < Store
|
|
279
303
|
end
|
280
304
|
|
281
305
|
klass.module_eval 'def self.table; OGTABLE; end'
|
282
|
-
|
306
|
+
|
283
307
|
eval_og_allocate(klass)
|
284
|
-
|
308
|
+
|
285
309
|
super
|
286
310
|
|
287
311
|
unless klass.polymorphic_parent?
|
288
|
-
# precompile class specific lifecycle methods.
|
312
|
+
# precompile class specific lifecycle methods.
|
289
313
|
eval_og_create_schema(klass)
|
290
314
|
eval_og_insert(klass)
|
291
315
|
eval_og_update(klass)
|
@@ -309,7 +333,9 @@ class SqlStore < Store
|
|
309
333
|
# Loads an object from the store using the primary key.
|
310
334
|
|
311
335
|
def load(pk, klass)
|
312
|
-
|
336
|
+
sql = "SELECT * FROM #{klass::OGTABLE} WHERE #{klass.pk_symbol}=#{pk}"
|
337
|
+
sql << " AND ogtype='#{klass}'" if klass.schema_inheritance_child?
|
338
|
+
res = query sql
|
313
339
|
read_one(res, klass)
|
314
340
|
end
|
315
341
|
alias_method :exist?, :load
|
@@ -318,7 +344,9 @@ class SqlStore < Store
|
|
318
344
|
|
319
345
|
def reload(obj, pk)
|
320
346
|
raise 'Cannot reload unmanaged object' unless obj.saved?
|
321
|
-
|
347
|
+
sql = "SELECT * FROM #{obj.class.table} WHERE #{obj.class.pk_symbol}=#{pk}"
|
348
|
+
sql << " AND ogtype='#{obj.class}'" if obj.class.schema_inheritance_child?
|
349
|
+
res = query sql
|
322
350
|
obj.og_read(res.next, 0)
|
323
351
|
ensure
|
324
352
|
res.close if res
|
@@ -330,7 +358,7 @@ class SqlStore < Store
|
|
330
358
|
#--
|
331
359
|
# gmosx, THINK: condition is not really useful here :(
|
332
360
|
#++
|
333
|
-
|
361
|
+
|
334
362
|
def update(obj, options = nil)
|
335
363
|
if options and properties = options[:only]
|
336
364
|
if properties.is_a?(Array)
|
@@ -352,7 +380,7 @@ class SqlStore < Store
|
|
352
380
|
|
353
381
|
# Update selected properties of an object or class of
|
354
382
|
# objects.
|
355
|
-
|
383
|
+
|
356
384
|
def update_properties(target, *properties)
|
357
385
|
update(target, :only => properties)
|
358
386
|
end
|
@@ -360,10 +388,10 @@ class SqlStore < Store
|
|
360
388
|
alias_method :update_property, :update_properties
|
361
389
|
|
362
390
|
# More generalized method, also allows for batch updates.
|
363
|
-
|
391
|
+
|
364
392
|
def update_by_sql(target, set, options = nil)
|
365
393
|
set = set.gsub(/@/, '')
|
366
|
-
|
394
|
+
|
367
395
|
if target.is_a?(Class)
|
368
396
|
sql = "UPDATE #{target.table} SET #{set} "
|
369
397
|
sql << " WHERE #{options[:condition]}" if options and options[:condition]
|
@@ -381,7 +409,7 @@ class SqlStore < Store
|
|
381
409
|
#
|
382
410
|
# User.find(:condition => 'age > 15', :order => 'score ASC', :offet => 10, :limit =>10)
|
383
411
|
# Comment.find(:include => :entry)
|
384
|
-
|
412
|
+
|
385
413
|
def find(options)
|
386
414
|
klass = options[:class]
|
387
415
|
sql = resolve_options(klass, options)
|
@@ -389,7 +417,7 @@ class SqlStore < Store
|
|
389
417
|
end
|
390
418
|
|
391
419
|
# Find one object.
|
392
|
-
|
420
|
+
|
393
421
|
def find_one(options)
|
394
422
|
klass = options[:class]
|
395
423
|
# gmosx, THINK: should not set this by default.
|
@@ -400,15 +428,15 @@ class SqlStore < Store
|
|
400
428
|
|
401
429
|
# Perform a custom sql query and deserialize the
|
402
430
|
# results.
|
403
|
-
|
431
|
+
|
404
432
|
def select(sql, klass)
|
405
433
|
sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
|
406
434
|
read_all(query(sql), klass)
|
407
435
|
end
|
408
436
|
alias_method :find_by_sql, :select
|
409
|
-
|
437
|
+
|
410
438
|
# Specialized one result version of select.
|
411
|
-
|
439
|
+
|
412
440
|
def select_one(sql, klass)
|
413
441
|
sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
|
414
442
|
read_one(query(sql), klass)
|
@@ -416,7 +444,7 @@ class SqlStore < Store
|
|
416
444
|
alias_method :find_by_sql_one, :select_one
|
417
445
|
|
418
446
|
# Perform an aggregation over query results.
|
419
|
-
|
447
|
+
|
420
448
|
def aggregate(options)
|
421
449
|
if options.is_a?(String)
|
422
450
|
sql = options
|
@@ -425,16 +453,20 @@ class SqlStore < Store
|
|
425
453
|
sql = "SELECT #{aggregate} FROM #{options[:class].table}"
|
426
454
|
if condition = options[:condition]
|
427
455
|
sql << " WHERE #{condition}"
|
456
|
+
sql << " AND " if options[:class].schema_inheritance_child?
|
457
|
+
else
|
458
|
+
sql << " WHERE " if options[:class].schema_inheritance_child?
|
428
459
|
end
|
429
|
-
|
430
|
-
|
460
|
+
sql << "ogtype='#{options[:class]}'" if options[:class].schema_inheritance_child?
|
461
|
+
end
|
462
|
+
|
431
463
|
query(sql).first_value.to_i
|
432
464
|
end
|
433
465
|
alias_method :count, :aggregate
|
434
|
-
|
466
|
+
|
435
467
|
# Relate two objects through an intermediate join table.
|
436
468
|
# Typically used in joins_many and many_to_many relations.
|
437
|
-
|
469
|
+
|
438
470
|
def join(obj1, obj2, table, options = nil)
|
439
471
|
first, second = join_object_ordering(obj1, obj2)
|
440
472
|
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
@@ -443,48 +475,48 @@ class SqlStore < Store
|
|
443
475
|
else
|
444
476
|
exec "INSERT INTO #{table} (#{first_key},#{second_key}) VALUES (#{first.pk}, #{second.pk})"
|
445
477
|
end
|
446
|
-
end
|
478
|
+
end
|
447
479
|
|
448
480
|
# Unrelate two objects be removing their relation from the
|
449
481
|
# join table.
|
450
|
-
|
482
|
+
|
451
483
|
def unjoin(obj1, obj2, table)
|
452
484
|
first, second = join_object_ordering(obj1, obj2)
|
453
485
|
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
454
486
|
exec "DELETE FROM #{table} WHERE #{first_key}=#{first.pk} AND #{second_key}=#{second.pk}"
|
455
487
|
end
|
456
|
-
|
488
|
+
|
457
489
|
def delete_all(klass)
|
458
490
|
exec "DELETE FROM #{klass.table}"
|
459
491
|
end
|
460
|
-
|
492
|
+
|
461
493
|
# :section: Transaction methods.
|
462
|
-
|
494
|
+
|
463
495
|
# Start a new transaction.
|
464
|
-
|
496
|
+
|
465
497
|
def start
|
466
498
|
exec('START TRANSACTION') if @transaction_nesting < 1
|
467
499
|
@transaction_nesting += 1
|
468
500
|
end
|
469
|
-
|
501
|
+
|
470
502
|
# Commit a transaction.
|
471
|
-
|
503
|
+
|
472
504
|
def commit
|
473
505
|
@transaction_nesting -= 1
|
474
506
|
exec('COMMIT') if @transaction_nesting < 1
|
475
507
|
end
|
476
|
-
|
508
|
+
|
477
509
|
# Rollback a transaction.
|
478
|
-
|
510
|
+
|
479
511
|
def rollback
|
480
512
|
@transaction_nesting -= 1
|
481
513
|
exec('ROLLBACK') if @transaction_nesting < 1
|
482
514
|
end
|
483
515
|
|
484
516
|
# :section: Low level methods.
|
485
|
-
|
517
|
+
|
486
518
|
# Encapsulates a low level update method.
|
487
|
-
|
519
|
+
|
488
520
|
def sql_update(sql)
|
489
521
|
exec(sql)
|
490
522
|
# return affected rows.
|
@@ -503,6 +535,13 @@ private
|
|
503
535
|
# persisted.
|
504
536
|
|
505
537
|
def drop_table(klass)
|
538
|
+
# Remove leftover data from some join tabkes.
|
539
|
+
klass.relations.each do |rel|
|
540
|
+
if rel.class.to_s == "Og::JoinsMany" and rel.join_table
|
541
|
+
target_class = rel.target_class
|
542
|
+
exec "DELETE FROM #{rel.join_table}"
|
543
|
+
end
|
544
|
+
end
|
506
545
|
exec "DROP TABLE #{klass.table}"
|
507
546
|
end
|
508
547
|
alias_method :destroy, :drop_table
|
@@ -632,7 +671,7 @@ private
|
|
632
671
|
props = klass.properties.values.dup
|
633
672
|
values = props.collect { |p| write_prop(p) }.join(',')
|
634
673
|
|
635
|
-
if klass.ann.
|
674
|
+
if klass.ann.self[:superclass] or klass.ann.self[:subclasses]
|
636
675
|
props << Property.new(:symbol => :ogtype, :klass => String)
|
637
676
|
values << ", '#{klass}'"
|
638
677
|
end
|
@@ -641,9 +680,9 @@ private
|
|
641
680
|
|
642
681
|
klass.module_eval %{
|
643
682
|
def og_insert(store)
|
644
|
-
#{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
683
|
+
#{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
645
684
|
store.exec "#{sql}"
|
646
|
-
#{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
685
|
+
#{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
647
686
|
end
|
648
687
|
}
|
649
688
|
end
|
@@ -662,11 +701,11 @@ private
|
|
662
701
|
|
663
702
|
klass.module_eval %{
|
664
703
|
def og_update(store, options = nil)
|
665
|
-
#{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
|
704
|
+
#{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
|
666
705
|
sql = "#{sql}"
|
667
706
|
sql << " AND \#{options[:condition]}" if options and options[:condition]
|
668
707
|
changed = store.sql_update(sql)
|
669
|
-
#{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
|
708
|
+
#{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
|
670
709
|
return changed
|
671
710
|
end
|
672
711
|
}
|
@@ -684,57 +723,57 @@ private
|
|
684
723
|
|
685
724
|
for p in props
|
686
725
|
f = field_for_property(p).to_sym
|
687
|
-
|
726
|
+
|
688
727
|
if col = field_map[f]
|
689
728
|
code << "@#{p} = #{read_prop(p, col)}"
|
690
729
|
end
|
691
730
|
end
|
692
|
-
|
731
|
+
|
693
732
|
code = code.join('; ')
|
694
733
|
|
695
734
|
klass.module_eval %{
|
696
735
|
def og_read(res, row = 0, offset = 0)
|
697
|
-
#{Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
|
736
|
+
#{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
|
698
737
|
#{code}
|
699
|
-
#{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
|
738
|
+
#{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
|
700
739
|
end
|
701
740
|
}
|
702
|
-
end
|
741
|
+
end
|
703
742
|
|
704
743
|
#--
|
705
744
|
# FIXME: is pk needed as parameter?
|
706
745
|
#++
|
707
|
-
|
746
|
+
|
708
747
|
def eval_og_delete(klass)
|
709
748
|
klass.module_eval %{
|
710
749
|
def og_delete(store, pk, cascade = true)
|
711
|
-
#{Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
|
750
|
+
#{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
|
712
751
|
pk ||= @#{klass.pk_symbol}
|
713
752
|
transaction do |tx|
|
714
753
|
tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}"
|
715
|
-
if cascade and #{klass}.ann.
|
716
|
-
#{klass}.ann.
|
754
|
+
if cascade and #{klass}.ann.self[:descendants]
|
755
|
+
#{klass}.ann.self.descendants.each do |dclass, foreign_key|
|
717
756
|
tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}"
|
718
757
|
end
|
719
758
|
end
|
720
759
|
end
|
721
|
-
#{Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
|
760
|
+
#{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
|
722
761
|
end
|
723
|
-
}
|
762
|
+
}
|
724
763
|
end
|
725
764
|
|
726
765
|
# Creates the schema for this class. Can be intercepted with
|
727
766
|
# aspects to add special behaviours.
|
728
|
-
|
767
|
+
|
729
768
|
def eval_og_create_schema(klass)
|
730
769
|
klass.module_eval %{
|
731
770
|
def og_create_schema(store)
|
732
771
|
if Og.create_schema
|
733
|
-
#{Aspects.gen_advice_code(:og_create_schema, klass.advices, :pre) if klass.respond_to?(:advices)}
|
772
|
+
#{Glue::Aspects.gen_advice_code(:og_create_schema, klass.advices, :pre) if klass.respond_to?(:advices)}
|
734
773
|
unless self.class.superclass.ancestors.include? SchemaInheritanceBase
|
735
774
|
store.send(:create_table, #{klass})
|
736
775
|
end
|
737
|
-
#{Aspects.gen_advice_code(:og_create_schema, klass.advices, :post) if klass.respond_to?(:advices)}
|
776
|
+
#{Glue::Aspects.gen_advice_code(:og_create_schema, klass.advices, :post) if klass.respond_to?(:advices)}
|
738
777
|
end
|
739
778
|
end
|
740
779
|
}
|
@@ -758,7 +797,7 @@ private
|
|
758
797
|
}
|
759
798
|
end
|
760
799
|
end
|
761
|
-
|
800
|
+
|
762
801
|
# :section: Misc methods.
|
763
802
|
|
764
803
|
def handle_sql_exception(ex, sql = nil)
|
@@ -770,12 +809,26 @@ private
|
|
770
809
|
return nil
|
771
810
|
end
|
772
811
|
|
812
|
+
# Resolve the finder options. Also takes scope into account.
|
813
|
+
#--
|
814
|
+
# FIXME: cleanup/refactor.
|
815
|
+
#++
|
816
|
+
|
773
817
|
def resolve_options(klass, options)
|
818
|
+
# Factor in scope.
|
819
|
+
|
820
|
+
if scope = klass.get_scope
|
821
|
+
scope = scope.dup
|
822
|
+
scond = scope.delete(:condition)
|
823
|
+
scope.update(options)
|
824
|
+
options = scope
|
825
|
+
end
|
826
|
+
|
774
827
|
if sql = options[:sql]
|
775
828
|
sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
|
776
829
|
return sql
|
777
830
|
end
|
778
|
-
|
831
|
+
|
779
832
|
tables = [klass::OGTABLE]
|
780
833
|
|
781
834
|
if included = options[:include]
|
@@ -785,7 +838,7 @@ private
|
|
785
838
|
if rel = klass.relation(name)
|
786
839
|
target_table = rel[:target_class]::OGTABLE
|
787
840
|
tables << target_table
|
788
|
-
|
841
|
+
|
789
842
|
if rel.is_a?(JoinsMany)
|
790
843
|
tables << rel[:join_table]
|
791
844
|
owner_key, target_key = klass.ogmanager.store.join_table_keys(klass, rel[:target_class])
|
@@ -813,16 +866,21 @@ private
|
|
813
866
|
update_condition options, options[:join_condition]
|
814
867
|
end
|
815
868
|
|
816
|
-
|
869
|
+
# Factor in scope in the conditions.
|
870
|
+
|
871
|
+
update_condition(options, scond) if scond
|
872
|
+
|
873
|
+
# rp: type is not set in all instances such as Class.first so this fix goes here for now.
|
874
|
+
if ogtype = options[:type] || (klass.schema_inheritance_child? ? "#{klass}" : nil)
|
817
875
|
update_condition options, "ogtype='#{ogtype}'"
|
818
876
|
end
|
819
877
|
|
820
878
|
sql = "SELECT #{fields} FROM #{tables.join(',')}"
|
821
|
-
|
879
|
+
|
822
880
|
if condition = options[:condition] || options[:where]
|
823
881
|
sql << " WHERE #{condition}"
|
824
882
|
end
|
825
|
-
|
883
|
+
|
826
884
|
if order = options[:order]
|
827
885
|
sql << " ORDER BY #{order}"
|
828
886
|
end
|
@@ -831,10 +889,10 @@ private
|
|
831
889
|
|
832
890
|
if extra = options[:extra]
|
833
891
|
sql << " #{extra}"
|
834
|
-
end
|
835
|
-
|
892
|
+
end
|
893
|
+
|
836
894
|
return sql
|
837
|
-
end
|
895
|
+
end
|
838
896
|
|
839
897
|
# Subclasses can override this if they need some other order.
|
840
898
|
# This is needed because different backends require different
|
@@ -853,15 +911,15 @@ private
|
|
853
911
|
# :section: Deserialization methods.
|
854
912
|
|
855
913
|
# Read a field (column) from a result set row.
|
856
|
-
|
914
|
+
|
857
915
|
def read_field
|
858
916
|
end
|
859
|
-
|
917
|
+
|
860
918
|
# Dynamicaly deserializes a result set row into an object.
|
861
919
|
# Used for specialized queries or join queries. Please
|
862
920
|
# not that this deserialization method is slower than the
|
863
921
|
# precompiled og_read method.
|
864
|
-
|
922
|
+
|
865
923
|
def read_row(obj, res, res_row, row)
|
866
924
|
res.fields.each_with_index do |field, idx|
|
867
925
|
obj.instance_variable_set "@#{field}", res_row[idx]
|
@@ -869,10 +927,10 @@ private
|
|
869
927
|
end
|
870
928
|
|
871
929
|
# Deserialize the join relations.
|
872
|
-
|
930
|
+
|
873
931
|
def read_join_relations(obj, res_row, row, join_relations)
|
874
932
|
offset = obj.class.properties.size
|
875
|
-
|
933
|
+
|
876
934
|
for rel in join_relations
|
877
935
|
rel_obj = rel[:target_class].og_allocate(res_row, row)
|
878
936
|
rel_obj.og_read(res_row, row, offset)
|
@@ -882,7 +940,7 @@ private
|
|
882
940
|
end
|
883
941
|
|
884
942
|
# Deserialize one object from the ResultSet.
|
885
|
-
|
943
|
+
|
886
944
|
def read_one(res, klass, options = nil)
|
887
945
|
return nil if res.blank?
|
888
946
|
|
@@ -891,11 +949,12 @@ private
|
|
891
949
|
klass.relation(n)
|
892
950
|
end
|
893
951
|
end
|
894
|
-
|
952
|
+
|
895
953
|
res_row = res.next
|
896
|
-
|
954
|
+
# causes STI classes to come back as the correct child class if accessed from the superclass
|
955
|
+
klass = Og::Entity::entity_from_string(res_row.result.flatten[res_row.fieldnum('ogtype')]) if klass.schema_inheritance?
|
897
956
|
obj = klass.og_allocate(res_row, 0)
|
898
|
-
|
957
|
+
|
899
958
|
if options and options[:select]
|
900
959
|
read_row(obj, res, res_row, 0)
|
901
960
|
else
|
@@ -904,7 +963,7 @@ private
|
|
904
963
|
end
|
905
964
|
|
906
965
|
return obj
|
907
|
-
|
966
|
+
|
908
967
|
ensure
|
909
968
|
res.close
|
910
969
|
end
|
@@ -936,10 +995,10 @@ private
|
|
936
995
|
objects << obj
|
937
996
|
end
|
938
997
|
end
|
939
|
-
|
998
|
+
|
940
999
|
return objects
|
941
|
-
|
942
|
-
ensure
|
1000
|
+
|
1001
|
+
ensure
|
943
1002
|
res.close
|
944
1003
|
end
|
945
1004
|
|
@@ -962,3 +1021,4 @@ end
|
|
962
1021
|
# * Ghislain Mary
|
963
1022
|
# * Ysabel <deb@ysabel.org>
|
964
1023
|
# * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
|
1024
|
+
# * Rob Pitt <rob@motionpath.com>
|