Merge 'sybase_insert_bulk' into 'sybase_support'
Peter Rabbitson [Sun, 20 Sep 2009 23:18:40 +0000 (23:18 +0000)]
main sybase branch ready

1  2 
lib/DBIx/Class.pm
lib/DBIx/Class/Storage/DBI.pm
lib/DBIx/Class/Storage/DBI/Sybase.pm
t/746sybase.t

@@@ -43,6 -48,47 +43,19 @@@ sub _attr_cache 
    return $@ ? $cache : { %$cache, %$rest };
  }
  
 -# SQLT version handling
 -{
 -  my $_sqlt_version_ok;     # private
 -  my $_sqlt_version_error;  # private
 -
 -  sub _sqlt_version_ok {
 -    if (!defined $_sqlt_version_ok) {
 -      eval "use SQL::Translator $minimum_sqlt_version";
 -      if ($@) {
 -        $_sqlt_version_ok = 0;
 -        $_sqlt_version_error = $@;
 -      }
 -      else {
 -        $_sqlt_version_ok = 1;
 -      }
 -    }
 -    return $_sqlt_version_ok;
 -  }
 -
 -  sub _sqlt_version_error {
 -    shift->_sqlt_version_ok unless defined $_sqlt_version_ok;
 -    return $_sqlt_version_error;
 -  }
 -
 -  sub _sqlt_minimum_version { $minimum_sqlt_version };
 -}
 -
+ # Pretty printer for debug messages
+ sub _pretty_print {
+   require Data::Dumper;
+   local $Data::Dumper::Terse = 1;
+   local $Data::Dumper::Indent = 1;
+   local $Data::Dumper::Useqq = 1;
+   local $Data::Dumper::Quotekeys = 0;
+   local $Data::Dumper::Sortkeys = 1;
+   return Data::Dumper::Dumper ($_[1]);
+ }
 -
  1;
  
  =head1 NAME
