denisenkom / go-mssqldb

Microsoft SQL server driver written in go language
BSD 3-Clause "New" or "Revised" License
1.82k stars 495 forks source link

Size of typeBigBinary should not be 0 #685

Open shueybubbles opened 3 years ago

shueybubbles commented 3 years ago

https://github.com/denisenkom/go-mssqldb/blob/ee2fbc25fd8f2d8c56af5ca2d7b661fb50fdb23a/types.go#L1467

I think this should return ti.Size, true

tc-hib commented 3 years ago

Hello. Yes, it should do the same as for typeBigChar.

r, err := db.Query("SELECT CAST('1' AS CHAR(10)), CAST(0x02 AS BINARY(20)), CAST(0x03 AS VARBINARY(30))")
if err != nil {
    log.Fatalln(err)
}
c, err := r.ColumnTypes()
if err != nil {
    log.Fatalln(err)
}
for i := range c {
    fmt.Println(c[i].Length())
}
// 10 true
// 0 false (should be 20 true)
// 30 true

I think you can make a PR.

shueybubbles commented 3 years ago

varbinary handling overall looks funny. EDIT: this behavior difference seems to be a convention for varbinary conversion to string as described here: https://docs.microsoft.com/sql/odbc/reference/appendixes/sql-to-c-binary?view=sql-server-ver15

When binary SQL data is converted to character C data, each byte (8 bits) of source data is represented as two ASCII characters. These characters are the ASCII character representation of the number in its hexadecimal form. For example, a binary 00000001 is converted to "01" and a binary 11111111 is converted to "FF".

I tried with my own app and with usql and it doesn't print what I expect.

ms:sqltools2019-3-> select cast(0x123456 as varbinary(max)) as name;
  name
--------
 \x124V
(1 row)

Whereas both SSMS and SqlCmd do this:

1> select cast(0x123456 as varbinary(max)) as mylongenoughname
2> go
mylongenoughname
----------------
0x123456

(1 rows affected)
1> select cast('123456' as varbinary(max)) as mylongenoughname
2> go
mylongenoughname
----------------
0x313233343536

(1 rows affected)
tc-hib commented 3 years ago

Looks like your program is displaying the value as a string literal: 0x12 has to be escaped as \x12, then you have 0x34 4 and 0x56 V. This is correct, actually.

You don't have to cast, the literal value already is a varbinary(3).

The link you shared is from the ODBC driver, and it talks about reading C strings. That is not relevant here. go-mssqldb never handles C strings and is unrelated to ODBC.

shueybubbles commented 3 years ago

yeah it's not that the bits on the wire are wrong somehow, it's just a matter of end user expectations for rendering such as the scenarios of printing out query results to a screen or file. The previous tools provided by Microsoft like SSMS and sqlcmd emit binary values using that convention. It's easy enough for my own app to print them this way to follow that convention. I was only referencing the ODBC doc for reference to the convention.

tc-hib commented 3 years ago

That doesn't happen in the driver. I think SSMS just reads the []byte value from the driver, and then it prints it itself as 0x123456.

Even in ADO.NET, I tried GetString and GetSqlChars instead of GetValue or GetSqlBinary, and all I got was an exception:

System.InvalidCastException : 'Unable to cast object of type 'System.Byte[]' to type 'System.String'.'

The only difference in Go is that scanning a binary into a string is allowed. And you get something closer to what would happen in T-SQL:

declare @val varchar(max)
select @val = 0x123456
select @val