[Rt-commit] r4373 - in DBIx-SearchBuilder/trunk: SearchBuilder
ruz at bestpractical.com
ruz at bestpractical.com
Wed Jan 11 13:20:54 EST 2006
Author: ruz
Date: Wed Jan 11 13:20:52 2006
New Revision: 4373
Added:
DBIx-SearchBuilder/trunk/t/03transactions.t
Modified:
DBIx-SearchBuilder/trunk/SearchBuilder/Handle.pm
Log:
* EndTransaction method
* calculate nested transactions depth per dbh
* basic transactions tests
* update documentation
Modified: DBIx-SearchBuilder/trunk/SearchBuilder/Handle.pm
==============================================================================
--- DBIx-SearchBuilder/trunk/SearchBuilder/Handle.pm (original)
+++ DBIx-SearchBuilder/trunk/SearchBuilder/Handle.pm Wed Jan 11 13:20:52 2006
@@ -1,19 +1,19 @@
# $Header: /home/jesse/DBIx-SearchBuilder/history/SearchBuilder/Handle.pm,v 1.21 2002/01/28 06:11:37 jesse Exp $
package DBIx::SearchBuilder::Handle;
+
use strict;
+use warnings;
+
use Carp qw(croak cluck);
use DBI;
use Class::ReturnValue;
use Encode;
-use vars qw($VERSION @ISA %DBIHandle $PrevHandle $DEBUG $TRANSDEPTH);
-
-$TRANSDEPTH = 0;
+use vars qw($VERSION @ISA %DBIHandle $PrevHandle $DEBUG %TRANSDEPTH);
$VERSION = '$Version$';
-
=head1 NAME
DBIx::SearchBuilder::Handle - Perl extension which is a generic DBI handle
@@ -311,12 +311,11 @@
=cut
sub Disconnect {
- my $self = shift;
- if ($self->dbh) {
- return ($self->dbh->disconnect());
- } else {
- return;
- }
+ my $self = shift;
+ my $dbh = $self->dbh;
+ return unless $dbh;
+ $self->Rollback(1);
+ return $dbh->disconnect;
}
@@ -644,88 +643,118 @@
return ($field, $operator, $value,undef);
}
+=head2 Transactions
+L<DBIx::SearchBuilder::Handle> emulates nested transactions,
+by keeping a transaction stack depth.
+B<NOTE:> In nested transactions you shouldn't mix rollbacks and commits,
+because only last action really do commit/rollback. For example next code
+would produce desired results:
+
+ $handle->BeginTransaction;
+ $handle->BeginTransaction;
+ ...
+ $handle->Rollback;
+ $handle->BeginTransaction;
+ ...
+ $handle->Commit;
+ $handle->Commit;
-=head2 BeginTransaction
+Only last action(Commit in example) finilize transaction in DB.
-Tells DBIx::SearchBuilder to begin a new SQL transaction. This will
-temporarily suspend Autocommit mode.
+=head3 BeginTransaction
-Emulates nested transactions, by keeping a transaction stack depth.
+Tells DBIx::SearchBuilder to begin a new SQL transaction.
+This will temporarily suspend Autocommit mode.
=cut
sub BeginTransaction {
my $self = shift;
- $TRANSDEPTH++;
- if ($TRANSDEPTH > 1 ) {
- return ($TRANSDEPTH);
- } else {
- return($self->dbh->begin_work);
- }
+
+ my $depth = $self->TransactionDepth;
+ return unless defined $depth;
+
+ $self->TransactionDepth(++$depth);
+ return 1 if $depth > 1;
+
+ return $self->dbh->begin_work;
}
+=head3 EndTransaction [Action => 'commit'] [Force => 0]
+Tells to end the current transaction. Takes C<Action> argument
+that could be C<commit> or C<rollback>, the default value
+is C<commit>.
-=head2 Commit
+If C<Force> argument is true then all nested transactions
+would be committed or rolled back.
-Tells DBIx::SearchBuilder to commit the current SQL transaction.
-This will turn Autocommit mode back on.
+If there is no transaction in progress then method throw
+warning unless action is forced.
+
+Method returns true on success or false if error occured.
=cut
-sub Commit {
+sub EndTransaction {
my $self = shift;
- unless ($TRANSDEPTH) {Carp::confess("Attempted to commit a transaction with none in progress")};
- $TRANSDEPTH--;
+ my %args = ( Action => 'commit', Force => 0, @_ );
+ my $action = lc $args{'Action'} eq 'commit'? 'commit': 'rollback';
- if ($TRANSDEPTH == 0 ) {
- return($self->dbh->commit);
- } else { #we're inside a transaction
- return($TRANSDEPTH);
+ my $depth = $self->TransactionDepth || 0;
+ unless ( $depth ) {
+ unless( $args{'Force'} ) {
+ Carp::cluck( "Attempted to $action a transaction with none in progress" );
+ return 0;
+ }
+ return 1;
+ } else {
+ $depth--;
}
-}
-
+ $depth = 0 if $args{'Force'};
+ $self->TransactionDepth( $depth );
+ return 1 if $depth;
+ return $self->dbh->rollback unless $action eq 'commit';
+ return $self->dbh->commit;
+}
-=head2 Rollback [FORCE]
+=head3 Commit [FORCE]
-Tells DBIx::SearchBuilder to abort the current SQL transaction.
-This will turn Autocommit mode back on.
+Tells to commit the current SQL transaction.
-If this method is passed a true argument, stack depth is blown away and the outermost transaction is rolled back
+Method uses C<EndTransaction> method, read its
+L<description|DBIx::SearchBuilder::Handle/EndTransaction>.
=cut
-sub Rollback {
+sub Commit {
my $self = shift;
- my $force = shift;
+ $self->EndTransaction( Action => 'commit', Force => shift );
+}
- my $dbh = $self->dbh;
- unless( $dbh ) {
- $TRANSDEPTH = 0;
- return;
- }
- #unless ($TRANSDEPTH) {Carp::confess("Attempted to rollback a transaction with none in progress")};
- if ($force) {
- $TRANSDEPTH = 0;
- return($dbh->rollback);
- }
+=head3 Rollback [FORCE]
- $TRANSDEPTH-- if ($TRANSDEPTH >= 1);
- if ($TRANSDEPTH == 0 ) {
- return($dbh->rollback);
- } else { #we're inside a transaction
- return($TRANSDEPTH);
- }
+Tells to abort the current SQL transaction.
+
+Method uses C<EndTransaction> method, read its
+L<description|DBIx::SearchBuilder::Handle/EndTransaction>.
+
+=cut
+
+sub Rollback {
+ my $self = shift;
+ $self->EndTransaction( Action => 'rollback', Force => shift );
}
-=head2 ForceRollback
+=head3 ForceRollback
-Force the handle to rollback. Whether or not we're deep in nested transactions
+Force the handle to rollback.
+Whether or not we're deep in nested transactions.
=cut
@@ -735,24 +764,35 @@
}
-=head2 TransactionDepth
+=head3 TransactionDepth
-Return the current depth of the faked nested transaction stack.
+Returns the current depth of the nested transaction stack.
+Returns C<undef> if there is no connection to database.
=cut
sub TransactionDepth {
my $self = shift;
- return ($TRANSDEPTH);
-}
+ my $dbh = $self->dbh;
+ return undef unless $dbh && $dbh->ping;
+
+ if ( @_ ) {
+ my $depth = shift;
+ if ( $depth ) {
+ $TRANSDEPTH{ $dbh } = $depth;
+ } else {
+ delete $TRANSDEPTH{ $dbh };
+ }
+ }
+ return $TRANSDEPTH{ $dbh } || 0;
+}
=head2 ApplyLimits STATEMENTREF ROWS_PER_PAGE FIRST_ROW
takes an SQL SELECT statement and massages it to return ROWS_PER_PAGE starting with FIRST_ROW;
-
=cut
sub ApplyLimits {
Added: DBIx-SearchBuilder/trunk/t/03transactions.t
==============================================================================
--- (empty file)
+++ DBIx-SearchBuilder/trunk/t/03transactions.t Wed Jan 11 13:20:52 2006
@@ -0,0 +1,177 @@
+#!/usr/bin/perl -w
+
+
+use strict;
+use warnings;
+use File::Spec;
+use Test::More;
+BEGIN { require "t/utils.pl" }
+our (@AvailableDrivers);
+
+use constant TESTS_PER_DRIVER => 42;
+
+my $total = scalar(@AvailableDrivers) * TESTS_PER_DRIVER;
+plan tests => $total;
+
+foreach my $d ( @AvailableDrivers ) {
+SKIP: {
+ unless( has_schema( 'TestApp::Address', $d ) ) {
+ skip "No schema for '$d' driver", TESTS_PER_DRIVER;
+ }
+ unless( should_test( $d ) ) {
+ skip "ENV is not defined for driver '$d'", TESTS_PER_DRIVER;
+ }
+
+ my $handle = get_handle( $d );
+ isa_ok($handle, 'DBIx::SearchBuilder::Handle');
+ { # clear PrevHandle
+ no warnings 'once';
+ $DBIx::SearchBuilder::Handle::PrevHandle = undef;
+ }
+
+diag("disconnected handle") if $ENV{'TEST_VERBOSE'};
+ is($handle->TransactionDepth, undef, "undefined transaction depth");
+ is($handle->BeginTransaction, undef, "couldn't begin transaction");
+ is($handle->TransactionDepth, undef, "still undefined transaction depth");
+ ok($handle->EndTransaction(Action => 'commit', Force => 1), "force commit success silently");
+ ok($handle->Commit('force'), "force commit success silently");
+ ok($handle->EndTransaction(Action => 'rollback', Force => 1), "force rollback success silently");
+ ok($handle->Rollback('force'), "force rollback success silently");
+ # XXX: ForceRollback function should deprecated
+ ok($handle->ForceRollback, "force rollback success silently");
+ {
+ my $warn = 0;
+ local $SIG{__WARN__} = sub{ $_[0] =~ /transaction with none in progress/? $warn++: warn @_ };
+ ok(!$handle->Rollback, "not forced rollback returns false");
+ is($warn, 1, "not forced rollback fires warning");
+ ok(!$handle->Commit, "not forced commit returns false");
+ is($warn, 2, "not forced commit fires warning");
+ }
+
+ connect_handle( $handle );
+ isa_ok($handle->dbh, 'DBI::db');
+
+diag("connected handle without transaction") if $ENV{'TEST_VERBOSE'};
+ is($handle->TransactionDepth, 0, "transaction depth is 0");
+ ok($handle->Commit('force'), "force commit success silently");
+ ok($handle->Rollback('force'), "force rollback success silently");
+ {
+ my $warn = 0;
+ local $SIG{__WARN__} = sub{ $_[0] =~ /transaction with none in progress/? $warn++: warn @_ };
+ ok(!$handle->Rollback, "not forced rollback returns false");
+ is($warn, 1, "not forced rollback fires warning");
+ ok(!$handle->Commit, "not forced commit returns false");
+ is($warn, 2, "not forced commit fires warning");
+ }
+
+diag("begin and commit empty transaction") if $ENV{'TEST_VERBOSE'};
+ ok($handle->BeginTransaction, "begin transaction");
+ is($handle->TransactionDepth, 1, "transaction depth is 1");
+ ok($handle->Commit, "commit successed");
+ is($handle->TransactionDepth, 0, "transaction depth is 0");
+
+diag("begin and rollback empty transaction") if $ENV{'TEST_VERBOSE'};
+ ok($handle->BeginTransaction, "begin transaction");
+ is($handle->TransactionDepth, 1, "transaction depth is 1");
+ ok($handle->Rollback, "rollback successed");
+ is($handle->TransactionDepth, 0, "transaction depth is 0");
+
+diag("nested empty transactions") if $ENV{'TEST_VERBOSE'};
+ ok($handle->BeginTransaction, "begin transaction");
+ is($handle->TransactionDepth, 1, "transaction depth is 1");
+ ok($handle->BeginTransaction, "begin nested transaction");
+ is($handle->TransactionDepth, 2, "transaction depth is 2");
+ ok($handle->Commit, "commit successed");
+ is($handle->TransactionDepth, 1, "transaction depth is 1");
+ ok($handle->Commit, "commit successed");
+ is($handle->TransactionDepth, 0, "transaction depth is 0");
+
+diag("init schema in transaction and commit") if $ENV{'TEST_VERBOSE'};
+ # MySQL doesn't support transactions for CREATE TABLE
+ # so it's fake transactions test
+ ok($handle->BeginTransaction, "begin transaction");
+ is($handle->TransactionDepth, 1, "transaction depth is 1");
+ my $ret = init_schema( 'TestApp::Address', $handle );
+ isa_ok($ret, 'DBI::st', "Inserted the schema. got a statement handle back");
+ ok($handle->Commit, "commit successed");
+ is($handle->TransactionDepth, 0, "transaction depth is 0");
+
+ cleanup_schema( 'TestApp::Address', $handle );
+}} # SKIP, foreach blocks
+
+1;
+
+
+
+package TestApp::Address;
+
+use base qw/DBIx::SearchBuilder::Record/;
+
+sub _Init {
+ my $self = shift;
+ my $handle = shift;
+ $self->Table('Address');
+ $self->_Handle($handle);
+}
+
+sub ValidateName
+{
+ my ($self, $value) = @_;
+ return 0 if $value =~ /invalid/i;
+ return 1;
+}
+
+sub _ClassAccessible {
+
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(14)', default => ''},
+ Phone =>
+ {read => 1, write => 1, type => 'varchar(18)', length => 18, default => ''},
+ EmployeeId =>
+ {read => 1, write => 1, type => 'int(8)', default => ''},
+
+}
+
+}
+
+sub schema_mysql {
+<<EOF;
+CREATE TEMPORARY TABLE Address (
+ id integer AUTO_INCREMENT,
+ Name varchar(36),
+ Phone varchar(18),
+ EmployeeId int(8),
+ PRIMARY KEY (id)) TYPE='InnoDB'
+EOF
+
+}
+
+sub schema_pg {
+<<EOF;
+CREATE TEMPORARY TABLE Address (
+ id serial PRIMARY KEY,
+ Name varchar,
+ Phone varchar,
+ EmployeeId integer
+)
+EOF
+
+}
+
+sub schema_sqlite {
+
+<<EOF;
+CREATE TABLE Address (
+ id integer primary key,
+ Name varchar(36),
+ Phone varchar(18),
+ EmployeeId int(8))
+EOF
+
+}
+
+1;
More information about the Rt-commit
mailing list