remove unsafe_insert
[dbsrgits/DBIx-Class.git] / t / 746sybase.t
index 63e4d76..b8a42e2 100644 (file)
@@ -6,10 +6,12 @@ use Test::More;
 use Test::Exception;
 use lib qw(t/lib);
 use DBICTest;
+use DBIx::Class::Storage::DBI::Sybase;
+use DBIx::Class::Storage::DBI::Sybase::NoBindVars;
 
 my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_SYBASE_${_}" } qw/DSN USER PASS/};
 
-my $TESTS = 29 + 2;
+my $TESTS = 40 + 2;
 
 if (not ($dsn && $user)) {
   plan skip_all =>
@@ -17,7 +19,7 @@ if (not ($dsn && $user)) {
     "\nWarning: This test drops and creates the tables " .
     "'artist' and 'bindtype_test'";
 } else {
-  plan tests => $TESTS*2;
+  plan tests => $TESTS*2 + 1;
 }
 
 my @storage_types = (
@@ -27,26 +29,37 @@ my @storage_types = (
 my $schema;
 my $storage_idx = -1;
 
+sub get_schema {
+  DBICTest::Schema->connect($dsn, $user, $pass, {
+    on_connect_call => [
+      [ blob_setup => log_on_update => 1 ], # this is a safer option
+    ],
+  });
+}
+
+my $ping_count = 0;
+{
+  my $ping = DBIx::Class::Storage::DBI::Sybase->can('_ping');
+  *DBIx::Class::Storage::DBI::Sybase::_ping = sub {
+    $ping_count++;
+    goto $ping;
+  };
+}
+
 for my $storage_type (@storage_types) {
   $storage_idx++;
-  $schema = DBICTest::Schema->clone;
 
   unless ($storage_type eq 'DBI::Sybase') { # autodetect
-    $schema->storage_type("::$storage_type");
+    DBICTest::Schema->storage_type("::$storage_type");
   }
-  $schema->connection($dsn, $user, $pass, {
-    AutoCommit => 1,
-    on_connect_call => [
-      [ blob_setup => log_on_update => 1 ], # this is a safer option
-    ],
-  });
+
+  $schema = get_schema();
 
   $schema->storage->ensure_connected;
-  $schema->storage->_dbh->disconnect;
 
   if ($storage_idx == 0 &&
       $schema->storage->isa('DBIx::Class::Storage::DBI::Sybase::NoBindVars')) {
-# no placeholders in this version of Sybase or DBD::Sybase
+# no placeholders in this version of Sybase or DBD::Sybase (or using FreeTDS)
       my $tb = Test::More->builder;
       $tb->skip('no placeholders') for 1..$TESTS;
       next;
@@ -54,6 +67,7 @@ for my $storage_type (@storage_types) {
 
   isa_ok( $schema->storage, "DBIx::Class::Storage::$storage_type" );
 
+  $schema->storage->_dbh->disconnect;
   lives_ok (sub { $schema->storage->dbh }, 'reconnect works');
 
   $schema->storage->dbh_do (sub {
@@ -80,12 +94,27 @@ SQL
 
   $seen_id{$new->artistid}++;
 
-  for (1..6) {
+# check redispatch to storage-specific insert when auto-detected storage
+  if ($storage_type eq 'DBI::Sybase') {
+    DBICTest::Schema->storage_type('::DBI');
+    $schema = get_schema();
+  }
+
+  $new = $schema->resultset('Artist')->create({ name => 'Artist 1' });
+  is ( $seen_id{$new->artistid}, undef, 'id for Artist 1 is unique' );
+  $seen_id{$new->artistid}++;
+
+# inserts happen in a txn, so we make sure it still works inside a txn too
+  $schema->txn_begin;
+
+  for (2..6) {
     $new = $schema->resultset('Artist')->create({ name => 'Artist ' . $_ });
     is ( $seen_id{$new->artistid}, undef, "id for Artist $_ is unique" );
     $seen_id{$new->artistid}++;
   }
 
+  $schema->txn_commit;
+
 # test simple count
   is ($schema->resultset('Artist')->count, 7, 'count(*) of whole table ok');
 
@@ -128,12 +157,52 @@ SQL
 
   is( $it->count, 7, 'COUNT of GROUP_BY ok' );
 
+# do an identity insert (which should happen with no txn when using
+# placeholders.)
+  {
+    no warnings 'redefine';
+
+    my @debug_out;
+    local $schema->storage->{debug} = 1;
+    local $schema->storage->debugobj->{callback} = sub {
+      push @debug_out, $_[1];
+    };
+
+    my $txn_used = 0;
+    my $txn_commit = \&DBIx::Class::Storage::DBI::txn_commit;
+    local *DBIx::Class::Storage::DBI::txn_commit = sub {
+      $txn_used = 1;
+      goto &$txn_commit;
+    };
+
+    $schema->resultset('Artist')
+      ->create({ artistid => 999, name => 'mtfnpy' });
+
+    ok((grep /IDENTITY_INSERT/i, @debug_out), 'IDENTITY_INSERT');
+
+    SKIP: {
+      skip 'not testing lack of txn on IDENTITY_INSERT with NoBindVars', 1
+        if $storage_type =~ /NoBindVars/i;
+
+      is $txn_used, 0, 'no txn on insert with IDENTITY_INSERT';
+    }
+  }
+
+# test correlated subquery
+  my $subq = $schema->resultset('Artist')->search({ artistid => { '>' => 3 } })
+    ->get_column('artistid')
+    ->as_query;
+  my $subq_rs = $schema->resultset('Artist')->search({
+    artistid => { -in => $subq }
+  });
+  is $subq_rs->count, 11, 'correlated subquery';
+
 # mostly stolen from the blob stuff Nniuq wrote for t/73oracle.t
   SKIP: {
-    skip 'Need at least version 1.09 of DBD::Sybase to test TEXT/IMAGE', 12
-        unless $DBD::Sybase::VERSION >= 1.09;
+    skip 'TEXT/IMAGE support does not work with FreeTDS', 12
+      if $schema->storage->using_freetds;
 
-    my $dbh = $schema->storage->dbh;
+    my $dbh = $schema->storage->_dbh;
     {
       local $SIG{__WARN__} = sub {};
       eval { $dbh->do('DROP TABLE bindtype_test') };
@@ -153,9 +222,12 @@ SQL
     $binstr{'large'} = $binstr{'small'} x 1024;
 
     my $maxloblen = length $binstr{'large'};
-    note
-      "Localizing LongReadLen to $maxloblen to avoid truncation of test data";
-    local $dbh->{'LongReadLen'} = $maxloblen * 2;
+    
+    if (not $schema->storage->using_freetds) {
+      $dbh->{'LongReadLen'} = $maxloblen * 2;
+    } else {
+      $dbh->do("set textsize ".($maxloblen * 2));
+    }
 
     my $rs = $schema->resultset('BindType');
     my $last_id;
@@ -179,6 +251,7 @@ SQL
     }
 
     # blob insert with explicit PK
+    # also a good opportunity to test IDENTITY_INSERT
     {
       local $SIG{__WARN__} = sub {};
       eval { $dbh->do('DROP TABLE bindtype_test') };
@@ -186,7 +259,7 @@ SQL
       $dbh->do(qq[
         CREATE TABLE bindtype_test 
         (
-          id    INT   PRIMARY KEY,
+          id    INT   IDENTITY PRIMARY KEY,
           bytea INT   NULL,
           blob  IMAGE NULL,
           clob  TEXT  NULL
@@ -205,6 +278,13 @@ SQL
 
     # try a blob update
     my $new_str = $binstr{large} . 'mtfnpy';
+
+    # check redispatch to storage-specific update when auto-detected storage
+    if ($storage_type eq 'DBI::Sybase') {
+      DBICTest::Schema->storage_type('::DBI');
+      $schema = get_schema();
+    }
+
     eval { $rs->search({ id => 1 })->update({ blob => $new_str }) };
     ok !$@, 'updated blob successfully';
     diag $@ if $@;
@@ -214,12 +294,87 @@ SQL
     diag $@ if $@;
     ok($got eq $new_str, "verified updated blob");
   }
+
+# test MONEY column support
+  $schema->storage->dbh_do (sub {
+      my ($storage, $dbh) = @_;
+      eval { $dbh->do("DROP TABLE money_test") };
+      $dbh->do(<<'SQL');
+CREATE TABLE money_test (
+   id INT IDENTITY PRIMARY KEY,
+   amount MONEY NULL
+)
+SQL
+  });
+
+# test insert transaction when there's an active cursor
+  SKIP: {
+    skip 'not testing insert with active cursor if using ::NoBindVars', 1
+      if $storage_type =~ /NoBindVars/i;
+
+    my $artist_rs = $schema->resultset('Artist');
+    $artist_rs->first;
+    lives_ok {
+      my $row = $schema->resultset('Money')->create({ amount => 100 });
+      $row->delete;
+    } 'inserted a row with an active cursor';
+    $ping_count-- if $@; # dbh_do calls ->connected
+  }
+
+# test insert in an outer transaction when there's an active cursor
+  TODO: {
+    local $TODO = 'this should work once we have eager cursors';
+
+# clear state, or we get a deadlock on $row->delete
+# XXX figure out why this happens
+    $schema->storage->disconnect;
+
+    lives_ok {
+      $schema->txn_do(sub {
+        my $artist_rs = $schema->resultset('Artist');
+        $artist_rs->first;
+        my $row = $schema->resultset('Money')->create({ amount => 100 });
+        $row->delete;
+      });
+    } 'inserted a row with an active cursor in outer txn';
+    $ping_count-- if $@; # dbh_do calls ->connected
+  }
+
+# Now test money values.
+  my $rs = $schema->resultset('Money');
+
+  my $row;
+  lives_ok {
+    $row = $rs->create({ amount => 100 });
+  } 'inserted a money value';
+
+  is eval { $rs->find($row->id)->amount }, 100, 'money value round-trip';
+
+  lives_ok {
+    $row->update({ amount => 200 });
+  } 'updated a money value';
+
+  is eval { $rs->find($row->id)->amount },
+    200, 'updated money value round-trip';
+
+  lives_ok {
+    $row->update({ amount => undef });
+  } 'updated a money value to NULL';
+
+  my $null_amount = eval { $rs->find($row->id)->amount };
+  ok(
+    (($null_amount == undef) && (not $@)),
+    'updated money value to NULL round-trip'
+  );
+  diag $@ if $@;
 }
 
+is $ping_count, 0, 'no pings';
+
 # clean up our mess
 END {
   if (my $dbh = eval { $schema->storage->_dbh }) {
-    $dbh->do('DROP TABLE artist');
-    eval { $dbh->do('DROP TABLE bindtype_test')    };
+    eval { $dbh->do("DROP TABLE $_") }
+      for qw/artist bindtype_test money_test/;
   }
 }