og 0.10.0 → 0.11.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.
@@ -1,13 +1,13 @@
1
1
  # * George Moschovitis <gm@navel.gr>
2
2
  # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: adapter.rb 255 2005-02-10 12:45:32Z gmosx $
3
+ # $Id: adapter.rb 266 2005-02-28 14:50:48Z gmosx $
4
4
 
5
5
  require 'yaml'
6
6
  require 'singleton'
7
7
 
8
8
  require 'og/connection'
9
9
 
10
- class Og
10
+ module Og
11
11
 
12
12
  # An adapter communicates with the backend datastore.
13
13
  # The adapters for all supported datastores extend this
@@ -74,6 +74,7 @@ class Adapter
74
74
  # TODO: Optimize this
75
75
 
76
76
  def self.parse_timestamp(str)
77
+ return nil unless str
77
78
  return Time.parse(str)
78
79
  end
79
80
 
@@ -108,7 +109,7 @@ class Adapter
108
109
  end
109
110
 
110
111
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
111
- # :section: OR mapping methods and utilities.
112
+ # :section: O->R mapping methods and utilities.
112
113
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
113
114
 
114
115
  # Encode the name of the klass as an sql safe string.
@@ -121,24 +122,40 @@ class Adapter
121
122
  end
122
123
 
123
124
  # The name of the SQL table where objects of this class
124
- # are stored.
125
+ # are stored. A prefix is needed to avoid colision with
126
+ # reserved prefices (for example User maps to user which
127
+ # is reserved in postgresql). The prefix should start
128
+ # with an alphanumeric character to be compatible with
129
+ # all RDBMS (most notable Oracle).
130
+ #
131
+ # You may want to override this method to map an existing
132
+ # database schema using Og.
125
133
 
126
134
  def self.table(klass)
127
- "_#{Og.table_prefix}#{encode(klass)}"
135
+ "og_#{Og.table_prefix}#{encode(klass)}"
128
136
  end
129
137
 
130
138
  # The name of the join table for the two given classes.
139
+ # A prefix is needed to avoid colision with reserved
140
+ # prefices (for example User maps to user which
141
+ # is reserved in postgresql). The prefix should start
142
+ # with an alphanumeric character to be compatible with
143
+ # all RDBMS (most notable Oracle).
144
+ #
145
+ # You may want to override this method to map an existing
146
+ # database schema using Og.
131
147
 
132
148
  def self.join_table(klass1, klass2)
133
- "_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
149
+ "og_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
134
150
  end
135
-
151
+
136
152
  # Return an sql string evaluator for the property.
137
153
  # No need to optimize this, used only to precalculate code.
138
154
  # YAML is used to store general Ruby objects to be more
139
155
  # portable.
140
- #
156
+ #--
141
157
  # FIXME: add extra handling for float.
158
+ #++
142
159
 
143
160
  def write_prop(p)
144
161
  if p.klass.ancestors.include?(Integer)
@@ -146,7 +163,7 @@ class Adapter
146
163
  elsif p.klass.ancestors.include?(Float)
147
164
  return "#\{@#{p.symbol} || 'NULL'\}"
148
165
  elsif p.klass.ancestors.include?(String)
149
- return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
166
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
150
167
  elsif p.klass.ancestors.include?(Time)
151
168
  return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
152
169
  elsif p.klass.ancestors.include?(Date)
@@ -154,6 +171,7 @@ class Adapter
154
171
  elsif p.klass.ancestors.include?(TrueClass)
155
172
  return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
156
173
  else
174
+ # gmosx: keep the '' for nil symbols.
157
175
  return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
158
176
  end
159
177
  end
@@ -197,9 +215,18 @@ class Adapter
197
215
  field << " #{p.meta[:sql]}"
198
216
  else
199
217
  field << " #{@typemap[p.klass]}"
