og 0.23.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ProjectInfo +58 -0
- data/README +5 -4
- data/Rakefile +2 -2
- data/doc/AUTHORS +10 -7
- data/doc/RELEASES +108 -0
- data/lib/og.rb +1 -3
- data/lib/og/collection.rb +4 -4
- data/lib/og/entity.rb +96 -27
- data/lib/og/evolution.rb +78 -0
- data/lib/og/manager.rb +29 -32
- data/lib/og/mixin/hierarchical.rb +1 -1
- data/lib/og/mixin/optimistic_locking.rb +5 -8
- data/lib/og/mixin/orderable.rb +15 -2
- data/lib/og/mixin/schema_inheritance_base.rb +12 -0
- data/lib/og/mixin/taggable.rb +29 -25
- data/lib/og/mixin/timestamped.rb +4 -2
- data/lib/og/mixin/tree.rb +0 -1
- data/lib/og/relation.rb +161 -116
- data/lib/og/relation/all.rb +6 -0
- data/lib/og/relation/belongs_to.rb +4 -1
- data/lib/og/relation/has_many.rb +6 -5
- data/lib/og/relation/joins_many.rb +13 -12
- data/lib/og/relation/refers_to.rb +3 -3
- data/lib/og/store.rb +9 -9
- data/lib/og/store/{filesys.rb → alpha/filesys.rb} +0 -0
- data/lib/og/store/alpha/kirby.rb +284 -0
- data/lib/og/store/{memory.rb → alpha/memory.rb} +2 -0
- data/lib/og/store/{sqlserver.rb → alpha/sqlserver.rb} +6 -6
- data/lib/og/store/kirby.rb +145 -162
- data/lib/og/store/mysql.rb +58 -27
- data/lib/og/store/psql.rb +15 -13
- data/lib/og/store/sql.rb +136 -135
- data/lib/og/store/sqlite.rb +13 -12
- data/lib/og/validation.rb +2 -2
- data/lib/vendor/kbserver.rb +20 -0
- data/lib/vendor/kirbybase.rb +2790 -1601
- data/test/og/CONFIG.rb +79 -0
- data/test/og/mixin/tc_hierarchical.rb +1 -1
- data/test/og/mixin/tc_optimistic_locking.rb +1 -3
- data/test/og/mixin/tc_orderable.rb +42 -1
- data/test/og/mixin/tc_taggable.rb +1 -1
- data/test/og/mixin/tc_timestamped.rb +1 -1
- data/test/og/store/tc_filesys.rb +1 -2
- data/test/og/tc_delete_all.rb +45 -0
- data/test/og/tc_inheritance.rb +10 -38
- data/test/og/tc_join.rb +2 -11
- data/test/og/tc_multiple.rb +3 -16
- data/test/og/tc_override.rb +3 -3
- data/test/og/tc_polymorphic.rb +3 -13
- data/test/og/tc_relation.rb +8 -6
- data/test/og/tc_reverse.rb +2 -11
- data/test/og/tc_select.rb +2 -15
- data/test/og/tc_store.rb +4 -63
- data/test/og/tc_types.rb +1 -2
- metadata +80 -77
data/lib/og/store/mysql.rb
CHANGED
@@ -2,7 +2,7 @@ begin
|
|
2
2
|
require 'mysql'
|
3
3
|
rescue Object => ex
|
4
4
|
Logger.error 'Ruby-Mysql bindings are not installed!'
|
5
|
-
Logger.error 'Trying to
|
5
|
+
Logger.error 'Trying to use the pure-Ruby binding included in Og'
|
6
6
|
begin
|
7
7
|
# Attempt to use the included pure ruby version.
|
8
8
|
require 'vendor/mysql'
|
@@ -87,8 +87,19 @@ module MysqlUtils
|
|
87
87
|
end
|
88
88
|
|
89
89
|
# A Store that persists objects into a MySQL database.
|
90
|
-
# To read documentation about the methods, consult
|
91
|
-
# for SqlStore and Store.
|
90
|
+
# To read documentation about the methods, consult
|
91
|
+
# the documentation for SqlStore and Store.
|
92
|
+
#
|
93
|
+
# Here is some useful code to initialize your MySQL
|
94
|
+
# RDBMS for development. You probably want to be
|
95
|
+
# more careful with provileges on your production
|
96
|
+
# environment.
|
97
|
+
#
|
98
|
+
# mysql> GRANT ALL PRIVELEGES
|
99
|
+
# ON keystone.*
|
100
|
+
# TO <$sys_dbuser name>@localhost
|
101
|
+
# IDENTIFIED BY '(password)'
|
102
|
+
# WITH GRANT OPTION;
|
92
103
|
|
93
104
|
class MysqlStore < SqlStore
|
94
105
|
extend MysqlUtils
|
@@ -109,6 +120,16 @@ class MysqlStore < SqlStore
|
|
109
120
|
super
|
110
121
|
end
|
111
122
|
|
123
|
+
# Initialize the MySQL store.
|
124
|
+
#
|
125
|
+
# === Options
|
126
|
+
#
|
127
|
+
# * :address, the addres where the server is listening.
|
128
|
+
# * :socket, is useful when the pure ruby driver is used.
|
129
|
+
# this is the location of mysql.sock. For Ubuntu/Debian
|
130
|
+
# this is '/var/run/mysqld/mysqld.sock'. You can find
|
131
|
+
# the location for your system in my.cnf
|
132
|
+
|
112
133
|
def initialize(options)
|
113
134
|
super
|
114
135
|
|
@@ -118,9 +139,11 @@ class MysqlStore < SqlStore
|
|
118
139
|
options[:address] || 'localhost',
|
119
140
|
options[:user],
|
120
141
|
options[:password],
|
121
|
-
options[:name]
|
142
|
+
options[:name],
|
143
|
+
options[:port],
|
144
|
+
options[:socket]
|
122
145
|
)
|
123
|
-
|
146
|
+
|
124
147
|
# You should set recconect to true to avoid MySQL has
|
125
148
|
# gone away errors.
|
126
149
|
|
@@ -143,9 +166,9 @@ class MysqlStore < SqlStore
|
|
143
166
|
super
|
144
167
|
end
|
145
168
|
|
146
|
-
def enchant(klass, manager)
|
147
|
-
if klass.
|
148
|
-
unless klass.properties.
|
169
|
+
def enchant(klass, manager)
|
170
|
+
if klass.ann.this.primary_key.symbol == :oid
|
171
|
+
unless klass.properties.include? :oid
|
149
172
|
klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
|
150
173
|
end
|
151
174
|
end
|
@@ -195,14 +218,23 @@ class MysqlStore < SqlStore
|
|
195
218
|
private
|
196
219
|
|
197
220
|
def create_table(klass)
|
221
|
+
# rp: fixes problems when user doesn't have
|
222
|
+
# write access to db.
|
223
|
+
# THINK, should a method more like this be
|
224
|
+
# used instead of catching database exceptions
|
225
|
+
# for 'table exists'?
|
226
|
+
|
227
|
+
return if @conn.list_tables.include?(klass::OGTABLE)
|
228
|
+
|
229
|
+
|
198
230
|
fields = fields_for_class(klass)
|
199
231
|
|
200
232
|
sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
|
201
233
|
|
202
|
-
# Create table
|
234
|
+
# Create table constraints.
|
203
235
|
|
204
|
-
if
|
205
|
-
sql << ", #{
|
236
|
+
if constraints = klass.ann.this[:sql_constraint]
|
237
|
+
sql << ", #{constraints.join(', ')}"
|
206
238
|
end
|
207
239
|
|
208
240
|
if table_type = @options[:table_type]
|
@@ -213,7 +245,7 @@ private
|
|
213
245
|
|
214
246
|
# Create indices.
|
215
247
|
|
216
|
-
if
|
248
|
+
if indices = klass.ann.this[:index]
|
217
249
|
for data in indices
|
218
250
|
idx, options = *data
|
219
251
|
idx = idx.to_s
|
@@ -240,7 +272,7 @@ private
|
|
240
272
|
# Create join tables if needed. Join tables are used in
|
241
273
|
# 'many_to_many' relations.
|
242
274
|
|
243
|
-
if
|
275
|
+
if join_tables = klass.ann.this[:join_tables]
|
244
276
|
for info in join_tables
|
245
277
|
begin
|
246
278
|
create_join_table_sql(info).each do |sql|
|
@@ -248,8 +280,8 @@ private
|
|
248
280
|
end
|
249
281
|
Logger.debug "Created jointable '#{info[:table]}'."
|
250
282
|
rescue => ex
|
251
|
-
if ex.respond_to?(:errno) and ex.errno == 1050 # table already exists.
|
252
|
-
Logger.debug 'Join table already exists' if $DBG
|
283
|
+
if ex.respond_to?(:errno) and ex.errno == 1050 # table already exists.
|
284
|
+
Logger.debug 'Join table already exists' if $DBG
|
253
285
|
else
|
254
286
|
raise
|
255
287
|
end
|
@@ -274,20 +306,20 @@ private
|
|
274
306
|
|
275
307
|
def write_prop(p)
|
276
308
|
if p.klass.ancestors.include?(Integer)
|
277
|
-
return "#\{@#{p
|
309
|
+
return "#\{@#{p} || 'NULL'\}"
|
278
310
|
elsif p.klass.ancestors.include?(Float)
|
279
|
-
return "#\{@#{p
|
311
|
+
return "#\{@#{p} || 'NULL'\}"
|
280
312
|
elsif p.klass.ancestors.include?(String)
|
281
|
-
return %|#\{@#{p
|
313
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.escape(@#{p})\}'" : 'NULL'\}|
|
282
314
|
elsif p.klass.ancestors.include?(Time)
|
283
|
-
return %|#\{@#{p
|
315
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.timestamp(@#{p})\}'" : 'NULL'\}|
|
284
316
|
elsif p.klass.ancestors.include?(Date)
|
285
|
-
return %|#\{@#{p
|
317
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.date(@#{p})\}'" : 'NULL'\}|
|
286
318
|
elsif p.klass.ancestors.include?(TrueClass)
|
287
|
-
return "#\{@#{p
|
319
|
+
return "#\{@#{p} ? \"'1'\" : 'NULL' \}"
|
288
320
|
else
|
289
321
|
# gmosx: keep the '' for nil symbols.
|
290
|
-
return %|#\{@#{p
|
322
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.escape(@#{p}.to_yaml)\}'" : "''"\}|
|
291
323
|
end
|
292
324
|
end
|
293
325
|
|
@@ -303,18 +335,18 @@ private
|
|
303
335
|
elsif p.klass.ancestors.include?(Date)
|
304
336
|
return "#{self.class}.parse_date(res[#{col} + offset])"
|
305
337
|
elsif p.klass.ancestors.include?(TrueClass)
|
306
|
-
return "('
|
338
|
+
return "('1' == res[#{col} + offset])"
|
307
339
|
else
|
308
340
|
return "YAML.load(res[#{col} + offset])"
|
309
341
|
end
|
310
342
|
end
|
311
343
|
|
312
344
|
def eval_og_insert(klass)
|
313
|
-
props = klass.properties.
|
345
|
+
props = klass.properties.values
|
314
346
|
values = props.collect { |p| write_prop(p) }.join(',')
|
315
347
|
|
316
|
-
if klass.
|
317
|
-
props << Property.new(:ogtype, String)
|
348
|
+
if klass.schema_inheritance?
|
349
|
+
props << Property.new(:symbol => :ogtype, :klass => String)
|
318
350
|
values << ", '#{klass}'"
|
319
351
|
end
|
320
352
|
|
@@ -323,7 +355,6 @@ private
|
|
323
355
|
klass.class_eval %{
|
324
356
|
def og_insert(store)
|
325
357
|
#{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
326
|
-
puts "#{sql}"
|
327
358
|
store.conn.query_with_result = false
|
328
359
|
store.conn.query "#{sql}"
|
329
360
|
@#{klass.pk_symbol} = store.conn.insert_id
|
data/lib/og/store/psql.rb
CHANGED
@@ -59,6 +59,8 @@ module PsqlUtils
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def parse_blob(val)
|
62
|
+
return '' unless val
|
63
|
+
|
62
64
|
val.gsub(/\\(\\|'|[0-3][0-7][0-7])/) do |s|
|
63
65
|
if s.size == 2 then s[1,1] else s[1,3].oct.chr end
|
64
66
|
end
|
@@ -126,14 +128,14 @@ class PsqlStore < SqlStore
|
|
126
128
|
end
|
127
129
|
|
128
130
|
def enchant(klass, manager)
|
129
|
-
if
|
130
|
-
klass.const_set 'OGSEQ', "#{table(
|
131
|
+
if klass.schema_inheritance_child?
|
132
|
+
klass.const_set 'OGSEQ', "#{table(klass.schema_inheritance_root_class)}_oid_seq"
|
131
133
|
else
|
132
134
|
klass.const_set 'OGSEQ', "#{table(klass)}_oid_seq"
|
133
135
|
end
|
134
136
|
|
135
|
-
if klass.
|
136
|
-
unless klass.properties.
|
137
|
+
if klass.ann.this.primary_key.symbol == :oid
|
138
|
+
unless klass.properties.include? :oid
|
137
139
|
klass.property :oid, Fixnum, :sql => 'serial PRIMARY KEY'
|
138
140
|
end
|
139
141
|
end
|
@@ -177,17 +179,17 @@ private
|
|
177
179
|
|
178
180
|
sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
|
179
181
|
|
180
|
-
# Create table
|
182
|
+
# Create table constraints.
|
181
183
|
|
182
|
-
if
|
183
|
-
sql << ", #{
|
184
|
+
if constraints = klass.ann.this[:sql_constraint]
|
185
|
+
sql << ", #{constraints.join(', ')}"
|
184
186
|
end
|
185
187
|
|
186
188
|
sql << ") WITHOUT OIDS;"
|
187
189
|
|
188
190
|
# Create indices.
|
189
191
|
|
190
|
-
if
|
192
|
+
if indices = klass.ann.this[:index]
|
191
193
|
for data in indices
|
192
194
|
idx, options = *data
|
193
195
|
idx = idx.to_s
|
@@ -213,7 +215,7 @@ private
|
|
213
215
|
# Create join tables if needed. Join tables are used in
|
214
216
|
# 'many_to_many' relations.
|
215
217
|
|
216
|
-
if
|
218
|
+
if join_tables = klass.ann.this[:join_tables]
|
217
219
|
for info in join_tables
|
218
220
|
begin
|
219
221
|
create_join_table_sql(info).each do |sql|
|
@@ -275,11 +277,11 @@ private
|
|
275
277
|
#++
|
276
278
|
|
277
279
|
def eval_og_insert(klass)
|
278
|
-
props = klass.properties.dup
|
280
|
+
props = klass.properties.values.dup
|
279
281
|
values = props.collect { |p| write_prop(p) }.join(',')
|
280
282
|
|
281
|
-
if klass.
|
282
|
-
props << Property.new(:ogtype, String)
|
283
|
+
if klass.schema_inheritance?
|
284
|
+
props << Property.new(:symbol => :ogtype, :klass => String)
|
283
285
|
values << ", '#{klass}'"
|
284
286
|
end
|
285
287
|
|
@@ -298,7 +300,7 @@ private
|
|
298
300
|
end
|
299
301
|
|
300
302
|
def eval_og_allocate(klass)
|
301
|
-
if klass.
|
303
|
+
if klass.ann.this[:subclasses]
|
302
304
|
klass.module_eval %{
|
303
305
|
def self.og_allocate(res, row = 0)
|
304
306
|
Object.constant(res.getvalue(row, 0)).allocate
|
data/lib/og/store/sql.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'time'
|
2
3
|
|
3
|
-
require 'nano/
|
4
|
+
require 'nano/kernel/constant'
|
4
5
|
|
5
6
|
module Og
|
6
7
|
|
@@ -105,53 +106,50 @@ module SqlUtils
|
|
105
106
|
|
106
107
|
# Apply table name conventions to a class name.
|
107
108
|
|
108
|
-
def tableize(klass)
|
109
|
-
"#{klass.to_s.gsub(/::/, "_").downcase}"
|
110
|
-
end
|
109
|
+
def tableize(klass)
|
110
|
+
"#{klass.to_s.gsub(/::/, "_").downcase}"
|
111
|
+
end
|
111
112
|
|
112
113
|
def table(klass)
|
113
|
-
|
114
|
-
|
114
|
+
klass.ann.this[:sql_table] || klass.ann.this[:table] || "#{Og.table_prefix}#{tableize(klass)}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def join_object_ordering(obj1, obj2)
|
118
|
+
if obj1.class.to_s <= obj2.class.to_s
|
119
|
+
return obj1, obj2
|
115
120
|
else
|
116
|
-
|
117
|
-
end
|
121
|
+
return obj2, obj1, true
|
122
|
+
end
|
118
123
|
end
|
119
124
|
|
120
|
-
def
|
121
|
-
if obj1.class.to_s <= obj2.class.to_s
|
122
|
-
return obj1, obj2
|
123
|
-
else
|
124
|
-
return obj2, obj1, true
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def join_class_ordering(class1, class2)
|
125
|
+
def join_class_ordering(class1, class2)
|
129
126
|
if class1.to_s <= class2.to_s
|
130
|
-
return class1, class2
|
127
|
+
return class1, class2
|
131
128
|
else
|
132
|
-
return class2, class1, true
|
129
|
+
return class2, class1, true
|
133
130
|
end
|
134
131
|
end
|
135
|
-
|
136
|
-
def build_join_name(class1, class2, postfix = nil)
|
137
|
-
# Don't reorder arguments, as this is used in places that
|
138
|
-
# have already determined the order they want.
|
139
|
-
"#{Og.table_prefix}j_#{tableize(class1)}_#{tableize(class2)}#{postfix}"
|
140
|
-
end
|
141
|
-
|
142
|
-
def join_table(class1, class2, postfix = nil)
|
143
|
-
first, second = join_class_ordering(class1, class2)
|
144
|
-
build_join_name(first, second, postfix)
|
145
|
-
end
|
146
|
-
|
147
|
-
def join_table_index(key)
|
148
|
-
"#{key}_idx"
|
149
|
-
end
|
150
|
-
|
151
|
-
def join_table_key(klass)
|
152
|
-
|
153
|
-
|
154
|
-
|
132
|
+
|
133
|
+
def build_join_name(class1, class2, postfix = nil)
|
134
|
+
# Don't reorder arguments, as this is used in places that
|
135
|
+
# have already determined the order they want.
|
136
|
+
"#{Og.table_prefix}j_#{tableize(class1)}_#{tableize(class2)}#{postfix}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def join_table(class1, class2, postfix = nil)
|
140
|
+
first, second = join_class_ordering(class1, class2)
|
141
|
+
build_join_name(first, second, postfix)
|
142
|
+
end
|
143
|
+
|
144
|
+
def join_table_index(key)
|
145
|
+
"#{key}_idx"
|
146
|
+
end
|
147
|
+
|
148
|
+
def join_table_key(klass)
|
149
|
+
klass = klass.schema_inheritance_root_class if klass.schema_inheritance_child?
|
150
|
+
"#{klass.to_s.split('::').last.downcase}_oid"
|
151
|
+
end
|
152
|
+
|
155
153
|
def join_table_keys(class1, class2)
|
156
154
|
if class1 == class2
|
157
155
|
# Fix for the self-join case.
|
@@ -165,8 +163,14 @@ module SqlUtils
|
|
165
163
|
first, second = join_class_ordering(class1, class2)
|
166
164
|
return join_table_keys(first, second)
|
167
165
|
end
|
168
|
-
|
169
|
-
def join_table_info(owner_class, target_class, postfix = nil)
|
166
|
+
|
167
|
+
def join_table_info(owner_class, target_class, postfix = nil)
|
168
|
+
|
169
|
+
# some fixes for schema inheritance.
|
170
|
+
|
171
|
+
owner_class = owner_class.schema_inheritance_root_class if owner_class.schema_inheritance_child?
|
172
|
+
target_class = target_class.schema_inheritance_root_class if target_class.schema_inheritance_child?
|
173
|
+
|
170
174
|
owner_key, target_key = join_table_keys(owner_class, target_class)
|
171
175
|
first, second, changed = join_class_ordering(owner_class, target_class)
|
172
176
|
|
@@ -175,35 +179,35 @@ module SqlUtils
|
|
175
179
|
else
|
176
180
|
first_key, second_key = owner_key, target_key
|
177
181
|
end
|
178
|
-
|
179
|
-
return {
|
180
|
-
:table => join_table(owner_class, target_class, postfix),
|
181
|
-
:owner_key => owner_key,
|
182
|
-
:target_key => target_key,
|
183
|
-
:first_table => table(first),
|
184
|
-
:first_key => first_key,
|
185
|
-
:first_index => join_table_index(first_key),
|
186
|
-
:second_table => table(second),
|
187
|
-
:second_key => second_key,
|
188
|
-
:second_index => join_table_index(second_key)
|
189
|
-
}
|
190
|
-
end
|
191
|
-
|
182
|
+
|
183
|
+
return {
|
184
|
+
:table => join_table(owner_class, target_class, postfix),
|
185
|
+
:owner_key => owner_key,
|
186
|
+
:target_key => target_key,
|
187
|
+
:first_table => table(first),
|
188
|
+
:first_key => first_key,
|
189
|
+
:first_index => join_table_index(first_key),
|
190
|
+
:second_table => table(second),
|
191
|
+
:second_key => second_key,
|
192
|
+
:second_index => join_table_index(second_key)
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
192
196
|
# Subclasses can override this if they need a different
|
193
197
|
# syntax.
|
194
|
-
|
195
|
-
def create_join_table_sql(join_table_info, suffix = 'NOT NULL', key_type = 'integer')
|
198
|
+
|
199
|
+
def create_join_table_sql(join_table_info, suffix = 'NOT NULL', key_type = 'integer')
|
196
200
|
join_table = join_table_info[:table]
|
197
|
-
first_index = join_table_info[:first_index]
|
198
|
-
first_key = join_table_info[:first_key]
|
199
|
-
second_key = join_table_info[:second_key]
|
201
|
+
first_index = join_table_info[:first_index]
|
202
|
+
first_key = join_table_info[:first_key]
|
203
|
+
second_key = join_table_info[:second_key]
|
200
204
|
second_index = join_table_info[:second_index]
|
201
205
|
|
202
206
|
sql = []
|
203
207
|
|
204
208
|
sql << %{
|
205
|
-
CREATE TABLE #{join_table} (
|
206
|
-
#{first_key} integer NOT NULL,
|
209
|
+
CREATE TABLE #{join_table} (
|
210
|
+
#{first_key} integer NOT NULL,
|
207
211
|
#{second_key} integer NOT NULL,
|
208
212
|
PRIMARY KEY(#{first_key}, #{second_key})
|
209
213
|
)
|
@@ -213,9 +217,9 @@ module SqlUtils
|
|
213
217
|
# sql << "CREATE INDEX #{first_index} ON #{join_table} (#{first_key})"
|
214
218
|
# sql << "CREATE INDEX #{second_index} ON #{join_table} (#{second_key})"
|
215
219
|
|
216
|
-
return sql
|
217
|
-
end
|
218
|
-
|
220
|
+
return sql
|
221
|
+
end
|
222
|
+
|
219
223
|
end
|
220
224
|
|
221
225
|
# An abstract SQL powered store.
|
@@ -267,8 +271,9 @@ class SqlStore < Store
|
|
267
271
|
|
268
272
|
# setup the table where this class is mapped.
|
269
273
|
|
270
|
-
if
|
271
|
-
|
274
|
+
if klass.schema_inheritance_child?
|
275
|
+
# farms: allow deeper inheritance (TODO: use annotation :superclass)
|
276
|
+
klass.const_set 'OGTABLE', table(klass.schema_inheritance_root_class)
|
272
277
|
else
|
273
278
|
klass.const_set 'OGTABLE', table(klass)
|
274
279
|
end
|
@@ -431,24 +436,28 @@ class SqlStore < Store
|
|
431
436
|
# Typically used in joins_many and many_to_many relations.
|
432
437
|
|
433
438
|
def join(obj1, obj2, table, options = nil)
|
434
|
-
first, second = join_object_ordering(obj1, obj2)
|
439
|
+
first, second = join_object_ordering(obj1, obj2)
|
435
440
|
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
436
441
|
if options
|
437
442
|
exec "INSERT INTO #{table} (#{first_key},#{second_key}, #{options.keys.join(',')}) VALUES (#{first.pk},#{second.pk}, #{options.values.map { |v| quote(v) }.join(',')})"
|
438
443
|
else
|
439
444
|
exec "INSERT INTO #{table} (#{first_key},#{second_key}) VALUES (#{first.pk}, #{second.pk})"
|
440
|
-
end
|
445
|
+
end
|
441
446
|
end
|
442
447
|
|
443
448
|
# Unrelate two objects be removing their relation from the
|
444
449
|
# join table.
|
445
450
|
|
446
451
|
def unjoin(obj1, obj2, table)
|
447
|
-
first, second = join_object_ordering(obj1, obj2)
|
448
|
-
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
452
|
+
first, second = join_object_ordering(obj1, obj2)
|
453
|
+
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
449
454
|
exec "DELETE FROM #{table} WHERE #{first_key}=#{first.pk} AND #{second_key}=#{second.pk}"
|
450
455
|
end
|
451
456
|
|
457
|
+
def delete_all(klass)
|
458
|
+
exec "DELETE FROM #{klass.table}"
|
459
|
+
end
|
460
|
+
|
452
461
|
# :section: Transaction methods.
|
453
462
|
|
454
463
|
# Start a new transaction.
|
@@ -496,6 +505,7 @@ private
|
|
496
505
|
def drop_table(klass)
|
497
506
|
exec "DROP TABLE #{klass.table}"
|
498
507
|
end
|
508
|
+
alias_method :destroy, :drop_table
|
499
509
|
|
500
510
|
# Evolve (recreate) the sql table where objects of this class
|
501
511
|
# are persisted.
|
@@ -509,57 +519,46 @@ private
|
|
509
519
|
# Return the field for the given property.
|
510
520
|
|
511
521
|
def field_for_property(property)
|
512
|
-
if f = property.
|
522
|
+
if f = property.field
|
513
523
|
return f.to_s
|
514
524
|
else
|
515
|
-
return property.
|
525
|
+
return property.to_s
|
516
526
|
end
|
517
527
|
end
|
518
528
|
|
519
529
|
# Create the fields that correpsond to the klass properties.
|
520
530
|
# The generated fields array is used in create_table.
|
521
|
-
# If the property has an :sql
|
522
|
-
# default mapping. If the property has an :extra_sql
|
531
|
+
# If the property has an :sql annotation this overrides the
|
532
|
+
# default mapping. If the property has an :extra_sql annotation
|
523
533
|
# the extra sql is appended after the default mapping.
|
524
534
|
|
525
535
|
def fields_for_class(klass)
|
526
536
|
fields = []
|
527
|
-
properties = klass.properties
|
537
|
+
properties = klass.properties.dup
|
528
538
|
|
529
|
-
if
|
539
|
+
if klass.ancestors.include? SchemaInheritanceBase
|
530
540
|
# This class as a superclass in a single table inheritance
|
531
541
|
# chain. So inject a special class ogtype field that
|
532
542
|
# holds the class name.
|
533
543
|
fields << "ogtype VARCHAR(30)"
|
534
|
-
|
535
|
-
for
|
536
|
-
properties.
|
544
|
+
|
545
|
+
for desc in klass.descendents
|
546
|
+
properties.update(desc.properties)
|
537
547
|
end
|
538
|
-
|
539
|
-
properties.uniq!
|
540
548
|
end
|
541
|
-
|
542
|
-
properties.
|
543
|
-
klass.index(p.symbol) if p.
|
549
|
+
|
550
|
+
for p in properties.values
|
551
|
+
klass.index(p.symbol) if p.index
|
544
552
|
|
545
553
|
field = field_for_property(p)
|
546
554
|
|
547
|
-
if p.
|
548
|
-
field << " #{p.
|
555
|
+
if p.sql
|
556
|
+
field << " #{p.sql}"
|
549
557
|
else
|
550
558
|
field << " #{type_for_class(p.klass)}"
|
551
|
-
|
552
|
-
if p.
|
553
|
-
|
554
|
-
|
555
|
-
if default = p.meta[:default]
|
556
|
-
field << " DEFAULT #{default.inspect} NOT NULL"
|
557
|
-
end
|
558
|
-
|
559
|
-
if extra_sql = p.meta[:extra_sql]
|
560
|
-
field << " #{extra_sql}"
|
561
|
-
end
|
562
|
-
end
|
559
|
+
field << " UNIQUE" if p.unique
|
560
|
+
field << " DEFAULT #{p.default.inspect} NOT NULL" if p.default
|
561
|
+
field << " #{p.extra_sql}" if p.extra_sql
|
563
562
|
end
|
564
563
|
|
565
564
|
fields << field
|
@@ -582,22 +581,22 @@ private
|
|
582
581
|
|
583
582
|
def write_prop(p)
|
584
583
|
if p.klass.ancestors.include?(Integer)
|
585
|
-
return "#\{@#{p
|
584
|
+
return "#\{@#{p} || 'NULL'\}"
|
586
585
|
elsif p.klass.ancestors.include?(Float)
|
587
|
-
return "#\{@#{p
|
586
|
+
return "#\{@#{p} || 'NULL'\}"
|
588
587
|
elsif p.klass.ancestors.include?(String)
|
589
|
-
return %|#\{@#{p
|
588
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.escape(@#{p})\}'" : 'NULL'\}|
|
590
589
|
elsif p.klass.ancestors.include?(Time)
|
591
|
-
return %|#\{@#{p
|
590
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.timestamp(@#{p})\}'" : 'NULL'\}|
|
592
591
|
elsif p.klass.ancestors.include?(Date)
|
593
|
-
return %|#\{@#{p
|
592
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.date(@#{p})\}'" : 'NULL'\}|
|
594
593
|
elsif p.klass.ancestors.include?(TrueClass)
|
595
|
-
return "#\{@#{p
|
594
|
+
return "#\{@#{p} ? \"'t'\" : 'NULL' \}"
|
596
595
|
elsif p.klass.ancestors.include?(Og::Blob)
|
597
|
-
return %|#\{@#{p
|
596
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.escape(#{self.class}.blob(@#{p}))\}'" : 'NULL'\}|
|
598
597
|
else
|
599
598
|
# gmosx: keep the '' for nil symbols.
|
600
|
-
return %|#\{@#{p
|
599
|
+
return %|#\{@#{p} ? "'#\{#{self.class}.escape(@#{p}.to_yaml)\}'" : "''"\}|
|
601
600
|
end
|
602
601
|
end
|
603
602
|
|
@@ -630,11 +629,11 @@ private
|
|
630
629
|
|
631
630
|
def eval_og_insert(klass)
|
632
631
|
pk = klass.pk_symbol
|
633
|
-
props = klass.properties.dup
|
632
|
+
props = klass.properties.values.dup
|
634
633
|
values = props.collect { |p| write_prop(p) }.join(',')
|
635
634
|
|
636
|
-
if klass.
|
637
|
-
props << Property.new(:ogtype, String)
|
635
|
+
if klass.ann.this[:superclass] or klass.ann.this[:subclasses]
|
636
|
+
props << Property.new(:symbol => :ogtype, :klass => String)
|
638
637
|
values << ", '#{klass}'"
|
639
638
|
end
|
640
639
|
|
@@ -653,7 +652,7 @@ private
|
|
653
652
|
|
654
653
|
def eval_og_update(klass)
|
655
654
|
pk = klass.pk_symbol
|
656
|
-
props = klass.properties.reject { |p| pk == p.symbol }
|
655
|
+
props = klass.properties.values.reject { |p| pk == p.symbol }
|
657
656
|
|
658
657
|
updates = props.collect { |p|
|
659
658
|
"#{field_for_property(p)}=#{write_prop(p)}"
|
@@ -680,14 +679,14 @@ private
|
|
680
679
|
|
681
680
|
def eval_og_read(klass)
|
682
681
|
code = []
|
683
|
-
props = klass.properties
|
682
|
+
props = klass.properties.values
|
684
683
|
field_map = create_field_map(klass)
|
685
684
|
|
686
|
-
|
687
|
-
f = field_for_property(p).
|
685
|
+
for p in props
|
686
|
+
f = field_for_property(p).to_sym
|
688
687
|
|
689
688
|
if col = field_map[f]
|
690
|
-
code << "@#{p
|
689
|
+
code << "@#{p} = #{read_prop(p, col)}"
|
691
690
|
end
|
692
691
|
end
|
693
692
|
|
@@ -713,8 +712,8 @@ private
|
|
713
712
|
pk ||= @#{klass.pk_symbol}
|
714
713
|
transaction do |tx|
|
715
714
|
tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}"
|
716
|
-
if cascade and #{klass}.
|
717
|
-
#{klass}.
|
715
|
+
if cascade and #{klass}.ann.this[:descendants]
|
716
|
+
#{klass}.ann.this.descendants.each do |dclass, foreign_key|
|
718
717
|
tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}"
|
719
718
|
end
|
720
719
|
end
|
@@ -732,7 +731,9 @@ private
|
|
732
731
|
def og_create_schema(store)
|
733
732
|
if Og.create_schema
|
734
733
|
#{Aspects.gen_advice_code(:og_create_schema, klass.advices, :pre) if klass.respond_to?(:advices)}
|
735
|
-
|
734
|
+
unless self.class.superclass.ancestors.include? SchemaInheritanceBase
|
735
|
+
store.send(:create_table, #{klass})
|
736
|
+
end
|
736
737
|
#{Aspects.gen_advice_code(:og_create_schema, klass.advices, :post) if klass.respond_to?(:advices)}
|
737
738
|
end
|
738
739
|
end
|
@@ -743,7 +744,7 @@ private
|
|
743
744
|
# STI parent classes it reads the class from the resultset.
|
744
745
|
|
745
746
|
def eval_og_allocate(klass)
|
746
|
-
if klass.
|
747
|
+
if klass.schema_inheritance?
|
747
748
|
klass.module_eval %{
|
748
749
|
def self.og_allocate(res, row = 0)
|
749
750
|
Object.constant(res[0]).allocate
|
@@ -784,14 +785,14 @@ private
|
|
784
785
|
if rel = klass.relation(name)
|
785
786
|
target_table = rel[:target_class]::OGTABLE
|
786
787
|
tables << target_table
|
787
|
-
|
788
|
+
|
788
789
|
if rel.is_a?(JoinsMany)
|
789
790
|
tables << rel[:join_table]
|
790
791
|
owner_key, target_key = klass.ogmanager.store.join_table_keys(klass, rel[:target_class])
|
791
|
-
join_conditions << "#{rel
|
792
|
-
#{rel
|
792
|
+
join_conditions << "#{rel.join_table}.#{owner_key}=#{klass::OGTABLE}.#{rel.owner_class.primary_key} AND \
|
793
|
+
#{rel.join_table}.#{target_key}=#{rel.target_class::OGTABLE}.#{rel.target_class.primary_key}"
|
793
794
|
else
|
794
|
-
join_conditions << "#{klass::OGTABLE}.#{rel
|
795
|
+
join_conditions << "#{klass::OGTABLE}.#{rel.foreign_key}=#{target_table}.#{rel.target_class.primary_key}"
|
795
796
|
end
|
796
797
|
else
|
797
798
|
raise 'Unknown relation name'
|
@@ -826,7 +827,7 @@ private
|
|
826
827
|
sql << " ORDER BY #{order}"
|
827
828
|
end
|
828
829
|
|
829
|
-
resolve_limit_options(options, sql)
|
830
|
+
resolve_limit_options(options, sql)
|
830
831
|
|
831
832
|
if extra = options[:extra]
|
832
833
|
sql << " #{extra}"
|
@@ -834,20 +835,20 @@ private
|
|
834
835
|
|
835
836
|
return sql
|
836
837
|
end
|
837
|
-
|
838
|
+
|
838
839
|
# Subclasses can override this if they need some other order.
|
839
840
|
# This is needed because different backends require different
|
840
841
|
# order of the keywords.
|
841
|
-
|
842
|
-
def resolve_limit_options(options, sql)
|
843
|
-
if limit = options[:limit]
|
844
|
-
sql << " LIMIT #{limit}"
|
845
|
-
|
846
|
-
if offset = options[:offset]
|
847
|
-
sql << " OFFSET #{offset}"
|
848
|
-
end
|
849
|
-
end
|
850
|
-
end
|
842
|
+
|
843
|
+
def resolve_limit_options(options, sql)
|
844
|
+
if limit = options[:limit]
|
845
|
+
sql << " LIMIT #{limit}"
|
846
|
+
|
847
|
+
if offset = options[:offset]
|
848
|
+
sql << " OFFSET #{offset}"
|
849
|
+
end
|
850
|
+
end
|
851
|
+
end
|
851
852
|
|
852
853
|
# :section: Deserialization methods.
|
853
854
|
|