better DT inflation for Firebird and _ping
Rafael Kitover [Mon, 8 Feb 2010 13:26:41 +0000 (13:26 +0000)]
lib/DBIx/Class/SQLAHacks/ODBC/Firebird.pm [deleted file]
lib/DBIx/Class/Storage/DBI/InterBase.pm
lib/DBIx/Class/Storage/DBI/ODBC/Firebird.pm
t/750firebird.t
t/inflate/datetime_firebird.t

diff --git a/lib/DBIx/Class/SQLAHacks/ODBC/Firebird.pm b/lib/DBIx/Class/SQLAHacks/ODBC/Firebird.pm
deleted file mode 100644 (file)
index b6697e7..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package # Hide from PAUSE
-  DBIx::Class::SQLAHacks::ODBC::Firebird;
-
-use strict;
-use warnings;
-use base qw( DBIx::Class::SQLAHacks );
-use Carp::Clan qw/^DBIx::Class|^SQL::Abstract/;
-
-sub insert {
-  my $self = shift;
-  my ($table, $vals, $opts) = @_;
-
-# Quoting RETURNING values breaks the Firebird ODBC driver, so we convert to
-# scalarref with unquoted values.
-  my $returning = $opts->{returning};
-
-  if ($returning && ref $returning eq 'ARRAY') {
-    $opts->{returning} = \join ', ' => @$returning;
-  }
-
-  return $self->next::method(@_);
-}
-
-1;
index 1e46215..4baa26c 100644 (file)
@@ -24,6 +24,12 @@ L<DBIx::Class::InflateColumn::DateTime> support.
 
 For ODBC support, see L<DBIx::Class::Storage::DBI::ODBC::Firebird>.
 
