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
}
String implements the stringer interface for a column info
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
}