sql - 在 Go 中泛化 *sql.Rows 扫描

标签 sql go reflection refactoring generalization

我正在使用 Go 开发一个 Web API,并且有很多冗余的数据库查询扫描代码。

func (m *ContractModel) WorkQuestions(cid int) ([]models.WorkQuestion, error) {
    results, err := m.DB.Query(queries.WORK_QUESTIONS, cid)
    if err != nil {
        return nil, err
    }

    var workQuestions []models.WorkQuestion
    for results.Next() {
        var wq models.WorkQuestion
        err = results.Scan(&wq.ContractStateID, &wq.QuestionID, &wq.Question, &wq.ID, &wq.Answer, &wq.Compulsory)
        if err != nil {
            return nil, err
        }
        workQuestions = append(workQuestions, wq)
    }

    return workQuestions, nil
}

func (m *ContractModel) Questions(cid int) ([]models.Question, error) {
    results, err := m.DB.Query(queries.QUESTIONS, cid)
    if err != nil {
        return nil, err
    }

    var questions []models.Question
    for results.Next() {
        var q models.Question
        err = results.Scan(&q.Question, &q.Answer)
        if err != nil {
            return nil, err
        }
        questions = append(questions, q)
    }

    return questions, nil
}

func (m *ContractModel) Documents(cid int) ([]models.Document, error) {
    results, err := m.DB.Query(queries.DOCUMENTS, cid)
    if err != nil {
        return nil, err
    }

    var documents []models.Document
    for results.Next() {
        var d models.Document
        err = results.Scan(&d.Document, &d.S3Region, &d.S3Bucket, &d.Source)
        if err != nil {
            return nil, err
        }
        documents = append(documents, d)
    }

    return documents, nil
}

我需要概括这段代码,以便我可以将结果 *sql.Rows 传递给函数并获得包含扫描行的结构 slice 。我知道 sqlx 包中有一个 StructScan 方法,但这不能使用,因为我有大量使用 go 标准 database/sql< 编写的代码/strong> 包。

使用 reflect 包,我可以创建一个通用的 StructScan 函数,但 reflect 包无法从传递的 interface{} 类型创建结构片段。我需要实现的是如下所示

func RowsToStructs(rows *sql.Rows, model interface{}) ([]interface{}, error) {
    // 1. Create a slice of structs from the passed struct type of model
    // 2. Loop through each row,
    // 3. Create a struct of passed mode interface{} type
    // 4. Scan the row results to a slice of interface{}
    // 5. Set the field values of struct created in step 3 using the slice in step 4
    // 6. Add the struct created in step 3 to slice created in step 1
    // 7. Return the struct slice
}

我似乎无法找到一种方法来扫描作为模型参数传递的结构并使用反射包创建它的一部分。是否有任何解决方法,或者我是否以错误的方式看待这个问题?

结构字段从结果中返回正确数量的列并以正确的顺序排列

最佳答案

通过将指向目标 slice 的指针作为参数传递,可以避免在调用函数中使用类型断言。这是经过修改的 RowsToStructs:

// RowsToStructs scans rows to the slice pointed to by dest.
// The slice elements must be pointers to structs with exported
// fields corresponding to the the columns in the result set.
//
// The function panics if dest is not as described above.
func RowsToStructs(rows *sql.Rows, dest interface{}) error {

    // 1. Create a slice of structs from the passed struct type of model
    //
    // Not needed, the caller passes pointer to destination slice.
    // Elem() dereferences the pointer.
    //
    // If you do need to create the slice in this function
    // instead of using the argument, then use
    // destv := reflect.MakeSlice(reflect.TypeOf(model).

    destv := reflect.ValueOf(dest).Elem()

    // Allocate argument slice once before the loop.

    args := make([]interface{}, destv.Type().Elem().NumField())

    // 2. Loop through each row

    for rows.Next() {

        // 3. Create a struct of passed mode interface{} type
        rowp := reflect.New(destv.Type().Elem())
        rowv := rowp.Elem()

        // 4. Scan the row results to a slice of interface{}
        // 5. Set the field values of struct created in step 3 using the slice in step 4
        //
        // Scan directly to the struct fields so the database
        // package handles the conversion from database
        // types to a Go types.
        //
        // The slice args is filled with pointers to struct fields.

        for i := 0; i < rowv.NumField(); i++ {
            args[i] = rowv.Field(i).Addr().Interface()
        }

        if err := rows.Scan(args...); err != nil {
            return err
        }

        // 6. Add the struct created in step 3 to slice created in step 1

        destv.Set(reflect.Append(destv, rowv))

    }
    return nil
}

这样调用它:

func (m *ContractModel) Documents(cid int) ([]*models.Document, error) {
    results, err := m.DB.Query(queries.DOCUMENTS, cid)
    if err != nil {
        return nil, err
    }
    defer results.Close()
    var documents []*models.Document
    err := RowsToStruct(results, &documents)
    return documents, err
}

通过将查询移至辅助函数来消除更多样板文件:

func QueryToStructs(dest interface{}, db *sql.DB, q string, args ...interface{}) error {
    rows, err := db.Query(q, args...)
    if err != nil {
        return err
    }
    defer rows.Close()
    return RowsToStructs(rows, dest)
}

这样调用它:

func (m *ContractModel) Documents(cid int) ([]*models.Document, error) {
    var documents []*model.Document
    err := QueryToStructs(&documents, m.DB, queries.DOCUMENTS, cid)
    return documents, err
}

关于sql - 在 Go 中泛化 *sql.Rows 扫描,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62240553/

相关文章:

sql - 在 postgresql 中无法检查表是否存在

c# - 缩短这个反射属性名称辅助方法?

go - 执行大量 I/O 的 go 程序崩溃

go - 我可以列出所有标准的 Go 包吗?

go - 测试函数以获得 100% 的覆盖率

c++ - 如何传递指向构造函数的函数指针?

c# - 在 C# 中访问非公共(public)基础属性

sql - 如何在 Access 中的查询中使用 LIMIT,但不使用 TOP

mysql - sql/mysql 过滤器只包括最大值

php - 如果在另一行中指定,则MySQL选择行