+To turn on L<DBIx::Class::InflateColumn::DateTime> support, add:
+
+    on_connect_call => 'datetime_setup'
+
+to your L<DBIx::Class::Storage::DBI/connect_info>.
+
 =cut
 
 sub _prep_for_execute {
@@ -102,29 +108,79 @@ sub _sql_maker_opts {
   return { limit_dialect => 'FirstSkip', %{$self->{_sql_maker_opts}||{}} };
 }
 
-sub datetime_parser_type { __PACKAGE__ }
+sub _svp_begin {
+    my ($self, $name) = @_;
+
+    $self->_get_dbh->do("SAVEPOINT $name");
+}
+
+sub _svp_release {
+    my ($self, $name) = @_;
+
+    $self->_get_dbh->do("RELEASE SAVEPOINT $name");
+}
+
+sub _svp_rollback {
+    my ($self, $name) = @_;
+
+    $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
+}
+
+sub _ping {
+  my $self = shift;
+
+  my $dbh = $self->_dbh or return 0;
+
+  local $dbh->{RaiseError} = 1;
+
+  eval {
+    $dbh->do('select 1 from rdb$database');
+  };
+
+  return $@ ? 0 : 1;
+}
+
+=head2 connect_call_datetime_setup
 
-my ($datetime_parser, $datetime_formatter);
+Used as:
 
-sub parse_datetime {
-    shift;
-    require DateTime::Format::Strptime;
-    $datetime_parser ||= DateTime::Format::Strptime->new(
-        pattern => '%a %d %b %Y %r',
-# there should be a %Z (TZ) on the end, but it's ambiguous and not parsed
-        on_error => 'croak',
-    );
-    $datetime_parser->parse_datetime(shift);
+  on_connect_call => 'datetime_setup'
+
+In L<DBIx::Class::Storage::DBI/connect_info> to set the date and timestamp
+formats using:
+
+  $dbh->{ib_time_all} = 'ISO';
+
+See L<DBD::InterBase> for more details.
+
+The C<TIMESTAMP> data type supports up to 4 digits after the decimal point for
+second precision. The full precision is used.
+
+You will need the L<DateTime::Format::Strptime> module for inflation to work.
+
+For L<DBIx::Class::Storage::DBI::ODBC::Firebird>, this is a noop and sub-second
+precision is not currently available.
+
+=cut
+
+sub connect_call_datetime_setup {
+  my $self = shift;
+
+  $self->_get_dbh->{ib_time_all} = 'ISO';
 }
 
-sub format_datetime {
-    shift;
-    require DateTime::Format::Strptime;
-    $datetime_formatter ||= DateTime::Format::Strptime->new(
-        pattern => '%F %H:%M:%S.%4N',
-        on_error => 'croak',
-    );
-    $datetime_formatter->format_datetime(shift);
+
+# from MSSQL
+
+sub build_datetime_parser {
+  my $self = shift;
+  my $type = "DateTime::Format::Strptime";
+  eval "use ${type}";
+  $self->throw_exception("Couldn't load ${type}: $@") if $@;
+  return $type->new(
+    pattern => '%Y-%m-%d %H:%M:%S.%4N', # %F %T
+    on_error => 'croak',
+  );
 }
 
 1;
@@ -139,16 +195,6 @@ C<last_insert_id> support only works for Firebird versions 2 or greater. To
 work with earlier versions, we'll need to figure out how to retrieve the bodies
 of C<BEFORE INSERT> triggers and parse them for the C<GENERATOR> name.
 
-=item *
-
-C<TIMESTAMP> values are written with precision of 4 numbers after the decimal
-point for seconds, but read with only second precision.
-
-If you know of a session variable we can set to control how timestamps look as
-strings, please let us know (via RT.)
-
-Otherwise we'll need to rewrite the produced SQL for timestamps, at some point.
-
 =back
 
 =head1 AUTHOR
index a17fa4a..58a3675 100644 (file)
@@ -12,7 +12,7 @@ through ODBC
 
 =head1 SYNOPSIS
 
-All functionality is provided by L<DBIx::Class::Storage::DBI::Interbase>, see
+Most functionality is provided by L<DBIx::Class::Storage::DBI::Interbase>, see
 that module for details.
 
 To build the ODBC driver for Firebird on Linux for unixODBC, see:
@@ -21,24 +21,29 @@ L<http://www.firebirdnews.org/?p=1324>
 
 =cut
 
-__PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::ODBC::Firebird');
+# XXX seemingly no equivalent to ib_time_all in DBD::InterBase via ODBC
+sub connect_call_datetime_setup { 1 }
 
-sub datetime_parser_type { __PACKAGE__ }
+# from MSSQL
 
-my $datetime_parser;
-
-sub parse_datetime {
-    shift;
-    require DateTime::Format::Strptime;
-    $datetime_parser ||= DateTime::Format::Strptime->new(
-        pattern => '%F %H:%M:%S',
-        on_error => 'croak',
-    );
-    $datetime_parser->parse_datetime(shift);
+sub build_datetime_parser {
+  my $self = shift;
+  my $type = "DateTime::Format::Strptime";
+  eval "use ${type}";
+  $self->throw_exception("Couldn't load ${type}: $@") if $@;
+  return $type->new(
+    pattern => '%Y-%m-%d %H:%M:%S', # %F %T
+    on_error => 'croak',
+  );
 }
 
 1;
 
+=head1 CAVEATS
+
+This driver (unlike L<DBD::InterBase>) does not currently support reading or
+writing C<TIMESTAMP> values with sub-second precision.
+
 =head1 AUTHOR
 
 See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
index 5b79b23..0c98f95 100644 (file)
@@ -29,7 +29,9 @@ foreach my $conn_idx (0..1) {
 
   next unless $dsn;
 
-  $schema = DBICTest::Schema->connect($dsn, $user, $pass);
+  $schema = DBICTest::Schema->connect($dsn, $user, $pass, {
+    auto_savepoint => 1
+  });
   my $dbh = $schema->storage->dbh;
 
   my $sg = Scope::Guard->new(\&cleanup);
@@ -63,6 +65,24 @@ EOF
   my $new = $ars->create({ name => 'foo' });
   ok($new->artistid, "Auto-PK worked");
 
+# test savepoints
+#  eval {
+#    $schema->txn_do(sub {
+#      eval {
+#        $schema->txn_do(sub {
+#          $ars->create({ name => 'in_savepoint' });
+#          die "rolling back savepoint";
+#        });
+#      };
+#      ok ((not $ars->search({ name => 'in_savepoint' })->first),
+#        'savepoint rolled back');
+#      $ars->create({ name => 'in_outer_txn' });
+#      die "rolling back outer txn";
+#    });
+#  };
+#  ok ((not $ars->search({ name => 'in_outer_txn' })->first),
+#    'outer txn rolled back');
+
 # test explicit key spec
   $new = $ars->create ({ name => 'bar', artistid => 66 });
   is($new->artistid, 66, 'Explicit PK worked');
index 9212f37..714c127 100644 (file)
@@ -32,14 +32,12 @@ my @info = (
 
 my $schema;
 
-foreach my $info (@info) {
-  my ($dsn, $user, $pass) = @$info;
+foreach my $conn_idx (0..$#info) {
+  my ($dsn, $user, $pass) = @{ $info[$conn_idx] };
 
   next unless $dsn;
 
-  $schema = DBICTest::Schema->clone;
-
-  $schema->connection($dsn, $user, $pass, {
+  $schema = DBICTest::Schema->connect($dsn, $user, $pass, {
     on_connect_call => [ 'datetime_setup' ],
   });
 
@@ -53,6 +51,7 @@ foreach my $info (@info) {
   )
 SQL
   my $now = DateTime->now;
+  $now->set_nanosecond(555600000);
   my $row;
   ok( $row = $schema->resultset('Event')->create({
         id => 1,
@@ -62,7 +61,12 @@ SQL
     ->search({ id => 1 }, { select => ['created_on'] })
     ->first
   );
-  is( $row->created_on, $now, 'DateTime roundtrip' );
+  is $row->created_on, $now, 'DateTime roundtrip';
+
+  if ($conn_idx == 0) { # skip for ODBC
+    cmp_ok $row->created_on->nanosecond, '==', 555600000,
+      'fractional part of a second survived';
+  }
 }
 
 done_testing;