sqlitegostructgo-sqlite3

Pass struct interface to sqlite exec


In Go, I'm trying to pass an interface{} to the statement.Exec() function from go-sqlite3. I'm sure this is a solved problem, but I cannot figure it out.

Basically I have a struct with the row data which I want to pass to a function that will insert it to a sqlite db. The thing is I want to be able to programmatically control what goes into the statement.Exec() function

Here is an excerpt:


type hostRows struct {
    domain   string
}

type clientRows struct {
    name   string
}

func main() {

    ...

    data := hostRows{domain: "dom.com"}
    insertRow(sqliteDatabase, data)

    data2 := clientRows{name: "bob"}
    insertRow(sqliteDatabase, data2)

    ...

}

func insertRow(db *sql.DB, row interface{}) {

    insertSQL := "INSERT INTO table(col) VALUES (?)"
    statement, _ := db.Prepare(insertSQL)
    
    statement.Exec(row) // here's the issue, how can I extract the element in the interface to pass it to the function for Exec to understand
    
}

I know that in this example, I could hard code the row type to the struct and type statement.Exec(row.(hostRows).domain), but now the code will break to when the client struct is passed.

here is the deceleration for the Exec function

func (s *Stmt) Exec(args ...interface{}) (Result, error) 

I've tried playing with reflect but it hasn't worked for me so far. My only solution for the moment is using a switch condition that could check and prepare the right command for Exec, but this is less than dodgy.

type hostRows struct {
    domain   string
}

type clientRows struct {
    name   string
}

func main() {

    ...

    data := hostRows{domain: "dom.com"}
    insertRow(sqliteDatabase, 1, data)

    data2 := clientRows{name: "bob"}
    insertRow(sqliteDatabase, 2, data2)

    ...

}

func insertRow(db *sql.DB, i int, row interface{}) {

    insertSQL := "INSERT INTO table(col) VALUES (?)"
    statement, _ := db.Prepare(insertSQL)
    
    // This basically could be a working solution, but I'm sure there is a better one
    switch i {
        case 1:
            data := row.(hostRows)
            statement.Exec(data.domain)
        case 2:
            data := row.(clientRows)
            statement.Exec(data.name)
    }
    
}

edit: corrected the INSERT statement ; forget the columns. corrected statement.Exec(row.domain) to statement.Exec(row.(hostRows).domain) edit2: added second example


Solution

  • Remember that in order for reflect.Interface() to work, you must export the fields. To achieve what you want using reflection, you could try something like this:

    type hostRows struct {
        //Should export field to read it using reflect.Value.Interface()
        Domain string
    }
    
    type clientRows struct {
        //Should export field to read it using reflect.Value.Interface()
        Name string
    }
    
    func insertRow(db *sql.DB, i int, row interface{}) {
        rv := reflect.ValueOf(row)
        var args []interface{}
        for i := 0; i < rv.NumField(); i++ {
            args = append(args, rv.Field(i).Interface())
        }
        db.Exec("Insert Satement...", args...)
    }