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
@@ -1,256 +0,0 @@
|
|
1
|
-
# WARNING:
|
2
|
-
# This store is not converted to the latest Og codebase.
|
3
|
-
# DO NOT USE YET!
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'dbi'
|
7
|
-
rescue Object => ex
|
8
|
-
Logger.error 'Ruby-DBI bindings not present or ADO support not available.'
|
9
|
-
Logger.error ex
|
10
|
-
end
|
11
|
-
|
12
|
-
require 'og/store/sql'
|
13
|
-
|
14
|
-
# Customize the standard SqlServer resultset to make
|
15
|
-
# more compatible with Og.
|
16
|
-
=begin
|
17
|
-
class Sqlserver::Result
|
18
|
-
def blank?
|
19
|
-
0 == num_rows
|
20
|
-
end
|
21
|
-
|
22
|
-
alias_method :next, :fetch_row
|
23
|
-
|
24
|
-
def each_row
|
25
|
-
each do |row|
|
26
|
-
yield(row, 0)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def first_value
|
31
|
-
val = fetch_row[0]
|
32
|
-
free
|
33
|
-
return val
|
34
|
-
end
|
35
|
-
|
36
|
-
alias_method :close, :free
|
37
|
-
end
|
38
|
-
=end
|
39
|
-
|
40
|
-
module Og
|
41
|
-
|
42
|
-
module SqlserverUtils
|
43
|
-
include SqlUtils
|
44
|
-
|
45
|
-
def escape(str)
|
46
|
-
return nil unless str
|
47
|
-
return Sqlserver.quote(str)
|
48
|
-
end
|
49
|
-
|
50
|
-
def quote(val)
|
51
|
-
case val
|
52
|
-
when Fixnum, Integer, Float
|
53
|
-
val ? val.to_s : 'NULL'
|
54
|
-
when String
|
55
|
-
val ? "'#{escape(val)}'" : 'NULL'
|
56
|
-
when Time
|
57
|
-
val ? "'#{timestamp(val)}'" : 'NULL'
|
58
|
-
when Date
|
59
|
-
val ? "'#{date(val)}'" : 'NULL'
|
60
|
-
when TrueClass
|
61
|
-
val ? "'1'" : 'NULL'
|
62
|
-
else
|
63
|
-
# gmosx: keep the '' for nil symbols.
|
64
|
-
val ? escape(val.to_yaml) : ''
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# A Store that persists objects into a Sqlserver database.
|
70
|
-
# To read documentation about the methods, consult the documentation
|
71
|
-
# for SqlStore and Store.
|
72
|
-
|
73
|
-
class SqlserverStore < SqlStore
|
74
|
-
extend SqlserverUtils
|
75
|
-
include SqlserverUtils
|
76
|
-
|
77
|
-
def self.create(options)
|
78
|
-
raise 'Not implemented'
|
79
|
-
end
|
80
|
-
|
81
|
-
def self.destroy(options)
|
82
|
-
raise 'Not implemented'
|
83
|
-
end
|
84
|
-
|
85
|
-
def initialize(options)
|
86
|
-
super
|
87
|
-
|
88
|
-
begin
|
89
|
-
@conn = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=#{options[:address]};Initial Catalog=#{options[:name]};User Id=#{options[:user]};Password=#{options[:password]};")
|
90
|
-
rescue => ex
|
91
|
-
# gmosx, FIXME: drak, fix this!
|
92
|
-
if ex.to_s =~ /database .* does not exist/i
|
93
|
-
Logger.info "Database '#{options[:name]}' not found!"
|
94
|
-
self.class.create(options)
|
95
|
-
retry
|
96
|
-
end
|
97
|
-
raise
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def close
|
102
|
-
@conn.disconnect
|
103
|
-
super
|
104
|
-
end
|
105
|
-
|
106
|
-
def enchant(klass, manager)
|
107
|
-
klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
|
108
|
-
super
|
109
|
-
end
|
110
|
-
|
111
|
-
def query(sql)
|
112
|
-
# Logger.debug sql if $DBG
|
113
|
-
return @conn.select_all(sql)
|
114
|
-
rescue => ex
|
115
|
-
handle_sql_exception(ex, sql)
|
116
|
-
end
|
117
|
-
|
118
|
-
def exec(sql)
|
119
|
-
# Logger.debug sql if $DBG
|
120
|
-
@conn.execute(sql).finish
|
121
|
-
rescue => ex
|
122
|
-
handle_sql_exception(ex, sql)
|
123
|
-
end
|
124
|
-
|
125
|
-
private
|
126
|
-
|
127
|
-
def create_table(klass)
|
128
|
-
fields = fields_for_class(klass)
|
129
|
-
|
130
|
-
sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
|
131
|
-
|
132
|
-
# Create table constraints.
|
133
|
-
|
134
|
-
if klass.__meta and constraints = klass.__meta[:sql_constraint]
|
135
|
-
sql << ", #{constraints.join(', ')}"
|
136
|
-
end
|
137
|
-
|
138
|
-
sql << ");"
|
139
|
-
|
140
|
-
# Create indices.
|
141
|
-
|
142
|
-
if klass.__meta and indices = klass.__meta[:index]
|
143
|
-
for data in indices
|
144
|
-
idx, options = *data
|
145
|
-
idx = idx.to_s
|
146
|
-
pre_sql, post_sql = options[:pre], options[:post]
|
147
|
-
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
148
|
-
sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
conn.query_with_result = false
|
153
|
-
|
154
|
-
begin
|
155
|
-
conn.query(sql)
|
156
|
-
Logger.info "Created table '#{klass::OGTABLE}'."
|
157
|
-
rescue => ex
|
158
|
-
# gmosx: any idea how to better test this?
|
159
|
-
if ex.errno == 1050 # table already exists.
|
160
|
-
Logger.debug 'Table already exists' if $DBG
|
161
|
-
return
|
162
|
-
else
|
163
|
-
raise
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# Create join tables if needed. Join tables are used in
|
168
|
-
# 'many_to_many' relations.
|
169
|
-
|
170
|
-
if klass.__meta and join_tables = klass.__meta[:join_tables]
|
171
|
-
for join_table in join_tables
|
172
|
-
begin
|
173
|
-
conn.query(create_join_table_sql(join_table))
|
174
|
-
rescue => ex
|
175
|
-
# gmosx: any idea how to better test this?
|
176
|
-
if ex.errno == 1050 # table already exists.
|
177
|
-
Logger.debug 'Join table already exists' if $DBG
|
178
|
-
else
|
179
|
-
raise
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
def create_field_map(klass)
|
187
|
-
conn.query_with_result = true
|
188
|
-
res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
|
189
|
-
map = {}
|
190
|
-
|
191
|
-
res.num_fields.times do |i|
|
192
|
-
map[res.fetch_field.name.intern] = i
|
193
|
-
end
|
194
|
-
|
195
|
-
return map
|
196
|
-
ensure
|
197
|
-
res.close if res
|
198
|
-
end
|
199
|
-
|
200
|
-
def write_prop(p)
|
201
|
-
if p.klass.ancestors.include?(Integer)
|
202
|
-
return "#\{@#{p.symbol} || 'NULL'\}"
|
203
|
-
elsif p.klass.ancestors.include?(Float)
|
204
|
-
return "#\{@#{p.symbol} || 'NULL'\}"
|
205
|
-
elsif p.klass.ancestors.include?(String)
|
206
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
|
207
|
-
elsif p.klass.ancestors.include?(Time)
|
208
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
209
|
-
elsif p.klass.ancestors.include?(Date)
|
210
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
|
211
|
-
elsif p.klass.ancestors.include?(TrueClass)
|
212
|
-
return "#\{@#{p.symbol} ? \"'1'\" : 'NULL' \}"
|
213
|
-
else
|
214
|
-
# gmosx: keep the '' for nil symbols.
|
215
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
def read_prop(p, col)
|
220
|
-
if p.klass.ancestors.include?(Integer)
|
221
|
-
return "res[#{col} + offset].to_i"
|
222
|
-
elsif p.klass.ancestors.include?(Float)
|
223
|
-
return "res[#{col} + offset].to_f"
|
224
|
-
elsif p.klass.ancestors.include?(String)
|
225
|
-
return "res[#{col} + offset]"
|
226
|
-
elsif p.klass.ancestors.include?(Time)
|
227
|
-
return "#{self.class}.parse_timestamp(res[#{col} + offset])"
|
228
|
-
elsif p.klass.ancestors.include?(Date)
|
229
|
-
return "#{self.class}.parse_date(res[#{col} + offset])"
|
230
|
-
elsif p.klass.ancestors.include?(TrueClass)
|
231
|
-
return "('0' != res[#{col} + offset])"
|
232
|
-
else
|
233
|
-
return "YAML.load(res[#{col} + offset])"
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
def eval_og_insert(klass)
|
238
|
-
props = klass.properties
|
239
|
-
values = props.collect { |p| write_prop(p) }.join(',')
|
240
|
-
|
241
|
-
sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
|
242
|
-
|
243
|
-
klass.class_eval %{
|
244
|
-
def og_insert(store)
|
245
|
-
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
246
|
-
store.conn.query_with_result = false
|
247
|
-
store.conn.query "#{sql}"
|
248
|
-
@#{klass.pk_symbol} = store.conn.insert_id
|
249
|
-
#{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
250
|
-
end
|
251
|
-
}
|
252
|
-
end
|
253
|
-
|
254
|
-
end
|
255
|
-
|
256
|
-
end
|
data/lib/og/store/kirby.rb
DELETED
@@ -1,490 +0,0 @@
|
|
1
|
-
begin
|
2
|
-
require 'kirbybase'
|
3
|
-
rescue Object => ex
|
4
|
-
Logger.error "KirbyBase is not installed. Please run 'gem install KirbyBase'"
|
5
|
-
Logger.error ex
|
6
|
-
end
|
7
|
-
|
8
|
-
require 'fileutils'
|
9
|
-
|
10
|
-
require 'og/store/sql'
|
11
|
-
|
12
|
-
module Og
|
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
|
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
|
29
|
-
|
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)
|
46
|
-
super
|
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
|
78
|
-
end
|
79
|
-
|
80
|
-
def close
|
81
|
-
# Nothing to do.
|
82
|
-
super
|
83
|
-
end
|
84
|
-
|
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
|
92
|
-
|
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
|
112
|
-
end
|
113
|
-
|
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
|
121
|
-
end
|
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
|
129
|
-
end
|
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)
|
139
|
-
end
|
140
|
-
|
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
|
197
|
-
|
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
|
281
|
-
|
282
|
-
result_set.map { |object| convert_to_instance(klass, object) }
|
283
|
-
end
|
284
|
-
|
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
|
334
|
-
|
335
|
-
def typemap(key)
|
336
|
-
@typemap[key]
|
337
|
-
end
|
338
|
-
|
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
|
358
|
-
else
|
359
|
-
Logger.warn "Table '#{klass.table}' is missing field '#{needed_field}' and :evolve_schema is not set to true!"
|
360
|
-
end
|
361
|
-
end
|
362
|
-
|
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
|
372
|
-
else
|
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!"
|
374
|
-
end
|
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)
|
380
|
-
end
|
381
|
-
|
382
|
-
=begin
|
383
|
-
# Create join tables if needed. Join tables are used in
|
384
|
-
# 'many_to_many' relations.
|
385
|
-
|
386
|
-
if join_tables = klass.ann.self[:join_tables]
|
387
|
-
for info in join_tables
|
388
|
-
unless @conn.table_exists?(info[:table].to_sym)
|
389
|
-
@conn.create_table(info[:table].to_sym, *create_join_table_sql(info))
|
390
|
-
Logger.debug "Created jointable '#{info[:table]}'." if $DBG
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
=end
|
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]
|
399
|
-
end
|
400
|
-
|
401
|
-
def drop_table(klass)
|
402
|
-
@conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
|
403
|
-
end
|
404
|
-
|
405
|
-
def field_names_for_class(klass)
|
406
|
-
properties = get_properties_for_class(klass)
|
407
|
-
properties.values.map {|p| p.symbol }
|
408
|
-
end
|
409
|
-
|
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
|
421
|
-
end
|
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
|
442
|
-
|
443
|
-
# Compile the og_update method for the class.
|
444
|
-
|
445
|
-
def eval_og_update(klass)
|
446
|
-
pk = klass.pk_symbol
|
447
|
-
updates = klass.properties.keys.collect { |p| ":#{p} => @#{p}" }
|
448
|
-
|
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
|
481
|
-
end
|
482
|
-
end
|
483
|
-
#{::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
|
484
|
-
end
|
485
|
-
}
|
486
|
-
end
|
487
|
-
end
|
488
|
-
end
|
489
|
-
|
490
|
-
# * George Moschovitis <gm@navel.gr>
|