Allow recovery from failed transaction

rollback to savepoint can recover a failed transaction. Therefore we
shouldn't block any activities while the transaction is broken. Instead
we only have the Tx.Status() method return the information.

refs 
issue421
Jack Christensen 2018-05-12 19:53:53 -05:00
parent e096a14b3e
commit f114ec85a1
2 changed files with 19 additions and 19 deletions

24
tx.go
View File

@ -179,10 +179,7 @@ func (tx *Tx) Exec(sql string, arguments ...interface{}) (commandTag CommandTag,
// ExecEx delegates to the underlying *Conn
func (tx *Tx) ExecEx(ctx context.Context, sql string, options *QueryExOptions, arguments ...interface{}) (commandTag CommandTag, err error) {
if tx.Status() == TxStatusInFailure {
return CommandTag(""), ErrTxInFailure
}
if tx.Status() != TxStatusInProgress {
if tx.status != TxStatusInProgress {
return CommandTag(""), ErrTxClosed
}
@ -196,10 +193,7 @@ func (tx *Tx) Prepare(name, sql string) (*PreparedStatement, error) {
// PrepareEx delegates to the underlying *Conn
func (tx *Tx) PrepareEx(ctx context.Context, name, sql string, opts *PrepareExOptions) (*PreparedStatement, error) {
if tx.Status() == TxStatusInFailure {
return nil, ErrTxInFailure
}
if tx.Status() != TxStatusInProgress {
if tx.status != TxStatusInProgress {
return nil, ErrTxClosed
}
@ -213,12 +207,7 @@ func (tx *Tx) Query(sql string, args ...interface{}) (*Rows, error) {
// QueryEx delegates to the underlying *Conn
func (tx *Tx) QueryEx(ctx context.Context, sql string, options *QueryExOptions, args ...interface{}) (*Rows, error) {
if tx.Status() == TxStatusInFailure {
// Because checking for errors can be deferred to the *Rows, build one with the error
err := ErrTxInFailure
return &Rows{closed: true, err: err}, err
}
if tx.Status() != TxStatusInProgress {
if tx.status != TxStatusInProgress {
// Because checking for errors can be deferred to the *Rows, build one with the error
err := ErrTxClosed
return &Rows{closed: true, err: err}, err
@ -241,10 +230,7 @@ func (tx *Tx) QueryRowEx(ctx context.Context, sql string, options *QueryExOption
// CopyFrom delegates to the underlying *Conn
func (tx *Tx) CopyFrom(tableName Identifier, columnNames []string, rowSrc CopyFromSource) (int, error) {
if tx.Status() == TxStatusInFailure {
return 0, ErrTxInFailure
}
if tx.Status() != TxStatusInProgress {
if tx.status != TxStatusInProgress {
return 0, ErrTxClosed
}
@ -255,7 +241,7 @@ func (tx *Tx) CopyFrom(tableName Identifier, columnNames []string, rowSrc CopyFr
// pgx.TxStatus* constants.
func (tx *Tx) Status() int8 {
if tx.status == TxStatusInProgress && tx.conn.txStatus == 'E' {
tx.status = TxStatusInFailure
return TxStatusInFailure
}
return tx.status
}

View File

@ -369,6 +369,11 @@ func TestTxStatusErrorInTransactions(t *testing.T) {
t.Fatalf("Expected status to be %v, but it was %v", pgx.TxStatusInProgress, status)
}
_, err = tx.Exec("savepoint s")
if err != nil {
t.Fatal(err)
}
_, err = tx.Exec("syntax error")
if err == nil {
t.Fatal("expected an error but did not get one")
@ -378,6 +383,15 @@ func TestTxStatusErrorInTransactions(t *testing.T) {
t.Fatalf("Expected status to be %v, but it was %v", pgx.TxStatusInFailure, status)
}
_, err = tx.Exec("rollback to s")
if err != nil {
t.Fatal(err)
}
if status := tx.Status(); status != pgx.TxStatusInProgress {
t.Fatalf("Expected status to be %v, but it was %v", pgx.TxStatusInProgress, status)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}