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