Your Question

how to control timeout for every single sql statement,like create,update,query,raw sql

The document you expected this should be explained

i wanna control timeout for every single sql statement,like create,update,query,raw sql

one way is to use context every statement like:

ctx,cancel := context.WithTimeout(ctx,time.Second)
db.WithContext(ctx).Take(xxx_A)
cancel()

ctx2,cancel2 := context.WithTimeout(ctx,time.Second)
db.WithContext(ctx2).Take(xxx_B)
cancel2()

too ugly!!! forget this one

or create a plugin:

db.Callback().Row().Before("gorm:row").Register("c:before_row", func(db *gorm.DB) { 
    ctx, cancelFn := context.WithTimeout(db.Statement.Context, time.Second)
    ctxInfo := &gormCtxInfo{
        Origin:   db.Statement.Context,
        cancelFn: cancelFn,
    }
    db.Statement.Context = context.WithValue(ctx, gormCtxInfoKey{}, ctxInfo)
})

db.Callback().Row().After("gorm:row").Register("c:after_row", func(db *gorm.DB) {
    ctxInfo := db.Statement.Context.Value(gormCtxInfoKey{}).(*gormCtxInfo)
    if ctxInfo != nil {
        ctxInfo.cancelFn()
    }
})

then exec raw sql

sqlStr := "select count(*) as num from user_v2"
err = gdb.Raw(sqlStr).Scan(&res).Error

// output context cancel error

but this will cause an error: context cancel

this is Scan source code

func (db *DB) Scan(dest interface{}) (tx *DB) {
    config := *db.Config
    currentLogger, newLogger := config.Logger, logger.Recorder.New()
    config.Logger = newLogger

    tx = db.getInstance()
    tx.Config = &config

    if rows, err := tx.Rows(); err == nil {
        if rows.Next() {
            tx.ScanRows(rows, dest)
        } else {
            tx.RowsAffected = 0
            tx.AddError(rows.Err())
        }
        tx.AddError(rows.Close())
    }

    currentLogger.Trace(tx.Statement.Context, newLogger.BeginAt, func() (string, int64) {
        return newLogger.SQL, tx.RowsAffected
    }, tx.Error)
    tx.Logger = currentLogger
    return
}

notice there are two steps to get result: tx.Rows() then call rows.Next(), but after tx.Rows called, ctx is cancelled by plugin. so the rows.Next() failed.

Rows ctx done related source code:

func (rs *Rows) awaitDone(ctx, txctx, closectx context.Context) {
    var txctxDone <-chan struct{}
    if txctx != nil {
        txctxDone = txctx.Done()
    }
    select {
    case <-ctx.Done():
        err := ctx.Err()
        rs.contextDone.Store(&err)
    case <-txctxDone:
        err := txctx.Err()
        rs.contextDone.Store(&err)
    case <-closectx.Done():
        // rs.cancel was called via Close(); don't store this into contextDone
        // to ensure Err() is unaffected.
    }
    rs.close(ctx.Err())
}

Expected answer

Is there a simple way to implement this scenario, which control every sql statement timeout?

Comment From: Tang-RoseChild

could Gorm provider callback for Scan ?

because Gorm' Scan func call Rows and Scans in one function, but only callbacks for Rows, if ctx cancelled in plugin, ScanRows also failed