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

Comment From: github-actions[bot]

This issue has been automatically marked as stale because it has been open 360 days with no activity. Remove stale label or comment or this will be closed in 180 days