@@@ -1344,12 -1339,51 +1344,17 @@@ sub insert_bulk 
    }
  
    my %colvalues;
 +  my $table = $source->from;
    @colvalues{@$cols} = (0..$#$cols);
-   my ($sql, @bind) = $self->sql_maker->insert($table, \%colvalues);
 -  # bind literal sql if it's the same in all slices
 -  for my $i (0..$#$cols) {
 -    my $first_val = $data->[0][$i];
 -    next unless (Scalar::Util::reftype($first_val)||'') eq 'SCALAR';
 -
 -    $colvalues{ $cols->[$i] } = $first_val
 -      if (grep {
 -        (Scalar::Util::reftype($_)||'') eq 'SCALAR' &&
 -        $$_ eq $$first_val
 -      } map $data->[$_][$i], (1..$#$data)) == (@$data - 1);
 -  }
 -
+   my ($sql, $bind) = $self->_prep_for_execute (
+     'insert', undef, $source, [\%colvalues]
+   );
 -  my @bind = @$bind;
 -
 -  my $empty_bind = 1 if (not @bind) &&
 -    (grep { (Scalar::Util::reftype($_)||'') eq 'SCALAR' } values %colvalues)
 -    == @$cols;
 -
 -  if ((not @bind) && (not $empty_bind)) {
 -    croak 'Cannot insert_bulk without support for placeholders';
 -  }
++  my @bind = @$bind
++    or croak 'Cannot insert_bulk without support for placeholders';
  
    $self->_query_start( $sql, @bind );
 -  my $sth = $self->sth($sql, 'insert', $sth_attr);
 -
 -  if ($empty_bind) {
 -    # bind_param_array doesn't work if there are no binds
 -    eval {
 -      local $self->_get_dbh->{RaiseError} = 1;
 -      local $self->_get_dbh->{PrintError} = 0;
 -      foreach (0..$#$data) {
 -        $sth->execute;
 -        $sth->fetchall_arrayref;
 -      }
 -    };
 -    my $exception = $@;
 -    $sth->finish;
 -    $self->throw_exception($exception) if $exception;
 -    return;
 -  }
 +  my $sth = $self->sth($sql);
  
  #  @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args
  
      $sth->bind_param_array( $placeholder_index, [@data], $attributes );
      $placeholder_index++;
    }
 -
    my $rv = eval { $sth->execute_array({ArrayTupleStatus => $tuple_status}) };
+   $sth->finish;
 -  if (my $err = $@ || $sth->errstr) {
 +  if (my $err = $@) {
      my $i = 0;
      ++$i while $i <= $#$tuple_status && !ref $tuple_status->[$i];
  
 -    $self->throw_exception("Unexpected populate error: $err")
 +    $self->throw_exception($sth->errstr || "Unexpected populate error: $err")
        if ($i > $#$tuple_status);
  
-     require Data::Dumper;
-     local $Data::Dumper::Terse = 1;
-     local $Data::Dumper::Indent = 1;
-     local $Data::Dumper::Useqq = 1;
-     local $Data::Dumper::Quotekeys = 0;
-     local $Data::Dumper::Sortkeys = 1;
      $self->throw_exception(sprintf "%s for populate slice:\n%s",
 -      ($tuple_status->[$i][1] || $err),
 +      $tuple_status->[$i][1],
-       Data::Dumper::Dumper(
-         { map { $cols->[$_] => $data->[$i][$_] } (0 .. $#$cols) }
-       ),
+       $self->_pretty_print ({
+         map { $cols->[$_] => $data->[$i][$_] } (0 .. $#$cols)
+       }),
      );
    }
 +  $self->throw_exception($sth->errstr) if !$rv;
  
    $self->_query_end( $sql, @bind );
    return (wantarray ? ($rv, $sth, @bind) : $rv);
@@@ -17,7 -19,10 +17,10 @@@ __PACKAGE__->mk_group_accessors('simple
         _identity_method/
  );
  
 -my @also_proxy_to_extra_storages = qw/
 +my @also_proxy_to_writer_storage = qw/
+   connect_call_set_auto_cast auto_cast connect_call_blob_setup
+   connect_call_datetime_setup
    disconnect _connect_info _sql_maker _sql_maker_opts disable_sth_caching
    auto_savepoint unsafe cursor_class debug debugobj schema
  /;
@@@ -122,14 -127,31 +125,16 @@@ sub _init 
  
    my $writer_storage = (ref $self)->new;
  
 -  $writer_storage->_is_extra_storage(1);
 +  $writer_storage->_is_writer_storage(1);
    $writer_storage->connect_info($self->connect_info);
+   $writer_storage->auto_cast($self->auto_cast);
  
    $self->_writer_storage($writer_storage);
 -
 -# create a bulk storage unless connect_info is a coderef
 -  return
 -    if (Scalar::Util::reftype($self->_dbi_connect_info->[0])||'') eq 'CODE';
 -
 -  my $bulk_storage = (ref $self)->new;
 -
 -  $bulk_storage->_is_extra_storage(1);
 -  $bulk_storage->_is_bulk_storage(1); # for special ->disconnect acrobatics
 -  $bulk_storage->connect_info($self->connect_info);
 -
 -# this is why
 -  $bulk_storage->_dbi_connect_info->[0] .= ';bulkLogin=1';
 -  
 -  $self->_bulk_storage($bulk_storage);
  }
  
 -for my $method (@also_proxy_to_extra_storages) {
 +for my $method (@also_proxy_to_writer_storage) {
    no strict 'refs';
+   no warnings 'redefine';
  
    my $replaced = __PACKAGE__->can($method);
  
@@@ -384,13 -469,14 +428,14 @@@ sub update 
    return $wantarray ? @res : $res[0];
  }
  
 -### the insert_bulk partially stolen from DBI/MSSQL.pm
 +### the insert_bulk stuff stolen from DBI/MSSQL.pm
  
  sub _set_identity_insert {
-   my ($self, $table) = @_;
+   my ($self, $table, $op) = @_;
  
    my $sql = sprintf (
-     'SET IDENTITY_INSERT %s ON',
+     'SET IDENTITY_%s %s ON',
+     (uc($op) || 'INSERT'),
      $self->sql_maker->_quote ($table),
    );
  
@@@ -412,35 -505,238 +464,44 @@@ sub _unset_identity_insert 
      $self->sql_maker->_quote ($table),
    );
  
+   $self->_query_start($sql);
    my $dbh = $self->_get_dbh;
    $dbh->do ($sql);
+   $self->_query_end($sql);
  }
  
+ # for tests
+ sub _can_insert_bulk { 1 }
 +# XXX this should use the DBD::Sybase bulk API, where possible
  sub insert_bulk {
    my $self = shift;
    my ($source, $cols, $data) = @_;
  
 -  my $identity_col = List::Util::first
 -    { $source->column_info($_)->{is_auto_increment} }
 -    $source->columns;
 -
    my $is_identity_insert = (List::Util::first
 -    { $source->column_info ($_)->{is_auto_increment} }
 -    @{$cols}
 -  ) ? 1 : 0;
 -
 -  my @source_columns = $source->columns;
 -
 -  my $use_bulk_api =
 -    $self->_bulk_storage && 
 -    $self->_get_dbh->{syb_has_blk};
 -
 -  if ((not $use_bulk_api) &&
 -      (Scalar::Util::reftype($self->_dbi_connect_info->[0])||'') eq 'CODE' &&
 -      (not $self->_bulk_disabled_due_to_coderef_connect_info_warned)) {
 -    carp <<'EOF';
 -Bulk API support disabled due to use of a CODEREF connect_info. Reverting to
 -array inserts.
 -EOF
 -    $self->_bulk_disabled_due_to_coderef_connect_info_warned(1);
 -  }
 -
 -  if (not $use_bulk_api) {
 -    my $blob_cols = $self->_remove_blob_cols_array($source, $cols, $data);
 -
 -    my $dumb_last_insert_id =
 -         $identity_col
 -      && (not $is_identity_insert)
 -      && ($self->_identity_method||'') ne '@@IDENTITY';
 -
 -    ($self, my ($guard)) = do {
 -      if ($self->{transaction_depth} == 0 &&
 -          ($blob_cols || $dumb_last_insert_id)) {
 -        ($self->_writer_storage, $self->_writer_storage->txn_scope_guard);
 -      }
 -      else {
 -        ($self, undef);
 -      }
 -    };
 -
 -    $self->_set_identity_insert ($source->name)   if $is_identity_insert;
 -    $self->next::method(@_);
 -    $self->_unset_identity_insert ($source->name) if $is_identity_insert;
 -
 -    if ($blob_cols) {
 -      if ($is_identity_insert) {
 -        $self->_insert_blobs_array ($source, $blob_cols, $cols, $data);
 -      }
 -      else {
 -        my @cols_with_identities = (@$cols, $identity_col);
 -
 -        ## calculate identities
 -        # XXX This assumes identities always increase by 1, which may or may not
 -        # be true.
 -        my ($last_identity) =
 -          $self->_dbh->selectrow_array (
 -            $self->_fetch_identity_sql($source, $identity_col)
 -          );
 -        my @identities = (($last_identity - @$data + 1) .. $last_identity);
 -
 -        my @data_with_identities = map [@$_, shift @identities], @$data;
 -
 -        $self->_insert_blobs_array (
 -          $source, $blob_cols, \@cols_with_identities, \@data_with_identities
 -        );
 -      }
 -    }
 -
 -    $guard->commit if $guard;
 -    return;
 -  }
 -
 -# otherwise, use the bulk API
 -
 -# rearrange @$data so that columns are in database order
 -  my %orig_idx;
 -  @orig_idx{@$cols} = 0..$#$cols;
 -
 -  my %new_idx;
 -  @new_idx{@source_columns} = 0..$#source_columns;
 -
 -  my @new_data;
 -  for my $datum (@$data) {
 -    my $new_datum = [];
 -    for my $col (@source_columns) {
 -# identity data will be 'undef' if not $is_identity_insert
 -# columns with defaults will also be 'undef'
 -      $new_datum->[ $new_idx{$col} ] =
 -        exists $orig_idx{$col} ? $datum->[ $orig_idx{$col} ] : undef;
 -    }
 -    push @new_data, $new_datum;
 +      { $source->column_info ($_)->{is_auto_increment} }
 +      (@{$cols})
 +  )
 +     ? 1
 +     : 0;
 +
 +  if ($is_identity_insert) {
 +     $self->_set_identity_insert ($source->name);
    }
  
 -# bcp identity index is 1-based
 -  my $identity_idx = exists $new_idx{$identity_col} ?
 -    $new_idx{$identity_col} + 1 : 0;
 -
 -## Set a client-side conversion error handler, straight from DBD::Sybase docs.
 -# This ignores any data conversion errors detected by the client side libs, as
 -# they are usually harmless.
 -  my $orig_cslib_cb = DBD::Sybase::set_cslib_cb(
 -    Sub::Name::subname insert_bulk => sub {
 -      my ($layer, $origin, $severity, $errno, $errmsg, $osmsg, $blkmsg) = @_;
 -
 -      return 1 if $errno == 36;
 -
 -      carp 
 -        "Layer: $layer, Origin: $origin, Severity: $severity, Error: $errno" .
 -        ($errmsg ? "\n$errmsg" : '') .
 -        ($osmsg  ? "\n$osmsg"  : '')  .
 -        ($blkmsg ? "\n$blkmsg" : '');
 -      
 -      return 0;
 -  });
 -
 -  eval {
 -    my $bulk = $self->_bulk_storage;
 -
 -    my $guard = $bulk->txn_scope_guard;
 -
 -## XXX get this to work instead of our own $sth
 -## will require SQLA or *Hacks changes for ordered columns
 -#    $bulk->next::method($source, \@source_columns, \@new_data, {
 -#      syb_bcp_attribs => {
 -#        identity_flag   => $is_identity_insert,
 -#        identity_column => $identity_idx, 
 -#      }
 -#    });
 -    my $sql = 'INSERT INTO ' .
 -      $bulk->sql_maker->_quote($source->name) . ' (' .
 -# colname list is ignored for BCP, but does no harm
 -      (join ', ', map $bulk->sql_maker->_quote($_), @source_columns) . ') '.
 -      ' VALUES ('.  (join ', ', ('?') x @source_columns) . ')';
 -
 -## XXX there's a bug in the DBD::Sybase bulk support that makes $sth->finish for
 -## a prepare_cached statement ineffective. Replace with ->sth when fixed, or
 -## better yet the version above. Should be fixed in DBD::Sybase .
 -    my $sth = $bulk->_get_dbh->prepare($sql,
 -#      'insert', # op
 -      {
 -        syb_bcp_attribs => {
 -          identity_flag   => $is_identity_insert,
 -          identity_column => $identity_idx, 
 -        }
 -      }
 -    );
 -
 -    my $bind_attributes = $self->source_bind_attributes($source);
 -
 -    foreach my $slice_idx (0..$#source_columns) {
 -      my $col = $source_columns[$slice_idx];
 -
 -      my $attributes = $bind_attributes->{$col}
 -        if $bind_attributes && defined $bind_attributes->{$col};
 -
 -      my @slice = map $_->[$slice_idx], @new_data;
 -
 -      $sth->bind_param_array(($slice_idx + 1), \@slice, $attributes);
 -    }
 -
 -    $bulk->_query_start($sql);
 -
 -# this is stolen from DBI::insert_bulk
 -    my $tuple_status = [];
 -    my $rv = eval { $sth->execute_array({ArrayTupleStatus => $tuple_status}) };
 -
 -    if (my $err = $@ || $sth->errstr) {
 -      my $i = 0;
 -      ++$i while $i <= $#$tuple_status && !ref $tuple_status->[$i];
 -
 -      $self->throw_exception("Unexpected populate error: $err")
 -        if ($i > $#$tuple_status);
 -
 -      $self->throw_exception(sprintf "%s for populate slice:\n%s",
 -        ($tuple_status->[$i][1] || $err),
 -        $self->_pretty_print ({
 -          map { $source_columns[$_] => $new_data[$i][$_] } (0 .. $#$cols)
 -        }),
 -      );
 -    }
 -
 -    $guard->commit;
 -    $sth->finish;
 -
 -    $bulk->_query_end($sql);
 -  };
 -  my $exception = $@;
 -  if ($exception =~ /-Y option/) {
 -    carp <<"EOF";
 -
 -Sybase bulk API operation failed due to character set incompatibility, reverting
 -to regular array inserts:
 -
 -*** Try unsetting the LANG environment variable.
 +  $self->next::method(@_);
  
 -$@
 -EOF
 -    $self->_bulk_storage(undef);
 -    DBD::Sybase::set_cslib_cb($orig_cslib_cb);
 -    unshift @_, $self;
 -    goto \&insert_bulk;
 -  }
 -  elsif ($exception) {
 -    DBD::Sybase::set_cslib_cb($orig_cslib_cb);
 -# rollback makes the bulkLogin connection unusable
 -    $self->_bulk_storage->disconnect;
 -    $self->throw_exception($exception);
 +  if ($is_identity_insert) {
 +     $self->_unset_identity_insert ($source->name);
    }
 -
 -  DBD::Sybase::set_cslib_cb($orig_cslib_cb);
  }
  
 +### end of stolen insert_bulk section
 +
+ # Make sure blobs are not bound as placeholders, and return any non-empty ones
+ # as a hash.
  sub _remove_blob_cols {
    my ($self, $source, $fields) = @_;
  
diff --cc t/746sybase.t
@@@ -11,7 -12,7 +12,7 @@@ require DBIx::Class::Storage::DBI::Syba
  
  my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_SYBASE_${_}" } qw/DSN USER PASS/};
  
- my $TESTS = 48 + 2;
 -my $TESTS = 58 + 2;
++my $TESTS = 51 + 2;
  
  if (not ($dsn && $user)) {
    plan skip_all =>
      name => { -like => 'bulk artist %' }
    });
  
-   is $bulk_rs->count, 3, 'correct number inserted via insert_bulk';
-   is ((grep $_->charfield eq 'foo', $bulk_rs->all), 3,
-     'column set correctly via insert_bulk');
-   my %bulk_ids;
-   @bulk_ids{map $_->artistid, $bulk_rs->all} = ();
-   is ((scalar keys %bulk_ids), 3,
-     'identities generated correctly in insert_bulk');
 -# test insert_bulk using populate.
++# test insert_bulk using populate, this should always pass whether or not it
++# does anything Sybase specific or not. Just here to aid debugging.
+   SKIP: {
+     skip 'insert_bulk not supported', 4
+       unless $schema->storage->_can_insert_bulk;
  
-   $bulk_rs->delete;
+     lives_ok {
+       $schema->resultset('Artist')->populate([
+         {
+           name => 'bulk artist 1',
+           charfield => 'foo',
+         },
+         {
+           name => 'bulk artist 2',
+           charfield => 'foo',
+         },
+         {
+           name => 'bulk artist 3',
+           charfield => 'foo',
+         },
+       ]);
+     } 'insert_bulk via populate';
+     is $bulk_rs->count, 3, 'correct number inserted via insert_bulk';
+     is ((grep $_->charfield eq 'foo', $bulk_rs->all), 3,
+       'column set correctly via insert_bulk');
+     my %bulk_ids;
+     @bulk_ids{map $_->artistid, $bulk_rs->all} = ();
+     is ((scalar keys %bulk_ids), 3,
+       'identities generated correctly in insert_bulk');
+     $bulk_rs->delete;
+   }
  
 -# make sure insert_bulk works a second time on the same connection
 -  SKIP: {
 -    skip 'insert_bulk not supported', 3
 -      unless $schema->storage->_can_insert_bulk;
 -
 -    lives_ok {
 -      $schema->resultset('Artist')->populate([
 -        {
 -          name => 'bulk artist 1',
 -          charfield => 'bar',
 -        },
 -        {
 -          name => 'bulk artist 2',
 -          charfield => 'bar',
 -        },
 -        {
 -          name => 'bulk artist 3',
 -          charfield => 'bar',
 -        },
 -      ]);
 -    } 'insert_bulk via populate called a second time';
 -
 -    is $bulk_rs->count, 3,
 -      'correct number inserted via insert_bulk';
 -
 -    is ((grep $_->charfield eq 'bar', $bulk_rs->all), 3,
 -      'column set correctly via insert_bulk');
 -
 -    $bulk_rs->delete;
 -  }
 -
 -# test invalid insert_bulk (missing required column)
 -#
 -# There should be a rollback, reconnect and the next valid insert_bulk should
 -# succeed.
 -  throws_ok {
 -    $schema->resultset('Artist')->populate([
 -      {
 -        charfield => 'foo',
 -      }
 -    ]);
 -  } qr/no value or default|does not allow null|placeholders/i,
 -# The second pattern is the error from fallback to regular array insert on
 -# incompatible charset.
 -# The third is for ::NoBindVars with no syb_has_blk.
 -  'insert_bulk with missing required column throws error';
 -
  # now test insert_bulk with IDENTITY_INSERT
-   lives_ok {
-     $schema->resultset('Artist')->populate([
-       {
-         artistid => 2001,
-         name => 'bulk artist 1',
-         charfield => 'foo',
-       },
-       {
-         artistid => 2002,
-         name => 'bulk artist 2',
-         charfield => 'foo',
-       },
-       {
-         artistid => 2003,
-         name => 'bulk artist 3',
-         charfield => 'foo',
-       },
-     ]);
-   } 'insert_bulk with IDENTITY_INSERT via populate';
-   is $bulk_rs->count, 3,
-     'correct number inserted via insert_bulk with IDENTITY_INSERT';
-   is ((grep $_->charfield eq 'foo', $bulk_rs->all), 3,
-     'column set correctly via insert_bulk with IDENTITY_INSERT');
-   $bulk_rs->delete;
+   SKIP: {
+     skip 'insert_bulk not supported', 3
+       unless $schema->storage->_can_insert_bulk;
+     lives_ok {
+       $schema->resultset('Artist')->populate([
+         {
+           artistid => 2001,
+           name => 'bulk artist 1',
+           charfield => 'foo',
+         },
+         {
+           artistid => 2002,
+           name => 'bulk artist 2',
+           charfield => 'foo',
+         },
+         {
+           artistid => 2003,
+           name => 'bulk artist 3',
+           charfield => 'foo',
+         },
+       ]);
+     } 'insert_bulk with IDENTITY_INSERT via populate';
+     is $bulk_rs->count, 3,
+       'correct number inserted via insert_bulk with IDENTITY_INSERT';
+     is ((grep $_->charfield eq 'foo', $bulk_rs->all), 3,
+       'column set correctly via insert_bulk with IDENTITY_INSERT');
+     $bulk_rs->delete;
+   }
  
  # test correlated subquery
    my $subq = $schema->resultset('Artist')->search({ artistid => { '>' => 3 } })
  
  # mostly stolen from the blob stuff Nniuq wrote for t/73oracle.t
    SKIP: {
-     skip 'TEXT/IMAGE support does not work with FreeTDS', 13
 -    skip 'TEXT/IMAGE support does not work with FreeTDS', 18
++    skip 'TEXT/IMAGE support does not work with FreeTDS', 15
        if $schema->storage->using_freetds;
  
      my $dbh = $schema->storage->_dbh;
        }
      }
  
 -    $rs->delete;
 -
      # blob insert with explicit PK
      # also a good opportunity to test IDENTITY_INSERT
-     {
-       local $SIG{__WARN__} = sub {};
-       eval { $dbh->do('DROP TABLE bindtype_test') };
 -    lives_ok {
 -      $rs->create( { id => 1, blob => $binstr{large} } )
 -    } 'inserted large blob without dying with manual PK';
  
-       $dbh->do(qq[
-         CREATE TABLE bindtype_test 
-         (
-           id    INT   IDENTITY PRIMARY KEY,
-           bytea INT   NULL,
-           blob  IMAGE NULL,
-           clob  TEXT  NULL
-         )
-       ],{ RaiseError => 1, PrintError => 0 });
-     }
 -    lives_and {
 -      ok($rs->find(1)->blob eq $binstr{large})
 -    } 'verified inserted large blob with manual PK';
++    $rs->delete;
++
 +    my $created = eval { $rs->create( { id => 1, blob => $binstr{large} } ) };
 +    ok(!$@, "inserted large blob without dying with manual PK");
 +    diag $@ if $@;
 +
 +    my $got = eval {
 +      $rs->find(1)->blob
 +    };
 +    diag $@ if $@;
 +    ok($got eq $binstr{large}, "verified inserted large blob with manual PK");
  
      # try a blob update
      my $new_str = $binstr{large} . 'mtfnpy';
        $schema = get_schema();
      }
  
 -    lives_ok {
 -      $rs->search({ id => 1 })->update({ blob => $new_str })
 -    } 'updated blob successfully';
 -
 -    lives_and {
 -      ok($rs->find(1)->blob eq $new_str)
 -    } 'verified updated blob';
 +    eval { $rs->search({ id => 1 })->update({ blob => $new_str }) };
 +    ok !$@, 'updated blob successfully';
 +    diag $@ if $@;
 +    $got = eval {
 +      $rs->find(1)->blob
 +    };
 +    diag $@ if $@;
 +    ok($got eq $new_str, "verified updated blob");
  
+     # try a blob update with IDENTITY_UPDATE
+     lives_and {
+       $new_str = $binstr{large} . 'hlagh';
+       $rs->find(1)->update({ id => 999, blob => $new_str });
+       ok($rs->find(999)->blob eq $new_str);
+     } 'verified updated blob with IDENTITY_UPDATE';
      ## try multi-row blob update
      # first insert some blobs
-     $rs->find(1)->delete;
 -    $new_str = $binstr{large} . 'foo';
 -    lives_and {
 -      $rs->delete;
 -      $rs->create({ blob => $binstr{large} }) for (1..2);
 -      $rs->update({ blob => $new_str });
 -      is((grep $_->blob eq $new_str, $rs->all), 2);
 -    } 'multi-row blob update';
 -
+     $rs->delete;
 -
 -    # now try insert_bulk with blobs
 -    $new_str = $binstr{large} . 'bar';
 -    lives_ok {
 -      $rs->populate([
 -        {
 -          bytea => 1,
 -          blob => $binstr{large},
 -          clob => $new_str,
 -        },
 -        {
 -          bytea => 1,
 -          blob => $binstr{large},
 -          clob => $new_str,
 -        },
 -      ]);
 -    } 'insert_bulk with blobs does not die';
 -
 -    is((grep $_->blob eq $binstr{large}, $rs->all), 2,
 -      'IMAGE column set correctly via insert_bulk');
 -
 -    is((grep $_->clob eq $new_str, $rs->all), 2,
 -      'TEXT column set correctly via insert_bulk');
 +    $rs->create({ blob => $binstr{large} }) for (1..3);
 +    $new_str = $binstr{large} . 'foo';
 +    $rs->update({ blob => $new_str });
 +    is((grep $_->blob eq $new_str, $rs->all), 3, 'multi-row blob update');
+     # make sure impossible blob update throws
+     throws_ok {
+       $rs->update({ clob => 'foo' });
+       $rs->create({ clob => 'bar' });
+       $rs->search({ clob => 'foo' })->update({ clob => 'bar' });
+     } qr/impossible/, 'impossible blob update throws';
    }
  
  # test MONEY column support