2022-12-07 23:21:36 +01:00
package sq
import (
"context"
"database/sql"
"github.com/jmoiron/sqlx"
2024-01-13 14:19:19 +01:00
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
2022-12-07 23:21:36 +01:00
"gogs.mikescher.com/BlackForestBytes/goext/langext"
)
2023-07-27 17:12:41 +02:00
type TxStatus string
const (
TxStatusInitial TxStatus = "INITIAL"
TxStatusActive TxStatus = "ACTIVE"
TxStatusComitted TxStatus = "COMMITTED"
TxStatusRollback TxStatus = "ROLLBACK"
)
2022-12-07 23:21:36 +01:00
type Tx interface {
2023-12-29 19:25:36 +01:00
Queryable
2022-12-07 23:21:36 +01:00
Rollback ( ) error
Commit ( ) error
2023-07-27 17:12:41 +02:00
Status ( ) TxStatus
2022-12-07 23:21:36 +01:00
}
type transaction struct {
2023-07-27 17:12:41 +02:00
tx * sqlx . Tx
id uint16
status TxStatus
execCtr int
queryCtr int
2023-12-29 19:25:36 +01:00
db * database
2022-12-07 23:21:36 +01:00
}
2023-12-29 19:25:36 +01:00
func NewTransaction ( xtx * sqlx . Tx , txid uint16 , db * database ) Tx {
2022-12-07 23:21:36 +01:00
return & transaction {
2023-07-27 17:12:41 +02:00
tx : xtx ,
id : txid ,
status : TxStatusInitial ,
execCtr : 0 ,
queryCtr : 0 ,
2023-12-29 19:25:36 +01:00
db : db ,
2022-12-07 23:21:36 +01:00
}
}
func ( tx * transaction ) Rollback ( ) error {
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2022-12-21 15:34:59 +01:00
err := v . PreTxRollback ( tx . id )
if err != nil {
2024-01-13 14:19:19 +01:00
return exerr . Wrap ( err , "failed to call SQL pre-rollback listener" ) . Int ( "tx.id" , int ( tx . id ) ) . Build ( )
2022-12-21 15:34:59 +01:00
}
2022-12-07 23:21:36 +01:00
}
2022-12-21 15:34:59 +01:00
result := tx . tx . Rollback ( )
2023-07-27 17:16:30 +02:00
if result == nil {
2023-07-27 17:12:41 +02:00
tx . status = TxStatusRollback
}
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2022-12-21 15:34:59 +01:00
v . PostTxRollback ( tx . id , result )
}
return result
2022-12-07 23:21:36 +01:00
}
func ( tx * transaction ) Commit ( ) error {
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2022-12-21 15:34:59 +01:00
err := v . PreTxCommit ( tx . id )
if err != nil {
2024-01-13 14:19:19 +01:00
return exerr . Wrap ( err , "failed to call SQL pre-commit listener" ) . Int ( "tx.id" , int ( tx . id ) ) . Build ( )
2022-12-21 15:34:59 +01:00
}
}
result := tx . tx . Commit ( )
2023-07-27 17:16:30 +02:00
if result == nil {
2023-07-27 17:12:41 +02:00
tx . status = TxStatusComitted
}
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2022-12-21 15:34:59 +01:00
v . PostTxRollback ( tx . id , result )
2022-12-07 23:21:36 +01:00
}
2022-12-21 15:34:59 +01:00
return result
2022-12-07 23:21:36 +01:00
}
2022-12-21 15:34:59 +01:00
func ( tx * transaction ) Exec ( ctx context . Context , sqlstr string , prep PP ) ( sql . Result , error ) {
origsql := sqlstr
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2022-12-21 15:41:41 +01:00
err := v . PreExec ( ctx , langext . Ptr ( tx . id ) , & sqlstr , & prep )
2022-12-21 15:34:59 +01:00
if err != nil {
2024-01-13 14:19:19 +01:00
return nil , exerr . Wrap ( err , "failed to call SQL pre-exec listener" ) . Int ( "tx.id" , int ( tx . id ) ) . Str ( "original_sql" , origsql ) . Str ( "sql" , sqlstr ) . Any ( "sql_params" , prep ) . Build ( )
2022-12-21 15:34:59 +01:00
}
}
res , err := tx . tx . NamedExecContext ( ctx , sqlstr , prep )
2023-07-27 17:16:30 +02:00
if tx . status == TxStatusInitial && err == nil {
2023-07-27 17:12:41 +02:00
tx . status = TxStatusActive
}
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2022-12-21 15:34:59 +01:00
v . PostExec ( langext . Ptr ( tx . id ) , origsql , sqlstr , prep )
2022-12-07 23:21:36 +01:00
}
if err != nil {
2024-01-13 14:19:19 +01:00
return nil , exerr . Wrap ( err , "Failed to [exec] sql statement" ) . Int ( "tx.id" , int ( tx . id ) ) . Str ( "original_sql" , origsql ) . Str ( "sql" , sqlstr ) . Any ( "sql_params" , prep ) . Build ( )
2022-12-07 23:21:36 +01:00
}
return res , nil
}
2022-12-21 15:34:59 +01:00
func ( tx * transaction ) Query ( ctx context . Context , sqlstr string , prep PP ) ( * sqlx . Rows , error ) {
origsql := sqlstr
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2022-12-21 15:41:41 +01:00
err := v . PreQuery ( ctx , langext . Ptr ( tx . id ) , & sqlstr , & prep )
2022-12-21 15:34:59 +01:00
if err != nil {
2024-01-13 14:19:19 +01:00
return nil , exerr . Wrap ( err , "failed to call SQL pre-query listener" ) . Int ( "tx.id" , int ( tx . id ) ) . Str ( "original_sql" , origsql ) . Str ( "sql" , sqlstr ) . Any ( "sql_params" , prep ) . Build ( )
2022-12-21 15:34:59 +01:00
}
}
rows , err := sqlx . NamedQueryContext ( ctx , tx . tx , sqlstr , prep )
2023-07-27 17:16:30 +02:00
if tx . status == TxStatusInitial && err == nil {
2023-07-27 17:12:41 +02:00
tx . status = TxStatusActive
}
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2022-12-21 15:34:59 +01:00
v . PostQuery ( langext . Ptr ( tx . id ) , origsql , sqlstr , prep )
2022-12-07 23:21:36 +01:00
}
if err != nil {
2024-01-13 14:19:19 +01:00
return nil , exerr . Wrap ( err , "Failed to [query] sql statement" ) . Int ( "tx.id" , int ( tx . id ) ) . Str ( "original_sql" , origsql ) . Str ( "sql" , sqlstr ) . Any ( "sql_params" , prep ) . Build ( )
2022-12-07 23:21:36 +01:00
}
return rows , nil
}
2023-07-27 17:12:41 +02:00
func ( tx * transaction ) Status ( ) TxStatus {
return tx . status
}
2023-12-29 19:25:36 +01:00
func ( tx * transaction ) ListConverter ( ) [ ] DBTypeConverter {
return tx . db . conv
}
2023-07-27 17:12:41 +02:00
func ( tx * transaction ) Traffic ( ) ( int , int ) {
return tx . execCtr , tx . queryCtr
}