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.
Files changed (46) hide show
  1. data/CHANGELOG +77 -0
  2. data/INSTALL +3 -0
  3. data/README +5 -3
  4. data/Rakefile +148 -5
  5. data/benchmark/bench.rb +1 -1
  6. data/doc/AUTHORS +4 -4
  7. data/doc/RELEASES +41 -1
  8. data/examples/mock_example.rb +1 -1
  9. data/examples/mysql_to_psql.rb +1 -1
  10. data/examples/run.rb +1 -1
  11. data/install.rb +1 -1
  12. data/lib/og.rb +4 -2
  13. data/lib/og/{adapter.rb → adapters/base.rb} +334 -152
  14. data/lib/og/adapters/filesys.rb +3 -7
  15. data/lib/og/adapters/mysql.rb +5 -9
  16. data/lib/og/adapters/oracle.rb +5 -9
  17. data/lib/og/adapters/psql.rb +5 -9
  18. data/lib/og/adapters/sqlite.rb +5 -9
  19. data/lib/og/adapters/sqlserver.rb +5 -9
  20. data/lib/og/database.rb +13 -11
  21. data/lib/og/enchant.rb +1 -1
  22. data/lib/og/errors.rb +21 -0
  23. data/lib/og/meta.rb +10 -9
  24. data/lib/og/mixins/hierarchical.rb +4 -4
  25. data/lib/og/mixins/orderable.rb +1 -1
  26. data/lib/og/mixins/timestamped.rb +24 -0
  27. data/lib/og/mixins/tree.rb +3 -1
  28. data/lib/og/testing/mock.rb +2 -2
  29. data/lib/og/typemacros.rb +1 -1
  30. data/lib/og/validation.rb +4 -4
  31. data/test/og/{tc_filesys.rb → adapters/tc_filesys.rb} +1 -1
  32. data/test/og/{tc_sqlite.rb → adapters/tc_sqlite.rb} +3 -4
  33. data/test/og/{tc_sqlserver.rb → adapters/tc_sqlserver.rb} +5 -2
  34. data/test/og/mixins/tc_hierarchical.rb +0 -1
  35. data/test/og/mixins/tc_orderable.rb +0 -1
  36. data/test/og/tc_automanage.rb +3 -2
  37. data/test/og/tc_lifecycle.rb +15 -14
  38. data/test/og/tc_many_to_many.rb +0 -1
  39. data/test/og/tc_meta.rb +5 -5
  40. data/test/og/tc_validation.rb +1 -1
  41. data/test/tc_og.rb +0 -20
  42. metadata +33 -34
  43. data/examples/test.db +0 -0
  44. data/lib/og/connection.rb +0 -325
  45. data/lib/og/observer.rb +0 -53
  46. 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: adapter.rb 323 2005-03-24 09:43:52Z gmosx $
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 'og/connection'
8
+ require 'glue/property'
9
+ require 'glue/array'
10
+ require 'glue/time'
11
+ require 'glue/attribute'
9
12
 
10
- module Og
11
-
12
- # Encapsulates a lower lever adapter SQL excpetion.
13
+ require 'og'
14
+ require 'og/errors'
13
15
 
14
- class SqlException < Exception
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
- require "og/adapters/#{name}"
43
- eval %{ return #{name.capitalize}Adapter.instance }
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
- #{insert_code(klass, db, pre_cb, post_cb)}
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
- #{pre_cb}
357
+ #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
454
358
  conn.exec "#{sql}"
455
- #{post_cb}
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
- if klass.instance_methods.include?('og_pre_read')
481
- pre_cb = 'og_pre_read(conn);'
482
- else
483
- pre_cb = ''
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
- if klass.instance_methods.include?('og_post_read')
487
- post_cb = 'og_post_read(conn);'
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
- post_cb = ''
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
- # Attach observers.
554
+ # Force update of managed object.
555
+
556
+ def update(obj)
557
+ obj.og_update(self)
558
+ end
493
559
 
494
- if observers = klass.__meta[:og_observers]
495
- observers.each_with_index do |o, idx|
496
- if o.is_a?(Class)
497
- obs = "#{o}.instance"
498
- o = o.instance
499
- else
500
- obs = "self.class.__meta[:og_observers][#{idx}]"
501
- end
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
- if o.respond_to?(:og_pre_read)
504
- pre_cb << "#{obs}.og_pre_read(conn, self);"
505
- end
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
- if o.respond_to?(:og_post_read)
508
- post_cb << "#{obs}.og_post_read(conn, self);"
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
- klass.class_eval %{
514
- def og_read(res, tuple = 0)
515
- #{pre_cb}
516
- #{code.join('; ')}
517
- #{post_cb}
518
- end
519
- }
520
- end
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