Skip to content

Scanner should return one of int64,float64,bool,[]byte,string,time.Time,nil , but I got uint64  #1575

Open

Description

@bronze1man

It maybe a bug of database/sql , if it is ,please tell me.

Issue description

https://pkg.go.dev/database/sql#Scanner says Scanner should return one of int64,float64,bool,[]byte,string,time.Time,nil , but I got uint64

Example code

the sql:

select * from INFORMATION_SCHEMA.TABLES

some of my scaner_t implement.

type scaner_t struct{
	src any
}
func (s *scaner_t) Scan(src any) error{
	s.src = src
	return nil
}
func (s *scaner_t) AsString() (outS string,err error){
	switch obj:=s.src.(type) {
	case int64:
		return strconv.FormatInt(obj,10),nil
	case float64:
		return strconv.FormatFloat(obj,'f',-1,64),nil
	case bool:
		return strconv.FormatBool(obj),nil
	case []byte:
		return string(obj),nil
	case string:
		return obj,nil
	case time.Time:
		b,err:=obj.MarshalText()
		if err!=nil{
			return "",err
		}
		return string(b),nil
	case nil:
		return "",nil
	default:
		return "",errors.New("unknow type "+fmt.Sprintf("%T",s.src))
	}
}

Error log

unknow type uint64

Configuration

Driver version (or git SHA):
github.com/go-sql-driver/mysql v1.8.0

Go version: run go version in your console
go version go1.19.2 darwin/amd64

Server version: E.g. MySQL 5.6, MariaDB 10.0.20

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.44    |
+-----------+

Server OS: E.g. Debian 8.1 (Jessie), Windows 10
mac os 14.2.1