200
- # attach extra sql
201
- if p.meta and extra_sql = p.meta[:extra_sql]
202
- field << " #{extra_sql}"
218
+
219
+ if p.meta
220
+ # set default value (gmosx: not that useful in the
221
+ # current implementation).
222
+ if default = p.meta[:default]
223
+ field << " DEFAULT #{default.inspect} NOT NULL"
224
+ end
225
+
226
+ # attach extra sql
227
+ if extra_sql = p.meta[:extra_sql]
228
+ field << " #{extra_sql}"
229
+ end
203
230
  end
204
231
  end
205
232
 
@@ -235,7 +262,7 @@ class Adapter
235
262
  # object properties.
236
263
 
237
264
  def calc_field_index(klass, og)
238
- raise 'Not implemented!'
265
+ # Implement if needed.
239
266
  end
240
267
 
241
268
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -0,0 +1,121 @@
1
+ # * George Moschovitis <gm@navel.gr>
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: filesys.rb 264 2005-02-23 13:46:55Z gmosx $
4
+
5
+ require 'fileutils'
6
+
7
+ require 'og/adapter'
8
+ require 'og/connection'
9
+ require 'glue/attribute'
10
+
11
+ module Og
12
+
13
+ # The Filesys adapter. This adapter stores Og objectes in
14
+ # the filesystem. This adapter is a proof of concept and
15
+ # at the moment severely limited. For extra documentation
16
+ # see lib/og/adapter.rb
17
+
18
+ class FilesysAdapter < Adapter
19
+
20
+ # Creates the name of the database root dir.
21
+
22
+ def self.dir(database)
23
+ "#{database}_db"
24
+ end
25
+
26
+ def create_db(database, user = nil, password = nil)
27
+ begin
28
+ FileUtils.mkdir_p(self.class.dir(database))
29
+ rescue
30
+ Logger.error "Cannot create '#{database}'!"
31
+ end
32
+ end
33
+
34
+ def drop_db(database, user = nil, password = nil)
35
+ begin
36
+ FileUtils.rmdir(self.class.dir(database))
37
+ super
38
+ rescue
39
+ Logger.error "Cannot drop '#{database}'!"
40
+ end
41
+ end
42
+
43
+ def insert_code(klass, db, pre_cb, post_cb)
44
+ props = props_for_insert(klass)
45
+ values = props.collect { |p| write_prop(p) }.join(',')
46
+
47
+ sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
48
+
49
+ %{
50
+ #{pre_cb}
51
+ conn.store.query("#{sql}").close
52
+ @oid = conn.store.last_insert_row_id
53
+ #{post_cb}
54
+ }
55
+ end
56
+
57
+ def new_connection(db)
58
+ return FilesysConnection.new(db)
59
+ end
60
+
61
+ # A 'table' is emulated by using a directory to store
62
+ # one file per table row. Each file contains an object
63
+ # marshaled in YAML format. A special file called
64
+ # '_sequence' stores the sequence for this 'table'.
65
+
66
+ def create_table(klass, db)
67
+ class_dir = File.join(self.class.dir(db.config[:database]), klass::DBTABLE)
68
+ FileUtils.mkdir_p(class_dir)
69
+
70
+ seq_file = File.join(class_dir, 'seq')
71
+ File.open(seq_file, 'w') { |f| f << '1' }
72
+ rescue => ex
73
+ Logger.error "Cannot create directory to store '#{klass}' classes!"
74
+ end
75
+
76
+ end
77
+
78
+ # The Filesys adapter connection.
79
+
80
+ class FilesysConnection < Connection
81
+
82
+ def initialize(db)
83
+ super
84
+ config = db.config
85
+
86
+ begin
87
+ raise unless File.directory?(FilesysAdapter.dir(config[:database]))
88
+ rescue => ex
89
+ if true # ex.to_s =~ /database .* does not exist/i
90
+ Logger.info "Database '#{config[:database]}' not found!"
91
+ @db.adapter.create_db(config[:database], config[:user])
92
+ retry
93
+ end
94
+ raise
95
+ end
96
+ end
97
+
98
+ def save(obj)
99
+ seq = nil
100
+ class_dir = File.join(FilesysAdapter.dir(@db.config[:database]), obj.class::DBTABLE)
101
+ File.open(File.join(class_dir, 'seq'), 'r') { |f| seq = f.read.to_i }
102
+ obj.oid = seq
103
+ File.open(File.join(class_dir, "#{seq}.yml"), 'w') { |f| f << obj.to_yaml }
104
+ seq += 1
105
+ File.open(File.join(class_dir, 'seq'), 'w') { |f| f << seq }
106
+ end
107
+
108
+ def load(oid, klass)
109
+ obj = nil
110
+ class_dir = File.join(FilesysAdapter.dir(@db.config[:database]), klass::DBTABLE)
111
+ File.open(File.join(class_dir, "#{oid}.yml"), 'r') { |f| obj = YAML::load(f.read) }
112
+ return obj
113
+ end
114
+
115
+ def close
116
+ Logger.debug "Closed DB connection." if $DBG
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -1,6 +1,6 @@
1
1
  # * George Moschovitis <gm@navel.gr>
