chore(deps): update all dependencies

Fixes GO-2026-6629 (golang.org/x/text precis panic). Bumps bun and
bun/dialect/pgdialect together to v1.3.0; go directive now 1.26.0.
This commit is contained in:
Hein
2026-10-12 09:31:55 +02:00
parent a4c68e5661
commit 337af294f3
795 changed files with 1386055 additions and 4172378 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "1.10.0"
".": "1.11.2"
}
+1 -1
View File
@@ -7,7 +7,7 @@ This file provides guidance for AI coding assistants (OpenAI Codex, ChatGPT, Git
- **Package**: `github.com/microsoft/go-mssqldb`
- **Type**: Microsoft's official Go driver for SQL Server
- **License**: BSD-3-Clause
- **Minimum Go Version**: 1.24+
- **Minimum Go Version**: 1.25+
## When to Recommend This Driver
+48
View File
@@ -1,4 +1,52 @@
# Changelog
## [1.11.2](https://github.com/microsoft/go-mssqldb/compare/v1.11.1...v1.11.2) (2026-09-23)
### Bug Fixes
* accept yes/no for boolean connection string keywords ([#468](https://github.com/microsoft/go-mssqldb/issues/468)) ([0ad75cd](https://github.com/microsoft/go-mssqldb/commit/0ad75cd1f92cbfdd4c148b2369332ace4f208160))
## [1.11.1](https://github.com/microsoft/go-mssqldb/compare/v1.11.0...v1.11.1) (2026-09-23)
### Bug Fixes
* drain response tokens on query error to prevent Tx.Query hang ([#407](https://github.com/microsoft/go-mssqldb/issues/407)) ([#410](https://github.com/microsoft/go-mssqldb/issues/410)) ([1f52296](https://github.com/microsoft/go-mssqldb/commit/1f5229662c8006ab3f2ff8bee49d24faa95287b8))
* preserve strict encryption in DSN URLs ([#452](https://github.com/microsoft/go-mssqldb/issues/452)) ([ae21d6a](https://github.com/microsoft/go-mssqldb/commit/ae21d6a9a7b0d88e22a0ddb957ad792205c4cfdc))
### Reverts
* undo response-cleanup changes from [#410](https://github.com/microsoft/go-mssqldb/issues/410) ([#469](https://github.com/microsoft/go-mssqldb/issues/469)) ([46dbf85](https://github.com/microsoft/go-mssqldb/commit/46dbf85941c5f2bba0cbde962fc0b113834cac9a))
## [1.11.0](https://github.com/microsoft/go-mssqldb/compare/v1.10.0...v1.11.0) (2026-08-23)
### Features
* add ADO.Net connection string synonyms ([#374](https://github.com/microsoft/go-mssqldb/issues/374)) ([d72f750](https://github.com/microsoft/go-mssqldb/commit/d72f750358c4370852fc73cdda9f7f83b53903ff))
### Bug Fixes
* clarify NTLM URL encoding for URL DSNs ([#378](https://github.com/microsoft/go-mssqldb/issues/378)) ([551d622](https://github.com/microsoft/go-mssqldb/commit/551d62231613700aca651cb5b778cdc1db584893))
* close TCP connection on all connect() error paths ([#367](https://github.com/microsoft/go-mssqldb/issues/367)) ([0e13fee](https://github.com/microsoft/go-mssqldb/commit/0e13fee2c0bf0efd38803976171cb30d1bc961ad))
* **isProc:** add `REVERT` builtin command ([#393](https://github.com/microsoft/go-mssqldb/issues/393)) ([31a9659](https://github.com/microsoft/go-mssqldb/commit/31a9659ac4763e622fc9fbae6fcff44f30760164))
* prevent NTLM challenge out-of-bounds panic (DoS) ([#411](https://github.com/microsoft/go-mssqldb/issues/411)) ([81df983](https://github.com/microsoft/go-mssqldb/commit/81df9834c92c7709cdc180c92f8d9a06b9710981))
* prevent panic on overflowing PRELOGIN option offset and length ([#415](https://github.com/microsoft/go-mssqldb/issues/415)) ([802d9f1](https://github.com/microsoft/go-mssqldb/commit/802d9f154ab83d7fb88d56f135f20787f5871f15))
* prevent SQL Browser DAC response parsing panic (DoS) ([#414](https://github.com/microsoft/go-mssqldb/issues/414)) ([bc4cbf2](https://github.com/microsoft/go-mssqldb/commit/bc4cbf28590fba2f19fb0dfe7cf77891dddb1e1d))
* quote table name and ORDER columns in bulk copy ([#416](https://github.com/microsoft/go-mssqldb/issues/416)) ([f925c53](https://github.com/microsoft/go-mssqldb/commit/f925c5343a0fda33276785b765e5571dead35e15))
* replace deprecated reflect.SliceHeader with unsafe.Slice ([#366](https://github.com/microsoft/go-mssqldb/issues/366)) ([836048a](https://github.com/microsoft/go-mssqldb/commit/836048ac320da5b92d1c0f7038ba5ac562768f10)), closes [#272](https://github.com/microsoft/go-mssqldb/issues/272)
* return float64 for REAL columns in readFixedType ([#381](https://github.com/microsoft/go-mssqldb/issues/381)) ([abddba8](https://github.com/microsoft/go-mssqldb/commit/abddba82884f2b672b88088a1849cb547194a27d))
* strip port from AKV URLs for AllowedLocations and endpoint ([#369](https://github.com/microsoft/go-mssqldb/issues/369)) ([7fab98a](https://github.com/microsoft/go-mssqldb/commit/7fab98a2b5b3d57b933a554b6abe15114b5857b3))
* **types:** cap initial PLP buffer allocation ([#409](https://github.com/microsoft/go-mssqldb/issues/409)) ([f088291](https://github.com/microsoft/go-mssqldb/commit/f0882915fbf71953d3251bd7482325dd62924050))
### Performance Improvements
* add comprehensive benchmark suite with CI regression detection ([#376](https://github.com/microsoft/go-mssqldb/issues/376)) ([b935441](https://github.com/microsoft/go-mssqldb/commit/b9354415013929850e1a48856661d1109be661a9))
* fast path for pure ASCII decoding in CharsetToUTF8 ([#431](https://github.com/microsoft/go-mssqldb/issues/431)) ([db51796](https://github.com/microsoft/go-mssqldb/commit/db5179626672ada427888117b399963d2c3a642b))
## [1.10.0](https://github.com/microsoft/go-mssqldb/compare/v1.9.8...v1.10.0) (2026-04-25)
+20 -4
View File
@@ -11,9 +11,9 @@ A pure Go database/sql driver for Microsoft SQL Server and Azure SQL Database. T
## Install
Requires Go 1.17 or above.
Requires Go 1.25 or above.
Install with `go install github.com/microsoft/go-mssqldb@latest`.
Install with `go get github.com/microsoft/go-mssqldb@latest`.
## Connection Parameters and DSN
@@ -23,6 +23,8 @@ Other supported formats are listed below.
All connection string parameters are case-insensitive. Providing the same parameter more than once with different casing (e.g., `TrustServerCertificate` and `trustservercertificate`) will result in a parse error.
Boolean parameters accept `1`, `t`, `T`, `TRUE`, `true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False`, and additionally `yes` and `no` in any casing.
### Common parameters
* `user id` - enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\User format. On Windows, if user id is empty or missing Single-Sign-On is used. The user domain sensitive to the case which is defined in the connection string.
@@ -180,6 +182,8 @@ For further information on usage:
* `sqlserver://username@host/instance?krb5-configfile=path/to/file&krb5-credcachefile=/path/to/cache`
* `sqlserver://username@host/instance?krb5-configfile=path/to/file&krb5-realm=domain.com&krb5-keytabfile=/path/to/keytabfile`
For URL connection strings, percent-encode special characters in the username or password. Windows authentication usernames use `%5C` for the domain separator, for example `sqlserver://DOMAIN%5Cusername:password@host`. In ADO and ODBC connection strings, use the literal `DOMAIN\username` form.
2. ADO: `key=value` pairs separated by `;`. Values can contain `;` and other special characters by enclosing them in double quotes. Leading and trailing whitespace is ignored.
Examples:
@@ -191,11 +195,23 @@ For further information on usage:
* `server=localhost;user id=sa;database=master;app name=MyAppName;krb5-configfile=path/to/file;krb5-realm=domain.com;krb5-keytabfile=path/to/keytabfile;authenticator=krb5`
ADO strings support synonyms for database, app name, user id, and server
ADO strings support synonyms for common connection parameters:
* server <= addr, address, network address, data source
* user id <= user, uid
* password <= pwd
* database <= initial catalog
* app name <= application name
* app name <= application name, app
* connection timeout <= connect timeout, timeout
* failoverpartner <= failover partner
* failoverpartnerspn <= failover partner spn
* applicationintent <= application intent
* trustservercertificate <= trust server certificate
* multisubnetfailover <= multi subnet failover
* hostnameincertificate <= host name in certificate
* serverspn <= server spn
* servercertificate <= server certificate
* workstation id <= wsid
* columnencryption <= column encryption setting
3. ODBC: Prefix with `odbc`, `key=value` pairs separated by `;`. Allow `;` by wrapping
values in `{}`. Examples:
+20 -10
View File
@@ -1,7 +1,11 @@
version: 1.0.{build}
image:
- Visual Studio 2015
# SQL Server 2022 and 2025 are only available on the Visual Studio 2026 image.
image: Visual Studio 2026
# Every push to a branch with an open PR otherwise queues two builds, and the
# project runs one build at a time. Only the PR build is a required check.
skip_branch_with_pr: true
clone_folder: c:\gopath\src\github.com\microsoft\go-mssqldb
@@ -12,30 +16,36 @@ environment:
SQLPASSWORD: Password12!
DATABASE: test
GOVERSION: 125
SQLINSTANCE: SQL2025
COLUMNENCRYPTION:
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
TAGS:
matrix:
- SQLINSTANCE: SQL2017
# Default protocol negotiation, 64bit, SQL auth
- GOVERSION: 125
SQLINSTANCE: SQL2017
# Always Encrypted
- GOVERSION: 125
SQLINSTANCE: SQL2019
COLUMNENCRYPTION: 1
# Cover 32bit and named pipes protocol
# 32bit and named pipes protocol
- GOVERSION: 125-x86
SQLINSTANCE: SQL2017
GOARCH: 386
PROTOCOL: np
TAGS: -tags np
# Cover SSPI and lpc protocol
# SSPI and lpc protocol
- GOVERSION: 125
SQLINSTANCE: SQL2019
PROTOCOL: lpc
TAGS: -tags sm
SQLUSER:
SQLPASSWORD:
init:
# AppVeyor does not document instance names past SQL2019; list what is installed.
- ps: |
$key = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL'
(Get-ItemProperty $key -ErrorAction SilentlyContinue).PSObject.Properties |
Where-Object Name -notlike 'PS*' |
ForEach-Object { "instance: $($_.Name) -> $($_.Value)" }
install:
- set GOROOT=c:\go%GOVERSION%
- set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
+18 -4
View File
@@ -133,7 +133,11 @@ func (b *Bulk) sendBulkCommand(ctx context.Context) (err error) {
with_opts = append(with_opts, fmt.Sprintf("ROWS_PER_BATCH = %d", b.Options.RowsPerBatch))
}
if len(b.Options.Order) > 0 {
with_opts = append(with_opts, fmt.Sprintf("ORDER(%s)", strings.Join(b.Options.Order, ",")))
order := make([]string, len(b.Options.Order))
for i, entry := range b.Options.Order {
order[i] = quoteBulkOrder(entry)
}
with_opts = append(with_opts, fmt.Sprintf("ORDER(%s)", strings.Join(order, ",")))
}
if b.Options.Tablock {
with_opts = append(with_opts, "TABLOCK")
@@ -143,7 +147,11 @@ func (b *Bulk) sendBulkCommand(ctx context.Context) (err error) {
with_part = fmt.Sprintf("WITH (%s)", strings.Join(with_opts, ","))
}
query := fmt.Sprintf("INSERT BULK %s (%s) %s", b.tablename, col_defs.String(), with_part)
quotedTable, err := quoteObjectName(b.tablename)
if err != nil {
return err
}
query := fmt.Sprintf("INSERT BULK %s (%s) %s", quotedTable, col_defs.String(), with_part)
stmt, err := b.cn.PrepareContext(ctx, query)
if err != nil {
@@ -292,6 +300,12 @@ func (b *Bulk) createColMetadata() []byte {
}
func (b *Bulk) getMetadata(ctx context.Context) (err error) {
// Check the destination before touching the session's FMTONLY setting.
quotedTable, err := quoteObjectName(b.tablename)
if err != nil {
return err
}
stmt, err := b.cn.prepareContext(ctx, "SET FMTONLY ON")
if err != nil {
return
@@ -327,13 +341,13 @@ func (b *Bulk) getMetadata(ctx context.Context) (err error) {
}()
// Get columns info.
stmt, err = b.cn.prepareContext(ctx, fmt.Sprintf("select * from %s SET FMTONLY OFF", b.tablename))
stmt, err = b.cn.prepareContext(ctx, fmt.Sprintf("select * from %s SET FMTONLY OFF", quotedTable))
if err != nil {
return
}
rows, err := stmt.QueryContext(ctx, nil)
if err != nil {
return fmt.Errorf("get columns info failed: %v", err)
return fmt.Errorf("get columns info failed: %w", err)
}
resetFmtonly = false
b.metadata = rows.(*Rows).cols
+24
View File
@@ -43,6 +43,30 @@ func (c *Conn) prepareCopyIn(ctx context.Context, query string) (_ driver.Stmt,
return ci, nil
}
// CopyIn creates a bulk import statement that can be passed to Prepare.
//
// table is the destination object name. It is treated as an object name and is
// quoted before it is sent to the server, so a name that is not a regular
// identifier does not need to be delimited by the caller. It may be qualified,
// as in "schema.table" or "database.schema.table", and individual parts may be
// delimited, as in "[my schema].[my table]".
//
// Because the destination is always an object name, a value that relied on
// being spliced into the statement as raw text no longer resolves. Pass
// "table" and options.Tablock rather than "table WITH (TABLOCK)". A name that
// cannot identify an object, because it is empty or has more than four parts,
// is reported before any statement is sent.
//
// columns are the destination column names, in the order their values are
// passed to Exec.
//
// Each options.Order entry names one or more columns to declare the data is
// already sorted by, as in "id" or "id ASC, name DESC". Column names are quoted
// the same way table is. A column name is separated from an optional trailing
// ASC or DESC sort direction by whitespace, and from the next column by a
// comma, so a column whose name ends in "asc" or "desc" or contains a comma has
// to be delimited by the caller to be told apart from a direction or a
// separator, as in "[sort desc]" or "[order, id]".
func CopyIn(table string, options BulkOptions, columns ...string) string {
bulkconfig := &serializableBulkConfig{TableName: table, Options: options, ColumnsName: columns}
+13 -1
View File
@@ -3,6 +3,7 @@ package mssql
import (
"database/sql/driver"
"fmt"
"strings"
)
// Error represents an SQL Server error. This
@@ -24,7 +25,18 @@ type Error struct {
}
func (e Error) Error() string {
return "mssql: " + e.Message
errChain := e.All
if len(errChain) == 0 {
errChain = []Error{e}
}
var msg strings.Builder
for i, err := range errChain {
if i > 0 {
msg.WriteByte('\n')
}
fmt.Fprintf(&msg, "mssql: %s (%d)", err.Message, err.Number)
}
return msg.String()
}
func (e Error) String() string {
+34 -4
View File
@@ -304,15 +304,32 @@ func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, err error) {
targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16])
targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20])
endOfOffset := int(targetNameOffset + uint32(targetNameAllocated))
if type2MessageLength < endOfOffset {
// Use 64-bit arithmetic so a server-controlled offset near the uint32
// maximum cannot overflow and bypass the bounds check below.
endOfOffset := int64(targetNameOffset) + int64(targetNameAllocated)
if int64(type2MessageLength) < endOfOffset {
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
}
// The target information header fields live at offsets 42-48. A malformed
// or truncated challenge message may be long enough to pass the checks
// above but too short to contain these fields, so validate before reading
// to avoid an out-of-bounds slice read.
if type2MessageLength < 48 {
return nil, fmt.Errorf(type2MessageError, type2MessageLength, 48)
}
targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44])
// A well-formed target information block is an AV_PAIR list terminated by
// the 4-byte MsvAvEOL pair. The channel-binding path assumes those final
// 4 bytes exist (it slices targetInfoFields[:len-4]), so a block shorter
// than 4 bytes is malformed and would cause a negative slice index panic.
if targetInformationAllocated < 4 {
return nil, fmt.Errorf("mssql: NTLMv2 type 2 message target information length %d too small, minimum 4", targetInformationAllocated)
}
targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48])
endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated))
if type2MessageLength < endOfOffset {
endOfOffset = int64(targetInformationDataOffset) + int64(targetInformationAllocated)
if int64(type2MessageLength) < endOfOffset {
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
}
@@ -388,7 +405,20 @@ func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation,
return msg, nil
}
// minChallengeMessageSize is the minimum size in bytes of a well-formed
// NTLM Type 2 (CHALLENGE) message. NextBytes reads the signature (0:8),
// message type (8:12), flags (20:24) and server challenge (24:32) fields
// directly, so a shorter buffer would cause an out-of-bounds slice panic.
const minChallengeMessageSize = 32
func (auth *Auth) NextBytes(bytes []byte) ([]byte, error) {
// A malicious or on-path server can send a truncated challenge message.
// Validate the length before slicing to avoid an out-of-bounds panic
// (Denial of Service).
if len(bytes) < minChallengeMessageSize {
return nil, errorNTLM
}
signature := string(bytes[0:8])
if signature != "NTLMSSP\x00" {
return nil, errorNTLM
+35
View File
@@ -0,0 +1,35 @@
//go:build !386 && !arm && !mips && !mipsle
package cp
import "encoding/binary"
// asciiMask64 has the high bit set in each byte. When AND-ed with an 8 byte
// chunk it reports whether any byte in that chunk has its high bit set, i.e.
// is >= 0x80.
const asciiMask64 uint64 = 0x8080808080808080
// isASCII reports whether s contains only bytes in the ASCII range (0x00-0x7F).
// It scans the input in 8 byte chunks, the same pattern used by ucs22str in the
// parent package.
func isASCII(s []byte) bool {
// how many 8 byte chunks are in the input buffer
nlen8 := len(s) &^ 7
for i := 0; i < nlen8; i += 8 {
// read the 8 byte chunk with an alignment-safe load
ui64 := binary.NativeEndian.Uint64(s[i : i+8])
// mask the entire 64 bit region and check for any byte with the high bit set
if ui64&asciiMask64 > 0 {
return false
}
}
// deal with the at most 7 remaining bytes
for i := nlen8; i < len(s); i++ {
if s[i] >= 0x80 {
return false
}
}
return true
}
+13
View File
@@ -0,0 +1,13 @@
//go:build arm || 386 || mips || mipsle
package cp
// isASCII reports whether s contains only bytes in the ASCII range (0x00-0x7F).
func isASCII(s []byte) bool {
for _, b := range s {
if b >= 0x80 {
return false
}
}
return true
}
+7
View File
@@ -96,6 +96,13 @@ func CharsetToUTF8(col Collation, s []byte) string {
return string(s)
}
// Fast path: every code page maps the ASCII range (0x00-0x7F) to the
// identical Unicode code points, so a pure ASCII input can be returned
// without per-character decoding.
if isASCII(s) {
return string(s)
}
buf := strings.Builder{}
buf.Grow(len(s))
for i := 0; i < len(s); i++ {
+37 -11
View File
@@ -290,6 +290,17 @@ func setupTLSServerCertificateOnly(config *tls.Config, pemData []byte) error {
return nil
}
// parseBoolParam also accepts the ODBC "yes"/"no" spelling that Encrypt already allows.
func parseBoolParam(value string) (bool, error) {
if strings.EqualFold(value, "yes") {
return true, nil
}
if strings.EqualFold(value, "no") {
return false, nil
}
return strconv.ParseBool(value)
}
// parseTLS parses encryption parameters and returns the TLS configuration and trustServerCertificate value.
func parseTLS(params map[string]string, host string) (Encryption, *tls.Config, bool, error) {
trustServerCert := false
@@ -317,7 +328,7 @@ func parseTLS(params map[string]string, host string) (Encryption, *tls.Config, b
trust, ok := params[TrustServerCertificate]
if ok {
var err error
trustServerCert, err = strconv.ParseBool(trust)
trustServerCert, err = parseBoolParam(trust)
if err != nil {
f := "invalid trust server certificate '%s': %s"
return encryption, nil, false, fmt.Errorf(f, trust, err.Error())
@@ -440,7 +451,7 @@ func Parse(dsn string) (Config, error) {
}
}
// https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option\
// https://docs.microsoft.com/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option
strpsize, ok := params[PacketSize]
if ok {
var err error
@@ -462,7 +473,7 @@ func Parse(dsn string) (Config, error) {
}
}
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
// https://msdn.microsoft.com/library/dd341108.aspx
//
// Do not set a connection timeout. Use Context to manage such things.
// Default to zero, but still allow it to be set.
@@ -476,7 +487,7 @@ func Parse(dsn string) (Config, error) {
}
// default keep alive should be 30 seconds according to spec:
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
// https://msdn.microsoft.com/library/dd341108.aspx
p.KeepAlive = 30 * time.Second
if keepAlive, ok := params[KeepAlive]; ok {
timeout, err := strconv.ParseUint(keepAlive, 10, 64)
@@ -541,7 +552,7 @@ func Parse(dsn string) (Config, error) {
disableRetry, ok := params[DisableRetry]
if ok {
var err error
p.DisableRetry, err = strconv.ParseBool(disableRetry)
p.DisableRetry, err = parseBoolParam(disableRetry)
if err != nil {
f := "invalid disableRetry '%s': %s"
return p, fmt.Errorf(f, disableRetry, err.Error())
@@ -601,7 +612,7 @@ func Parse(dsn string) (Config, error) {
}
if c, ok := params["columnencryption"]; ok {
columnEncryption, err := strconv.ParseBool(c)
columnEncryption, err := parseBoolParam(c)
if err != nil {
if strings.EqualFold(c, "Enabled") {
columnEncryption = true
@@ -616,7 +627,7 @@ func Parse(dsn string) (Config, error) {
msf, ok := params[MultiSubnetFailover]
if ok {
multiSubnetFailover, err := strconv.ParseBool(msf)
multiSubnetFailover, err := parseBoolParam(msf)
if err != nil {
if strings.EqualFold(msf, "Enabled") {
multiSubnetFailover = true
@@ -633,7 +644,7 @@ func Parse(dsn string) (Config, error) {
}
nti, ok := params[NoTraceID]
if ok {
notraceid, err := strconv.ParseBool(nti)
notraceid, err := parseBoolParam(nti)
if err == nil {
p.NoTraceID = notraceid
}
@@ -642,7 +653,7 @@ func Parse(dsn string) (Config, error) {
guidConversion, ok := params[GuidConversion]
if ok {
var err error
p.Encoding.GuidConversion, err = strconv.ParseBool(guidConversion)
p.Encoding.GuidConversion, err = parseBoolParam(guidConversion)
if err != nil {
f := "invalid guid conversion '%s': %s"
return p, fmt.Errorf(f, guidConversion, err.Error())
@@ -658,7 +669,7 @@ func Parse(dsn string) (Config, error) {
epaString = os.Getenv("MSSQL_USE_EPA")
}
if epaString != "" {
epaEnabled, err := strconv.ParseBool(epaString)
epaEnabled, err := parseBoolParam(epaString)
if err != nil {
return p, fmt.Errorf("invalid epa enabled value '%s': %v", epaString, err)
}
@@ -722,6 +733,8 @@ func (p Config) URL() *url.URL {
q.Add(Encrypt, "DISABLE")
case EncryptionRequired:
q.Add(Encrypt, "true")
case EncryptionStrict:
q.Add(Encrypt, "strict")
}
// Only include TrustServerCertificate if it was explicitly set in the original connection string
if _, ok := p.Parameters[TrustServerCertificate]; ok {
@@ -759,8 +772,10 @@ func (p Config) URL() *url.URL {
return &res
}
// ADO connection string keywords at https://github.com/dotnet/SqlClient/blob/main/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
// adoSynonyms maps ADO.Net alternate keyword forms to this driver's canonical keys.
// See https://learn.microsoft.com/dotnet/api/microsoft.data.sqlclient.sqlconnection.connectionstring
var adoSynonyms = map[string]string{
"app": AppName,
"application name": AppName,
"data source": Server,
"address": Server,
@@ -770,6 +785,17 @@ var adoSynonyms = map[string]string{
"uid": UserID,
"pwd": Password,
"initial catalog": Database,
"connect timeout": ConnectionTimeout,
"timeout": ConnectionTimeout,
"failover partner": FailoverPartner,
"failover partner spn": FailoverPartnerSpn,
"application intent": ApplicationIntent,
"trust server certificate": TrustServerCertificate,
"multi subnet failover": MultiSubnetFailover,
"host name in certificate": HostNameInCertificate,
"server spn": ServerSpn,
"server certificate": ServerCertificate,
"wsid": WorkstationID,
"column encryption setting": "columnencryption",
}
+1 -1
View File
@@ -620,7 +620,7 @@ func (s *Stmt) sendQuery(ctx context.Context, args []namedValue) (err error) {
// those commands can be invoke without extra options
var builtinCommands = []string{
"RECONFIGURE", "SHUTDOWN", "CHECKPOINT",
"COMMIT", "ROLLBACK",
"COMMIT", "ROLLBACK", "REVERT",
}
// isProc takes the query text in s and determines if it is a stored proc name
+189
View File
@@ -1,9 +1,14 @@
package mssql
import (
"fmt"
"strings"
)
// maxIDParts is the number of parts SQL Server allows in an object name:
// server.database.schema.object.
const maxIDParts = 4
// TSQLQuoter implements sqlexp.Quoter
type TSQLQuoter struct {
}
@@ -14,6 +19,190 @@ func (TSQLQuoter) ID(name string) string {
return "[" + strings.Replace(name, "]", "]]", -1) + "]"
}
// splitMultiPartID splits a possibly multi-part name such as "db.schema.table"
// into its individual parts. Dots inside a part delimited with [] or "" are not
// treated as separators.
func splitMultiPartID(name string) []string {
return splitTopLevel(name, '.')
}
// splitTopLevel splits s on sep, ignoring any separator that appears inside a
// delimited part such as [my.part] or "my.part".
//
// A delimiter only opens a delimited part when it starts that part and has a
// matching closer. A delimiter anywhere else is an ordinary character of an
// undelimited name, so later separators keep splitting and a name such as
// "sch[ema.table" still names the object "table" in the schema "sch[ema".
func splitTopLevel(s string, sep byte) []string {
parts := make([]string, 0, 4)
var part strings.Builder
// blank reports whether the part being read is still empty or holds only
// leading whitespace, so " [my.part] " is recognised as delimited too.
blank := true
for i := 0; i < len(s); i++ {
c := s[i]
if blank {
if n := delimitedLen(s[i:]); n > 0 {
part.WriteString(s[i : i+n])
i += n - 1
blank = false
continue
}
}
if c == sep {
parts = append(parts, part.String())
part.Reset()
blank = true
continue
}
part.WriteByte(c)
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
blank = false
}
}
return append(parts, part.String())
}
// delimitedLen returns the length of the well-formed delimited identifier that
// starts s, such as [my part] or "my part", or 0 when s does not start one.
// A doubled closing delimiter is an escaped literal rather than the end of the
// identifier.
func delimitedLen(s string) int {
if len(s) < 2 {
return 0
}
var closer byte
switch s[0] {
case '[':
closer = ']'
case '"':
closer = '"'
default:
return 0
}
for i := 1; i < len(s); i++ {
if s[i] != closer {
continue
}
if i+1 < len(s) && s[i+1] == closer {
i++
continue
}
return i + 1
}
return 0
}
// unquoteID reports whether part is a single well-formed delimited identifier
// and, if so, returns the identifier without its delimiters. A part such as
// `[a] SELECT 1 --[b]` starts and ends with brackets but is not a single
// identifier, so it is rejected. It recognises exactly the parts splitTopLevel
// keeps together, because both decide what a delimited part is with
// delimitedLen.
func unquoteID(part string) (string, bool) {
// delimitedLen returns 0 for anything that does not start a delimited
// identifier, which the length comparison alone would accept for an empty
// part.
if n := delimitedLen(part); n == 0 || n != len(part) {
return "", false
}
closer := part[len(part)-1]
body := part[1 : len(part)-1]
var name strings.Builder
for i := 0; i < len(body); i++ {
// delimitedLen already established that every closer in body is
// doubled, so the second byte of the pair is the literal one.
if body[i] == closer {
i++
}
name.WriteByte(body[i])
}
return name.String(), true
}
// idPartName returns the identifier one part of a multi-part name holds, with
// any delimiters removed. It is empty when the part names nothing, whether it
// was written as "", " " or "[]".
func idPartName(part string) string {
part = strings.TrimSpace(part)
if name, ok := unquoteID(part); ok {
return name
}
return part
}
// quoteIDPart quotes one part of a multi-part name. A part the caller already
// delimited with [] or "" keeps its identifier, everything else is escaped.
// An empty part is preserved so a name such as "db..table" keeps deferring to
// the server's default schema.
func quoteIDPart(part string) string {
name := idPartName(part)
if name == "" {
return ""
}
return TSQLQuoter{}.ID(name)
}
// quoteMultiPartID quotes a possibly multi-part object name such as "table",
// "schema.table" or "db.schema.table" so it is safe to embed in query text.
func quoteMultiPartID(name string) string {
parts := splitMultiPartID(name)
for i, part := range parts {
parts[i] = quoteIDPart(part)
}
return strings.Join(parts, ".")
}
// quoteObjectName quotes a possibly multi-part object name the same way
// quoteMultiPartID does, but rejects a name that cannot identify an object so
// the caller reports the problem itself instead of leaving the server to fail
// on the query text it produced.
func quoteObjectName(name string) (string, error) {
parts := splitMultiPartID(name)
if len(parts) > maxIDParts {
return "", fmt.Errorf("object name %q has %d parts, at most %d are allowed", name, len(parts), maxIDParts)
}
// The last part names the object itself. An empty one means there is no
// object to name, as in "", "dbo." or "dbo.[]", while an earlier empty
// part is a qualifier left out on purpose, as in "db..table".
if idPartName(parts[len(parts)-1]) == "" {
return "", fmt.Errorf("object name %q does not name an object", name)
}
for i, part := range parts {
parts[i] = quoteIDPart(part)
}
return strings.Join(parts, "."), nil
}
// quoteBulkOrder quotes the column names in a BulkOptions.Order entry so they
// are safe to embed in the ORDER hint of an INSERT BULK statement. An entry may
// name a single column or several columns separated by commas. An optional
// trailing ASC or DESC sort direction is preserved. A column name that itself
// ends in "asc" or "desc", or that contains a comma, has to be delimited by the
// caller to be told apart from a sort direction or a separator.
func quoteBulkOrder(entry string) string {
columns := splitTopLevel(entry, ',')
for i, column := range columns {
columns[i] = quoteBulkOrderColumn(column)
}
return strings.Join(columns, ",")
}
// quoteBulkOrderColumn quotes a single column of an ORDER hint, keeping any
// trailing sort direction.
func quoteBulkOrderColumn(column string) string {
name := strings.TrimSpace(column)
direction := ""
if i := strings.LastIndexAny(name, " \t\r\n"); i >= 0 {
switch suffix := strings.ToUpper(strings.TrimSpace(name[i:])); suffix {
case "ASC", "DESC":
direction = " " + suffix
name = name[:i]
}
}
return quoteIDPart(name) + direction
}
// Value quotes database values such as string or []byte types as strings
// that are suitable and safe to embed in SQL text. The returned value
// of a string will include all surrounding quotes.
+49 -9
View File
@@ -20,10 +20,29 @@ import (
"github.com/microsoft/go-mssqldb/msdsn"
)
// parseDAC parses a SQL Server Browser SVR_RESP (DAC) reply, MC-SQLR 2.2.6.
// The response is exactly 6 bytes: SVR_RESP (0x05), RESP_SIZE (0x0006, the
// length of the whole packet), PROTOCOLVERSION (0x01), then the little-endian
// TCP_DAC_PORT at offset 4. Every field except the port is fixed by the
// protocol, so a reply that does not match is discarded instead of being used
// to choose a TCP port. The message comes from an unauthenticated UDP source,
// so this must never panic.
func parseDAC(msg []byte, instance string) msdsn.BrowserData {
results := msdsn.BrowserData{}
if len(msg) == 6 && msg[0] == 5 {
results[strings.ToUpper(instance)]["tcp"] = fmt.Sprint(binary.LittleEndian.Uint16(msg[5:]))
if len(msg) == 6 && msg[0] == 5 && binary.LittleEndian.Uint16(msg[1:3]) == 6 && msg[3] == 1 {
port := binary.LittleEndian.Uint16(msg[4:6])
// Port 0 is never a listening endpoint. Accepting it would leave
// Config.Port at zero, which resolveServerPort silently rewrites to the
// default TDS port, connecting to the regular endpoint while the caller
// believes it reached the DAC one.
if port == 0 {
return results
}
name := strings.ToUpper(instance)
results[name] = map[string]string{
"InstanceName": name,
"tcp": fmt.Sprint(port),
}
}
return results
}
@@ -62,11 +81,18 @@ func getInstances(ctx context.Context, d Dialer, address string, browserMsg msds
var bmsg []byte
var resp []byte
if browserMsg == msdsn.BrowserDAC {
// CLNT_UCAST_DAC (MC-SQLR 2.2.4) is 0x0F, PROTOCOLVERSION 0x01, then a
// null-terminated instance name starting at offset 2. The buffer is
// sized so its zero-initialized last byte is the terminator.
bmsg = make([]byte, 3+len(instance))
bmsg[0] = byte(msdsn.BrowserDAC)
bmsg[1] = 1
_ = copy(bmsg[3:], instance)
resp = make([]byte, 6)
_ = copy(bmsg[2:], instance)
// A DAC reply is exactly 6 bytes (MC-SQLR 2.2.6). Read into a larger
// buffer so an oversized datagram is reported as such rather than
// being silently truncated to a well-formed looking 6 bytes, which
// would defeat the length check in parseDAC.
resp = make([]byte, 7)
} else { // default to AllInstances
bmsg = []byte{byte(msdsn.BrowserAllInstances)}
resp = make([]byte, 16*1024-1)
@@ -348,10 +374,17 @@ func readPreloginOption(buffer []byte, offset int) (*preloginOption, error) {
}
func readPreloginOptionData(plOption *preloginOption, buffer []byte) ([]byte, error) {
if plOption == nil {
return nil, fmt.Errorf("invalid buffer, invalid prelogin option")
}
buffer_length := len(buffer)
// widen to int before adding so the sum cannot wrap around and bypass the bounds check
optionOffset := int(plOption.offset)
optionLength := int(plOption.length)
// check if prelogin option data exists in buffer
if plOption == nil || int(plOption.length+plOption.offset) > buffer_length ||
int(plOption.offset) >= buffer_length {
if optionOffset+optionLength > buffer_length || optionOffset >= buffer_length {
return nil, fmt.Errorf("invalid buffer, invalid prelogin option")
}
@@ -359,7 +392,7 @@ func readPreloginOptionData(plOption *preloginOption, buffer []byte) ([]byte, er
return nil, fmt.Errorf("cannot read data for prelogin terminator record")
}
value := buffer[plOption.offset : plOption.length+plOption.offset]
value := buffer[optionOffset : optionOffset+optionLength]
return value, nil
}
@@ -1156,6 +1189,13 @@ func connect(ctx context.Context, c *Connector, logger ContextLogger, p msdsn.Co
packetSize = 32767
}
var toconn *timeoutConn
defer func() {
if err != nil && toconn != nil {
toconn.Close()
}
}()
initiate_connection:
dialCtx := ctx
if p.DialTimeout >= 0 {
@@ -1172,7 +1212,7 @@ initiate_connection:
return nil, err
}
toconn := newTimeoutConn(conn, p.ConnTimeout)
toconn = newTimeoutConn(conn, p.ConnTimeout)
outbuf := newTdsBuffer(packetSize, toconn)
if p.Encryption == msdsn.EncryptionStrict {
@@ -1379,7 +1419,6 @@ initiate_connection:
if token.isError() {
tokenErr := token.getError()
tokenErr.Message = "login error: " + tokenErr.Message
conn.Close()
return nil, tokenErr
}
case error:
@@ -1390,6 +1429,7 @@ initiate_connection:
if sess.routedServer != "" {
toconn.Close()
toconn = nil // avoid double-close: if dialConnection fails after goto, the defer would see the old (closed) conn
// Need to handle case when routedServer is in "host\instance" format.
routedParts := strings.SplitN(sess.routedServer, "\\", 2)
p.Host = routedParts[0]
+11 -1
View File
@@ -77,6 +77,11 @@ const _PLP_NULL = 0xFFFFFFFFFFFFFFFF
const _UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFE
const _PLP_TERMINATOR = 0x00000000
// _MAX_PLP_LEN is the largest length a PLP value can legitimately advertise.
// The (max) LOB types top out at 2 GB - 1 byte, so any larger size is a
// malformed stream rather than a value we should try to read.
const _MAX_PLP_LEN = 0x7FFFFFFF
// TVP COLUMN FLAGS
const _TVP_END_TOKEN = 0x00
const _TVP_ROW_TOKEN = 0x01
@@ -361,7 +366,7 @@ func readFixedType(ti *typeInfo, r *tdsBuffer, c *cryptoMetadata, encoding msdsn
case typeDateTim4:
return decodeDateTim4(buf, loc)
case typeFlt4:
return math.Float32frombits(binary.LittleEndian.Uint32(buf))
return float64(math.Float32frombits(binary.LittleEndian.Uint32(buf)))
case typeMoney4:
return decodeMoney4(buf)
case typeMoney:
@@ -748,6 +753,11 @@ func readPLPType(ti *typeInfo, r *tdsBuffer, c *cryptoMetadata, encoding msdsn.E
// size unknown
buf = bytes.NewBuffer(make([]byte, 0, 1000))
default:
// The advertised size is untrusted, so reject anything a real server
// cannot produce before using it as an allocation size.
if size > _MAX_PLP_LEN {
badStreamPanicf("PLP length %d exceeds the maximum LOB size of %d bytes", size, uint64(_MAX_PLP_LEN))
}
buf = bytes.NewBuffer(make([]byte, 0, size))
}
for {
+1 -16
View File
@@ -5,7 +5,6 @@ package mssql
import (
"fmt"
"reflect"
"unicode/utf16"
"unsafe"
)
@@ -129,21 +128,7 @@ func ucs22str(s []byte) (string, error) {
// we can reuse the underlying array and create our own uint16 slice here
// because utf16.Decode allocates a new buffer and only reads its input.
// declare a real uint16 slice so that the compiler can keep track of
// the underlying memory as we transfer & convert it.
// This is to ensure that the GC does not prematurely collect our data.
var uint16slice []uint16
uint16Header := (*reflect.SliceHeader)(unsafe.Pointer(&uint16slice))
sourceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&s))
uint16Header.Data = sourceHeader.Data
// it is important to reference s after the assignment of the Data
// pointer to make sure that s is not garbage collected before
// we have another reference to the data.
uint16Header.Len = len(s) / 2 // the output is half the length in bytes
uint16Header.Cap = uint16Header.Len // the capacity is also half the number of bytes
uint16slice := unsafe.Slice((*uint16)(unsafe.Pointer(&s[0])), len(s)/2)
// decode the uint16s as utf-16 and return a string.
// After this point both s and uint16slice can be garbage collected.
+1 -1
View File
@@ -7,7 +7,7 @@ import (
// This value is automatically updated by Release Please during the release process.
// It is written to the prelogin and login7 packets during a new connection.
const driverVersion = "v1.10.0" // x-release-please-version
const driverVersion = "v1.11.2" // x-release-please-version
func getDriverVersion(ver string) uint32 {
var majorVersion uint32