og 0.9.3 → 0.9.5

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.
@@ -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