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"
2024-10-05 00:45:55 +02:00
"time"
2022-12-07 23:21:36 +01:00
)
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 {
2024-10-05 00:58:15 +02:00
constructorContext context . Context
tx * sqlx . Tx
id uint16
status TxStatus
execCtr int
queryCtr int
db * database
2022-12-07 23:21:36 +01:00
}
2024-10-05 00:58:15 +02:00
func newTransaction ( ctx context . Context , xtx * sqlx . Tx , txid uint16 , db * database ) Tx {
2022-12-07 23:21:36 +01:00
return & transaction {
2024-10-05 00:58:15 +02:00
constructorContext : ctx ,
tx : xtx ,
id : txid ,
status : TxStatusInitial ,
execCtr : 0 ,
queryCtr : 0 ,
db : db ,
2022-12-07 23:21:36 +01:00
}
}
func ( tx * transaction ) Rollback ( ) error {
2024-10-05 00:45:55 +02:00
t0 := time . Now ( )
2024-10-05 00:58:15 +02:00
preMeta := PreTxRollbackMeta { ConstructorContext : tx . constructorContext }
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2024-10-05 00:45:55 +02:00
err := v . PreTxRollback ( tx . id , preMeta )
2022-12-21 15:34:59 +01:00
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
}
2024-10-05 00:45:55 +02:00
t1 := time . Now ( )
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
}
2024-10-05 00:58:15 +02:00
postMeta := PostTxRollbackMeta { ConstructorContext : tx . constructorContext , Init : t0 , Start : t1 , End : time . Now ( ) , ExecCounter : tx . execCtr , QueryCounter : tx . queryCtr }
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2024-10-05 00:45:55 +02:00
v . PostTxRollback ( tx . id , result , postMeta )
2022-12-21 15:34:59 +01:00
}
return result
2022-12-07 23:21:36 +01:00
}
func ( tx * transaction ) Commit ( ) error {
2024-10-05 00:45:55 +02:00
t0 := time . Now ( )
2024-10-05 00:58:15 +02:00
preMeta := PreTxCommitMeta { ConstructorContext : tx . constructorContext }
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2024-10-05 00:45:55 +02:00
err := v . PreTxCommit ( tx . id , preMeta )
2022-12-21 15:34:59 +01:00
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
}
}
2024-10-05 00:45:55 +02:00
t1 := time . Now ( )
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
}
2024-10-05 00:58:15 +02:00
postMeta := PostTxCommitMeta { ConstructorContext : tx . constructorContext , Init : t0 , Start : t1 , End : time . Now ( ) , ExecCounter : tx . execCtr , QueryCounter : tx . queryCtr }
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2024-10-05 00:45:55 +02:00
v . PostTxCommit ( tx . id , result , postMeta )
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
2024-10-05 00:45:55 +02:00
t0 := time . Now ( )
preMeta := PreExecMeta { }
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2024-10-05 00:45:55 +02:00
err := v . PreExec ( ctx , langext . Ptr ( tx . id ) , & sqlstr , & prep , preMeta )
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
}
}
2024-10-05 00:45:55 +02:00
t1 := time . Now ( )
2022-12-21 15:34:59 +01:00
res , err := tx . tx . NamedExecContext ( ctx , sqlstr , prep )
2024-10-05 00:45:55 +02:00
tx . execCtr ++
2022-12-21 15:34:59 +01:00
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
}
2024-10-05 00:45:55 +02:00
postMeta := PostExecMeta { Init : t0 , Start : t1 , End : time . Now ( ) }
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2024-10-05 00:45:55 +02:00
v . PostExec ( langext . Ptr ( tx . id ) , origsql , sqlstr , prep , postMeta )
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
2024-10-05 00:45:55 +02:00
t0 := time . Now ( )
preMeta := PreQueryMeta { }
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2024-10-05 00:45:55 +02:00
err := v . PreQuery ( ctx , langext . Ptr ( tx . id ) , & sqlstr , & prep , preMeta )
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
}
}
2024-10-05 00:45:55 +02:00
t1 := time . Now ( )
2022-12-21 15:34:59 +01:00
rows , err := sqlx . NamedQueryContext ( ctx , tx . tx , sqlstr , prep )
2024-10-05 00:45:55 +02:00
tx . queryCtr ++
2022-12-21 15:34:59 +01:00
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
}
2024-10-05 00:45:55 +02:00
postMeta := PostQueryMeta { Init : t0 , Start : t1 , End : time . Now ( ) }
2023-12-29 19:25:36 +01:00
for _ , v := range tx . db . lstr {
2024-10-05 00:45:55 +02:00
v . PostQuery ( langext . Ptr ( tx . id ) , origsql , sqlstr , prep , postMeta )
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
}