og 0.15.0 → 0.16.0

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