如何使用反射调用Scan可变参数函数

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

How to call the Scan variadic function using reflection

问题

我想使用反射调用Rows.Scan()函数。然而,它接受可变数量的指针,但是没有很多源代码示例。我需要使用反射,因为我计划使用Query调用的值填充一个切片。所以基本上使用rows.Columns()来获取行的长度,然后使用make()创建一个[]interface{}切片来填充通常使用传递给Scan()函数的指针填充的数据点。

基本上像这样的代码:

col := rows.Columns()
vals := make([]interface{}, len(cols))
rows.Scan(&vals)

有人有一个使用反射调用接受指针的可变函数的示例吗?

编辑:
以下是似乎不符合我的要求的示例代码。

package main

import (
	_ "github.com/lib/pq"
	"database/sql"
	"fmt"
)


func main() {

	db, _ := sql.Open(
		"postgres",
		"user=postgres dbname=Go_Testing password=ssap sslmode=disable")

	rows, _ := db.Query("SELECT * FROM _users;")

	cols, _ := rows.Columns()

	for rows.Next() {
		
		data := make([]interface{}, len(cols))

		rows.Scan(data...)

		fmt.Println(data)
	}

}

结果:

[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
英文:

I'm looking to call the Rows.Scan() function using reflection. However it takes a variable number of pointers, but there are not a lot of source examples. I need to use reflection because I plan on filling a slice with the values from a Query call. So basically using rows.Columns() to get the length of the row and then make() a slice of []interface{} to fill with the data points that would normally be filled using the pointers passed to the Scan() function.

Basically something like this code:

col := rows.Columns()
vals := make([]interface{}, len(cols))
rows.Scan(&amp;vals)

Anyone have an example of calling a variadic function that takes pointers using reflection that I can take a look at?

Edit:
Sample code that doesn't appear to do what I'm after.

package main

import (
	_ &quot;github.com/lib/pq&quot;
	&quot;database/sql&quot;
	&quot;fmt&quot;
)


func main() {

	db, _ := sql.Open(
		&quot;postgres&quot;,
		&quot;user=postgres dbname=Go_Testing password=ssap sslmode=disable&quot;)

	rows, _ := db.Query(&quot;SELECT * FROM _users;&quot;)

	cols, _ := rows.Columns()

	for rows.Next() {
		
		data := make([]interface{}, len(cols))

		rows.Scan(data...)

		fmt.Println(data)
	}

}

The results:

[&lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt;]
[&lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt;]
[&lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt;]
[&lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt;]
[&lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt;]
[&lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt; &lt;nil&gt;]

答案1

得分: 60

这是我得出的解决方案。它在遍历数据之前不获取类型,因此在通过Scan()提取值之前不知道每个值的类型,但重点是不需要事先知道类型。

关键是创建两个切片,一个用于存储值,另一个与值切片并行存储指针。然后,一旦使用指针填充数据,实际上会用数据填充值数组,然后可以用它来填充其他数据结构。

package main

import (
	"fmt"
	_ "github.com/lib/pq"
	"database/sql"
)

func main() {
	db, _ := sql.Open(
		"postgres",
		"user=postgres dbname=go_testing password=pass sslmode=disable")

	rows, _ := db.Query("SELECT * FROM _user;")

	columns, _ := rows.Columns()
	count := len(columns)
	values := make([]interface{}, count)
	valuePtrs := make([]interface{}, count)

	for rows.Next() {
		for i := range columns {
			valuePtrs[i] = &values[i]
		}

		rows.Scan(valuePtrs...)

		for i, col := range columns {
			val := values[i]

			b, ok := val.([]byte)
            var v interface{}
			if (ok) {
				v = string(b)
			} else {
				v = val
			}

			fmt.Println(col, v)
		}
	}
}
英文:

Here's the solution that I've arrived at. It doesn't get the Types before traversing the data, and so doesn't know before hand the type of each value before pulling the values out through Scan(), but the point really is to not have to know the types before hand.

The trick was to create 2 slices, one for the values, and one that holds pointers in parallel to the values slice. Then once the pointers are used to fill data, the values array is actually filled with the data, which can then be used to populate other data structures.

package main

import (
	&quot;fmt&quot;
	_ &quot;github.com/lib/pq&quot;
	&quot;database/sql&quot;
)

func main() {
	db, _ := sql.Open(
		&quot;postgres&quot;,
		&quot;user=postgres dbname=go_testing password=pass sslmode=disable&quot;)

	rows, _ := db.Query(&quot;SELECT * FROM _user;&quot;)

	columns, _ := rows.Columns()
	count := len(columns)
	values := make([]interface{}, count)
	valuePtrs := make([]interface{}, count)

	for rows.Next() {
		for i := range columns {
			valuePtrs[i] = &amp;values[i]
		}

		rows.Scan(valuePtrs...)

		for i, col := range columns {
			val := values[i]

			b, ok := val.([]byte)
            var v interface{}
			if (ok) {
				v = string(b)
			} else {
				v = val
			}

			fmt.Println(col, v)
		}
	}
}

答案2

得分: 10

给lucidquiet:你也可以分配一个接口而不是创建一个切片

以下代码运行良好:

var sql = "select * from table"
rows, err := db.Query(sql)
columns, err = rows.Columns()
colNum := len(columns)

var values = make([]interface{}, colNum)
for i, _ := range values {
var ii interface{}
values[i] = &ii
}

for rows.Next() {
err := rows.Scan(values...)
for i, colName := range columns {
var raw_value = *(values[i].(*interface{}))
var raw_type = reflect.TypeOf(raw_value)

    fmt.Println(colName,raw_type,raw_value)
}

}

英文:

To lucidquiet: you can also assign a interface instead of making a slice

The following code works good:

var sql = &quot;select * from table&quot;
rows, err := db.Query(sql)
columns, err = rows.Columns()
colNum := len(columns)

var values = make([]interface{}, colNum)
for i, _ := range values {
    var ii interface{}
    values[i] = &amp;ii
}

for rows.Next() {
    err := rows.Scan(values...)
    for i, colName := range columns {
        var raw_value = *(values[i].(*interface{}))
        var raw_type = reflect.TypeOf(raw_value)
        
        fmt.Println(colName,raw_type,raw_value)
    }
}

答案3

得分: 3

我不认为你需要反射来做这个 - 你可以使用切片和...操作符将多个值传递给可变参数函数。

col := rows.Columns()
vals := make([]interface{}, col)
rows.Scan(vals...)

不过,我可能对你想做的事情有误解!

英文:

I don't think you need reflection for this - you can use a slice and the ... operator to pass multiple values to a variadic function.

col := rows.Columns()
vals := make([]interface{}, col)
rows.Scan(vals...)

I may be misunderstanding what you want to do though!

答案4

得分: 2

以下解决方案允许您通过字段名称而不是索引来引用字段。它更像是PHP风格:

表定义:

CREATE TABLE `salesOrder` (
  `idOrder` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid` int(10) unsigned NOT NULL,
  `changed` datetime NOT NULL,
  PRIMARY KEY (`idOrder`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

main.go:

package main

import (
        "database/sql"
        "encoding/json"
        "fmt"
        _ "github.com/go-sql-driver/mysql"
        "log"
        "reflect"
        "strings"
)

var (
        db *sql.DB
)

func initDB() {
        var err error

        // 数据库/sql包会自动为您管理连接池。
        // sql.Open(..)返回一个表示连接池而不是单个连接的句柄。
        // 如果池中的所有连接都忙,则数据库/sql包会自动打开一个新连接。
        // 参考:http://stackoverflow.com/questions/17376207/how-to-share-mysql-connection-between-http-goroutines
        db, err = sql.Open("mysql", "MyUser:MyPassword@tcp(localhost:3306)/MyDB")
        //db, err = sql.Open("mysql", "MyUser:MyPassword@tcp(localhost:3306)/MyDB?tx_isolation='READ-COMMITTED'") // 可选

        if err != nil {
                log.Fatalf("初始化数据库连接时出错:%v", err.Error())
        }

        // Open不会打开连接。验证DSN数据:
        err = db.Ping()

        if err != nil {
                log.Fatalf("打开数据库连接时出错:%v", err.Error())
        }
}

func StrutToSliceOfFieldAddress(s interface{}) []interface{} {
        fieldArr := reflect.ValueOf(s).Elem()

        fieldAddrArr := make([]interface{}, fieldArr.NumField())

        for i := 0; i < fieldArr.NumField(); i++ {
                f := fieldArr.Field(i)
                fieldAddrArr[i] = f.Addr().Interface()
        }

        return fieldAddrArr
}

func testSelectMultipleRowsV3(optArr map[string]interface{}) {
        // 查询
        query := []string{}
        param := []interface{}{}

        if val, ok := optArr["idOrder"]; ok {
                query = append(query, "salesOrder.idOrder >= ?")
                param = append(param, val)
        }

        // 字段名的第一个字符必须大写。否则,您将得到以下错误:
        // panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
        var sqlField = struct {
                IdOrder int
                Uid     int
                Changed string
        }{}

        var rowArr []interface{}

        sqlFieldArrPtr := StrutToSliceOfFieldAddress(&sqlField)

        sql := "SELECT "
        sql += "  salesOrder.idOrder "
        sql += ", salesOrder.uid "
        sql += ", salesOrder.changed "
        sql += "FROM salesOrder "
        sql += "WHERE " + strings.Join(query, " AND ") + " "
        sql += "ORDER BY salesOrder.idOrder "

        stmt, err := db.Prepare(sql)
        if err != nil {
                log.Printf("错误:%v", err)
        }
        defer stmt.Close()

        rows, err := stmt.Query(param...)

        if err != nil {
                log.Printf("错误:%v", err)
        }

        defer rows.Close()

        if err != nil {
                log.Printf("错误:%v", err)
        }

        //sqlFields, err := rows.Columns()

        for rows.Next() {
                err := rows.Scan(sqlFieldArrPtr...)

                if err != nil {
                        log.Printf("错误:%v", err)
                }

                // 显示每个结构字段的类型
                f1 := reflect.TypeOf(sqlField.IdOrder)
                f2 := reflect.TypeOf(sqlField.Uid)
                f3 := reflect.TypeOf(sqlField.Changed)
                fmt.Printf("类型:%v\t%v\t%v\n", f1, f2, f3)

                // 显示每个字段的值
                fmt.Printf("行:%v\t%v\t%v\n\n", sqlField.IdOrder, sqlField.Uid, sqlField.Changed)

                rowArr = append(rowArr, sqlField)
        }

        if err := rows.Err(); err != nil {
                log.Printf("错误:%v", err)
        }

        // 生成格式整齐的输出
        if data, err := json.MarshalIndent(rowArr, "", " "); err != nil {
                log.Fatalf("JSON编组失败:%s", err)
        } else {
                fmt.Printf("json.MarshalIndent:\n%s\n\n", data)
        }
}

func main() {
        initDB()
        defer db.Close()

        // 此示例演示如何动态分配字段名列表给rows.Scan()函数。
        optArr := map[string]interface{}{}
        optArr["idOrder"] = 1
        testSelectMultipleRowsV3(optArr)
}

示例输出:

# go run main.go

类型:int       int     string
行:1  1       2016-05-06 20:41:06

类型:int       int     string
行:2  2       2016-05-06 20:41:35

json.MarshalIndent:
[
 {
  "IdOrder": 1,
  "Uid": 1,
  "Changed": "2016-05-06 20:41:06"
 },
 {
  "IdOrder": 2,
  "Uid": 2,
  "Changed": "2016-05-06 20:41:35"
 }
]
英文:

The following solution allows you to refer to the field by field name instead of index. It's more like PHP style:

Table definition:

CREATE TABLE `salesOrder` (
  `idOrder` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid` int(10) unsigned NOT NULL,
  `changed` datetime NOT NULL,
  PRIMARY KEY (`idOrder`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

main.go:

package main

import (
        &quot;database/sql&quot;
        &quot;encoding/json&quot;
        &quot;fmt&quot;
        _ &quot;github.com/go-sql-driver/mysql&quot;
        &quot;log&quot;
        &quot;reflect&quot;
        &quot;strings&quot;
)

var (
        db *sql.DB
)

func initDB() {
        var err error

        // The database/sql package manages the connection pooling automatically for you.
        // sql.Open(..) returns a handle which represents a connection pool, not a single connection.
        // The database/sql package automatically opens a new connection if all connections in the pool are busy.
        // Reference: http://stackoverflow.com/questions/17376207/how-to-share-mysql-connection-between-http-goroutines
        db, err = sql.Open(&quot;mysql&quot;, &quot;MyUser:MyPassword@tcp(localhost:3306)/MyDB&quot;)
        //db, err = sql.Open(&quot;mysql&quot;, &quot;MyUser:MyPassword@tcp(localhost:3306)/MyDB?tx_isolation=&#39;READ-COMMITTED&#39;&quot;) // optional

        if err != nil {
                log.Fatalf(&quot;Error on initializing database connection: %v&quot;, err.Error())
        }

        // Open doesn&#39;t open a connection. Validate DSN data:
        err = db.Ping()

        if err != nil {
                log.Fatalf(&quot;Error on opening database connection: %v&quot;, err.Error())
        }
}

func StrutToSliceOfFieldAddress(s interface{}) []interface{} {
        fieldArr := reflect.ValueOf(s).Elem()

        fieldAddrArr := make([]interface{}, fieldArr.NumField())

        for i := 0; i &lt; fieldArr.NumField(); i++ {
                f := fieldArr.Field(i)
                fieldAddrArr[i] = f.Addr().Interface()
        }

        return fieldAddrArr
}

func testSelectMultipleRowsV3(optArr map[string]interface{}) {
        // queries
        query := []string{}
        param := []interface{}{}

        if val, ok := optArr[&quot;idOrder&quot;]; ok {
                query = append(query, &quot;salesOrder.idOrder &gt;= ?&quot;)
                param = append(param, val)
        }

        // The first character of the field name must be in upper case. Otherwise, you would get:
        // panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
        var sqlField = struct {
                IdOrder int
                Uid     int
                Changed string
        }{}

        var rowArr []interface{}

        sqlFieldArrPtr := StrutToSliceOfFieldAddress(&amp;sqlField)

        sql := &quot;SELECT &quot;
        sql += &quot;  salesOrder.idOrder &quot;
        sql += &quot;, salesOrder.uid &quot;
        sql += &quot;, salesOrder.changed &quot;
        sql += &quot;FROM salesOrder &quot;
        sql += &quot;WHERE &quot; + strings.Join(query, &quot; AND &quot;) + &quot; &quot;
        sql += &quot;ORDER BY salesOrder.idOrder &quot;

        stmt, err := db.Prepare(sql)
        if err != nil {
                log.Printf(&quot;Error: %v&quot;, err)
        }
        defer stmt.Close()

        rows, err := stmt.Query(param...)

        if err != nil {
                log.Printf(&quot;Error: %v&quot;, err)
        }

        defer rows.Close()

        if err != nil {
                log.Printf(&quot;Error: %v&quot;, err)
        }

        //sqlFields, err := rows.Columns()

        for rows.Next() {
                err := rows.Scan(sqlFieldArrPtr...)

                if err != nil {
                        log.Printf(&quot;Error: %v&quot;, err)
                }

                // Show the type of each struct field
                f1 := reflect.TypeOf(sqlField.IdOrder)
                f2 := reflect.TypeOf(sqlField.Uid)
                f3 := reflect.TypeOf(sqlField.Changed)
                fmt.Printf(&quot;Type: %v\t%v\t%v\n&quot;, f1, f2, f3)

                // Show the value of each field
                fmt.Printf(&quot;Row: %v\t%v\t%v\n\n&quot;, sqlField.IdOrder, sqlField.Uid, sqlField.Changed)

                rowArr = append(rowArr, sqlField)
        }

        if err := rows.Err(); err != nil {
                log.Printf(&quot;Error: %v&quot;, err)
        }

        // produces neatly indented output
        if data, err := json.MarshalIndent(rowArr, &quot;&quot;, &quot; &quot;); err != nil {
                log.Fatalf(&quot;JSON marshaling failed: %s&quot;, err)
        } else {
                fmt.Printf(&quot;json.MarshalIndent:\n%s\n\n&quot;, data)
        }
}

func main() {
        initDB()
        defer db.Close()

        // this example shows how to dynamically assign a list of field name to the rows.Scan() function.
        optArr := map[string]interface{}{}
        optArr[&quot;idOrder&quot;] = 1
        testSelectMultipleRowsV3(optArr)
}

Sample output:

# go run main.go

Type: int       int     string
Row: 1  1       2016-05-06 20:41:06

Type: int       int     string
Row: 2  2       2016-05-06 20:41:35

json.MarshalIndent:
[
 {
  &quot;IdOrder&quot;: 1,
  &quot;Uid&quot;: 1,
  &quot;Changed&quot;: &quot;2016-05-06 20:41:06&quot;
 },
 {
  &quot;IdOrder&quot;: 2,
  &quot;Uid&quot;: 2,
  &quot;Changed&quot;: &quot;2016-05-06 20:41:35&quot;
 }
]

huangapple
  • 本文由 发表于 2013年7月25日 06:06:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/17845619.html
匿名

发表评论

匿名网友

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

确定