Golang sql行。用于所有通用类型的所有字段的scan函数

发布于 2025-01-22 09:07:11 字数 918 浏览 0 评论 0原文

我想从 sql 软件包中使用scan()函数来执行一个可能(或不)返回多行的选择语句,并在我的函数中返回这些结果。

我是Golang Generics 的新手,并且对如何实现这一目标感到困惑。 通常,我们会在scan函数上使用*SQL.Rows,并提供对我们的所有预期“结果类型”字段的引用,我们想将行读入,例如:

var alb Album
rows.Scan(&alb.ID, &alb.Title, &alb.Artist,
            &alb.Price, &alb.Quantity)

专辑是一个结构类型,其中显示了这五个字段。

现在,为了不为我拥有的每个SQL表编写类似的函数n次,我想改用 generic type r 。 r是通用接口类型结果,我将此类型定义为n个不同的结构之一:

type Result interface {
    StructA | StructB | StructC
}

func ExecSelect[R Result](conn *sql.DB, cmd Command, template R) []R

我现在如何编写lows.scan(...)在所有字段上应用扫描操作我对R的混凝土类型的结构?例如,我想拥有lows.scan(& res.field1,& res.field2,...) res属于R类型R,并且扫描应接收我当前混凝土的所有字段类型R。我实际上需要提供一个“模板”作为R的混凝土类型的参数,以便在运行时清楚哪个结构现在相关?

请在考虑仿制药的任何错误上纠正我。

I want to use the Scan() function from the sql package for executing a select statement that might (or not) return multiple rows, and return these results in my function.

I´m new to Golang generics, and am confused about how to achieve this.
Usually, we would use the Scan function on a *sql.Rows and provide the references to all fields of our expected 'result type' we want to read the rows into, e.g.:

var alb Album
rows.Scan(&alb.ID, &alb.Title, &alb.Artist,
            &alb.Price, &alb.Quantity)

where Album is a struct type with those five fields shown.

Now, for the purpose of not writing a similar function N times for every SQL table I have, I want to use a generic type R instead. R is of generic interface type Result, and I will define this type as one of N different structs:

type Result interface {
    StructA | StructB | StructC
}

func ExecSelect[R Result](conn *sql.DB, cmd Command, template R) []R

How can I now write rows.Scan(...) to apply the Scan operation on all fields of my struct of R´s concrete type? e.g. I would want to have rows.Scan(&res.Field1, &res.Field2, ...) where res is of type R, and Scan should receive all fields of my current concrete type R. And do I actually need to provide a 'template' as argument of R´s concrete type, so that at runtime it becomes clear which struct is now relevant?

Please correct me on any mistake I´m making considering the generics.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

_蜘蛛 2025-01-29 09:07:11

这是仿制药的糟糕用例。

函数的参数 sql.rows.sql.rows.scan 应该是扫描目的地,即您的结构字段,结果集中的每一列之一,在通用功能主体中您 > r 类型参数。

即使您这样做了,结果中的结构约束可能具有不同的字段...?那么,您如何设想与不同的字段一起使用的通用代码?

您可以使用提供任意结构扫描的软件包来完成想要的工作.structscan“ rel =” nofollow noreferrer“> structscan ,但这使用引擎盖下的反射将struct字段映射到sql.rows.scan.scan.scan.scan.scan参数,参数,参数,参数,参数,参数>因此,您对仿制药根本没有任何好处。

如果有的话,您会使情况变得更糟,因为现在您拥有使用类型参数的其他性能开销。

This is a poor use case for generics.

The arguments to the function sql.Rows.Scan are supposed to be the scan destinations, i.e. your struct fields, one for each column in the result set, and within the generic function body you do not have access to the fields of R type parameter.

Even if you did, the structs in your Result constraint likely have different fields...? So how do you envision writing generic code that works with different fields?

You might accomplish what you want with a package that provides arbitrary struct scanning like sqlx with facilities like StructScan, but that uses reflection under the hood to map the struct fields into sql.Rows.Scan arguments, so you are not getting any benefit at all with generics.

If anything, you are making it worse, because now you have the additional performance overheads of using type parameters.

沉溺在你眼里的海 2025-01-29 09:07:11

另一个答案显示了如何创建

对于每种类型,您需要一个函数来创建指向新值和功能的指针,以尊重指针。让我们声明所需功能的接口:

type FieldTyper interface {
    // Return pointer to new value.
    New() (ptr any)
    // Dereference pointer.
    Deref(ptr any) (val any)
}

创建该接口的通用实现:

type FieldType[T any] struct{}

func (v FieldType[T]) New() any {
    return new(T)
}

func (v FieldType[T]) Deref(p any) any {
    return *p.(*T)
}

创建列名称为fieldTypers:

var fieldTypes = map[string]FieldTyper{
    "id":   FieldType[Type_int]{},
    "name": FieldType[Type_string]{},
}

使用字段打字器设置扫描ARGS并尊重这些args并添加到映射。

func SetupScanArgs(columnNames []string, fieldTypes map[string]FieldTyper) []any {
    args := make([]any, len(columnNames))
    for i, n := range columnNames {
        args[i] = fieldTypes[n].New()
    }
    return args
}

func ArgsToValueMap(columnNames []string, fieldTypes map[string]FieldTyper, args []any) map[string]any {
    result := make(map[string]any)
    for i, n := range columnNames {
        result[n] = fieldTypes[n].Deref(args[i])
    }
    return result
}

这样的扫描:

args := SetupScanArgs(columnNames, fieldTypes)
if err := rows.Scan(args...); err != nil {
    return err
}
m := ArgsToValueMap(columnNames, fieldTypes, args)

Another answer shows how to create a map of types as asked in the question. You don't actually need a map of types.

For each type, you need a function to create a pointer to a new value and a function to deference the pointer. Let's declare an interface for the required functionality:

type FieldTyper interface {
    // Return pointer to new value.
    New() (ptr any)
    // Dereference pointer.
    Deref(ptr any) (val any)
}

Create a generic implementation of that interface:

type FieldType[T any] struct{}

func (v FieldType[T]) New() any {
    return new(T)
}

func (v FieldType[T]) Deref(p any) any {
    return *p.(*T)
}

Create a map of column names to FieldTypers:

var fieldTypes = map[string]FieldTyper{
    "id":   FieldType[Type_int]{},
    "name": FieldType[Type_string]{},
}

Use the field typers to setup the scan args and deference those args and add to map.

func SetupScanArgs(columnNames []string, fieldTypes map[string]FieldTyper) []any {
    args := make([]any, len(columnNames))
    for i, n := range columnNames {
        args[i] = fieldTypes[n].New()
    }
    return args
}

func ArgsToValueMap(columnNames []string, fieldTypes map[string]FieldTyper, args []any) map[string]any {
    result := make(map[string]any)
    for i, n := range columnNames {
        result[n] = fieldTypes[n].Deref(args[i])
    }
    return result
}

Scan like this:

args := SetupScanArgs(columnNames, fieldTypes)
if err := rows.Scan(args...); err != nil {
    return err
}
m := ArgsToValueMap(columnNames, fieldTypes, args)
一袭水袖舞倾城 2025-01-29 09:07:11

使用反射软件包:

func scan(rows *sql.Rows, types map[string]reflect.Type) (map[string]any, error) {
    names, _ := rows.Columns()
    ptrs := make([]any, len(names))
    vals := make([]reflect.Value, len(names))
    for i, n := range names {
        vals[i] = reflect.New(types[n])
        ptrs[i] = vals[i].Interface()
    }
    if err := rows.Scan(ptrs...); err != nil {
        return nil, err
    }
    result := make(map[string]any)
    for i, v := range vals {
        result[names[i]] = v.Elem().Interface()
    }
    return result, nil
}

其中类型就是这样:

types = map[string]reflect.Type{
   "id": reflect.TypeOf(Type_int(0)),
   "name": reflect.TypeOf(Type_string("")),
}

Use the reflect package:

func scan(rows *sql.Rows, types map[string]reflect.Type) (map[string]any, error) {
    names, _ := rows.Columns()
    ptrs := make([]any, len(names))
    vals := make([]reflect.Value, len(names))
    for i, n := range names {
        vals[i] = reflect.New(types[n])
        ptrs[i] = vals[i].Interface()
    }
    if err := rows.Scan(ptrs...); err != nil {
        return nil, err
    }
    result := make(map[string]any)
    for i, v := range vals {
        result[names[i]] = v.Elem().Interface()
    }
    return result, nil
}

where types is something like this:

types = map[string]reflect.Type{
   "id": reflect.TypeOf(Type_int(0)),
   "name": reflect.TypeOf(Type_string("")),
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文