og 0.10.0 → 0.11.0

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