og 0.31.0 → 0.40.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.
- data/doc/{AUTHORS → CONTRIBUTORS} +26 -10
- data/doc/LICENSE +2 -3
- data/doc/RELEASES +56 -7
- data/doc/tutorial.txt +15 -15
- data/lib/glue/cacheable.rb +2 -5
- data/lib/glue/hierarchical.rb +1 -4
- data/lib/glue/optimistic_locking.rb +0 -2
- data/lib/glue/orderable.rb +79 -75
- data/lib/glue/revisable.rb +19 -24
- data/lib/glue/searchable.rb +0 -2
- data/lib/glue/taggable.rb +31 -29
- data/lib/glue/timestamped.rb +4 -2
- data/lib/og.rb +50 -29
- data/lib/og/adapter.rb +19 -0
- data/lib/og/adapter/mysql.rb +212 -0
- data/lib/og/adapter/mysql/override.rb +34 -0
- data/lib/og/adapter/mysql/script.rb +15 -0
- data/lib/og/adapter/mysql/utils.rb +40 -0
- data/lib/og/adapter/postgresql.rb +231 -0
- data/lib/og/adapter/postgresql/override.rb +117 -0
- data/lib/og/adapter/postgresql/script.rb +15 -0
- data/lib/og/adapter/postgresql/utils.rb +35 -0
- data/lib/og/adapter/sqlite.rb +132 -0
- data/lib/og/adapter/sqlite/override.rb +33 -0
- data/lib/og/adapter/sqlite/script.rb +15 -0
- data/lib/og/collection.rb +35 -7
- data/lib/og/{evolution.rb → dump.rb} +4 -5
- data/lib/og/entity.rb +102 -173
- data/lib/og/entity/clone.rb +119 -0
- data/lib/og/errors.rb +0 -2
- data/lib/og/manager.rb +85 -37
- data/lib/og/relation.rb +52 -34
- data/lib/og/relation/belongs_to.rb +0 -2
- data/lib/og/relation/has_many.rb +27 -4
- data/lib/og/relation/joins_many.rb +41 -14
- data/lib/og/relation/many_to_many.rb +10 -0
- data/lib/og/relation/refers_to.rb +22 -5
- data/lib/og/store.rb +80 -86
- data/lib/og/store/sql.rb +710 -713
- data/lib/og/store/sql/evolution.rb +119 -0
- data/lib/og/store/sql/join.rb +155 -0
- data/lib/og/store/sql/utils.rb +149 -0
- data/lib/og/test/assertions.rb +1 -3
- data/lib/og/test/testcase.rb +0 -2
- data/lib/og/types.rb +2 -5
- data/lib/og/validation.rb +6 -9
- data/test/{og/mixin → glue}/tc_hierarchical.rb +3 -13
- data/test/glue/tc_og_paginate.rb +47 -0
- data/test/{og/mixin → glue}/tc_optimistic_locking.rb +2 -12
- data/test/{og/mixin → glue}/tc_orderable.rb +15 -23
- data/test/glue/tc_orderable2.rb +47 -0
- data/test/glue/tc_revisable.rb +3 -3
- data/test/{og/mixin → glue}/tc_taggable.rb +20 -10
- data/test/{og/mixin → glue}/tc_timestamped.rb +2 -12
- data/test/glue/tc_webfile.rb +36 -0
- data/test/og/CONFIG.rb +8 -11
- data/test/og/multi_validations_model.rb +14 -0
- data/test/og/store/tc_filesys.rb +3 -1
- data/test/og/store/tc_kirby.rb +16 -13
- data/test/og/store/tc_sti.rb +11 -11
- data/test/og/store/tc_sti2.rb +79 -0
- data/test/og/tc_build.rb +41 -0
- data/test/og/tc_cacheable.rb +3 -2
- data/test/og/tc_has_many.rb +96 -0
- data/test/og/tc_inheritance.rb +6 -4
- data/test/og/tc_joins_many.rb +93 -0
- data/test/og/tc_multi_validations.rb +5 -7
- data/test/og/tc_multiple.rb +7 -6
- data/test/og/tc_override.rb +13 -7
- data/test/og/tc_primary_key.rb +30 -0
- data/test/og/tc_relation.rb +8 -14
- data/test/og/tc_reldelete.rb +163 -0
- data/test/og/tc_reverse.rb +17 -14
- data/test/og/tc_scoped.rb +3 -11
- data/test/og/tc_setup.rb +13 -11
- data/test/og/tc_store.rb +21 -28
- data/test/og/tc_validation2.rb +2 -2
- data/test/og/tc_validation_loop.rb +17 -15
- metadata +109 -103
- data/INSTALL +0 -91
- data/ProjectInfo +0 -51
- data/README +0 -177
- data/doc/config.txt +0 -28
- data/examples/README +0 -23
- data/examples/mysql_to_psql.rb +0 -71
- data/examples/run.rb +0 -271
- data/lib/glue/tree.rb +0 -218
- data/lib/og/store/alpha/filesys.rb +0 -110
- data/lib/og/store/alpha/memory.rb +0 -295
- data/lib/og/store/alpha/sqlserver.rb +0 -256
- data/lib/og/store/kirby.rb +0 -490
- data/lib/og/store/mysql.rb +0 -415
- data/lib/og/store/psql.rb +0 -875
- data/lib/og/store/sqlite.rb +0 -348
- data/lib/og/store/sqlite2.rb +0 -241
- data/setup.rb +0 -1585
- data/test/og/tc_sti_find.rb +0 -35
data/lib/og/store/sql.rb
CHANGED
@@ -1,280 +1,31 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
4
|
-
require 'facet/kernel/constant'
|
5
|
-
require 'facet/string/capitalized'
|
6
|
-
require 'facet/ormsupport'
|
1
|
+
require 'facets/core/module/ancestor'
|
2
|
+
require 'facets/core/class/subclasses'
|
7
3
|
|
4
|
+
require 'og/store'
|
5
|
+
require 'og/store/sql/utils'
|
6
|
+
require 'og/store/sql/join'
|
7
|
+
require 'og/store/sql/evolution'
|
8
8
|
|
9
9
|
module Og
|
10
10
|
|
11
|
-
#
|
12
|
-
|
13
|
-
module SqlUtils
|
14
|
-
|
15
|
-
# Escape an SQL string
|
16
|
-
|
17
|
-
def escape(str)
|
18
|
-
return nil unless str
|
19
|
-
return str.gsub(/'/, "''")
|
20
|
-
end
|
21
|
-
|
22
|
-
# Convert a ruby time to an sql timestamp.
|
23
|
-
#--
|
24
|
-
# TODO: Optimize this.
|
25
|
-
#++
|
26
|
-
|
27
|
-
def timestamp(time = Time.now)
|
28
|
-
return nil unless time
|
29
|
-
return time.strftime("%Y-%m-%d %H:%M:%S")
|
30
|
-
end
|
31
|
-
|
32
|
-
# Output YYY-mm-dd
|
33
|
-
#--
|
34
|
-
# TODO: Optimize this.
|
35
|
-
#++
|
36
|
-
|
37
|
-
def date(date)
|
38
|
-
return nil unless date
|
39
|
-
return "#{date.year}-#{date.month}-#{date.mday}"
|
40
|
-
end
|
41
|
-
|
42
|
-
#--
|
43
|
-
# TODO: implement me!
|
44
|
-
#++
|
45
|
-
|
46
|
-
def blob(val)
|
47
|
-
val
|
48
|
-
end
|
49
|
-
|
50
|
-
# Parse an integer.
|
51
|
-
|
52
|
-
def parse_int(int)
|
53
|
-
int = int.to_i if int
|
54
|
-
int
|
55
|
-
end
|
56
|
-
|
57
|
-
# Parse a float.
|
58
|
-
|
59
|
-
def parse_float(fl)
|
60
|
-
fl = fl.to_f if fl
|
61
|
-
fl
|
62
|
-
end
|
63
|
-
|
64
|
-
# Parse sql datetime
|
65
|
-
#--
|
66
|
-
# TODO: Optimize this.
|
67
|
-
#++
|
68
|
-
|
69
|
-
def parse_timestamp(str)
|
70
|
-
return nil unless str
|
71
|
-
return Time.parse(str)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Input YYYY-mm-dd
|
75
|
-
#--
|
76
|
-
# TODO: Optimize this.
|
77
|
-
#++
|
78
|
-
|
79
|
-
def parse_date(str)
|
80
|
-
return nil unless str
|
81
|
-
return Date.strptime(str)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Parse a boolean
|
85
|
-
# true, 1, t => true
|
86
|
-
# other => false
|
87
|
-
|
88
|
-
def parse_boolean(str)
|
89
|
-
return true if (str=='true' || str=='t' || str=='1')
|
90
|
-
return false
|
91
|
-
end
|
92
|
-
|
93
|
-
#--
|
94
|
-
# TODO: implement me!!
|
95
|
-
#++
|
96
|
-
|
97
|
-
def parse_blob(val)
|
98
|
-
val
|
99
|
-
end
|
100
|
-
|
101
|
-
# Escape the various Ruby types.
|
102
|
-
|
103
|
-
def quote(vals)
|
104
|
-
vals = [vals] unless vals.is_a?(Array)
|
105
|
-
quoted = vals.inject("") do |s,val|
|
106
|
-
s += case val
|
107
|
-
when Fixnum, Integer, Float
|
108
|
-
val ? val.to_s : 'NULL'
|
109
|
-
when String
|
110
|
-
val ? "'#{escape(val)}'" : 'NULL'
|
111
|
-
when Time
|
112
|
-
val ? "'#{timestamp(val)}'" : 'NULL'
|
113
|
-
when Date
|
114
|
-
val ? "'#{date(val)}'" : 'NULL'
|
115
|
-
when TrueClass
|
116
|
-
val ? "'t'" : 'NULL'
|
117
|
-
else
|
118
|
-
# gmosx: keep the '' for nil symbols.
|
119
|
-
val ? escape(val.to_yaml) : ''
|
120
|
-
end + ','
|
121
|
-
end
|
122
|
-
quoted.chop!
|
123
|
-
vals.size > 1 ? "(#{quoted})" : quoted
|
124
|
-
end
|
125
|
-
|
126
|
-
# Escape the Array Ruby type.
|
127
|
-
|
128
|
-
def quote_array(val)
|
129
|
-
case val
|
130
|
-
when Array
|
131
|
-
val.collect{ |v| quotea(v) }.join(',')
|
132
|
-
else
|
133
|
-
quote(val)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
alias_method :quotea, :quote_array
|
137
|
-
|
138
|
-
# Apply table name conventions to a class name.
|
139
|
-
|
140
|
-
def tableize(klass)
|
141
|
-
"#{klass.to_s.gsub(/::/, "_").downcase}"
|
142
|
-
end
|
143
|
-
|
144
|
-
def table(klass)
|
145
|
-
klass.ann.self[:sql_table] || klass.ann.self[:table] || "#{Og.table_prefix}#{tableize(klass)}"
|
146
|
-
end
|
147
|
-
|
148
|
-
def join_object_ordering(obj1, obj2)
|
149
|
-
if obj1.class.to_s <= obj2.class.to_s
|
150
|
-
return obj1, obj2
|
151
|
-
else
|
152
|
-
return obj2, obj1, true
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def join_class_ordering(class1, class2)
|
157
|
-
if class1.to_s <= class2.to_s
|
158
|
-
return class1, class2
|
159
|
-
else
|
160
|
-
return class2, class1, true
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def build_join_name(class1, class2, postfix = nil)
|
165
|
-
# Don't reorder arguments, as this is used in places that
|
166
|
-
# have already determined the order they want.
|
167
|
-
"#{Og.table_prefix}j_#{tableize(class1)}_#{tableize(class2)}#{postfix}"
|
168
|
-
end
|
169
|
-
|
170
|
-
def join_table(class1, class2, postfix = nil)
|
171
|
-
first, second = join_class_ordering(class1, class2)
|
172
|
-
build_join_name(first, second, postfix)
|
173
|
-
end
|
174
|
-
|
175
|
-
def join_table_index(key)
|
176
|
-
"#{key}_idx"
|
177
|
-
end
|
178
|
-
|
179
|
-
def join_table_key(klass)
|
180
|
-
klass = klass.schema_inheritance_root_class if klass.schema_inheritance_child?
|
181
|
-
"#{klass.to_s.demodulize.underscore.downcase}_oid"
|
182
|
-
end
|
183
|
-
|
184
|
-
def join_table_keys(class1, class2)
|
185
|
-
if class1 == class2
|
186
|
-
# Fix for the self-join case.
|
187
|
-
return join_table_key(class1), "#{join_table_key(class2)}2"
|
188
|
-
else
|
189
|
-
return join_table_key(class1), join_table_key(class2)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def ordered_join_table_keys(class1, class2)
|
194
|
-
first, second = join_class_ordering(class1, class2)
|
195
|
-
return join_table_keys(first, second)
|
196
|
-
end
|
197
|
-
|
198
|
-
def join_table_info(relation, postfix = nil)
|
199
|
-
|
200
|
-
# some fixes for schema inheritance.
|
201
|
-
|
202
|
-
owner_class, target_class = relation.owner_class, relation.target_class
|
203
|
-
|
204
|
-
raise "Undefined owner_class in #{target_class}" unless owner_class
|
205
|
-
raise "Undefined target_class in #{owner_class}" unless target_class
|
206
|
-
|
207
|
-
owner_class = owner_class.schema_inheritance_root_class if owner_class.schema_inheritance_child?
|
208
|
-
target_class = target_class.schema_inheritance_root_class if target_class.schema_inheritance_child?
|
209
|
-
|
210
|
-
owner_key, target_key = join_table_keys(owner_class, target_class)
|
211
|
-
first, second, changed = join_class_ordering(owner_class, target_class)
|
212
|
-
|
213
|
-
if changed
|
214
|
-
first_key, second_key = target_key, owner_key
|
215
|
-
else
|
216
|
-
first_key, second_key = owner_key, target_key
|
217
|
-
end
|
218
|
-
|
219
|
-
table = (relation.table ?
|
220
|
-
relation.table :
|
221
|
-
join_table(owner_class, target_class, postfix)
|
222
|
-
)
|
223
|
-
|
224
|
-
return {
|
225
|
-
:table => table,
|
226
|
-
:owner_key => owner_key,
|
227
|
-
:owner_table => table(owner_class),
|
228
|
-
:target_key => target_key,
|
229
|
-
:target_table => table(target_class),
|
230
|
-
:first_table => table(first),
|
231
|
-
:first_key => first_key,
|
232
|
-
:first_index => join_table_index(first_key),
|
233
|
-
:second_table => table(second),
|
234
|
-
:second_key => second_key,
|
235
|
-
:second_index => join_table_index(second_key)
|
236
|
-
}
|
237
|
-
end
|
238
|
-
|
239
|
-
# Subclasses can override this if they need a different
|
240
|
-
# syntax.
|
241
|
-
|
242
|
-
def create_join_table_sql(join_table_info, suffix = 'NOT NULL', key_type = 'integer')
|
243
|
-
join_table = join_table_info[:table]
|
244
|
-
first_index = join_table_info[:first_index]
|
245
|
-
first_key = join_table_info[:first_key]
|
246
|
-
second_key = join_table_info[:second_key]
|
247
|
-
second_index = join_table_info[:second_index]
|
248
|
-
|
249
|
-
sql = []
|
250
|
-
|
251
|
-
sql << %{
|
252
|
-
CREATE TABLE #{join_table} (
|
253
|
-
#{first_key} integer NOT NULL,
|
254
|
-
#{second_key} integer NOT NULL,
|
255
|
-
PRIMARY KEY(#{first_key}, #{second_key})
|
256
|
-
)
|
257
|
-
}
|
258
|
-
|
259
|
-
# gmosx: not that useful?
|
260
|
-
# sql << "CREATE INDEX #{first_index} ON #{join_table} (#{first_key})"
|
261
|
-
# sql << "CREATE INDEX #{second_index} ON #{join_table} (#{second_key})"
|
262
|
-
|
263
|
-
return sql
|
264
|
-
end
|
265
|
-
|
266
|
-
end
|
267
|
-
|
268
|
-
# An abstract SQL powered store.
|
11
|
+
# The base implementation for all SQL stores. Reused by all
|
12
|
+
# SQL adapters.
|
269
13
|
|
270
14
|
class SqlStore < Store
|
271
|
-
extend SqlUtils
|
272
|
-
include SqlUtils
|
273
|
-
|
274
|
-
# The connection to the backend SQL RDBMS.
|
275
15
|
|
16
|
+
# The connection to the SQL backend.
|
17
|
+
|
276
18
|
attr_accessor :conn
|
19
|
+
|
20
|
+
# Ruby type <-> SQL type mappings.
|
21
|
+
|
22
|
+
attr_accessor :typemap
|
277
23
|
|
24
|
+
# Initialize the store.
|
25
|
+
#--
|
26
|
+
# Override in the adapter.
|
27
|
+
#++
|
28
|
+
|
278
29
|
def initialize(options)
|
279
30
|
super
|
280
31
|
|
@@ -297,42 +48,61 @@ class SqlStore < Store
|
|
297
48
|
end
|
298
49
|
|
299
50
|
#--
|
300
|
-
#
|
51
|
+
# Override in the adapter.
|
301
52
|
#++
|
302
53
|
|
303
|
-
def
|
304
|
-
|
305
|
-
|
306
|
-
klass.send :include, ::Aspects
|
307
|
-
klass.pre "Logger.info sql", :on => [:exec, :query]
|
308
|
-
::Aspects.wrap(klass, [:exec, :query])
|
54
|
+
def close
|
55
|
+
@conn.close
|
56
|
+
super
|
309
57
|
end
|
310
58
|
|
311
|
-
#
|
312
|
-
#
|
59
|
+
# Creates the database where Og managed objects are
|
60
|
+
# serialized.
|
61
|
+
#--
|
62
|
+
# Override in the adapter.
|
63
|
+
#++
|
313
64
|
|
314
|
-
def
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
65
|
+
def create_db(options)
|
66
|
+
Logger.info "Created database '#{options[:name]}'"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Destroys the database where Og managed objects are
|
70
|
+
# serialized.
|
71
|
+
#--
|
72
|
+
# Override in the adapter.
|
73
|
+
#++
|
74
|
+
|
75
|
+
def destroy_db(options)
|
76
|
+
Logger.info "Dropped database '#{options[:name]}'"
|
77
|
+
end
|
78
|
+
alias drop_db destroy_db
|
79
|
+
|
80
|
+
# The type used for default primary keys.
|
81
|
+
|
82
|
+
def primary_key_type
|
83
|
+
'integer PRIMARY KEY'
|
321
84
|
end
|
322
85
|
|
323
|
-
#
|
324
|
-
# support a class managed by the supplied manager.
|
86
|
+
# Force the creation of a primary key class.
|
325
87
|
|
326
|
-
def
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
88
|
+
def force_primary_key(klass)
|
89
|
+
# Automatically add an :oid serializable field if none is
|
90
|
+
# defined and no other primary key is defined.
|
91
|
+
|
92
|
+
if klass.primary_key == :oid and !klass.attributes.include?(:oid)
|
93
|
+
klass.attr_accessor :oid, Fixnum, :sql => primary_key_type
|
331
94
|
end
|
332
|
-
ret
|
333
95
|
end
|
334
96
|
|
335
|
-
#
|
97
|
+
# Performs SQL related enchanting to the class. This method
|
98
|
+
# is further extended in more specialized adapters to add
|
99
|
+
# backend specific enchanting.
|
100
|
+
#
|
101
|
+
# Defines:
|
102
|
+
#
|
103
|
+
# * the OGTABLE constant, and .table / .schema aliases.
|
104
|
+
# * the index method for defing sql indices.
|
105
|
+
# * precompiles the object lifecycle callbacks.
|
336
106
|
|
337
107
|
def enchant(klass, manager)
|
338
108
|
# setup the table where this class is mapped.
|
@@ -344,15 +114,17 @@ class SqlStore < Store
|
|
344
114
|
klass.const_set 'OGTABLE', table(klass)
|
345
115
|
end
|
346
116
|
|
347
|
-
|
348
|
-
# FIXME: use an SQL agnostic name like schema instead
|
349
|
-
# of table.
|
350
|
-
#++
|
117
|
+
# Define table and schema aliases for OGTABLE.
|
351
118
|
|
352
|
-
klass.module_eval
|
119
|
+
klass.module_eval %{
|
120
|
+
def self.table; OGTABLE; end
|
121
|
+
def self.schema; OGTABLE; end
|
122
|
+
}
|
353
123
|
|
354
124
|
eval_og_allocate(klass)
|
355
125
|
|
126
|
+
# Perform base store enchantment.
|
127
|
+
|
356
128
|
super
|
357
129
|
|
358
130
|
unless klass.polymorphic_parent?
|
@@ -381,10 +153,12 @@ class SqlStore < Store
|
|
381
153
|
# :section: Lifecycle methods.
|
382
154
|
|
383
155
|
# Loads an object from the store using the primary key.
|
384
|
-
|
156
|
+
# Returns nil if the passes pk is nil.
|
157
|
+
|
385
158
|
def load(pk, klass)
|
386
|
-
|
387
|
-
|
159
|
+
return nil unless pk
|
160
|
+
|
161
|
+
sql = "SELECT * FROM #{klass.table} WHERE #{pk_field klass}=#{quote(pk)}"
|
388
162
|
sql << " AND ogtype='#{klass}'" if klass.schema_inheritance_child?
|
389
163
|
res = query sql
|
390
164
|
read_one(res, klass)
|
@@ -392,36 +166,40 @@ class SqlStore < Store
|
|
392
166
|
alias_method :exist?, :load
|
393
167
|
|
394
168
|
# Reloads an object from the store.
|
169
|
+
# Returns nil if the passes pk is nil.
|
395
170
|
|
396
171
|
def reload(obj, pk)
|
172
|
+
return nil unless pk
|
173
|
+
|
174
|
+
klass = obj.class
|
397
175
|
raise 'Cannot reload unmanaged object' unless obj.saved?
|
398
|
-
sql = "SELECT * FROM #{
|
399
|
-
sql << " AND ogtype='#{
|
176
|
+
sql = "SELECT * FROM #{klass.table} WHERE #{pk_field klass}=#{quote(pk)}"
|
177
|
+
sql << " AND ogtype='#{klass}'" if klass.schema_inheritance_child?
|
400
178
|
res = query sql
|
401
179
|
obj.og_read(res.next, 0)
|
402
180
|
ensure
|
403
181
|
res.close if res
|
404
182
|
end
|
405
183
|
|
406
|
-
# If
|
407
|
-
# selected
|
184
|
+
# If an attributes collection is provided, only updates the
|
185
|
+
# selected attributes. Pass the required attributes as symbols
|
408
186
|
# or strings.
|
409
187
|
#--
|
410
188
|
# gmosx, THINK: condition is not really useful here :(
|
411
189
|
#++
|
412
190
|
|
413
191
|
def update(obj, options = nil)
|
414
|
-
if options and
|
415
|
-
if
|
192
|
+
if options and attrs = options[:only]
|
193
|
+
if attrs.is_a?(Array)
|
416
194
|
set = []
|
417
|
-
for
|
418
|
-
set << "#{
|
195
|
+
for a in attrs
|
196
|
+
set << "#{a}=#{quote(obj.send(a))}"
|
419
197
|
end
|
420
198
|
set = set.join(',')
|
421
199
|
else
|
422
|
-
set = "#{
|
200
|
+
set = "#{attrs}=#{quote(obj.send(attrs))}"
|
423
201
|
end
|
424
|
-
sql = "UPDATE #{obj.class.table} SET #{set} WHERE #{obj.class
|
202
|
+
sql = "UPDATE #{obj.class.table} SET #{set} WHERE #{pk_field obj.class}=#{quote(obj.pk)}"
|
425
203
|
sql << " AND #{options[:condition]}" if options[:condition]
|
426
204
|
sql_update(sql)
|
427
205
|
else
|
@@ -429,26 +207,17 @@ class SqlStore < Store
|
|
429
207
|
end
|
430
208
|
end
|
431
209
|
|
432
|
-
# Update selected properties of an object or class of
|
433
|
-
# objects.
|
434
|
-
|
435
|
-
def update_properties(target, *properties)
|
436
|
-
update(target, :only => properties)
|
437
|
-
end
|
438
|
-
alias_method :pupdate, :update_properties
|
439
|
-
alias_method :update_property, :update_properties
|
440
|
-
|
441
210
|
# More generalized method, also allows for batch updates.
|
442
211
|
|
443
212
|
def update_by_sql(target, set, options = nil)
|
444
213
|
set = set.gsub(/@/, '')
|
445
214
|
|
446
|
-
if target.is_a?
|
215
|
+
if target.is_a? Class
|
447
216
|
sql = "UPDATE #{target.table} SET #{set} "
|
448
217
|
sql << " WHERE #{options[:condition]}" if options and options[:condition]
|
449
218
|
sql_update(sql)
|
450
219
|
else
|
451
|
-
sql = "UPDATE #{target.class.table} SET #{set} WHERE #{target.class
|
220
|
+
sql = "UPDATE #{target.class.table} SET #{set} WHERE #{pk_field target.class}=#{quote(target.pk)}"
|
452
221
|
sql << " AND #{options[:condition]}" if options and options[:condition]
|
453
222
|
sql_update(sql)
|
454
223
|
end
|
@@ -498,6 +267,10 @@ class SqlStore < Store
|
|
498
267
|
# This low level method is used by the Entity
|
499
268
|
# calculation / aggregation methods.
|
500
269
|
#
|
270
|
+
# === Options
|
271
|
+
#
|
272
|
+
# :field = the return type.
|
273
|
+
#
|
501
274
|
# === Example
|
502
275
|
#
|
503
276
|
# calculate 'COUNT(*)'
|
@@ -505,152 +278,128 @@ class SqlStore < Store
|
|
505
278
|
# calculate 'SUM(age)', :group => :name
|
506
279
|
|
507
280
|
def aggregate(term = 'COUNT(*)', options = {})
|
281
|
+
# Leave this .dup here, causes problems because options are changed
|
282
|
+
options = options.dup
|
283
|
+
|
508
284
|
klass = options[:class]
|
509
285
|
field = options[:field]
|
510
|
-
|
511
|
-
|
512
|
-
|
286
|
+
|
287
|
+
# Rename search term, SQL92 but _not_ SQL89 compatible
|
288
|
+
options.update(:select => "#{term} AS #{term[/^\w+/]}")
|
289
|
+
|
290
|
+
unless options[:group] || options[:group_by]
|
291
|
+
options.delete(:order)
|
292
|
+
options.delete(:order_by)
|
513
293
|
end
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
if condition = options[:condition]
|
520
|
-
sql << " WHERE #{condition}"
|
521
|
-
sql << " AND " if options[:class].schema_inheritance_child?
|
522
|
-
else
|
523
|
-
sql << " WHERE " if options[:class].schema_inheritance_child?
|
524
|
-
end
|
525
|
-
sql << "ogtype='#{options[:class]}'" if options[:class].schema_inheritance_child?
|
526
|
-
if group = options[:group]
|
527
|
-
sql << " GROUP BY #{group}"
|
528
|
-
end
|
529
|
-
if extra = options[:extra_sql]
|
530
|
-
sql << " {extra}"
|
531
|
-
end
|
294
|
+
|
295
|
+
sql = resolve_options(klass, options)
|
296
|
+
|
297
|
+
if anno = klass.ann[field]
|
298
|
+
return_type = anno.class
|
532
299
|
end
|
533
|
-
|
534
|
-
|
300
|
+
return_type ||= Integer
|
301
|
+
|
302
|
+
if options[:group] || options[:group_by]
|
535
303
|
# This is an aggregation, so return the calculated values
|
536
304
|
# as an array.
|
537
305
|
values = []
|
538
306
|
res = query(sql)
|
539
307
|
res.each_row do |row, idx|
|
540
|
-
|
541
|
-
values << type_cast(return_type, value)
|
308
|
+
values << type_cast(return_type, row[0])
|
542
309
|
end
|
543
310
|
return values
|
544
311
|
else
|
545
|
-
#--
|
546
|
-
# gmosx, TODO: don't convert to float by default, perhaps
|
547
|
-
# should consult an option.
|
548
|
-
#++
|
549
312
|
return type_cast(return_type, query(sql).first_value)
|
550
313
|
end
|
314
|
+
|
551
315
|
end
|
552
316
|
alias_method :calculate, :aggregate
|
553
317
|
|
554
|
-
def type_cast(klass, val)
|
555
|
-
typemap = {
|
556
|
-
Time => :parse_timestamp,
|
557
|
-
Date => :parse_date,
|
558
|
-
}
|
559
|
-
|
560
|
-
if method = typemap[klass]
|
561
|
-
send(method, val)
|
562
|
-
else
|
563
|
-
val.to_f
|
564
|
-
end
|
565
|
-
end
|
566
|
-
|
567
318
|
# Perform a count query.
|
568
319
|
|
569
320
|
def count(options = {})
|
570
321
|
calculate('COUNT(*)', options).to_i
|
571
322
|
end
|
572
323
|
|
573
|
-
#
|
574
|
-
|
575
|
-
|
576
|
-
def join(obj1, obj2, table, options = nil)
|
577
|
-
first, second = join_object_ordering(obj1, obj2)
|
578
|
-
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
579
|
-
if options
|
580
|
-
exec "INSERT INTO #{table} (#{first_key},#{second_key}, #{options.keys.join(',')}) VALUES (#{first.pk},#{second.pk}, #{options.values.map { |v| quote(v) }.join(',')})"
|
581
|
-
else
|
582
|
-
exec "INSERT INTO #{table} (#{first_key},#{second_key}) VALUES (#{first.pk}, #{second.pk})"
|
583
|
-
end
|
584
|
-
end
|
585
|
-
|
586
|
-
# Unrelate two objects be removing their relation from the
|
587
|
-
# join table.
|
588
|
-
|
589
|
-
def unjoin(obj1, obj2, table)
|
590
|
-
first, second = join_object_ordering(obj1, obj2)
|
591
|
-
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
592
|
-
exec "DELETE FROM #{table} WHERE #{first_key}=#{first.pk} AND #{second_key}=#{second.pk}"
|
593
|
-
end
|
594
|
-
|
324
|
+
# Delete all instances of the given class from the backend.
|
325
|
+
|
595
326
|
def delete_all(klass)
|
596
327
|
sql = "DELETE FROM #{klass.table}"
|
597
328
|
sql << " WHERE ogtype='#{klass}'" if klass.schema_inheritance? and not klass.schema_inheritance_root?
|
598
329
|
exec sql
|
599
330
|
end
|
331
|
+
|
332
|
+
# :section: Misc methods.
|
333
|
+
|
334
|
+
# Create the SQL table where instances of the given class
|
335
|
+
# will be serialized.
|
336
|
+
|
337
|
+
def create_table(klass)
|
338
|
+
fields = fields_for_class(klass)
|
600
339
|
|
601
|
-
|
602
|
-
|
603
|
-
# Start a new transaction.
|
604
|
-
|
605
|
-
def start
|
606
|
-
exec('START TRANSACTION') if @transaction_nesting < 1
|
607
|
-
@transaction_nesting += 1
|
608
|
-
end
|
609
|
-
|
610
|
-
# Commit a transaction.
|
340
|
+
sql = "CREATE TABLE #{klass.table} (#{fields.join(', ')}"
|
611
341
|
|
612
|
-
|
613
|
-
@transaction_nesting -= 1
|
614
|
-
exec('COMMIT') if @transaction_nesting < 1
|
615
|
-
end
|
342
|
+
# Create table constraints.
|
616
343
|
|
617
|
-
|
344
|
+
if constraints = klass.ann.self[:sql_constraint]
|
345
|
+
sql << ", #{constraints.join(', ')}"
|
346
|
+
end
|
618
347
|
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
348
|
+
# Set the table type (Mysql default, InnoDB, Hash, etc)
|
349
|
+
|
350
|
+
if table_type = @options[:table_type]
|
351
|
+
sql << ") TYPE = #{table_type};"
|
352
|
+
else
|
353
|
+
sql << ");"
|
354
|
+
end
|
623
355
|
|
624
|
-
|
356
|
+
# Create indices.
|
357
|
+
#
|
358
|
+
# An example index definition:
|
359
|
+
#
|
360
|
+
# classs MyClass
|
361
|
+
# attr_accessor :age, Fixnum, :index => true, :pre_index => ..., :post_index => ...
|
362
|
+
# end
|
363
|
+
|
364
|
+
for idx in sql_indices_for_class(klass)
|
365
|
+
anno = klass.ann(idx)
|
366
|
+
idx = idx.to_s
|
367
|
+
pre_sql, post_sql = anno[:pre_index], options[:post_index]
|
368
|
+
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
369
|
+
sql << " CREATE #{pre_sql} INDEX #{klass.table}_#{idxname}_idx #{post_sql} ON #{klass.table} (#{idx});"
|
370
|
+
end
|
625
371
|
|
626
|
-
|
372
|
+
begin
|
373
|
+
exec(sql, false)
|
374
|
+
Logger.info "Created table '#{klass.table}'."
|
375
|
+
rescue Object => ex
|
376
|
+
if table_already_exists_exception? ex
|
377
|
+
# Don't return yet. Fall trough to also check for the
|
378
|
+
# join table.
|
379
|
+
else
|
380
|
+
handle_sql_exception(ex, sql)
|
381
|
+
end
|
382
|
+
end
|
627
383
|
|
628
|
-
|
629
|
-
|
630
|
-
# return affected rows.
|
631
|
-
end
|
384
|
+
# Create join tables if needed. Join tables are used in
|
385
|
+
# 'many_to_many' relations.
|
632
386
|
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
387
|
+
if join_tables = klass.ann.self[:join_tables]
|
388
|
+
for info in join_tables
|
389
|
+
begin
|
390
|
+
create_join_table_sql(info).each do |sql|
|
391
|
+
exec(sql, false)
|
392
|
+
end
|
393
|
+
Logger.debug "Created jointable '#{info[:table]}'." if $DBG
|
394
|
+
rescue Object => ex
|
395
|
+
if table_already_exists_exception? ex
|
396
|
+
Logger.debug 'Join table already exists' if $DBG
|
397
|
+
else
|
398
|
+
raise
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
643
402
|
end
|
644
|
-
condition
|
645
|
-
end
|
646
|
-
|
647
|
-
private
|
648
|
-
|
649
|
-
# Create the sql table where objects of this class are
|
650
|
-
# persisted.
|
651
|
-
|
652
|
-
def create_table(klass)
|
653
|
-
raise 'Not implemented'
|
654
403
|
end
|
655
404
|
|
656
405
|
# Drop the sql table where objects of this class are
|
@@ -669,290 +418,50 @@ private
|
|
669
418
|
alias_method :destroy, :drop_table
|
670
419
|
alias_method :drop_schema, :drop_schema
|
671
420
|
|
672
|
-
#
|
673
|
-
# are persisted.
|
674
|
-
|
675
|
-
def evolve_table(klass)
|
676
|
-
drop_table(klass)
|
677
|
-
create_table(klass)
|
678
|
-
end
|
679
|
-
alias_method :update_table, :evolve_table
|
421
|
+
# Perform an sql query with results.
|
680
422
|
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
423
|
+
def query(sql, rescue_exception = true)
|
424
|
+
Logger.debug sql if $DBG
|
425
|
+
return query_statement(sql)
|
426
|
+
rescue Object => ex
|
427
|
+
if rescue_exception
|
428
|
+
handle_sql_exception(ex, sql)
|
429
|
+
else
|
430
|
+
raise
|
688
431
|
end
|
689
432
|
end
|
433
|
+
|
434
|
+
#--
|
435
|
+
# Override.
|
436
|
+
#++
|
690
437
|
|
691
|
-
|
692
|
-
|
693
|
-
#of the class hierarchy, starting from the schema inheritance
|
694
|
-
#root class
|
695
|
-
|
696
|
-
def get_properties_for_class(klass)
|
697
|
-
properties = klass.properties.dup
|
698
|
-
if(klass.schema_inheritance?)
|
699
|
-
for desc in klass.schema_inheritance_root_class.descendents
|
700
|
-
properties.update(desc.properties)
|
701
|
-
end
|
702
|
-
end
|
703
|
-
properties
|
438
|
+
def query_statement(sql)
|
439
|
+
return @conn.query(sql)
|
704
440
|
end
|
705
|
-
|
706
|
-
# Create the fields that correspond to the klass properties.
|
707
|
-
# The generated fields array is used in create_table.
|
708
|
-
# If the property has an :sql annotation this overrides the
|
709
|
-
# default mapping. If the property has an :extra_sql annotation
|
710
|
-
# the extra sql is appended after the default mapping.
|
711
441
|
|
712
|
-
|
713
|
-
fields = []
|
714
|
-
properties = get_properties_for_class(klass)
|
715
|
-
|
716
|
-
if klass.schema_inheritance?
|
717
|
-
# This class as a superclass in a single table inheritance
|
718
|
-
# chain. So inject a special class ogtype field that
|
719
|
-
# holds the class name.
|
720
|
-
|
721
|
-
fields << "ogtype VARCHAR(50)"
|
722
|
-
end
|
723
|
-
|
724
|
-
for p in properties.values
|
725
|
-
klass.index(p.symbol) if p.index
|
726
|
-
|
727
|
-
field = field_for_property(p)
|
728
|
-
|
729
|
-
if p.sql
|
730
|
-
field << " #{p.sql}"
|
731
|
-
else
|
732
|
-
field << " #{type_for_class(p.klass)}"
|
733
|
-
field << " UNIQUE" if p.unique
|
734
|
-
field << " DEFAULT #{p.default.inspect} NOT NULL" if p.default
|
735
|
-
field << " #{p.extra_sql}" if p.extra_sql
|
736
|
-
end
|
737
|
-
|
738
|
-
fields << field
|
739
|
-
end
|
740
|
-
|
741
|
-
return fields
|
742
|
-
end
|
442
|
+
# Perform an sql query with no results.
|
743
443
|
|
744
|
-
def
|
745
|
-
|
444
|
+
def exec(sql, rescue_exception = true)
|
445
|
+
Logger.debug sql if $DBG
|
446
|
+
exec_statement(sql)
|
447
|
+
rescue Object => ex
|
448
|
+
if rescue_exception
|
449
|
+
handle_sql_exception(ex, sql)
|
450
|
+
else
|
451
|
+
raise
|
452
|
+
end
|
746
453
|
end
|
747
454
|
|
748
|
-
# Return an sql string evaluator for the property.
|
749
|
-
# No need to optimize this, used only to precalculate code.
|
750
|
-
# YAML is used to store general Ruby objects to be more
|
751
|
-
# portable.
|
752
455
|
#--
|
753
|
-
#
|
456
|
+
# Override.
|
754
457
|
#++
|
755
458
|
|
756
|
-
def
|
757
|
-
|
758
|
-
|
759
|
-
elsif p.klass.ancestors.include?(Float)
|
760
|
-
return "#\{@#{p} || 'NULL'\}"
|
761
|
-
elsif p.klass.ancestors.include?(String)
|
762
|
-
return %|#\{@#{p} ? "'#\{#{self.class}.escape(@#{p})\}'" : 'NULL'\}|
|
763
|
-
elsif p.klass.ancestors.include?(Time)
|
764
|
-
return %|#\{@#{p} ? "'#\{#{self.class}.timestamp(@#{p})\}'" : 'NULL'\}|
|
765
|
-
elsif p.klass.ancestors.include?(Date)
|
766
|
-
return %|#\{@#{p} ? "'#\{#{self.class}.date(@#{p})\}'" : 'NULL'\}|
|
767
|
-
elsif p.klass.ancestors.include?(TrueClass)
|
768
|
-
return "#\{@#{p} ? \"'t'\" : 'NULL' \}"
|
769
|
-
elsif p.klass.ancestors.include?(Og::Blob)
|
770
|
-
return %|#\{@#{p} ? "'#\{#{self.class}.escape(#{self.class}.blob(@#{p}))\}'" : 'NULL'\}|
|
771
|
-
else
|
772
|
-
# gmosx: keep the '' for nil symbols.
|
773
|
-
return %|#\{@#{p} ? "'#\{#{self.class}.escape(@#{p}.to_yaml)\}'" : "''"\}|
|
774
|
-
end
|
775
|
-
end
|
459
|
+
def exec_statement(sql)
|
460
|
+
return @conn.exec(sql)
|
461
|
+
end
|
776
462
|
|
777
|
-
#
|
778
|
-
# No need to optimize this, used only to precalculate code.
|
779
|
-
|
780
|
-
def read_prop(p, col)
|
781
|
-
if p.klass.ancestors.include?(Integer)
|
782
|
-
return "#{self.class}.parse_int(res[#{col} + offset])"
|
783
|
-
elsif p.klass.ancestors.include?(Float)
|
784
|
-
return "#{self.class}.parse_float(res[#{col} + offset])"
|
785
|
-
elsif p.klass.ancestors.include?(String)
|
786
|
-
return "res[#{col} + offset]"
|
787
|
-
elsif p.klass.ancestors.include?(Time)
|
788
|
-
return "#{self.class}.parse_timestamp(res[#{col} + offset])"
|
789
|
-
elsif p.klass.ancestors.include?(Date)
|
790
|
-
return "#{self.class}.parse_date(res[#{col} + offset])"
|
791
|
-
elsif p.klass.ancestors.include?(TrueClass)
|
792
|
-
return "#{self.class}.parse_boolean(res[#{col} + offset])"
|
793
|
-
elsif p.klass.ancestors.include?(Og::Blob)
|
794
|
-
return "#{self.class}.parse_blob(res[#{col} + offset])"
|
795
|
-
else
|
796
|
-
return "YAML::load(res[#{col} + offset])"
|
797
|
-
end
|
798
|
-
end
|
799
|
-
|
800
|
-
# :section: Lifecycle method compilers.
|
801
|
-
|
802
|
-
|
803
|
-
# Get the fields from the database table. Also handles the
|
804
|
-
# change of ordering of the fields in the table.
|
805
|
-
#
|
806
|
-
# To ignore a database field use the ignore_fields annotation
|
807
|
-
# ie,
|
808
|
-
#
|
809
|
-
# class Article
|
810
|
-
# ann self, :ignore_fields => [ :tsearch_idx, :ext_field ]
|
811
|
-
# end
|
812
|
-
#
|
813
|
-
# other aliases for ignore_fiels: ignore_field, ignore_column.
|
463
|
+
# Gracefully handle a backend exception.
|
814
464
|
|
815
|
-
def create_field_map(klass)
|
816
|
-
end
|
817
|
-
|
818
|
-
# Compile the og_insert method for the class.
|
819
|
-
|
820
|
-
def eval_og_insert(klass)
|
821
|
-
pk = klass.pk_symbol
|
822
|
-
props = klass.properties.values.dup
|
823
|
-
values = props.collect { |p| write_prop(p) }.join(',')
|
824
|
-
|
825
|
-
if klass.ann.self[:superclass] or klass.ann.self[:subclasses]
|
826
|
-
props << Property.new(:symbol => :ogtype, :klass => String)
|
827
|
-
values << ", '#{klass}'"
|
828
|
-
end
|
829
|
-
|
830
|
-
sql = "INSERT INTO #{klass.table} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
|
831
|
-
|
832
|
-
klass.module_eval %{
|
833
|
-
def og_insert(store)
|
834
|
-
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
835
|
-
store.exec "#{sql}"
|
836
|
-
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
837
|
-
end
|
838
|
-
}
|
839
|
-
end
|
840
|
-
|
841
|
-
# Compile the og_update method for the class.
|
842
|
-
|
843
|
-
def eval_og_update(klass)
|
844
|
-
pk = klass.pk_symbol
|
845
|
-
pk_field = klass.primary_key.field || klass.primary_key.symbol
|
846
|
-
|
847
|
-
props = klass.properties.values.reject { |p| pk == p.symbol }
|
848
|
-
|
849
|
-
updates = props.collect { |p|
|
850
|
-
"#{field_for_property(p)}=#{write_prop(p)}"
|
851
|
-
}
|
852
|
-
|
853
|
-
sql = "UPDATE #{klass::OGTABLE} SET #{updates.join(', ')} WHERE #{pk_field}=#\{@#{pk}\}"
|
854
|
-
|
855
|
-
klass.module_eval %{
|
856
|
-
def og_update(store, options = nil)
|
857
|
-
#{::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
|
858
|
-
sql = "#{sql}"
|
859
|
-
sql << " AND \#{options[:condition]}" if options and options[:condition]
|
860
|
-
changed = store.sql_update(sql)
|
861
|
-
#{::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
|
862
|
-
return changed
|
863
|
-
end
|
864
|
-
}
|
865
|
-
end
|
866
|
-
|
867
|
-
# Compile the og_read method for the class. This method is
|
868
|
-
# used to read (deserialize) the given class from the store.
|
869
|
-
# In order to allow for changing field/attribute orders a
|
870
|
-
# field mapping hash is used.
|
871
|
-
|
872
|
-
def eval_og_read(klass)
|
873
|
-
code = []
|
874
|
-
props = klass.properties.values
|
875
|
-
field_map = create_field_map(klass)
|
876
|
-
|
877
|
-
for p in props
|
878
|
-
f = field_for_property(p).to_sym
|
879
|
-
|
880
|
-
if col = field_map[f]
|
881
|
-
code << "@#{p} = #{read_prop(p, col)}"
|
882
|
-
end
|
883
|
-
end
|
884
|
-
|
885
|
-
code = code.join('; ')
|
886
|
-
|
887
|
-
klass.module_eval %{
|
888
|
-
def og_read(res, row = 0, offset = 0)
|
889
|
-
#{::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
|
890
|
-
#{code}
|
891
|
-
#{::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
|
892
|
-
end
|
893
|
-
}
|
894
|
-
end
|
895
|
-
|
896
|
-
#--
|
897
|
-
# FIXME: is pk needed as parameter?
|
898
|
-
#++
|
899
|
-
|
900
|
-
def eval_og_delete(klass)
|
901
|
-
klass.module_eval %{
|
902
|
-
def og_delete(store, pk, cascade = true)
|
903
|
-
#{::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
|
904
|
-
pk ||= @#{klass.pk_symbol}
|
905
|
-
transaction do |tx|
|
906
|
-
tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}"
|
907
|
-
if cascade and #{klass}.ann.self[:descendants]
|
908
|
-
#{klass}.ann.self.descendants.each do |dclass, foreign_key|
|
909
|
-
tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}"
|
910
|
-
end
|
911
|
-
end
|
912
|
-
end
|
913
|
-
#{::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
|
914
|
-
end
|
915
|
-
}
|
916
|
-
end
|
917
|
-
|
918
|
-
# Creates the schema for this class. Can be intercepted with
|
919
|
-
# aspects to add special behaviours.
|
920
|
-
|
921
|
-
def eval_og_create_schema(klass)
|
922
|
-
klass.module_eval %{
|
923
|
-
def og_create_schema(store)
|
924
|
-
if Og.create_schema
|
925
|
-
#{::Aspects.gen_advice_code(:og_create_schema, klass.advices, :pre) if klass.respond_to?(:advices)}
|
926
|
-
# unless self.class.superclass.ancestors.include? SchemaInheritanceBase
|
927
|
-
store.send(:create_table, #{klass})
|
928
|
-
# end
|
929
|
-
#{::Aspects.gen_advice_code(:og_create_schema, klass.advices, :post) if klass.respond_to?(:advices)}
|
930
|
-
end
|
931
|
-
end
|
932
|
-
}
|
933
|
-
end
|
934
|
-
|
935
|
-
# Precompile a class specific allocate method. If this is an
|
936
|
-
# STI parent classes it reads the class from the resultset.
|
937
|
-
|
938
|
-
def eval_og_allocate(klass)
|
939
|
-
if klass.schema_inheritance?
|
940
|
-
klass.module_eval %{
|
941
|
-
def self.og_allocate(res, row = 0)
|
942
|
-
Object.constant(res[0]).allocate
|
943
|
-
end
|
944
|
-
}
|
945
|
-
else
|
946
|
-
klass.module_eval %{
|
947
|
-
def self.og_allocate(res, row = 0)
|
948
|
-
self.allocate
|
949
|
-
end
|
950
|
-
}
|
951
|
-
end
|
952
|
-
end
|
953
|
-
|
954
|
-
# :section: Misc methods.
|
955
|
-
|
956
465
|
def handle_sql_exception(ex, sql = nil)
|
957
466
|
Logger.error "DB error #{ex}, [#{sql}]"
|
958
467
|
Logger.error ex.backtrace.join("\n")
|
@@ -962,6 +471,25 @@ private
|
|
962
471
|
return nil
|
963
472
|
end
|
964
473
|
|
474
|
+
# Perform an sql update, return the number of updated rows.
|
475
|
+
#--
|
476
|
+
# Override
|
477
|
+
#++
|
478
|
+
|
479
|
+
def sql_update(sql)
|
480
|
+
exec(sql)
|
481
|
+
# return affected rows.
|
482
|
+
end
|
483
|
+
|
484
|
+
# Return the last inserted row id.
|
485
|
+
#--
|
486
|
+
# Override
|
487
|
+
#--
|
488
|
+
|
489
|
+
def last_insert_id(klass = nil)
|
490
|
+
# return last insert id
|
491
|
+
end
|
492
|
+
|
965
493
|
# Resolve the finder options. Also takes scope into account.
|
966
494
|
# This method handles among other the following cases:
|
967
495
|
#
|
@@ -1049,24 +577,42 @@ private
|
|
1049
577
|
# If an array is passed as a condition, use prepared
|
1050
578
|
# statement style escaping.
|
1051
579
|
|
1052
|
-
condition
|
580
|
+
if condition.is_a?(Array)
|
581
|
+
condition = prepare_statement(condition)
|
582
|
+
end
|
1053
583
|
|
1054
584
|
sql << " WHERE #{condition}"
|
1055
585
|
end
|
586
|
+
|
587
|
+
if group = options[:group] || options[:group_by]
|
588
|
+
sql << " GROUP BY #{group}"
|
589
|
+
end
|
1056
590
|
|
1057
|
-
if order = options[:order]
|
591
|
+
if order = options[:order] || options[:order_by]
|
1058
592
|
sql << " ORDER BY #{order}"
|
1059
593
|
end
|
1060
594
|
|
1061
595
|
resolve_limit_options(options, sql)
|
1062
596
|
|
1063
|
-
if extra = options[:extra]
|
597
|
+
if extra = options[:extra] || options[:extra_sql]
|
1064
598
|
sql << " #{extra}"
|
1065
599
|
end
|
1066
600
|
|
1067
601
|
return sql
|
1068
602
|
end
|
1069
603
|
|
604
|
+
#takes an array, the first parameter of which is a prepared statement
|
605
|
+
#style string; this handles parameter escaping.
|
606
|
+
|
607
|
+
def prepare_statement(condition)
|
608
|
+
args = condition.dup
|
609
|
+
str = args.shift
|
610
|
+
# ? handles a single type.
|
611
|
+
# ?* handles an array.
|
612
|
+
args.each { |arg| str.sub!(/\?\*/, quotea(arg)); str.sub!(/\?/, quote(arg)) }
|
613
|
+
condition = str
|
614
|
+
end
|
615
|
+
|
1070
616
|
# Subclasses can override this if they need some other order.
|
1071
617
|
# This is needed because different backends require different
|
1072
618
|
# order of the keywords.
|
@@ -1081,6 +627,414 @@ private
|
|
1081
627
|
end
|
1082
628
|
end
|
1083
629
|
|
630
|
+
# :section: Utility methods for serialization/deserialization.
|
631
|
+
|
632
|
+
# Return either the serializable attributes for the class or,
|
633
|
+
# in the case of schema inheritance, all of the serializable
|
634
|
+
# attributes of the class hierarchy, starting from the schema
|
635
|
+
# inheritance root class.
|
636
|
+
|
637
|
+
def serializable_attributes_for_class(klass)
|
638
|
+
attrs = klass.serializable_attributes
|
639
|
+
if klass.schema_inheritance?
|
640
|
+
for desc in klass.schema_inheritance_root_class.descendents
|
641
|
+
attrs.concat desc.serializable_attributes
|
642
|
+
end
|
643
|
+
end
|
644
|
+
return attrs.uniq
|
645
|
+
end
|
646
|
+
|
647
|
+
# Return the SQL table field for the given serializable
|
648
|
+
# attribute. You can override the default field name by
|
649
|
+
# annotating the attribute with a :field annotation.
|
650
|
+
|
651
|
+
def field_for_attribute(a, anno)
|
652
|
+
(f = anno[:field]) ? f : a
|
653
|
+
end
|
654
|
+
|
655
|
+
def field_sql_for_attribute(a, anno)
|
656
|
+
field = field_for_attribute(a, anno).to_s
|
657
|
+
|
658
|
+
if anno.sql?
|
659
|
+
field << " #{anno.sql}"
|
660
|
+
else
|
661
|
+
field << " #{sql_type_for_class(anno.class)}"
|
662
|
+
field << " UNIQUE" if anno.unique?
|
663
|
+
field << " DEFAULT #{quote(anno.default)} NOT NULL" if anno.default?
|
664
|
+
field << " #{anno.extra_sql}" if anno.extra_sql?
|
665
|
+
end
|
666
|
+
|
667
|
+
return field
|
668
|
+
end
|
669
|
+
|
670
|
+
# Create the fields that correspond to the class serializable
|
671
|
+
# attributes. The generated fields array is used in
|
672
|
+
# create_table.
|
673
|
+
#
|
674
|
+
# If the property has an :sql annotation this overrides the
|
675
|
+
# default mapping. If the property has an :extra_sql annotation
|
676
|
+
# the extra sql is appended after the default mapping.
|
677
|
+
|
678
|
+
def fields_for_class(klass)
|
679
|
+
fields = []
|
680
|
+
attrs = serializable_attributes_for_class(klass)
|
681
|
+
|
682
|
+
# FIXME: move to serializable attributes.
|
683
|
+
|
684
|
+
if klass.schema_inheritance?
|
685
|
+
# This class is a superclass in a single table inheritance
|
686
|
+
# chain. So inject a special class ogtype field that
|
687
|
+
# holds the class name.
|
688
|
+
fields << "ogtype VARCHAR(30)"
|
689
|
+
end
|
690
|
+
|
691
|
+
for a in attrs
|
692
|
+
anno = klass.ann[a]
|
693
|
+
if anno.null?
|
694
|
+
klass.subclasses.each do |subklass|
|
695
|
+
anno = subklass.ann[a] if anno.null?
|
696
|
+
end
|
697
|
+
end
|
698
|
+
fields << field_sql_for_attribute(a, anno)
|
699
|
+
end
|
700
|
+
|
701
|
+
return fields
|
702
|
+
end
|
703
|
+
|
704
|
+
# Returns the SQL indexed serializable attributes for the
|
705
|
+
# given class.
|
706
|
+
|
707
|
+
def sql_indices_for_class klass
|
708
|
+
indices = []
|
709
|
+
|
710
|
+
for a in klass.serializable_attributes
|
711
|
+
indices << a if klass.ann(a).index?
|
712
|
+
end
|
713
|
+
|
714
|
+
return indices
|
715
|
+
end
|
716
|
+
|
717
|
+
# Return the SQL type for the given Ruby class.
|
718
|
+
|
719
|
+
def sql_type_for_class klass
|
720
|
+
@typemap[klass]
|
721
|
+
end
|
722
|
+
|
723
|
+
# Generate code to serialize an attribute to an SQL table
|
724
|
+
# field.
|
725
|
+
# YAML is used (instead of Marshal) to store general Ruby
|
726
|
+
# objects to be more
|
727
|
+
# portable.
|
728
|
+
#
|
729
|
+
# === Input
|
730
|
+
#
|
731
|
+
# * s = attribute symbol
|
732
|
+
# * a = attribute annotations
|
733
|
+
#--
|
734
|
+
# No need to optimize this, used only to precalculate code.
|
735
|
+
# FIXME: add extra handling for float.
|
736
|
+
#++
|
737
|
+
|
738
|
+
def write_attr(s, a)
|
739
|
+
store = self.class
|
740
|
+
|
741
|
+
if a.class.ancestor? Integer
|
742
|
+
"#\{@#{s} || 'NULL'\}"
|
743
|
+
|
744
|
+
elsif a.class.ancestor? Float
|
745
|
+
"#\{@#{s} || 'NULL'\}"
|
746
|
+
|
747
|
+
elsif a.class.ancestor? String
|
748
|
+
%|#\{@#{s} ? "'#\{#{store}.escape(@#{s})\}'" : 'NULL'\}|
|
749
|
+
|
750
|
+
elsif a.class.ancestor? Time
|
751
|
+
%|#\{@#{s} ? "'#\{#{store}.timestamp(@#{s})\}'" : 'NULL'\}|
|
752
|
+
|
753
|
+
elsif a.class.ancestor? Date
|
754
|
+
%|#\{@#{s} ? "'#\{#{store}.date(@#{s})\}'" : 'NULL'\}|
|
755
|
+
|
756
|
+
elsif a.class.ancestor? TrueClass
|
757
|
+
"#\{@#{s} ? \"'t'\" : 'NULL' \}"
|
758
|
+
|
759
|
+
elsif a.class.ancestor? Og::Blob
|
760
|
+
%|#\{@#{s} ? "'#\{#{store}.escape(#{store}.blob(@#{s}))\}'" : 'NULL'\}|
|
761
|
+
|
762
|
+
else
|
763
|
+
# keep the '' for nil symbols.
|
764
|
+
%|#\{@#{s} ? "'#\{#{store}.escape(@#{s}.to_yaml)\}'" : "''"\}|
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
# Generate code to deserialize an SQL table field into an
|
769
|
+
# attribute.
|
770
|
+
#--
|
771
|
+
# No need to optimize this, used only to precalculate code.
|
772
|
+
#++
|
773
|
+
|
774
|
+
def read_attr(s, a, col)
|
775
|
+
store = self.class
|
776
|
+
|
777
|
+
if a.class.ancestor? Integer
|
778
|
+
"#{store}.parse_int(res[#{col} + offset])"
|
779
|
+
|
780
|
+
elsif a.class.ancestor? Float
|
781
|
+
"#{store}.parse_float(res[#{col} + offset])"
|
782
|
+
|
783
|
+
elsif a.class.ancestor? String
|
784
|
+
"res[#{col} + offset]"
|
785
|
+
|
786
|
+
elsif a.class.ancestor? Time
|
787
|
+
"#{store}.parse_timestamp(res[#{col} + offset])"
|
788
|
+
|
789
|
+
elsif a.class.ancestor? Date
|
790
|
+
"#{store}.parse_date(res[#{col} + offset])"
|
791
|
+
|
792
|
+
elsif a.class.ancestor? TrueClass
|
793
|
+
"#{store}.parse_boolean(res[#{col} + offset])"
|
794
|
+
|
795
|
+
elsif a.class.ancestor? Og::Blob
|
796
|
+
"#{store}.parse_blob(res[#{col} + offset])"
|
797
|
+
|
798
|
+
else
|
799
|
+
"YAML::load(res[#{col} + offset])"
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
# Get the fields from the database table. Also handles the
|
804
|
+
# change of ordering of the fields in the table.
|
805
|
+
#
|
806
|
+
# To ignore a database field use the ignore_fields annotation
|
807
|
+
# ie,
|
808
|
+
#
|
809
|
+
# class Article
|
810
|
+
# ann self, :ignore_fields => [ :tsearch_idx, :ext_field ]
|
811
|
+
# end
|
812
|
+
#
|
813
|
+
# other aliases for ignore_fiels: ignore_field, ignore_column.
|
814
|
+
#--
|
815
|
+
# Even though great care has been taken to make this
|
816
|
+
# method reusable, oveeride if needed in your adapter.
|
817
|
+
#++
|
818
|
+
|
819
|
+
def create_field_map(klass)
|
820
|
+
res = query "SELECT * FROM #{klass.table} LIMIT 1"
|
821
|
+
map = {}
|
822
|
+
|
823
|
+
# Check if the field should be ignored.
|
824
|
+
|
825
|
+
ignore = klass.ann.self[:ignore_field] || klass.ann.self[:ignore_fields] || klass.ann.self[:ignore_columns]
|
826
|
+
|
827
|
+
res.fields.each_with_index do |f, i|
|
828
|
+
field_name = f.to_sym
|
829
|
+
unless (ignore and ignore.include?(field_name))
|
830
|
+
map[field_name] = i
|
831
|
+
end
|
832
|
+
end
|
833
|
+
|
834
|
+
return map
|
835
|
+
ensure
|
836
|
+
res.close if res
|
837
|
+
end
|
838
|
+
|
839
|
+
# Generates the SQL field of the primary key for this class.
|
840
|
+
|
841
|
+
def pk_field klass
|
842
|
+
pk = klass.primary_key
|
843
|
+
return klass.ann(pk)[:field] || pk
|
844
|
+
end
|
845
|
+
|
846
|
+
# :section: Lifecycle method compilers.
|
847
|
+
|
848
|
+
# Compile the og_insert method for the class. This method is
|
849
|
+
# used to create insert the object into a new Row in the
|
850
|
+
# database.
|
851
|
+
#--
|
852
|
+
# Override the og_insert_kernel method to customize for
|
853
|
+
# adapters.
|
854
|
+
#++
|
855
|
+
|
856
|
+
def eval_og_insert(klass)
|
857
|
+
fields = []
|
858
|
+
values = []
|
859
|
+
|
860
|
+
for a in klass.serializable_attributes
|
861
|
+
anno = klass.ann(a)
|
862
|
+
fields << field_for_attribute(a, anno)
|
863
|
+
values << write_attr(a, anno)
|
864
|
+
end
|
865
|
+
|
866
|
+
# If the class participates in STI, automatically insert
|
867
|
+
# an ogtype serializable attribute.
|
868
|
+
|
869
|
+
if klass.schema_inheritance?
|
870
|
+
fields << :ogtype
|
871
|
+
values << quote(klass.name)
|
872
|
+
end
|
873
|
+
|
874
|
+
fields = fields.join(', ')
|
875
|
+
values = values.join(', ')
|
876
|
+
|
877
|
+
sql = "INSERT INTO #{klass.table} (#{fields}) VALUES (#{values})"
|
878
|
+
|
879
|
+
klass.module_eval %{
|
880
|
+
def og_insert(store)
|
881
|
+
#{insert_advices :pre, :og_insert, klass, :advices}
|
882
|
+
#{insert_sql sql, klass}
|
883
|
+
#{insert_advices :post, :og_insert, klass, :advices}
|
884
|
+
end
|
885
|
+
}
|
886
|
+
end
|
887
|
+
|
888
|
+
# The insert sql statements.
|
889
|
+
|
890
|
+
def insert_sql(sql, klass)
|
891
|
+
%{
|
892
|
+
store.exec "#{sql}"
|
893
|
+
@#{klass.primary_key} = store.last_insert_id
|
894
|
+
}
|
895
|
+
end
|
896
|
+
|
897
|
+
# Compile the og_update method for the class.
|
898
|
+
|
899
|
+
def eval_og_update(klass)
|
900
|
+
pk = klass.primary_key
|
901
|
+
|
902
|
+
updates = []
|
903
|
+
|
904
|
+
for a in klass.serializable_attributes.reject { |a| a == pk }
|
905
|
+
anno = klass.ann(a)
|
906
|
+
updates << "#{field_for_attribute a, anno}=#{write_attr a, anno}"
|
907
|
+
end
|
908
|
+
|
909
|
+
updates = updates.join(', ')
|
910
|
+
|
911
|
+
sql = "UPDATE #{klass.table} SET #{updates} WHERE #{pk_field klass}=#\{@#{pk}\}"
|
912
|
+
|
913
|
+
klass.module_eval %{
|
914
|
+
def og_update(store, options = nil)
|
915
|
+
#{insert_advices :pre, :og_update, klass, :advices}
|
916
|
+
sql = "#{sql}"
|
917
|
+
sql << " AND \#{options[:condition]}" if options and options[:condition]
|
918
|
+
changed = store.sql_update(sql)
|
919
|
+
#{insert_advices :post, :og_update, klass, :advices}
|
920
|
+
return changed
|
921
|
+
end
|
922
|
+
}
|
923
|
+
end
|
924
|
+
|
925
|
+
# Compile the og_read method for the class. This method is
|
926
|
+
# used to read (deserialize) the given class from the store.
|
927
|
+
# In order to allow for changing field/attribute orders a
|
928
|
+
# field mapping hash is used.
|
929
|
+
|
930
|
+
def eval_og_read(klass)
|
931
|
+
code = []
|
932
|
+
|
933
|
+
attrs = klass.attributes
|
934
|
+
field_map = create_field_map(klass)
|
935
|
+
|
936
|
+
for a in attrs
|
937
|
+
anno = klass.ann(a)
|
938
|
+
|
939
|
+
f = field_for_attribute(a, anno)
|
940
|
+
|
941
|
+
if col = field_map[f]
|
942
|
+
code << "@#{a} = #{read_attr a, anno, col}"
|
943
|
+
end
|
944
|
+
end
|
945
|
+
|
946
|
+
code = code.join('; ')
|
947
|
+
|
948
|
+
klass.module_eval %{
|
949
|
+
def og_read(res, row = 0, offset = 0)
|
950
|
+
#{insert_advices :pre, :og_read, klass, :advices}
|
951
|
+
#{code}
|
952
|
+
#{insert_advices :post, :og_read, klass, :advices}
|
953
|
+
end
|
954
|
+
}
|
955
|
+
end
|
956
|
+
|
957
|
+
# Compiles the og_delete method for this class. This method
|
958
|
+
# is used to delete instances of this class.
|
959
|
+
|
960
|
+
def eval_og_delete klass
|
961
|
+
klass.module_eval %{
|
962
|
+
def og_delete(store, cascade = true)
|
963
|
+
#{insert_advices :pre, :og_delete, klass, :advices}
|
964
|
+
|
965
|
+
if cascade
|
966
|
+
|
967
|
+
transaction do |tx|
|
968
|
+
tx.exec "DELETE FROM #{klass.table} WHERE #{pk_field klass}=\#{pk}"
|
969
|
+
if descendants = #{klass}.ann.self[:descendants]
|
970
|
+
descendants.each do |dclass, foreign_key|
|
971
|
+
tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}"
|
972
|
+
end
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
else
|
977
|
+
tx.exec "DELETE FROM #{klass.table} WHERE #{pk_field klass}=\#{pk}"
|
978
|
+
end
|
979
|
+
|
980
|
+
#{insert_advices :post, :og_delete, klass, :advices}
|
981
|
+
end
|
982
|
+
}
|
983
|
+
end
|
984
|
+
|
985
|
+
# Creates the schema for this class. Can be intercepted with
|
986
|
+
# aspects to add special behaviours.
|
987
|
+
|
988
|
+
def eval_og_create_schema(klass)
|
989
|
+
klass.module_eval %{
|
990
|
+
def og_create_schema(store)
|
991
|
+
if Og.create_schema
|
992
|
+
#{insert_advices :pre, :og_create_schema, klass, :advices}
|
993
|
+
unless self.class.schema_inheritance_child?
|
994
|
+
store.create_table #{klass}
|
995
|
+
end
|
996
|
+
store.evolve_schema(#{klass})
|
997
|
+
#{insert_advices :post, :og_create_schema, klass, :advices}
|
998
|
+
end
|
999
|
+
end
|
1000
|
+
}
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
# Precompile a class specific allocate method. If this is an
|
1004
|
+
# STI parent classes it reads the class from the resultset.
|
1005
|
+
|
1006
|
+
def eval_og_allocate(klass)
|
1007
|
+
if klass.schema_inheritance?
|
1008
|
+
klass.module_eval %{
|
1009
|
+
def self.og_allocate(res, row = 0)
|
1010
|
+
Object.constant(res[0]).allocate
|
1011
|
+
end
|
1012
|
+
}
|
1013
|
+
else
|
1014
|
+
klass.module_eval %{
|
1015
|
+
def self.og_allocate(res, row = 0)
|
1016
|
+
self.allocate
|
1017
|
+
end
|
1018
|
+
}
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
# ...
|
1023
|
+
|
1024
|
+
def type_cast(klass, val)
|
1025
|
+
typemap = {
|
1026
|
+
Time => :parse_timestamp,
|
1027
|
+
Date => :parse_date,
|
1028
|
+
TrueClass => :parse_boolean
|
1029
|
+
}
|
1030
|
+
|
1031
|
+
if method = typemap[klass]
|
1032
|
+
send(method, val)
|
1033
|
+
else
|
1034
|
+
Integer(val) rescue Float(val) rescue raise "No conversion for #{klass} (#{val.inspect})"
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
|
1084
1038
|
# :section: Deserialization methods.
|
1085
1039
|
|
1086
1040
|
# Read a field (column) from a result set row.
|
@@ -1102,16 +1056,48 @@ private
|
|
1102
1056
|
# Deserialize the join relations.
|
1103
1057
|
|
1104
1058
|
def read_join_relations(obj, res_row, row, join_relations)
|
1105
|
-
offset = obj.class.
|
1059
|
+
offset = obj.class.serializable_attributes.size
|
1106
1060
|
|
1107
1061
|
for rel in join_relations
|
1108
1062
|
rel_obj = rel[:target_class].og_allocate(res_row, row)
|
1109
1063
|
rel_obj.og_read(res_row, row, offset)
|
1110
|
-
offset += rel_obj.class.
|
1064
|
+
offset += rel_obj.class.serializable_attributes.size
|
1111
1065
|
obj.instance_variable_set("@#{rel[:name]}", rel_obj)
|
1112
1066
|
end
|
1113
1067
|
end
|
1114
1068
|
|
1069
|
+
# Deserialize one object from the ResultSet.
|
1070
|
+
|
1071
|
+
def read_one(res, klass, options = nil)
|
1072
|
+
return nil if res.blank?
|
1073
|
+
|
1074
|
+
if options and join_relations = options[:include]
|
1075
|
+
join_relations = [join_relations].flatten.collect do |n|
|
1076
|
+
klass.relation(n)
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
res_row = res.next
|
1081
|
+
|
1082
|
+
# causes STI classes to come back as the correct child class
|
1083
|
+
# if accessed from the superclass.
|
1084
|
+
|
1085
|
+
klass = Og::Entity::entity_from_string(res_row[0]) if klass.schema_inheritance?
|
1086
|
+
obj = klass.og_allocate(res_row, 0)
|
1087
|
+
|
1088
|
+
if options and options[:select]
|
1089
|
+
read_row(obj, res, res_row, 0)
|
1090
|
+
else
|
1091
|
+
obj.og_read(res_row)
|
1092
|
+
read_join_relations(obj, res_row, 0, join_relations) if join_relations
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
return obj
|
1096
|
+
|
1097
|
+
ensure
|
1098
|
+
res.close
|
1099
|
+
end
|
1100
|
+
|
1115
1101
|
# Deserialize all objects from the ResultSet.
|
1116
1102
|
|
1117
1103
|
def read_all(res, klass, options = nil)
|
@@ -1156,13 +1142,24 @@ private
|
|
1156
1142
|
end
|
1157
1143
|
end
|
1158
1144
|
|
1159
|
-
|
1145
|
+
# Returns true if a table exists within the database, false
|
1146
|
+
# otherwise.
|
1147
|
+
|
1148
|
+
def table_exists?(table)
|
1149
|
+
table_info(table) ? true : false
|
1150
|
+
end
|
1151
|
+
alias_method :table_exist?, :table_exists?
|
1152
|
+
|
1153
|
+
private
|
1160
1154
|
|
1155
|
+
def database_does_not_exist_exception?(ex)
|
1156
|
+
false
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
def table_already_exists_exception?(ex)
|
1160
|
+
false
|
1161
|
+
end
|
1162
|
+
|
1161
1163
|
end
|
1162
1164
|
|
1163
|
-
|
1164
|
-
# * Michael Neumann <mneumann@ntecs.de>
|
1165
|
-
# * Ghislain Mary
|
1166
|
-
# * Ysabel <deb@ysabel.org>
|
1167
|
-
# * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
|
1168
|
-
# * Rob Pitt <rob@motionpath.com>
|
1165
|
+
end
|