<commit_before>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gdb

import (
	"context"
	"database/sql"
	"fmt"
	"reflect"

	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/util/gconv"

	"github.com/gogf/gf/text/gregex"
)

// TX is the struct for transaction management.
type TX struct {
	db               DB      // db is the current gdb database manager.
	tx               *sql.Tx // tx is the raw and underlying transaction manager.
	master           *sql.DB // master is the raw and underlying database manager.
	transactionCount int     // transactionCount marks the times that Begins.
}

const (
	transactionPointerPrefix = "transaction"
	contextTransactionKey    = "TransactionObject"
)

// WithTX injects given transaction object into context and returns a new context.
func WithTX(ctx context.Context, tx *TX) context.Context {
	return context.WithValue(ctx, contextTransactionKey, tx)
}

// TXFromCtx retrieves and returns transaction object from context.
// It is usually used in nested transaction feature, and it returns nil if it is not set previously.
func TXFromCtx(ctx context.Context) *TX {
	if ctx == nil {
		return nil
	}
	v := ctx.Value(contextTransactionKey)
	if v != nil {
		return v.(*TX)
	}
	return nil
}

// Commit commits current transaction.
// Note that it releases previous saved transaction point if it's in a nested transaction procedure,
// or else it commits the hole transaction.
func (tx *TX) Commit() error {
	if tx.transactionCount > 0 {
		tx.transactionCount--
		_, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKey())
		return err
	}
	var (
		sqlStr = "COMMIT"
		mTime1 = gtime.TimestampMilli()
		err    = tx.tx.Commit()
		mTime2 = gtime.TimestampMilli()
		sqlObj = &Sql{
			Sql:    sqlStr,
			Type:   "TX.Commit",
			Args:   nil,
			Format: sqlStr,
			Error:  err,
			Start:  mTime1,
			End:    mTime2,
			Group:  tx.db.GetGroup(),
		}
	)
	tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj)
	if tx.db.GetDebug() {
		tx.db.writeSqlToLogger(sqlObj)
	}
	return err
}

// Rollback aborts current transaction.
// Note that it aborts current transaction if it's in a nested transaction procedure,
// or else it aborts the hole transaction.
func (tx *TX) Rollback() error {
	if tx.transactionCount > 0 {
		tx.transactionCount--
		_, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKey())
		return err
	}
	var (
		sqlStr = "ROLLBACK"
		mTime1 = gtime.TimestampMilli()
		err    = tx.tx.Rollback()
		mTime2 = gtime.TimestampMilli()
		sqlObj = &Sql{
			Sql:    sqlStr,
			Type:   "TX.Rollback",
			Args:   nil,
			Format: sqlStr,
			Error:  err,
			Start:  mTime1,
			End:    mTime2,
			Group:  tx.db.GetGroup(),
		}
	)
	tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj)
	if tx.db.GetDebug() {
		tx.db.writeSqlToLogger(sqlObj)
	}
	return err
}

// Begin starts a nested transaction procedure.
func (tx *TX) Begin() error {
	_, err := tx.Exec("SAVEPOINT " + tx.transactionKey())
	if err != nil {
		return err
	}
	tx.transactionCount++
	return nil
}

// SavePoint performs `SAVEPOINT xxx` SQL statement that saves transaction at current point.
// The parameter `point` specifies the point name that will be saved to server.
func (tx *TX) SavePoint(point string) error {
	_, err := tx.Exec("SAVEPOINT " + tx.db.QuoteWord(point))
	return err
}

// RollbackTo performs `ROLLBACK TO SAVEPOINT xxx` SQL statement that rollbacks to specified saved transaction.
// The parameter `point` specifies the point name that was saved previously.
func (tx *TX) RollbackTo(point string) error {
	_, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.QuoteWord(point))
	return err
}

// transactionKey forms and returns the transaction key at current save point.
func (tx *TX) transactionKey() string {
	return tx.db.QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount))
}

// Transaction wraps the transaction logic using function `f`.
// It rollbacks the transaction and returns the error from function `f` if
// it returns non-nil error. It commits the transaction and returns nil if
// function `f` returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function `f`
// as it is automatically handled by this function.
func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) {
	err = tx.Begin()
	if err != nil {
		return err
	}<commit_msg>improve nested transaction feature for package gdb<commit_after>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gdb

import (
	"context"
	"database/sql"
	"fmt"
	"reflect"

	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/util/gconv"

	"github.com/gogf/gf/text/gregex"
)

// TX is the struct for transaction management.
type TX struct {
	db               DB      // db is the current gdb database manager.
	tx               *sql.Tx // tx is the raw and underlying transaction manager.
	master           *sql.DB // master is the raw and underlying database manager.
	transactionCount int     // transactionCount marks the times that Begins.
}

const (
	transactionPointerPrefix = "transaction"
	contextTransactionKey    = "TransactionObject"
)

// WithTX injects given transaction object into context and returns a new context.
func WithTX(ctx context.Context, tx *TX) context.Context {
	return context.WithValue(ctx, contextTransactionKey, tx)
}

// TXFromCtx retrieves and returns transaction object from context.
// It is usually used in nested transaction feature, and it returns nil if it is not set previously.
func TXFromCtx(ctx context.Context) *TX {
	if ctx == nil {
		return nil
	}
	v := ctx.Value(contextTransactionKey)
	if v != nil {
		return v.(*TX)
	}
	return nil
}

// Commit commits current transaction.
// Note that it releases previous saved transaction point if it's in a nested transaction procedure,
// or else it commits the hole transaction.
func (tx *TX) Commit() error {
	if tx.transactionCount > 0 {
		tx.transactionCount--
		_, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKey())
		return err
	}
	var (
		sqlStr = "COMMIT"
		mTime1 = gtime.TimestampMilli()
		err    = tx.tx.Commit()
		mTime2 = gtime.TimestampMilli()
		sqlObj = &Sql{
			Sql:    sqlStr,
			Type:   "TX.Commit",
			Args:   nil,
			Format: sqlStr,
			Error:  err,
			Start:  mTime1,
			End:    mTime2,
			Group:  tx.db.GetGroup(),
		}
	)
	tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj)
	if tx.db.GetDebug() {
		tx.db.writeSqlToLogger(sqlObj)
	}
	return err
}

// Rollback aborts current transaction.
// Note that it aborts current transaction if it's in a nested transaction procedure,
// or else it aborts the hole transaction.
func (tx *TX) Rollback() error {
	if tx.transactionCount > 0 {
		tx.transactionCount--
		_, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKey())
		return err
	}
	var (
		sqlStr = "ROLLBACK"
		mTime1 = gtime.TimestampMilli()
		err    = tx.tx.Rollback()
		mTime2 = gtime.TimestampMilli()
		sqlObj = &Sql{
			Sql:    sqlStr,
			Type:   "TX.Rollback",
			Args:   nil,
			Format: sqlStr,
			Error:  err,
			Start:  mTime1,
			End:    mTime2,
			Group:  tx.db.GetGroup(),
		}
	)
	tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj)
	if tx.db.GetDebug() {
		tx.db.writeSqlToLogger(sqlObj)
	}
	return err
}

// Begin starts a nested transaction procedure.
func (tx *TX) Begin() error {
	_, err := tx.Exec("SAVEPOINT " + tx.transactionKey())
	if err != nil {
		return err
	}
	tx.transactionCount++
	return nil
}

// SavePoint performs `SAVEPOINT xxx` SQL statement that saves transaction at current point.
// The parameter `point` specifies the point name that will be saved to server.
func (tx *TX) SavePoint(point string) error {
	_, err := tx.Exec("SAVEPOINT " + tx.db.QuoteWord(point))
	return err
}

// RollbackTo performs `ROLLBACK TO SAVEPOINT xxx` SQL statement that rollbacks to specified saved transaction.
// The parameter `point` specifies the point name that was saved previously.
func (tx *TX) RollbackTo(point string) error {
	_, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.QuoteWord(point))
	return err
}

// transactionKey forms and returns the transaction key at current save point.
func (tx *TX) transactionKey() string {
	return tx.db.QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount))
}

// Transaction wraps the transaction logic using function `f`.
// It rollbacks the transaction and returns the error from function `f` if
// it returns non-nil error. It commits the transaction and returns nil if
// function `f` returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function `f`
// as it is automatically handled by this function.
func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) {
	// Check transaction object from context.
	if TXFromCtx(ctx) == nil {
		// Inject transaction object into context.
		ctx = WithTX(ctx, tx)
	}
	err = tx.Begin()
	if err != nil {
		return err
	}
{'fraction_non_alphanumeric': 0.10822417865578698, 'fraction_numerical': 0.002147305132059266, 'mean_word_length': 3.365042174320525, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2466594', 'n_tokens_mistral': 3095, 'n_tokens_neox': 2946, 'n_words': 1036}