package pgx

import (
	
	
	
	
	

	
)

// TxIsoLevel is the transaction isolation level (serializable, repeatable read, read committed or read uncommitted)
type TxIsoLevel string

// Transaction isolation levels
const (
	Serializable    TxIsoLevel = "serializable"
	RepeatableRead  TxIsoLevel = "repeatable read"
	ReadCommitted   TxIsoLevel = "read committed"
	ReadUncommitted TxIsoLevel = "read uncommitted"
)

// TxAccessMode is the transaction access mode (read write or read only)
type TxAccessMode string

// Transaction access modes
const (
	ReadWrite TxAccessMode = "read write"
	ReadOnly  TxAccessMode = "read only"
)

// TxDeferrableMode is the transaction deferrable mode (deferrable or not deferrable)
type TxDeferrableMode string

// Transaction deferrable modes
const (
	Deferrable    TxDeferrableMode = "deferrable"
	NotDeferrable TxDeferrableMode = "not deferrable"
)

// TxOptions are transaction modes within a transaction block
type TxOptions struct {
	IsoLevel       TxIsoLevel
	AccessMode     TxAccessMode
	DeferrableMode TxDeferrableMode

	// BeginQuery is the SQL query that will be executed to begin the transaction. This allows using non-standard syntax
	// such as BEGIN PRIORITY HIGH with CockroachDB. If set this will override the other settings.
	BeginQuery string
}

var emptyTxOptions TxOptions

func ( TxOptions) () string {
	if  == emptyTxOptions {
		return "begin"
	}

	if .BeginQuery != "" {
		return .BeginQuery
	}

	var  strings.Builder
	.Grow(64) // 64 - maximum length of string with available options
	.WriteString("begin")

	if .IsoLevel != "" {
		.WriteString(" isolation level ")
		.WriteString(string(.IsoLevel))
	}
	if .AccessMode != "" {
		.WriteByte(' ')
		.WriteString(string(.AccessMode))
	}
	if .DeferrableMode != "" {
		.WriteByte(' ')
		.WriteString(string(.DeferrableMode))
	}

	return .String()
}

var ErrTxClosed = errors.New("tx is closed")

// ErrTxCommitRollback occurs when an error has occurred in a transaction and
// Commit() is called. PostgreSQL accepts COMMIT on aborted transactions, but
// it is treated as ROLLBACK.
var ErrTxCommitRollback = errors.New("commit unexpectedly resulted in rollback")

// Begin starts a transaction. Unlike database/sql, the context only affects the begin command. i.e. there is no
// auto-rollback on context cancellation.
func ( *Conn) ( context.Context) (Tx, error) {
	return .BeginTx(, TxOptions{})
}

// BeginTx starts a transaction with txOptions determining the transaction mode. Unlike database/sql, the context only
// affects the begin command. i.e. there is no auto-rollback on context cancellation.
func ( *Conn) ( context.Context,  TxOptions) (Tx, error) {
	,  := .Exec(, .beginSQL())
	if  != nil {
		// begin should never fail unless there is an underlying connection issue or
		// a context timeout. In either case, the connection is possibly broken.
		.die(errors.New("failed to begin transaction"))
		return nil, 
	}

	return &dbTx{conn: }, nil
}

// Tx represents a database transaction.
//
// Tx is an interface instead of a struct to enable connection pools to be implemented without relying on internal pgx
// state, to support pseudo-nested transactions with savepoints, and to allow tests to mock transactions. However,
// adding a method to an interface is technically a breaking change. If new methods are added to Conn it may be
// desirable to add them to Tx as well. Because of this the Tx interface is partially excluded from semantic version
// requirements. Methods will not be removed or changed, but new methods may be added.
type Tx interface {
	// Begin starts a pseudo nested transaction.
	Begin(ctx context.Context) (Tx, error)

	// Commit commits the transaction if this is a real transaction or releases the savepoint if this is a pseudo nested
	// transaction. Commit will return an error where errors.Is(ErrTxClosed) is true if the Tx is already closed, but is
	// otherwise safe to call multiple times. If the commit fails with a rollback status (e.g. the transaction was already
	// in a broken state) then an error where errors.Is(ErrTxCommitRollback) is true will be returned.
	Commit(ctx context.Context) error

	// Rollback rolls back the transaction if this is a real transaction or rolls back to the savepoint if this is a
	// pseudo nested transaction. Rollback will return an error where errors.Is(ErrTxClosed) is true if the Tx is already
	// closed, but is otherwise safe to call multiple times. Hence, a defer tx.Rollback() is safe even if tx.Commit() will
	// be called first in a non-error condition. Any other failure of a real transaction will result in the connection
	// being closed.
	Rollback(ctx context.Context) error

	CopyFrom(ctx context.Context, tableName Identifier, columnNames []string, rowSrc CopyFromSource) (int64, error)
	SendBatch(ctx context.Context, b *Batch) BatchResults
	LargeObjects() LargeObjects

	Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error)

	Exec(ctx context.Context, sql string, arguments ...any) (commandTag pgconn.CommandTag, err error)
	Query(ctx context.Context, sql string, args ...any) (Rows, error)
	QueryRow(ctx context.Context, sql string, args ...any) Row

	// Conn returns the underlying *Conn that on which this transaction is executing.
	Conn() *Conn
}

