og 0.5.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/AUTHORS +19 -0
- data/LICENSE +32 -0
- data/README.og +104 -0
- data/RELEASES.og +23 -0
- data/examples/og/README +4 -0
- data/examples/og/run.rb +251 -0
- data/lib/glue.rb +52 -0
- data/lib/glue/array.rb +84 -0
- data/lib/glue/cache.rb +140 -0
- data/lib/glue/hash.rb +143 -0
- data/lib/glue/inflector.rb +91 -0
- data/lib/glue/logger.rb +51 -0
- data/lib/glue/macro.rb +56 -0
- data/lib/glue/mixins.rb +45 -0
- data/lib/glue/number.rb +30 -0
- data/lib/glue/pool.rb +63 -0
- data/lib/glue/property.rb +301 -0
- data/lib/glue/string.rb +224 -0
- data/lib/glue/time.rb +93 -0
- data/lib/og.rb +411 -0
- data/lib/og/backend.rb +258 -0
- data/lib/og/backends/mysql.rb +360 -0
- data/lib/og/backends/psql.rb +359 -0
- data/lib/og/connection.rb +265 -0
- data/lib/og/meta.rb +139 -0
- data/lib/og/version.rb +8 -0
- data/test/tc_og.rb +179 -0
- metadata +71 -0
@@ -0,0 +1,359 @@
|
|
1
|
+
# code:
|
2
|
+
# * George Moschovitis <gm@navel.gr>
|
3
|
+
#
|
4
|
+
# (c) 2004 Navel, all rights reserved.
|
5
|
+
# $Id: psql.rb 159 2004-11-18 10:18:30Z gmosx $
|
6
|
+
|
7
|
+
require "postgres"
|
8
|
+
|
9
|
+
require "og/backend"
|
10
|
+
|
11
|
+
module Og
|
12
|
+
|
13
|
+
# = Utils
|
14
|
+
#
|
15
|
+
# A collection of useful utilities.
|
16
|
+
#
|
17
|
+
module Utils
|
18
|
+
|
19
|
+
# Escape an SQL string
|
20
|
+
#
|
21
|
+
def self.escape(str)
|
22
|
+
return nil unless str
|
23
|
+
return PGconn.escape(str)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convert a ruby time to an sql timestamp.
|
27
|
+
# TODO: Optimize this
|
28
|
+
#
|
29
|
+
def self.timestamp(time = Time.now)
|
30
|
+
return nil unless time
|
31
|
+
return time.strftime("%Y-%m-%d %H:%M:%S")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Output YYY-mm-dd
|
35
|
+
# TODO: Optimize this
|
36
|
+
#
|
37
|
+
def self.date(date)
|
38
|
+
return nil unless date
|
39
|
+
return "#{date.year}-#{date.month}-#{date.mday}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Parse sql datetime
|
43
|
+
# TODO: Optimize this
|
44
|
+
#
|
45
|
+
def self.parse_timestamp(str)
|
46
|
+
return Time.parse(str)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Input YYYY-mm-dd
|
50
|
+
# TODO: Optimize this
|
51
|
+
#
|
52
|
+
def self.parse_date(str)
|
53
|
+
return nil unless str
|
54
|
+
return Date.strptime(str)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return an sql string evaluator for the property.
|
58
|
+
# No need to optimize this, used only to precalculate code.
|
59
|
+
# YAML is used to store general Ruby objects to be more
|
60
|
+
# portable.
|
61
|
+
#
|
62
|
+
# FIXME: add extra handling for float.
|
63
|
+
#
|
64
|
+
def self.write_prop(p)
|
65
|
+
if p.klass.ancestors.include?(Integer)
|
66
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
67
|
+
elsif p.klass.ancestors.include?(Float)
|
68
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
69
|
+
elsif p.klass.ancestors.include?(String)
|
70
|
+
return "'#\{Og::Utils.escape(@#{p.symbol})\}'"
|
71
|
+
elsif p.klass.ancestors.include?(Time)
|
72
|
+
return %|#\{@#{p.symbol} ? "'#\{Og::Utils.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
73
|
+
elsif p.klass.ancestors.include?(Date)
|
74
|
+
return %|#\{@#{p.symbol} ? "'#\{Og::Utils.date(@#{p.symbol})\}'" : 'NULL'\}|
|
75
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
76
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
77
|
+
else
|
78
|
+
return %|#\{@#{p.symbol} ? "'#\{Og::Utils.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return an evaluator for reading the property.
|
83
|
+
# No need to optimize this, used only to precalculate code.
|
84
|
+
#
|
85
|
+
def self.read_prop(p, idx)
|
86
|
+
if p.klass.ancestors.include?(Integer)
|
87
|
+
return "res.getvalue(tuple, #{idx}).to_i()"
|
88
|
+
elsif p.klass.ancestors.include?(Float)
|
89
|
+
return "res.getvalue(tuple, #{idx}).to_f()"
|
90
|
+
elsif p.klass.ancestors.include?(String)
|
91
|
+
return "res.getvalue(tuple, #{idx})"
|
92
|
+
elsif p.klass.ancestors.include?(Time)
|
93
|
+
return "Og::Utils.parse_timestamp(res.getvalue(tuple, #{idx}))"
|
94
|
+
elsif p.klass.ancestors.include?(Date)
|
95
|
+
return "Og::Utils.parse_date(res.getvalue(tuple, #{idx}))"
|
96
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
97
|
+
return "('true' == res.getvalue(tuple, #{idx}))"
|
98
|
+
else
|
99
|
+
return "YAML::load(res.getvalue(tuple, #{idx}))"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns the code that actually inserts the object into the
|
104
|
+
# database. Returns the code as String.
|
105
|
+
#
|
106
|
+
def self.insert_code(klass, sql, pre_cb, post_cb)
|
107
|
+
%{
|
108
|
+
#{pre_cb}
|
109
|
+
res = conn.db.query("SELECT nextval('#{klass::DBSEQ}')")
|
110
|
+
@oid = res.getvalue(0, 0).to_i
|
111
|
+
conn.exec "#{sql}"
|
112
|
+
#{post_cb}
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
# generate the mapping of the database fields to the
|
117
|
+
# object properties.
|
118
|
+
#
|
119
|
+
def self.calc_field_index(klass, og)
|
120
|
+
res = og.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
|
121
|
+
meta = og.managed_classes[klass]
|
122
|
+
|
123
|
+
for field in res.fields
|
124
|
+
meta.field_index[field] = res.fieldnum(field)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Generate the property for oid
|
129
|
+
#
|
130
|
+
def self.eval_og_oid(klass)
|
131
|
+
klass.class_eval %{
|
132
|
+
prop_accessor :oid, Fixnum, :sql => "integer PRIMARY KEY"
|
133
|
+
}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# = PsqlBackend
|
138
|
+
#
|
139
|
+
# Implements a PostgreSQL powered backend.
|
140
|
+
#
|
141
|
+
class PsqlBackend < Og::Backend
|
142
|
+
|
143
|
+
# A mapping between Ruby and SQL types.
|
144
|
+
#
|
145
|
+
TYPEMAP = {
|
146
|
+
Integer => "integer",
|
147
|
+
Fixnum => "integer",
|
148
|
+
Float => "float",
|
149
|
+
String => "text",
|
150
|
+
Time => "timestamp",
|
151
|
+
Date => "date",
|
152
|
+
TrueClass => "boolean",
|
153
|
+
Object => "text",
|
154
|
+
Array => "text",
|
155
|
+
Hash => "text"
|
156
|
+
}
|
157
|
+
|
158
|
+
# Intitialize the connection to the RDBMS.
|
159
|
+
#
|
160
|
+
def initialize(config)
|
161
|
+
begin
|
162
|
+
@conn = PGconn.connect(nil, nil, nil, nil, config[:database],
|
163
|
+
config[:user], config[:password])
|
164
|
+
rescue => ex
|
165
|
+
# gmosx: any idea how to better test this?
|
166
|
+
if ex.to_s =~ /database .* does not exist/i
|
167
|
+
$log.info "Database '#{config[:database]}' not found!"
|
168
|
+
PsqlBackend.create_db(config[:database], config[:user])
|
169
|
+
retry
|
170
|
+
end
|
171
|
+
raise
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Create the database.
|
176
|
+
#
|
177
|
+
def self.create_db(database, user = nil, password = nil)
|
178
|
+
$log.info "Creating database '#{database}'."
|
179
|
+
`createdb #{database} -U #{user}`
|
180
|
+
end
|
181
|
+
|
182
|
+
# Drop the database.
|
183
|
+
#
|
184
|
+
def self.drop_db(database, user = nil, password = nil)
|
185
|
+
$log.info "Dropping database '#{database}'."
|
186
|
+
`dropdb #{database} -U #{user}`
|
187
|
+
end
|
188
|
+
|
189
|
+
# Execute an SQL query and return the result
|
190
|
+
#
|
191
|
+
def query(sql)
|
192
|
+
$log.debug sql if $DBG
|
193
|
+
return @conn.exec(sql)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Execute an SQL query, no result returned.
|
197
|
+
#
|
198
|
+
def exec(sql)
|
199
|
+
$log.debug sql if $DBG
|
200
|
+
res = @conn.exec(sql)
|
201
|
+
res.clear()
|
202
|
+
end
|
203
|
+
|
204
|
+
# Execute an SQL query and return the result. Wrapped in a rescue
|
205
|
+
# block.
|
206
|
+
#
|
207
|
+
def safe_query(sql)
|
208
|
+
$log.debug sql if $DBG
|
209
|
+
begin
|
210
|
+
return @conn.exec(sql)
|
211
|
+
rescue => ex
|
212
|
+
$log.error "DB error #{ex}, [#{sql}]"
|
213
|
+
$log.error ex.backtrace
|
214
|
+
return nil
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Execute an SQL query, no result returned. Wrapped in a rescue
|
219
|
+
# block.
|
220
|
+
#
|
221
|
+
def safe_exec(sql)
|
222
|
+
$log.debug sql if $DBG
|
223
|
+
begin
|
224
|
+
res = @conn.exec(sql)
|
225
|
+
res.clear()
|
226
|
+
rescue => ex
|
227
|
+
$log.error "DB error #{ex}, [#{sql}]"
|
228
|
+
$log.error ex.backtrace
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Check if it is a valid resultset.
|
233
|
+
#
|
234
|
+
def valid?(res)
|
235
|
+
return !(res.nil? or 0 == res.num_tuples)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Create the managed object table. The properties of the
|
239
|
+
# object are mapped to the table columns. Additional sql relations
|
240
|
+
# and constrains are created (indicices, sequences, etc).
|
241
|
+
#
|
242
|
+
def create_table(klass)
|
243
|
+
fields = []
|
244
|
+
|
245
|
+
klass.__props.each do |p|
|
246
|
+
klass.sql_index(p.symbol) if p.meta[:sql_index]
|
247
|
+
|
248
|
+
field = "#{p.symbol}"
|
249
|
+
|
250
|
+
if p.meta and p.meta[:sql]
|
251
|
+
field << " #{p.meta[:sql]}"
|
252
|
+
else
|
253
|
+
field << " #{TYPEMAP[p.klass]}"
|
254
|
+
end
|
255
|
+
|
256
|
+
fields << field
|
257
|
+
end
|
258
|
+
|
259
|
+
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
260
|
+
|
261
|
+
# Create table constrains
|
262
|
+
|
263
|
+
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
264
|
+
sql << ", #{constrains.join(', ')}"
|
265
|
+
end
|
266
|
+
|
267
|
+
sql << ") WITHOUT OIDS;"
|
268
|
+
|
269
|
+
# Create indices
|
270
|
+
|
271
|
+
if klass.__meta
|
272
|
+
for data in klass.__meta[:sql_index]
|
273
|
+
idx, options = *data
|
274
|
+
idx = idx.to_s
|
275
|
+
pre_sql, post_sql = options[:pre], options[:post]
|
276
|
+
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
277
|
+
sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
begin
|
282
|
+
exec(sql)
|
283
|
+
$log.info "Created table '#{klass::DBTABLE}'."
|
284
|
+
rescue => ex
|
285
|
+
# gmosx: any idea how to better test this?
|
286
|
+
if ex.to_s =~ /relation .* already exists/i
|
287
|
+
$log.debug "Table already exists" if $DBG
|
288
|
+
else
|
289
|
+
raise
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# create the sequence for this table. Even if the table
|
294
|
+
# uses the oids_seq, attempt to create it. This makes
|
295
|
+
# the system more fault tolerant.
|
296
|
+
begin
|
297
|
+
exec "CREATE SEQUENCE #{klass::DBSEQ}"
|
298
|
+
$log.info "Created sequence '#{klass::DBSEQ}'."
|
299
|
+
rescue => ex
|
300
|
+
# gmosx: any idea how to better test this?
|
301
|
+
if ex.to_s =~ /relation .* already exists/i
|
302
|
+
$log.debug "Sequence already exists" if $DBG
|
303
|
+
else
|
304
|
+
raise
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# Drop the managed object table
|
310
|
+
#
|
311
|
+
def drop_table(klass)
|
312
|
+
super
|
313
|
+
exec "DROP SEQUENCE #{klass::DBSEQ}"
|
314
|
+
end
|
315
|
+
|
316
|
+
# Deserialize one row of the resultset.
|
317
|
+
#
|
318
|
+
def deserialize_one(res, klass)
|
319
|
+
return nil unless valid?(res)
|
320
|
+
|
321
|
+
# gmosx: Managed objects should have no params constructor.
|
322
|
+
entity = klass.new()
|
323
|
+
entity.og_deserialize(res, 0)
|
324
|
+
|
325
|
+
# get_join_fields(res, 0, entity, join_fields) if join_fields
|
326
|
+
|
327
|
+
res.clear()
|
328
|
+
return entity
|
329
|
+
end
|
330
|
+
|
331
|
+
# Deserialize all rows of the resultset.
|
332
|
+
#
|
333
|
+
def deserialize_all(res, klass)
|
334
|
+
return nil unless valid?(res)
|
335
|
+
|
336
|
+
entities = []
|
337
|
+
|
338
|
+
for tuple in (0...res.num_tuples)
|
339
|
+
entity = klass.new()
|
340
|
+
entity.og_deserialize(res, tuple)
|
341
|
+
|
342
|
+
# get_join_fields(res, tuple, entity, join_fields) if join_fields
|
343
|
+
|
344
|
+
entities << entity
|
345
|
+
end
|
346
|
+
|
347
|
+
res.clear()
|
348
|
+
return entities
|
349
|
+
end
|
350
|
+
|
351
|
+
# Return a single integer value from the resultset.
|
352
|
+
#
|
353
|
+
def get_int(res, idx = 0)
|
354
|
+
return res.getvalue(0, idx).to_i
|
355
|
+
end
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
end # module
|
@@ -0,0 +1,265 @@
|
|
1
|
+
# code:
|
2
|
+
# * George Moschovitis <gm@navel.gr>
|
3
|
+
#
|
4
|
+
# (c) 2004 Navel, all rights reserved.
|
5
|
+
# $Id: connection.rb 167 2004-11-23 14:03:10Z gmosx $
|
6
|
+
|
7
|
+
module Og;
|
8
|
+
|
9
|
+
require "glue/property"
|
10
|
+
require "glue/array"
|
11
|
+
require "glue/time"
|
12
|
+
|
13
|
+
# = Connection
|
14
|
+
#
|
15
|
+
# A Connection to the Database. This file defines the skeleton
|
16
|
+
# functionality. A backend specific implementation file (driver)
|
17
|
+
# implements all methods.
|
18
|
+
#
|
19
|
+
# === Future
|
20
|
+
#
|
21
|
+
# - support caching.
|
22
|
+
# - support prepared statements.
|
23
|
+
#
|
24
|
+
class Connection
|
25
|
+
# The frontend (Og) contains useful strucutres.
|
26
|
+
attr_reader :og
|
27
|
+
|
28
|
+
# The backend
|
29
|
+
attr_reader :db
|
30
|
+
|
31
|
+
# If set to true, the select methods deserialize the
|
32
|
+
# resultset to create entities.
|
33
|
+
attr_accessor :deserialize
|
34
|
+
|
35
|
+
# Initialize a connection to the database
|
36
|
+
#
|
37
|
+
def initialize(og)
|
38
|
+
@og = og
|
39
|
+
@db = @og.config[:backend].new(@og.config)
|
40
|
+
@deserialize = true
|
41
|
+
$log.debug "Created DB connection."
|
42
|
+
end
|
43
|
+
|
44
|
+
# Close the connection to the database
|
45
|
+
#
|
46
|
+
def close()
|
47
|
+
@db.close()
|
48
|
+
$log.debug "Closed DB connection."
|
49
|
+
end
|
50
|
+
|
51
|
+
# Save an object to the database. Insert if this is a new object or
|
52
|
+
# update if this is already stored in the database.
|
53
|
+
#
|
54
|
+
def save(obj)
|
55
|
+
if obj.oid
|
56
|
+
# object allready inserted, update!
|
57
|
+
obj.og_update(self)
|
58
|
+
else
|
59
|
+
# not in the database, insert!
|
60
|
+
obj.og_insert(self)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
alias_method :<<, :save
|
64
|
+
alias_method :put, :save
|
65
|
+
|
66
|
+
# Force insertion of managed object.
|
67
|
+
#
|
68
|
+
def insert(obj)
|
69
|
+
obj.og_insert(self)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Force update of managed object.
|
73
|
+
#
|
74
|
+
def update(obj)
|
75
|
+
obj.og_update(self)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Update only specific fields of the managed object.
|
79
|
+
#
|
80
|
+
# Input:
|
81
|
+
# sql = the sql code to updated the properties.
|
82
|
+
#
|
83
|
+
# WARNING: the object in memoryis not updated.
|
84
|
+
#--
|
85
|
+
# TODO: should update the object in memory.
|
86
|
+
#++
|
87
|
+
#
|
88
|
+
def update_properties(update_sql, obj_or_oid, klass = nil)
|
89
|
+
oid = obj_or_oid.to_i
|
90
|
+
klass = obj_or_oid.class unless klass
|
91
|
+
|
92
|
+
exec "UPDATE #{klass::DBTABLE} SET #{update_sql} WHERE oid=#{oid}"
|
93
|
+
end
|
94
|
+
alias_method :pupdate, :update_properties
|
95
|
+
|
96
|
+
# Load an object from the database.
|
97
|
+
#
|
98
|
+
# Input:
|
99
|
+
# oid = the object oid, OR the object name.
|
100
|
+
#
|
101
|
+
def load(oid, klass)
|
102
|
+
if oid.to_i > 0 # a valid Fixnum ?
|
103
|
+
load_by_oid(oid, klass)
|
104
|
+
else
|
105
|
+
load_by_name(oid, klass)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
alias_method :get, :load
|
109
|
+
|
110
|
+
# Load an object by oid.
|
111
|
+
#
|
112
|
+
def load_by_oid(oid, klass)
|
113
|
+
res = query "SELECT * FROM #{klass::DBTABLE} WHERE oid=#{oid}"
|
114
|
+
@deserialize? @db.deserialize_one(res, klass) : res
|
115
|
+
end
|
116
|
+
alias_method :get_by_oid, :load_by_oid
|
117
|
+
|
118
|
+
# Load an object by name.
|
119
|
+
#
|
120
|
+
def load_by_name(name, klass)
|
121
|
+
res = query "SELECT * FROM #{klass::DBTABLE} WHERE name='#{name}'"
|
122
|
+
@deserialize? @db.deserialize_one(res, klass) : res
|
123
|
+
end
|
124
|
+
alias_method :get_by_name, :load_by_name
|
125
|
+
|
126
|
+
# Load all objects of the given klass.
|
127
|
+
# Used to be called 'collect' in an earlier version.
|
128
|
+
#
|
129
|
+
def load_all(klass, extrasql = nil)
|
130
|
+
res = query "SELECT * FROM #{klass::DBTABLE} #{extrasql}"
|
131
|
+
@deserialize? @db.deserialize_all(res, klass) : res
|
132
|
+
end
|
133
|
+
alias_method :get_all, :load_all
|
134
|
+
|
135
|
+
# Perform a standard SQL query to the database. Deserializes the
|
136
|
+
# results.
|
137
|
+
#
|
138
|
+
def select(sql, klass)
|
139
|
+
unless sql =~ /SELECT/i
|
140
|
+
sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
|
141
|
+
end
|
142
|
+
|
143
|
+
res = @db.safe_query(sql)
|
144
|
+
@deserialize? @db.deserialize_all(res, klass) : res
|
145
|
+
end
|
146
|
+
|
147
|
+
# Optimized for one result.
|
148
|
+
#
|
149
|
+
def select_one(sql, klass)
|
150
|
+
unless sql =~ /SELECT/i
|
151
|
+
sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
|
152
|
+
end
|
153
|
+
|
154
|
+
res = @db.safe_query(sql)
|
155
|
+
@deserialize? @db.deserialize_one(res, klass) : res
|
156
|
+
end
|
157
|
+
|
158
|
+
# Perform a count query.
|
159
|
+
#
|
160
|
+
def count(sql, klass = nil)
|
161
|
+
unless sql =~ /SELECT/i
|
162
|
+
sql = "SELECT COUNT(*) FROM #{klass::DBTABLE} WHERE #{sql}"
|
163
|
+
end
|
164
|
+
|
165
|
+
res = @db.safe_query(sql)
|
166
|
+
|
167
|
+
return @db.get_int(res)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Delete an object from the database. Allways perform a deep delete.
|
171
|
+
#
|
172
|
+
# No need to optimize here with pregenerated code. Deletes are
|
173
|
+
# not used as much as reads or writes.
|
174
|
+
#
|
175
|
+
# === Input:
|
176
|
+
#
|
177
|
+
# obj_or_oid = Object or oid to delete.
|
178
|
+
# klass = Class of object (can be nil if an object is passed)
|
179
|
+
#
|
180
|
+
def delete(obj_or_oid, klass = nil, cascade = true)
|
181
|
+
oid = obj_or_oid.to_i
|
182
|
+
klass = obj_or_oid.class unless klass
|
183
|
+
|
184
|
+
# this is a class callback!
|
185
|
+
if klass.respond_to?(:og_pre_delete)
|
186
|
+
klass.og_pre_delete(self, oid)
|
187
|
+
end
|
188
|
+
|
189
|
+
# TODO: implement this as stored procedure? naaah.
|
190
|
+
transaction do |tx|
|
191
|
+
tx.exec "DELETE FROM #{klass::DBTABLE} WHERE oid=#{oid}"
|
192
|
+
|
193
|
+
if cascade and klass.respond_to?(:og_descendants)
|
194
|
+
klass.og_descendants.each do |dclass, linkback|
|
195
|
+
tx.exec "DELETE FROM #{dclass::DBTABLE} WHERE #{linkback}=#{oid}"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
alias_method :delete!, :delete
|
201
|
+
|
202
|
+
# Create the managed object table. The properties of the
|
203
|
+
# object are mapped to the table columns. Additional sql relations
|
204
|
+
# and constrains are created (indicices, sequences, etc).
|
205
|
+
#
|
206
|
+
def create_table(klass)
|
207
|
+
@db.create_table(klass)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Drop the managed object table.
|
211
|
+
#
|
212
|
+
def drop_table(klass)
|
213
|
+
@db.drop_table(klass)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Execute an SQL query and return the result
|
217
|
+
#
|
218
|
+
def query(sql)
|
219
|
+
@db.safe_query(sql)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Execute an SQL query, no result returned.
|
223
|
+
#
|
224
|
+
def exec(sql)
|
225
|
+
@db.safe_exec(sql)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Start a new transaction.
|
229
|
+
#
|
230
|
+
def start
|
231
|
+
@db.start()
|
232
|
+
end
|
233
|
+
|
234
|
+
# Commit a transaction.
|
235
|
+
#
|
236
|
+
def commit
|
237
|
+
@db.commit()
|
238
|
+
end
|
239
|
+
|
240
|
+
# Rollback transaction.
|
241
|
+
#
|
242
|
+
def rollback
|
243
|
+
@db.rollback()
|
244
|
+
end
|
245
|
+
|
246
|
+
# Transaction helper. In the transaction block use
|
247
|
+
# the db pointer to the backend.
|
248
|
+
#
|
249
|
+
def transaction(&block)
|
250
|
+
begin
|
251
|
+
@db.start()
|
252
|
+
yield(@db)
|
253
|
+
@db.commit()
|
254
|
+
rescue => ex
|
255
|
+
$log.error "DB Error: ERROR IN TRANSACTION"
|
256
|
+
$log.error #{ex}
|
257
|
+
$log.error #{ex.backtrace}
|
258
|
+
@db.rollback()
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
end # module
|
265
|
+
|