Comprehensive MSAccess support over both DBD::ODBC and DBD::ADO
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / ODBC / ACCESS.pm
index b41b1f3..2a0624f 100644 (file)
 package DBIx::Class::Storage::DBI::ODBC::ACCESS;
+
 use strict;
 use warnings;
-
-use base qw/DBIx::Class::Storage::DBI/;
+use base qw/
+  DBIx::Class::Storage::DBI::ODBC
+  DBIx::Class::Storage::DBI::ACCESS
+/;
 use mro 'c3';
 
-use DBI;
-
-my $ERR_MSG_START = __PACKAGE__ . ' failed: ';
+__PACKAGE__->mk_group_accessors(inherited =>
+  'disable_sth_caching_for_image_insert_or_update'
+);
 
-__PACKAGE__->sql_limit_dialect ('Top');
-__PACKAGE__->sql_quote_char ([qw/[ ]/]);
-
-sub insert {
-    my $self = shift;
-    my ( $source, $to_insert ) = @_;
+__PACKAGE__->disable_sth_caching_for_image_insert_or_update(1);
 
-    my ( undef, $sth ) = $self->_execute( 'insert', $source, $to_insert );
-
-    #store the identity here since @@IDENTITY is connection global and this prevents
-    #possibility that another insert to a different table overwrites it for this resultsource
-    my $identity = 'SELECT @@IDENTITY';
-    my $max_sth  = $self->{ _dbh }->prepare( $identity )
-        or $self->throw_exception( $ERR_MSG_START . $self->{ _dbh }->errstr() );
-    $max_sth->execute() or $self->throw_exception( $ERR_MSG_START . $max_sth->errstr );
-
-    my $row = $max_sth->fetchrow_arrayref()
-        or $self->throw_exception( $ERR_MSG_START . "$identity did not return any result." );
-
-    $self->{ last_pk }->{ $source->name() } = $row;
+=head1 NAME
 
-    return $to_insert;
-}
+DBIx::Class::Storage::DBI::ODBC::ACCESS - Support specific to MS Access over ODBC
 
-sub last_insert_id {
-    my $self = shift;
-    my ( $result_source ) = @_;
+=head1 DESCRIPTION
 
-    return @{ $self->{ last_pk }->{ $result_source->name() } };
-}
+This class implements support specific to Microsoft Access over ODBC.
 
-sub bind_attribute_by_data_type {
-    my $self = shift;
+It is a subclass of L<DBIx::Class::Storage::DBI::ODBC> and
+L<DBIx::Class::Storage::DBI::ACCESS>, see those classes for more
+information.
 
-    my ( $data_type ) = @_;
+It is loaded automatically by by L<DBIx::Class::Storage::DBI::ODBC> when it
+detects a MS Access back-end.
 
-    return { TYPE => $data_type } if $data_type == DBI::SQL_LONGVARCHAR;
+This driver implements workarounds for C<IMAGE> and C<MEMO> columns, and
+L<DBIx::Class::InflateColumn::DateTime> support for C<DATETIME> columns.
 
-    return;
-}
+=head1 EXAMPLE DSN
 
-sub sqlt_type { 'ACCESS' }
+  dbi:ODBC:driver={Microsoft Access Driver (*.mdb, *.accdb)};dbq=C:\Users\rkitover\Documents\access_sample.accdb
 
-1;
+=head1 TEXT/IMAGE/MEMO COLUMNS
 
-=head1 NAME
+Avoid using C<TEXT> columns as they will be truncated to 255 bytes. Some other
+drivers (like L<ADO|DBIx::Class::Storage::DBI::ADO::MS_Jet>) will automatically
+convert C<TEXT> columns to C<MEMO>, but the ODBC driver does not.
 
-DBIx::Class::Storage::DBI::ODBC::ACCESS - Support specific to MS Access over ODBC
+C<IMAGE> columns work correctly, but the statements for inserting or updating an
+C<IMAGE> column will not be L<cached|DBI/prepare_cached>, due to a bug in the
+Access ODBC driver.
 
-=head1 WARNING
+C<MEMO> columns work correctly as well, but you must take care to set
+L<LongReadLen|DBI/LongReadLen> to C<$max_memo_size * 2 + 1>. This is done for
+you automatically if you pass L<LongReadLen|DBI/LongReadLen> in your
+L<connect_info|DBIx::Class::Storage::DBI/connect_info>; but if you set this
+attribute directly on the C<$dbh>, keep this limitation in mind.
 
-I am not a DBI, DBIx::Class or MS Access guru. Use this module with that in
-mind.
+=cut
 
