og 0.31.0 → 0.40.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|