open-vault/vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go
Jeff Mitchell 4e7ce6f42b
Update deps (#6580)
* Update deps

* Change azure dep to match plugin
2019-04-12 11:51:37 -04:00

168 lines
4.3 KiB
Go

// +build go1.9
package mssql
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"reflect"
"time"
)
var (
ErrorEmptyTVPName = errors.New("TVPTypeName must not be empty")
ErrorTVPTypeSlice = errors.New("TVPType must be slice type")
ErrorTVPTypeSliceIsEmpty = errors.New("TVPType mustn't be null value")
)
//TVPType is driver type, which allows supporting Table Valued Parameters (TVP) in SQL Server
type TVPType struct {
//TVP param name, mustn't be default value
TVPTypeName string
//TVP scheme name
TVPScheme string
//TVP Value. Param must be the slice, mustn't be nil
TVPValue interface{}
}
func (tvp TVPType) check() error {
if len(tvp.TVPTypeName) == 0 {
return ErrorEmptyTVPName
}
valueOf := reflect.ValueOf(tvp.TVPValue)
if valueOf.Kind() != reflect.Slice {
return ErrorTVPTypeSlice
}
if valueOf.IsNil() {
return ErrorTVPTypeSliceIsEmpty
}
if reflect.TypeOf(tvp.TVPValue).Elem().Kind() != reflect.Struct {
return ErrorTVPTypeSlice
}
return nil
}
func (tvp TVPType) encode() ([]byte, error) {
columnStr, err := tvp.columnTypes()
if err != nil {
return nil, err
}
preparedBuffer := make([]byte, 0, 20+(10*len(columnStr)))
buf := bytes.NewBuffer(preparedBuffer)
err = writeBVarChar(buf, "")
if err != nil {
return nil, err
}
writeBVarChar(buf, tvp.TVPScheme)
writeBVarChar(buf, tvp.TVPTypeName)
binary.Write(buf, binary.LittleEndian, uint16(len(columnStr)))
for i, column := range columnStr {
binary.Write(buf, binary.LittleEndian, uint32(column.UserType))
binary.Write(buf, binary.LittleEndian, uint16(column.Flags))
writeTypeInfo(buf, &columnStr[i].ti)
writeBVarChar(buf, "")
}
buf.WriteByte(_TVP_END_TOKEN)
conn := new(Conn)
conn.sess = new(tdsSession)
conn.sess.loginAck = loginAckStruct{TDSVersion: verTDS73}
stmt := &Stmt{
c: conn,
}
val := reflect.ValueOf(tvp.TVPValue)
for i := 0; i < val.Len(); i++ {
refStr := reflect.ValueOf(val.Index(i).Interface())
buf.WriteByte(_TVP_ROW_TOKEN)
for j := 0; j < refStr.NumField(); j++ {
field := refStr.Field(j)
tvpVal := field.Interface()
valOf := reflect.ValueOf(tvpVal)
elemKind := field.Kind()
if elemKind == reflect.Ptr && valOf.IsNil() {
switch tvpVal.(type) {
case *bool, *time.Time, *int8, *int16, *int32, *int64, *float32, *float64:
binary.Write(buf, binary.LittleEndian, uint8(0))
continue
default:
binary.Write(buf, binary.LittleEndian, uint64(_PLP_NULL))
continue
}
}
if elemKind == reflect.Slice && valOf.IsNil() {
binary.Write(buf, binary.LittleEndian, uint64(_PLP_NULL))
continue
}
cval, err := convertInputParameter(tvpVal)
if err != nil {
return nil, fmt.Errorf("failed to convert tvp parameter row col: %s", err)
}
param, err := stmt.makeParam(cval)
if err != nil {
return nil, fmt.Errorf("failed to make tvp parameter row col: %s", err)
}
columnStr[j].ti.Writer(buf, param.ti, param.buffer)
}
}
buf.WriteByte(_TVP_END_TOKEN)
return buf.Bytes(), nil
}
func (tvp TVPType) columnTypes() ([]columnStruct, error) {
val := reflect.ValueOf(tvp.TVPValue)
var firstRow interface{}
if val.Len() != 0 {
firstRow = val.Index(0).Interface()
} else {
firstRow = reflect.New(reflect.TypeOf(tvp.TVPValue).Elem()).Elem().Interface()
}
tvpRow := reflect.TypeOf(firstRow)
columnCount := tvpRow.NumField()
defaultValues := make([]interface{}, 0, columnCount)
for i := 0; i < columnCount; i++ {
typeField := tvpRow.Field(i).Type
if typeField.Kind() == reflect.Ptr {
v := reflect.New(typeField.Elem())
defaultValues = append(defaultValues, v.Interface())
continue
}
defaultValues = append(defaultValues, reflect.Zero(typeField).Interface())
}
conn := new(Conn)
conn.sess = new(tdsSession)
conn.sess.loginAck = loginAckStruct{TDSVersion: verTDS73}
stmt := &Stmt{
c: conn,
}
columnConfiguration := make([]columnStruct, 0, columnCount)
for index, val := range defaultValues {
cval, err := convertInputParameter(val)
if err != nil {
return nil, fmt.Errorf("failed to convert tvp parameter row %d col %d: %s", index, val, err)
}
param, err := stmt.makeParam(cval)
if err != nil {
return nil, err
}
column := columnStruct{
ti: param.ti,
}
switch param.ti.TypeId {
case typeNVarChar, typeBigVarBin:
column.ti.Size = 0
}
columnConfiguration = append(columnConfiguration, column)
}
return columnConfiguration, nil
}