Sqlx: Get treats json.RawMessage like db.RawBytes

Created on 4 Jan 2016  Â·  7Comments  Â·  Source: jmoiron/sqlx

I have a struct where one of the fields is a json.RawMessage. When I use DB.Get() to populate it, the field is volatile and changes with more calls to DB.Get().

Here is a simple program to reproduce it.

package main

import (
    "encoding/json"
    "fmt"

    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
)

type Var struct {
    Raw json.RawMessage
}

func main() {
    db, err := sqlx.Open("mysql", "root@/")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    var v Var
    if err = db.Get(&v, `SELECT '{"a":"b"}' AS raw`); err != nil {
        panic(err)
    }
    // first time works great
    fmt.Printf("v: %s\n", v.Raw)

    var q Var
    if err = db.Get(&q, `SELECT 'null' AS raw`); err != nil {
        panic(err)
    }
    // "v" has changed???
    fmt.Printf("v: %s\n", v.Raw)
}

The output I get is:

v: {"a":"b"}
v: null�

Most helpful comment

FYI - I submitted @jmoiron's patch, which has been included in Go 1.7, so you should be safe against this problem if you are using it!

All 7 comments

I believe this is a Go issue and I've submitted an issue for it. That ticket describes the problem in more detail; rest assured that this problem exists with db.QueryRow as well. Unfortunately, for now you'll have to use []byte to get a copy of that data, though it should not involve a copy to type-convert it into json.RawMessage.

Here's a test for sqlx_test.go that shows that this behavior is consistent across any named []byte type:

func TestIssue197(t *testing.T) {
    type mybyte []byte
    type Var struct{ Raw json.RawMessage }
    type Var2 struct{ Raw []byte }
    type Var3 struct{ Raw mybyte }
    RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
        var err error
        var v, q Var
        if err = db.Get(&v, `SELECT '{"a": "b"}' AS raw`); err != nil {
            t.Fatal(err)
        }
        fmt.Printf("%s: v %s\n", db.DriverName(), v.Raw)
        if err = db.Get(&q, `SELECT 'null' AS raw`); err != nil {
            t.Fatal(err)
        }
        fmt.Printf("%s: v %s\n", db.DriverName(), v.Raw)

        var v2, q2 Var2
        if err = db.Get(&v2, `SELECT '{"a": "b"}' AS raw`); err != nil {
            t.Fatal(err)
        }
        fmt.Printf("%s: v2 %s\n", db.DriverName(), v2.Raw)
        if err = db.Get(&q2, `SELECT 'null' AS raw`); err != nil {
            t.Fatal(err)
        }
        fmt.Printf("%s: v2 %s\n", db.DriverName(), v2.Raw)

        var v3, q3 Var3
        if err = db.QueryRow(`SELECT '{"a": "b"}' AS raw`).Scan(&v3.Raw); err != nil {
            t.Fatal(err)
        }
        fmt.Printf("v3 %s\n", v3.Raw)
        if err = db.QueryRow(`SELECT '{"c": "d"}' AS raw`).Scan(&q3.Raw); err != nil {
            t.Fatal(err)
        }
        fmt.Printf("v3 %s\n", v3.Raw)
        t.Fail()
    })
}

Output:

postgres: v  1": "b"}
postgres: v  1l
postgres: v2 {"a": "b"}
postgres: v2 {"a": "b"}
v3  1": "b"}
v3  1": "d"}
sqlite3: v {"a": "b"}
sqlite3: v {"a": "b"}
sqlite3: v2 {"a": "b"}
sqlite3: v2 {"a": "b"}
v3 {"a": "b"}
v3 {"a": "b"}
mysql: v {"a": "b"}
mysql: v null�
mysql: v2 {"a": "b"}
mysql: v2 {"a": "b"}
v3 {"a": "b"}
v3 {"c": "d"}

I'm going to close this issue because it's not an sqlx issue, however it does make some things in sqlx/types potentially dangerous to use, so I think I will document that this bug is there for certain Go versions.

Okay thanks for shedding light on this. In the meanwhile I am explicitly using []byte and converting to a json.RawMessage afterwards, as you suggested.

@jmoiron Are you sure the warnings are appropriate for types that implement Scanner? It would seem they are always passed driver memory and are expected to copy the []byte.

You're right, the types versions are not vulnerable because they implement Scanner, and other types that implement scanner are also not vulnerable. Any type foo []byte that is not implementing Scanner is vulnerable.

Cool. If you haven't, see my comment on golang/go#13905 for a workaround on scanning types that are convertible to []byte. Could sqlx detect non-scannable structs (edit: struct fields) that are so convertible, e.g. under fieldsByTraversal(), then convert the pointer value to *byte[] and pass that to Row.Scan(...)?

FYI - I submitted @jmoiron's patch, which has been included in Go 1.7, so you should be safe against this problem if you are using it!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

luiscvega picture luiscvega  Â·  4Comments

ascepanovic picture ascepanovic  Â·  6Comments

webRat picture webRat  Â·  4Comments

davisford picture davisford  Â·  5Comments

mewben picture mewben  Â·  5Comments