-This module is currently considered alpha software and can change without notice.
+# set LongReadLen = LongReadLen * 2 + 1 (see docs on MEMO)
+sub _run_connection_actions {
+  my $self = shift;
 
-=head1 DESCRIPTION
+  my $long_read_len = $self->_dbh->{LongReadLen};
 
-This class implements support specific to Microsoft Access over ODBC.
+  # 80 is another default (just like 0) on some drivers
+  if ($long_read_len != 0 && $long_read_len != 80) {
+    $self->_dbh->{LongReadLen} = $long_read_len * 2 + 1;
+  }
 
-It is loaded automatically by by DBIx::Class::Storage::DBI::ODBC when it
-detects a MS Access back-end.
+  return $self->next::method(@_);
+}
 
-=head1 SUPPORTED VERSIONS
+sub insert {
+  my $self = shift;
+  my ($source, $to_insert) = @_;
 
-This module have currently only been tested on MS Access 2003 using the Jet 4.0 engine.
+  my $columns_info = $source->columns_info;
 
-As far as my knowledge it should work on MS Access 2000 or later, but that have not been tested.
-Information about support for different version of MS Access is welcome.
+  my $is_image_insert = 0;
 
-=head1 IMPLEMENTATION NOTES
+  for my $col (keys %$to_insert) {
+    if ($self->_is_binary_lob_type($columns_info->{$col}{data_type})) {
+      $is_image_insert = 1;
+      last;
+    }
+  }
 
-MS Access supports the @@IDENTITY function for retrieving the id of the latest inserted row.
-@@IDENTITY is global to the connection, so to support the possibility of getting the last inserted
-id for different tables, the insert() function stores the inserted id on a per table basis.
-last_insert_id() then just returns the stored value.
+  local $self->{disable_sth_caching} = 1 if $is_image_insert
+    && $self->disable_sth_caching_for_image_insert_or_update;
 
-=head1 KNOWN ACCESS PROBLEMS
+  return $self->next::method(@_);
+}
 
-=over
+sub update {
+  my $self = shift;
+  my ($source, $fields) = @_;
 
-=item Invalid precision value
+  my $columns_info = $source->columns_info;
 
-This error message is received when trying to store more than 255 characters in a MEMO field.
-The problem is (to my knowledge) an error in the MS Access ODBC driver. The problem is fixed
-by setting the C<data_type> of the column to C<SQL_LONGVARCHAR> in C<add_columns>. 
-C<SQL_LONGVARCHAR> is a constant in the C<DBI> module.
+  my $is_image_insert = 0;
 
-=back
+  for my $col (keys %$fields) {
+    if ($self->_is_binary_lob_type($columns_info->{$col}{data_type})) {
+      $is_image_insert = 1;
+      last;
+    }
+  }
 
-=head1 IMPLEMENTED FUNCTIONS
+  local $self->{disable_sth_caching} = 1 if $is_image_insert
+    && $self->disable_sth_caching_for_image_insert_or_update;
 
-=head2 bind_attribute_by_data_type
+  return $self->next::method(@_);
+}
 
-This function currently supports the SQL_LONGVARCHAR column type.
+sub datetime_parser_type {
+  'DBIx::Class::Storage::DBI::ODBC::ACCESS::DateTime::Format'
+}
 
-=head2 insert
+package # hide from PAUSE
+  DBIx::Class::Storage::DBI::ODBC::ACCESS::DateTime::Format;
 
-=head2 last_insert_id
+my $datetime_format = '%Y-%m-%d %H:%M:%S'; # %F %T, no fractional part
+my $datetime_parser;
 
-=head2 sqlt_type
+sub parse_datetime {
+  shift;
+  require DateTime::Format::Strptime;
+  $datetime_parser ||= DateTime::Format::Strptime->new(
+    pattern  => $datetime_format,
+    on_error => 'croak',
+  );
+  return $datetime_parser->parse_datetime(shift);
+}
 
-=head1 BUGS
+sub format_datetime {
+  shift;
+  require DateTime::Format::Strptime;
+  $datetime_parser ||= DateTime::Format::Strptime->new(
+    pattern  => $datetime_format,
+    on_error => 'croak',
+  );
+  return $datetime_parser->format_datetime(shift);
+}
 
-Most likely. Bug reports are welcome.
+1;
 
-=head1 AUTHORS
+=head1 AUTHOR
 
-Øystein Torget C<< <oystein.torget@dnv.com> >>
+See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
 
-=head1 COPYRIGHT
+=head1 LICENSE
 
 You may distribute this code under the same terms as Perl itself.
 
-Det Norske Veritas AS (DNV)
-
-http://www.dnv.com
-
 =cut
-
+# vim:sts=2 sw=2: