og 0.40.0 → 0.41.0

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