4e7ce6f42b
* Update deps * Change azure dep to match plugin
168 lines
4.3 KiB
Go
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
|
|
}
|