From f7c02e6802cbd628009de63cf27a80ce3ce0ce39 Mon Sep 17 00:00:00 2001 From: Tim Ebbinghaus Date: Mon, 23 Jan 2023 08:53:30 +0100 Subject: [PATCH 1/2] task: adds pre tds 7.1 compatibility --- tds.go | 4 +++ token.go | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/tds.go b/tds.go index e0ce5526..dc989dfe 100644 --- a/tds.go +++ b/tds.go @@ -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 { + // 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 diff --git a/token.go b/token.go index 76d4e025..2e28c2f2 100644 --- a/token.go +++ b/token.go @@ -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 @@ -586,6 +598,24 @@ func parseColMetadata72(r *tdsBuffer) (columns []columnStruct) { } return columns } +func parseColMetadata71(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(r) + column.ColName = r.BVarChar() + } + return columns +} // http://msdn.microsoft.com/en-us/library/dd357254.aspx func parseRow(r *tdsBuffer, columns []columnStruct, row []interface{}) { @@ -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() @@ -635,6 +677,18 @@ 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) { @@ -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 { @@ -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) @@ -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.buf) + } else { + columns = parseColMetadata72(sess.buf) + } ch <- columns colsReceived = true if outs.msgq != nil { @@ -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)) } @@ -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)) } From 3c2d1b551a876eeeabc6fbb5262db1ab22c8db0f Mon Sep 17 00:00:00 2001 From: Tim Ebbinghaus Date: Tue, 24 Jan 2023 09:05:39 +0100 Subject: [PATCH 2/2] task: adds version-sensitive column-metadata-parsing --- token.go | 18 +++++++++--------- types.go | 28 ++++++++++++++++++---------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/token.go b/token.go index 2e28c2f2..d45b072a 100644 --- a/token.go +++ b/token.go @@ -580,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 @@ -593,12 +593,12 @@ 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(r *tdsBuffer) (columns []columnStruct) { +func parseColMetadata71(sess *tdsSession, r *tdsBuffer) (columns []columnStruct) { count := r.uint16() if count == 0xffff { // no metadata is sent @@ -611,7 +611,7 @@ func parseColMetadata71(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 @@ -691,7 +691,7 @@ func parseInfo71(r *tdsBuffer) (res Error) { } // 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 @@ -707,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 } @@ -846,9 +846,9 @@ func processSingleResponse(ctx context.Context, sess *tdsSession, ch chan tokenS } case tokenColMetadata: if sess.loginAck.TDSVersion <= verTDS71rev1 { - columns = parseColMetadata71(sess.buf) + columns = parseColMetadata71(sess, sess.buf) } else { - columns = parseColMetadata72(sess.buf) + columns = parseColMetadata72(sess, sess.buf) } ch <- columns colsReceived = true @@ -900,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 { diff --git a/types.go b/types.go index 3b4760e3..a275a9eb 100644 --- a/types.go +++ b/types.go @@ -119,7 +119,7 @@ type xmlInfo struct { XmlSchemaCollection string } -func readTypeInfo(r *tdsBuffer) (res typeInfo) { +func readTypeInfo(sess *tdsSession, r *tdsBuffer) (res typeInfo) { res.TypeId = r.byte() switch res.TypeId { case typeNull, typeInt1, typeBit, typeInt2, typeInt4, typeDateTim4, @@ -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 } @@ -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 @@ -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: