Sqlx: Embedded Struct Field shadows Struct Field

Created on 6 Aug 2018  Â·  7Comments  Â·  Source: jmoiron/sqlx

I am trying to understand how this works, but it seems embedded struct fields take precedence over the parent struct. For example:

type Foo struct {
  Baz int `db:"baz"`
}

type Bar struct {
  Foo
  Baz int `db:"baz"`
}

func main() {
  var bar Bar
  sqlx.Get(&bar, "SELECT baz FROM bars")
  // bar.Baz = 0, bar.Foo.Baz = value
}

Most helpful comment

I'd also like to add, this seems confusing because its the opposite behavior of the standard library's JSON library.

All 7 comments

Looking at this test: https://github.com/jmoiron/sqlx/blob/0dae4fefe7c0e190f7b5a78dac28a1c82cc8d849/reflectx/reflect_test.go#L110

This seems to be the defined behavior. I wish I knew why the embeddeds field always takes precedence, or if both fields could be updated.

Never noticed this. Interesting question. I also would want to know

I'd also like to add, this seems confusing because its the opposite behavior of the standard library's JSON library.

I don't get this either…especially since the guide says sqlx "assigns to fields using the same precedence rules that Go uses for embedded attribute and method access". Yet in Go, assignment goes to the outer struct, not the embedded one. This caused some significant hair-pulling for me today!

@jmoiron Is this intentional? In any case perhaps it should be called out in the docs.

package main

import (
    "fmt"
    "github.com/jmoiron/sqlx"
    _ "github.com/mattn/go-sqlite3"
)

func check(err error) {
    if err != nil {
        panic(err)
    }
}

type A struct {
    I int
}

type B struct {
    A
    I int
}

func main() {
    db, _ := sqlx.Connect("sqlite3", "/tmp/test.sqlite3")
    db.MustExec("drop table test; create table test (i int); insert into test (i) values (42)")
    query := "select i from test"

    // sqlx assigns the INNER field
    b := B{}
    check(db.Get(&b, query))
    fmt.Printf("%+v\n", b) // {A:{I:42} I:0}

    // Go assigns the OUTER field
    b = B{}
    b.I = 42
    fmt.Printf("%+v\n", b) // {A:{I:0} I:42}
}

Under Advanced Scanning, the guide does state:

Because of the way that sqlx builds the mapping of field name to field address, by the time you Scan into a struct, it no longer knows whether or not a name was encountered twice during its traversal of the struct tree. So unlike Go, StructScan will choose the "first" field encountered which has that name. Since Go struct fields are ordered from top to bottom, and sqlx does a breadth-first traversal in order to maintain precedence rules, it would happen in the shallowest, top-most definition.

Unfortunately, if two fields have the same name, the first field appears to receive both values. I feel like it’d be more useful if the fields were populated in the same order as the columns returned in the result, which would make it much easier to do joins like:

result := []struct{
    A
    B
}{}

db.Select(&result, "SELECT a.*, b.* FROM a INNER JOIN b ON …")

@mandykoh I did see that, but even if the field comes first in the outer struct, the inner one is still assigned:

type C struct {
    I int
    A
}

    // sqlx still assigns the INNER field
    c := C{}
    check(db.Get(&c, query))
    fmt.Printf("%+v\n", c) // {I:0 A:{I:42}}

Are there any plans to make implementation match the docs description in Advanced Scanning -> where the example above by @wrs should print {I:42 A:{I:0}}

@jmoiron ^

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mewben picture mewben  Â·  5Comments

fpolli picture fpolli  Â·  5Comments

luiscvega picture luiscvega  Â·  4Comments

fwhezfwhez picture fwhezfwhez  Â·  4Comments

x1unix picture x1unix  Â·  4Comments