Как вызвать функцию сканирования переменной с помощью отражения
Я пытаюсь вызвать функцию 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>]
Ответы
Ответ 1
Вот решение, к которому я пришел. Он не получает типы перед прохождением данных и поэтому не знает перед рукой тип каждого значения перед извлечением значений через Scan()
, но суть в том, что не нужно знать типы заранее.
Хитрость заключалась в том, чтобы создать 2 среза, один для значений и один, который содержит указатели параллельно срезу значений. Затем, когда указатели используются для заполнения данных, массив значений фактически заполняется данными, которые затем можно использовать для заполнения других структур данных.
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)
}
}
}
Ответ 2
Для 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)
}
}
Ответ 3
Я не думаю, что вам нужно отражение для этого - вы можете использовать срез и оператор ...
для передачи нескольких значений в вариационную функцию.
col := rows.Columns()
vals := make([]interface{}, col)
rows.Scan(vals...)
Возможно, я не понимаю, что вы хотите делать!
Ответ 4
Следующее решение позволяет вам ссылаться на поле по имени поля вместо индекса. Это больше похоже на стиль 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
// 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/info/17376207/how-to-share-mysql-connection-between-http-goroutines
db, err = sql.Open("mysql", "MyUser:[email protected](localhost:3306)/MyDB")
//db, err = sql.Open("mysql", "MyUser:[email protected](localhost:3306)/MyDB?tx_isolation='READ-COMMITTED'") // optional
if err != nil {
log.Fatalf("Error on initializing database connection: %v", err.Error())
}
// Open doesn't open a connection. Validate DSN data:
err = db.Ping()
if err != nil {
log.Fatalf("Error on opening database connection: %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{}) {
// queries
query := []string{}
param := []interface{}{}
if val, ok := optArr["idOrder"]; ok {
query = append(query, "salesOrder.idOrder >= ?")
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(&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("Error: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(param...)
if err != nil {
log.Printf("Error: %v", err)
}
defer rows.Close()
if err != nil {
log.Printf("Error: %v", err)
}
//sqlFields, err := rows.Columns()
for rows.Next() {
err := rows.Scan(sqlFieldArrPtr...)
if err != nil {
log.Printf("Error: %v", 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("Type: %v\t%v\t%v\n", f1, f2, f3)
// Show the value of each field
fmt.Printf("Row: %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("Error: %v", err)
}
// produces neatly indented output
if data, err := json.MarshalIndent(rowArr, "", " "); err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
} else {
fmt.Printf("json.MarshalIndent:\n%s\n\n", 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["idOrder"] = 1
testSelectMultipleRowsV3(optArr)
}
Пример вывода:
# 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:
[
{
"IdOrder": 1,
"Uid": 1,
"Changed": "2016-05-06 20:41:06"
},
{
"IdOrder": 2,
"Uid": 2,
"Changed": "2016-05-06 20:41:35"
}
]