// dbTx represents a database transaction.
//
// All dbTx methods return ErrTxClosed if Commit or Rollback has already been
// called on the dbTx.
type dbTx struct {
	conn         *Conn
	savepointNum int64
	closed       bool
}

// Begin starts a pseudo nested transaction implemented with a savepoint.
func ( *dbTx) ( context.Context) (Tx, error) {
	if .closed {
		return nil, ErrTxClosed
	}

	.savepointNum++
	,  := .conn.Exec(, "savepoint sp_"+strconv.FormatInt(.savepointNum, 10))
	if  != nil {
		return nil, 
	}

	return &dbSimulatedNestedTx{tx: , savepointNum: .savepointNum}, nil
}

// Commit commits the transaction.
func ( *dbTx) ( context.Context) error {
	if .closed {
		return ErrTxClosed
	}

	,  := .conn.Exec(, "commit")
	.closed = true
	if  != nil {
		if .conn.PgConn().TxStatus() != 'I' {
			_ = .conn.Close() // already have error to return
		}
		return 
	}
	if .String() == "ROLLBACK" {
		return ErrTxCommitRollback
	}

	return nil
}

// Rollback rolls back the transaction. Rollback will return ErrTxClosed if the
// Tx is already closed, but is otherwise safe to call multiple times. Hence, a
// defer tx.Rollback() is safe even if tx.Commit() will be called first in a
// non-error condition.
func ( *dbTx) ( context.Context) error {
	if .closed {
		return ErrTxClosed
	}

	,  := .conn.Exec(, "rollback")
	.closed = true
	if  != nil {
		// A rollback failure leaves the connection in an undefined state
		.conn.die(fmt.Errorf("rollback failed: %w", ))
		return 
	}

	return nil
}

// Exec delegates to the underlying *Conn
func ( *dbTx) ( context.Context,  string,  ...any) ( pgconn.CommandTag,  error) {
	if .closed {
		return pgconn.CommandTag{}, ErrTxClosed
	}

	return .conn.Exec(, , ...)
}

// Prepare delegates to the underlying *Conn
func ( *dbTx) ( context.Context, ,  string) (*pgconn.StatementDescription, error) {
	if .closed {
		return nil, ErrTxClosed
	}

	return .conn.Prepare(, , )
}

// Query delegates to the underlying *Conn
func ( *dbTx) ( context.Context,  string,  ...any) (Rows, error) {
	if .closed {
		// Because checking for errors can be deferred to the *Rows, build one with the error
		 := ErrTxClosed
		return &baseRows{closed: true, err: }, 
	}

	return .conn.Query(, , ...)
}

// QueryRow delegates to the underlying *Conn
func ( *dbTx) ( context.Context,  string,  ...any) Row {
	,  := .Query(, , ...)
	return (*connRow)(.(*baseRows))
}

// CopyFrom delegates to the underlying *Conn
func ( *dbTx) ( context.Context,  Identifier,  []string,  CopyFromSource) (int64, error) {
	if .closed {
		return 0, ErrTxClosed
	}

	return .conn.CopyFrom(, , , )
}

// SendBatch delegates to the underlying *Conn
func ( *dbTx) ( context.Context,  *Batch) BatchResults {
	if .closed {
		return &batchResults{err: ErrTxClosed}
	}

	return .conn.SendBatch(, )
}

// LargeObjects returns a LargeObjects instance for the transaction.
func ( *dbTx) () LargeObjects {
	return LargeObjects{tx: }
}

func ( *dbTx) () *Conn {
	return .conn
}

// dbSimulatedNestedTx represents a simulated nested transaction implemented by a savepoint.
type dbSimulatedNestedTx struct {
	tx           Tx
	savepointNum int64
	closed       bool
}

