Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds pre tds 7.1 compatibility #83

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions tds.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ func readPreloginOption(buffer []byte, offset int) (*preloginOption, error) {
rec_type := buffer[offset]
if rec_type == preloginTERMINATOR {
return &preloginOption{token: rec_type}, nil
} else if rec_type == preloginTHREADID {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rec_type == preloginTHREADID {

does this check need to be protected by the TDS version check too?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868 there seems to be no constraint on the TDS-Version.

As I understand the documentation, the parameter is used for the client to communicate its thread-id for debugging-purposes. If sent by the server, it should be empty in any case.

// This value SHOULD be empty when being sent from the server to the client.
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868
return &preloginOption{token: rec_type}, nil
}

// check if prelogin option exists in buffer
Expand Down
104 changes: 91 additions & 13 deletions token.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,20 +408,32 @@ func parseOrder(r *tdsBuffer) (res orderStruct) {
}

// https://msdn.microsoft.com/en-us/library/dd340421.aspx
func parseDone(r *tdsBuffer) (res doneStruct) {
func parseDone72(r *tdsBuffer) (res doneStruct) {
res.Status = r.uint16()
res.CurCmd = r.uint16()
res.RowCount = r.uint64()
return res
}
func parseDone71(r *tdsBuffer) (res doneStruct) {
res.Status = r.uint16()
res.CurCmd = r.uint16()
res.RowCount = uint64(r.uint32())
return res
}

// https://msdn.microsoft.com/en-us/library/dd340553.aspx
func parseDoneInProc(r *tdsBuffer) (res doneInProcStruct) {
func parseDoneInProc72(r *tdsBuffer) (res doneInProcStruct) {
res.Status = r.uint16()
res.CurCmd = r.uint16()
res.RowCount = r.uint64()
return res
}
func parseDoneInProc71(r *tdsBuffer) (res doneInProcStruct) {
res.Status = r.uint16()
res.CurCmd = r.uint16()
res.RowCount = uint64(r.uint32())
return res
}

type sspiMsg []byte

Expand Down Expand Up @@ -568,7 +580,7 @@ func parseFeatureExtAck(r *tdsBuffer) map[byte]interface{} {
}

// http://msdn.microsoft.com/en-us/library/dd357363.aspx
func parseColMetadata72(r *tdsBuffer) (columns []columnStruct) {
func parseColMetadata72(sess *tdsSession, r *tdsBuffer) (columns []columnStruct) {
count := r.uint16()
if count == 0xffff {
// no metadata is sent
Expand All @@ -581,7 +593,25 @@ func parseColMetadata72(r *tdsBuffer) (columns []columnStruct) {
column.Flags = r.uint16()

// parsing TYPE_INFO structure
column.ti = readTypeInfo(r)
column.ti = readTypeInfo(sess, r)
column.ColName = r.BVarChar()
}
return columns
}
func parseColMetadata71(sess *tdsSession, r *tdsBuffer) (columns []columnStruct) {
count := r.uint16()
if count == 0xffff {
// no metadata is sent
return nil
}
columns = make([]columnStruct, count)
for i := range columns {
column := &columns[i]
column.UserType = uint32(r.uint16())
column.Flags = r.uint16()

// parsing TYPE_INFO structure
column.ti = readTypeInfo(sess, r)
column.ColName = r.BVarChar()
}
return columns
Expand Down Expand Up @@ -621,9 +651,21 @@ func parseError72(r *tdsBuffer) (res Error) {
res.LineNo = r.int32()
return
}
func parseError71(r *tdsBuffer) (res Error) {
length := r.uint16()
_ = length // ignore length
res.Number = r.int32()
res.State = r.byte()
res.Class = r.byte()
res.Message = r.UsVarChar()
res.ServerName = r.BVarChar()
res.ProcName = r.BVarChar()
res.LineNo = int32(r.uint16())
return
}

// http://msdn.microsoft.com/en-us/library/dd304156.aspx
func parseInfo(r *tdsBuffer) (res Error) {
func parseInfo72(r *tdsBuffer) (res Error) {
length := r.uint16()
_ = length // ignore length
res.Number = r.int32()
Expand All @@ -635,9 +677,21 @@ func parseInfo(r *tdsBuffer) (res Error) {
res.LineNo = r.int32()
return
}
func parseInfo71(r *tdsBuffer) (res Error) {
length := r.uint16()
_ = length // ignore length
res.Number = r.int32()
res.State = r.byte()
res.Class = r.byte()
res.Message = r.UsVarChar()
res.ServerName = r.BVarChar()
res.ProcName = r.BVarChar()
res.LineNo = int32(r.uint16())
return
}

// https://msdn.microsoft.com/en-us/library/dd303881.aspx
func parseReturnValue(r *tdsBuffer) (nv namedValue) {
func parseReturnValue(sess *tdsSession, r *tdsBuffer) (nv namedValue) {
/*
ParamOrdinal
ParamName
Expand All @@ -653,7 +707,7 @@ func parseReturnValue(r *tdsBuffer) (nv namedValue) {
r.byte()
r.uint32() // UserType (uint16 prior to 7.2)
r.uint16()
ti := readTypeInfo(r)
ti := readTypeInfo(sess, r)
nv.Value = ti.Reader(&ti, r)
return
}
Expand Down Expand Up @@ -714,7 +768,12 @@ func processSingleResponse(ctx context.Context, sess *tdsSession, ch chan tokenS
order := parseOrder(sess.buf)
ch <- order
case tokenDoneInProc:
done := parseDoneInProc(sess.buf)
var done doneInProcStruct
if sess.loginAck.TDSVersion <= verTDS71rev1 {
done = parseDoneInProc71(sess.buf)
} else {
done = parseDoneInProc72(sess.buf)
}

ch <- done
if done.Status&doneCount != 0 {
Expand Down Expand Up @@ -742,7 +801,12 @@ func processSingleResponse(ctx context.Context, sess *tdsSession, ch chan tokenS
return
}
case tokenDone, tokenDoneProc:
done := parseDone(sess.buf)
var done doneStruct
if sess.loginAck.TDSVersion <= verTDS71rev1 {
done = parseDone71(sess.buf)
} else {
done = parseDone72(sess.buf)
}
done.errors = errs
if outs.msgq != nil {
errs = make([]Error, 0, 5)
Expand Down Expand Up @@ -781,7 +845,11 @@ func processSingleResponse(ctx context.Context, sess *tdsSession, ch chan tokenS
return
}
case tokenColMetadata:
columns = parseColMetadata72(sess.buf)
if sess.loginAck.TDSVersion <= verTDS71rev1 {
columns = parseColMetadata71(sess, sess.buf)
} else {
columns = parseColMetadata72(sess, sess.buf)
}
ch <- columns
colsReceived = true
if outs.msgq != nil {
Expand All @@ -799,7 +867,12 @@ func processSingleResponse(ctx context.Context, sess *tdsSession, ch chan tokenS
case tokenEnvChange:
processEnvChg(ctx, sess)
case tokenError:
err := parseError72(sess.buf)
var err Error
if sess.loginAck.TDSVersion <= verTDS71rev1 {
err = parseError71(sess.buf)
} else {
err = parseError72(sess.buf)
}
if sess.logFlags&logDebug != 0 {
sess.logger.Log(ctx, msdsn.LogDebug, fmt.Sprintf("got ERROR %d %s", err.Number, err.Message))
}
Expand All @@ -811,7 +884,12 @@ func processSingleResponse(ctx context.Context, sess *tdsSession, ch chan tokenS
_ = sqlexp.ReturnMessageEnqueue(ctx, outs.msgq, sqlexp.MsgError{Error: err})
}
case tokenInfo:
info := parseInfo(sess.buf)
var info Error
if sess.loginAck.TDSVersion <= verTDS71rev1 {
info = parseInfo71(sess.buf)
} else {
info = parseInfo72(sess.buf)
}
if sess.logFlags&logDebug != 0 {
sess.logger.Log(ctx, msdsn.LogDebug, fmt.Sprintf("got INFO %d %s", info.Number, info.Message))
}
Expand All @@ -822,7 +900,7 @@ func processSingleResponse(ctx context.Context, sess *tdsSession, ch chan tokenS
_ = sqlexp.ReturnMessageEnqueue(ctx, outs.msgq, sqlexp.MsgNotice{Message: info})
}
case tokenReturnValue:
nv := parseReturnValue(sess.buf)
nv := parseReturnValue(sess, sess.buf)
if len(nv.Name) > 0 {
name := nv.Name[1:] // Remove the leading "@".
if ov, has := outs.params[name]; has {
Expand Down
28 changes: 18 additions & 10 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ type xmlInfo struct {
XmlSchemaCollection string
}

func readTypeInfo(r *tdsBuffer) (res typeInfo) {
func readTypeInfo(sess *tdsSession, r *tdsBuffer) (res typeInfo) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the code-style. I did not want to copy the whole readTypeInfo func for v7.1 compatibility as the version-info is needed in readVarLen

res.TypeId = r.byte()
switch res.TypeId {
case typeNull, typeInt1, typeBit, typeInt2, typeInt4, typeDateTim4,
Expand All @@ -140,7 +140,7 @@ func readTypeInfo(r *tdsBuffer) (res typeInfo) {
res.Reader = readFixedType
res.Buffer = make([]byte, res.Size)
default: // all others are VARLENTYPE
readVarLen(&res, r)
readVarLen(sess, &res, r)
}
return
}
Expand Down Expand Up @@ -719,7 +719,7 @@ func writePLPType(w io.Writer, ti typeInfo, buf []byte) (err error) {
}
}

func readVarLen(ti *typeInfo, r *tdsBuffer) {
func readVarLen(sess *tdsSession, ti *typeInfo, r *tdsBuffer) {
switch ti.TypeId {
case typeDateN:
ti.Size = 3
Expand Down Expand Up @@ -798,17 +798,25 @@ func readVarLen(ti *typeInfo, r *tdsBuffer) {
switch ti.TypeId {
case typeText, typeNText:
ti.Collation = readCollation(r)
// ignore tablenames
numparts := int(r.byte())
for i := 0; i < numparts; i++ {
// only present in TDS > 7.2
if sess.loginAck.TDSVersion >= verTDS72 {
// ignore tablenames
numparts := int(r.byte())
for i := 0; i < numparts; i++ {
r.UsVarChar()
}
} else {
r.UsVarChar()
}
ti.Reader = readLongLenType
case typeImage:
// ignore tablenames
numparts := int(r.byte())
for i := 0; i < numparts; i++ {
r.UsVarChar()
// only present in TDS >= 7.2
if sess.loginAck.TDSVersion >= verTDS72 {
// ignore tablenames
numparts := int(r.byte())
for i := 0; i < numparts; i++ {
r.UsVarChar()
}
}
ti.Reader = readLongLenType
case typeVariant:
Expand Down