Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

database/sql: convertAssign throws away type information stored at interface{} #23703

Closed
AlekSi opened this issue Feb 5, 2018 · 6 comments
Closed
Assignees
Milestone

Comments

@AlekSi
Copy link
Contributor

@AlekSi AlekSi commented Feb 5, 2018

What version of Go are you using (go version)?

Both 1.9.3 and current master (e6756ec).

What did you do?

var scan1 int8
var scan2 interface{} = int8(0)
err := db.QueryRow("SELECT 42, 42").Scan(&scan1, &scan2)
if err != nil {
	t.Fatal(err)
}
t.Logf("%v (%T) %v (%T)", scan1, scan1, scan2, scan2)

What did you expect to see?

42 (int8) 42 (int8)

What did you see instead?

42 (int8) 42 (int64)

https://golang.org/pkg/database/sql/#Rows.Scan says about it:

In the most simple case, if the type of the value from the source column is an integer, bool or string type T and dest is of type *T, Scan simply assigns the value through the pointer.

If an argument has type *interface{}, Scan copies the value provided by the underlying driver without conversion.

Is it possible to honor type information stored at interface{} in Go 1.x? If yes, I can work on it. If not, can we do that in Go 2, and expand documentation to mention current behavior?

@AlekSi
Copy link
Contributor Author

@AlekSi AlekSi commented Feb 5, 2018

Even more disturbing example:

var scan1 float64
var scan2 interface{} = float64(0)
err := db.QueryRow("SELECT 3.14, 3.14").Scan(&scan1, &scan2)
if err != nil {
	t.Fatal(err)
}
t.Logf("%v (%T) %v (%T)", scan1, scan1, scan2, scan2)

3.14 (float64) 3.14 (string)

@AlekSi
Copy link
Contributor Author

@AlekSi AlekSi commented Feb 19, 2018

Poke @kardianos

@kardianos kardianos self-assigned this Feb 19, 2018
@kardianos
Copy link
Contributor

@kardianos kardianos commented Feb 19, 2018

@AlekSi Are you using sqlite? If not, what DB storage are you using.

What you are noticing is the underlying type the database provides is coming though in scan2. You see, even when you assign a float64 or int8 to an interface{} type, the type is still an interface type. It just happens to have something stored in it already. We don't check for what is already stored in it, we just look at the type.

The exact SQL used would depend on the system used, but for postgresql you could use select cast(3.14 as double precision), cast(3.14 as double precision); and it would be a float64 in either case. Postgres doesn't have an int8 type exactly, but SQL Server does called tinyint and that would be select convert(tinyint, 42), convert(tinyint, 42); which again would scan both as the same type.

You may already know all this. Then the question is, why would you like to do this? What is your goal? Do you want a generic or runtime configurable converter? If so, you could use something like this https://godoc.org/bitbucket.org/kardianos/table to pull data out, then have a custom routine that converts to your appropriate data type. What is the problem you want to solve?

@AlekSi
Copy link
Contributor Author

@AlekSi AlekSi commented Feb 19, 2018

Sorry, I wasn't clear enough in my original message. I'm writing my own driver for MySQL X Protocol: https://github.com/AlekSi/mysqlx I noticed the behavior explained in the first message while writing tests. I think it is not very intuitive, although I also don't think my situation is very typical in real non-test non-driver code.

@ianlancetaylor ianlancetaylor added this to the Go1.11 milestone Mar 28, 2018
@kardianos
Copy link
Contributor

@kardianos kardianos commented Mar 29, 2018

Sorry for the delay @AlekSi Looking at the original example, scan1 has a Go type of int8, while scan2 has a Go type of interface{} (fmt.Spritf("%T") does a bit of reflection to look at the type stored in interface{}). I'm not convinced we want to look at what is currently stored in a type to determine how something should be scanned (which is what you are asking for I think).

For your second example with float64 scanning, after looking through convertAssign, I can only assume that you are returning a Go string type from the driver and convertAssign is converting it to float64 in the scan1 case and directly assigning the string to the interface in the second case.

Do you think convertAssign should do something different?

@AlekSi
Copy link
Contributor Author

@AlekSi AlekSi commented Mar 29, 2018

For your second example with float64 scanning, after looking through convertAssign, I can only assume that you are returning a Go string type from the driver and convertAssign is converting it to float64 in the scan1 case and directly assigning the string to the interface in the second case.

It turned out that my driver code handles float numbers coming from database correctly, but in case of unqualified SELECT 3.14 mysqlx really returns BYTES DECIMAL (edited).

I don't think anything should be done for this issue. Thank you.

@AlekSi AlekSi closed this Mar 29, 2018
@golang golang locked and limited conversation to collaborators Mar 29, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.