select for patch from justin
Wallace Reis [Sat, 29 Sep 2007 00:35:55 +0000 (00:35 +0000)]
lib/DBIx/Class/Storage/DBI.pm
lib/DBIx/Class/Storage/DBI/Oracle/WhereJoins.pm
t/72pg.t

index c0b17ad..f656c08 100644 (file)
@@ -19,6 +19,9 @@ __PACKAGE__->mk_group_accessors('simple' =>
 
 __PACKAGE__->cursor_class('DBIx::Class::Storage::DBI::Cursor');
 
+__PACKAGE__->mk_group_accessors('inherited' => qw/sql_maker_class/);
+__PACKAGE__->sql_maker_class('DBIC::SQL::Abstract');
+
 BEGIN {
 
 package DBIC::SQL::Abstract; # Would merge upstream, but nate doesn't reply :(
@@ -83,6 +86,15 @@ sub select {
   my ($sql, @ret) = $self->SUPER::select(
     $table, $self->_recurse_fields($fields), $where, $order, @rest
   );
+  $sql .= 
+    $self->{for} ?
+    (
+      $self->{for} eq 'update' ? ' FOR UPDATE' :
+      $self->{for} eq 'shared' ? ' FOR SHARE'  :
+      ''
+    ) :
+    ''
+  ;
   return wantarray ? ($sql, @ret, @{$self->{having_bind}}) : $sql;
 }
 
@@ -745,7 +757,8 @@ sub _sql_maker_args {
 sub sql_maker {
   my ($self) = @_;
   unless ($self->_sql_maker) {
-    $self->_sql_maker(new DBIC::SQL::Abstract( $self->_sql_maker_args ));
+    my $sql_maker_class = $self->sql_maker_class;
+    $self->_sql_maker($sql_maker_class->new( $self->_sql_maker_args ));
   }
   return $self->_sql_maker;
 }
@@ -1085,9 +1098,15 @@ sub delete {
 sub _select {
   my ($self, $ident, $select, $condition, $attrs) = @_;
   my $order = $attrs->{order_by};
+
   if (ref $condition eq 'SCALAR') {
     $order = $1 if $$condition =~ s/ORDER BY (.*)$//i;
   }
+
+  my $for = delete $attrs->{for};
+  my $sql_maker = $self->sql_maker;
+  local $sql_maker->{for} = $for;
+
   if (exists $attrs->{group_by} || $attrs->{having}) {
     $order = {
       group_by => $attrs->{group_by},
@@ -1105,6 +1124,7 @@ sub _select {
       if (defined($attrs->{rows}) && !($attrs->{rows} > 0));
     push @args, $attrs->{rows}, $attrs->{offset};
   }
+
   return $self->_execute(@args);
 }
 
index cd1f7da..f25415a 100644 (file)
@@ -5,6 +5,8 @@ use base qw( DBIx::Class::Storage::DBI::Oracle::Generic );
 use strict;
 use warnings;
 
+__PACKAGE__->sql_maker_class('DBIC::SQL::Abstract::Oracle');
+
 BEGIN {
   package DBIC::SQL::Abstract::Oracle;
 
@@ -91,18 +93,6 @@ BEGIN {
   }
 }
 
-sub sql_maker {
-  my ($self) = @_;
-
-  unless ($self->_sql_maker) {
-    $self->_sql_maker(
-      new DBIC::SQL::Abstract::Oracle( $self->_sql_maker_args )
-    );
-  }
-
-  return $self->_sql_maker;
-}
-
 1;
 
 __END__
index 277248e..7b99451 100644 (file)
--- a/t/72pg.t
+++ b/t/72pg.t
@@ -27,7 +27,7 @@ my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_PG_${_}" } qw/DSN USER PASS/};
 plan skip_all => 'Set $ENV{DBICTEST_PG_DSN}, _USER and _PASS to run this test'
  . ' (note: creates and drops tables named artist and casecheck!)' unless ($dsn && $user);
 
-plan tests => 10;
+plan tests => 16;
 
 DBICTest::Schema->load_classes( 'Casecheck' );
 my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
@@ -99,6 +99,83 @@ is( $NAME_info->{size}, 2, "Case sensitive matching info for 'NAME'" );
 my $uc_name_info = $schema->source('Casecheck')->column_info( 'uc_name' );
 is( $uc_name_info->{size}, 3, "Case insensitive matching info for 'uc_name'" );
 
+# Test SELECT ... FOR UPDATE
+my $HaveSysSigAction = eval "require Sys::SigAction" && !$@;
+if ($HaveSysSigAction) {
+    Sys::SigAction->import( 'set_sig_handler' );
+}
+
+SKIP: {
+    skip "Sys::SigAction is not available", 3 unless $HaveSysSigAction;
+    # create a new schema
+    my $schema2 = DBICTest::Schema->connect($dsn, $user, $pass);
+    $schema2->source("Artist")->name("testschema.artist");
+
+    $schema->txn_do( sub {
+        my $artist = $schema->resultset('Artist')->search(
+            {
+                artistid => 1
+            },
+            {
+                for => 'update'
+            }
+        )->first;
+        is($artist->artistid, 1, "select for update returns artistid = 1");
+
+        my $artist_from_schema2;
+        my $error_ok = 0;
+        eval {
+            my $h = set_sig_handler( 'ALRM', sub { die "DBICTestTimeout" } );
+            alarm(2);
+            $artist_from_schema2 = $schema2->resultset('Artist')->find(1);
+            $artist_from_schema2->name('fooey');
+            $artist_from_schema2->update;
+            alarm(0);
+        };
+        if (my $e = $@) {
+            $error_ok = $e =~ /DBICTestTimeout/;
+        }
+
+        # Make sure that an error was raised, and that the update failed
+        ok($error_ok, "update from second schema times out");
+        ok($artist_from_schema2->is_column_changed('name'), "'name' column is still dirty from second schema");
+    });
+}
+
+SKIP: {
+    skip "Sys::SigAction is not available", 3 unless $HaveSysSigAction;
+    # create a new schema
+    my $schema2 = DBICTest::Schema->connect($dsn, $user, $pass);
+    $schema2->source("Artist")->name("testschema.artist");
+
+    $schema->txn_do( sub {
+        my $artist = $schema->resultset('Artist')->search(
+            {
+                artistid => 1
+            },
+        )->first;
+        is($artist->artistid, 1, "select for update returns artistid = 1");
+
+        my $artist_from_schema2;
+        my $error_ok = 0;
+        eval {
+            my $h = set_sig_handler( 'ALRM', sub { die "DBICTestTimeout" } );
+            alarm(2);
+            $artist_from_schema2 = $schema2->resultset('Artist')->find(1);
+            $artist_from_schema2->name('fooey');
+            $artist_from_schema2->update;
+            alarm(0);
+        };
+        if (my $e = $@) {
+            $error_ok = $e =~ /DBICTestTimeout/;
+        }
+
+        # Make sure that an error was NOT raised, and that the update succeeded
+        ok(! $error_ok, "update from second schema DOES NOT timeout");
+        ok(! $artist_from_schema2->is_column_changed('name'), "'name' column is NOT dirty from second schema");
+    });
+}
+
 END {
     if($dbh) {
         $dbh->do("DROP TABLE testschema.artist;");