Golang服务器:发送具有可变列数的SQL查询结果的JSON

huangapple go评论74阅读模式
英文:

Golang server: send JSON with SQL query result that has variable number of columns

问题

我正在使用Go服务器创建一个简单的RESTful API实现。

我正在从URL中提取查询参数(我知道这不安全,我稍后会尝试修复,但如果您对此有任何建议,即使是在这个主题上,它们也会很有帮助)。

我有表名、所需列和一些条件保存在3个字符串变量中。
我正在使用以下查询:

rows, _ := db.Query(fmt.Sprintf("SELECT %s FROM %s WHERE %s", columns, table, conditions))

我想将查询结果作为JSON发送回前端。由于我有未知数量的列,所以无法以“标准”方式完成。
我能想到的一个解决方案是从查询结果和rows.Columns()手动构建JSON字符串。

但我想以更复杂的方式使用可变接口之类的东西来做到这一点。问题是,即使尝试了很多次,我仍然不明白它是如何工作的。

我尝试使用以下代码:

cols, err := rows.Columns()             // 获取列名;记得检查错误
vals := make([]sql.RawBytes, len(cols)) // 分配足够的值
ints := make([]interface{}, len(cols))  // 创建一个[]interface{}切片
for i := range ints {
    vals[i] = &ints[i] // 将引用复制到切片中
}
for rows.Next() {
    err := rows.Scan(vals...)
    // 现在你可以检查vals的每个元素是否为nil,
    // 你可以使用类型内省和类型断言将列提取到一个有类型的变量中。
}

来自这个教程,但它不起作用,我得到了类似于

cannot use &ints[i] (type *interface {}) as type sql.RawBytes in assignment

的错误。

即使它能工作,我也不理解它。

有人对此有一个好的解决方案吗?一些解释也会很好。

非常感谢。

英文:

I'm creating a little implementation of RESTful API with Go server.

I'm extracting query parameters from URL (I know it's not safe, I'll try to fix this later on, but if you have any recommendations even on this topic, they would be helpful).

I have table name, desired columns and some conditions saved in 3 sring variables.
I'm using this query:

rows, _ := db.Query(fmt.Sprintf("SELECT %s FROM %s WHERE %s", columns, table, conditions))

I want to send the query result back to my frontend, as JSON. I have variable number of unknown columns, so I can't do it "standard" way.
One solution I can think of is to build a JSON string "manually" from from query result and rows.Columns().

But I'd like to do this in a more sofisticated way using something like variadic interface and stuff like that. The problem is, even after trying a lot, I still dont understand how it works.

I tried using following code

cols, err := rows.Columns()             // Get the column names; remember to check err
vals := make([]sql.RawBytes, len(cols)) // Allocate enough values
ints := make([]interface{}, len(cols))  // Make a slice of []interface{}
for i := range ints {
    vals[i] = &ints[i] // Copy references into the slice
}
for rows.Next() {
    err := rows.Scan(vals...)
    // Now you can check each element of vals for nil-ness,
    // and you can use type introspection and type assertions
    // to fetch the column into a typed variable.
}

from this tutorial but it doesn't work, I'm getting errors like

cannot use &ints[i] (type *interface {}) as type sql.RawBytes in assignment

And even if it'd work, I dont understand it.

Does anyone have a good solution for this? Some explanation would be great aswell.

Thanks a lot.

答案1

得分: 4

第一个问题在这里:

    vals[i] = &ints[i] // 将引用复制到切片中
}```

这里是你将本来应该是 RawBytes 的值设置为指向接口的指针。

在我解释这是做什么之前,我先看看能否解释一下这里的一般思路。

通常在 Go 中从 SQL 获取响应时,你会有一个包含每个列和类型(id int,name string,...)的切片,这样你就可以将每个 SQL 记录读入到这个切片中,每个列都将映射到相同类型的值。

对于像你这样的情况,响应从 SQL 中获取的多样性更大,需要 Go 来处理,你可以这样做:

```for i := range ints {
    ints[i] = &vals[i] // 将引用复制到切片中
}```

这段代码的意思是,你的每个 `interface` 值都持有一个指向将保存 SQL 响应的 `vals` 数组的指针。(在我的示例中,我使用的是 [][]byte 而不是 RawBytes,所以 `vals` 中的值将是从 SQL 中获取的字节切片。)

然后你可以这样做:

```    err := rows.Scan(ints...) ```

由于 `interface` 可以评估为任何类型,当 `ints` 数组被填充时,它将接受任何值,然后根据指针从 SQL 中的值更新 `vals` 数组中的位置,类型为 RawBytes。

希望对你有帮助。

<details>
<summary>英文:</summary>

The first problem is here:

```for i := range ints {
    vals[i] = &amp;ints[i] // Copy references into the slice
}```

This is you setting values that are meant to be RawBytes as pointers to interfaces.

Before I explain what that&#39;s meant to be doing I&#39;ll see if I can explain what the general idea is here.

So normally when getting the response from SQL in Go you&#39;d have a slice with each column and type (id int, name string, ...) so you can then read each SQL record into this slice and each column will be mapped to the value of the same type.

For cases like yours where you will have more variety in the response from SQL and need Go to handle it, you&#39;d do this:

```for i := range ints {
    ints[i] = &amp;vals[i] // Copy references into the slice
}``` 

What this is saying is each of your `interface` values holds a pointer to the `vals` array that will hold the response from SQL. (In my examples I use [][]byte instead of RawBytes so value in  `vals` would be a slice of byte values from SQL.)

You&#39;d then do:

```    err := rows.Scan(ints...) ```

Since `interface` can evaluate to any type, when the `ints` array becomes populated  it will accept any value then update the position in vals array based on the pointer with the value from SQL as a RawBytes type.

HTH

</details>



huangapple
  • 本文由 发表于 2016年2月24日 22:41:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/35605149.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定