og 0.29.0 → 0.30.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 +17 -28
- data/README +6 -6
- data/doc/AUTHORS +6 -0
- data/doc/RELEASES +36 -0
- data/lib/glue/hierarchical.rb +3 -4
- data/lib/glue/optimistic_locking.rb +1 -1
- data/lib/glue/orderable.rb +3 -3
- data/lib/glue/taggable.rb +29 -26
- data/lib/glue/timestamped.rb +3 -4
- data/lib/og.rb +21 -9
- data/lib/og/entity.rb +44 -2
- data/lib/og/manager.rb +9 -6
- data/lib/og/markers.rb +9 -1
- data/lib/og/relation.rb +9 -5
- data/lib/og/relation/joins_many.rb +2 -2
- data/lib/og/store/alpha/memory.rb +8 -8
- data/lib/og/store/alpha/sqlserver.rb +3 -3
- data/lib/og/store/kirby.rb +422 -279
- data/lib/og/store/mysql.rb +33 -24
- data/lib/og/store/psql.rb +28 -18
- data/lib/og/store/sql.rb +99 -33
- data/lib/og/store/sqlite.rb +13 -7
- data/lib/og/store/sqlite2.rb +14 -4
- data/test/og/CONFIG.rb +2 -2
- data/test/og/store/tc_sti.rb +41 -0
- data/test/og/tc_aggregations_calculations.rb +8 -2
- data/test/og/tc_cacheable.rb +6 -2
- data/test/og/tc_camel_case_join.rb +51 -0
- data/test/og/tc_ez.rb +30 -1
- data/test/og/tc_join.rb +73 -6
- data/test/og/tc_multi_validations.rb +1 -1
- metadata +53 -53
- data/Rakefile +0 -220
data/lib/og/manager.rb
CHANGED
@@ -140,7 +140,7 @@ class Manager
|
|
140
140
|
# Manage a class. Converts the class to an Entity.
|
141
141
|
|
142
142
|
def manage(klass)
|
143
|
-
return if managed?(klass) or
|
143
|
+
return if managed?(klass) or Og.unmanageable_classes.include?(klass)
|
144
144
|
|
145
145
|
info = EntityInfo.new(klass)
|
146
146
|
|
@@ -173,12 +173,14 @@ class Manager
|
|
173
173
|
end
|
174
174
|
|
175
175
|
# Is this class manageable by Og?
|
176
|
-
|
177
|
-
#
|
178
|
-
|
179
|
-
|
176
|
+
#
|
177
|
+
# Unmanageable classes include classes:
|
178
|
+
# * without properties
|
179
|
+
# * explicitly marked as Unmanageable (is Og::Unamanageable)
|
180
|
+
# * are polymorphic_parents (ie thay are used to spawn polymorphic relations)
|
181
|
+
|
180
182
|
def manageable?(klass)
|
181
|
-
klass.respond_to?(:properties) and (!klass.properties.empty?)
|
183
|
+
klass.respond_to?(:properties) and (!klass.properties.empty?) and (!Og.unmanageable_classes.include?(klass)) and (!klass.polymorphic_parent?)
|
182
184
|
end
|
183
185
|
|
184
186
|
# Is the class managed by Og?
|
@@ -201,6 +203,7 @@ class Manager
|
|
201
203
|
def manageable_classes
|
202
204
|
classes = []
|
203
205
|
|
206
|
+
# for c in Property.classes
|
204
207
|
ObjectSpace.each_object(Class) do |c|
|
205
208
|
if manageable?(c)
|
206
209
|
classes << c
|
data/lib/og/markers.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
module Og
|
2
2
|
|
3
|
+
# This file contains a collection of 'marker' modules. You can
|
4
|
+
# include these modules to your classes to pass hints to the
|
5
|
+
# object manager (typically Og).
|
6
|
+
|
3
7
|
# Marker module. If included in a class, the Og automanager
|
4
8
|
# ignores this class.
|
5
9
|
|
6
|
-
module Unmanageable;
|
10
|
+
module Unmanageable;
|
11
|
+
def self.included(base)
|
12
|
+
Og.unmanageable_classes << base
|
13
|
+
end
|
14
|
+
end
|
7
15
|
|
8
16
|
# This is a marker module that denotes that the
|
9
17
|
# base class follows the SingleTableInheritance
|
data/lib/og/relation.rb
CHANGED
@@ -94,7 +94,8 @@ class Relation
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def to_s
|
97
|
-
|
97
|
+
self.class.to_s.underscore
|
98
|
+
# @options[:target_name]
|
98
99
|
end
|
99
100
|
|
100
101
|
# Access the hash values as methods.
|
@@ -138,8 +139,11 @@ class Relation
|
|
138
139
|
def resolve_targets(klass)
|
139
140
|
for r in klass.relations
|
140
141
|
if r.target_class.is_a? Symbol
|
141
|
-
klass = symbol_to_class(r.target_class, r.owner_class)
|
142
|
-
|
142
|
+
if klass = symbol_to_class(r.target_class, r.owner_class)
|
143
|
+
r.options[:target_class] = klass
|
144
|
+
else
|
145
|
+
Logger.error "Cannot resolve target class '#{r.target_class}' for relation '#{r}' of class '#{r.owner_class}'!"
|
146
|
+
end
|
143
147
|
end
|
144
148
|
end
|
145
149
|
end
|
@@ -208,9 +212,9 @@ class Relation
|
|
208
212
|
|
209
213
|
unless r[target_name]
|
210
214
|
r[target_name] = if r.collection
|
211
|
-
r.target_class.to_s.demodulize.underscore.downcase.plural.
|
215
|
+
r.target_class.to_s.demodulize.underscore.downcase.plural.to_sym
|
212
216
|
else
|
213
|
-
r.target_class.to_s.demodulize.underscore.downcase.
|
217
|
+
r.target_class.to_s.demodulize.underscore.downcase.to_sym
|
214
218
|
end
|
215
219
|
end
|
216
220
|
|
@@ -31,13 +31,13 @@ class JoinsMany < Relation
|
|
31
31
|
if sclass = owner_class.ann.self[:superclass]
|
32
32
|
join_class = sclass
|
33
33
|
end
|
34
|
-
join_table_info = store.join_table_info(
|
34
|
+
join_table_info = store.join_table_info(self)
|
35
35
|
|
36
36
|
if through = self[:through]
|
37
37
|
# A custom class is used for the join. Use the class
|
38
38
|
# table and don't create a new table.
|
39
39
|
through = Relation.symbol_to_class(through, owner_class) if through.is_a?(Symbol)
|
40
|
-
join_table = self[:join_table] =
|
40
|
+
join_table = self[:join_table] = store.table(through)
|
41
41
|
else
|
42
42
|
# Calculate the name of the join table
|
43
43
|
join_table = self[:join_table] = join_table_info[:table]
|
@@ -236,10 +236,10 @@ private
|
|
236
236
|
|
237
237
|
klass.class_eval %{
|
238
238
|
def og_insert(store)
|
239
|
-
#{
|
239
|
+
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
240
240
|
@#{pk} = store.conn[#{klass}].size + 1
|
241
241
|
store.conn[#{klass}][@#{pk}] = self
|
242
|
-
#{
|
242
|
+
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
243
243
|
end
|
244
244
|
}
|
245
245
|
end
|
@@ -251,9 +251,9 @@ private
|
|
251
251
|
|
252
252
|
klass.class_eval %{
|
253
253
|
def og_update(store, options)
|
254
|
-
#{
|
254
|
+
#{::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
|
255
255
|
store.conn[#{klass}][@#{pk}] = self
|
256
|
-
#{
|
256
|
+
#{::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
|
257
257
|
end
|
258
258
|
}
|
259
259
|
end
|
@@ -264,8 +264,8 @@ private
|
|
264
264
|
def eval_og_read(klass)
|
265
265
|
klass.class_eval %{
|
266
266
|
def og_read
|
267
|
-
#{
|
268
|
-
#{
|
267
|
+
#{::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
|
268
|
+
#{::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
|
269
269
|
end
|
270
270
|
}
|
271
271
|
end
|
@@ -273,7 +273,7 @@ private
|
|
273
273
|
def eval_og_delete(klass)
|
274
274
|
klass.module_eval %{
|
275
275
|
def og_delete(store, pk, cascade = true)
|
276
|
-
#{
|
276
|
+
#{::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
|
277
277
|
pk ||= @#{klass.primary_key.first}
|
278
278
|
transaction do |tx|
|
279
279
|
tx.conn[#{klass}].delete(pk)
|
@@ -283,7 +283,7 @@ private
|
|
283
283
|
end
|
284
284
|
end
|
285
285
|
end
|
286
|
-
#{
|
286
|
+
#{::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
|
287
287
|
end
|
288
288
|
}
|
289
289
|
end
|
@@ -174,7 +174,7 @@ private
|
|
174
174
|
rescue => ex
|
175
175
|
# gmosx: any idea how to better test this?
|
176
176
|
if ex.errno == 1050 # table already exists.
|
177
|
-
Logger.debug 'Join table already exists'
|
177
|
+
Logger.debug 'Join table already exists' if $DBG
|
178
178
|
else
|
179
179
|
raise
|
180
180
|
end
|
@@ -242,11 +242,11 @@ private
|
|
242
242
|
|
243
243
|
klass.class_eval %{
|
244
244
|
def og_insert(store)
|
245
|
-
#{
|
245
|
+
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
246
246
|
store.conn.query_with_result = false
|
247
247
|
store.conn.query "#{sql}"
|
248
248
|
@#{klass.pk_symbol} = store.conn.insert_id
|
249
|
-
#{
|
249
|
+
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
250
250
|
end
|
251
251
|
}
|
252
252
|
end
|
data/lib/og/store/kirby.rb
CHANGED
@@ -10,226 +10,374 @@ require 'fileutils'
|
|
10
10
|
require 'og/store/sql'
|
11
11
|
|
12
12
|
module Og
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# for
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# Override if needed.
|
21
|
-
|
22
|
-
def self.base_dir(options)
|
23
|
-
options[:base_dir] || './kirbydb'
|
13
|
+
|
14
|
+
#Fix for schema inheritance. Instead of inferring the property on each
|
15
|
+
#access of the database we add it explicitly when the marker module
|
16
|
+
#is imported. Currently this fix is applied for this store only. The
|
17
|
+
#other stores should be similarly altered.
|
18
|
+
module SchemaInheritanceBase
|
19
|
+
property :ogtype, String
|
24
20
|
end
|
21
|
+
|
22
|
+
# A Store that persists objects into a KirbyBase database.
|
23
|
+
# To read documentation about the methods, consult the documentation
|
24
|
+
# for SqlStore and Store.
|
25
|
+
|
26
|
+
class KirbyStore < SqlStore
|
27
|
+
|
28
|
+
Text = :Text
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
# Override if needed.
|
31
|
+
|
32
|
+
def self.base_dir(options)
|
33
|
+
options[:base_dir] || './kirbydb'
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.destroy(options)
|
37
|
+
begin
|
38
|
+
FileUtils.rm_rf(base_dir(options))
|
39
|
+
super
|
40
|
+
rescue Object
|
41
|
+
Logger.info 'Cannot drop database!'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(options)
|
29
46
|
super
|
30
|
-
|
31
|
-
|
47
|
+
|
48
|
+
@typemap = {
|
49
|
+
Integer => :Integer,
|
50
|
+
Fixnum => :Integer,
|
51
|
+
Float => :Float,
|
52
|
+
String => :String,
|
53
|
+
Time => :Time,
|
54
|
+
Date => :Date,
|
55
|
+
TrueClass => :Boolean,
|
56
|
+
FalseClass => :Boolean,
|
57
|
+
Object => :Object,
|
58
|
+
Array => :String,
|
59
|
+
Hash => :String
|
60
|
+
}
|
61
|
+
|
62
|
+
mode = options[:mode] || :local
|
63
|
+
|
64
|
+
if mode == :client
|
65
|
+
# Use a client/server configuration.
|
66
|
+
@conn = KirbyBase.new(:client, options[:address], options[:port])
|
67
|
+
else
|
68
|
+
# Use an in-process configuration.
|
69
|
+
base_dir = self.class.base_dir(options)
|
70
|
+
FileUtils.mkdir_p(base_dir) unless File.exist?(base_dir)
|
71
|
+
@conn = KirbyBase.new(
|
72
|
+
:local,
|
73
|
+
nil, nil,
|
74
|
+
base_dir,
|
75
|
+
options[:ext] || '.tbl'
|
76
|
+
)
|
77
|
+
end
|
32
78
|
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def initialize(options)
|
36
|
-
super
|
37
79
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
}
|
42
|
-
|
43
|
-
mode = options[:mode] || :local
|
44
|
-
|
45
|
-
if mode == :client
|
46
|
-
# Use a client/server configuration.
|
47
|
-
@conn = KirbyBase.new(:client, options[:address], options[:port])
|
48
|
-
else
|
49
|
-
# Use an in-process configuration.
|
50
|
-
base_dir = self.class.base_dir(options)
|
51
|
-
FileUtils.mkdir_p(base_dir) unless File.exist?(base_dir)
|
52
|
-
@conn = KirbyBase.new(
|
53
|
-
:local,
|
54
|
-
nil, nil,
|
55
|
-
base_dir,
|
56
|
-
options[:ext] || '.tbl'
|
57
|
-
)
|
80
|
+
def close
|
81
|
+
# Nothing to do.
|
82
|
+
super
|
58
83
|
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def close
|
62
|
-
# Nothing to do.
|
63
|
-
super
|
64
|
-
end
|
65
|
-
|
66
|
-
def enchant(klass, manager)
|
67
|
-
klass.send :attr_accessor, :oid
|
68
|
-
klass.send :alias_method, :recno, :oid
|
69
|
-
klass.send :alias_method, :recno=, :oid=
|
70
84
|
|
71
|
-
|
85
|
+
def enchant(klass, manager)
|
86
|
+
klass.send(:attr_accessor, :oid)
|
87
|
+
klass.send(:alias_method, :recno, :oid)
|
88
|
+
klass.send(:alias_method, :recno=, :oid=)
|
89
|
+
|
90
|
+
super
|
91
|
+
end
|
72
92
|
|
73
|
-
klass
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
93
|
+
def get_table(klass)
|
94
|
+
@conn.get_table(klass.table.to_sym)
|
95
|
+
end
|
96
|
+
|
97
|
+
# :section: Lifecycle methods.
|
98
|
+
|
99
|
+
def load(pk, klass)
|
100
|
+
convert_to_instance(klass, get_table(klass)[pk.to_i])
|
101
|
+
end
|
102
|
+
|
103
|
+
#convert the given record into an instance of the given class.
|
104
|
+
#Supports schema_inheritance.
|
105
|
+
def convert_to_instance(klass, record)
|
106
|
+
return nil unless record
|
107
|
+
|
108
|
+
if(klass.schema_inheritance?)
|
109
|
+
obj = eval("#{record.ogtype}.allocate")
|
110
|
+
elsif
|
111
|
+
obj = klass.allocate
|
79
112
|
end
|
80
|
-
}
|
81
|
-
|
82
|
-
super
|
83
|
-
end
|
84
|
-
|
85
|
-
def get_table(klass)
|
86
|
-
@conn.get_table(klass.table.to_sym)
|
87
|
-
end
|
88
|
-
|
89
|
-
# :section: Lifecycle methods.
|
90
|
-
|
91
|
-
def load(pk, klass)
|
92
|
-
get_table(klass)[pk.to_i]
|
93
|
-
end
|
94
|
-
alias_method :exist?, :load
|
95
|
-
|
96
|
-
def reload(obj, pk)
|
97
|
-
raise 'Cannot reload unmanaged object' unless obj.saved?
|
98
|
-
new_obj = load(pk, obj.class)
|
99
|
-
obj.clone(new_obj)
|
100
|
-
end
|
101
|
-
|
102
|
-
def find(options)
|
103
|
-
query(options)
|
104
|
-
end
|
105
|
-
|
106
|
-
def find_one(options)
|
107
|
-
query(options).first
|
108
|
-
end
|
109
|
-
|
110
|
-
#--
|
111
|
-
# FIXME: optimize me!
|
112
|
-
#++
|
113
|
-
|
114
|
-
def count(options)
|
115
|
-
find(options).size
|
116
|
-
end
|
117
|
-
|
118
|
-
def query(options)
|
119
|
-
Logger.debug "Querying with #{options.inspect}." if $DBG
|
120
|
-
|
121
|
-
klass = options[:class]
|
122
|
-
table = get_table(klass)
|
123
113
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
objects = eval("table.select { |o| #{condition} }")
|
132
|
-
else
|
133
|
-
objects = table.select
|
114
|
+
record.each_pair do |k, v|
|
115
|
+
assign_sym = (k.to_s + '=').to_sym
|
116
|
+
if (v && obj.respond_to?(assign_sym))
|
117
|
+
obj.method(assign_sym).call(v)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
obj
|
134
121
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
122
|
+
|
123
|
+
alias_method :exist?, :load
|
124
|
+
|
125
|
+
def reload(obj, pk)
|
126
|
+
raise 'Cannot reload unmanaged object' unless obj.saved?
|
127
|
+
new_obj = load(pk, obj.class)
|
128
|
+
#TODO: replace obj with new_obj
|
142
129
|
end
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
def commit
|
154
|
-
# nop, not supported?
|
155
|
-
end
|
156
|
-
|
157
|
-
# Rollback a transaction.
|
158
|
-
|
159
|
-
def rollback
|
160
|
-
# nop, not supported?
|
161
|
-
end
|
162
|
-
|
163
|
-
def sql_update(sql)
|
164
|
-
# nop, not supported.
|
165
|
-
end
|
166
|
-
|
167
|
-
def join(obj1, obj2, table, options = nil)
|
168
|
-
first, second = join_object_ordering(obj1, obj2)
|
169
|
-
@conn.get_table(table.to_sym).insert(first.pk, second.pk)
|
170
|
-
end
|
171
|
-
|
172
|
-
def unjoin(obj1, obj2, table)
|
173
|
-
first, second = join_object_ordering(obj1, obj2)
|
174
|
-
|
175
|
-
@conn.get_table(table.to_sym).delete do |r|
|
176
|
-
require 'dev-utils/debug'
|
177
|
-
breakpoint
|
178
|
-
r.send(:first_key) == first.pk and
|
179
|
-
r.send(:second_key) == second.pk
|
130
|
+
|
131
|
+
def find(options)
|
132
|
+
#FIXME: this currently overrides options with scope; this should
|
133
|
+
#work the other way around
|
134
|
+
if scope = options[:class].get_scope
|
135
|
+
scope = scope.dup
|
136
|
+
options.update(scope)
|
137
|
+
end
|
138
|
+
query(options)
|
180
139
|
end
|
181
|
-
end
|
182
140
|
|
183
|
-
|
141
|
+
def find_one(options)
|
142
|
+
query(options).first
|
143
|
+
end
|
144
|
+
|
145
|
+
#--
|
146
|
+
# FIXME: optimize me!
|
147
|
+
#++
|
148
|
+
|
149
|
+
def count(options)
|
150
|
+
find(options).size
|
151
|
+
end
|
152
|
+
|
153
|
+
#--
|
154
|
+
# FIXME: optimize me!
|
155
|
+
#
|
156
|
+
#++
|
157
|
+
|
158
|
+
def aggregate(term = 'COUNT(*)', options = {})
|
159
|
+
case term.upcase
|
160
|
+
when /COUNT/
|
161
|
+
count(options)
|
162
|
+
when /MIN/
|
163
|
+
term =~ /MIN\((\w*)\)/
|
164
|
+
find_column_values($1, options){|result| result.min}
|
165
|
+
when /MAX/
|
166
|
+
term =~ /MAX\((\w*)\)/
|
167
|
+
find_column_values($1, options) {|result| result.max}
|
168
|
+
when /AVG/
|
169
|
+
term =~ /AVG\((\w*)\)/
|
170
|
+
values = find_column_values($1, options) do |results|
|
171
|
+
results.inject(0.0){|sum, result| sum + result} / results.size
|
172
|
+
end
|
173
|
+
when /SUM/
|
174
|
+
term =~ /SUM\((\w*)\)/
|
175
|
+
values = find_column_values($1, options) do |results|
|
176
|
+
results.inject(0.0){|sum, result| sum + result}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
alias_method :calculate, :aggregate
|
181
|
+
|
182
|
+
#find an array of values for the given column; or a hash of arrays
|
183
|
+
#if there is a group by expression. Takes an optional block
|
184
|
+
#that operates on each value in the array.
|
185
|
+
|
186
|
+
def find_column_values(column_name, options)
|
187
|
+
options[:column] = column_name
|
188
|
+
results = find(options)
|
189
|
+
if (block_given?)
|
190
|
+
if (options[:group]) #hash of arrays
|
191
|
+
results.values.collect { |group| yield group}
|
192
|
+
else
|
193
|
+
yield results
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
184
197
|
|
185
|
-
|
186
|
-
|
187
|
-
|
198
|
+
#needs refactoring, badly
|
199
|
+
def query(options)
|
200
|
+
Logger.debug "Querying with #{options.inspect}." if $DBG
|
201
|
+
|
202
|
+
klass = options[:class]
|
203
|
+
|
204
|
+
table = get_table(klass)
|
205
|
+
result_set = []
|
206
|
+
if (klass.schema_inheritance_child?)
|
207
|
+
type_condition = 'ogtype = \'' + klass.name + '\''
|
208
|
+
condition = options[:condition]
|
209
|
+
|
210
|
+
if (condition)
|
211
|
+
#if there is already an ogtype condition do nothing
|
212
|
+
if (condition !~ /ogtype/)
|
213
|
+
options[:condition] = condition + " and #{type_condition}"
|
214
|
+
end
|
215
|
+
else
|
216
|
+
options[:condition] = type_condition
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
#FIXME: add support for select statements with mathematics (tc_select)
|
221
|
+
#as well as 'from table' statements
|
222
|
+
if condition = options[:condition] || options[:where]
|
223
|
+
condition = condition.is_a?(Array) ? prepare_statement(condition) : condition
|
224
|
+
condition.gsub!(/ogtc_resolveoptions\w*\./, '')
|
225
|
+
condition.gsub!(/([^><])=/, '\1==')
|
226
|
+
condition.gsub!(/ OR /, ' or ')
|
227
|
+
condition.gsub!(/ AND /, ' and ')
|
228
|
+
condition.gsub!(/LIKE '(.*?)'/, '=~ /\1/')
|
229
|
+
condition.gsub!(/\%/, '')
|
230
|
+
condition.gsub!(/(\w*?)\s?([=><])(.)/, 'o.\1 \2\3')
|
231
|
+
condition.gsub!(/o.oid/, 'o.recno')
|
232
|
+
if(condition =~ /LIMIT (\d+)/)
|
233
|
+
condition.gsub!(/LIMIT (\d+)/, '')
|
234
|
+
options[:limit] = $1.to_i
|
235
|
+
end
|
236
|
+
result_set = eval("table.select do |o| #{condition} end")
|
237
|
+
else
|
238
|
+
result_set = table.select
|
239
|
+
end
|
240
|
+
|
241
|
+
if (order = options[:order])
|
242
|
+
order = order.to_s
|
243
|
+
desc = (order =~ /DESC/)
|
244
|
+
order = order.gsub(/DESC/, '').gsub(/ASC/, '')
|
245
|
+
eval("result_set.sort { |x, y| x.#{order} <=> y.#{order} }")
|
246
|
+
result_set.reverse! if desc
|
247
|
+
end
|
248
|
+
|
249
|
+
if (options[:limit])
|
250
|
+
limit = options[:limit]
|
251
|
+
if (result_set.size > limit)
|
252
|
+
result_set = result_set[0, limit]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
if (column = options[:column])
|
257
|
+
if (!result_set.methods.include?(column))
|
258
|
+
return []
|
259
|
+
end
|
260
|
+
begin
|
261
|
+
#if there is a group by expression on a valid column
|
262
|
+
#gather arrays of values
|
263
|
+
if ((group = options[:group]) && result_set.method(options[:group]))
|
264
|
+
#store an empty array under the key if there is no value
|
265
|
+
groups = Hash.new {|groups, key| groups[key] = []}
|
266
|
+
result_set.each do |object|
|
267
|
+
groups[object.method(group).call] << object.method(column).call
|
268
|
+
end
|
269
|
+
return groups
|
270
|
+
end
|
271
|
+
rescue NameError => error
|
272
|
+
Logger.warn 'Attempt to group by missing column:'
|
273
|
+
Logger.warn 'Column \'' + options[:group].to_s + '\' does not exist on table \'' + klass.name + '\''
|
274
|
+
raise error
|
275
|
+
end
|
276
|
+
|
277
|
+
#when called on a resultset a column method returns an array of that
|
278
|
+
#column's values
|
279
|
+
return result_set.method(column).call()
|
280
|
+
end
|
188
281
|
|
189
|
-
|
190
|
-
|
191
|
-
get_table(klass).pack # Kirby specific method of database cleanup.
|
282
|
+
result_set.map { |object| convert_to_instance(klass, object) }
|
283
|
+
end
|
192
284
|
|
193
|
-
|
194
|
-
|
285
|
+
def start
|
286
|
+
# nop
|
287
|
+
end
|
288
|
+
|
289
|
+
# Commit a transaction.
|
290
|
+
|
291
|
+
def commit
|
292
|
+
# nop, not supported?
|
293
|
+
end
|
294
|
+
|
295
|
+
# Rollback a transaction.
|
296
|
+
|
297
|
+
def rollback
|
298
|
+
# nop, not supported?
|
299
|
+
end
|
300
|
+
|
301
|
+
def sql_update(sql)
|
302
|
+
# nop, not supported.
|
303
|
+
end
|
304
|
+
|
305
|
+
#FIXME: this method relies on naming conventions and does not account
|
306
|
+
#for more than one set of '::' marks.
|
307
|
+
|
308
|
+
def join(obj1, obj2, table, options = nil)
|
309
|
+
first, second = join_object_ordering(obj1, obj2)
|
310
|
+
|
311
|
+
options[get_key(first)] = first.pk
|
312
|
+
options[get_key(second)] = second.pk
|
313
|
+
|
314
|
+
@conn.get_table(table.to_sym).insert(options)
|
315
|
+
end
|
316
|
+
|
317
|
+
#retrieve the join table key name for the object
|
318
|
+
|
319
|
+
def get_key(obj)
|
320
|
+
obj.class.name =~ /::(\w*)/
|
321
|
+
($1.downcase + '_oid').to_sym
|
322
|
+
end
|
323
|
+
|
324
|
+
def unjoin(obj1, obj2, table)
|
325
|
+
first, second = join_object_ordering(obj1, obj2)
|
326
|
+
|
327
|
+
@conn.get_table(table.to_sym).delete do |r|
|
328
|
+
r.send(get_key(first)) == first.pk and
|
329
|
+
r.send(get_key(second)) == second.pk
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
195
334
|
|
196
|
-
|
197
|
-
|
335
|
+
def typemap(key)
|
336
|
+
@typemap[key]
|
337
|
+
end
|
198
338
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
339
|
+
def create_table(klass)
|
340
|
+
if @conn.table_exists?(klass.table.to_sym)
|
341
|
+
get_table(klass).pack # Kirby specific method of database cleanup.
|
342
|
+
|
343
|
+
field_names = field_names_for_class(klass)
|
344
|
+
|
345
|
+
actual_fields = get_table(klass).field_names
|
346
|
+
|
347
|
+
field_names.each do |needed_field|
|
348
|
+
next if actual_fields.include?(needed_field)
|
349
|
+
|
350
|
+
if @options[:evolve_schema] == true
|
351
|
+
Logger.debug "Adding field '#{needed_field}' to '#{klass.table}'" if $DBG
|
352
|
+
field_type = typemap(klass.properties[needed_field].klass)
|
353
|
+
if get_table(klass).respond_to?(:add_column)
|
354
|
+
get_table(klass).add_column(needed_field, field_type)
|
355
|
+
else
|
356
|
+
@conn.add_table_column(klass.table, needed_field, field_type)
|
357
|
+
end
|
204
358
|
else
|
205
|
-
|
359
|
+
Logger.warn "Table '#{klass.table}' is missing field '#{needed_field}' and :evolve_schema is not set to true!"
|
206
360
|
end
|
207
|
-
else
|
208
|
-
Logger.warn "Table '#{klass.table}' is missing field '#{needed_field}' and :evolve_schema is not set to true!"
|
209
361
|
end
|
210
|
-
end
|
211
|
-
|
212
|
-
actual_fields.each do |obsolete_field|
|
213
|
-
next if field_names.include?(obsolete_field)
|
214
362
|
|
215
|
-
|
216
|
-
|
217
|
-
if
|
218
|
-
|
363
|
+
actual_fields.each do |obsolete_field|
|
364
|
+
next if field_names.include?(obsolete_field) || obsolete_field == :recno
|
365
|
+
if @options[:evolve_schema] == true and @options[:evolve_schema_cautious] == false
|
366
|
+
Logger.debug "Removing obsolete field '#{obsolete_field}' from '#{klass.table}'" if $DBG
|
367
|
+
if get_table(klass).respond_to?(:drop_column)
|
368
|
+
get_table(klass).drop_column(obsolete_field)
|
369
|
+
else
|
370
|
+
@conn.drop_table_column(klass.table, obsolete_field)
|
371
|
+
end
|
219
372
|
else
|
220
|
-
|
373
|
+
Logger.warn "You have an obsolete field '#{obsolete_field}' on table '#{klass.table}' and :evolve_schema is not set or is in cautious mode!"
|
221
374
|
end
|
222
|
-
else
|
223
|
-
Logger.warn "You have an obsolete field '#{obsolete_field}' on table '#{klass.table}' and :evolve_schema is not set or is in cautious mode!"
|
224
375
|
end
|
376
|
+
else
|
377
|
+
Logger.debug "Creating table '#{klass.table}'" if $DBG
|
378
|
+
fields = fields_for_class(klass)
|
379
|
+
table = @conn.create_table(klass.table.to_sym, *fields)
|
225
380
|
end
|
226
|
-
else
|
227
|
-
Logger.debug "Creating table '#{klass.table}'"
|
228
|
-
fields = fields_for_class(klass)
|
229
|
-
table = @conn.create_table(klass.table.to_sym, *fields) do |t|
|
230
|
-
t.record_class = klass
|
231
|
-
end
|
232
|
-
end
|
233
381
|
|
234
382
|
=begin
|
235
383
|
# Create join tables if needed. Join tables are used in
|
@@ -239,109 +387,104 @@ private
|
|
239
387
|
for info in join_tables
|
240
388
|
unless @conn.table_exists?(info[:table].to_sym)
|
241
389
|
@conn.create_table(info[:table].to_sym, *create_join_table_sql(info))
|
242
|
-
Logger.debug "Created jointable '#{info[:table]}'."
|
390
|
+
Logger.debug "Created jointable '#{info[:table]}'." if $DBG
|
243
391
|
end
|
244
392
|
end
|
245
393
|
end
|
246
394
|
=end
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
[join_info[:first_key].to_sym, :Integer, join_info[:second_key].to_sym, :Integer]
|
252
|
-
end
|
253
|
-
|
254
|
-
def drop_table(klass)
|
255
|
-
@conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
|
256
|
-
end
|
257
|
-
|
258
|
-
def field_names_for_class(klass)
|
259
|
-
klass.properties.values.map {|p| p.symbol }
|
260
|
-
end
|
261
|
-
|
262
|
-
def fields_for_class(klass)
|
263
|
-
fields = []
|
264
|
-
|
265
|
-
klass.properties.values.each do |p|
|
266
|
-
klass.index(p.symbol) if p.index
|
267
|
-
|
268
|
-
fields << p.symbol
|
269
|
-
|
270
|
-
type = typemap(p.klass.name.to_sym)
|
271
|
-
|
272
|
-
fields << type
|
395
|
+
end
|
396
|
+
|
397
|
+
def create_join_table_sql(join_info)
|
398
|
+
[join_info[:first_key].to_sym, :Integer, join_info[:second_key].to_sym, :Integer]
|
273
399
|
end
|
274
400
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
def eval_og_insert(klass)
|
279
|
-
pk = klass.primary_key.symbol
|
280
|
-
props = klass.properties.values.dup
|
281
|
-
values = props.collect { |p| write_prop(p) }.join(',')
|
401
|
+
def drop_table(klass)
|
402
|
+
@conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
|
403
|
+
end
|
282
404
|
|
283
|
-
|
284
|
-
|
285
|
-
values
|
405
|
+
def field_names_for_class(klass)
|
406
|
+
properties = get_properties_for_class(klass)
|
407
|
+
properties.values.map {|p| p.symbol }
|
286
408
|
end
|
287
409
|
|
288
|
-
klass
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
410
|
+
def fields_for_class(klass)
|
411
|
+
fields = []
|
412
|
+
|
413
|
+
get_properties_for_class(klass).values.each do |p|
|
414
|
+
klass.index(p.symbol) if p.index
|
415
|
+
|
416
|
+
fields << p.symbol
|
417
|
+
|
418
|
+
type = typemap(p.klass)
|
419
|
+
|
420
|
+
fields << type
|
294
421
|
end
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
422
|
+
|
423
|
+
fields
|
424
|
+
end
|
425
|
+
|
426
|
+
def eval_og_insert(klass)
|
427
|
+
pk = klass.primary_key.symbol
|
428
|
+
|
429
|
+
fields = get_properties_for_class(klass).keys
|
430
|
+
fields = fields.map { |f| ":#{f} => (self.respond_to?(:#{f}) ? self.#{f} : nil)"}
|
431
|
+
field_string = fields.join(', ')
|
432
|
+
klass.class_eval %{
|
433
|
+
def og_insert(store)
|
434
|
+
@ogtype = #{klass}
|
435
|
+
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
436
|
+
Logger.debug "Inserting \#{self}." if $DBG
|
437
|
+
@#{pk} = store.get_table(#{klass}).insert({#{field_string}})
|
438
|
+
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
439
|
+
end
|
440
|
+
}
|
441
|
+
end
|
303
442
|
|
304
|
-
|
305
|
-
def og_update(store, options = nil)
|
306
|
-
#{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
|
307
|
-
store.get_table(#{klass}).update { |r| r.recno == #{pk} }.set(#{updates.join(', ')})
|
308
|
-
#{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
|
309
|
-
end
|
310
|
-
}
|
311
|
-
end
|
443
|
+
# Compile the og_update method for the class.
|
312
444
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
#{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
|
317
|
-
#{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
|
318
|
-
end
|
319
|
-
}
|
320
|
-
end
|
445
|
+
def eval_og_update(klass)
|
446
|
+
pk = klass.pk_symbol
|
447
|
+
updates = klass.properties.keys.collect { |p| ":#{p} => @#{p}" }
|
321
448
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
449
|
+
klass.module_eval %{
|
450
|
+
def og_update(store, options = nil)
|
451
|
+
#{::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
|
452
|
+
store.get_table(#{klass}).update { |r| r.recno == #{pk} }.set(#{updates.join(', ')})
|
453
|
+
#{::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
|
454
|
+
end
|
455
|
+
}
|
456
|
+
end
|
457
|
+
|
458
|
+
def eval_og_read(klass)
|
459
|
+
klass.module_eval %{
|
460
|
+
def og_read(res, row = 0, offset = 0)
|
461
|
+
#{::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
|
462
|
+
#{::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
|
463
|
+
end
|
464
|
+
}
|
465
|
+
end
|
466
|
+
|
467
|
+
def eval_og_delete(klass)
|
468
|
+
klass.module_eval %{
|
469
|
+
def og_delete(store, pk, cascade = true)
|
470
|
+
pk ||= self.pk
|
471
|
+
pk = pk.to_i
|
472
|
+
#{::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
|
473
|
+
table = store.get_table(self.class)
|
474
|
+
transaction do |tx|
|
475
|
+
table.delete { |r| r.recno == pk }
|
476
|
+
if cascade and #{klass}.ann.self[:descendants]
|
477
|
+
#{klass}.ann.self.descendants.each do |dclass, foreign_key|
|
478
|
+
dtable = store.get_table(dclass)
|
479
|
+
dtable.delete { |r| foreign_key == pk }
|
480
|
+
end
|
335
481
|
end
|
336
482
|
end
|
483
|
+
#{::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
|
337
484
|
end
|
338
|
-
|
339
|
-
|
340
|
-
}
|
485
|
+
}
|
486
|
+
end
|
341
487
|
end
|
342
|
-
|
343
|
-
end
|
344
|
-
|
345
488
|
end
|
346
489
|
|
347
490
|
# * George Moschovitis <gm@navel.gr>
|