// Begin starts a pseudo nested transaction implemented with a savepoint.
func ( *dbSimulatedNestedTx) ( context.Context) (Tx, error) {
	if .closed {
		return nil, ErrTxClosed
	}

	return .tx.Begin()
}

// Commit releases the savepoint essentially committing the pseudo nested transaction.
func ( *dbSimulatedNestedTx) ( context.Context) error {
	if .closed {
		return ErrTxClosed
	}

	,  := .Exec(, "release savepoint sp_"+strconv.FormatInt(.savepointNum, 10))
	.closed = true
	return 
}

// Rollback rolls back to the savepoint essentially rolling back the pseudo nested transaction. Rollback will return
// ErrTxClosed if the dbSavepoint is already closed, but is otherwise safe to call multiple times. Hence, a defer sp.Rollback()
// is safe even if sp.Commit() will be called first in a non-error condition.
func ( *dbSimulatedNestedTx) ( context.Context) error {
	if .closed {
		return ErrTxClosed
	}

	,  := .Exec(, "rollback to savepoint sp_"+strconv.FormatInt(.savepointNum, 10))
	.closed = true
	return 
}

// Exec delegates to the underlying Tx
func ( *dbSimulatedNestedTx) ( context.Context,  string,  ...any) ( pgconn.CommandTag,  error) {
	if .closed {
		return pgconn.CommandTag{}, ErrTxClosed
	}

	return .tx.Exec(, , ...)
}

// Prepare delegates to the underlying Tx
func ( *dbSimulatedNestedTx) ( context.Context, ,  string) (*pgconn.StatementDescription, error) {
	if .closed {
		return nil, ErrTxClosed
	}

	return .tx.Prepare(, , )
}

// Query delegates to the underlying Tx
func ( *dbSimulatedNestedTx) ( context.Context,  string,  ...any) (Rows, error) {
	if .closed {
		// Because checking for errors can be deferred to the *Rows, build one with the error
		 := ErrTxClosed
		return &baseRows{closed: true, err: }, 
	}

	return .tx.Query(, , ...)
}

// QueryRow delegates to the underlying Tx
func ( *dbSimulatedNestedTx) ( context.Context,  string,  ...any) Row {
	,  := .Query(, , ...)
	return (*connRow)(.(*baseRows))
}

// CopyFrom delegates to the underlying *Conn
func ( *dbSimulatedNestedTx) ( context.Context,  Identifier,  []string,  CopyFromSource) (int64, error) {
	if .closed {
		return 0, ErrTxClosed
	}

	return .tx.CopyFrom(, , , )
}

// SendBatch delegates to the underlying *Conn
func ( *dbSimulatedNestedTx) ( context.Context,  *Batch) BatchResults {
	if .closed {
		return &batchResults{err: ErrTxClosed}
	}

	return .tx.SendBatch(, )
}

func ( *dbSimulatedNestedTx) () LargeObjects {
	return LargeObjects{tx: }
}

func ( *dbSimulatedNestedTx) () *Conn {
	return .tx.Conn()
}

// BeginFunc calls Begin on db and then calls fn. If fn does not return an error then it calls Commit on db. If fn
// returns an error it calls Rollback on db. The context will be used when executing the transaction control statements
// (BEGIN, ROLLBACK, and COMMIT) but does not otherwise affect the execution of fn.
func (
	 context.Context,
	 interface {
		( context.Context) (Tx, error)
	},
	 func(Tx) error,
) ( error) {
	var  Tx
	,  = .()
	if  != nil {
		return 
	}

	return beginFuncExec(, , )
}

// BeginTxFunc calls BeginTx on db and then calls fn. If fn does not return an error then it calls Commit on db. If fn
// returns an error it calls Rollback on db. The context will be used when executing the transaction control statements
// (BEGIN, ROLLBACK, and COMMIT) but does not otherwise affect the execution of fn.
func (
	 context.Context,
	 interface {
		( context.Context,  TxOptions) (Tx, error)
	},
	 TxOptions,
	 func(Tx) error,
) ( error) {
	var  Tx
	,  = .(, )
	if  != nil {
		return 
	}

	return beginFuncExec(, , )
}

func beginFuncExec( context.Context,  Tx,  func(Tx) error) ( error) {
	defer func() {
		 := .Rollback()
		if  != nil && !errors.Is(, ErrTxClosed) {
			 = 
		}
	}()

	 := ()
	if  != nil {
		_ = .Rollback() // ignore rollback error as there is already an error to return
		return 
	}

	return .Commit()
}