func (i *colInfo) Read(e *binary.Encoder) (err error) {
	var columnID, tableID, status uint8
	var name string
	for {
		columnID = e.Uint8() - 1
		tableID = e.Uint8() - 1
		status = e.Uint8()
		if status&0x20 != 0 {
			name, _ = e.ReadString(8)
		}
		err = e.Err()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			break
		}
		if int(columnID)+1 >= len(*i.columns) || int(tableID)+1 >= len(i.tables.names) {
			continue
		}
		(*i.columns)[columnID].table = i.tables.names[tableID]
		(*i.columns)[columnID].realName = name
		if status&hiddenColumnInfo != 0 {
			(*i.columns)[columnID].flags |= uint32(hidden)
		}
		if status&keyColumnInfo != 0 {
			(*i.columns)[columnID].flags |= uint32(key)
		}
		if status&writableColumnInfo != 0 {
			(*i.columns)[columnID].flags |= uint32(writable)
		}
	}
	return err
}
read reads a colInfo struct from the wire, and set the relevant options
as we are given pointers to column list and table names list
package tds
import (
	"database/sql/driver"
	"fmt"
	"io"
	"math"
	"reflect"
	"strconv"
	"github.com/thda/tds/binary"
	"errors"
)
type columnFlag int
const (
	hidden   columnFlag = 0x01
	key      columnFlag = 0x02
	writable columnFlag = 0x10
	nullable columnFlag = 0x20
	identity columnFlag = 0x40
)
const (
	wide  = 0x01
	param = 0x02
)
type colType struct {
	msg
	userType   int32
	dataType   dataType
	size       int
	precision  int8
	scale      int8
	locale     string
	table      string
	classID    string
	bufferSize uint32
	options    uint8
	writeOptions  uint8
	encodingProps typeAttribute
	valid         bool  
}
func getType(dataType dataType, size int) colType {
	ti := colType{dataType: dataType, size: size}
	ti.getTypeProperties()
	return ti
}
func (t *colType) getTypeProperties() error {
	props, ok := typeAttributes[t.dataType]
	if !ok {
		return fmt.Errorf("tds: could not find type info for %#x type", byte(t.dataType))
	}
	t.encodingProps = props
	t.valid = true
	t.options = props.options
	if int(t.userType) <= len(concreteTypes) {
		if cProps, ok := typeAttributes[concreteTypes[t.userType]]; ok {
			if props.options&isConcrete == 0 {
				t.encodingProps = cProps
			}
			t.encodingProps.name = cProps.name
		}
	}
	cProps, ok := typeAttributes[t.encodingProps.encodingType]
	if !ok {
		return nil
	}
	t.writeOptions = cProps.options
	return nil
}
func (t colType) write(e *binary.Encoder) (err error) {
	if !t.valid {
		return errors.New("typeInfo.write: missing type properties")
	}
	e.WriteInt32(t.userType)
	e.WriteByte(byte(t.encodingProps.encodingType))
	if t.options&isLong != 0 {
		if t.size != 0 {
			e.WriteUint32(uint32(t.size))
		} else {
			e.WriteUint32(uint32(t.encodingProps.numBytes))
		}
	} else {
		if t.size != 0 {
			e.WriteUint8(uint8(t.size))
		} else {
			e.WriteInt8(t.encodingProps.numBytes)
		}
	}
	if t.options&isLob != 0 {
		e.WriteStringWithLen(16, t.table)
	}
	if t.options&hasPrec != 0 {
		e.WriteInt8(t.precision)
	}
	if t.options&hasScale != 0 {
		e.WriteInt8(t.scale)
	}
	e.WriteInt8(0)
	return err
}
func (t *colType) read(e *binary.Encoder) (err error) {
	t.userType = e.Int32()
	t.dataType = dataType(e.ReadByte())
	if err = t.getTypeProperties(); err != nil {
		return fmt.Errorf("tds: could not get type info:%s", err)
	}
	if t.options&hasLength != 0 || t.options&isNullable != 0 {
		if t.options&isLong != 0 {
			t.size = int(e.Uint32())
		} else {
			t.size = int(e.Uint8())
		}
	}
	if t.options&isLob != 0 {
		t.table, _ = e.ReadString(16)
	}
	if t.options&hasPrec != 0 {
		t.precision = e.Int8()
	}
	if t.options&hasScale != 0 {
		t.scale = e.Int8()
	}
	t.locale, _ = e.ReadString(8)
	return err
}
func (t colType) dataWrite(e *binary.Encoder, data interface{}) error {
	if !t.valid {
		return errors.New("typeInfo.dataWrite: missing type properties")
	}
	if data == nil {
		if t.writeOptions&isLong != 0 && t.writeOptions&isLob == 0 {
			e.WriteUint32(0)
		} else {
			e.WriteUint8(0)
		}
		return nil
	}
	if t.encodingProps.numBytes != 0 && t.writeOptions&isNullable != 0 {
		e.WriteInt8(t.encodingProps.numBytes)
	}
	return t.encodingProps.writer(e, data, t)
}
func (t *colType) dataRead(e *binary.Encoder) (interface{}, error) {
	if !t.valid {
		return nil, errors.New("tds: missing type properties")
	}
	if t.options&hasLength != 0 || t.options&isNullable != 0 {
		if t.options&isLong != 0 && t.options&isLob == 0 {
			t.bufferSize = e.Uint32()
		} else {
			t.bufferSize = uint32(e.Uint8())
		}
		if err := e.Err(); err != nil {
			return nil, fmt.Errorf("tds: error while getting type length: %s", err)
		}
		if t.bufferSize == 0 {
			return nil, nil
		}
	}
	data, err := t.encodingProps.reader(e, *t)
	return data, err
}
func (t colType) scanType() reflect.Type {
	if !t.valid {
		return nil
	}
	return t.encodingProps.scanType
}
func (t colType) databaseTypeName() string {
	if !t.valid {
		return ""
	}
	return t.encodingProps.name
}
func (t colType) length() (int64, bool) {
	if !t.valid ||
		t.options&hasPrec != 0 || t.options&hasScale != 0 {
		return 0, false
	}
	if t.options&isLong != 0 {
		if t.dataType == longCharType {
			return int64(t.size / 2), true
		}
		if t.encodingProps.scanType == reflect.TypeOf("") {
			return int64(t.size), true
		}
		return math.MaxInt32, true
	}
	return int64(t.size), t.options&hasLength != 0
}
func (t colType) precisionScale() (int64, int64, bool) {
	if !t.valid {
		return 0, 0, false
	}
	if t.options&hasPrec != 0 &&
		t.options&hasScale != 0 {
		return int64(t.precision), int64(t.scale), true
	}
	return 0, 0, false
}
func (t colType) String() string {
	out := fmt.Sprintf("user type: %3d (%#x)", t.userType, int(t.userType))
	out += fmt.Sprintf("\ndata type: %3d (%#x) %s", t.dataType, int(t.dataType), t.dataType)
	if !t.valid {
		return out + "\ncould not get encoding properties"
	}
	if t.options&hasLength != 0 || t.options&isNullable != 0 {
		out += "\nsize: " + strconv.Itoa(int(t.size))
	}
	if t.options&isLob != 0 {
		out += "\ntable name: " + t.table
	}
	if t.options&hasPrec != 0 {
		out += "\nprecision: " + strconv.Itoa(int(t.precision))
	}
	if t.options&hasScale != 0 {
		out += "\nscale: " + strconv.Itoa(int(t.scale))
	}
	return out
}
type colFmt struct {
	msg
	name string
	flags uint32
	colType
	realName string
	DB       string
	owner    string
	classID  string
	cmpOperator uint8  
	cmpOperand  uint8  
}
func (f colFmt) Write(e *binary.Encoder, flags int) error {
	e.WriteStringWithLen(8, f.name)
	if flags&wide != 0 && flags&param == 0 {
		e.WriteStringWithLen(8, f.DB)
		e.WriteStringWithLen(8, f.owner)
		e.WriteStringWithLen(8, f.table)
	}
	if flags&wide != 0 {
		e.WriteUint32(uint32(f.flags))
	} else {
		e.WriteUint8(uint8(f.flags))
	}
	if err := f.colType.write(e); err != nil {
		return err
	}
	if f.dataType == extendedType {
		e.WriteStringWithLen(16, f.classID)
	}
	err := e.Err()
	return err
}
func (f *colFmt) Read(e *binary.Encoder, flags int) error {
	f.name, _ = e.ReadString(8)
	if flags&wide != 0 && flags&param == 0 {
		f.DB, _ = e.ReadString(8)
		f.owner, _ = e.ReadString(8)
		f.table, _ = e.ReadString(8)
		f.realName, _ = e.ReadString(8)
	}
	if flags&wide != 0 {
		f.flags = e.Uint32()
	} else {
		f.flags = uint32(e.Uint8())
	}
	if err := f.colType.read(e); err != nil {
		return err
	}
	if f.dataType == extendedType {
		f.classID, _ = e.ReadString(16)
	}
	err := e.Err()
	return err
}
func (f colFmt) String() string {
	out := "name: " + f.name
	if f.DB != "" {
		out += "\ndb: " + f.DB
	}
	if f.owner != "" {
		out += "\nowner: " + f.owner
	}
	if f.table != "" {
		out += "\ntable: " + f.table
	}
	if f.realName != "" {
		out += "\nrealName: " + f.realName
	}
	out += "\n" + f.colType.String()
	flags := ""
	for _, flag := range [...]columnFlag{hidden, key, writable, nullable, identity} {
		if f.flags&uint32(flag) != 0 {
			if flags == "" {
				flags = fmt.Sprint(flag)
			} else {
				flags += "," + fmt.Sprint(flag)
			}
		}
	}
	if flags == "" {
		flags = "none"
	}
	out += "\nflags: " + fmt.Sprintf("%#x", byte(f.flags)) + " (" + flags + ")"
	return out
}
func (f *colFmt) parameterConverter() driver.ValueConverter {
	if !f.valid || f.encodingProps.converter == nil {
		return driver.DefaultParameterConverter
	}
	return f.encodingProps.converter(f).(driver.ValueConverter)
}
type columns struct {
	msg
	flags int
	fmts  []colFmt
}
func (c columns) Write(e *binary.Encoder) (err error) {
	var colErr error
	if c.flags&wide != 0 && c.flags&param == 0 {
		e.WriteUint32(uint32(len(c.fmts)))
	} else {
		e.WriteUint16(uint16(len(c.fmts)))
	}
	for i := range c.fmts[:] {
		if colErr = c.fmts[i].Write(e, c.flags); colErr != nil {
			err = colErr
		}
	}
	if colErr = e.Err(); colErr != nil {
		return colErr
	}
	return err
}
func (c *columns) Read(e *binary.Encoder) (err error) {
	var colErr error
	colCnt := int(e.Int16())
	c.fmts = make([]colFmt, colCnt)
	for i := range c.fmts[:] {
		if colErr = c.fmts[i].Read(e, c.flags); colErr != nil {
			err = colErr
		}
	}
	if colErr = e.Err(); colErr != nil {
		return colErr
	}
	return err
}
func (c columns) String() (out string) {
	for i := range c.fmts[:] {
		out += "Column " + strconv.Itoa(i) + ":\n" + c.fmts[i].String() + "\n"
	}
	return out
}
type row struct {
	msg
	columns []colFmt
	data    []driver.Value
}
func (r row) Write(e *binary.Encoder) (err error) {
	var rowErr error
	if len(r.data) != len(r.columns) {
		return errors.New("row.write: mismatch between count in row and column info")
	}
	for i, t := range r.columns[:] {
		if rowErr = t.dataWrite(e, r.data[i]); rowErr != nil {
			err = rowErr
		}
	}
	if rowErr = e.Err(); rowErr != nil {
		return rowErr
	}
	return err
}
func (r *row) Read(e *binary.Encoder) (err error) {
	var rowErr error
	if r.columns != nil && len(r.columns) <= 0 {
		return errors.New("row.read: no type info for row or count mismatch")
	}
	if len(r.data) != len(r.columns) {
		r.data = make([]driver.Value, len(r.columns))
	}
	for i, t := range r.columns[:] {
		r.data[i], rowErr = t.dataRead(e)
		if rowErr != nil {
			err = rowErr
		}
	}
	if rowErr = e.Err(); rowErr != nil {
		return rowErr
	}
	return err
}
const (
	cntToken = 0x4B + iota
	cntUToken
	sumToken
	sumUToken
	avgToken
	avgUToken
	minToken
	maxToken
	countBigToken = 0x61
)
var cmpLabels = map[byte]string{
	cntToken:      "count",
	cntUToken:     "count",
	sumToken:      "sum",
	sumUToken:     "sum",
	avgToken:      "avg",
	avgUToken:     "avg",
	minToken:      "min",
	maxToken:      "max",
	countBigToken: "count_big",
}
type cmpColumnFmt colFmt
func (f *cmpColumnFmt) Read(e *binary.Encoder) error {
	f.cmpOperator = e.Uint8()
	f.cmpOperand = e.Uint8()
	if err := f.colType.read(e); err != nil {
		return err
	}
	err := e.Err()
	return err
}
type cmpColumns struct {
	msg
	id uint16
	byColumns []int
	fmts []colFmt
	name string
}
func (c *cmpColumns) Read(e *binary.Encoder) error {
	c.id = e.Uint16()
	colCnt := e.Uint8()
	c.fmts = make([]colFmt, colCnt)
	for i := range c.fmts[:] {
		cmpColumnFmt := cmpColumnFmt{msg: c.msg}
		cmpColumnFmt.Read(e)
		c.fmts[i] = colFmt(cmpColumnFmt)
	}
	byColCnt := e.Uint8()
	c.byColumns = make([]int, byColCnt)
	for i := uint8(0); i < byColCnt; i++ {
		c.byColumns[i] = int(e.Uint8())
	}
	err := e.Err()
	return err
}
type cmpRow struct {
	msg
	id uint16
	infos map[uint16]cmpColumns
	data  []driver.Value
}
func (r *cmpRow) Read(e *binary.Encoder) error {
	r.id = e.Uint16()
	var err error
	var cols cmpColumns
	var ok bool
	if cols, ok = r.infos[r.id]; !ok {
		return errors.New("could not get info for computed row")
	}
	if len(cols.fmts) <= 0 {
		return errors.New("no type info for row or count mismatch")
	}
	if len(r.data) != len(cols.fmts) {
		r.data = make([]driver.Value, len(cols.fmts))
	}
	for i, t := range cols.fmts[:] {
		r.data[i], err = t.dataRead(e)
	}
	return err
}
type cmpName struct {
	msg
	computeID uint16
	infos map[uint16]cmpColumns
}
func (r *cmpName) Read(e *binary.Encoder) error {
	r.computeID = e.Uint16()
	var cols cmpColumns
	var ok bool
	if cols, ok = r.infos[r.computeID]; !ok {
		return errors.New("could not get info for computed row")
	}
	cols.name, _ = e.ReadString(8)
	r.infos[r.computeID] = cols
	err := e.Err()
	return err
}
const (
	hiddenColumnInfo   = 0x10
	keyColumnInfo      = 0x8
	writableColumnInfo = 0x4
)
type tableName struct {
	msg
	names []string
}
func (t *tableName) Read(e *binary.Encoder) (err error) {
	var name string
	for {
		name, _ = e.ReadString(8)
		err = e.Err()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			break
		}
		t.names = append(t.names, name)
	}
	return err
}
type colInfo struct {
	msg
	columns *[]colFmt
	tables  *tableName
}