og 0.23.0 → 0.24.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/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
|
|