og 0.40.0 → 0.41.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,3 +1,29 @@
1
+ == Version 0.41.0
2
+
3
+ This is a bug fix release. As it fixes some important bugs of the
4
+ previous release, including a DOS vulnurability you are strongly
5
+ advised to update your version. However, you will also find
6
+ a new feature:
7
+
8
+ * Extended entity .finder method can now handle relations.
9
+
10
+ Post.find_by_title_and_forum_name(title,forumName)
11
+
12
+ class Forum
13
+ property :name, String
14
+ has_many :posts, Post
15
+ end
16
+
17
+ class Post
18
+ property :title, String
19
+ property :message, String
20
+ belongs_to :forum, Forum
21
+ end
22
+
23
+ 'forum' is the :forum from belongs_to, 'name' is a property from the
24
+ relations. It creates a SQL subquery to find the correct forum_oid.
25
+
26
+
1
27
  == Version 0.40.0
2
28
 
3
29
  This is the biggest release yet! Tons of new wonderful features,
@@ -70,7 +70,11 @@ class Tag
70
70
  # Return all tagged objects from all categories.
71
71
 
72
72
  def tagged
73
- # TODO.
73
+ t = []
74
+ self.class.relations.each do |rel|
75
+ t += rel[:target_class].find_with_any_tag(name)
76
+ end
77
+ return t
74
78
  end
75
79
 
76
80
  # Helper method
@@ -79,8 +83,16 @@ class Tag
79
83
  tags.inject(1) { |total, t| total += t.count }
80
84
  end
81
85
 
86
+ #--
87
+ # gmosx: Extra check, useful for utf-8 names on urls.
88
+ #++
89
+
82
90
  def to_s
83
- @name
91
+ if /^\w+$/ =~ @name
92
+ @name
93
+ else
94
+ @oid.to_s
95
+ end
84
96
  end
85
97
  end
86
98
 
@@ -112,8 +124,6 @@ module Taggable
112
124
 
113
125
  include Og::EntityMixin
114
126
 
115
- many_to_many Tag
116
-
117
127
  # Add a tag for this object.
118
128
 
119
129
  def tag(the_tags, options = {})
@@ -227,10 +237,9 @@ module Taggable
227
237
  end
228
238
 
229
239
  def self.included(base)
230
- Tag.module_eval do
231
- many_to_many base
232
- end
233
- base.extend(ClassMethods)
240
+ Tag.many_to_many base
241
+ base.extend ClassMethods
242
+ base.many_to_many Tag
234
243
  #--
235
244
  # FIXME: Og should handle this automatically.
236
245
  #++
data/lib/og.rb CHANGED
@@ -42,7 +42,7 @@ module Og
42
42
 
43
43
  # The version.
44
44
 
45
- Version = '0.40.0'
45
+ Version = '0.41.0'
46
46
 
47
47
  # Library path.
48
48
 
@@ -141,22 +141,22 @@ class MysqlAdapter < SqlStore
141
141
  # Start a transaction.
142
142
 
143
143
  def start
144
- # nop
145
- # FIXME: InnoDB supports transactions.
144
+ # nop on myISAM based tables
145
+ exec_statement "START TRANSACTION"
146
146
  end
147
147
 
148
148
  # Commit a transaction.
149
149
 
150
150
  def commit
151
- # nop, not supported?
152
- # FIXME: InnoDB supports transactions.
151
+ # nop on myISAM based tables
152
+ exec_statement "COMMIT"
153
153
  end
154
154
 
155
155
  # Rollback a transaction.
156
156
 
157
157
  def rollback
158
- # nop, not supported?
159
- # FIXME: InnoDB supports transactions.
158
+ # nop on myISAM based tables
159
+ exec_statement "ROLLBACK"
160
160
  end
161
161
 
162
162
  def write_attr(s, a)