Activity

  1. methane commented on Mar 25, 2024

    @methane
    Member

    Yes, it violates database/sql convention.
    But this violation is necessary to support uint64. So we can not fix it until database/sql supports uint64 natively.

  2. bronze1man commented on Mar 26, 2024

    @bronze1man
    Author

    @methane Is there any document or examples about how to implement sql.Scanner to work with https://github.com/go-sql-driver/mysql?
    I do not like try and fail again and again.

  3. methane commented on Mar 26, 2024

    @methane
    Member

    There is perfect document.

    https://cs.opensource.google/go/go/+/master:src/database/sql/convert.go;l=219-480

    mysql/packets.go

    Lines 769 to 856 in d7ddb8b

    func (rows *textRows) readRow(dest []driver.Value) error {
    mc := rows.mc
    if rows.rs.done {
    return io.EOF
    }
    data, err := mc.readPacket()
    if err != nil {
    return err
    }
    // EOF Packet
    if data[0] == iEOF && len(data) == 5 {
    // server_status [2 bytes]
    rows.mc.status = readStatus(data[3:])
    rows.rs.done = true
    if !rows.HasNextResultSet() {
    rows.mc = nil
    }
    return io.EOF
    }
    if data[0] == iERR {
    rows.mc = nil
    return mc.handleErrorPacket(data)
    }
    // RowSet Packet
    var (
    n int
    isNull bool
    pos int = 0
    )
    for i := range dest {
    // Read bytes and convert to string
    var buf []byte
    buf, isNull, n, err = readLengthEncodedString(data[pos:])
    pos += n
    if err != nil {
    return err
    }
    if isNull {
    dest[i] = nil
    continue
    }
    switch rows.rs.columns[i].fieldType {
    case fieldTypeTimestamp,
    fieldTypeDateTime,
    fieldTypeDate,
    fieldTypeNewDate:
    if mc.parseTime {
    dest[i], err = parseDateTime(buf, mc.cfg.Loc)
    } else {
    dest[i] = buf
    }
    case fieldTypeTiny, fieldTypeShort, fieldTypeInt24, fieldTypeYear, fieldTypeLong:
    dest[i], err = strconv.ParseInt(string(buf), 10, 64)
    case fieldTypeLongLong:
    if rows.rs.columns[i].flags&flagUnsigned != 0 {
    dest[i], err = strconv.ParseUint(string(buf), 10, 64)
    } else {
    dest[i], err = strconv.ParseInt(string(buf), 10, 64)
    }
    case fieldTypeFloat:
    var d float64
    d, err = strconv.ParseFloat(string(buf), 32)
    dest[i] = float32(d)
    case fieldTypeDouble:
    dest[i], err = strconv.ParseFloat(string(buf), 64)
    default:
    dest[i] = buf
    }
    if err != nil {
    return err
    }
    }
    return nil
    }

    mysql/packets.go

    Lines 1230 to 1403 in d7ddb8b

    func (rows *binaryRows) readRow(dest []driver.Value) error {
    data, err := rows.mc.readPacket()
    if err != nil {
    return err
    }
    // packet indicator [1 byte]
    if data[0] != iOK {
    // EOF Packet
    if data[0] == iEOF && len(data) == 5 {
    rows.mc.status = readStatus(data[3:])
    rows.rs.done = true
    if !rows.HasNextResultSet() {
    rows.mc = nil
    }
    return io.EOF
    }
    mc := rows.mc
    rows.mc = nil
    // Error otherwise
    return mc.handleErrorPacket(data)
    }
    // NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
    pos := 1 + (len(dest)+7+2)>>3
    nullMask := data[1:pos]
    for i := range dest {
    // Field is NULL
    // (byte >> bit-pos) % 2 == 1
    if ((nullMask[(i+2)>>3] >> uint((i+2)&7)) & 1) == 1 {
    dest[i] = nil
    continue
    }
    // Convert to byte-coded string
    switch rows.rs.columns[i].fieldType {
    case fieldTypeNULL:
    dest[i] = nil
    continue
    // Numeric Types
    case fieldTypeTiny:
    if rows.rs.columns[i].flags&flagUnsigned != 0 {
    dest[i] = int64(data[pos])
    } else {
    dest[i] = int64(int8(data[pos]))
    }
    pos++
    continue
    case fieldTypeShort, fieldTypeYear:
    if rows.rs.columns[i].flags&flagUnsigned != 0 {
    dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
    } else {
    dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
    }
    pos += 2
    continue
    case fieldTypeInt24, fieldTypeLong:
    if rows.rs.columns[i].flags&flagUnsigned != 0 {
    dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
    } else {
    dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
    }
    pos += 4
    continue
    case fieldTypeLongLong:
    if rows.rs.columns[i].flags&flagUnsigned != 0 {
    val := binary.LittleEndian.Uint64(data[pos : pos+8])
    if val > math.MaxInt64 {
    dest[i] = uint64ToString(val)
    } else {
    dest[i] = int64(val)
    }
    } else {
    dest[i] = int64(binary.LittleEndian.Uint64(data[pos : pos+8]))
    }
    pos += 8
    continue
    case fieldTypeFloat:
    dest[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))
    pos += 4
    continue
    case fieldTypeDouble:
    dest[i] = math.Float64frombits(binary.LittleEndian.Uint64(data[pos : pos+8]))
    pos += 8
    continue
    // Length coded Binary Strings
    case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
    fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
    fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
    fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON:
    var isNull bool
    var n int
    dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
    pos += n
    if err == nil {
    if !isNull {
    continue
    } else {
    dest[i] = nil
    continue
    }
    }
    return err
    case
    fieldTypeDate, fieldTypeNewDate, // Date YYYY-MM-DD
    fieldTypeTime, // Time [-][H]HH:MM:SS[.fractal]
    fieldTypeTimestamp, fieldTypeDateTime: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal]
    num, isNull, n := readLengthEncodedInteger(data[pos:])
    pos += n
    switch {
    case isNull:
    dest[i] = nil
    continue
    case rows.rs.columns[i].fieldType == fieldTypeTime:
    // database/sql does not support an equivalent to TIME, return a string
    var dstlen uint8
    switch decimals := rows.rs.columns[i].decimals; decimals {
    case 0x00, 0x1f:
    dstlen = 8
    case 1, 2, 3, 4, 5, 6:
    dstlen = 8 + 1 + decimals
    default:
    return fmt.Errorf(
    "protocol error, illegal decimals value %d",
    rows.rs.columns[i].decimals,
    )
    }
    dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen)
    case rows.mc.parseTime:
    dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
    default:
    var dstlen uint8
    if rows.rs.columns[i].fieldType == fieldTypeDate {
    dstlen = 10
    } else {
    switch decimals := rows.rs.columns[i].decimals; decimals {
    case 0x00, 0x1f:
    dstlen = 19
    case 1, 2, 3, 4, 5, 6:
    dstlen = 19 + 1 + decimals
    default:
    return fmt.Errorf(
    "protocol error, illegal decimals value %d",
    rows.rs.columns[i].decimals,
    )
    }
    }
    dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen)
    }
    if err == nil {
    pos += int(num)
    continue
    } else {
    return err
    }
    // Please report if this happens!
    default:
    return fmt.Errorf("unknown field type %d", rows.rs.columns[i].fieldType)
    }
    }

    It must be readable than my English.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions