Restore ability to handle underdefined root (t/prefetch/incomplete.t)
[dbsrgits/DBIx-Class.git] / t / 747mssql_ado.t
index d86e4e1..77a88dc 100644 (file)
@@ -3,24 +3,49 @@ use warnings;
 
 use Test::More;
 use Test::Exception;
+use Try::Tiny;
+use DBIx::Class::Optional::Dependencies ();
 use lib qw(t/lib);
 use DBICTest;
 
+plan skip_all => 'Test needs ' . DBIx::Class::Optional::Dependencies->req_missing_for ('test_rdbms_mssql_ado')
+  unless DBIx::Class::Optional::Dependencies->req_ok_for ('test_rdbms_mssql_ado');
+
+# Example DSN (from frew):
+# dbi:ADO:PROVIDER=sqlncli10;SERVER=tcp:172.24.2.10;MARS Connection=True;Initial Catalog=CIS;UID=cis_web;PWD=...;DataTypeCompatibility=80;
+
 my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MSSQL_ADO_${_}" } qw/DSN USER PASS/};
 
 plan skip_all => 'Set $ENV{DBICTEST_MSSQL_ADO_DSN}, _USER and _PASS to run this test'
   unless ($dsn && $user);
 
-plan tests => 11;
+DBICTest::Schema->load_classes(qw/VaryingMAX ArtistGUID/);
+
+my %binstr = ( 'small' => join('', map { chr($_) } ( 1 .. 127 )) );
+$binstr{'large'} = $binstr{'small'} x 1024;
+
+my $maxloblen = length $binstr{'large'};
+
+my $schema = DBICTest::Schema->connect($dsn, $user, $pass, {
+  auto_savepoint => 1,
+  LongReadLen => $maxloblen,
+});
 
-my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
 $schema->storage->ensure_connected;
 
-isa_ok( $schema->storage, 'DBIx::Class::Storage::DBI::ADO::Microsoft_SQL_Server' );
+isa_ok($schema->storage, 'DBIx::Class::Storage::DBI::ADO::Microsoft_SQL_Server');
+
+my $ver = $schema->storage->_server_info->{normalized_dbms_version};
+
+ok $ver, 'can introspect DBMS version';
+
+# 2005 and greater
+is $schema->storage->sql_limit_dialect, ($ver >= 9 ? 'RowNumberOver' : 'Top'),
+  'correct limit dialect detected';
 
 $schema->storage->dbh_do (sub {
     my ($storage, $dbh) = @_;
-    eval { $dbh->do("DROP TABLE artist") };
+    try { local $^W = 0; $dbh->do("DROP TABLE artist") };
     $dbh->do(<<'SQL');
 CREATE TABLE artist (
    artistid INT IDENTITY NOT NULL,
@@ -32,25 +57,61 @@ CREATE TABLE artist (
 SQL
 });
 
-my $new = $schema->resultset('Artist')->create({ name => 'foo' });
+$schema->storage->dbh_do (sub {
+  my ($storage, $dbh) = @_;
+  try { local $^W = 0; $dbh->do("DROP TABLE artist_guid") };
+  $dbh->do(<<"SQL");
+CREATE TABLE artist_guid (
+ artistid UNIQUEIDENTIFIER NOT NULL,
+ name VARCHAR(100),
+ rank INT NULL,
+ charfield CHAR(10) NULL,
+ a_guid UNIQUEIDENTIFIER,
+ primary key(artistid)
+)
+SQL
+});
+
+my $have_max = $ver >= 9; # 2005 and greater
+
+$schema->storage->dbh_do (sub {
+    my ($storage, $dbh) = @_;
+    try { local $^W = 0; $dbh->do("DROP TABLE varying_max_test") };
+    $dbh->do("
+CREATE TABLE varying_max_test (
+   id INT IDENTITY NOT NULL,
+" . ($have_max ? "
+   varchar_max VARCHAR(MAX),
+   nvarchar_max NVARCHAR(MAX),
+   varbinary_max VARBINARY(MAX),
+" : "
+   varchar_max TEXT,
+   nvarchar_max NTEXT,
+   varbinary_max IMAGE,
+") . "
+   primary key(id)
+)");
+});
+
+my $ars = $schema->resultset('Artist');
+
+my $new = $ars->create({ name => 'foo' });
 ok($new->artistid > 0, 'Auto-PK worked');
 
 # make sure select works
 my $found = $schema->resultset('Artist')->search({ name => 'foo' })->first;
 is $found->artistid, $new->artistid, 'search works';
 
-# try a select with a big column list
-TODO: {
-  local $TODO = 'select with a big column list does not work';
-
-  $found = $schema->resultset('Artist')->search({ name => 'foo' }, {
-    select => ['name', map "'foo' foo_$_", 0..50]
-  })->first;
-  is $found->artistid, $new->artistid, 'select with big column list';
-}
+# test large column list in select
+$found = $schema->resultset('Artist')->search({ name => 'foo' }, {
+  select => ['artistid', 'name', map \"'foo' foo_$_", 0..50],
+  as     => ['artistid', 'name', map        "foo_$_", 0..50],
+})->first;
+is $found->artistid, $new->artistid, 'select with big column list';
+is $found->get_column('foo_50'), 'foo', 'last item in big column list';
 
 # create a few more rows
-for (1..6) {
+for (1..12) {
   $schema->resultset('Artist')->create({ name => 'Artist ' . $_ });
 }
 
@@ -59,14 +120,270 @@ my $rs1 = $schema->resultset('Artist')->search({}, { order_by => 'artistid' });
 my $rs2 = $schema->resultset('Artist')->search({}, { order_by => 'name' });
 
 while ($rs1->next) {
-  ok eval { $rs2->next }, 'multiple active cursors';
+  ok try { $rs2->next }, 'multiple active cursors';
 }
 
+# test bug where ADO blows up if the first bindparam is shorter than the second
+is $schema->resultset('Artist')->search({ artistid => 2 })->first->name,
+  'Artist 1',
+  'short bindparam';
+
+is $schema->resultset('Artist')->search({ artistid => 13 })->first->name,
+  'Artist 12',
+  'longer bindparam';
+
+# test explicit key spec
+$new = $ars->create ({ name => 'bar', artistid => 66 });
+is($new->artistid, 66, 'Explicit PK worked');
+$new->discard_changes;
+is($new->artistid, 66, 'Explicit PK assigned');
+
+# test basic transactions
+$schema->txn_do(sub {
+  $ars->create({ name => 'transaction_commit' });
+});
+ok($ars->search({ name => 'transaction_commit' })->first,
+  'transaction committed');
+$ars->search({ name => 'transaction_commit' })->delete,
+throws_ok {
+  $schema->txn_do(sub {
+    $ars->create({ name => 'transaction_rollback' });
+    die 'rolling back';
+  });
+} qr/rolling back/, 'rollback executed';
+is $ars->search({ name => 'transaction_rollback' })->first, undef,
+  'transaction rolled back';
+
+# test two-phase commit and inner transaction rollback from nested transactions
+$schema->txn_do(sub {
+  $ars->create({ name => 'in_outer_transaction' });
+  $schema->txn_do(sub {
+    $ars->create({ name => 'in_inner_transaction' });
+  });
+  ok($ars->search({ name => 'in_inner_transaction' })->first,
+    'commit from inner transaction visible in outer transaction');
+  throws_ok {
+    $schema->txn_do(sub {
+      $ars->create({ name => 'in_inner_transaction_rolling_back' });
+      die 'rolling back inner transaction';
+    });
+  } qr/rolling back inner transaction/, 'inner transaction rollback executed';
+});
+ok($ars->search({ name => 'in_outer_transaction' })->first,
+  'commit from outer transaction');
+ok($ars->search({ name => 'in_inner_transaction' })->first,
+  'commit from inner transaction');
+is $ars->search({ name => 'in_inner_transaction_rolling_back' })->first,
+  undef,
+  'rollback from inner transaction';
+$ars->search({ name => 'in_outer_transaction' })->delete;
+$ars->search({ name => 'in_inner_transaction' })->delete;
+
+# test populate
+lives_ok (sub {
+  my @pop;
+  for (1..2) {
+    push @pop, { name => "Artist_$_" };
+  }
+  $ars->populate (\@pop);
+});
+
+# test populate with explicit key
+lives_ok (sub {
+  my @pop;
+  for (1..2) {
+    push @pop, { name => "Artist_expkey_$_", artistid => 100 + $_ };
+  }
+  $ars->populate (\@pop);
+});
+
+# count what we did so far
+is ($ars->count, 18, 'Simple count works');
+
+# test empty insert
+my $current_artistid = $ars->search({}, {
+  select => [ { max => 'artistid' } ], as => ['artistid']
+})->first->artistid;
+
+my $row;
+lives_ok { $row = $ars->create({}) }
+  'empty insert works';
+
+$row->discard_changes;
+
+is $row->artistid, $current_artistid+1,
+  'empty insert generated correct PK';
+
+# test that autoinc column still works after empty insert
+  $row = $ars->create({ name => 'after_empty_insert' });
+
+  is $row->artistid, $current_artistid+2,
+    'autoincrement column functional aftear empty insert';
+
+my $rs = $schema->resultset('VaryingMAX');
+
+foreach my $size (qw/small large/) {
+  my $orig_debug = $schema->storage->debug;
+
+  $schema->storage->debug(0) if $size eq 'large';
+
+  my $str = $binstr{$size};
+  my $row;
+  lives_ok {
+    $row = $rs->create({
+      varchar_max => $str, nvarchar_max => $str, varbinary_max => $str
+    });
+  } "created $size VARXXX(MAX) LOBs";
+
+  lives_ok {
+    $row->discard_changes;
+  } 're-selected just-inserted LOBs';
+
+  cmp_ok try { $row->varchar_max },   'eq', $str, 'VARCHAR(MAX) matches';
+  cmp_ok try { $row->nvarchar_max },  'eq', $str, 'NVARCHAR(MAX) matches';
+  cmp_ok try { $row->varbinary_max }, 'eq', $str, 'VARBINARY(MAX) matches';
+
+  $schema->storage->debug($orig_debug);
+}
+
+# test regular blobs
+
+try { local $^W = 0; $schema->storage->dbh->do('DROP TABLE bindtype_test') };
+$schema->storage->dbh->do(qq[
+CREATE TABLE bindtype_test
+(
+  id     INT IDENTITY NOT NULL PRIMARY KEY,
+  bytea  INT NULL,
+  blob   IMAGE NULL,
+  clob   TEXT NULL,
+  a_memo NTEXT NULL
+)
+],{ RaiseError => 1, PrintError => 1 });
+
+$rs = $schema->resultset('BindType');
+my $id = 0;
+
+foreach my $type (qw( blob clob a_memo )) {
+  foreach my $size (qw( small large )) {
+    $id++;
+
+    lives_ok { $rs->create( { 'id' => $id, $type => $binstr{$size} } ) }
+      "inserted $size $type without dying" or next;
+
+    my $from_db = eval { $rs->find($id)->$type } || '';
+    diag $@ if $@;
+
+    ok($from_db eq $binstr{$size}, "verified inserted $size $type" )
+      or do {
+        my $hexdump = sub {
+          join '', map sprintf('%02X', ord), split //, shift
+        };
+        diag 'Got: ', "\n", substr($hexdump->($from_db),0,255), '...',
+          substr($hexdump->($from_db),-255);
+        diag 'Size: ', length($from_db);
+        diag 'Expected Size: ', length($binstr{$size});
+        diag 'Expected: ', "\n",
+          substr($hexdump->($binstr{$size}), 0, 255),
+          "...", substr($hexdump->($binstr{$size}),-255);
+      };
+  }
+}
+# test IMAGE update
+lives_ok {
+  $rs->search({ id => 0 })->update({ blob => $binstr{small} });
+} 'updated IMAGE to small binstr without dying';
+
+lives_ok {
+  $rs->search({ id => 0 })->update({ blob => $binstr{large} });
+} 'updated IMAGE to large binstr without dying';
+
+# test GUIDs
+lives_ok {
+  $row = $schema->resultset('ArtistGUID')->create({ name => 'mtfnpy' })
+} 'created a row with a GUID';
+
+ok(
+  eval { $row->artistid },
+  'row has GUID PK col populated',
+);
+diag $@ if $@;
+
+my $guid = try { $row->artistid }||'';
+
+ok(($guid !~ /^{.*?}\z/), 'GUID not enclosed in braces')
+  or diag "GUID is: $guid";
+
+ok(
+  eval { $row->a_guid },
+  'row has a GUID col with auto_nextval populated',
+);
+diag $@ if $@;
+
+my $row_from_db = $schema->resultset('ArtistGUID')
+  ->search({ name => 'mtfnpy' })->first;
+
+is try { $row_from_db->artistid }, try { $row->artistid },
+  'PK GUID round trip (via ->search->next)';
+
+is try { $row_from_db->a_guid }, try { $row->a_guid },
+  'NON-PK GUID round trip (via ->search->next)';
+
+$row_from_db = try { $schema->resultset('ArtistGUID')
+  ->find($row->artistid) };
+
+is try { $row_from_db->artistid }, try { $row->artistid },
+  'PK GUID round trip (via ->find)';
+
+is try { $row_from_db->a_guid }, try { $row->a_guid },
+  'NON-PK GUID round trip (via ->find)';
+
+($row_from_db) = $schema->resultset('ArtistGUID')
+  ->search({ name => 'mtfnpy' })->all;
+
+is try { $row_from_db->artistid }, try { $row->artistid },
+  'PK GUID round trip (via ->search->all)';
+
+is try { $row_from_db->a_guid }, try { $row->a_guid },
+  'NON-PK GUID round trip (via ->search->all)';
+
+lives_ok {
+  $row = $schema->resultset('ArtistGUID')->create({
+      artistid => '70171270-4822-4450-81DF-921F99BA3C06',
+      name => 'explicit_guid',
+  });
+} 'created a row with explicit PK GUID';
+
+is try { $row->artistid }, '70171270-4822-4450-81DF-921F99BA3C06',
+  'row has correct PK GUID';
+
+lives_ok {
+  $row->update({ artistid => '70171270-4822-4450-81DF-921F99BA3C07' });
+} "updated row's PK GUID";
+
+is try { $row->artistid }, '70171270-4822-4450-81DF-921F99BA3C07',
+  'row has correct PK GUID';
+
+lives_ok {
+  $row->delete;
+} 'deleted the row';
+
+lives_ok {
+  $schema->resultset('ArtistGUID')->populate([{
+      artistid => '70171270-4822-4450-81DF-921F99BA3C06',
+      name => 'explicit_guid',
+  }]);
+} 'created a row with explicit PK GUID via ->populate in void context';
+
+done_testing;
+
 # clean up our mess
 END {
-  if (my $dbh = eval { $schema->storage->_dbh }) {
-    eval { $dbh->do("DROP TABLE $_") }
-      for qw/artist/;
+  local $SIG{__WARN__} = sub {};
+  if (my $dbh = try { $schema->storage->_dbh }) {
+    (try { $dbh->do("DROP TABLE $_") })
+      for qw/artist artist_guid varying_max_test bindtype_test/;
   }
+
+  undef $schema;
 }
 # vim:sw=2 sts=2