og 0.15.0 → 0.16.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.
- data/CHANGELOG +77 -0
- data/INSTALL +3 -0
- data/README +5 -3
- data/Rakefile +148 -5
- data/benchmark/bench.rb +1 -1
- data/doc/AUTHORS +4 -4
- data/doc/RELEASES +41 -1
- data/examples/mock_example.rb +1 -1
- data/examples/mysql_to_psql.rb +1 -1
- data/examples/run.rb +1 -1
- data/install.rb +1 -1
- data/lib/og.rb +4 -2
- data/lib/og/{adapter.rb → adapters/base.rb} +334 -152
- data/lib/og/adapters/filesys.rb +3 -7
- data/lib/og/adapters/mysql.rb +5 -9
- data/lib/og/adapters/oracle.rb +5 -9
- data/lib/og/adapters/psql.rb +5 -9
- data/lib/og/adapters/sqlite.rb +5 -9
- data/lib/og/adapters/sqlserver.rb +5 -9
- data/lib/og/database.rb +13 -11
- data/lib/og/enchant.rb +1 -1
- data/lib/og/errors.rb +21 -0
- data/lib/og/meta.rb +10 -9
- data/lib/og/mixins/hierarchical.rb +4 -4
- data/lib/og/mixins/orderable.rb +1 -1
- data/lib/og/mixins/timestamped.rb +24 -0
- data/lib/og/mixins/tree.rb +3 -1
- data/lib/og/testing/mock.rb +2 -2
- data/lib/og/typemacros.rb +1 -1
- data/lib/og/validation.rb +4 -4
- data/test/og/{tc_filesys.rb → adapters/tc_filesys.rb} +1 -1
- data/test/og/{tc_sqlite.rb → adapters/tc_sqlite.rb} +3 -4
- data/test/og/{tc_sqlserver.rb → adapters/tc_sqlserver.rb} +5 -2
- data/test/og/mixins/tc_hierarchical.rb +0 -1
- data/test/og/mixins/tc_orderable.rb +0 -1
- data/test/og/tc_automanage.rb +3 -2
- data/test/og/tc_lifecycle.rb +15 -14
- data/test/og/tc_many_to_many.rb +0 -1
- data/test/og/tc_meta.rb +5 -5
- data/test/og/tc_validation.rb +1 -1
- data/test/tc_og.rb +0 -20
- metadata +33 -34
- data/examples/test.db +0 -0
- data/lib/og/connection.rb +0 -325
- data/lib/og/observer.rb +0 -53
- data/test/og/tc_observer.rb +0 -85
@@ -1,28 +1,28 @@
|
|
1
1
|
# * George Moschovitis <gm@navel.gr>
|
2
2
|
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id:
|
3
|
+
# $Id: base.rb 17 2005-04-14 16:03:40Z gmosx $
|
4
4
|
|
5
5
|
require 'yaml'
|
6
6
|
require 'singleton'
|
7
7
|
|
8
|
-
require '
|
8
|
+
require 'glue/property'
|
9
|
+
require 'glue/array'
|
10
|
+
require 'glue/time'
|
11
|
+
require 'glue/attribute'
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
# Encapsulates a lower lever adapter SQL excpetion.
|
13
|
+
require 'og'
|
14
|
+
require 'og/errors'
|
13
15
|
|
14
|
-
|
15
|
-
attr_accessor :adapter_exception, :sql
|
16
|
+
module Og
|
16
17
|
|
17
|
-
def initialize(adapter_exception, sql = nil)
|
18
|
-
@adapter_exception, @sql = adapter_exception, sql
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
18
|
# An adapter communicates with the backend datastore.
|
23
19
|
# The adapters for all supported datastores extend this
|
24
20
|
# class. Typically, an RDBMS is used to implement a
|
25
|
-
# datastore.
|
21
|
+
# datastore.
|
22
|
+
#
|
23
|
+
# This is the base adapter implementation. Adapters for
|
24
|
+
# well known RDBMS systems and other stores inherit
|
25
|
+
# from this class.
|
26
26
|
|
27
27
|
class Adapter
|
28
28
|
include Singleton
|
@@ -39,8 +39,13 @@ class Adapter
|
|
39
39
|
# Lookup the adapter instance from the adapter name.
|
40
40
|
|
41
41
|
def self.for_name(name)
|
42
|
-
|
43
|
-
eval
|
42
|
+
# gmosx: RDoc complains about this, so lets use an
|
43
|
+
# eval, AAAAAAAARGH!
|
44
|
+
# require "og/adapters/#{name}"
|
45
|
+
eval %{
|
46
|
+
require 'og/adapters/#{name}'
|
47
|
+
return #{name.capitalize}Adapter.instance
|
48
|
+
}
|
44
49
|
end
|
45
50
|
|
46
51
|
def initialize
|
@@ -326,61 +331,11 @@ class Adapter
|
|
326
331
|
# The generated code sets the oid when inserting!
|
327
332
|
|
328
333
|
def eval_og_insert(klass, db)
|
329
|
-
|
330
|
-
# Attach object callbacks.
|
331
|
-
|
332
|
-
if klass.instance_methods.include?('og_pre_insert')
|
333
|
-
pre_cb = 'og_pre_insert(conn);'
|
334
|
-
else
|
335
|
-
pre_cb = ''
|
336
|
-
end
|
337
|
-
|
338
|
-
if klass.instance_methods.include?('og_post_insert')
|
339
|
-
post_cb = 'og_post_insert(conn);'
|
340
|
-
else
|
341
|
-
post_cb = ''
|
342
|
-
end
|
343
|
-
|
344
|
-
if klass.instance_methods.include?('og_pre_insert_update')
|
345
|
-
pre_cb << 'og_pre_insert_update(conn);'
|
346
|
-
end
|
347
|
-
|
348
|
-
if klass.instance_methods.include?('og_post_insert_update')
|
349
|
-
post_cb << 'og_post_insert_update(conn);'
|
350
|
-
end
|
351
|
-
|
352
|
-
# Attach observers.
|
353
|
-
|
354
|
-
if observers = klass.__meta[:og_observers]
|
355
|
-
observers.each_with_index do |o, idx|
|
356
|
-
if o.is_a?(Class)
|
357
|
-
obs = "#{o}.instance"
|
358
|
-
o = o.instance
|
359
|
-
else
|
360
|
-
obs = "self.class.__meta[:og_observers][#{idx}]"
|
361
|
-
end
|
362
|
-
|
363
|
-
if o.respond_to?(:og_pre_insert)
|
364
|
-
pre_cb << "#{obs}.og_pre_insert(conn, self);"
|
365
|
-
end
|
366
|
-
|
367
|
-
if o.respond_to?(:og_post_insert)
|
368
|
-
post_cb << "#{obs}.og_post_insert(conn, self);"
|
369
|
-
end
|
370
|
-
|
371
|
-
if o.respond_to?(:og_pre_insert_update)
|
372
|
-
pre_cb << "#{obs}.og_pre_insert_update(conn, self);"
|
373
|
-
end
|
374
|
-
|
375
|
-
if o.respond_to?(:og_post_insert_update)
|
376
|
-
post_cb << "#{obs}.og_post_insert_update(conn, self);"
|
377
|
-
end
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
334
|
klass.class_eval %{
|
382
335
|
def og_insert(conn)
|
383
|
-
#{
|
336
|
+
#{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
337
|
+
#{insert_code(klass, db)}
|
338
|
+
#{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
384
339
|
end
|
385
340
|
}
|
386
341
|
end
|
@@ -397,62 +352,11 @@ class Adapter
|
|
397
352
|
|
398
353
|
sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE oid=#\{@oid\}"
|
399
354
|
|
400
|
-
# Attach object callbacks.
|
401
|
-
|
402
|
-
if klass.instance_methods.include?('og_pre_update')
|
403
|
-
pre_cb = 'og_pre_update(conn);'
|
404
|
-
else
|
405
|
-
pre_cb = ''
|
406
|
-
end
|
407
|
-
|
408
|
-
if klass.instance_methods.include?('og_post_update')
|
409
|
-
post_cb = 'og_post_update(conn);'
|
410
|
-
else
|
411
|
-
post_cb = ''
|
412
|
-
end
|
413
|
-
|
414
|
-
if klass.instance_methods.include?('og_pre_insert_update')
|
415
|
-
pre_cb << 'og_pre_insert_update(conn);'
|
416
|
-
end
|
417
|
-
|
418
|
-
if klass.instance_methods.include?('og_post_insert_update')
|
419
|
-
post_cb << 'og_post_insert_update(conn);'
|
420
|
-
end
|
421
|
-
|
422
|
-
# Attach observers.
|
423
|
-
|
424
|
-
if observers = klass.__meta[:og_observers]
|
425
|
-
observers.each_with_index do |o, idx|
|
426
|
-
if o.is_a?(Class)
|
427
|
-
obs = "#{o}.instance"
|
428
|
-
o = o.instance
|
429
|
-
else
|
430
|
-
obs = "self.class.__meta[:og_observers][#{idx}]"
|
431
|
-
end
|
432
|
-
|
433
|
-
if o.respond_to?(:og_pre_update)
|
434
|
-
pre_cb << "#{obs}.og_pre_update(conn, self);"
|
435
|
-
end
|
436
|
-
|
437
|
-
if o.respond_to?(:og_post_update)
|
438
|
-
post_cb << "#{obs}.og_post_update(conn, self);"
|
439
|
-
end
|
440
|
-
|
441
|
-
if o.respond_to?(:og_pre_insert_update)
|
442
|
-
pre_cb << "#{obs}.og_pre_insert_update(conn, self);"
|
443
|
-
end
|
444
|
-
|
445
|
-
if o.respond_to?(:og_post_insert_update)
|
446
|
-
post_cb << "#{obs}.og_post_insert_update(conn, self);"
|
447
|
-
end
|
448
|
-
end
|
449
|
-
end
|
450
|
-
|
451
355
|
klass.class_eval %{
|
452
356
|
def og_update(conn)
|
453
|
-
#{
|
357
|
+
#{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
|
454
358
|
conn.exec "#{sql}"
|
455
|
-
#{
|
359
|
+
#{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
|
456
360
|
end
|
457
361
|
}
|
458
362
|
end
|
@@ -474,50 +378,328 @@ class Adapter
|
|
474
378
|
code << "@#{p.name} = #{read_prop(p, idx)}"
|
475
379
|
end
|
476
380
|
end
|
477
|
-
|
478
|
-
# Attach object callbacks.
|
479
381
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
382
|
+
klass.class_eval %{
|
383
|
+
def og_read(res, tuple = 0)
|
384
|
+
#{Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
|
385
|
+
#{code.join('; ')}
|
386
|
+
#{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
|
387
|
+
end
|
388
|
+
}
|
389
|
+
end
|
390
|
+
|
391
|
+
end
|
392
|
+
|
393
|
+
# A Connection to the Database. This file defines the skeleton
|
394
|
+
# functionality. A store specific implementation file (adapter)
|
395
|
+
# implements all methods.
|
396
|
+
#--
|
397
|
+
# - support caching (memoize).
|
398
|
+
# - use prepared statements.
|
399
|
+
#++
|
400
|
+
|
401
|
+
class Connection
|
402
|
+
|
403
|
+
# The Og database object.
|
404
|
+
|
405
|
+
attr_reader :db
|
406
|
+
|
407
|
+
# The actual connection to the backend store.
|
408
|
+
|
409
|
+
attr_accessor :store
|
410
|
+
|
411
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
412
|
+
# :section: Backend connection methods.
|
413
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
414
|
+
|
415
|
+
# Initialize a connection to the database.
|
416
|
+
|
417
|
+
def initialize(db)
|
418
|
+
@db = db
|
419
|
+
Logger.debug "Created DB connection." if $DBG
|
420
|
+
end
|
421
|
+
|
422
|
+
# Close the connection to the database.
|
423
|
+
|
424
|
+
def close
|
425
|
+
Logger.debug "Closed DB connection." if $DBG
|
426
|
+
end
|
427
|
+
|
428
|
+
# Create the managed object table. The properties of the
|
429
|
+
# object are mapped to the table columns. Additional sql relations
|
430
|
+
# and constrains are created (indicices, sequences, etc).
|
431
|
+
|
432
|
+
def create_table(klass)
|
433
|
+
raise 'Not implemented!'
|
434
|
+
end
|
435
|
+
|
436
|
+
# Drop the managed object table.
|
437
|
+
|
438
|
+
def drop_table(klass)
|
439
|
+
exec "DROP TABLE #{klass::DBTABLE}"
|
440
|
+
end
|
441
|
+
|
442
|
+
# Prepare an sql statement.
|
443
|
+
|
444
|
+
def prepare(sql)
|
445
|
+
raise 'Not implemented!'
|
446
|
+
end
|
447
|
+
|
448
|
+
# Execute an SQL query and return the result.
|
449
|
+
|
450
|
+
def query(sql)
|
451
|
+
raise 'Not implemented!'
|
452
|
+
end
|
453
|
+
|
454
|
+
# Execute an SQL query, no result returned.
|
455
|
+
|
456
|
+
def exec(sql)
|
457
|
+
raise 'Not implemented!'
|
458
|
+
end
|
459
|
+
alias_method :execute, :exec
|
460
|
+
|
461
|
+
# Start a new transaction.
|
462
|
+
|
463
|
+
def start
|
464
|
+
exec 'START TRANSACTION'
|
465
|
+
end
|
466
|
+
|
467
|
+
# Commit a transaction.
|
468
|
+
|
469
|
+
def commit
|
470
|
+
exec 'COMMIT'
|
471
|
+
end
|
472
|
+
|
473
|
+
# Rollback a transaction.
|
474
|
+
|
475
|
+
def rollback
|
476
|
+
exec 'ROLLBACK'
|
477
|
+
end
|
478
|
+
|
479
|
+
# Transaction helper. In the transaction block use
|
480
|
+
# the db pointer to the backend.
|
481
|
+
|
482
|
+
def transaction(&block)
|
483
|
+
begin
|
484
|
+
start
|
485
|
+
yield(self)
|
486
|
+
commit
|
487
|
+
rescue => ex
|
488
|
+
Logger.error "DB Error: ERROR IN TRANSACTION"
|
489
|
+
Logger.error "#{ex}"
|
490
|
+
Logger.error "#{ex.backtrace}"
|
491
|
+
rollback
|
484
492
|
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
496
|
+
# :section: Deserialization methods.
|
497
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
498
|
+
|
499
|
+
# Is the given resultset valid?
|
500
|
+
|
501
|
+
def valid_res?(res)
|
502
|
+
return !(res.nil?)
|
503
|
+
end
|
504
|
+
|
505
|
+
# Read (deserialize) one row of the resultset.
|
506
|
+
|
507
|
+
def read_one(res, klass)
|
508
|
+
raise 'Not implemented!'
|
509
|
+
end
|
510
|
+
|
511
|
+
# Read (deserialize) all rows of the resultset.
|
512
|
+
|
513
|
+
def read_all(res, klass)
|
514
|
+
raise 'Not implemented!'
|
515
|
+
end
|
485
516
|
|
486
|
-
|
487
|
-
|
517
|
+
# Read the first column of the resultset as an Integer.
|
518
|
+
|
519
|
+
def read_int(res, idx = 0)
|
520
|
+
raise 'Not implemented!'
|
521
|
+
end
|
522
|
+
|
523
|
+
# Get a row from the resultset.
|
524
|
+
|
525
|
+
def get_row(res)
|
526
|
+
return res
|
527
|
+
end
|
528
|
+
|
529
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
530
|
+
# :section: Managed object methods.
|
531
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
532
|
+
|
533
|
+
# Save an object to the database. Insert if this is a new object or
|
534
|
+
# update if this is already stored in the database.
|
535
|
+
|
536
|
+
def save(obj)
|
537
|
+
if obj.oid
|
538
|
+
# object allready inserted, update!
|
539
|
+
obj.og_update(self)
|
488
540
|
else
|
489
|
-
|
541
|
+
# not in the database, insert!
|
542
|
+
obj.og_insert(self)
|
490
543
|
end
|
544
|
+
end
|
545
|
+
alias_method :<<, :save
|
546
|
+
alias_method :put, :save
|
547
|
+
|
548
|
+
# Force insertion of managed object.
|
549
|
+
|
550
|
+
def insert(obj)
|
551
|
+
obj.og_insert(self)
|
552
|
+
end
|
491
553
|
|
492
|
-
|
554
|
+
# Force update of managed object.
|
555
|
+
|
556
|
+
def update(obj)
|
557
|
+
obj.og_update(self)
|
558
|
+
end
|
493
559
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
560
|
+
# Update only specific fields of the managed object.
|
561
|
+
#
|
562
|
+
# Input:
|
563
|
+
# sql = the sql code to updated the properties.
|
564
|
+
#
|
565
|
+
# WARNING: the object in memory is not updated.
|
566
|
+
#--
|
567
|
+
# TODO: should update the object in memory.
|
568
|
+
#++
|
569
|
+
|
570
|
+
def update_properties(update_sql, obj_or_oid, klass = nil)
|
571
|
+
oid = obj_or_oid.to_i
|
572
|
+
klass = obj_or_oid.class unless klass
|
573
|
+
|
574
|
+
exec "UPDATE #{klass::DBTABLE} SET #{update_sql} WHERE oid=#{oid}"
|
575
|
+
end
|
576
|
+
alias_method :pupdate, :update_properties
|
577
|
+
alias_method :update_propery, :update_properties
|
502
578
|
|
503
|
-
|
504
|
-
|
505
|
-
|
579
|
+
# Load an object from the database.
|
580
|
+
#
|
581
|
+
# Input:
|
582
|
+
# oid = the object oid, OR the object name.
|
583
|
+
|
584
|
+
def load(oid, klass)
|
585
|
+
if oid.to_i > 0 # a valid Fixnum ?
|
586
|
+
load_by_oid(oid, klass)
|
587
|
+
else
|
588
|
+
load_by_name(oid, klass)
|
589
|
+
end
|
590
|
+
end
|
591
|
+
alias_method :get, :load
|
592
|
+
|
593
|
+
# Load an object by oid.
|
594
|
+
|
595
|
+
def load_by_oid(oid, klass)
|
596
|
+
res = query "SELECT * FROM #{klass::DBTABLE} WHERE oid=#{oid}"
|
597
|
+
read_one(res, klass)
|
598
|
+
end
|
599
|
+
alias_method :get_by_oid, :load_by_oid
|
600
|
+
|
601
|
+
# Load an object by name.
|
602
|
+
|
603
|
+
def load_by_name(name, klass)
|
604
|
+
res = query "SELECT * FROM #{klass::DBTABLE} WHERE name='#{name}'"
|
605
|
+
read_one(res, klass)
|
606
|
+
end
|
607
|
+
alias_method :get_by_name, :load_by_name
|
608
|
+
|
609
|
+
# Load all objects of the given klass.
|
610
|
+
# Used to be called 'collect' in an earlier version.
|
611
|
+
|
612
|
+
def load_all(klass, extrasql = nil)
|
613
|
+
res = query "SELECT * FROM #{klass::DBTABLE} #{extrasql}"
|
614
|
+
read_all(res, klass)
|
615
|
+
end
|
616
|
+
alias_method :get_all, :load_all
|
617
|
+
|
618
|
+
# Perform a standard SQL query to the database. Deserializes the
|
619
|
+
# results.
|
620
|
+
|
621
|
+
def select(sql, klass)
|
622
|
+
unless sql =~ /SELECT/i
|
623
|
+
sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
|
624
|
+
end
|
625
|
+
|
626
|
+
res = query(sql)
|
627
|
+
read_all(res, klass)
|
628
|
+
end
|
629
|
+
|
630
|
+
# Optimized for one result.
|
631
|
+
|
632
|
+
def select_one(sql, klass)
|
633
|
+
unless sql =~ /SELECT/i
|
634
|
+
sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
|
635
|
+
end
|
636
|
+
|
637
|
+
res = query(sql)
|
638
|
+
read_one(res, klass)
|
639
|
+
end
|
506
640
|
|
507
|
-
|
508
|
-
|
641
|
+
# Perform a count query.
|
642
|
+
|
643
|
+
def count(sql, klass = nil)
|
644
|
+
unless sql =~ /SELECT/i
|
645
|
+
sql = "SELECT COUNT(*) FROM #{klass::DBTABLE} WHERE #{sql}"
|
646
|
+
end
|
647
|
+
|
648
|
+
res = query(sql)
|
649
|
+
return read_int(res)
|
650
|
+
end
|
651
|
+
|
652
|
+
# Delete an object from the database. Allways perform a deep delete.
|
653
|
+
#
|
654
|
+
# No need to optimize here with pregenerated code. Deletes are
|
655
|
+
# not used as much as reads or writes.
|
656
|
+
#
|
657
|
+
# Input:
|
658
|
+
#
|
659
|
+
# obj_or_oid = Object or oid to delete.
|
660
|
+
# klass = Class of object (can be nil if an object is passed)
|
661
|
+
#
|
662
|
+
#--
|
663
|
+
# TODO: pre evaluate for symmetry to the other methods
|
664
|
+
#++
|
665
|
+
|
666
|
+
def delete(obj_or_oid, klass = nil, cascade = true)
|
667
|
+
oid = obj_or_oid.to_i
|
668
|
+
klass = obj_or_oid.class unless klass
|
669
|
+
|
670
|
+
# this is a class callback!
|
671
|
+
|
672
|
+
if klass.respond_to?(:og_pre_delete)
|
673
|
+
klass.og_pre_delete(self, obj_or_oid)
|
674
|
+
end
|
675
|
+
|
676
|
+
# TODO: implement this as stored procedure? naaah.
|
677
|
+
# TODO: also handle many_to_many relations.
|
678
|
+
|
679
|
+
transaction do |tx|
|
680
|
+
tx.exec "DELETE FROM #{klass::DBTABLE} WHERE oid=#{oid}"
|
681
|
+
if cascade and klass.__meta.include?(:descendants)
|
682
|
+
klass.__meta[:descendants].each do |dclass, linkback|
|
683
|
+
tx.exec "DELETE FROM #{dclass::DBTABLE} WHERE #{linkback}=#{oid}"
|
509
684
|
end
|
510
685
|
end
|
511
686
|
end
|
687
|
+
end
|
688
|
+
alias_method :delete!, :delete
|
689
|
+
|
690
|
+
protected
|
512
691
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
692
|
+
# Handles an adapter exception.
|
693
|
+
|
694
|
+
def handle_db_exception(ex, sql = nil)
|
695
|
+
Logger.error "DB error #{ex}, [#{sql}]"
|
696
|
+
Logger.error ex.backtrace.join("\n")
|
697
|
+
raise SqlException.new(ex, sql) if Og.raise_db_exceptions
|
698
|
+
|
699
|
+
# FIXME: should return :error or something.
|
700
|
+
|
701
|
+
return nil
|
702
|
+
end
|
521
703
|
|
522
704
|
end
|
523
705
|
|