Support INSERT RETURNING for SQL Server
Arthur Axel 'fREW' Schmidt [Fri, 31 May 2013 14:25:13 +0000 (09:25 -0500)]
lib/DBIx/Class/SQLMaker/MSSQL.pm
lib/DBIx/Class/Storage/DBI/MSSQL.pm
t/746mssql.t

index 39e2c4f..943bf4d 100644 (file)
@@ -13,4 +13,74 @@ sub _rno_default_order {
   return \ '(SELECT(1))';
 }
 
-1;
+# more or less copy pasted directly from ::SQLMaker
+sub insert {
+  my $self    = shift;
+  my $table   = $self->_table(shift);
+  my $data    = shift || return;
+  my $options = shift;
+
+  if (! $data or (ref $data eq 'HASH' and !keys %{$data} ) ) {
+    my @bind;
+    my $sql = sprintf(
+      'INSERT INTO %s DEFAULT VALUES', $_[0]->_quote($table)
+    );
+
+    if ( ($options||{})->{returning} ) {
+      my $s;
+      ($s, @bind) = $self->_insert_returning ($options);
+      $sql .= $s;
+    }
+
+    return ($sql, @bind);
+  }
+
+  my $method = $self->_METHOD_FOR_refkind("_insert", $data);
+  my ($sql, @bind) = $self->$method($data);
+
+  $sql = join " ", $self->_sqlcase('insert into'), $table, $sql;
+
+  if ($options->{returning}) {
+    my ($s, @b) = $self->_insert_returning ($options);
+    $sql =~ s/\bVALUES\b/$s VALUES/;
+    @bind = (@b, @bind);
+  }
+
+  return wantarray ? ($sql, @bind) : $sql;
+}
+
+
+# insert returning docs at
+# http://msdn.microsoft.com/en-us/library/ms177564.aspx
+
+sub _insert_returning {
+  my ($self, $options) = @_;
+
+  my $f = $options->{returning};
+
+  my @f_list = do {
+    if (! ref $f) {
+      ($f)
+    }
+    elsif (ref $f eq 'ARRAY') {
+      @$f
+    }
+    elsif (ref $f eq 'SCALAR') {
+      (
+        ($$f)
+      )
+    }
+    else {
+      $self->throw_exception("Unsupported INSERT RETURNING option $f");
+    }
+  };
+
+  return (
+    join ' ',
+    $self->_sqlcase(' output'),
+    join ', ',
+    map $self->_quote("INSERTED.$_"), @f_list,
+  );
+}
+
+1
index 5cc2644..11b4af6 100644 (file)
@@ -71,7 +71,11 @@ sub _prep_for_execute {
   # point we don't have many guarantees we will get what we expected.
   # http://msdn.microsoft.com/en-us/library/ms190315.aspx
   # http://davidhayden.com/blog/dave/archive/2006/01/17/2736.aspx
-  if ($self->_perform_autoinc_retrieval and not $self->_no_scope_identity_query) {
+  if (
+    not $self->_use_insert_returning and
+    $self->_perform_autoinc_retrieval and
+    not $self->_no_scope_identity_query
+  ) {
     $sql .= "\nSELECT SCOPE_IDENTITY()";
   }
 
@@ -91,7 +95,9 @@ sub _execute {
     my $identity;
 
     # we didn't even try on ftds
-    unless ($self->_no_scope_identity_query) {
+    if (not $self->_use_insert_returning and
+        not $self->_no_scope_identity_query
+    ) {
       ($identity) = try { $sth->fetchrow_array };
       $sth->finish;
     }
@@ -194,6 +200,9 @@ sub _ping {
   };
 }
 
+# check for 2005 or greater here.
+sub _use_insert_returning { $_[0]->_sql_server_2005_or_higher }
+
 package # hide from PAUSE
   DBIx::Class::Storage::DBI::MSSQL::DateTime::Format;
 
index 5e062f6..db86ffc 100644 (file)
@@ -16,6 +16,12 @@ my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MSSQL_ODBC_${_}" } qw/DSN USER PA
 
 plan skip_all => 'Set $ENV{DBICTEST_MSSQL_ODBC_DSN}, _USER and _PASS to run this test'
   unless ($dsn && $user);
+my $schema;
+for my $use_insert_returning (0, 1) {
+  no warnings qw/redefine once/;
+  require DBIx::Class::Storage::DBI::MSSQL;
+  local *DBIx::Class::Storage::DBI::MSSQL::_use_insert_returning =
+    sub { $use_insert_returning };
 
 {
   my $srv_ver = DBICTest::Schema->connect($dsn, $user, $pass)->storage->_server_info->{dbms_version};
@@ -23,7 +29,7 @@ plan skip_all => 'Set $ENV{DBICTEST_MSSQL_ODBC_DSN}, _USER and _PASS to run this
 }
 
 DBICTest::Schema->load_classes('ArtistGUID');
-my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
+$schema = DBICTest::Schema->connect($dsn, $user, $pass);
 
 {
   no warnings 'redefine';
@@ -500,6 +506,7 @@ SQL
     }
   }
 }
+}
 
 done_testing;