og 0.9.3 → 0.9.5

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