2013-07-24 5 views
21

Ich suche die Rows.Scan() Funktion mit Reflektion zu rufen. Allerdings benötigt es eine variable Anzahl von Zeigern, aber ich bin neu in Golang und es gibt nicht viele Quellenbeispiele. Ich muss Reflektion verwenden, weil ich ein Slice mit den Werten eines Query-Aufrufs füllen möchte. Also im Grunde mit rows.Columns(), um die Länge der Zeile und dann make() ein Stück []interface{} mit den Datenpunkten füllen, die normalerweise mit den Zeigern an die Scan() Funktion übergeben gefüllt würde.Wie ruft man die Variadic-Funktion Scan in Golang mit Reflection auf?

Im Grunde so etwas wie diesem Code:

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

Wer ein Beispiel für eine variadische Funktion aufrufen, die Zeiger mit Reflexion nimmt, dass ich einen Blick nehmen?

Bearbeiten: Beispielcode, scheint nicht zu tun, was ich danach bin.

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) 
    } 

} 

Die Ergebnisse:

[<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>] 

Antwort

38

Hier ist die Lösung, die ich erreicht habe. Es bekommt die Typen vor dem Durchlaufen der Daten nicht und weiß daher nicht, bevor Sie den Wert jedes Wertes übergeben, bevor Sie die Werte durch Scan() herausziehen, aber der Punkt ist wirklich, die Typen vorher nicht zu kennen.

Der Trick bestand darin, zwei Slices zu erstellen, einen für die Werte und einen, der Zeiger parallel zum Wertebereich hält. Wenn die Zeiger dann zum Füllen der Daten verwendet werden, wird das Array Werte tatsächlich mit den Daten gefüllt, die dann zum Auffüllen anderer Datenstrukturen verwendet werden können.

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 { 

      var v interface{} 

      val := values[i] 

      b, ok := val.([]byte) 

      if (ok) { 
       v = string(b) 
      } else { 
       v = val 
      } 

      fmt.Println(col, v) 
     } 
    } 
} 
+0

Erstellen eines Befehlszeilen-Terminals zum Abfragen von Datenbanken in der Cloud. Das hat gut als Ausgangspunkt funktioniert ... Danke! – openwonk

+0

Wer hat das in 1.4.2 oder höher versucht ?. Ich bekomme alle Felder in String-Typ – Anuruddha

+0

Genau das, was ich brauchte, liebe ich diese Gemeinschaft – Mauricio

4

Ich glaube nicht, Sie Reflexion für diese benötigen - Sie eine Scheibe und die ... Operator können mehrere Werte zu einer variadische Funktion zu übergeben.

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

Ich bin vielleicht falsch verstanden, was Sie tun möchten, obwohl!

+0

Nun, das scheint wie es funktionieren würde. Ich habe meine Frage mit Beispielcode aktualisiert. Die Verwendung des Operators '...' mit diesem Code erzeugt und 'ungültige Speicheradresse oder Nullzeigerdereferenzierung'. Ich habe das Gefühl, ich muss das Datenarray mit Zeigern oder etwas initialisieren ... – lucidquiet

+0

Scratch, dass das Problem mit der Speicheradresse mit meinem SQL und nicht der Betreiber verwendet wurde. Ich kann jetzt den Code mit dem Update kompilieren und ausführen, aber ich bekomme keine Daten, jeder Eintrag in die Daten (oder in Ihrem Beispiel Vals), ist Null. Letztendlich funktionierte das nicht ganz so, wie ich es gerne hätte. – lucidquiet

+3

-1: 'Rows.Columns()' returns '([] string, error)'; weiter, 'Scan()' scheitert, wenn Sie keine Zeiger übergeben (und es wird sich beschweren, dass '[] Interface {}' kein Array von Zeigern ist, es sei denn, Sie initialisieren seine Werte als Zeiger auf Schnittstellen nach der akzeptierten Antwort). – weberc2

1

Um 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) 
    } 
} 
1

Die folgende Lösung ermöglicht es Ihnen, von Feld zu Feld verweisen: Sie können auch eine Schnittstelle, anstatt, eine Scheibe

Der folgende Code funktioniert gut zuweisen Name statt Index. Es ist mehr wie PHP-Stil:

Tabellendefinition:

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/questions/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) 
} 

Beispielausgabe:

# go laufen main.gehen

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" 
} 
] 
+0

Ich bin mir nicht sicher, ob dies die OP-Frage beantwortet hat, aber ich muss sagen, dass Sie mich Stunden der Suche auf dem gespeichert haben Web, wie man Zeilendaten von einer SQL-Datenbank abruft und JSonify es ... es ist so einfach in Go, aber Sie müssen wissen, wie! Also, vielen Dank! :) –

Verwandte Themen