#!/usr/bin/perl -w

use strict;

use Test::More tests => 598;

BEGIN 
{
  require 't/test-lib.pl';
  use_ok('Rose::DB::Object');
  use_ok('Rose::DB::Object::Util');
}

Rose::DB::Object::Util->import(':all');

eval { require Time::HiRes };
our $Have_HiRes_Time = $@ ? 0 : 1;

our($PG_HAS_CHKPASS, $HAVE_PG, $HAVE_MYSQL, $HAVE_INFORMIX, $HAVE_SQLITE,
    $HAVE_ORACLE, $INNODB);

#
# PostgreSQL
#

SKIP: foreach my $db_type (qw(pg pg_with_schema))
{
  skip("PostgreSQL tests", 242)  unless($HAVE_PG);

  Rose::DB->default_type($db_type);

  TEST_HACK:
  {
    no warnings;
    *MyPgObject::init_db = sub { Rose::DB->new($db_type) };
  }

  my $o = MyPgObject->new(name => 'John', 
                          k1   => 1,
                          k2   => undef,
                          k3   => 3);

  ok(ref $o && $o->isa('MyPgObject'), "new() 1 - $db_type");

  $o->flag2('TRUE');
  $o->date_created('now');
  $o->date_created_tz('now');
  $o->timestamp_tz2('now');
  $o->last_modified($o->date_created);
  $o->save_col(7);

  if(rand >= 0.5)
  {
    ok($o->save, "save() 1 - $db_type");
  }
  else
  {
    ok($o->insert, "insert() 1 - $db_type");
  }

  MyPgObject->meta->sql_qualify_column_names_on_load(1);

  my $schema = $db_type eq 'pg_with_schema' ? 'rose_db_object_private.' : '';

  is(MyPgObject->meta->load_all_sql(undef, $o->db), 
     qq(SELECT rose_db_object_test.name, rose_db_object_test.code, rose_db_object_test.id, rose_db_object_test.k1, rose_db_object_test.k2, rose_db_object_test.k3,@{[ $PG_HAS_CHKPASS ? ' rose_db_object_test.passwd,' : '' ]} rose_db_object_test.flag, rose_db_object_test.flag2, rose_db_object_test.status, rose_db_object_test.start, rose_db_object_test.save, rose_db_object_test.nums, rose_db_object_test.bitz, rose_db_object_test.decs, rose_db_object_test.dur, rose_db_object_test.epoch, rose_db_object_test.hiepoch, rose_db_object_test.bint1, rose_db_object_test.bint2, rose_db_object_test.bint3, rose_db_object_test.bint4, rose_db_object_test.tee_time, rose_db_object_test.tee_time0, rose_db_object_test.tee_time5, rose_db_object_test.tee_time9, rose_db_object_test.date_created, rose_db_object_test.date_created_tz, rose_db_object_test.timestamp_tz2, rose_db_object_test.last_modified FROM ${schema}rose_db_object_test WHERE rose_db_object_test.id = ?),
     "sql_qualify_column_names_on_load() 1 - $db_type");

  is(MyPgObject->meta->load_sql(undef, $o->db), 
     qq(SELECT rose_db_object_test.name, rose_db_object_test.code, rose_db_object_test.id, rose_db_object_test.k1, rose_db_object_test.k3,@{[ $PG_HAS_CHKPASS ? ' rose_db_object_test.passwd,' : '' ]} rose_db_object_test.flag, rose_db_object_test.flag2, rose_db_object_test.status, rose_db_object_test.save, rose_db_object_test.nums, rose_db_object_test.bitz, rose_db_object_test.decs, rose_db_object_test.dur, rose_db_object_test.epoch, rose_db_object_test.hiepoch, rose_db_object_test.bint1, rose_db_object_test.bint2, rose_db_object_test.bint3, rose_db_object_test.bint4, rose_db_object_test.tee_time, rose_db_object_test.tee_time0, rose_db_object_test.tee_time5, rose_db_object_test.tee_time9, rose_db_object_test.date_created, rose_db_object_test.date_created_tz, rose_db_object_test.timestamp_tz2, rose_db_object_test.last_modified FROM ${schema}rose_db_object_test WHERE rose_db_object_test.id = ?),
     "sql_qualify_column_names_on_load() 2 - $db_type");

  is(MyPgObject->meta->load_all_sql_with_null_key([ qw(k1 k2 k3) ], [ 1, undef, 3 ], $o->db), 
     qq(SELECT rose_db_object_test.name, rose_db_object_test.code, rose_db_object_test.id, rose_db_object_test.k1, rose_db_object_test.k2, rose_db_object_test.k3,@{[ $PG_HAS_CHKPASS ? ' rose_db_object_test.passwd,' : '' ]} rose_db_object_test.flag, rose_db_object_test.flag2, rose_db_object_test.status, rose_db_object_test.start, rose_db_object_test.save, rose_db_object_test.nums, rose_db_object_test.bitz, rose_db_object_test.decs, rose_db_object_test.dur, rose_db_object_test.epoch, rose_db_object_test.hiepoch, rose_db_object_test.bint1, rose_db_object_test.bint2, rose_db_object_test.bint3, rose_db_object_test.bint4, rose_db_object_test.tee_time, rose_db_object_test.tee_time0, rose_db_object_test.tee_time5, rose_db_object_test.tee_time9, rose_db_object_test.date_created, rose_db_object_test.date_created_tz, rose_db_object_test.timestamp_tz2, rose_db_object_test.last_modified FROM ${schema}rose_db_object_test WHERE rose_db_object_test.k1 = ? AND rose_db_object_test.k2 IS NULL AND rose_db_object_test.k3 = ?),
     "sql_qualify_column_names_on_load() 3 - $db_type");

  is(MyPgObject->meta->load_sql_with_null_key([ qw(k1 k2 k3) ], [ 1, undef, 3 ], $o->db), 
     qq(SELECT rose_db_object_test.name, rose_db_object_test.code, rose_db_object_test.id, rose_db_object_test.k1, rose_db_object_test.k3,@{[ $PG_HAS_CHKPASS ? ' rose_db_object_test.passwd,' : '' ]} rose_db_object_test.flag, rose_db_object_test.flag2, rose_db_object_test.status, rose_db_object_test.save, rose_db_object_test.nums, rose_db_object_test.bitz, rose_db_object_test.decs, rose_db_object_test.dur, rose_db_object_test.epoch, rose_db_object_test.hiepoch, rose_db_object_test.bint1, rose_db_object_test.bint2, rose_db_object_test.bint3, rose_db_object_test.bint4, rose_db_object_test.tee_time, rose_db_object_test.tee_time0, rose_db_object_test.tee_time5, rose_db_object_test.tee_time9, rose_db_object_test.date_created, rose_db_object_test.date_created_tz, rose_db_object_test.timestamp_tz2, rose_db_object_test.last_modified FROM ${schema}rose_db_object_test WHERE rose_db_object_test.k1 = ? AND rose_db_object_test.k2 IS NULL AND rose_db_object_test.k3 = ?),
     "sql_qualify_column_names_on_load() 4 - $db_type");

  MyPgObject->meta->sql_qualify_column_names_on_load(rand > 0.6 ? 0 : 1); # excitement! :)

  is($o->meta->primary_key->sequence_names->[0], 'rose_db_object_test_id_seq', 
     "pk sequence name - $db_type");

  ok(is_in_db($o), "is_in_db - $db_type");

  is($o->id, 1, "auto-generated primary key - $db_type");

  ok($o->load, "load() 1 - $db_type");

  is($o->date_created->time_zone->name, 'floating', "timestamp without time zone - $db_type");
  isnt($o->date_created_tz->time_zone->name, 'floating', "timestamp with time zone - $db_type");
  is($o->timestamp_tz2->time_zone->name, 'Antarctica/Vostok', "timestamp with time zone override - $db_type");  

  # Make sure we're not in the Antarctica/Vostok time zone or any other
  # time zone with the same offset.
  my $error;

  TRY:
  {
    local $@;

    eval
    {
      my $dt1 = DateTime->now(time_zone => 'local');
      my $dt2 = $dt1->clone;
      $dt2->set_time_zone('Antarctica/Vostok');
      die "local is equivalent to Antarctica/Vostok"  if($dt1->iso8601 eq $dt2->iso8601);
    };

    $error = $@;
  }

  if($error)
  {
    SKIP: { skip("timestamp with time zone time change - $db_type", 2) }
  }
  else
  {
    isnt($o->date_created_tz->iso8601, $o->timestamp_tz2->iso8601, "timestamp with time zone time change - $db_type");

    $o->save;
    $o->load;

    my $dt = $o->timestamp_tz2->clone;
    $dt->set_time_zone($o->date_created_tz->time_zone);

    is($o->date_created_tz->iso8601, $dt->iso8601, "timestamp with time zone time change 2 - $db_type");
  }

  $o->name('C' x 50);
  is($o->name, 'C' x 32, "varchar truncation - $db_type");

  $o->name('John');

  $o->code('A');
  is($o->code, 'A     ', "character padding - $db_type");

  $o->code('C' x 50);
  is($o->code, 'C' x 6, "character truncation - $db_type");

  my $ouk;
  ok($ouk = MyPgObject->new(k1 => 1,
                            k2 => undef,
                            k3 => 3)->load, "load() uk 1 - $db_type");

  ok(!$ouk->not_found, "not_found() uk 1 - $db_type");

  is($ouk->id, 1, "load() uk 2 - $db_type");
  is($ouk->name, 'John', "load() uk 3 - $db_type");

  ok($ouk->save, "save() uk 1 - $db_type");

  my $o2 = MyPgObject->new(id => $o->id);

  ok(ref $o2 && $o2->isa('MyPgObject'), "new() 2 - $db_type");

  is($o2->bits->to_Bin, '00101', "bits() (bitfield default value) - $db_type");

  ok($o2->load, "load() 2 - $db_type");
  ok(!$o2->not_found, "not_found() 1 - $db_type");

  is($o2->name, $o->name, "load() verify 1 - $db_type");
  is($o2->date_created, $o->date_created, "load() verify 2 - $db_type");
  is($o2->last_modified, $o->last_modified, "load() verify 3 - $db_type");
  is($o2->status, 'active', "load() verify 4 (default value) - $db_type");
  is($o2->flag, 1, "load() verify 5 (default boolean value) - $db_type");
  is($o2->flag2, 1, "load() verify 6 (boolean value) - $db_type");
  is($o2->save_col, 7, "load() verify 7 (aliased column) - $db_type");
  is($o2->start->ymd, '1980-12-24', "load() verify 8 (date value) - $db_type");

  $o2->flag2(undef);
  $o2->save;

  is($o2->flag2, undef, "boolean null - $db_type");

  $o2->set_status('foo');
  is($o2->get_status, 'foo', "get_status() - $db_type");
  $o2->set_status('active');
  eval { $o2->set_status };
  ok($@, "set_status() - $db_type");

  is($o2->bits->to_Bin, '00101', "load() verify 9 (bitfield value) - $db_type");

  my $clone = $o2->clone;
  ok($o2->start eq $clone->start, "clone() 1 - $db_type");
  $clone->start->set(year => '1960');
  ok($o2->start ne $clone->start, "clone() 2 - $db_type");

  $o2->start('5/24/2001');

  sleep(1); # keep the last modified dates from being the same

  $o2->last_modified('now');
  ok($o2->save, "save() 2 - $db_type");
  ok($o2->load, "load() 3 - $db_type");

  ok(!has_modified_columns($o2), "no modified columns after load() - $db_type");

  $o2->name('John 2');
  $o2->save(changes_only => 1);

  is($o2->date_created, $o->date_created, "save() verify 1 - $db_type");
  ok($o2->last_modified ne $o->last_modified, "save() verify 2 - $db_type");
  is($o2->start->ymd, '2001-05-24', "save() verify 3 (date value) - $db_type");

  my $bo = MyPgObject->new(id => $o->id);
  $bo->load;
  $bo->flag(0);
  $bo->save;

  $bo = MyPgObject->new(id => $o->id);
  $bo->load;

  ok(!$bo->flag, "boolean check - $db_type");

  $bo->flag(0);
  $bo->save;

  my $o3 = MyPgObject->new();

  my $db = $o3->db or die $o3->error;

  ok(ref $db && $db->isa('Rose::DB'), "db() - $db_type");

  is($db->dbh, $o3->dbh, "dbh() - $db_type");

  my $o4 = MyPgObject->new(id => 999);
  ok(!$o4->load(speculative => 1), "load() nonexistent - $db_type");
  ok($o4->not_found, "not_found() 2 - $db_type");

  ok($o->load, "load() 4 - $db_type");

  SKIP:
  {
    if($PG_HAS_CHKPASS)
    {
      $o->{'password_encrypted'} = ':8R1Kf2nOS0bRE';

      ok($o->password_is('xyzzy'), "chkpass() 1 - $db_type");
      is($o->password, 'xyzzy', "chkpass() 2 - $db_type");

      $o->password('foobar');

      ok($o->password_is('foobar'), "chkpass() 3 - $db_type");
      is($o->password, 'foobar', "chkpass() 4 - $db_type");

      $o->code('C1');
      #local $Rose::DB::Object::Debug = 1;
      ok($o->save, "save() 3 - $db_type");

      $o = MyPgObject->new(id => $o->id)->load;      
      $o->code('C2');
      $o->save;

      $o = MyPgObject->new(id => $o->id)->load;
      ok($o->password_is('foobar'), "chkpass() 6 - $db_type");
    }
    else
    {
      skip("chkpass tests", 6);
    }
  }

  my $o5 = MyPgObject->new(id => $o->id);

  ok($o5->load, "load() 5 - $db_type");

  SKIP:
  {
    if($PG_HAS_CHKPASS)
    {
      ok($o5->password_is('foobar'), "chkpass() 7 - $db_type");
      is($o5->password, 'foobar', "chkpass() 8 - $db_type"); 
    }
    else
    {
      skip("chkpass tests", 2);
    }
  }

  $o5->nums([ 4, 5, 6 ]);
  ok($o5->save, "save() 4 - $db_type");
  ok($o->load, "load() 6 - $db_type");

  is($o5->nums->[0], 4, "load() verify 10 (array value) - $db_type");
  is($o5->nums->[1], 5, "load() verify 11 (array value) - $db_type");
  is($o5->nums->[2], 6, "load() verify 12 (array value) - $db_type");

  my @a = $o5->nums;

  is($a[0], 4, "load() verify 13 (array value) - $db_type");
  is($a[1], 5, "load() verify 14 (array value) - $db_type");
  is($a[2], 6, "load() verify 15 (array value) - $db_type");
  is(@a, 3, "load() verify 16 (array value) - $db_type");

  ok($o->delete, "delete() - $db_type");

  $o = MyPgObject->new(name => 'John', id => 9);
  $o->save_col(22);
  ok($o->save, "save() 4 - $db_type");
  $o->save_col(50);
  ok($o->save, "save() 5 - $db_type");

  $ouk = MyPgObject->new(save_col => 50);
  ok($ouk->load, "load() aliased unique key - $db_type");

  eval { $o->meta->alias_column(nonesuch => 'foo') };
  ok($@, "alias_column() nonesuch - $db_type");

  # This is okay now
  #eval { $o->meta->alias_column(id => 'foo') };
  #ok($@, "alias_column() primary key - $db_type");

  $o = MyPgObject->new(id => 777);

  $o->meta->error_mode('fatal');

  $o->dbh->{'PrintError'} = 0;

  eval { $o->load };
  ok($@ && $o->not_found, "load() not found fatal - $db_type");

  $o->id('abc');

  eval { $o->load };
  ok($@ && !$o->not_found, "load() fatal - $db_type");

  eval { $o->save };
  ok($@, "save() fatal - $db_type");

  $o = MyPgObject->new(id => 9999); # no such id

  $o->meta->error_mode('fatal');

  eval { $o->load() };
  ok($@, "load() non-speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 1 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  $o->meta->default_load_speculative(1);

  ok(!$o->load(), "load() speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 2 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  # Reset for next trip through loop
  $o->meta->default_load_speculative(0);
  $o->meta->error_mode('return');

  $o = MyPgObject->new(name => 'John', 
                       k1   => 1,
                       k2   => undef,
                       k3   => 3)->save;

  is($o->dur->months, 2, "interval months 1 - $db_type");
  is($o->dur->days, 5, "interval days 1 - $db_type");
  is($o->dur->seconds, 3, "interval seconds 1 - $db_type");

  $o->dur(DateTime::Duration->new(years => 7, nanoseconds => 3000));

  is($o->dur->in_units('years'), 7, "interval in_units years 1 - $db_type");
  is($o->dur->in_units('months'), 84, "interval in_units months 1 - $db_type");
  is($o->dur->nanoseconds, 3000, "interval nanoseconds 1 - $db_type");
  is($o->dur->days, 0, "interval days 2 - $db_type");
  is($o->dur->minutes, 0, "interval minutes 2 - $db_type");
  is($o->dur->seconds, 0, "interval seconds 2 - $db_type");

  $o->save;

  # Select for update tests
  $o = MyPgObject->new(id => $o->id);

  $o->db->begin_work;
  $o->load(for_update => 1);

  # Silence errors in eval blocks below
  Rose::DB->modify_db(type => $db_type)->print_error(0);

  my $lo;

  eval
  {
    $lo = MyPgObject->new(id => $o->id);
    $lo->meta->error_mode('fatal');
    $lo->load(lock => { for_update => 1, nowait => 1 });
  };

  is(DBI->err, 7, "select for update wait 1 error 7 - $db_type");
  ok($@, "select for update no wait - $db_type");

  $o->db->commit;

  Rose::DB->modify_db(type => $db_type)->print_error(1);

  $lo = MyPgObject->new(id => $o->id);
  $lo->load(lock => { type => 'shared' });

  $o = MyPgObject->new(id => $o->id)->load;

  is($o->dur->in_units('years'), 7, "interval in_units years 2 - $db_type");
  is($o->dur->in_units('months'), 84, "interval in_units months 2 - $db_type");
  is($o->dur->nanoseconds, 3000, "interval nanoseconds 2 - $db_type");
  is($o->dur->days, 0, "interval days 3 - $db_type");
  is($o->dur->minutes, 0, "interval minutes 3 - $db_type");
  is($o->dur->seconds, 0, "interval seconds 3 - $db_type");

  is($o->epoch(format => '%Y-%m-%d %H:%M:%S'), '1999-11-30 21:30:00', "epoch 1 - $db_type");

  $o->hiepoch('943997400.123456');
  is($o->hiepoch(format => '%Y-%m-%d %H:%M:%S.%6N'), '1999-11-30 21:30:00.123456', "epoch hires 1 - $db_type");

  $o->epoch('5/6/1980 12:34:56');

  $o->save;

  $o = MyPgObject->new(id => $o->id)->load;

  is($o->epoch(format => '%Y-%m-%d %H:%M:%S'), '1980-05-06 12:34:56', "epoch 2 - $db_type");
  is($o->hiepoch(format => '%Y-%m-%d %H:%M:%S.%6N'), '1999-11-30 21:30:00.123456', "epoch hires 2 - $db_type");

  is($o->bint1, '9223372036854775800', "bigint 1 - $db_type");
  is($o->bint2, '-9223372036854775800', "bigint 2 - $db_type");
  is($o->bint3, '9223372036854775000', "bigint 3 - $db_type");
  is($o->bint4, undef, "bigint null 1 - $db_type");

  $o->bint4(555);
  $o->bint1($o->bint1 + 1);
  $o->save;

  $o = MyPgObject->new(id => $o->id)->load;
  is($o->bint1, '9223372036854775801', "bigint 4 - $db_type");
  is($o->bint4, 555, "bigint null 2 - $db_type");

  $o->bint4(undef);

  $o->bint3(5);
  eval { $o->bint3(7) };
  ok($@, "bigint 5 - $db_type");

  is($o->tee_time5->as_string, '12:34:56.12345', "time(5) - $db_type");

  $o->tee_time0('1pm');
  $o->tee_time('allballs');
  $o->tee_time9('now');
  $o->save;

  $o =  MyPgObject->new(id => $o->id)->load;
  is($o->tee_time->as_string, '00:00:00', "time allballs - $db_type");
  ok($o->tee_time9->as_string =~ /^\d\d:\d\d:\d\d\.\d{1,6}$/, "time now - $db_type");
  is($o->bint4, undef, "bigint null 3 - $db_type");

  $o->tee_time(Time::Clock->new->parse('6:30 PM'));
  $o->save;

  $o =  MyPgObject->new(id => $o->id)->load;
  is($o->tee_time->as_string, '18:30:00', "time 6:30 PM - $db_type");
}

#
# MySQL
#

SKIP: foreach my $db_type ('mysql')
{
  skip("MySQL tests", 121)  unless($HAVE_MYSQL);

  Rose::DB->default_type($db_type);

  my $o = MyMySQLObject->new(name => 'John',
                             k1   => 1,
                             k2   => undef,
                             k3   => 3);

  # Checking to see that Perl code generation methods don't die (See: 0.767 changes)
  $o->meta->column('name')->check_in([ qw(a b c) ]);
  $o->meta->perl_class_definition;
  $o->meta->column('name')->check_in(undef);

  ok(ref $o && $o->isa('MyMySQLObject'), "new() 1 - $db_type");

  $o->flag2('true');
  $o->date_created('now');
  $o->last_modified($o->date_created);
  $o->save_col(22);

  $o->bitz3('11');

  if(rand >= 0.5)
  {
    ok($o->save, "save() 1 - $db_type");
  }
  else
  {
    ok($o->insert, "insert() 1 - $db_type");
  }

  # Select for update tests
  if($INNODB && $ENV{'RDBO_SLOW_TESTS'})
  {
    $o = MyMySQLObject->new(id => $o->id);

    $o->db->begin_work;
    $o->load(for_update => 1);

    # Silence errors in eval blocks below
    Rose::DB->modify_db(type => $db_type)->print_error(0);

    my $lo;

    eval
    {
      $lo = MyMySQLObject->new(id => $o->id);
      $lo->meta->error_mode('fatal');
      $lo->load(lock => { for_update => 1 });
    };

    is(DBI->err, 1205, "select for update wait 1 error 1205 - $db_type");
    ok($@, "select for update - $db_type");

    $o->db->commit;
  }
  else
  {
    if($INNODB)
    {
      SKIP: { skip("Select for update tests: RDBO_SLOW_TESTS not set - $db_type", 2) }
    }
    else
    {
      SKIP: { skip("Select for update tests: no InnoDB - $db_type", 2) }
    }
  }

  $o = MyMySQLObject->new(id => $o->id);
  $o->load(lock => { type => 'shared' });

  ok($o->load, "load() 1 - $db_type");

  is(ref $o->dt_default, 'DateTime', "now() default - $db_type");

  is($o->zepoch->ymd, '1970-01-01', "zero epoch default - $db_type");

  is_deeply([ sort $o->items ], [ qw(a c) ], "set default - $db_type");

  my $os = MyMySQLObject->new(id => $o->id)->load;
  $os->items;

  CATCH_STDERR:
  {
    local *STDERR;
    my $stderr;
    open(STDERR, '>', \$stderr) or die "Could not redirect STDERR - $!";

    local $Rose::DB::Object::Debug = 1;
    $os->save(changes_only => 1);
    is($stderr, undef, "save changes only for set column - $db_type");
  }

  my $ox = MyMySQLObject->new(id => $o->id)->load;
  is($ox->bitz2->to_Bin(), '00', "spot check bitfield 1 - $db_type");
  is($ox->bitz3->to_Bin(), '0011', "spot check bitfield 2 - $db_type");

  eval { $o->name('C' x 50) };
  ok($@, "varchar overflow fatal - $db_type");

  $o->name('John');

  $o->code('A');
  is($o->code, 'A     ', "character padding - $db_type");

  eval { $o->code('C' x 50) };
  ok($@, "code overflow fatal - $db_type");
  $o->code('C' x 6);

  is($o->enums, 'foo', "enum 1 - $db_type");
  eval { $o->enums('blee') };
  ok($@, "enum 2 - $db_type");

  $o->enums('bar');

  my $ouk;

  ok($ouk = MyMySQLObject->new(k1 => 1,
                               k2 => undef,
                               k3 => 3)->load, "load() uk 1 - $db_type");

  ok(!$ouk->not_found, "not_found() uk 1 - $db_type");

  is($ouk->id, 1, "load() uk 2 - $db_type");
  is($ouk->name, 'John', "load() uk 3 - $db_type");

  ok($ouk->save, "save() uk 1 - $db_type");

  my $o2 = MyMySQLObject->new(id => $o->id);

  ok(ref $o2 && $o2->isa('MyMySQLObject'), "new() 2 - $db_type");

  is($o2->bits->to_Bin, '00101', "bits() (bitfield default value) - $db_type");
  is($o2->bitz2->to_Bin, '00', "bitz2() (bitfield default value) - $db_type");

  ok($o2->load, "load() 2 - $db_type");

  ok(!$o2->not_found, "not_found() 1 - $db_type");

  is($o2->name, $o->name, "load() verify 1 - $db_type");
  is($o2->date_created, $o->date_created, "load() verify 2 - $db_type");
  is($o2->last_modified, $o->last_modified, "load() verify 3 - $db_type");
  is($o2->status, 'active', "load() verify 4 (default value) - $db_type");
  is($o2->flag, 1, "load() verify 5 (default boolean value) - $db_type");
  is($o2->flag2, 1, "load() verify 6 (boolean value) - $db_type");
  is($o2->save_col, 22, "load() verify 7 (aliased column) - $db_type");
  is($o2->start->ymd, '1980-12-24', "load() verify 8 (date value) - $db_type");

  $o2->set_status('foo');
  is($o2->get_status, 'foo', 'get_status()');
  $o2->set_status('active');
  eval { $o2->set_status };
  ok($@, 'set_status()');

  is($o2->bits->to_Bin, '00101', "load() verify 9 (bitfield value) - $db_type");
  is($o2->bitz2->to_Bin, '00', "load() verify 10 (bitfield value) - $db_type");
  is($o2->bitz3->to_Bin, '0011', "load() verify 11 (bitfield value) - $db_type");

  my $clone = $o2->clone;
  ok($o2->start eq $clone->start, "clone() 1 - $db_type");
  $clone->start->set(year => '1960');
  ok($o2->start ne $clone->start, "clone() 2 - $db_type");

  $o2->name('John 2');
  $o2->start('5/24/2001');

  sleep(1); # keep the last modified dates from being the same

  $o2->last_modified('now');
  ok($o2->save, "save() 2 - $db_type");
  ok($o2->load, "load() 3 - $db_type");

  is($o2->date_created, $o->date_created, "save() verify 1 - $db_type");
  ok($o2->last_modified ne $o->last_modified, "save() verify 2 - $db_type");
  is($o2->start->ymd, '2001-05-24', "save() verify 3 (date value) - $db_type");

  my $o3 = MyMySQLObject->new();

  my $db = $o3->db or die $o3->error;

  ok(ref $db && $db->isa('Rose::DB'), "db() - $db_type");

  is($db->dbh, $o3->dbh, "dbh() - $db_type");

  my $o4 = MyMySQLObject->new(id => 999);
  ok(!$o4->load(speculative => 1), "load() nonexistent - $db_type");
  ok($o4->not_found, "not_found() 2 - $db_type");

  eval { $o->items('z') };

  ok($@ =~ /Invalid value/, "set invalid value - $db_type");


  $o->items('a', 'b');
  $o->nums([ 4, 5, 6 ]);

  ok($o->save, "save() 3 - $db_type");
  ok($o->load, "load() 4 - $db_type");

  is_deeply([ sort $o->items ], [ qw(a b) ], "set default - $db_type");

  is($o->nums->[0], 4, "load() verify 10 (array value) - $db_type");
  is($o->nums->[1], 5, "load() verify 11 (array value) - $db_type");
  is($o->nums->[2], 6, "load() verify 12 (array value) - $db_type");

  my @a = $o->nums;

  is($a[0], 4, "load() verify 13 (array value) - $db_type");
  is($a[1], 5, "load() verify 14 (array value) - $db_type");
  is($a[2], 6, "load() verify 15 (array value) - $db_type");
  is(@a, 3, "load() verify 16 (array value) - $db_type");

  ok($o->delete, "delete() - $db_type");

  $o = MyMySQLObject->new(name => 'John', id => 9);
  $o->save_col(22);

  ok($o->save, "save() 4 - $db_type");
  $o->save_col(50);
  ok($o->save, "save() 5 - $db_type");

  $ouk = MyMySQLObject->new(save_col => 50);
  ok($ouk->load, "load() aliased unique key - $db_type");

  eval { $o->meta->alias_column(nonesuch => 'foo') };
  ok($@, "alias_column() nonesuch - $db_type");

  # This is okay now
  #eval { $o->meta->alias_column(id => 'foo') };
  #ok($@, "alias_column() primary key - $db_type");

  $o = MyMySQLObject->new(id => 777);

  $o->meta->error_mode('fatal');

  $o->dbh->{'PrintError'} = 0;

  eval { $o->load };
  ok($@ && $o->not_found, "load() not found fatal - $db_type");

  my $old_table = $o->meta->table;

  $o->meta->table('nonesuch');

  eval { $o->load };
  ok($@ && !$o->not_found, "load() fatal - $db_type");

  eval { $o->save };
  ok($@, "save() fatal - $db_type");

  $o->meta->table($old_table);  
  $o->meta->error_mode('return');

  $o = MyMPKMySQLObject->new(name => 'John');

  ok($o->save, "save() 1 multi-value primary key with generated values - $db_type");

  is($o->k1, 1, "save() verify 1 multi-value primary key with generated values - $db_type");
  is($o->k2, 2, "save() verify 2 multi-value primary key with generated values - $db_type");

  $o = MyMPKMySQLObject->new(name => 'Alex');

  ok($o->save, "save() 2 multi-value primary key with generated values - $db_type");

  is($o->k1, 3, "save() verify 3 multi-value primary key with generated values - $db_type");
  is($o->k2, 4, "save() verify 4 multi-value primary key with generated values - $db_type");

  is($ox->bitz3->to_Bin(), '0011', "spot check bitfield 3 - $db_type");

  $ox->bitz3->Bit_On(3);
  is($ox->bitz3->to_Bin(), '1011', "spot check bitfield 4 - $db_type");

  $ox->save(insert => 1);

  $ox = MyMySQLObject->new(id => $ox->id)->load;
  is($ox->bitz3->to_Bin(), '1011', "spot check bitfield 5 - $db_type");

  $ox->bitz3->Bit_On(2);
  $ox->save;
  $ox = MyMySQLObject->new(id => $ox->id)->load;
  is($ox->bitz3->to_Bin(), '1111', "spot check bitfield 6 - $db_type");

  $o = MyMySQLObject->new(id => 9999); # no such id

  $o->meta->error_mode('fatal');

  eval { $o->load() };
  ok($@, "load() non-speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 1 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  $o->meta->default_load_speculative(1);

  ok(!$o->load(), "load() speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 2 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  $o->meta->default_load_speculative(0);

  $o = MyMySQLObject->new(id => 1)->load;

  is($o->dur->months, 2, "interval months 1 - $db_type");
  is($o->dur->days, 5, "interval days 1 - $db_type");
  is($o->dur->seconds, 3, "interval seconds 1 - $db_type");

  $o->dur(DateTime::Duration->new(years => 7, nanoseconds => 3000));

  is($o->dur->in_units('years'), 7, "interval in_units years 1 - $db_type");
  is($o->dur->in_units('months'), 84, "interval in_units months 1 - $db_type");
  is($o->dur->nanoseconds, 3000, "interval nanoseconds 1 - $db_type");
  is($o->dur->days, 0, "interval days 2 - $db_type");
  is($o->dur->minutes, 0, "interval minutes 2 - $db_type");
  is($o->dur->seconds, 0, "interval seconds 2 - $db_type");

  $o->save;

  $o = MyMySQLObject->new(id => $o->id)->load;

  is($o->dur->in_units('years'), 7, "interval in_units years 2 - $db_type");
  is($o->dur->in_units('months'), 84, "interval in_units months 2 - $db_type");
  is($o->dur->nanoseconds, 3000, "interval nanoseconds 2 - $db_type");
  is($o->dur->days, 0, "interval days 3 - $db_type");
  is($o->dur->minutes, 0, "interval minutes 3 - $db_type");
  is($o->dur->seconds, 0, "interval seconds 3 - $db_type");

  is($o->meta->column('dur')->scale, 6, "interval scale - $db_type");

  is($o->epoch(format => '%Y-%m-%d %H:%M:%S'), '1999-11-30 21:30:00', "epoch 1 - $db_type");

  $o->hiepoch('943997400.123456');
  is($o->hiepoch(format => '%Y-%m-%d %H:%M:%S.%6N'), '1999-11-30 21:30:00.123456', "epoch hires 1 - $db_type");

  $o->epoch('5/6/1980 12:34:56');

  $o->save;

  $o = MyMySQLObject->new(id => $o->id)->load;

  is($o->epoch(format => '%Y-%m-%d %H:%M:%S'), '1980-05-06 12:34:56', "epoch 2 - $db_type");
  is($o->hiepoch(format => '%Y-%m-%d %H:%M:%S.%6N'), '1999-11-30 21:30:00.123456', "epoch hires 2 - $db_type");

  is($o->tee_time5->as_string, '12:34:56.12345', "time(5) - $db_type");

  $o->tee_time0('1pm');
  eval { $o->tee_time('allballs') };
  ok($@, "allballs - $db_type");
  $o->tee_time('0:00');
  $o->tee_time9('now');
  $o->save;

  $o = MyMySQLObject->new(id => $o->id)->load;
  is($o->tee_time->as_string, '00:00:00', "time allballs - $db_type");

  if($Have_HiRes_Time)
  {
    ok($o->tee_time9->as_string =~ /^\d\d:\d\d:\d\d\.\d+$/, "time now - $db_type");
  }
  else
  {
    ok($o->tee_time9->as_string =~ /^\d\d:\d\d:\d\d$/, "time now - $db_type");
  }

  $o->tee_time(Time::Clock->new->parse('6:30 PM'));
  $o->save;

  $o = MyMySQLObject->new(id => $o->id)->load;
  is($o->tee_time->as_string, '18:30:00', "time 6:30 PM - $db_type");

  MyMySQLObject->meta->column('save')->default('x');
  MyMySQLObject->meta->make_column_methods(replace_existing => 1);

  $o->meta->default_load_speculative(0);

  $o = MyMySQLObject->new(k1 => 1, k3 => 3);
  ok(!$o->load(speculative => 1), "load default key - $db_type"); 

  eval { $o->load(use_key => 'id') };
  ok($@, "use_key no such key - $db_type");

  $o->load(use_key => 'k1_k2_k3');
  is($o->k1, 1, "load specific key 1 - $db_type");  
  is($o->k3, 3, "load specific key 2 - $db_type");
  is($o->name, 'John', "load specific key 3 - $db_type");
}

#
# Informix
#

SKIP: foreach my $db_type ('informix')
{
  skip("Informix tests", 73)  unless($HAVE_INFORMIX);

  Rose::DB->default_type($db_type);

  my $o = MyInformixObject->new(name => 'John', 
                                id   => 1,
                                k1   => 1,
                                k2   => undef,
                                k3   => 3);

  ok(ref $o && $o->isa('MyInformixObject'), "new() 1 - $db_type");

  $o->meta->allow_inline_column_values(1);

  $o->flag2('true');
  $o->date_created('current year to fraction(5)');
  $o->last_modified($o->date_created);
  $o->save_col(22);

  if(rand >= 0.5)
  {
    ok($o->save, "save() 1 - $db_type");
  }
  else
  {
    ok($o->insert, "insert() 1 - $db_type");
  }

  ok($o->load, "load() 1 - $db_type");

  $o->name('C' x 50);
  is($o->name, 'C' x 32, "varchar truncation - $db_type");

  $o->name('John');

  $o->code('A');
  is($o->code, 'A     ', "character padding - $db_type");

  $o->code('C' x 50);
  is($o->code, 'C' x 6, "character truncation - $db_type");

  my $ouk;
  ok($ouk = MyInformixObject->new(k1 => 1,
                                  k2 => undef,
                                  k3 => 3)->load, "load() uk 1 - $db_type");

  ok(!$ouk->not_found, "not_found() uk 1 - $db_type");

  is($ouk->id, 1, "load() uk 2 - $db_type");
  is($ouk->name, 'John', "load() uk 3 - $db_type");

  ok($ouk->save, "save() uk 1 - $db_type");

  my $o2 = MyInformixObject->new(id => $o->id);

  ok(ref $o2 && $o2->isa('MyInformixObject'), "new() 2 - $db_type");

  is($o2->bits->to_Bin, '00101', "bits() (bitfield default value) - $db_type");

  ok($o2->load, "load() 2 - $db_type");
  ok(!$o2->not_found, "not_found() 1 - $db_type");

  is($o2->name, $o->name, "load() verify 1 - $db_type");
  is($o2->date_created, $o->date_created, "load() verify 2 - $db_type");
  is($o2->last_modified, $o->last_modified, "load() verify 3 - $db_type");
  is($o2->status, 'active', "load() verify 4 (default value) - $db_type");
  is($o2->flag, 1, "load() verify 5 (default boolean value) - $db_type");
  is($o2->flag2, 1, "load() verify 6 (boolean value) - $db_type");
  is($o2->save_col, 22, "load() verify 7 (aliased column) - $db_type");
  is($o2->start->ymd, '1980-12-24', "load() verify 8 (date value) - $db_type");

  $o2->set_status('foo');
  is($o2->get_status, 'foo', 'get_status()');
  $o2->set_status('active');
  eval { $o2->set_status };
  ok($@, 'set_status()');

  is($o2->bits->to_Bin, '00101', "load() verify 9 (bitfield value) - $db_type");

  my $clone = $o2->clone;
  ok($o2->start eq $clone->start, "clone() 1 - $db_type");
  $clone->start->set(year => '1960');
  ok($o2->start ne $clone->start, "clone() 2 - $db_type");

  $o2->name('John 2');
  $o2->start('5/24/2001');

  sleep(1); # keep the last modified dates from being the same

  $o2->last_modified('current year to second');
  ok($o2->save, "save() 2 - $db_type");
  ok($o2->load, "load() 3 - $db_type");

  is($o2->date_created, $o->date_created, "save() verify 1 - $db_type");
  ok($o2->last_modified ne $o->last_modified, "save() verify 2 - $db_type");
  is($o2->start->ymd, '2001-05-24', "save() verify 3 (date value) - $db_type");

  my $o3 = MyInformixObject->new();

  my $db = $o3->db or die $o3->error;

  ok(ref $db && $db->isa('Rose::DB'), "db() - $db_type");

  is($db->dbh, $o3->dbh, "dbh() - $db_type");

  my $o4 = MyInformixObject->new(id => 999);
  ok(!$o4->load(speculative => 1), "load() nonexistent - $db_type");
  ok($o4->not_found, "not_found() 2 - $db_type");

  $o->nums([ 4, 5, 6 ]);
  $o->names([ qw(a b 3.1) ]);

  ok($o->save, "save() 3 - $db_type");
  ok($o->load, "load() 4 - $db_type");

  is($o->nums->[0], 4, "load() verify 10 (array value) - $db_type");
  is($o->nums->[1], 5, "load() verify 11 (array value) - $db_type");
  is($o->nums->[2], 6, "load() verify 12 (array value) - $db_type");

  $o->nums(7, 8, 9);

  my @a = $o->nums;

  is($a[0], 7, "load() verify 13 (array value) - $db_type");
  is($a[1], 8, "load() verify 14 (array value) - $db_type");
  is($a[2], 9, "load() verify 15 (array value) - $db_type");
  is(@a, 3, "load() verify 16 (array value) - $db_type");

  is($o->names->[0], 'a', "load() verify 10 (set value) - $db_type");
  is($o->names->[1], 'b', "load() verify 11 (set value) - $db_type");
  is($o->names->[2], '3.1', "load() verify 12 (set value) - $db_type");

  $o->names('c', 'd', '4.2');

  @a = $o->names;

  is($a[0], 'c', "load() verify 13 (set value) - $db_type");
  is($a[1], 'd', "load() verify 14 (set value) - $db_type");
  is($a[2], '4.2', "load() verify 15 (set value) - $db_type");
  is(@a, 3, "load() verify 16 (set value) - $db_type");

  ok($o->delete, "delete() - $db_type");

  $o = MyInformixObject->new(name => 'John', id => 9);

  $o->flag2('true');
  $o->date_created('current year to fraction(5)');
  $o->last_modified($o->date_created);
  $o->save_col(22);

  ok($o->save, "save() 4 - $db_type");
  $o->save_col(50);

  ok($o->save, "save() 5 - $db_type");

  $ouk = MyInformixObject->new(save_col => 50);
  ok($ouk->load, "load() aliased unique key - $db_type");

  eval { $o->meta->alias_column(nonesuch => 'foo') };
  ok($@, "alias_column() nonesuch - $db_type");

  # This is okay now
  #eval { $o->meta->alias_column(id => 'foo') };
  #ok($@, "alias_column() primary key - $db_type");

  $o = MyInformixObject->new(id => 777);

  $o->meta->error_mode('fatal');

  $o->dbh->{'PrintError'} = 0;

  eval { $o->load };
  ok($@ && $o->not_found, "load() not found fatal - $db_type");

  $o->id('abc');

  eval { $o->load };
  ok($@ && !$o->not_found, "load() fatal - $db_type");

  eval { $o->save };
  ok($@, "save() fatal - $db_type");

  #$o->meta->error_mode('return');

  $o = MyInformixObject->new(id => 9999); # no such id

  $o->meta->error_mode('fatal');

  eval { $o->load() };
  ok($@, "load() non-speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 1 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  $o->meta->default_load_speculative(1);

  ok(!$o->load(), "load() speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 2 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");
}

#
# SQLite
#

SKIP: foreach my $db_type ('sqlite')
{
  skip("SQLite tests", 75)  unless($HAVE_SQLITE);

  Rose::DB->default_type($db_type);

  my $o = MySQLiteObject->new(name => 'John',
                              k1   => 1,
                              k2   => undef,
                              k3   => 3);

  ok(ref $o && $o->isa('MySQLiteObject'), "new() 1 - $db_type");

  $o->flag2('true');
  $o->date_created('now');
  $o->last_modified($o->date_created);
  $o->save_col(22);

  if(rand >= 0.5)
  {
    ok($o->save, "save() 1 - $db_type");
  }
  else
  {
    ok($o->insert, "insert() 1 - $db_type");
  }

  ok($o->load, "load() 1 - $db_type");

  $o->name('C' x 50);
  is($o->name, 'C' x 32, "varchar truncation - $db_type");

  $o->name('John');

  $o->code('A');
  is($o->code, 'A     ', "character padding - $db_type");

  $o->code('C' x 50);
  is($o->code, 'C' x 6, "character truncation - $db_type");

  my $ouk;
  ok($ouk = MySQLiteObject->new(k1 => 1,
                                k2 => undef,
                                k3 => 3)->load, "load() uk 1 - $db_type");

  ok(!$ouk->not_found, "not_found() uk 1 - $db_type");

  is($ouk->id->[0], 1, "load() uk 2 - $db_type");
  is($ouk->name, 'John', "load() uk 3 - $db_type");

  ok($ouk->save, "save() uk 1 - $db_type");

  my $o2 = MySQLiteObject->new(id => $o->id);

  ok(ref $o2 && $o2->isa('MySQLiteObject'), "new() 2 - $db_type");

  is($o2->bits->to_Bin, '00101', "bits() (bitfield default value) - $db_type");

  ok($o2->load, "load() 2 - $db_type");
  ok(!$o2->not_found, "not_found() 1 - $db_type");

  is($o2->name, $o->name, "load() verify 1 - $db_type");
  is($o2->date_created, $o->date_created, "load() verify 2 - $db_type");
  is($o2->last_modified, $o->last_modified, "load() verify 3 - $db_type");
  is($o2->status, 'active', "load() verify 4 (default value) - $db_type");
  is($o2->flag, 1, "load() verify 5 (default boolean value) - $db_type");
  is($o2->flag2, 1, "load() verify 6 (boolean value) - $db_type");
  is($o2->save_col, 22, "load() verify 7 (aliased column) - $db_type");
  is($o2->start->ymd, '1980-12-24', "load() verify 8 (date value) - $db_type");

  $o2->set_status('foo');
  is($o2->get_status, 'foo', 'get_status()');
  $o2->set_status('active');
  eval { $o2->set_status };
  ok($@, 'set_status()');

  is($o2->bits->to_Bin, '00101', "load() verify 9 (bitfield value) - $db_type");

  my $clone = $o2->clone;
  ok($o2->start eq $clone->start, "clone() 1 - $db_type");
  $clone->start->set(year => '1960');
  ok($o2->start ne $clone->start, "clone() 2 - $db_type");

  $o2->name('John 2');
  $o2->start('5/24/2001');

  sleep(1); # keep the last modified dates from being the same

  $o2->last_modified('now');
  ok($o2->save, "save() 2 - $db_type");
  ok($o2->load, "load() 3 - $db_type");

  is($o2->date_created, $o->date_created, "save() verify 1 - $db_type");
  ok($o2->last_modified ne $o->last_modified, "save() verify 2 - $db_type");
  is($o2->start->ymd, '2001-05-24', "save() verify 3 (date value) - $db_type");

  my $o3 = MySQLiteObject->new();

  my $db = $o3->db or die $o3->error;

  ok(ref $db && $db->isa('Rose::DB'), "db() - $db_type");

  is($db->dbh, $o3->dbh, "dbh() - $db_type");

  my $o4 = MySQLiteObject->new(id => 999);
  ok(!$o4->load(speculative => 1), "load() nonexistent - $db_type");
  ok($o4->not_found, "not_found() 2 - $db_type");

  $o->nums([ 4, 5, 6 ]);
  ok($o->save, "save() 3 - $db_type");
  ok($o->load, "load() 4 - $db_type");

  is($o->nums->[0], 4, "load() verify 10 (array value) - $db_type");
  is($o->nums->[1], 5, "load() verify 11 (array value) - $db_type");
  is($o->nums->[2], 6, "load() verify 12 (array value) - $db_type");

  my @a = $o->nums;

  is($a[0], 4, "load() verify 13 (array value) - $db_type");
  is($a[1], 5, "load() verify 14 (array value) - $db_type");
  is($a[2], 6, "load() verify 15 (array value) - $db_type");
  is(@a, 3, "load() verify 16 (array value) - $db_type");

  ok($o->delete, "delete() - $db_type");

  $o = MySQLiteObject->new(name => 'John', id => 9);
  $o->save_col(22);
  ok($o->save, "save() 4 - $db_type");
  $o->save_col(50);
  ok($o->save, "save() 5 - $db_type");

  $ouk = MySQLiteObject->new(save_col => 50);
  ok($ouk->load, "load() aliased unique key - $db_type");

  eval { $o->meta->alias_column(nonesuch => 'foo') };
  ok($@, "alias_column() nonesuch - $db_type");

  # This is okay now
  #eval { $o->meta->alias_column(id => 'foo') };
  #ok($@, "alias_column() primary key - $db_type");

  $o = MySQLiteObject->new(id => 777);

  $o->meta->error_mode('fatal');

  $o->dbh->{'PrintError'} = 0;

  eval { $o->load };
  ok($@ && $o->not_found, "load() not found fatal - $db_type");

  my $old_table = $o->meta->table;

  $o->meta->table('nonesuch');

  eval { $o->load };
  ok($@ && !$o->not_found, "load() fatal - $db_type");

  eval { $o->save };
  ok($@, "save() fatal - $db_type");

  $o->meta->table($old_table);  
  $o->meta->error_mode('return');

  $o = MyMPKSQLiteObject->new(name => 'John');

  ok($o->save, "save() 1 multi-value primary key with generated values - $db_type");

  is($o->k1, 1, "save() verify 1 multi-value primary key with generated values - $db_type");
  is($o->k2, 2, "save() verify 2 multi-value primary key with generated values - $db_type");

  $o = MyMPKSQLiteObject->new(name => 'Alex');

  ok($o->save, "save() 2 multi-value primary key with generated values - $db_type");

  is($o->k1, 3, "save() verify 3 multi-value primary key with generated values - $db_type");
  is($o->k2, 4, "save() verify 4 multi-value primary key with generated values - $db_type");

  $o = MySQLiteObject->new(id => 9999); # no such id

  $o->meta->error_mode('fatal');

  eval { $o->load() };
  ok($@, "load() non-speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 1 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  $o->meta->default_load_speculative(1);

  ok(!$o->load(), "load() speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 2 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  #
  # Test SQLite BLOB support
  #

  my $blob = "abc\0def";
  $o = MySQLiteObject->new(id => 888, name => 'Blob', data => $blob);
  $o->save;

  $o = MySQLiteObject->new(id => $o->id)->load;
  is($o->data, $blob, "blob check - $db_type");
}

SKIP: foreach my $db_type (qw(oracle))
{
  skip("Oracle tests", 85)  unless($HAVE_ORACLE);

  Rose::DB->default_type($db_type);

  TEST_HACK:
  {
    no warnings;
    *MyOracleObject::init_db = sub { Rose::DB->new($db_type) };
  }

  my $o = MyOracleObject->new(name => 'John', 
                              k1   => 1,
                              k2   => undef,
                              k3   => 3);

  ok(ref $o && $o->isa('MyOracleObject'), "new() 1 - $db_type");

  $o->flag2('TRUE');
  $o->date_created('now');
  $o->date_created_tz('now');
  $o->timestamp_tz2('now');
  $o->last_modified($o->date_created);
  $o->save_col(7);

  if(rand >= 0.5)
  {
    ok($o->save, "save() 1 - $db_type");
  }
  else
  {
    ok($o->insert, "insert() 1 - $db_type");
  }

  is($o->meta->primary_key->sequence_names->[0], 'ROSE_DB_OBJECT_TEST_ID_SEQ', 
     "pk sequence name - $db_type");

  ok(is_in_db($o), "is_in_db - $db_type");

  is($o->id, 1, "auto-generated primary key - $db_type");

  if(oracle_is_broken())
  {
    SKIP: { skip("tests that trigger the dreaded ORA-00600 kpofdr-long error", 4) }
  }
  else
  {
    ok($o->load, "load() 1 - $db_type");

    is($o->date_created->time_zone->name, 'floating', "timestamp without time zone - $db_type");
    isnt($o->date_created_tz->time_zone->name, 'floating', "timestamp with time zone - $db_type");
    is($o->timestamp_tz2->time_zone->name, 'Antarctica/Vostok', "timestamp with time zone override - $db_type");  

    # Make sure we're not in the Antarctica/Vostok time zone or any other
    # time zone with the same offset.
    my $error;

    TRY:
    {
      local $@;

      eval
      {
        my $dt1 = DateTime->now(time_zone => 'local');
        my $dt2 = $dt1->clone;
        $dt2->set_time_zone('Antarctica/Vostok');
        die "local is equivalent to Antarctica/Vostok"  if($dt1->iso8601 eq $dt2->iso8601);
      };

      $error = $@;
    }

    if($error)
    {
      SKIP: { skip("timestamp with time zone time change - $db_type", 2) }
    }
    else
    {
      isnt($o->date_created_tz->iso8601, $o->timestamp_tz2->iso8601, "timestamp with time zone time change - $db_type");

      $o->save;
      $o->load;

      my $dt = $o->timestamp_tz2->clone;
      $dt->set_time_zone($o->date_created_tz->time_zone);

      is($o->date_created_tz->iso8601, $dt->iso8601, "timestamp with time zone time change 2 - $db_type");
    }

    $o->name('C' x 50);
    is($o->name, 'C' x 32, "varchar truncation - $db_type");

    $o->name('John');

    $o->code('A');
    is($o->code, 'A     ', "character padding - $db_type");

    $o->code('C' x 50);
    is($o->code, 'C' x 6, "character truncation - $db_type");
  }

  my $ouk;
  ok($ouk = MyOracleObject->new(k1 => 1,
                                k2 => undef,
                                k3 => 3)->load, "load() uk 1 - $db_type");

  ok(!$ouk->not_found, "not_found() uk 1 - $db_type");

  is($ouk->id, 1, "load() uk 2 - $db_type");
  is($ouk->name, 'John', "load() uk 3 - $db_type");

  ok($ouk->save, "save() uk 1 - $db_type");

  my $o2 = MyOracleObject->new(id => $o->id);

  ok(ref $o2 && $o2->isa('MyOracleObject'), "new() 2 - $db_type");

  is($o2->bits->to_Bin, '00101', "bits() (bitfield default value) - $db_type");

  if(oracle_is_broken())
  {
    SKIP: { skip("tests that trigger the dreaded ORA-00600 kpofdr-long error", 22) }
  }
  else
  {
    ok($o2->load, "load() 2 - $db_type");
    ok(!$o2->not_found, "not_found() 1 - $db_type");

    is($o2->name, $o->name, "load() verify 1 - $db_type");
    is($o2->date_created, $o->date_created, "load() verify 2 - $db_type");
    is($o2->last_modified, $o->last_modified, "load() verify 3 - $db_type");
    is($o2->status, 'active', "load() verify 4 (default value) - $db_type");
    is($o2->flag, 1, "load() verify 5 (default boolean value) - $db_type");
    is($o2->flag2, 1, "load() verify 6 (boolean value) - $db_type");
    is($o2->save_col, 7, "load() verify 7 (aliased column) - $db_type");
    is($o2->start_date->ymd, '1980-12-24', "load() verify 8 (date value) - $db_type");

    $o2->set_status('foo');
    is($o2->get_status, 'foo', "get_status() - $db_type");
    $o2->set_status('active');
    eval { $o2->set_status };
    ok($@, "set_status() - $db_type");

    is($o2->bits->to_Bin, '00101', "load() verify 9 (bitfield value) - $db_type");

    my $clone = $o2->clone;
    ok($o2->start_date eq $clone->start_date, "clone() 1 - $db_type");
    $clone->start_date->set(year => '1960');
    ok($o2->start_date ne $clone->start_date, "clone() 2 - $db_type");

    $o2->start_date('5/24/2001');

    sleep(1); # keep the last modified dates from being the same

    $o2->last_modified('now');
    ok($o2->save, "save() 2 - $db_type");
    ok($o2->load, "load() 3 - $db_type");

    ok(!has_modified_columns($o2), "no modified columns after load() - $db_type");

    $o2->name('John 2');
    $o2->save(changes_only => 1);

    is($o2->date_created, $o->date_created, "save() verify 1 - $db_type");
    ok($o2->last_modified ne $o->last_modified, "save() verify 2 - $db_type");
    is($o2->start_date->ymd, '2001-05-24', "save() verify 3 (date value) - $db_type");

    my $bo = MyOracleObject->new(id => $o->id);
    $bo->load;
    $bo->flag(0);
    $bo->save;

    $bo = MyOracleObject->new(id => $o->id);
    $bo->load;

    ok(!$bo->flag, "boolean check - $db_type");

    $bo->flag(0);
    $bo->save;
  }

  my $o3 = MyOracleObject->new();

  my $db = $o3->db or die $o3->error;

  ok(ref $db && $db->isa('Rose::DB'), "db() - $db_type");

  is($db->dbh, $o3->dbh, "dbh() - $db_type");

  my $o4 = MyOracleObject->new(id => 999);
  ok(!$o4->load(speculative => 1), "load() nonexistent - $db_type");
  ok($o4->not_found, "not_found() 2 - $db_type");

  ok($o->load, "load() 4 - $db_type");

  my $o5 = MyOracleObject->new(id => $o->id);

  ok($o5->load, "load() 5 - $db_type");

  $o5->nums([ 4, 5, 6 ]);
  ok($o5->save, "save() 4 - $db_type");
  ok($o->load, "load() 6 - $db_type");

  is($o5->nums->[0], 4, "load() verify 10 (array value) - $db_type");
  is($o5->nums->[1], 5, "load() verify 11 (array value) - $db_type");
  is($o5->nums->[2], 6, "load() verify 12 (array value) - $db_type");

  my @a = $o5->nums;

  is($a[0], 4, "load() verify 13 (array value) - $db_type");
  is($a[1], 5, "load() verify 14 (array value) - $db_type");
  is($a[2], 6, "load() verify 15 (array value) - $db_type");
  is(@a, 3, "load() verify 16 (array value) - $db_type");

  ok($o->delete, "delete() - $db_type");

  $o = MyOracleObject->new(name => 'John', id => 9);
  $o->save_col(22);
  ok($o->save, "save() 4 - $db_type");
  $o->save_col(50);
  ok($o->save, "save() 5 - $db_type");

  $ouk = MyOracleObject->new(save_col => 50);
  ok($ouk->load, "load() aliased unique key - $db_type");

  eval { $o->meta->alias_column(nonesuch => 'foo') };
  ok($@, "alias_column() nonesuch - $db_type");

  # This is okay now
  #eval { $o->meta->alias_column(id => 'foo') };
  #ok($@, "alias_column() primary key - $db_type");

  $o = MyOracleObject->new(id => 777);

  $o->meta->error_mode('fatal');

  $o->dbh->{'PrintError'} = 0;

  eval { $o->load };
  ok($@ && $o->not_found, "load() not found fatal - $db_type");

  $o->id('abc');

  eval { $o->load };
  ok($@ && !$o->not_found, "load() fatal - $db_type");

  eval { $o->save };
  ok($@, "save() fatal - $db_type");

  $o = MyOracleObject->new(id => 9999); # no such id

  $o->meta->error_mode('fatal');

  eval { $o->load() };
  ok($@, "load() non-speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 1 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  $o->meta->default_load_speculative(1);

  ok(!$o->load(), "load() speculative implicit - $db_type");  
  ok(!$o->load(speculative => 1), "load() speculative explicit 2 - $db_type");
  eval { $o->load(speculative => 0) };
  ok($@, "load() non-speculative explicit 2 - $db_type");

  $o = MyOracleObject->new(name => 'Sequence Test', 
                           k1   => 4,
                           k2   => 5,
                           k3   => 6,
                           key  => 123);

  $o->save;

  like($o->id, qr/^\d+$/, "save() serial - $db_type");

  # Select for update tests

  $o = MyOracleObject->new(id => $o->id)->load(for_update => 1, lock => { columns => [ qw(k2 k3) ] });

  # Silence errors in eval blocks below
  Rose::DB->modify_db(type => $db_type)->print_error(0);

  eval
  {
    $o =
      MyOracleObject->new(id => $o->id)->load(
        lock =>
        {
          type   => 'for update',
          on     => [ qw(k2 k3) ],
          nowait => 1,
        });
  };

  ok($@, "select for update failure - $db_type");

  my $lo;

  eval
  {
    $lo = MyOracleObject->new(id => $o->id);
    $lo->load(lock => { for_update => 1, nowait => 1 });
  };

  is(DBI->err, 54, "select for update no wait ORA-00054 - $db_type");
  ok($@, "select for update no wait - $db_type");

  eval
  {
    $lo = MyOracleObject->new(id => $o->id);
    $lo->load(lock => { type => 'for update', wait => 1 });
  };

  is(DBI->err, 30006, "select for update wait 1 ORA-30006 - $db_type");
  ok($@, "select for update wait 1 - $db_type");

  $o->save;

  Rose::DB->modify_db(type => $db_type)->print_error(1);

  # Reset for next trip through loop (if any)
  $o->meta->default_load_speculative(0);
  $o->meta->error_mode('return');

  $o = MyOracleObject->new(key => 123);
  eval { $o->load };

  ok(!$@, "reserved-word load() - $db_type");
}

BEGIN
{
  #
  # PostgreSQL
  #

  my $dbh;

  eval 
  {
    $dbh = Rose::DB->new('pg_admin')->retain_dbh()
      or die Rose::DB->error;
  };

  if(!$@ && $dbh)
  {
    our $HAVE_PG = 1;

    # Drop existing table and create schema, ignoring errors
    {
      local $dbh->{'RaiseError'} = 0;
      local $dbh->{'PrintError'} = 0;
      $dbh->do('DROP TABLE rose_db_object_test');
      $dbh->do('DROP TABLE rose_db_object_private.rose_db_object_test');
      $dbh->do('DROP TABLE rose_db_object_chkpass_test');
      $dbh->do('CREATE SCHEMA rose_db_object_private');
    }

    our $PG_HAS_CHKPASS = pg_has_chkpass();

    $dbh->do(<<"EOF");
CREATE TABLE rose_db_object_test
(
  id              SERIAL NOT NULL PRIMARY KEY,
  k1              INT,
  k2              INT,
  k3              INT,
  @{[ $PG_HAS_CHKPASS ? 'passwd CHKPASS,' : '' ]}
  name            VARCHAR(32) NOT NULL,
  code            CHAR(6),
  flag            BOOLEAN NOT NULL,
  flag2           BOOLEAN,
  status          VARCHAR(32) DEFAULT 'active',
  bitz            BIT(5) NOT NULL DEFAULT B'00101',
  decs            DECIMAL(10,2),
  start           DATE,
  save            INT,
  nums            INT[],
  dur             INTERVAL(6) DEFAULT '2 months 5 days 3 seconds',
  epoch           INT DEFAULT 943997400,
  hiepoch         DECIMAL(16,6),
  bint1           BIGINT DEFAULT 9223372036854775800,
  bint2           BIGINT DEFAULT -9223372036854775800,
  bint3           BIGINT,
  bint4           BIGINT,
  tee_time        TIME,
  tee_time0       TIME(0),
  tee_time5       TIME(5),
  tee_time9       TIME(9),
  last_modified   TIMESTAMP,
  date_created    TIMESTAMP,
  date_created_tz TIMESTAMP WITH TIME ZONE,
  timestamp_tz2   TIMESTAMP WITH TIME ZONE,

  UNIQUE(k1, k2, k3)
)
EOF

    $dbh->do(<<"EOF");
CREATE TABLE rose_db_object_private.rose_db_object_test
(
  id              SERIAL NOT NULL PRIMARY KEY,
  k1              INT,
  k2              INT,
  k3              INT,
  @{[ $PG_HAS_CHKPASS ? 'passwd CHKPASS,' : '' ]}
  name            VARCHAR(32) NOT NULL,
  code            CHAR(6),
  flag            BOOLEAN NOT NULL,
  flag2           BOOLEAN,
  status          VARCHAR(32) DEFAULT 'active',
  bitz            BIT(5) NOT NULL DEFAULT B'00101',
  decs            DECIMAL(10,2),
  start           DATE,
  save            INT,
  nums            INT[],
  dur             INTERVAL(6) DEFAULT '2 months 5 days 3 seconds',
  epoch           INT DEFAULT 943997400,
  hiepoch         DECIMAL(16,6),
  bint1           BIGINT DEFAULT 9223372036854775800,
  bint2           BIGINT DEFAULT -9223372036854775800,
  bint3           BIGINT,
  bint4           BIGINT,
  tee_time        TIME,
  tee_time0       TIME(0),
  tee_time5       TIME(5),
  tee_time9       TIME(9),
  last_modified   TIMESTAMP,
  date_created    TIMESTAMP,
  date_created_tz TIMESTAMP WITH TIME ZONE,
  timestamp_tz2   TIMESTAMP WITH TIME ZONE,

  UNIQUE(k1, k2, k3)
)
EOF

    $dbh->disconnect;

    # Create test subclass

    package MyPgObject;

    use Rose::DB::Object::Helpers qw(clone);

    our @ISA = qw(Rose::DB::Object);

    sub init_db { Rose::DB->new('pg') }

    MyPgObject->meta->table('rose_db_object_test');

    MyPgObject->meta->columns
    (
      name     => { type => 'varchar', length => 32, overflow => 'truncate' },
      code     => { type => 'char', length => 6, overflow => 'truncate' },
      id       => { primary_key => 1, not_null => 1 },
      k1       => { type => 'int' },
      k2       => { type => 'int', lazy => 1 },
      k3       => { type => 'int' },
      ($PG_HAS_CHKPASS ? (passwd => { type => 'chkpass', alias => 'password' }) : ()),
      flag     => { type => 'boolean', default => 1 },
      flag2    => { type => 'boolean' },
      status   => { default => 'active', add_methods => [ qw(get set) ] },
      start    => { type => 'date', default => '12/24/1980', lazy => 1 },
      save     => { type => 'scalar' },
      nums     => { type => 'array' },
      bitz     => { type => 'bitfield', bits => 5, default => 101, alias => 'bits' },
      decs     => { type => 'decimal', precision => 10, scale => 2 },
      dur      => { type => 'interval', scale => 6, default => '2 months 5 days 3 seconds' },
      epoch    => { type => 'epoch', default => '11/30/1999 9:30pm' },
      hiepoch  => { type => 'epoch hires', default => '1144004926.123456' },
      bint1    => { type => 'bigint', default => '9223372036854775800' },
      bint2    => { type => 'bigint', default => '-9223372036854775800' },
      bint3    => { type => 'bigint', with_init => 1, check_in => [ '9223372036854775000', 5 ] },
      bint4    => { type => 'bigint' },
      tee_time  => { type => 'time' },
      tee_time0 => { type => 'time', scale => 0 },
      tee_time5 => { type => 'time', scale => 5, default => '12:34:56.123456789' },
      tee_time9 => { type => 'time', scale => 9 },
      #last_modified => { type => 'timestamp' },
      date_created => { type => 'timestamp' },
      date_created_tz => { type => 'timestamp with time zone' },
      timestamp_tz2 => { type => 'timestamp with time zone', time_zone => 'Antarctica/Vostok' },
      main::nonpersistent_column_definitions(),
    );

    MyPgObject->meta->add_unique_key('save');

    MyPgObject->meta->add_unique_key([ qw(k1 k2 k3) ]);

    MyPgObject->meta->add_columns(
      Rose::DB::Object::Metadata::Column::Timestamp->new(
        name => 'last_modified'));

    eval { MyPgObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() reserved method');

    MyPgObject->meta->alias_column(save => 'save_col');

    eval { MyPgObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() no override');

    MyPgObject->meta->initialize(preserve_existing => 1);

    Test::More::is(MyPgObject->meta->column('id')->is_primary_key_member, 1, 'is_primary_key_member - pg');
    Test::More::is(MyPgObject->meta->column('id')->primary_key_position, 1, 'primary_key_position 1 - pg');
    Test::More::ok(!defined MyPgObject->meta->column('k1')->primary_key_position, 'primary_key_position 2 - pg');
    MyPgObject->meta->column('k1')->primary_key_position(7);
    Test::More::ok(!defined MyPgObject->meta->column('k1')->primary_key_position, 'primary_key_position 3 - pg');

    sub init_bint3 { '9223372036854775000' }
  }

  #
  # MySQL
  #

  my $db_version;

  eval
  {
    my $db = Rose::DB->new('mysql_admin');
    $dbh = $db->retain_dbh() or die Rose::DB->error;
    $db_version = $db->database_version;
  };

  if(!$@ && $dbh)
  {
    our $HAVE_MYSQL = 1;

    # Drop existing table and create schema, ignoring errors
    {
      local $dbh->{'RaiseError'} = 0;
      local $dbh->{'PrintError'} = 0;
      $dbh->do('DROP TABLE rose_db_object_test');
      $dbh->do('DROP TABLE rose_db_object_test2');
    }

    # MySQL 5.0.3 or later has a completely stupid "native" BIT type
    my $bit_col1 = 
      ($db_version >= 5_000_003) ?
        q(bitz  BIT(5) NOT NULL DEFAULT B'00101') :
        q(bitz  BIT(5) NOT NULL DEFAULT '00101');

    my $bit_col2 = 
      ($db_version >= 5_000_003) ?
        q(bitz2  BIT(2) NOT NULL DEFAULT B'00') :
        q(bitz2  BIT(2) NOT NULL DEFAULT '0');

    my $set_col = 
      ($db_version >= 5_000_000) ?
        q(items  SET('a','b','c') NOT NULL DEFAULT 'a,c') :
        q(items  VARCHAR(255) NOT NULL DEFAULT 'a,c');

    my $engine = '';

    if(our $INNODB = mysql_supports_innodb())
    {
      $engine = 'ENGINE=InnoDB';
    }

    $dbh->do(<<"EOF");
CREATE TABLE rose_db_object_test
(
  id             INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  k1             INT,
  k2             INT,
  k3             INT,
  name           VARCHAR(32) NOT NULL,
  code           CHAR(6),
  flag           TINYINT(1) NOT NULL,
  flag2          TINYINT(1),
  status         VARCHAR(32) DEFAULT 'active',
  $bit_col1,
  $bit_col2,
  $set_col,
  bitz3          BIT(4),
  decs           FLOAT(10,2),
  nums           VARCHAR(255),
  start          DATE,
  save           INT,
  enums          ENUM('foo', 'bar', 'baz') DEFAULT 'foo',
  ndate          DATE NOT NULL DEFAULT '0000-00-00',
  dur            VARCHAR(255) DEFAULT '2 months 5 days 3 seconds',
  epoch          INT DEFAULT 943997400,
  hiepoch        DECIMAL(16,6),
  zepoch         INT NOT NULL DEFAULT 0,
  tee_time       VARCHAR(32),
  tee_time0      VARCHAR(32),
  tee_time5      VARCHAR(32) DEFAULT '12:34:56.123456789',
  tee_time9      VARCHAR(32),
  dt_default     TIMESTAMP,
  last_modified  TIMESTAMP,
  date_created   TIMESTAMP,

  UNIQUE(k1, k2, k3)
)
$engine
EOF

    $dbh->do(<<"EOF");
CREATE TABLE rose_db_object_test2
(
  k1             INT NOT NULL,
  k2             INT NOT NULL,
  name           VARCHAR(32),

  UNIQUE(k1, k2)
)
EOF

    $dbh->disconnect;

    # Create test subclass

    package MyMySQLObject;

    use Rose::DB::Object::Helpers qw(clone);

    our @ISA = qw(Rose::DB::Object);

    sub init_db { Rose::DB->new('mysql') }

    MyMySQLObject->meta->allow_inline_column_values(1);

    MyMySQLObject->meta->table('rose_db_object_test');

    MyMySQLObject->meta->columns
    (
      name     => { type => 'varchar', length => 32 },
      code     => { type => 'char', length => 6 },
      id       => { primary_key => 1, not_null => 1 },
      k1       => { type => 'int' },
      k2       => { type => 'int', lazy => 1 },
      k3       => { type => 'int' },
      flag     => { type => 'boolean', default => 1 },
      flag2    => { type => 'boolean' },
      status   => { default => 'active', methods => [ qw(get_set get set) ] },
      start    => { type => 'date', default => '12/24/1980', lazy => 1 },
      ndate    => { type => 'date', not_null => 1, default => '0000-00-00' },
      save     => { type => 'scalar' },
      nums     => { type => 'array' },
      enums    => { type => 'enum', values => [ qw(foo bar baz) ], default => 'foo' },
      bitz     => { type => 'bitfield', bits => 5, default => 101, alias => 'bits' },
      bitz2    => { type => 'bits', bits => 2, default => '0' },
      bitz3    => { type => 'bits', bits => 4 },
      items    => { type => 'set', check_in => [ qw(a b c) ], default => 'a,c' },
      decs     => { type => 'decimal', precision => 10, scale => 2 },
      dur      => { type => 'interval', scale => 6, default => '2 months 5 days 3 seconds' },
      epoch    => { type => 'epoch', default => '11/30/1999 9:30pm' },
      hiepoch  => { type => 'epoch hires', default => '1144004926.123456' },
      zepoch   => { type => 'epoch', default => 0, not_null => 1, time_zone => 'UTC' },
      tee_time  => { type => 'time' },
      tee_time0 => { type => 'time', scale => 0 },
      tee_time5 => { type => 'time', scale => 5, default => '12:34:56.123456789' },
      tee_time9 => { type => 'time', scale => 9 },
      dt_default => {  type => 'timestamp', default => 'now()' },
      last_modified => { type => 'timestamp' },
      date_created  => { type => 'timestamp' },
      main::nonpersistent_column_definitions(),
    );

    eval { MyMySQLObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() reserved method');

    MyMySQLObject->meta->alias_column(save => 'save_col');

    eval { MyMySQLObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() no override');

    MyMySQLObject->meta->add_unique_key('save');
    MyMySQLObject->meta->add_unique_key([ qw(k1 k2 k3) ]);

    MyMySQLObject->meta->initialize(preserve_existing => 1);

    Test::More::is(MyMySQLObject->meta->column('id')->is_primary_key_member, 1, 'is_primary_key_member - mysql');
    Test::More::is(MyMySQLObject->meta->column('id')->primary_key_position, 1, 'primary_key_position 1 - mysql');
    Test::More::ok(!defined MyMySQLObject->meta->column('k1')->primary_key_position, 'primary_key_position 2 - mysql');
    MyMySQLObject->meta->column('k1')->primary_key_position(7);
    Test::More::ok(!defined MyMySQLObject->meta->column('k1')->primary_key_position, 'primary_key_position 3 - mysql');

    package MyMPKMySQLObject;

    use Rose::DB::Object;

    our @ISA = qw(Rose::DB::Object);

    sub init_db { Rose::DB->new('mysql') }

    MyMPKMySQLObject->meta->table('rose_db_object_test2');

    MyMPKMySQLObject->meta->columns
    (
      k1          => { type => 'int', not_null => 1 },
      k2          => { type => 'int', not_null => 1 },
      name        => { type => 'varchar', length => 32 },
    );

    MyMPKMySQLObject->meta->primary_key_columns('k1', 'k2');

    my $i = 1;

    MyMPKMySQLObject->meta->setup
    (
      primary_key_generator => sub
      {
        my($meta, $db) = @_;

        my $k1 = $i++;
        my $k2 = $i++;

        return $k1, $k2;
      },
    );
  }

  #
  # Informix
  #

  eval
  {
    $dbh = Rose::DB->new('informix_admin')->retain_dbh()
      or die Rose::DB->error;
  };

  if(!$@ && $dbh)
  {
    our $HAVE_INFORMIX = 1;

    # Drop existing table and create schema, ignoring errors
    {
      local $dbh->{'RaiseError'} = 0;
      local $dbh->{'PrintError'} = 0;
      $dbh->do('DROP TABLE rose_db_object_test');
    }

    $dbh->do(<<"EOF");
CREATE TABLE rose_db_object_test
(
  id             SERIAL NOT NULL PRIMARY KEY,
  k1             INT,
  k2             INT,
  k3             INT,
  name           VARCHAR(32) NOT NULL,
  code           CHAR(6),
  flag           BOOLEAN NOT NULL,
  flag2          BOOLEAN,
  status         VARCHAR(32) DEFAULT 'active',
  bitz           VARCHAR(5) DEFAULT '00101' NOT NULL,
  decs           DECIMAL(10,2),
  nums           VARCHAR(255),
  start          DATE,
  save           INT,
  names          SET(VARCHAR(64) NOT NULL),
  last_modified  DATETIME YEAR TO FRACTION(5),
  date_created   DATETIME YEAR TO FRACTION(5)
)
EOF

    $dbh->disconnect;

    # Create test subclass

    package MyInformixObject;

    use Rose::DB::Object::Helpers qw(clone);

    our @ISA = qw(Rose::DB::Object);

    sub init_db { Rose::DB->new('informix') }

    MyInformixObject->meta->allow_inline_column_values(1);

    MyInformixObject->meta->table('rose_db_object_test');

    MyInformixObject->meta->columns
    (
      name     => { type => 'varchar', length => 32, overflow => 'truncate' },
      code     => { type => 'char', length => 6, overflow => 'truncate' },
      id       => { type => 'serial', primary_key => 1, not_null => 1 },
      k1       => { type => 'int' },
      k2       => { type => 'int', lazy => 1 },
      k3       => { type => 'int' },
      flag     => { type => 'boolean', default => 1 },
      flag2    => { type => 'boolean' },
      status   => { default => 'active', add_methods => [ qw(get set) ] },
      start    => { type => 'date', default => '12/24/1980', lazy => 1 },
      save     => { type => 'scalar' },
      nums     => { type => 'array' },
      bitz     => { type => 'bitfield', bits => 5, default => 101, alias => 'bits' },
      decs     => { type => 'decimal', precision => 10, scale => 2 },
      names    => { type => 'set' },
      last_modified => { type => 'timestamp' },
      date_created  => { type => 'datetime year to fraction(5)' },
      main::nonpersistent_column_definitions(),
    );

    eval { MyInformixObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() reserved method');

    MyInformixObject->meta->prepare_options({ix_CursorWithHold => 1});    

    MyInformixObject->meta->alias_column(save => 'save_col');

    eval { MyInformixObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() no override');

    MyInformixObject->meta->add_unique_key('save');
    MyInformixObject->meta->add_unique_key([ qw(k1 k2 k3) ]);

    MyInformixObject->meta->initialize(preserve_existing => 1);

    Test::More::is(MyInformixObject->meta->column('id')->is_primary_key_member, 1, 'is_primary_key_member - informix');
    Test::More::is(MyInformixObject->meta->column('id')->primary_key_position, 1, 'primary_key_position 1 - informix');
    Test::More::ok(!defined MyInformixObject->meta->column('k1')->primary_key_position, 'primary_key_position 2 - informix');
    MyInformixObject->meta->column('k1')->primary_key_position(7);
    Test::More::ok(!defined MyInformixObject->meta->column('k1')->primary_key_position, 'primary_key_position 3 - informix');
  }

  #
  # SQLite
  #

  eval 
  {
    $dbh = Rose::DB->new('sqlite_admin')->retain_dbh()
      or die Rose::DB->error;
  };

  if(!$@ && $dbh)
  {
    our $HAVE_SQLITE = 1;

    #
    # Method name conflict tests
    #

    local $@;

     eval
     {
       package MyNameConflictB;

       our @ISA = qw(Rose::DB::Object);

       sub init_db { Rose::DB->new('sqlite') }

       __PACKAGE__->meta->setup
       (
         table   => 'foob',
         columns => [ qw(id blee) ],
       );

       package MyNameConflictA;

       our @ISA = qw(Rose::DB::Object);

       sub init_db { Rose::DB->new('sqlite') }

       __PACKAGE__->meta->setup
       (
         table => 'fooa',
         columns => [ qw(bar baz) ],
         foreign_keys =>
         [
           new =>
           {
             class => 'MyNameConflictB',
             key_columns => { baz => 'id' },
           },
         ],
       );
     };

    like($@, qr/Rose::DB::Object defines a method with the same name/, 'method name conflict');

    # Drop existing table and create schema, ignoring errors
    {
      local $dbh->{'RaiseError'} = 0;
      local $dbh->{'PrintError'} = 0;
      $dbh->do('DROP TABLE rose_db_object_test');
      $dbh->do('DROP TABLE rose_db_object_test2');
    }

    $dbh->do(<<"EOF");
CREATE TABLE rose_db_object_test
(
  id             INTEGER PRIMARY KEY AUTOINCREMENT,
  k1             INT,
  k2             INT,
  k3             INT,
  name           VARCHAR(32) NOT NULL,
  code           CHAR(6),
  flag           BOOLEAN NOT NULL,
  flag2          BOOLEAN,
  status         VARCHAR(32) DEFAULT 'active',
  bitz           VARCHAR(5) DEFAULT '00101' NOT NULL,
  decs           DECIMAL(10,2),
  start          DATE,
  save           INT,
  nums           VARCHAR(255),
  data           BLOB,
  last_modified  TIMESTAMP,
  date_created   TIMESTAMP,

  UNIQUE(k1, k2, k3)
)
EOF

    $dbh->do(<<"EOF");
CREATE TABLE rose_db_object_test2
(
  k1             INT NOT NULL,
  k2             INT NOT NULL,
  name           VARCHAR(32),

  UNIQUE(k1, k2)
)
EOF

    $dbh->disconnect;

    # Create test subclass

    package MySQLiteObject;

    use Rose::DB::Object::Helpers qw(clone);

    our @ISA = qw(Rose::DB::Object);

    sub init_db { Rose::DB->new('sqlite') }

    MySQLiteObject->meta->table('rose_db_object_test');

    MySQLiteObject->meta->columns
    (
      name     => { type => 'varchar', length => 32, overflow => 'truncate' },
      code     => { type => 'char', length => 6, overflow => 'truncate' },
      id       => { primary_key => 1, not_null => 1 },
      k1       => { type => 'int' },
      k2       => { type => 'int', lazy => 1 },
      k3       => { type => 'int' },
      flag     => { type => 'boolean', default => 1 },
      flag2    => { type => 'boolean' },
      status   => { default => 'active', add_methods => [ qw(get set) ] },
      start    => { type => 'date', default => '12/24/1980', lazy => 1 },
      save     => { type => 'scalar' },
      nums     => { type => 'array' },
      bitz     => { type => 'bitfield', bits => 5, default => 101, alias => 'bits' },
      decs     => { type => 'decimal', precision => 10, scale => 2 },
      data     => { type => 'blob' },
      #last_modified => { type => 'timestamp' },
      date_created  => { type => 'scalar' },
      main::nonpersistent_column_definitions(),
    );

    MySQLiteObject->meta->replace_column(date_created => { type => 'timestamp' });

    MySQLiteObject->meta->add_unique_key('save');

    MySQLiteObject->meta->add_unique_key([ qw(k1 k2 k3) ]);

    MySQLiteObject->meta->add_columns(
      Rose::DB::Object::Metadata::Column::Timestamp->new(
        name => 'last_modified'));

    MySQLiteObject->meta->column('id')->add_trigger(inflate => sub { defined $_[1] ? [ $_[1] ] : undef });
    MySQLiteObject->meta->column('id')->add_trigger(deflate => sub { ref $_[1] ? (wantarray ? @{$_[1]} : $_[1]->[0]) : $_[1] });

    my $pre_inited = 0;
    MySQLiteObject->meta->pre_init_hook(sub { $pre_inited++ });

    eval { MySQLiteObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() reserved method');
    Test::More::is($pre_inited, 1, 'meta->pre_init_hook()');

    MySQLiteObject->meta->alias_column(save => 'save_col');

    eval { MySQLiteObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() no override');

    MySQLiteObject->meta->initialize(preserve_existing => 1);

    Test::More::is(MySQLiteObject->meta->column('id')->is_primary_key_member, 1, 'is_primary_key_member - sqlite');
    Test::More::is(MySQLiteObject->meta->column('id')->primary_key_position, 1, 'primary_key_position 1 - sqlite');
    Test::More::ok(!defined MySQLiteObject->meta->column('k1')->primary_key_position, 'primary_key_position 2 - sqlite');
    MySQLiteObject->meta->column('k1')->primary_key_position(7);
    Test::More::ok(!defined MySQLiteObject->meta->column('k1')->primary_key_position, 'primary_key_position 3 - sqlite');

    package MyMPKSQLiteObject;

    use Rose::DB::Object;

    our @ISA = qw(Rose::DB::Object);

    sub init_db { Rose::DB->new('sqlite') }

    MyMPKSQLiteObject->meta->table('rose_db_object_test2');

    MyMPKSQLiteObject->meta->columns
    (
      k1          => { type => 'int', not_null => 1 },
      k2          => { type => 'int', not_null => 1 },
      name        => { type => 'varchar', length => 32 },
    );

    MyMPKSQLiteObject->meta->primary_key_columns('k1', 'k2');

    MyMPKSQLiteObject->meta->initialize;

    my $i = 1;

    MyMPKSQLiteObject->meta->primary_key_generator(sub
    {
      my($meta, $db) = @_;

      my $k1 = $i++;
      my $k2 = $i++;

      return $k1, $k2;
    });
  }

  #
  # Oracle
  #

  eval
  {
    $dbh = Rose::DB->new('oracle_admin')->retain_dbh()
      or die Rose::DB->error;
  };

  if(!$@ && $dbh)
  {
    our $HAVE_ORACLE = 1;

    # Drop existing table and create schema, ignoring errors
    {
      local $dbh->{'RaiseError'} = 0;
      local $dbh->{'PrintError'} = 0;
      $dbh->do('DROP TABLE rose_db_object_test');
      $dbh->do('DROP SEQUENCE rose_db_object_test_id_seq');
    }

    $dbh->do(<<"EOF");
CREATE TABLE rose_db_object_test
(
  id              INT NOT NULL PRIMARY KEY,
  k1              INT,
  k2              INT,
  k3              INT,
  name            VARCHAR(32) NOT NULL,
  code            CHAR(6),
  flag            CHAR(1) NOT NULL CHECK(flag IN ('t', 'f')),
  flag2           CHAR(1) CHECK(flag2 IN ('t', 'f')),
  status          VARCHAR(32) DEFAULT 'active',
  bitz            VARCHAR(5) DEFAULT '00101' NOT NULL,
  decs            NUMBER(10,2),
  nums            VARCHAR(255),
  start_date      DATE,
  save            INT,
  claim#          INT,
  key             INT,
  last_modified   TIMESTAMP,
  date_created    TIMESTAMP,
  date_created_tz TIMESTAMP WITH TIME ZONE,
  timestamp_tz2   TIMESTAMP WITH TIME ZONE
)
EOF

    $dbh->do(<<"EOF");
CREATE SEQUENCE rose_db_object_test_id_seq
EOF

    $dbh->do(<<"EOF");
CREATE OR REPLACE TRIGGER rose_db_object_test_insert 
BEFORE INSERT ON rose_db_object_test FOR EACH ROW
BEGIN
  SELECT NVL(:new.id, rose_db_object_test_id_seq.nextval) INTO :new.id FROM dual;
END;
EOF

    $dbh->commit;
    $dbh->disconnect;

    # Create test subclass

    package MyOracleObject;

    use Rose::DB::Object::Helpers qw(clone);

    our @ISA = qw(Rose::DB::Object);

    sub init_db { Rose::DB->new('oracle') }

    MyOracleObject->meta->allow_inline_column_values(1);

    MyOracleObject->meta->table('rose_db_object_test');

    MyOracleObject->meta->columns
    (
      name     => { type => 'varchar', length => 32, overflow => 'truncate' },
      code     => { type => 'char', length => 6, overflow => 'truncate' },
      id       => { type => 'serial', primary_key => 1, not_null => 1 },
      k1       => { type => 'int' },
      k2       => { type => 'int', lazy => 1 },
      k3       => { type => 'int' },
      key      => { type => 'int' },
      flag     => { type => 'boolean', default => 1 },
      flag2    => { type => 'boolean' },
      status   => { default => 'active', add_methods => [ qw(get set) ] },
      start_date => { type => 'date', default => '12/24/1980', lazy => 1 },
      save     => { type => 'scalar' },
      'claim#' => { type => 'int' },
      nums     => { type => 'array' },
      bitz     => { type => 'bitfield', bits => 5, default => 101, alias => 'bits' },
      decs     => { type => 'decimal', precision => 10, scale => 2 },
      last_modified => { type => 'timestamp' },
      date_created  => { type => 'timestamp' },
      date_created_tz => { type => 'timestamp with time zone' },
      timestamp_tz2 => { type => 'timestamp with time zone', time_zone => 'Antarctica/Vostok' },
      main::nonpersistent_column_definitions(),
    );

    eval { MyOracleObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() reserved method');

    MyOracleObject->meta->prepare_options({ix_CursorWithHold => 1});    

    MyOracleObject->meta->alias_column(save => 'save_col');

    eval { MyOracleObject->meta->initialize };
    Test::More::ok($@, 'meta->initialize() no override');

    MyOracleObject->meta->add_unique_key('save');
    MyOracleObject->meta->add_unique_key('key');
    MyOracleObject->meta->add_unique_key([ qw(k1 k2 k3) ]);

    MyOracleObject->meta->initialize(preserve_existing => 1);

    Test::More::is(MyOracleObject->meta->column('id')->is_primary_key_member, 1, 'is_primary_key_member - oracle');
    Test::More::is(MyOracleObject->meta->column('id')->primary_key_position, 1, 'primary_key_position 1 - oracle');
    Test::More::ok(!defined MyOracleObject->meta->column('k1')->primary_key_position, 'primary_key_position 2 - oracle');
    MyOracleObject->meta->column('k1')->primary_key_position(7);
    Test::More::ok(!defined MyOracleObject->meta->column('k1')->primary_key_position, 'primary_key_position 3 - oracle');
  }
}

END
{
  # Delete test table

  if($HAVE_PG)
  {
    # PostgreSQL
    my $dbh = Rose::DB->new('pg_admin')->retain_dbh()
      or die Rose::DB->error;

    $dbh->do('DROP TABLE rose_db_object_test');
    $dbh->do('DROP TABLE rose_db_object_private.rose_db_object_test');
    $dbh->do('DROP SCHEMA rose_db_object_private CASCADE');

    $dbh->disconnect;
  }

  if($HAVE_MYSQL)
  {
    my $dbh = Rose::DB->new('mysql_admin')->retain_dbh()
      or die Rose::DB->error;

    $dbh->do('DROP TABLE rose_db_object_test');
    $dbh->do('DROP TABLE rose_db_object_test2');

    $dbh->disconnect;
  }

  if($HAVE_INFORMIX)
  {
    my $dbh = Rose::DB->new('informix_admin')->retain_dbh()
      or die Rose::DB->error;

    $dbh->do('DROP TABLE rose_db_object_test');

    $dbh->disconnect;
  }

  if($HAVE_SQLITE)
  {
    my $dbh = Rose::DB->new('sqlite_admin')->retain_dbh()
      or die Rose::DB->error;

    $dbh->do('DROP TABLE rose_db_object_test');
    $dbh->do('DROP TABLE rose_db_object_test2');

    $dbh->disconnect;
  }

  if($HAVE_ORACLE)
  {
    my $dbh = Rose::DB->new('oracle_admin')->retain_dbh()
      or die Rose::DB->error;

    $dbh->do('DROP TABLE rose_db_object_test');
    $dbh->do('DROP SEQUENCE rose_db_object_test_id_seq');

    $dbh->disconnect;
  }
}