@@ -0,0 +1,509 @@
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/store/sql'
9
+ #require 'og/adapter/oracle/override'
10
+ #require 'og/adapter/oracle/utils'
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 < SqlStore
19
+
20
+ def initialize
21
+ super
22
+
23
+ @typemap.update(
24
+ Integer => 'number',
25
+ Fixnum => 'number',
26
+ String => 'varchar2(1024)', # max might be 4000 (Oracle 8)
27
+ TrueClass => 'char(1)',
28
+ Numeric => 'number',
29
+ Object => 'varchar2(1024)',
30
+ Array => 'varchar2(1024)',
31
+ Hash => 'varchar2(1024)'
32
+ )
33
+
34
+ # TODO: how to pass address etc?
35
+ @store = Oracle.new(config[:user], config[:password], config[:database])
36
+ # gmosx: better use this???
37
+ # @store = Oracle.new(config[:tns])
38
+
39
+ # gmosx: does this work?
40
+ @store.autocommit = true
41
+ rescue Exception => ex
42
+ # mcb:
43
+ # Oracle will raise a ORA-01017 if username, password, or
44
+ # SID aren't valid. I verified this for all three.
45
+ # irb(main):002:0> conn = Oracle.new('keebler', 'dfdfd', 'kbsid')
46
+ # /usr/local/lib/ruby/site_ruby/1.8/oracle.rb:27:in `logon': ORA-01017: invalid username/password; logon denied (OCIError)
47
+ if database_does_not_exist_exception? ex
48
+ Logger.info "Database '#{options[:name]}' not found!"
49
+ create_db(options)
50
+ retry
51
+ end
52
+ raise
53
+ end
54
+
55
+ def close
56
+ @store.logoff
57
+ super
58
+ end
59
+
60
+ #--
61
+ # mcb:
62
+ # Unlike MySQL or Postgres, Oracle database/schema creation is a big deal.
63
+ # I don't know how to do it from the command line. I use Oracle's Database
64
+ # Configuration Assistant utility (dbca). I takes 30min - 1hr to create
65
+ # a full blown schema. So, your FIXME comments are fine. I'm thinking you
66
+ # won't be able to do this via Og, but once created, Og will be able to
67
+ # create tables, indexes, and other objects.
68
+ #++
69
+
70
+ def create_db(database, user = nil, password = nil)
71
+ # FIXME: what is appropriate for oracle?
72
+ # `createdb #{database} -U #{user}`
73
+ super
74
+ raise NotImplementedError, "Oracle Database/Schema creation n/a"
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
+ raise NotImplementedError, "Oracle Database/Schema dropping n/a"
82
+ end
83
+
84
+ # The type used for default primary keys.
85
+
86
+ def primary_key_type
87
+ 'integer PRIMARY KEY'
88
+ end
89
+
90
+ def enchant(klass, manager)
91
+ pk = klass.primary_key
92
+
93
+ seq = if klass.schema_inheritance_child?
94
+ "#{table(klass.schema_inheritance_root_class)}_#{pk}_seq"
95
+ else
96
+ "#{table(klass)}_#{pk}_seq"
97
+ end
98
+
99
+ pkann = klass.ann[pk]
100
+
101
+ pkann[:sequence] = seq unless pkann[:sequence] == false
102
+
103
+ super
104
+ end
105
+
106
+ def query_statement(sql)
107
+ return @conn.exec(sql)
108
+ end
109
+
110
+ def exec_statement(sql)
111
+ @conn.exec(sql).clear
112
+ end
113
+
114
+ def sql_update(sql)
115
+ Logger.debug sql if $DBG
116
+ res = @conn.exec(sql)
117
+ changed = res.cmdtuples
118
+ res.clear
119
+ return changed
120
+ end
121
+
122
+ # Return the last inserted row id.
123
+
124
+ def last_insert_id(klass)
125
+ seq = klass.ann[klass.primary_key][:sequence]
126
+
127
+ res = query_statement("SELECT #{seq}.nextval FROM DUAL")
128
+ lid = Integer(res.first_value)
129
+ res.close
130
+
131
+ return lid
132
+ end
133
+
134
+ # The insert sql statements.
135
+
136
+ def insert_sql(sql, klass)
137
+ str = ''
138
+
139
+ if klass.ann[klass.primary_key][:sequence]
140
+ str << "@#{klass.primary_key} = store.last_insert_id(#{klass})\n"
141
+ end
142
+
143
+ str << "store.exec \"#{sql}\""
144
+
145
+ return str
146
+ end
147
+
148
+ # :section: Transaction methods.
149
+
150
+ # Start a new transaction.
151
+
152
+ def start
153
+ @store.autocommit = false
154
+
155
+ @transaction_nesting += 1
156
+ end
157
+
158
+ # Commit a transaction.
159
+
160
+ def commit
161
+ @transaction_nesting -= 1
162
+ @store.commit if @transaction_nesting < 1
163
+ ensure
164
+ @store.autocommit = true
165
+ end
166
+
167
+ # Rollback a transaction.
168
+
169
+ def rollback
170
+ @transaction_nesting -= 1
171
+ @store.rollbackif @transaction_nesting < 1
172
+ ensure
173
+ @store.autocommit = true
174
+ end
175
+
176
+
177
+ def create_table(klass)
178
+ super
179
+
180
+ seq = klass.ann[klass.primary_key][:sequence]
181
+ # Create the sequence for this table.
182
+ begin
183
+ exec_statement("CREATE SEQUENCE #{seq}")
184
+ Logger.info "Created sequence '#{seq}'."
185
+ rescue Exception => ex
186
+ # gmosx: any idea how to better test this?
187
+ if table_already_exists_exception?(ex)
188
+ Logger.debug "Sequence #{seq} already exists" if $DBG
189
+ else
190
+ raise
191
+ end
192
+ end
193
+
194
+ end
195
+
196
+ def drop_table(klass)
197
+ super
198
+ exec_statement("DROP SEQUENCE #{klass.ann[klass.primary_key][:sequence]}")
199
+ end
200
+
201
+ def read_attr(s, a, col)
202
+ store = self.class
203
+ {
204
+ String => nil,
205
+ Integer => :parse_int,
206
+ Float => :parse_float,
207
+ Time => :parse_timestamp,
208
+ Date => :parse_date,
209
+ TrueClass => :parse_boolean,
210
+ Og::Blob => :parse_blob
211
+ }.each do |klass, meth|
212
+ if a.class.ancestor? klass
213
+ return meth ?
214
+ "#{store}.#{meth}(res[#{col} + offset])" : "res[#{col} + offset]"
215
+ end
216
+ end
217
+
218
+ # else try to load it via YAML
219
+ "YAML::load(res[#{col} + offset])"
220
+ end
221
+
222
+ =begin
223
+ def calc_field_index(klass, db)
224
+ # gmosx: This is incredible!!! argh!
225
+ # res = db.query "SELECT * FROM #{klass::DBTABLE} # LIMIT 1"
226
+ res = db.query "SELECT * FROM (SELECT * FROM #{klass::DBTABLE}) WHERE ROWNUM <= 1"
227
+ meta = db.managed_classes[klass]
228
+
229
+ columns = res.getColNames
230
+
231
+ for idx in (0...columns.size)
232
+ # mcb: Oracle will return column names in uppercase.
233
+ meta.field_index[columns[idx].downcase] = idx
234
+ end
235
+
236
+ ensure
237
+ res.close if res
238
+ end
239
+
240
+ def eval_og_oid(klass)
241
+ klass.class_eval %{
242
+ prop_accessor :oid, Fixnum, :sql => "number PRIMARY KEY"
243
+ }
244
+ end
245
+
246
+ def create_table(klass, db)
247
+ conn = db.get_connection
248
+
249
+ fields = create_fields(klass)
250
+
251
+ sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
252
+
253
+ # Create table constrains.
254
+
255
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
256
+ sql << ", #{constrains.join(', ')}"
257
+ end
258
+
259
+ # mcb: Oracle driver chokes on semicolon.
260
+ sql << ")"
261
+
262
+ # mcb:
263
+ # Oracle driver appears to have problems executing multiple SQL
264
+ # statements in single exec() call. Chokes with or without semicolon
265
+ # delimiter. Solution: make separate calls for each statement.
266
+
267
+ begin
268
+ conn.store.exec(sql).close
269
+ Logger.info "Created table '#{klass::DBTABLE}'."
270
+
271
+ # Create indices.
272
+
273
+ if klass.__meta and indices = klass.__meta[:sql_index]
274
+ for data in indices
275
+ idx, options = *data
276
+ idx = idx.to_s
277
+ pre_sql, post_sql = options[:pre], options[:post]
278
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
279
+ sql = " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx})"
280
+ conn.store.exec(sql).close
281
+ Logger.info "Created index '#{klass::DBTABLE}_#{idxname}_idx'."
282
+ end
283
+ end
284
+ rescue Exception => ex
285
+ # gmosx: any idea how to better test this?
286
+ if table_already_exists_exception?(ex)
287
+ Logger.debug 'Table or index already exists' if $DBG
288
+ return
289
+ else
290
+ raise
291
+ end
292
+ end
293
+
294
+ # Create the sequence for this table.
295
+ begin
296
+ conn.store.exec("CREATE SEQUENCE #{klass::DBSEQ}").close
297
+ Logger.info "Created sequence '#{klass::DBSEQ}'."
298
+ rescue Exception => ex
299
+ # gmosx: any idea how to better test this?
300
+ if table_already_exists_exception?(ex)
301
+ Logger.debug "Sequence already exists" if $DBG
302
+ else
303
+ raise
304
+ end
305
+ end
306
+
307
+ # Create join tables if needed. Join tables are used in
308
+ # 'many_to_many' relations.
309
+
310
+ if klass.__meta and joins = klass.__meta[:sql_join]
311
+ for data in joins
312
+ # the class to join to and some options.
313
+ join_class, options = *data
314
+
315
+ # gmosx: dont use DBTABLE here, perhaps the join class
316
+ # is not managed yet.
317
+ join_table = "#{self.class.join_table(klass, join_class)}"
318
+ join_src = "#{self.class.encode(klass)}_oid"
319
+ join_dst = "#{self.class.encode(join_class)}_oid"
320
+ begin
321
+ conn.store.exec("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").close
322
+ conn.store.exec("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").close
323
+ conn.store.exec("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").close
324
+ rescue Exception => ex
325
+ # gmosx: any idea how to better test this?
326
+ if table_already_exists_exception?(ex)
327
+ Logger.debug "Join table already exists" if $DBG
328
+ else
329
+ raise
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+ ensure
336
+ db.put_connection
337
+ end
338
+
339
+ def drop_table(klass)
340
+ super
341
+ exec "DROP SEQUENCE #{klass::DBSEQ}"
342
+ end
343
+
344
+ # Generate the property for oid.
345
+
346
+ #--
347
+ # mcb:
348
+ # Oracle doesn't have a "serial" datatype. Replace with
349
+ # integer (which is probably just a synonym for NUMBER(38,0))
350
+ # A sequence is created automatically by Og.
351
+ #++
352
+
353
+ def eval_og_oid(klass)
354
+ klass.class_eval %{
355
+ prop_accessor :oid, Fixnum, :sql => 'integer PRIMARY KEY'
356
+ }
357
+ end
358
+ =end
359
+ private
360
+
361
+ def database_does_not_exist_exception?(ex)
362
+ ex.message =~ /ORA-01017/i
363
+ end
364
+
365
+ def table_already_exists_exception?(ex)
366
+ ex.message =~ /ORA-00955/i
367
+ end
368
+
369
+ end
370
+
371
+ =begin
372
+ # The Oracle connection.
373
+
374
+ class OracleConnection < Connection
375
+
376
+ # mcb:
377
+ # The database connection details are tucked away in a
378
+ # TNS entry (Transparent Network Substrate) which specifies host,
379
+ # port, protocol, and database instance. Here is a sample TNS
380
+ # entry:
381
+ #
382
+ # File: tns_names.ora
383
+ #
384
+ # KBSID =
385
+ # (DESCRIPTION =
386
+ # (ADDRESS_LIST =
387
+ # (ADDRESS = (PROTOCOL = TCP)(HOST = keebler.farweststeel.com)(PORT = 1521))
388
+ # )
389
+ # (CONNECT_DATA =
390
+ # (SID = KBSID)
391
+ # )
392
+ # )
393
+
394
+ def initialize(db)
395
+ super
396
+ config = db.config
397
+
398
+ begin
399
+ # FIXME: how to pass address etc?
400
+ @store = Oracle.new(config[:user], config[:password], config[:database])
401
+ # gmosx: better use this???
402
+ # @store = Oracle.new(config[:tns])
403
+
404
+ # gmosx: does this work?
405
+ @store.autocommit = true
406
+ rescue Exception => ex
407
+ # mcb:
408
+ # Oracle will raise a ORA-01017 if username, password, or
409
+ # SID aren't valid. I verified this for all three.
410
+ # irb(main):002:0> conn = Oracle.new('keebler', 'dfdfd', 'kbsid')
411
+ # /usr/local/lib/ruby/site_ruby/1.8/oracle.rb:27:in `logon': ORA-01017: invalid username/password; logon denied (OCIError)
412
+ # gmosx:
413
+ # any idea how to better test this? an integer error id?
414
+ if ex.to_s =~ /ORA-01017/i
415
+ Logger.info "Database '#{config[:database]}' not found!"
416
+ @db.adapter.create_db(config[:database], config[:user])
417
+ retry
418
+ end
419
+ raise
420
+ end
421
+ end
422
+
423
+ def close
424
+ @store.logoff
425
+ super
426
+ end
427
+
428
+ def query(sql)
429
+ Logger.debug sql if $DBG
430
+ begin
431
+ return @store.exec(sql)
432
+ rescue Exception => ex
433
+ Logger.error "DB error #{ex}, [#{sql}]"
434
+ Logger.error ex.backtrace.join("\n")
435
+ raise
436
+ # return nil
437
+ end
438
+ end
439
+
440
+ def exec(sql)
441
+ Logger.debug sql if $DBG
442
+ begin
443
+ @store.exec(sql)
444
+ rescue Exception => ex
445
+ Logger.error "DB error #{ex}, [#{sql}]"
446
+ Logger.error ex.backtrace.join("\n")
447
+ raise
448
+ end
449
+ end
450
+
451
+ def start
452
+ @store.autocommit = false
453
+ end
454
+
455
+ def commit
456
+ @store.commit
457
+ ensure
458
+ @store.autocommit = true
459
+ end
460
+
461
+ def rollback
462
+ @store.rollback
463
+ ensure
464
+ @store.autocommit = true
465
+ end
466
+
467
+ def valid_res?(res)
468
+ return !(res.nil?)
469
+ end
470
+
471
+ def read_one(res, klass)
472
+ return nil unless valid_res?(res)
473
+
474
+ row = res.fetch
475
+ return nil unless row
476
+
477
+ obj = klass.new
478
+ obj.og_read(row)
479
+
480
+ res.close
481
+ return obj
482
+ end
483
+
484
+ def read_all(res, klass)
485
+ return [] unless valid_res?(res)
486
+ objects = []
487
+
488
+ while row = res.fetch
489
+ obj = klass.new
490
+ obj.og_read(row)
491
+ objects << obj
492
+ end
493
+
494
+ res.close
495
+ return objects
496
+ end
497
+
498
+ def read_int(res, idx = 0)
499
+ val = res.fetch[idx].to_i
500
+ res.close
501
+ return val
502
+ end
503
+
504
+ end
505
+
506
+ =end
507
+
508
+ end
509
+