2
2
  # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: mysql.rb 259 2005-02-15 08:54:54Z gmosx $
3
+ # $Id: mysql.rb 266 2005-02-28 14:50:48Z gmosx $
4
4
 
5
5
  require 'mysql'
6
6
 
@@ -8,7 +8,7 @@ require 'og/adapter'
8
8
  require 'og/connection'
9
9
  require 'glue/attribute'
10
10
 
11
- class Og
11
+ module Og
12
12
 
13
13
  # The MySQL adapter. This adapter communicates with
14
14
  # an MySQL rdbms. For extra documentation see
@@ -18,7 +18,7 @@ class MysqlAdapter < Adapter
18
18
 
19
19
  def initialize
20
20
  super
21
- @typemap.update({TrueClass => 'tinyint'})
21
+ @typemap.update(TrueClass => 'tinyint')
22
22
  end
23
23
 
24
24
  def self.escape(str)
@@ -42,7 +42,7 @@ class MysqlAdapter < Adapter
42
42
  elsif p.klass.ancestors.include?(Float)
43
43
  return "#\{@#{p.symbol} || 'NULL'\}"
44
44
  elsif p.klass.ancestors.include?(String)
45
- return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
45
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
46
46
  elsif p.klass.ancestors.include?(Time)
47
47
  return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
48
48
  elsif p.klass.ancestors.include?(Date)
@@ -50,6 +50,7 @@ class MysqlAdapter < Adapter
50
50
  elsif p.klass.ancestors.include?(TrueClass)
51
51
  return "#\{@#{p.symbol} ? 1 : 0 \}"
52
52
  else
53
+ # gmosx: keep the '' for nil symbols.
53
54
  return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
54
55
  end
55
56
  end
@@ -82,6 +83,10 @@ class MysqlAdapter < Adapter
82
83
  super
83
84
  end
84
85
 
86
+ def props_for_insert(klass)
87
+ klass.__props.reject { |p| :oid == p.symbol }
88
+ end
89
+
85
90
  def insert_code(klass, db, pre_cb, post_cb)
86
91
  props = props_for_insert(klass)
87
92
  values = props.collect { |p| write_prop(p) }.join(',')
@@ -98,7 +103,7 @@ class MysqlAdapter < Adapter
98
103
  end
99
104
 
100
105
  def new_connection(db)
101
- return Og::MysqlConnection.new(db)
106
+ return MysqlConnection.new(db)
102
107
  end
103
108
 
104
109
  def calc_field_index(klass, db)
@@ -0,0 +1,374 @@
1
+ # * Matt Bowen <matt.bowen@farweststeel.com>
2
+ # * George Moschovitis <gm@navel.gr>
3
+ # (c) 2004-2005 Navel, all rights reserved.
4
+ # $Id: oracle.rb 266 2005-02-28 14:50:48Z gmosx $
5
+
6
+ require 'oracle'
7
+
8
+ require 'og/adapter'
9
+ require 'og/connection'
10
+ require 'glue/attribute'
11
+
12
+ module Og
13
+
14
+ # The Oracle adapter. This adapter communicates with
15
+ # an Oracle rdbms. For extra documentation see
16
+ # lib/og/adapter.rb
17
+
18
+ class OracleAdapter < Adapter
19
+
20
+ def initialize
21
+ super
22
+ @typemap.update(
23
+ Integer => 'number',
24
+ Fixnum => 'number',
25
+ String => 'varchar2(512)',
26
+ TrueClass => 'number',
27
+ Object => 'varchar2(1024)',
28
+ Array => 'varchar2(1024)',
29
+ Hash => 'varchar2(1024)'
30
+ )
31
+ end
32
+
33
+ def self.timestamp(time = Time.now)
34
+ return nil unless time
35
+ return time.strftime("%Y-%m-%d %H:%M:%S")
36
+ end
37
+
38
+ def self.date(date)
39
+ return nil unless date
40
+ return "#{date.year}-#{date.month}-#{date.mday}"
41
+ end
42
+
43
+ def write_prop(p)
44
+ if p.klass.ancestors.include?(Integer)
45
+ return "#\{@#{p.symbol} || 'NULL'\}"
46
+ elsif p.klass.ancestors.include?(Float)
47
+ return "#\{@#{p.symbol} || 'NULL'\}"
48
+ elsif p.klass.ancestors.include?(String)
49
+ return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
50
+ elsif p.klass.ancestors.include?(Time)
51
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
52
+ elsif p.klass.ancestors.include?(Date)
53
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
54
+ elsif p.klass.ancestors.include?(TrueClass)
55
+ return "#\{@#{p.symbol} ? \"1\" : 'NULL' \}"
56
+ else
57
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
58
+ end
59
+ end
60
+
61
+ #--
62
+ # mcb:
63
+ # Unlike MySQL or Postgres, Oracle database/schema creation is a big deal.
64
+ # I don't know how to do it from the command line. I use Oracle's Database
65
+ # Configuration Assistant utility (dbca). I takes 30min - 1hr to create
66
+ # a full blown schema. So, your FIXME comments are fine. I'm thinking you
67
+ # won't be able to do this via Og, but once created, Og will be able to create
68
+ # tables, indexes, and other objects.
69
+ #++
70
+
71
+ def create_db(database, user = nil, password = nil)
72
+ # FIXME: what is appropriate for oracle?
73
+ # `createdb #{database} -U #{user}`
74
+ super
75
+ end
76
+
77
+ def drop_db(database, user = nil, password = nil)
78
+ # FIXME: what is appropriate for oracle?
79
+ # `dropdb #{database} -U #{user}`
80
+ super
81
+ end
82
+
83
+ def insert_code(klass, db, pre_cb, post_cb)
84
+ props = props_for_insert(klass)
85
+ values = props.collect { |p| write_prop(p) }.join(',')
86
+
87
+ sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
88
+
89
+ %{
90
+ #{pre_cb}
91
+ res = conn.store.exec("SELECT #{klass::DBSEQ}.nextval FROM DUAL")
92
+ @oid = res.fetch[0].to_i
93
+ res.close
94
+ conn.exec "#{sql}"
95
+ #{post_cb}
96
+ }
97
+ end
98
+
99
+ def new_connection(db)
100
+ return OracleConnection.new(db)
101
+ end
102
+
103
+ def calc_field_index(klass, db)
104
+ # gmosx: This is incredible!!! argh!
105
+ # res = db.query "SELECT * FROM #{klass::DBTABLE} # LIMIT 1"
106
+ res = db.query "SELECT * FROM (SELECT * FROM #{klass::DBTABLE}) WHERE ROWNUM <= 1"
107
+ meta = db.managed_classes[klass]
108
+
109
+ columns = res.getColNames
110
+
111
+ for idx in (0...columns.size)
112
+ # mcb: Oracle will return column names in uppercase.
113
+ meta.field_index[columns[idx].downcase] = idx
114
+ end
115
+
116
+ ensure
117
+ res.close if res
118
+ end
119
+
120
+ def eval_og_oid(klass)
121
+ klass.class_eval %{
122
+ prop_accessor :oid, Fixnum, :sql => "number PRIMARY KEY"
123
+ }
124
+ end
125
+
126
+ def create_table(klass, db)
127
+ conn = db.get_connection
128
+
129
+ fields = create_fields(klass)
130
+
131
+ sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
132
+
133
+ # Create table constrains.
134
+
135
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
136
+ sql << ", #{constrains.join(', ')}"
137
+ end
138
+
139
+ # mcb: Oracle driver chokes on semicolon.
140
+ sql << ")"
141
+
142
+ # mcb:
143
+ # Oracle driver appears to have problems executing multiple SQL
144
+ # statements in single exec() call. Chokes with or without semicolon
145
+ # delimiter. Solution: make separate calls for each statement.
146
+
147
+ begin
148
+ conn.store.exec(sql).close
149
+ Logger.info "Created table '#{klass::DBTABLE}'."
150
+
151
+ # Create indices.
152
+
153
+ if klass.__meta and indices = klass.__meta[:sql_index]
154
+ for data in indices
155
+ idx, options = *data
156
+ idx = idx.to_s
157
+ pre_sql, post_sql = options[:pre], options[:post]
158
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
159
+ sql = " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx})"
160
+ conn.store.exec(sql).close
161
+ Logger.info "Created index '#{klass::DBTABLE}_#{idxname}_idx'."
162
+ end
163
+ end
164
+ rescue Exception => ex
165
+ # gmosx: any idea how to better test this?
166
+ if ex.to_s =~ /ORA-00955/i
167
+ Logger.debug 'Table or index already exists' if $DBG
168
+ return
169
+ else
170
+ raise
171
+ end
172
+ end
173
+
174
+ # Create the sequence for this table.
175
+ begin
176
+ conn.store.exec("CREATE SEQUENCE #{klass::DBSEQ}").close
177
+ Logger.info "Created sequence '#{klass::DBSEQ}'."
178
+ rescue Exception => ex
179
+ # gmosx: any idea how to better test this?
180
+ if ex.to_s =~ /ORA-00955/i
181
+ Logger.debug "Sequence already exists" if $DBG
182
+ else
183
+ raise
184
+ end
185
+ end
186
+
187
+ # Create join tables if needed. Join tables are used in
188
+ # 'many_to_many' relations.
189
+
190
+ if klass.__meta and joins = klass.__meta[:sql_join]
191
+ for data in joins
192
+ # the class to join to and some options.
193
+ join_class, options = *data
194
+
195
+ # gmosx: dont use DBTABLE here, perhaps the join class
196
+ # is not managed yet.
197
+ join_table = "#{self.class.join_table(klass, join_class)}"
198
+ join_src = "#{self.class.encode(klass)}_oid"
199
+ join_dst = "#{self.class.encode(join_class)}_oid"
200
+ begin
201
+ conn.store.exec("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").close
202
+ conn.store.exec("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").close
203
+ conn.store.exec("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").close
204
+ rescue Exception => ex
205
+ # gmosx: any idea how to better test this?
206
+ if ex.to_s =~ /ORA-00955/i
207
+ Logger.debug "Join table already exists" if $DBG
208
+ else
209
+ raise
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ ensure
216
+ db.put_connection
217
+ end
218
+
219
+ def drop_table(klass)
220
+ super
221
+ exec "DROP SEQUENCE #{klass::DBSEQ}"
222
+ end
223
+
224
+ # Generate the property for oid.
225
+
226
+ #--
227
+ # mcb:
228
+ # Oracle doesn't have a "serial" datatype. Replace with
229
+ # integer (which is probably just a synonym for NUMBER(38,0))
230
+ # A sequence is created automatically by Og.
231
+ #++
232
+
233
+ def eval_og_oid(klass)
234
+ klass.class_eval %{
235
+ prop_accessor :oid, Fixnum, :sql => 'integer PRIMARY KEY'
236
+ }
237
+ end
238
+ end
239
+
240
+ # The Oracle connection.
241
+
242
+ class OracleConnection < Connection
243
+
244
+ # mcb:
245
+ # The database connection details are tucked away in a
246
+ # TNS entry (Transparent Network Substrate) which specifies host,
247
+ # port, protocol, and database instance. Here is a sample TNS
248
+ # entry:
249
+ #
250
+ # File: tns_names.ora
251
+ #
252
+ # KBSID =
253
+ # (DESCRIPTION =
254
+ # (ADDRESS_LIST =
255
+ # (ADDRESS = (PROTOCOL = TCP)(HOST = keebler.farweststeel.com)(PORT = 1521))
256
+ # )
257
+ # (CONNECT_DATA =
258
+ # (SID = KBSID)
259
+ # )
260
+ # )
261
+
262
+ def initialize(db)
263
+ super
264
+ config = db.config
265
+
266
+ begin
267
+ # FIXME: how to pass address etc?
268
+ @store = Oracle.new(config[:user], config[:password], config[:database])
269
+ # gmosx: better use this???
270
+ # @store = Oracle.new(config[:tns])
271
+
272
+ # gmosx: does this work?
273
+ @store.autocommit = true
274
+ rescue Exception => ex
275
+ # mcb:
276
+ # Oracle will raise a ORA-01017 if username, password, or
277
+ # SID aren't valid. I verified this for all three.
278
+ # irb(main):002:0> conn = Oracle.new('keebler', 'dfdfd', 'kbsid')
279
+ # /usr/local/lib/ruby/site_ruby/1.8/oracle.rb:27:in `logon': ORA-01017: invalid username/password; logon denied (OCIError)
280
+ # gmosx:
281
+ # any idea how to better test this? an integer error id?
282
+ if ex.to_s =~ /ORA-01017/i
283
+ Logger.info "Database '#{config[:database]}' not found!"
284
+ @db.adapter.create_db(config[:database], config[:user])
285
+ retry
286
+ end
287
+ raise
288
+ end
289
+ end
290
+
291
+ def close
292
+ @store.logoff
293
+ super
294
+ end
295
+
296
+ def query(sql)
297
+ Logger.debug sql if $DBG
298
+ begin
299
+ return @store.exec(sql)
300
+ rescue Exception => ex
301
+ Logger.error "DB error #{ex}, [#{sql}]"
302
+ Logger.error ex.backtrace.join("\n")
303
+ raise
304
+ # return nil
305
+ end
306
+ end
307
+
308
+ def exec(sql)
309
+ Logger.debug sql if $DBG
310
+ begin
311
+ @store.exec(sql)
312
+ rescue Exception => ex
313
+ Logger.error "DB error #{ex}, [#{sql}]"
314
+ Logger.error ex.backtrace.join("\n")
315
+ raise
316
+ end
317
+ end
318
+
319
+ def start
320
+ @store.autocommit = false
321
+ end
322
+
323
+ def commit
324
+ @store.commit
325
+ ensure
326
+ @store.autocommit = true
327
+ end
328
+
329
+ def rollback
330
+ @store.rollback
331
+ ensure
332
+ @store.autocommit = true
333
+ end
334
+
335
+ def valid_res?(res)
336
+ return !(res.nil?)
337
+ end
338
+
339
+ def read_one(res, klass)
340
+ return nil unless valid_res?(res)
341
+
342
+ row = res.fetch
343
+ return nil unless row
344
+
345
+ obj = klass.new
346
+ obj.og_read(row)
347
+
348
+ res.close
349
+ return obj
350
+ end
351
+
352
+ def read_all(res, klass)
353
+ return [] unless valid_res?(res)
354
+ objects = []
355
+
356
+ while row = res.fetch
357
+ obj = klass.new
358
+ obj.og_read(row)
359
+ objects << obj
360
+ end
361
+
362
+ res.close
363
+ return objects
364
+ end
365
+
366
+ def read_int(res, idx = 0)
367
+ val = res.fetch[idx].to_i
368
+ res.close
369
+ return val
370
+ end
371
+
372
+ end
373
+
374
+ end