update mssql drive to last working version 20180314172330-6a30f4e59a44 (#7306)

tokarchuk/v1.17
Antoine GIRARD 5 years ago committed by Lunny Xiao
parent aeb8f7aad8
commit 1e46eedce7
  1. 2
      go.mod
  2. 4
      go.sum
  3. 174
      vendor/github.com/denisenkom/go-mssqldb/README.md
  4. 45
      vendor/github.com/denisenkom/go-mssqldb/appveyor.yml
  5. 145
      vendor/github.com/denisenkom/go-mssqldb/buf.go
  6. 616
      vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go
  7. 93
      vendor/github.com/denisenkom/go-mssqldb/bulkcopy_sql.go
  8. 39
      vendor/github.com/denisenkom/go-mssqldb/collation.go
  9. 22
      vendor/github.com/denisenkom/go-mssqldb/decimal.go
  10. 12
      vendor/github.com/denisenkom/go-mssqldb/doc.go
  11. 8
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/charset.go
  12. 20
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/collation.go
  13. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1250.go
  14. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1251.go
  15. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1252.go
  16. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1253.go
  17. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1254.go
  18. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1255.go
  19. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1256.go
  20. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1257.go
  21. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp1258.go
  22. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp437.go
  23. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp850.go
  24. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp874.go
  25. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp932.go
  26. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp936.go
  27. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp949.go
  28. 2
      vendor/github.com/denisenkom/go-mssqldb/internal/cp/cp950.go
  29. 21
      vendor/github.com/denisenkom/go-mssqldb/log.go
  30. 611
      vendor/github.com/denisenkom/go-mssqldb/mssql.go
  31. 11
      vendor/github.com/denisenkom/go-mssqldb/mssql_go1.3.go
  32. 11
      vendor/github.com/denisenkom/go-mssqldb/mssql_go1.3pre.go
  33. 91
      vendor/github.com/denisenkom/go-mssqldb/mssql_go18.go
  34. 64
      vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go
  35. 12
      vendor/github.com/denisenkom/go-mssqldb/mssql_go19pre.go
  36. 2
      vendor/github.com/denisenkom/go-mssqldb/net.go
  37. 2
      vendor/github.com/denisenkom/go-mssqldb/ntlm.go
  38. 40
      vendor/github.com/denisenkom/go-mssqldb/parser.go
  39. 2
      vendor/github.com/denisenkom/go-mssqldb/sspi_windows.go
  40. 358
      vendor/github.com/denisenkom/go-mssqldb/tds.go
  41. 387
      vendor/github.com/denisenkom/go-mssqldb/token.go
  42. 53
      vendor/github.com/denisenkom/go-mssqldb/token_string.go
  43. 16
      vendor/github.com/denisenkom/go-mssqldb/tran.go
  44. 679
      vendor/github.com/denisenkom/go-mssqldb/types.go
  45. 74
      vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go
  46. 3
      vendor/modules.txt

@ -140,4 +140,4 @@ require (
xorm.io/core v0.6.3 xorm.io/core v0.6.3
) )
replace github.com/denisenkom/go-mssqldb => github.com/denisenkom/go-mssqldb v0.0.0-20161128230840-e32ca5036449 replace github.com/denisenkom/go-mssqldb => github.com/denisenkom/go-mssqldb v0.0.0-20180314172330-6a30f4e59a44

@ -60,8 +60,8 @@ github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20161128230840-e32ca5036449 h1:JpA+YMG4JLW8nzLmU05mTiuB0O17xHGxpWolEZ0zDuA= github.com/denisenkom/go-mssqldb v0.0.0-20180314172330-6a30f4e59a44 h1:x0uHqLQTSEL9LKic8sWDt3ASkq07ve5ojIIUl5uF64M=
github.com/denisenkom/go-mssqldb v0.0.0-20161128230840-e32ca5036449/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denisenkom/go-mssqldb v0.0.0-20180314172330-6a30f4e59a44/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac h1:xrQJVwQCGqDvOO7/0+RyIq5J2M3Q4ZF7Ug/BMQtML1E= github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac h1:xrQJVwQCGqDvOO7/0+RyIq5J2M3Q4ZF7Ug/BMQtML1E=
github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=

@ -1,78 +1,130 @@
# A pure Go MSSQL driver for Go's database/sql package # A pure Go MSSQL driver for Go's database/sql package
[![GoDoc](https://godoc.org/github.com/denisenkom/go-mssqldb?status.svg)](http://godoc.org/github.com/denisenkom/go-mssqldb)
[![Build status](https://ci.appveyor.com/api/projects/status/jrln8cs62wj9i0a2?svg=true)](https://ci.appveyor.com/project/denisenkom/go-mssqldb)
[![codecov](https://codecov.io/gh/denisenkom/go-mssqldb/branch/master/graph/badge.svg)](https://codecov.io/gh/denisenkom/go-mssqldb)
## Install ## Install
go get github.com/denisenkom/go-mssqldb Requires Go 1.8 or above.
## Tests Install with `go get github.com/denisenkom/go-mssqldb` .
`go test` is used for testing. A running instance of MSSQL server is required. ## Connection Parameters and DSN
Environment variables are used to pass login information.
Example: The recommended connection string uses a URL format:
`sqlserver://username:password@host/instance?param1=value&param2=value`
Other supported formats are listed below.
### Common parameters:
env HOST=localhost SQLUSER=sa SQLPASSWORD=sa DATABASE=test go test * `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.
* `password`
## Connection Parameters * `database`
* `connection timeout` - in seconds (default is 30)
* "server" - host or host\instance (default localhost) * `dial timeout` - in seconds (default is 5)
* "port" - used only when there is no instance in server (default 1433) * `encrypt`
* "failoverpartner" - host or host\instance (default is no partner). * `disable` - Data send between client and server is not encrypted.
* "failoverport" - used only when there is no instance in failoverpartner (default 1433) * `false` - Data sent between client and server is not encrypted beyond the login packet. (Default)
* "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. * `true` - Data sent between client and server is encrypted.
* "password" * `keepAlive` - in seconds; 0 to disable (default is 30)
* "database" * `app name` - The application name (default is go-mssqldb)
* "connection timeout" - in seconds (default is 30)
* "dial timeout" - in seconds (default is 5) ### Connection parameters for ODBC and ADO style connection strings:
* "keepAlive" - in seconds; 0 to disable (default is 0)
* "log" - logging flags (default 0/no logging, 63 for full logging) * `server` - host or host\instance (default localhost)
* `port` - used only when there is no instance in server (default 1433)
### Less common parameters:
* `failoverpartner` - host or host\instance (default is no partner).
* `failoverport` - used only when there is no instance in failoverpartner (default 1433)
* `packet size` - in bytes; 512 to 32767 (default is 4096)
* Encrypted connections have a maximum packet size of 16383 bytes
* Further information on usage: https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option
* `log` - logging flags (default 0/no logging, 63 for full logging)
* 1 log errors * 1 log errors
* 2 log messages * 2 log messages
* 4 log rows affected * 4 log rows affected
* 8 trace sql statements * 8 trace sql statements
* 16 log statement parameters * 16 log statement parameters
* 32 log transaction begin/end * 32 log transaction begin/end
* "encrypt" * `TrustServerCertificate`
* disable - Data send between client and server is not encrypted.
* false - Data sent between client and server is not encrypted beyond the login packet. (Default)
* true - Data sent between client and server is encrypted.
* "TrustServerCertificate"
* false - Server certificate is checked. Default is false if encypt is specified. * false - Server certificate is checked. Default is false if encypt is specified.
* true - Server certificate is not checked. Default is true if encrypt is not specified. If trust server certificate is true, driver accepts any certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing. * true - Server certificate is not checked. Default is true if encrypt is not specified. If trust server certificate is true, driver accepts any certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing.
* "certificate" - The file that contains the public key certificate of the CA that signed the SQL Server certificate. The specified certificate overrides the go platform specific CA certificates. * `certificate` - The file that contains the public key certificate of the CA that signed the SQL Server certificate. The specified certificate overrides the go platform specific CA certificates.
* "hostNameInCertificate" - Specifies the Common Name (CN) in the server certificate. Default value is the server host. * `hostNameInCertificate` - Specifies the Common Name (CN) in the server certificate. Default value is the server host.
* "ServerSPN" - The kerberos SPN (Service Principal Name) for the server. Default is MSSQLSvc/host:port. * `ServerSPN` - The kerberos SPN (Service Principal Name) for the server. Default is MSSQLSvc/host:port.
* "Workstation ID" - The workstation name (default is the host name) * `Workstation ID` - The workstation name (default is the host name)
* "app name" - The application name (default is go-mssqldb) * `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener.
* "ApplicationIntent" - Can be given the value "ReadOnly" to initiate a read-only connection to an Availability Group listener.
Example: ### The connection string can be specified in one of three formats:
```go
db, err := sql.Open("mssql", "server=localhost;user id=sa")
```
## Statement Parameters 1. URL: with `sqlserver` scheme. username and password appears before the host. Any instance appears as
the first segment in the path. All other options are query parameters. Examples:
In the SQL statement text, literals may be replaced by a parameter that matches one of the following: * `sqlserver://username:password@host/instance?param1=value&param2=value`
* `sqlserver://username:password@host:port?param1=value&param2=value`
* `sqlserver://sa@localhost/SQLExpress?database=master&connection+timeout=30` // `SQLExpress instance.
* `sqlserver://sa:mypass@localhost?database=master&connection+timeout=30` // username=sa, password=mypass.
* `sqlserver://sa:mypass@localhost:1234?database=master&connection+timeout=30"` // port 1234 on localhost.
* `sqlserver://sa:my%7Bpass@somehost?connection+timeout=30` // password is "my{pass"
* ? A string of this format can be constructed using the `URL` type in the `net/url` package.
* ?nnn
* :nnn
* $nnn
where nnn represents an integer that specifies a 1-indexed positional parameter. Ex: ```go
query := url.Values{}
query.Add("connection timeout", "30")
u := &url.URL{
Scheme: "sqlserver",
User: url.UserPassword(username, password),
Host: fmt.Sprintf("%s:%d", hostname, port),
// Path: instance, // if connecting to an instance instead of a port
RawQuery: query.Encode(),
}
db, err := sql.Open("sqlserver", u.String())
```
2. ADO: `key=value` pairs separated by `;`. Values may not contain `;`, leading and trailing whitespace is ignored.
Examples:
* `server=localhost\\SQLExpress;user id=sa;database=master;connection timeout=30`
* `server=localhost;user id=sa;database=master;connection timeout=30`
3. ODBC: Prefix with `odbc`, `key=value` pairs separated by `;`. Allow `;` by wrapping
values in `{}`. Examples:
* `odbc:server=localhost\\SQLExpress;user id=sa;database=master;connection timeout=30`
* `odbc:server=localhost;user id=sa;database=master;connection timeout=30`
* `odbc:server=localhost;user id=sa;password={foo;bar}` // Value marked with `{}`, password is "foo;bar"
* `odbc:server=localhost;user id=sa;password={foo{bar}` // Value marked with `{}`, password is "foo{bar"
* `odbc:server=localhost;user id=sa;password={foobar }` // Value marked with `{}`, password is "foobar "
* `odbc:server=localhost;user id=sa;password=foo{bar` // Literal `{`, password is "foo{bar"
* `odbc:server=localhost;user id=sa;password=foo}bar` // Literal `}`, password is "foo}bar"
* `odbc:server=localhost;user id=sa;password={foo{bar}` // Literal `{`, password is "foo{bar"
* `odbc:server=localhost;user id=sa;password={foo}}bar}` // Escaped `} with `}}`, password is "foo}bar"
## Executing Stored Procedures
To run a stored procedure, set the query text to the procedure name:
```go ```go
db.Query("SELECT * FROM t WHERE a = ?3, b = ?2, c = ?1", "x", "y", "z") var account = "abc"
_, err := db.ExecContext(ctx, "sp_RunMe",
sql.Named("ID", 123),
sql.Out{Dest{sql.Named("Account", &account)}
)
``` ```
will expand to roughly ## Statement Parameters
```sql The `sqlserver` driver uses normal MS SQL Server syntax and expects parameters in
SELECT * FROM t WHERE a = 'z', b = 'y', c = 'x' the sql query to be in the form of either `@Name` or `@p1` to `@pN` (ordinal position).
```
```go
db.QueryContext(ctx, `select * from t where ID = @ID and Name = @p2;`, sql.Named("ID", 6), "Bob")
```
## Features ## Features
@ -87,6 +139,34 @@ SELECT * FROM t WHERE a = 'z', b = 'y', c = 'x'
* Supports connections to AlwaysOn Availability Group listeners, including re-direction to read-only replicas. * Supports connections to AlwaysOn Availability Group listeners, including re-direction to read-only replicas.
* Supports query notifications * Supports query notifications
## Tests
`go test` is used for testing. A running instance of MSSQL server is required.
Environment variables are used to pass login information.
Example:
env SQLSERVER_DSN=sqlserver://user:pass@hostname/instance?database=test1 go test
## Deprecated
These features still exist in the driver, but they are are deprecated.
### Query Parameter Token Replace (driver "mssql")
If you use the driver name "mssql" (rather then "sqlserver" the SQL text
will be loosly parsed and an attempt to extract identifiers using one of
* ?
* ?nnn
* :nnn
* $nnn
will be used. This is not recommended with SQL Server.
There is at least one existing `won't fix` issue with the query parsing.
Use the native "@Name" parameters instead with the "sqlserver" driver name.
## Known Issues ## Known Issues
* SQL Server 2008 and 2008 R2 engine cannot handle login records when SSL encryption is not disabled. * SQL Server 2008 and 2008 R2 engine cannot handle login records when SSL encryption is not disabled.

@ -0,0 +1,45 @@
version: 1.0.{build}
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\denisenkom\go-mssqldb
environment:
GOPATH: c:\gopath
HOST: localhost
SQLUSER: sa
SQLPASSWORD: Password12!
DATABASE: test
GOVERSION: 110
matrix:
- GOVERSION: 18
SQLINSTANCE: SQL2016
- GOVERSION: 110
SQLINSTANCE: SQL2016
- SQLINSTANCE: SQL2014
- SQLINSTANCE: SQL2012SP1
- SQLINSTANCE: SQL2008R2SP2
install:
- set GOROOT=c:\go%GOVERSION%
- set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
- go version
- go env
build_script:
- go build
before_test:
# setup SQL Server
- ps: |
$instanceName = $env:SQLINSTANCE
Start-Service "MSSQL`$$instanceName"
Start-Service "SQLBrowser"
- sqlcmd -S "(local)\%SQLINSTANCE%" -Q "Use [master]; CREATE DATABASE test;"
- sqlcmd -S "(local)\%SQLINSTANCE%" -h -1 -Q "set nocount on; Select @@version"
- pip install codecov
test_script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
- codecov -f coverage.txt

@ -2,12 +2,14 @@ package mssql
import ( import (
"encoding/binary" "encoding/binary"
"io"
"errors" "errors"
"io"
) )
type packetType uint8
type header struct { type header struct {
PacketType uint8 PacketType packetType
Status uint8 Status uint8
Size uint16 Size uint16
Spid uint16 Spid uint16
@ -15,55 +17,84 @@ type header struct {
Pad uint8 Pad uint8
} }
// tdsBuffer reads and writes TDS packets of data to the transport.
// The write and read buffers are separate to make sending attn signals
// possible without locks. Currently attn signals are only sent during
// reads, not writes.
type tdsBuffer struct { type tdsBuffer struct {
buf []byte transport io.ReadWriteCloser
pos uint16
transport io.ReadWriteCloser packetSize int
size uint16
// Write fields.
wbuf []byte
wpos int
wPacketSeq byte
wPacketType packetType
// Read fields.
rbuf []byte
rpos int
rsize int
final bool final bool
packet_type uint8 rPacketType packetType
afterFirst func()
// afterFirst is assigned to right after tdsBuffer is created and
// before the first use. It is executed after the first packet is
// written and then removed.
afterFirst func()
} }
func newTdsBuffer(bufsize int, transport io.ReadWriteCloser) *tdsBuffer { func newTdsBuffer(bufsize uint16, transport io.ReadWriteCloser) *tdsBuffer {
buf := make([]byte, bufsize) return &tdsBuffer{
w := new(tdsBuffer) packetSize: int(bufsize),
w.buf = buf wbuf: make([]byte, 1<<16),
w.pos = 8 rbuf: make([]byte, 1<<16),
w.transport = transport rpos: 8,
w.size = 0 transport: transport,
return w }
}
func (rw *tdsBuffer) ResizeBuffer(packetSize int) {
rw.packetSize = packetSize
}
func (w *tdsBuffer) PackageSize() int {
return w.packetSize
} }
func (w *tdsBuffer) flush() (err error) { func (w *tdsBuffer) flush() (err error) {
// writing packet size // Write packet size.
binary.BigEndian.PutUint16(w.buf[2:], w.pos) w.wbuf[0] = byte(w.wPacketType)
binary.BigEndian.PutUint16(w.wbuf[2:], uint16(w.wpos))
w.wbuf[6] = w.wPacketSeq
// writing packet into underlying transport // Write packet into underlying transport.
if _, err = w.transport.Write(w.buf[:w.pos]); err != nil { if _, err = w.transport.Write(w.wbuf[:w.wpos]); err != nil {
return err return err
} }
// It is possible to create a whole new buffer after a flush.
// Useful for debugging. Normally reuse the buffer.
// w.wbuf = make([]byte, 1<<16)
// execute afterFirst hook if it is set // Execute afterFirst hook if it is set.
if w.afterFirst != nil { if w.afterFirst != nil {
w.afterFirst() w.afterFirst()
w.afterFirst = nil w.afterFirst = nil
} }
w.pos = 8 w.wpos = 8
// packet number w.wPacketSeq++
w.buf[6] += 1
return nil return nil
} }
func (w *tdsBuffer) Write(p []byte) (total int, err error) { func (w *tdsBuffer) Write(p []byte) (total int, err error) {
total = 0
for { for {
copied := copy(w.buf[w.pos:], p) copied := copy(w.wbuf[w.wpos:w.packetSize], p)
w.pos += uint16(copied) w.wpos += copied
total += copied total += copied
if copied == len(p) { if copied == len(p) {
break return
} }
if err = w.flush(); err != nil { if err = w.flush(); err != nil {
return return
@ -74,66 +105,64 @@ func (w *tdsBuffer) Write(p []byte) (total int, err error) {
} }
func (w *tdsBuffer) WriteByte(b byte) error { func (w *tdsBuffer) WriteByte(b byte) error {
if int(w.pos) == len(w.buf) { if int(w.wpos) == len(w.wbuf) {
if err := w.flush(); err != nil { if err := w.flush(); err != nil {
return err return err
} }
} }
w.buf[w.pos] = b w.wbuf[w.wpos] = b
w.pos += 1 w.wpos += 1
return nil return nil
} }
func (w *tdsBuffer) BeginPacket(packet_type byte) { func (w *tdsBuffer) BeginPacket(packetType packetType) {
w.buf[0] = packet_type w.wbuf[1] = 0 // Packet is incomplete. This byte is set again in FinishPacket.
w.buf[1] = 0 // packet is incomplete w.wpos = 8
w.buf[4] = 0 // spid w.wPacketSeq = 1
w.buf[5] = 0 w.wPacketType = packetType
w.buf[6] = 1 // packet id
w.buf[7] = 0 // window
w.pos = 8
} }
func (w *tdsBuffer) FinishPacket() error { func (w *tdsBuffer) FinishPacket() error {
w.buf[1] = 1 // this is last packet w.wbuf[1] = 1 // Mark this as the last packet in the message.
return w.flush() return w.flush()
} }
var headerSize = binary.Size(header{})
func (r *tdsBuffer) readNextPacket() error { func (r *tdsBuffer) readNextPacket() error {
header := header{} h := header{}
var err error var err error
err = binary.Read(r.transport, binary.BigEndian, &header) err = binary.Read(r.transport, binary.BigEndian, &h)
if err != nil { if err != nil {
return err return err
} }
offset := uint16(binary.Size(header)) if int(h.Size) > len(r.rbuf) {
if int(header.Size) > len(r.buf) {
return errors.New("Invalid packet size, it is longer than buffer size") return errors.New("Invalid packet size, it is longer than buffer size")
} }
if int(offset) > int(header.Size) { if headerSize > int(h.Size) {
return errors.New("Invalid packet size, it is shorter than header size") return errors.New("Invalid packet size, it is shorter than header size")
} }
_, err = io.ReadFull(r.transport, r.buf[offset:header.Size]) _, err = io.ReadFull(r.transport, r.rbuf[headerSize:h.Size])
if err != nil { if err != nil {
return err return err
} }
r.pos = offset r.rpos = headerSize
r.size = header.Size r.rsize = int(h.Size)
r.final = header.Status != 0 r.final = h.Status != 0
r.packet_type = header.PacketType r.rPacketType = h.PacketType
return nil return nil
} }
func (r *tdsBuffer) BeginRead() (uint8, error) { func (r *tdsBuffer) BeginRead() (packetType, error) {
err := r.readNextPacket() err := r.readNextPacket()
if err != nil { if err != nil {
return 0, err return 0, err
} }
return r.packet_type, nil return r.rPacketType, nil
} }
func (r *tdsBuffer) ReadByte() (res byte, err error) { func (r *tdsBuffer) ReadByte() (res byte, err error) {
if r.pos == r.size { if r.rpos == r.rsize {
if r.final { if r.final {
return 0, io.EOF return 0, io.EOF
} }
@ -142,8 +171,8 @@ func (r *tdsBuffer) ReadByte() (res byte, err error) {
return 0, err return 0, err
} }
} }
res = r.buf[r.pos] res = r.rbuf[r.rpos]
r.pos++ r.rpos++
return res, nil return res, nil
} }
@ -207,7 +236,7 @@ func (r *tdsBuffer) readUcs2(numchars int) string {
func (r *tdsBuffer) Read(buf []byte) (copied int, err error) { func (r *tdsBuffer) Read(buf []byte) (copied int, err error) {
copied = 0 copied = 0
err = nil err = nil
if r.pos == r.size { if r.rpos == r.rsize {
if r.final { if r.final {
return 0, io.EOF return 0, io.EOF
} }
@ -216,7 +245,7 @@ func (r *tdsBuffer) Read(buf []byte) (copied int, err error) {
return return
} }
} }
copied = copy(buf, r.buf[r.pos:r.size]) copied = copy(buf, r.rbuf[r.rpos:r.rsize])
r.pos += uint16(copied) r.rpos += copied
return return
} }

@ -0,0 +1,616 @@
package mssql
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"time"
)
type Bulk struct {
cn *Conn
metadata []columnStruct
bulkColumns []columnStruct
columnsName []string
tablename string
numRows int
headerSent bool
Options BulkOptions
Debug bool
}
type BulkOptions struct {
CheckConstraints bool
FireTriggers bool
KeepNulls bool
KilobytesPerBatch int
RowsPerBatch int
Order []string
Tablock bool
}
type DataValue interface{}
func (cn *Conn) CreateBulk(table string, columns []string) (_ *Bulk) {
b := Bulk{cn: cn, tablename: table, headerSent: false, columnsName: columns}
b.Debug = false
return &b
}
func (b *Bulk) sendBulkCommand() (err error) {
//get table columns info
err = b.getMetadata()
if err != nil {
return err
}
//match the columns
for _, colname := range b.columnsName {
var bulkCol *columnStruct
for _, m := range b.metadata {
if m.ColName == colname {
bulkCol = &m
break
}
}
if bulkCol != nil {
if bulkCol.ti.TypeId == typeUdt {
//send udt as binary
bulkCol.ti.TypeId = typeBigVarBin
}
b.bulkColumns = append(b.bulkColumns, *bulkCol)
b.dlogf("Adding column %s %s %#x", colname, bulkCol.ColName, bulkCol.ti.TypeId)
} else {
return fmt.Errorf("Column %s does not exist in destination table %s", colname, b.tablename)
}
}
//create the bulk command
//columns definitions
var col_defs bytes.Buffer
for i, col := range b.bulkColumns {
if i != 0 {
col_defs.WriteString(", ")
}
col_defs.WriteString("[" + col.ColName + "] " + makeDecl(col.ti))
}
//options
var with_opts []string
if b.Options.CheckConstraints {
with_opts = append(with_opts, "CHECK_CONSTRAINTS")
}
if b.Options.FireTriggers {
with_opts = append(with_opts, "FIRE_TRIGGERS")
}
if b.Options.KeepNulls {
with_opts = append(with_opts, "KEEP_NULLS")
}
if b.Options.KilobytesPerBatch > 0 {
with_opts = append(with_opts, fmt.Sprintf("KILOBYTES_PER_BATCH = %d", b.Options.KilobytesPerBatch))
}
if b.Options.RowsPerBatch > 0 {
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, ",")))
}
if b.Options.Tablock {
with_opts = append(with_opts, "TABLOCK")
}
var with_part string
if len(with_opts) > 0 {
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)
stmt, err := b.cn.Prepare(query)
if err != nil {
return fmt.Errorf("Prepare failed: %s", err.Error())
}
b.dlogf(query)
_, err = stmt.Exec(nil)
if err != nil {
return err
}
b.headerSent = true
var buf = b.cn.sess.buf
buf.BeginPacket(packBulkLoadBCP)
// send the columns metadata
columnMetadata := b.createColMetadata()
_, err = buf.Write(columnMetadata)
return
}
// AddRow immediately writes the row to the destination table.
// The arguments are the row values in the order they were specified.
func (b *Bulk) AddRow(row []interface{}) (err error) {
if !b.headerSent {
err = b.sendBulkCommand()
if err != nil {
return
}
}
if len(row) != len(b.bulkColumns) {
return fmt.Errorf("Row does not have the same number of columns than the destination table %d %d",
len(row), len(b.bulkColumns))
}
bytes, err := b.makeRowData(row)
if err != nil {
return
}
_, err = b.cn.sess.buf.Write(bytes)
if err != nil {
return
}
b.numRows = b.numRows + 1
return
}
func (b *Bulk) makeRowData(row []interface{}) ([]byte, error) {
buf := new(bytes.Buffer)
buf.WriteByte(byte(tokenRow))
var logcol bytes.Buffer
for i, col := range b.bulkColumns {
if b.Debug {
logcol.WriteString(fmt.Sprintf(" col[%d]='%v' ", i, row[i]))
}
param, err := b.makeParam(row[i], col)
if err != nil {
return nil, fmt.Errorf("bulkcopy: %s", err.Error())
}
if col.ti.Writer == nil {
return nil, fmt.Errorf("no writer for column: %s, TypeId: %#x",
col.ColName, col.ti.TypeId)
}
err = col.ti.Writer(buf, param.ti, param.buffer)
if err != nil {
return nil, fmt.Errorf("bulkcopy: %s", err.Error())
}
}
b.dlogf("row[%d] %s\n", b.numRows, logcol.String())
return buf.Bytes(), nil
}
func (b *Bulk) Done() (rowcount int64, err error) {
if b.headerSent == false {
//no rows had been sent
return 0, nil
}
var buf = b.cn.sess.buf
buf.WriteByte(byte(tokenDone))
binary.Write(buf, binary.LittleEndian, uint16(doneFinal))
binary.Write(buf, binary.LittleEndian, uint16(0)) // curcmd
if b.cn.sess.loginAck.TDSVersion >= verTDS72 {
binary.Write(buf, binary.LittleEndian, uint64(0)) //rowcount 0
} else {
binary.Write(buf, binary.LittleEndian, uint32(0)) //rowcount 0
}
buf.FinishPacket()
tokchan := make(chan tokenStruct, 5)
go processResponse(context.Background(), b.cn.sess, tokchan, nil)
var rowCount int64
for token := range tokchan {
switch token := token.(type) {
case doneStruct:
if token.Status&doneCount != 0 {
rowCount = int64(token.RowCount)
}
if token.isError() {
return 0, token.getError()
}
case error:
return 0, b.cn.checkBadConn(token)
}
}
return rowCount, nil
}
func (b *Bulk) createColMetadata() []byte {
buf := new(bytes.Buffer)
buf.WriteByte(byte(tokenColMetadata)) // token
binary.Write(buf, binary.LittleEndian, uint16(len(b.bulkColumns))) // column count
for i, col := range b.bulkColumns {
if b.cn.sess.loginAck.TDSVersion >= verTDS72 {
binary.Write(buf, binary.LittleEndian, uint32(col.UserType)) // usertype, always 0?
} else {
binary.Write(buf, binary.LittleEndian, uint16(col.UserType))
}
binary.Write(buf, binary.LittleEndian, uint16(col.Flags))
writeTypeInfo(buf, &b.bulkColumns[i].ti)
if col.ti.TypeId == typeNText ||
col.ti.TypeId == typeText ||
col.ti.TypeId == typeImage {
tablename_ucs2 := str2ucs2(b.tablename)
binary.Write(buf, binary.LittleEndian, uint16(len(tablename_ucs2)/2))
buf.Write(tablename_ucs2)
}
colname_ucs2 := str2ucs2(col.ColName)
buf.WriteByte(uint8(len(colname_ucs2) / 2))
buf.Write(colname_ucs2)
}
return buf.Bytes()
}
func (b *Bulk) getMetadata() (err error) {
stmt, err := b.cn.Prepare("SET FMTONLY ON")
if err != nil {
return
}
_, err = stmt.Exec(nil)
if err != nil {
return
}
//get columns info
stmt, err = b.cn.Prepare(fmt.Sprintf("select * from %s SET FMTONLY OFF", b.tablename))
if err != nil {
return
}
stmt2 := stmt.(*Stmt)
cols, err := stmt2.QueryMeta()
if err != nil {
return fmt.Errorf("get columns info failed: %v", err.Error())
}
b.metadata = cols
if b.Debug {
for _, col := range b.metadata {
b.dlogf("col: %s typeId: %#x size: %d scale: %d prec: %d flags: %d lcid: %#x\n",
col.ColName, col.ti.TypeId, col.ti.Size, col.ti.Scale, col.ti.Prec,
col.Flags, col.ti.Collation.LcidAndFlags)
}
}
return nil
}
// QueryMeta is almost the same as mssql.Stmt.Query, but returns all the columns info.
func (s *Stmt) QueryMeta() (cols []columnStruct, err error) {
if err = s.sendQuery(nil); err != nil {
return
}
tokchan := make(chan tokenStruct, 5)
go processResponse(context.Background(), s.c.sess, tokchan, s.c.outs)
s.c.clearOuts()
loop:
for tok := range tokchan {
switch token := tok.(type) {
case doneStruct:
break loop
case []columnStruct:
cols = token
break loop
case error:
return nil, s.c.checkBadConn(token)
}
}
return cols, nil
}
func (b *Bulk) makeParam(val DataValue, col columnStruct) (res Param, err error) {
res.ti.Size = col.ti.Size
res.ti.TypeId = col.ti.TypeId
if val == nil {
res.ti.Size = 0
return
}
switch col.ti.TypeId {
case typeInt1, typeInt2, typeInt4, typeInt8, typeIntN:
var intvalue int64
switch val := val.(type) {
case int:
intvalue = int64(val)
case int32:
intvalue = int64(val)
case int64:
intvalue = val
default:
err = fmt.Errorf("mssql: invalid type for int column")
return
}
res.buffer = make([]byte, res.ti.Size)
if col.ti.Size == 1 {
res.buffer[0] = byte(intvalue)
} else if col.ti.Size == 2 {
binary.LittleEndian.PutUint16(res.buffer, uint16(intvalue))
} else if col.ti.Size == 4 {
binary.LittleEndian.PutUint32(res.buffer, uint32(intvalue))
} else if col.ti.Size == 8 {
binary.LittleEndian.PutUint64(res.buffer, uint64(intvalue))
}
case typeFlt4, typeFlt8, typeFltN:
var floatvalue float64
switch val := val.(type) {
case float32:
floatvalue = float64(val)
case float64:
floatvalue = val
case int:
floatvalue = float64(val)
case int64:
floatvalue = float64(val)
default:
err = fmt.Errorf("mssql: invalid type for float column: %s", val)
return
}
if col.ti.Size == 4 {
res.buffer = make([]byte, 4)
binary.LittleEndian.PutUint32(res.buffer, math.Float32bits(float32(floatvalue)))
} else if col.ti.Size == 8 {
res.buffer = make([]byte, 8)
binary.LittleEndian.PutUint64(res.buffer, math.Float64bits(floatvalue))
}
case typeNVarChar, typeNText, typeNChar:
switch val := val.(type) {
case string:
res.buffer = str2ucs2(val)
case []byte:
res.buffer = val
default:
err = fmt.Errorf("mssql: invalid type for nvarchar column: %s", val)
return
}
res.ti.Size = len(res.buffer)
case typeVarChar, typeBigVarChar, typeText, typeChar, typeBigChar:
switch val := val.(type) {
case string:
res.buffer = []byte(val)
case []byte:
res.buffer = val
default:
err = fmt.Errorf("mssql: invalid type for varchar column: %s", val)
return
}
res.ti.Size = len(res.buffer)
case typeBit, typeBitN:
if reflect.TypeOf(val).Kind() != reflect.Bool {
err = fmt.Errorf("mssql: invalid type for bit column: %s", val)
return
}
res.ti.TypeId = typeBitN
res.ti.Size = 1
res.buffer = make([]byte, 1)
if val.(bool) {
res.buffer[0] = 1
}
case typeDateTime2N, typeDateTimeOffsetN:
switch val := val.(type) {
case time.Time:
days, ns := dateTime2(val)
ns /= int64(math.Pow10(int(col.ti.Scale)*-1) * 1000000000)
var data = make([]byte, 5)
data[0] = byte(ns)
data[1] = byte(ns >> 8)
data[2] = byte(ns >> 16)
data[3] = byte(ns >> 24)
data[4] = byte(ns >> 32)
if col.ti.Scale <= 2 {
res.ti.Size = 6
} else if col.ti.Scale <= 4 {
res.ti.Size = 7
} else {
res.ti.Size = 8
}
var buf []byte
buf = make([]byte, res.ti.Size)
copy(buf, data[0:res.ti.Size-3])
buf[res.ti.Size-3] = byte(days)
buf[res.ti.Size-2] = byte(days >> 8)
buf[res.ti.Size-1] = byte(days >> 16)
if col.ti.TypeId == typeDateTimeOffsetN {
_, offset := val.Zone()
var offsetMinute = uint16(offset / 60)
buf = append(buf, byte(offsetMinute))
buf = append(buf, byte(offsetMinute>>8))
res.ti.Size = res.ti.Size + 2
}
res.buffer = buf
default:
err = fmt.Errorf("mssql: invalid type for datetime2 column: %s", val)
return
}
case typeDateN:
switch val := val.(type) {
case time.Time:
days, _ := dateTime2(val)
res.ti.Size = 3
res.buffer = make([]byte, 3)
res.buffer[0] = byte(days)
res.buffer[1] = byte(days >> 8)
res.buffer[2] = byte(days >> 16)
default:
err = fmt.Errorf("mssql: invalid type for date column: %s", val)
return
}
case typeDateTime, typeDateTimeN, typeDateTim4:
switch val := val.(type) {
case time.Time:
if col.ti.Size == 4 {
res.ti.Size = 4
res.buffer = make([]byte, 4)
ref := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
dur := val.Sub(ref)
days := dur / (24 * time.Hour)
if days < 0 {
err = fmt.Errorf("mssql: Date %s is out of range", val)
return
}
mins := val.Hour()*60 + val.Minute()
binary.LittleEndian.PutUint16(res.buffer[0:2], uint16(days))
binary.LittleEndian.PutUint16(res.buffer[2:4], uint16(mins))
} else if col.ti.Size == 8 {
res.ti.Size = 8
res.buffer = make([]byte, 8)
days := divFloor(val.Unix(), 24*60*60)
//25567 - number of days since Jan 1 1900 UTC to Jan 1 1970
days = days + 25567
tm := (val.Hour()*60*60+val.Minute()*60+val.Second())*300 + int(val.Nanosecond()/10000000*3)
binary.LittleEndian.PutUint32(res.buffer[0:4], uint32(days))
binary.LittleEndian.PutUint32(res.buffer[4:8], uint32(tm))
} else {
err = fmt.Errorf("mssql: invalid size of column")
}
default:
err = fmt.Errorf("mssql: invalid type for datetime column: %s", val)
}
// case typeMoney, typeMoney4, typeMoneyN:
case typeDecimal, typeDecimalN, typeNumeric, typeNumericN:
var value float64
switch v := val.(type) {
case int:
value = float64(v)
case int8:
value = float64(v)
case int16:
value = float64(v)
case int32:
value = float64(v)
case int64:
value = float64(v)
case float32:
value = float64(v)
case float64:
value = v
case string:
if value, err = strconv.ParseFloat(v, 64); err != nil {
return res, fmt.Errorf("bulk: unable to convert string to float: %v", err)
}
default:
return res, fmt.Errorf("unknown value for decimal: %#v", v)
}
perc := col.ti.Prec
scale := col.ti.Scale
var dec Decimal
dec, err = Float64ToDecimalScale(value, scale)
if err != nil {
return res, err
}
dec.prec = perc
var length byte
switch {
case perc <= 9:
length = 4
case perc <= 19:
length = 8
case perc <= 28:
length = 12
default:
length = 16
}
buf := make([]byte, length+1)
// first byte length written by typeInfo.writer
res.ti.Size = int(length) + 1
// second byte sign
if value < 0 {
buf[0] = 0
} else {
buf[0] = 1
}
ub := dec.UnscaledBytes()
l := len(ub)
if l > int(length) {
err = fmt.Errorf("decimal out of range: %s", dec)
return res, err
}
// reverse the bytes
for i, j := 1, l-1; j >= 0; i, j = i+1, j-1 {
buf[i] = ub[j]
}
res.buffer = buf
case typeBigVarBin:
switch val := val.(type) {
case []byte:
res.ti.Size = len(val)
res.buffer = val
default:
err = fmt.Errorf("mssql: invalid type for Binary column: %s", val)
return
}
case typeGuid:
switch val := val.(type) {
case []byte:
res.ti.Size = len(val)
res.buffer = val
default:
err = fmt.Errorf("mssql: invalid type for Guid column: %s", val)
return
}
default:
err = fmt.Errorf("mssql: type %x not implemented", col.ti.TypeId)
}
return
}
func (b *Bulk) dlogf(format string, v ...interface{}) {
if b.Debug {
b.cn.sess.log.Printf(format, v...)
}
}

@ -0,0 +1,93 @@
package mssql
import (
"context"
"database/sql/driver"
"encoding/json"
"errors"
)
type copyin struct {
cn *Conn
bulkcopy *Bulk
closed bool
}
type serializableBulkConfig struct {
TableName string
ColumnsName []string
Options BulkOptions
}
func (d *Driver) OpenConnection(dsn string) (*Conn, error) {
return d.open(context.Background(), dsn)
}
func (c *Conn) prepareCopyIn(query string) (_ driver.Stmt, err error) {
config_json := query[11:]
bulkconfig := serializableBulkConfig{}
err = json.Unmarshal([]byte(config_json), &bulkconfig)
if err != nil {
return
}
bulkcopy := c.CreateBulk(bulkconfig.TableName, bulkconfig.ColumnsName)
bulkcopy.Options = bulkconfig.Options
ci := &copyin{
cn: c,
bulkcopy: bulkcopy,
}
return ci, nil
}
func CopyIn(table string, options BulkOptions, columns ...string) string {
bulkconfig := &serializableBulkConfig{TableName: table, Options: options, ColumnsName: columns}
config_json, err := json.Marshal(bulkconfig)
if err != nil {
panic(err)
}
stmt := "INSERTBULK " + string(config_json)
return stmt
}
func (ci *copyin) NumInput() int {
return -1
}
func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) {
return nil, errors.New("ErrNotSupported")
}
func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) {
if ci.closed {
return nil, errors.New("errCopyInClosed")
}
if len(v) == 0 {
rowCount, err := ci.bulkcopy.Done()
ci.closed = true
return driver.RowsAffected(rowCount), err
}
t := make([]interface{}, len(v))
for i, val := range v {
t[i] = val
}
err = ci.bulkcopy.AddRow(t)
if err != nil {
return
}
return driver.RowsAffected(0), nil
}
func (ci *copyin) Close() (err error) {
return nil
}

@ -1,39 +0,0 @@
package mssql
import (
"encoding/binary"
"io"
)
// http://msdn.microsoft.com/en-us/library/dd340437.aspx
type collation struct {
lcidAndFlags uint32
sortId uint8
}
func (c collation) getLcid() uint32 {
return c.lcidAndFlags & 0x000fffff
}
func (c collation) getFlags() uint32 {
return (c.lcidAndFlags & 0x0ff00000) >> 20
}
func (c collation) getVersion() uint32 {
return (c.lcidAndFlags & 0xf0000000) >> 28
}
func readCollation(r *tdsBuffer) (res collation) {
res.lcidAndFlags = r.uint32()
res.sortId = r.byte()
return
}
func writeCollation(w io.Writer, col collation) (err error) {
if err = binary.Write(w, binary.LittleEndian, col.lcidAndFlags); err != nil {
return
}
err = binary.Write(w, binary.LittleEndian, col.sortId)
return
}

@ -32,7 +32,13 @@ func (d Decimal) ToFloat64() float64 {
return val return val
} }
const autoScale = 100
func Float64ToDecimal(f float64) (Decimal, error) { func Float64ToDecimal(f float64) (Decimal, error) {
return Float64ToDecimalScale(f, autoScale)
}
func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) {
var dec Decimal var dec Decimal
if math.IsNaN(f) { if math.IsNaN(f) {
return dec, errors.New("NaN") return dec, errors.New("NaN")
@ -49,10 +55,10 @@ func Float64ToDecimal(f float64) (Decimal, error) {
} }
dec.prec = 20 dec.prec = 20
var integer float64 var integer float64
for dec.scale = 0; dec.scale <= 20; dec.scale++ { for dec.scale = 0; dec.scale <= scale; dec.scale++ {
integer = f * scaletblflt64[dec.scale] integer = f * scaletblflt64[dec.scale]
_, frac := math.Modf(integer) _, frac := math.Modf(integer)
if frac == 0 { if frac == 0 && scale == autoScale {
break break
} }
} }
@ -73,7 +79,7 @@ func init() {
} }
} }
func (d Decimal) Bytes() []byte { func (d Decimal) BigInt() big.Int {
bytes := make([]byte, 16) bytes := make([]byte, 16)
binary.BigEndian.PutUint32(bytes[0:4], d.integer[3]) binary.BigEndian.PutUint32(bytes[0:4], d.integer[3])
binary.BigEndian.PutUint32(bytes[4:8], d.integer[2]) binary.BigEndian.PutUint32(bytes[4:8], d.integer[2])
@ -84,9 +90,19 @@ func (d Decimal) Bytes() []byte {
if !d.positive { if !d.positive {
x.Neg(&x) x.Neg(&x)
} }
return x
}
func (d Decimal) Bytes() []byte {
x := d.BigInt()
return scaleBytes(x.String(), d.scale) return scaleBytes(x.String(), d.scale)
} }
func (d Decimal) UnscaledBytes() []byte {
x := d.BigInt()
return x.Bytes()
}
func scaleBytes(s string, scale uint8) []byte { func scaleBytes(s string, scale uint8) []byte {
z := make([]byte, 0, len(s)+1) z := make([]byte, 0, len(s)+1)
if s[0] == '-' || s[0] == '+' { if s[0] == '-' || s[0] == '+' {

@ -0,0 +1,12 @@
// package mssql implements the TDS protocol used to connect to MS SQL Server (sqlserver)
// database servers.
//
// This package registers two drivers:
// sqlserver: uses native "@" parameter placeholder names and does no pre-processing.
// mssql: expects identifiers to be prefixed with ":" and pre-processes queries.
//
// If the ordinal position is used for query parameters, identifiers will be named
// "@p1", "@p2", ... "@pN".
//
// Please refer to the README for the format of the DSN.
package mssql

@ -1,14 +1,14 @@
package mssql package cp
type charsetMap struct { type charsetMap struct {
sb [256]rune // single byte runes, -1 for a double byte character lead byte sb [256]rune // single byte runes, -1 for a double byte character lead byte
db map[int]rune // double byte runes db map[int]rune // double byte runes
} }
func collation2charset(col collation) *charsetMap { func collation2charset(col Collation) *charsetMap {
// http://msdn.microsoft.com/en-us/library/ms144250.aspx // http://msdn.microsoft.com/en-us/library/ms144250.aspx
// http://msdn.microsoft.com/en-us/library/ms144250(v=sql.105).aspx // http://msdn.microsoft.com/en-us/library/ms144250(v=sql.105).aspx
switch col.sortId { switch col.SortId {
case 30, 31, 32, 33, 34: case 30, 31, 32, 33, 34:
return cp437 return cp437
case 40, 41, 42, 44, 49, 55, 56, 57, 58, 59, 60, 61: case 40, 41, 42, 44, 49, 55, 56, 57, 58, 59, 60, 61:
@ -86,7 +86,7 @@ func collation2charset(col collation) *charsetMap {
return cp1252 return cp1252
} }
func charset2utf8(col collation, s []byte) string { func CharsetToUTF8(col Collation, s []byte) string {
cm := collation2charset(col) cm := collation2charset(col)
if cm == nil { if cm == nil {
return string(s) return string(s)

@ -0,0 +1,20 @@
package cp
// http://msdn.microsoft.com/en-us/library/dd340437.aspx
type Collation struct {
LcidAndFlags uint32
SortId uint8
}
func (c Collation) getLcid() uint32 {
return c.LcidAndFlags & 0x000fffff
}
func (c Collation) getFlags() uint32 {
return (c.LcidAndFlags & 0x0ff00000) >> 20
}
func (c Collation) getVersion() uint32 {
return (c.LcidAndFlags & 0xf0000000) >> 28
}

@ -1,4 +1,4 @@
package mssql package cp
var cp1250 *charsetMap = &charsetMap{ var cp1250 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp1251 *charsetMap = &charsetMap{ var cp1251 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp1252 *charsetMap = &charsetMap{ var cp1252 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp1253 *charsetMap = &charsetMap{ var cp1253 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp1254 *charsetMap = &charsetMap{ var cp1254 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp1255 *charsetMap = &charsetMap{ var cp1255 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp1256 *charsetMap = &charsetMap{ var cp1256 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp1257 *charsetMap = &charsetMap{ var cp1257 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp1258 *charsetMap = &charsetMap{ var cp1258 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp437 *charsetMap = &charsetMap{ var cp437 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp850 *charsetMap = &charsetMap{ var cp850 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp874 *charsetMap = &charsetMap{ var cp874 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp932 *charsetMap = &charsetMap{ var cp932 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp936 *charsetMap = &charsetMap{ var cp936 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp949 *charsetMap = &charsetMap{ var cp949 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -1,4 +1,4 @@
package mssql package cp
var cp950 *charsetMap = &charsetMap{ var cp950 *charsetMap = &charsetMap{
sb: [256]rune{ sb: [256]rune{

@ -4,19 +4,26 @@ import (
"log" "log"
) )
type Logger log.Logger type Logger interface {
Printf(format string, v ...interface{})
Println(v ...interface{})
}
type optionalLogger struct {
logger Logger
}
func (logger *Logger) Printf(format string, v ...interface{}) { func (o optionalLogger) Printf(format string, v ...interface{}) {
if logger != nil { if o.logger != nil {
(*log.Logger)(logger).Printf(format, v...) o.logger.Printf(format, v...)
} else { } else {
log.Printf(format, v...) log.Printf(format, v...)
} }
} }
func (logger *Logger) Println(v ...interface{}) { func (o optionalLogger) Println(v ...interface{}) {
if logger != nil { if o.logger != nil {
(*log.Logger)(logger).Println(v...) o.logger.Println(v...)
} else { } else {
log.Println(v...) log.Println(v...)
} }

@ -1,122 +1,269 @@
package mssql package mssql
import ( import (
"context"
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"math" "math"
"net" "net"
"reflect"
"strings" "strings"
"time" "time"
) )
var driverInstance = &Driver{processQueryText: true}
var driverInstanceNoProcess = &Driver{processQueryText: false}
func init() { func init() {
sql.Register("mssql", &MssqlDriver{}) sql.Register("mssql", driverInstance)
sql.Register("sqlserver", driverInstanceNoProcess)
createDialer = func(p *connectParams) dialer {
return tcpDialer{&net.Dialer{Timeout: p.dial_timeout, KeepAlive: p.keepAlive}}
}
}
// Abstract the dialer for testing and for non-TCP based connections.
type dialer interface {
Dial(ctx context.Context, addr string) (net.Conn, error)
}
var createDialer func(p *connectParams) dialer
type tcpDialer struct {
nd *net.Dialer
}
func (d tcpDialer) Dial(ctx context.Context, addr string) (net.Conn, error) {
return d.nd.DialContext(ctx, "tcp", addr)
}
type Driver struct {
log optionalLogger
processQueryText bool
}
// OpenConnector opens a new connector. Useful to dial with a context.
func (d *Driver) OpenConnector(dsn string) (*Connector, error) {
params, err := parseConnectParams(dsn)
if err != nil {
return nil, err
}
return &Connector{
params: params,
driver: d,
}, nil
}
func (d *Driver) Open(dsn string) (driver.Conn, error) {
return d.open(context.Background(), dsn)
}
// Connector holds the parsed DSN and is ready to make a new connection
// at any time.
//
// In the future, settings that cannot be passed through a string DSN
// may be set directly on the connector.
type Connector struct {
params connectParams
driver *Driver
} }
type MssqlDriver struct { // Connect to the server and return a TDS connection.
log *log.Logger func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) {
return c.driver.connect(ctx, c.params)
} }
func (d *MssqlDriver) SetLogger(logger *log.Logger) { // Driver underlying the Connector.
d.log = logger func (c *Connector) Driver() driver.Driver {
return c.driver
} }
func CheckBadConn(err error) error { func SetLogger(logger Logger) {
if err == io.EOF { driverInstance.SetLogger(logger)
driverInstanceNoProcess.SetLogger(logger)
}
func (d *Driver) SetLogger(logger Logger) {
d.log = optionalLogger{logger}
}
type Conn struct {
sess *tdsSession
transactionCtx context.Context
processQueryText bool
connectionGood bool
outs map[string]interface{}
}
func (c *Conn) checkBadConn(err error) error {
// this is a hack to address Issue #275
// we set connectionGood flag to false if
// error indicates that connection is not usable
// but we return actual error instead of ErrBadConn
// this will cause connection to stay in a pool
// but next request to this connection will return ErrBadConn
// it might be possible to revise this hack after
// https://github.com/golang/go/issues/20807
// is implemented
switch err {
case nil:
return nil
case io.EOF:
return driver.ErrBadConn return driver.ErrBadConn
case driver.ErrBadConn:
// It is an internal programming error if driver.ErrBadConn
// is ever passed to this function. driver.ErrBadConn should
// only ever be returned in response to a *mssql.Conn.connectionGood == false
// check in the external facing API.
panic("driver.ErrBadConn in checkBadConn. This should not happen.")
} }
switch e := err.(type) { switch err.(type) {
case net.Error: case net.Error:
if e.Timeout() { c.connectionGood = false
return e return err
} case StreamError:
return driver.ErrBadConn c.connectionGood = false
return err
default: default:
return err return err
} }
} }
type MssqlConn struct { func (c *Conn) clearOuts() {
sess *tdsSession c.outs = nil
} }
func (c *MssqlConn) Commit() error { func (c *Conn) simpleProcessResp(ctx context.Context) error {
headers := []headerStruct{
{hdrtype: dataStmHdrTransDescr,
data: transDescrHdr{c.sess.tranid, 1}.pack()},
}
if err := sendCommitXact(c.sess.buf, headers, "", 0, 0, ""); err != nil {
return err
}
tokchan := make(chan tokenStruct, 5) tokchan := make(chan tokenStruct, 5)
go processResponse(c.sess, tokchan) go processResponse(ctx, c.sess, tokchan, c.outs)
c.clearOuts()
for tok := range tokchan { for tok := range tokchan {
switch token := tok.(type) { switch token := tok.(type) {
case doneStruct:
if token.isError() {
return c.checkBadConn(token.getError())
}
case error: case error:
return token return c.checkBadConn(token)
} }
} }
return nil return nil
} }
func (c *MssqlConn) Rollback() error { func (c *Conn) Commit() error {
if !c.connectionGood {
return driver.ErrBadConn
}
if err := c.sendCommitRequest(); err != nil {
return c.checkBadConn(err)
}
return c.simpleProcessResp(c.transactionCtx)
}
func (c *Conn) sendCommitRequest() error {
headers := []headerStruct{ headers := []headerStruct{
{hdrtype: dataStmHdrTransDescr, {hdrtype: dataStmHdrTransDescr,
data: transDescrHdr{c.sess.tranid, 1}.pack()}, data: transDescrHdr{c.sess.tranid, 1}.pack()},
} }
if err := sendRollbackXact(c.sess.buf, headers, "", 0, 0, ""); err != nil { if err := sendCommitXact(c.sess.buf, headers, "", 0, 0, ""); err != nil {
return err if c.sess.logFlags&logErrors != 0 {
c.sess.log.Printf("Failed to send CommitXact with %v", err)
}
c.connectionGood = false
return fmt.Errorf("Faild to send CommitXact: %v", err)
}
return nil
}
func (c *Conn) Rollback() error {
if !c.connectionGood {
return driver.ErrBadConn
}
if err := c.sendRollbackRequest(); err != nil {
return c.checkBadConn(err)
} }
return c.simpleProcessResp(c.transactionCtx)
}
tokchan := make(chan tokenStruct, 5) func (c *Conn) sendRollbackRequest() error {
go processResponse(c.sess, tokchan) headers := []headerStruct{
for tok := range tokchan { {hdrtype: dataStmHdrTransDescr,
switch token := tok.(type) { data: transDescrHdr{c.sess.tranid, 1}.pack()},
case error: }
return token if err := sendRollbackXact(c.sess.buf, headers, "", 0, 0, ""); err != nil {
if c.sess.logFlags&logErrors != 0 {
c.sess.log.Printf("Failed to send RollbackXact with %v", err)
} }
c.connectionGood = false
return fmt.Errorf("Failed to send RollbackXact: %v", err)
} }
return nil return nil
} }
func (c *MssqlConn) Begin() (driver.Tx, error) { func (c *Conn) Begin() (driver.Tx, error) {
return c.begin(context.Background(), isolationUseCurrent)
}
func (c *Conn) begin(ctx context.Context, tdsIsolation isoLevel) (tx driver.Tx, err error) {
if !c.connectionGood {
return nil, driver.ErrBadConn
}
err = c.sendBeginRequest(ctx, tdsIsolation)
if err != nil {
return nil, c.checkBadConn(err)
}
tx, err = c.processBeginResponse(ctx)
if err != nil {
return nil, c.checkBadConn(err)
}
return
}
func (c *Conn) sendBeginRequest(ctx context.Context, tdsIsolation isoLevel) error {
c.transactionCtx = ctx
headers := []headerStruct{ headers := []headerStruct{
{hdrtype: dataStmHdrTransDescr, {hdrtype: dataStmHdrTransDescr,
data: transDescrHdr{0, 1}.pack()}, data: transDescrHdr{0, 1}.pack()},
} }
if err := sendBeginXact(c.sess.buf, headers, 0, ""); err != nil { if err := sendBeginXact(c.sess.buf, headers, tdsIsolation, ""); err != nil {
return nil, CheckBadConn(err) if c.sess.logFlags&logErrors != 0 {
} c.sess.log.Printf("Failed to send BeginXact with %v", err)
tokchan := make(chan tokenStruct, 5)
go processResponse(c.sess, tokchan)
for tok := range tokchan {
switch token := tok.(type) {
case error:
if c.sess.tranid != 0 {
return nil, token
}
return nil, CheckBadConn(token)
} }
c.connectionGood = false
return fmt.Errorf("Failed to send BiginXant: %v", err)
}
return nil
}
func (c *Conn) processBeginResponse(ctx context.Context) (driver.Tx, error) {
if err := c.simpleProcessResp(ctx); err != nil {
return nil, err
} }
// successful BEGINXACT request will return sess.tranid // successful BEGINXACT request will return sess.tranid
// for started transaction // for started transaction
return c, nil return c, nil
} }
func (d *MssqlDriver) Open(dsn string) (driver.Conn, error) { func (d *Driver) open(ctx context.Context, dsn string) (*Conn, error) {
params, err := parseConnectParams(dsn) params, err := parseConnectParams(dsn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return d.connect(ctx, params)
}
sess, err := connect(params) // connect to the server, using the provided context for dialing only.
func (d *Driver) connect(ctx context.Context, params connectParams) (*Conn, error) {
sess, err := connect(ctx, d.log, params)
if err != nil { if err != nil {
// main server failed, try fail-over partner // main server failed, try fail-over partner
if params.failOverPartner == "" { if params.failOverPartner == "" {
@ -128,24 +275,29 @@ func (d *MssqlDriver) Open(dsn string) (driver.Conn, error) {
params.port = params.failOverPort params.port = params.failOverPort
} }
sess, err = connect(params) sess, err = connect(ctx, d.log, params)
if err != nil { if err != nil {
// fail-over partner also failed, now fail // fail-over partner also failed, now fail
return nil, err return nil, err
} }
} }
conn := &MssqlConn{sess} conn := &Conn{
conn.sess.log = (*Logger)(d.log) sess: sess,
transactionCtx: context.Background(),
processQueryText: d.processQueryText,
connectionGood: true,
}
conn.sess.log = d.log
return conn, nil return conn, nil
} }
func (c *MssqlConn) Close() error { func (c *Conn) Close() error {
return c.sess.buf.transport.Close() return c.sess.buf.transport.Close()
} }
type MssqlStmt struct { type Stmt struct {
c *MssqlConn c *Conn
query string query string
paramCount int paramCount int
notifSub *queryNotifSub notifSub *queryNotifSub
@ -157,16 +309,30 @@ type queryNotifSub struct {
timeout uint32 timeout uint32
} }
func (c *MssqlConn) Prepare(query string) (driver.Stmt, error) { func (c *Conn) Prepare(query string) (driver.Stmt, error) {
q, paramCount := parseParams(query) if !c.connectionGood {
return &MssqlStmt{c, q, paramCount, nil}, nil return nil, driver.ErrBadConn
}
if len(query) > 10 && strings.EqualFold(query[:10], "INSERTBULK") {
return c.prepareCopyIn(query)
}
return c.prepareContext(context.Background(), query)
}
func (c *Conn) prepareContext(ctx context.Context, query string) (*Stmt, error) {
paramCount := -1
if c.processQueryText {
query, paramCount = parseParams(query)
}
return &Stmt{c, query, paramCount, nil}, nil
} }
func (s *MssqlStmt) Close() error { func (s *Stmt) Close() error {
return nil return nil
} }
func (s *MssqlStmt) SetQueryNotification(id, options string, timeout time.Duration) { func (s *Stmt) SetQueryNotification(id, options string, timeout time.Duration) {
to := uint32(timeout / time.Second) to := uint32(timeout / time.Second)
if to < 1 { if to < 1 {
to = 1 to = 1
@ -174,183 +340,273 @@ func (s *MssqlStmt) SetQueryNotification(id, options string, timeout time.Durati
s.notifSub = &queryNotifSub{id, options, to} s.notifSub = &queryNotifSub{id, options, to}
} }
func (s *MssqlStmt) NumInput() int { func (s *Stmt) NumInput() int {
return s.paramCount return s.paramCount
} }
func (s *MssqlStmt) sendQuery(args []driver.Value) (err error) { func (s *Stmt) sendQuery(args []namedValue) (err error) {
headers := []headerStruct{ headers := []headerStruct{
{hdrtype: dataStmHdrTransDescr, {hdrtype: dataStmHdrTransDescr,
data: transDescrHdr{s.c.sess.tranid, 1}.pack()}, data: transDescrHdr{s.c.sess.tranid, 1}.pack()},
} }
if s.notifSub != nil { if s.notifSub != nil {
headers = append(headers, headerStruct{hdrtype: dataStmHdrQueryNotif, headers = append(headers,
data: queryNotifHdr{s.notifSub.msgText, s.notifSub.options, s.notifSub.timeout}.pack()}) headerStruct{
} hdrtype: dataStmHdrQueryNotif,
data: queryNotifHdr{
if len(args) != s.paramCount { s.notifSub.msgText,
return errors.New(fmt.Sprintf("sql: expected %d parameters, got %d", s.paramCount, len(args))) s.notifSub.options,
} s.notifSub.timeout,
}.pack(),
})
}
// no need to check number of parameters here, it is checked by database/sql
if s.c.sess.logFlags&logSQL != 0 { if s.c.sess.logFlags&logSQL != 0 {
s.c.sess.log.Println(s.query) s.c.sess.log.Println(s.query)
} }
if s.c.sess.logFlags&logParams != 0 && len(args) > 0 { if s.c.sess.logFlags&logParams != 0 && len(args) > 0 {
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
s.c.sess.log.Printf("\t@p%d\t%v\n", i+1, args[i]) if len(args[i].Name) > 0 {
s.c.sess.log.Printf("\t@%s\t%v\n", args[i].Name, args[i].Value)
} else {
s.c.sess.log.Printf("\t@p%d\t%v\n", i+1, args[i].Value)
}
} }
} }
if len(args) == 0 { if len(args) == 0 {
if err = sendSqlBatch72(s.c.sess.buf, s.query, headers); err != nil { if err = sendSqlBatch72(s.c.sess.buf, s.query, headers); err != nil {
if s.c.sess.tranid != 0 { if s.c.sess.logFlags&logErrors != 0 {
return err s.c.sess.log.Printf("Failed to send SqlBatch with %v", err)
} }
return CheckBadConn(err) s.c.connectionGood = false
return fmt.Errorf("failed to send SQL Batch: %v", err)
} }
} else { } else {
params := make([]Param, len(args)+2) proc := Sp_ExecuteSql
decls := make([]string, len(args)) var params []Param
params[0], err = s.makeParam(s.query) if isProc(s.query) {
if err != nil { proc.name = s.query
return params, _, err = s.makeRPCParams(args, 0)
} } else {
for i, val := range args { var decls []string
params[i+2], err = s.makeParam(val) params, decls, err = s.makeRPCParams(args, 2)
if err != nil { if err != nil {
return return
} }
name := fmt.Sprintf("@p%d", i+1) params[0] = makeStrParam(s.query)
params[i+2].Name = name params[1] = makeStrParam(strings.Join(decls, ","))
decls[i] = fmt.Sprintf("%s %s", name, makeDecl(params[i+2].ti))
}
params[1], err = s.makeParam(strings.Join(decls, ","))
if err != nil {
return
} }
if err = sendRpc(s.c.sess.buf, headers, Sp_ExecuteSql, 0, params); err != nil { if err = sendRpc(s.c.sess.buf, headers, proc, 0, params); err != nil {
if s.c.sess.tranid != 0 { if s.c.sess.logFlags&logErrors != 0 {
return err s.c.sess.log.Printf("Failed to send Rpc with %v", err)
} }
return CheckBadConn(err) s.c.connectionGood = false
return fmt.Errorf("Failed to send RPC: %v", err)
} }
} }
return return
} }
func (s *MssqlStmt) Query(args []driver.Value) (res driver.Rows, err error) { // isProc takes the query text in s and determines if it is a stored proc name
// or SQL text.
func isProc(s string) bool {
if len(s) == 0 {
return false
}
if s[0] == '[' && s[len(s)-1] == ']' && strings.ContainsAny(s, "\n\r") == false {
return true
}
return !strings.ContainsAny(s, " \t\n\r;")
}
func (s *Stmt) makeRPCParams(args []namedValue, offset int) ([]Param, []string, error) {
var err error
params := make([]Param, len(args)+offset)
decls := make([]string, len(args))
for i, val := range args {
params[i+offset], err = s.makeParam(val.Value)
if err != nil {
return nil, nil, err
}
var name string
if len(val.Name) > 0 {
name = "@" + val.Name
} else {
name = fmt.Sprintf("@p%d", val.Ordinal)
}
params[i+offset].Name = name
decls[i] = fmt.Sprintf("%s %s", name, makeDecl(params[i+offset].ti))
}
return params, decls, nil
}
type namedValue struct {
Name string
Ordinal int
Value driver.Value
}
func convertOldArgs(args []driver.Value) []namedValue {
list := make([]namedValue, len(args))
for i, v := range args {
list[i] = namedValue{
Ordinal: i + 1,
Value: v,
}
}
return list
}
func (s *Stmt) Query(args []driver.Value) (driver.Rows, error) {
return s.queryContext(context.Background(), convertOldArgs(args))
}
func (s *Stmt) queryContext(ctx context.Context, args []namedValue) (rows driver.Rows, err error) {
if !s.c.connectionGood {
return nil, driver.ErrBadConn
}
if err = s.sendQuery(args); err != nil { if err = s.sendQuery(args); err != nil {
return return nil, s.c.checkBadConn(err)
} }
return s.processQueryResponse(ctx)
}
func (s *Stmt) processQueryResponse(ctx context.Context) (res driver.Rows, err error) {
tokchan := make(chan tokenStruct, 5) tokchan := make(chan tokenStruct, 5)
go processResponse(s.c.sess, tokchan) ctx, cancel := context.WithCancel(ctx)
go processResponse(ctx, s.c.sess, tokchan, s.c.outs)
s.c.clearOuts()
// process metadata // process metadata
var cols []string var cols []columnStruct
loop: loop:
for tok := range tokchan { for tok := range tokchan {
switch token := tok.(type) { switch token := tok.(type) {
// by ignoring DONE token we effectively // By ignoring DONE token we effectively
// skip empty result-sets // skip empty result-sets.
// this improves results in queryes like that: // This improves results in queries like that:
// set nocount on; select 1 // set nocount on; select 1
// see TestIgnoreEmptyResults test // see TestIgnoreEmptyResults test
//case doneStruct: //case doneStruct:
//break loop //break loop
case []columnStruct: case []columnStruct:
cols = make([]string, len(token)) cols = token
for i, col := range token {
cols[i] = col.ColName
}
break loop break loop
case error: case doneStruct:
if s.c.sess.tranid != 0 { if token.isError() {
return nil, token return nil, s.c.checkBadConn(token.getError())
} }
return nil, CheckBadConn(token) case error:
return nil, s.c.checkBadConn(token)
} }
} }
return &MssqlRows{sess: s.c.sess, tokchan: tokchan, cols: cols}, nil res = &Rows{stmt: s, tokchan: tokchan, cols: cols, cancel: cancel}
return
} }
func (s *MssqlStmt) Exec(args []driver.Value) (res driver.Result, err error) { func (s *Stmt) Exec(args []driver.Value) (driver.Result, error) {
return s.exec(context.Background(), convertOldArgs(args))
}
func (s *Stmt) exec(ctx context.Context, args []namedValue) (res driver.Result, err error) {
if !s.c.connectionGood {
return nil, driver.ErrBadConn
}
if err = s.sendQuery(args); err != nil { if err = s.sendQuery(args); err != nil {
return return nil, s.c.checkBadConn(err)
}
if res, err = s.processExec(ctx); err != nil {
return nil, s.c.checkBadConn(err)
} }
return
}
func (s *Stmt) processExec(ctx context.Context) (res driver.Result, err error) {
tokchan := make(chan tokenStruct, 5) tokchan := make(chan tokenStruct, 5)
go processResponse(s.c.sess, tokchan) go processResponse(ctx, s.c.sess, tokchan, s.c.outs)
s.c.clearOuts()
var rowCount int64 var rowCount int64
for token := range tokchan { for token := range tokchan {
switch token := token.(type) { switch token := token.(type) {
case doneInProcStruct: case doneInProcStruct:
if token.Status&doneCount != 0 { if token.Status&doneCount != 0 {
rowCount = int64(token.RowCount) rowCount += int64(token.RowCount)
} }
case doneStruct: case doneStruct:
if token.Status&doneCount != 0 { if token.Status&doneCount != 0 {
rowCount = int64(token.RowCount) rowCount += int64(token.RowCount)
} }
case error: if token.isError() {
if s.c.sess.logFlags&logErrors != 0 { return nil, token.getError()
s.c.sess.log.Println("got error:", token)
}
if s.c.sess.tranid != 0 {
return nil, token
} }
return nil, CheckBadConn(token) case error:
return nil, token
} }
} }
return &MssqlResult{s.c, rowCount}, nil return &Result{s.c, rowCount}, nil
} }
type MssqlRows struct { type Rows struct {
sess *tdsSession stmt *Stmt
cols []string cols []columnStruct
tokchan chan tokenStruct tokchan chan tokenStruct
nextCols []string nextCols []columnStruct
cancel func()
} }
func (rc *MssqlRows) Close() error { func (rc *Rows) Close() error {
rc.cancel()
for _ = range rc.tokchan { for _ = range rc.tokchan {
} }
rc.tokchan = nil rc.tokchan = nil
return nil return nil
} }
func (rc *MssqlRows) Columns() (res []string) { func (rc *Rows) Columns() (res []string) {
return rc.cols res = make([]string, len(rc.cols))
for i, col := range rc.cols {
res[i] = col.ColName
}
return
} }
func (rc *MssqlRows) Next(dest []driver.Value) (err error) { func (rc *Rows) Next(dest []driver.Value) error {
if !rc.stmt.c.connectionGood {
return driver.ErrBadConn
}
if rc.nextCols != nil { if rc.nextCols != nil {
return io.EOF return io.EOF
} }
for tok := range rc.tokchan { for tok := range rc.tokchan {
switch tokdata := tok.(type) { switch tokdata := tok.(type) {
case []columnStruct: case []columnStruct:
cols := make([]string, len(tokdata)) rc.nextCols = tokdata
for i, col := range tokdata {
cols[i] = col.ColName
}
rc.nextCols = cols
return io.EOF return io.EOF
case []interface{}: case []interface{}:
for i := range dest { for i := range dest {
dest[i] = tokdata[i] dest[i] = tokdata[i]
} }
return nil return nil
case doneStruct:
if tokdata.isError() {
return rc.stmt.c.checkBadConn(tokdata.getError())
}
case error: case error:
return tokdata return rc.stmt.c.checkBadConn(tokdata)
} }
} }
return io.EOF return io.EOF
} }
func (rc *MssqlRows) HasNextResultSet() bool { func (rc *Rows) HasNextResultSet() bool {
return rc.nextCols != nil return rc.nextCols != nil
} }
func (rc *MssqlRows) NextResultSet() error { func (rc *Rows) NextResultSet() error {
rc.cols = rc.nextCols rc.cols = rc.nextCols
rc.nextCols = nil rc.nextCols = nil
if rc.cols == nil { if rc.cols == nil {
@ -359,11 +615,69 @@ func (rc *MssqlRows) NextResultSet() error {
return nil return nil
} }
func (s *MssqlStmt) makeParam(val driver.Value) (res Param, err error) { // It should return
// the value type that can be used to scan types into. For example, the database
// column type "bigint" this should return "reflect.TypeOf(int64(0))".
func (r *Rows) ColumnTypeScanType(index int) reflect.Type {
return makeGoLangScanType(r.cols[index].ti)
}
// RowsColumnTypeDatabaseTypeName may be implemented by Rows. It should return the
// database system type name without the length. Type names should be uppercase.
// Examples of returned types: "VARCHAR", "NVARCHAR", "VARCHAR2", "CHAR", "TEXT",
// "DECIMAL", "SMALLINT", "INT", "BIGINT", "BOOL", "[]BIGINT", "JSONB", "XML",
// "TIMESTAMP".
func (r *Rows) ColumnTypeDatabaseTypeName(index int) string {
return makeGoLangTypeName(r.cols[index].ti)
}
// RowsColumnTypeLength may be implemented by Rows. It should return the length
// of the column type if the column is a variable length type. If the column is
// not a variable length type ok should return false.
// If length is not limited other than system limits, it should return math.MaxInt64.
// The following are examples of returned values for various types:
// TEXT (math.MaxInt64, true)
// varchar(10) (10, true)
// nvarchar(10) (10, true)
// decimal (0, false)
// int (0, false)
// bytea(30) (30, true)
func (r *Rows) ColumnTypeLength(index int) (int64, bool) {
return makeGoLangTypeLength(r.cols[index].ti)
}
// It should return
// the precision and scale for decimal types. If not applicable, ok should be false.
// The following are examples of returned values for various types:
// decimal(38, 4) (38, 4, true)
// int (0, 0, false)
// decimal (math.MaxInt64, math.MaxInt64, true)
func (r *Rows) ColumnTypePrecisionScale(index int) (int64, int64, bool) {
return makeGoLangTypePrecisionScale(r.cols[index].ti)
}
// The nullable value should
// be true if it is known the column may be null, or false if the column is known
// to be not nullable.
// If the column nullability is unknown, ok should be false.
func (r *Rows) ColumnTypeNullable(index int) (nullable, ok bool) {
nullable = r.cols[index].Flags&colFlagNullable != 0
ok = true
return
}
func makeStrParam(val string) (res Param) {
res.ti.TypeId = typeNVarChar
res.buffer = str2ucs2(val)
res.ti.Size = len(res.buffer)
return
}
func (s *Stmt) makeParam(val driver.Value) (res Param, err error) {
if val == nil { if val == nil {
res.ti.TypeId = typeNVarChar res.ti.TypeId = typeNull
res.buffer = nil res.buffer = nil
res.ti.Size = 2 res.ti.Size = 0
return return
} }
switch val := val.(type) { switch val := val.(type) {
@ -382,9 +696,7 @@ func (s *MssqlStmt) makeParam(val driver.Value) (res Param, err error) {
res.ti.Size = len(val) res.ti.Size = len(val)
res.buffer = val res.buffer = val
case string: case string:
res.ti.TypeId = typeNVarChar res = makeStrParam(val)
res.buffer = str2ucs2(val)
res.ti.Size = len(res.buffer)
case bool: case bool:
res.ti.TypeId = typeBitN res.ti.TypeId = typeBitN
res.ti.Size = 1 res.ti.Size = 1
@ -425,22 +737,21 @@ func (s *MssqlStmt) makeParam(val driver.Value) (res Param, err error) {
binary.LittleEndian.PutUint32(res.buffer[4:8], uint32(tm)) binary.LittleEndian.PutUint32(res.buffer[4:8], uint32(tm))
} }
default: default:
err = fmt.Errorf("mssql: unknown type for %T", val) return s.makeParamExtra(val)
return
} }
return return
} }
type MssqlResult struct { type Result struct {
c *MssqlConn c *Conn
rowsAffected int64 rowsAffected int64
} }
func (r *MssqlResult) RowsAffected() (int64, error) { func (r *Result) RowsAffected() (int64, error) {
return r.rowsAffected, nil return r.rowsAffected, nil
} }
func (r *MssqlResult) LastInsertId() (int64, error) { func (r *Result) LastInsertId() (int64, error) {
s, err := r.c.Prepare("select cast(@@identity as bigint)") s, err := r.c.Prepare("select cast(@@identity as bigint)")
if err != nil { if err != nil {
return 0, err return 0, err

@ -1,11 +0,0 @@
// +build go1.3
package mssql
import (
"net"
)
func createDialer(p connectParams) *net.Dialer {
return &net.Dialer{Timeout: p.dial_timeout, KeepAlive: p.keepAlive}
}

@ -1,11 +0,0 @@
// +build !go1.3
package mssql
import (
"net"
)
func createDialer(p *connectParams) *net.Dialer {
return &net.Dialer{Timeout: p.dial_timeout}
}

@ -0,0 +1,91 @@
// +build go1.8
package mssql
import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"strings"
)
var _ driver.Pinger = &Conn{}
// Ping is used to check if the remote server is available and satisfies the Pinger interface.
func (c *Conn) Ping(ctx context.Context) error {
if !c.connectionGood {
return driver.ErrBadConn
}
stmt := &Stmt{c, `select 1;`, 0, nil}
_, err := stmt.ExecContext(ctx, nil)
return err
}
var _ driver.ConnBeginTx = &Conn{}
// BeginTx satisfies ConnBeginTx.
func (c *Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if !c.connectionGood {
return nil, driver.ErrBadConn
}
if opts.ReadOnly {
return nil, errors.New("Read-only transactions are not supported")
}
var tdsIsolation isoLevel
switch sql.IsolationLevel(opts.Isolation) {
case sql.LevelDefault:
tdsIsolation = isolationUseCurrent
case sql.LevelReadUncommitted:
tdsIsolation = isolationReadUncommited
case sql.LevelReadCommitted:
tdsIsolation = isolationReadCommited
case sql.LevelWriteCommitted:
return nil, errors.New("LevelWriteCommitted isolation level is not supported")
case sql.LevelRepeatableRead:
tdsIsolation = isolationRepeatableRead
case sql.LevelSnapshot:
tdsIsolation = isolationSnapshot
case sql.LevelSerializable:
tdsIsolation = isolationSerializable
case sql.LevelLinearizable:
return nil, errors.New("LevelLinearizable isolation level is not supported")
default:
return nil, errors.New("Isolation level is not supported or unknown")
}
return c.begin(ctx, tdsIsolation)
}
func (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
if !c.connectionGood {
return nil, driver.ErrBadConn
}
if len(query) > 10 && strings.EqualFold(query[:10], "INSERTBULK") {
return c.prepareCopyIn(query)
}
return c.prepareContext(ctx, query)
}
func (s *Stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
if !s.c.connectionGood {
return nil, driver.ErrBadConn
}
list := make([]namedValue, len(args))
for i, nv := range args {
list[i] = namedValue(nv)
}
return s.queryContext(ctx, list)
}
func (s *Stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
if !s.c.connectionGood {
return nil, driver.ErrBadConn
}
list := make([]namedValue, len(args))
for i, nv := range args {
list[i] = namedValue(nv)
}
return s.exec(ctx, list)
}

@ -0,0 +1,64 @@
// +build go1.9
package mssql
import (
"database/sql"
"database/sql/driver"
"fmt"
// "github.com/cockroachdb/apd"
)
// Type alias provided for compibility.
//
// Deprecated: users should transition to the new names when possible.
type MssqlDriver = Driver
type MssqlBulk = Bulk
type MssqlBulkOptions = BulkOptions
type MssqlConn = Conn
type MssqlResult = Result
type MssqlRows = Rows
type MssqlStmt = Stmt
var _ driver.NamedValueChecker = &Conn{}
func (c *Conn) CheckNamedValue(nv *driver.NamedValue) error {
switch v := nv.Value.(type) {
case sql.Out:
if c.outs == nil {
c.outs = make(map[string]interface{})
}
c.outs[nv.Name] = v.Dest
// Unwrap the Out value and check the inner value.
lnv := *nv
lnv.Value = v.Dest
err := c.CheckNamedValue(&lnv)
if err != nil {
if err != driver.ErrSkip {
return err
}
lnv.Value, err = driver.DefaultParameterConverter.ConvertValue(lnv.Value)
if err != nil {
return err
}
}
nv.Value = sql.Out{Dest: lnv.Value}
return nil
// case *apd.Decimal:
// return nil
default:
return driver.ErrSkip
}
}
func (s *Stmt) makeParamExtra(val driver.Value) (res Param, err error) {
switch val := val.(type) {
case sql.Out:
res, err = s.makeParam(val.Dest)
res.Flags = fByRevValue
default:
err = fmt.Errorf("mssql: unknown type for %T", val)
}
return
}

@ -0,0 +1,12 @@
// +build !go1.9
package mssql
import (
"database/sql/driver"
"fmt"
)
func (s *Stmt) makeParamExtra(val driver.Value) (Param, error) {
return Param{}, fmt.Errorf("mssql: unknown type for %T", val)
}

@ -33,7 +33,7 @@ func (c *timeoutConn) Read(b []byte) (n int, err error) {
c.continueRead = false c.continueRead = false
} }
if !c.continueRead { if !c.continueRead {
var packet uint8 var packet packetType
packet, err = c.buf.BeginRead() packet, err = c.buf.BeginRead()
if err != nil { if err != nil {
err = fmt.Errorf("Cannot read handshake packet: %s", err.Error()) err = fmt.Errorf("Cannot read handshake packet: %s", err.Error())

@ -59,7 +59,7 @@ type NTLMAuth struct {
Workstation string Workstation string
} }
func getAuth(user, password, service, workstation string) (Auth, bool) { func getAuth(user, password, service, workstation string) (auth, bool) {
if !strings.ContainsRune(user, '\\') { if !strings.ContainsRune(user, '\\') {
return nil, false return nil, false
} }

@ -11,6 +11,9 @@ type parser struct {
w bytes.Buffer w bytes.Buffer
paramCount int paramCount int
paramMax int paramMax int
// using map as a set
namedParams map[string]bool
} }
func (p *parser) next() (rune, bool) { func (p *parser) next() (rune, bool) {
@ -39,13 +42,14 @@ type stateFunc func(*parser) stateFunc
func parseParams(query string) (string, int) { func parseParams(query string) (string, int) {
p := &parser{ p := &parser{
r: bytes.NewReader([]byte(query)), r: bytes.NewReader([]byte(query)),
namedParams: map[string]bool{},
} }
state := parseNormal state := parseNormal
for state != nil { for state != nil {
state = state(p) state = state(p)
} }
return p.w.String(), p.paramMax return p.w.String(), p.paramMax + len(p.namedParams)
} }
func parseNormal(p *parser) stateFunc { func parseNormal(p *parser) stateFunc {
@ -55,7 +59,7 @@ func parseNormal(p *parser) stateFunc {
return nil return nil
} }
if ch == '?' { if ch == '?' {
return parseParameter return parseOrdinalParameter
} else if ch == '$' || ch == ':' { } else if ch == '$' || ch == ':' {
ch2, ok := p.next() ch2, ok := p.next()
if !ok { if !ok {
@ -64,7 +68,9 @@ func parseNormal(p *parser) stateFunc {
} }
p.unread() p.unread()
if ch2 >= '0' && ch2 <= '9' { if ch2 >= '0' && ch2 <= '9' {
return parseParameter return parseOrdinalParameter
} else if 'a' <= ch2 && ch2 <= 'z' || 'A' <= ch2 && ch2 <= 'Z' {
return parseNamedParameter
} }
} }
p.write(ch) p.write(ch)
@ -83,7 +89,7 @@ func parseNormal(p *parser) stateFunc {
} }
} }
func parseParameter(p *parser) stateFunc { func parseOrdinalParameter(p *parser) stateFunc {
var paramN int var paramN int
var ok bool var ok bool
for { for {
@ -113,6 +119,30 @@ func parseParameter(p *parser) stateFunc {
return parseNormal return parseNormal
} }
func parseNamedParameter(p *parser) stateFunc {
var paramName string
var ok bool
for {
var ch rune
ch, ok = p.next()
if ok && (ch >= '0' && ch <= '9' || 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z') {
paramName = paramName + string(ch)
} else {
break
}
}
if ok {
p.unread()
}
p.namedParams[paramName] = true
p.w.WriteString("@")
p.w.WriteString(paramName)
if !ok {
return nil
}
return parseNormal
}
func parseQuote(p *parser) stateFunc { func parseQuote(p *parser) stateFunc {
for { for {
ch, ok := p.next() ch, ok := p.next()

@ -113,7 +113,7 @@ type SSPIAuth struct {
ctxt SecHandle ctxt SecHandle
} }
func getAuth(user, password, service, workstation string) (Auth, bool) { func getAuth(user, password, service, workstation string) (auth, bool) {
if user == "" { if user == "" {
return &SSPIAuth{Service: service}, true return &SSPIAuth{Service: service}, true
} }

@ -1,6 +1,7 @@
package mssql package mssql
import ( import (
"context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/binary" "encoding/binary"
@ -9,11 +10,13 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/url"
"os" "os"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"unicode/utf16" "unicode/utf16"
"unicode/utf8" "unicode/utf8"
) )
@ -47,8 +50,11 @@ func parseInstances(msg []byte) map[string]map[string]string {
return results return results
} }
func getInstances(address string) (map[string]map[string]string, error) { func getInstances(ctx context.Context, address string) (map[string]map[string]string, error) {
conn, err := net.DialTimeout("udp", address+":1434", 5*time.Second) dialer := &net.Dialer{
Timeout: 5 * time.Second,
}
conn, err := dialer.DialContext(ctx, "udp", address+":1434")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -79,11 +85,16 @@ const (
) )
// packet types // packet types
// https://msdn.microsoft.com/en-us/library/dd304214.aspx
const ( const (
packSQLBatch = 1 packSQLBatch packetType = 1
packRPCRequest = 3 packRPCRequest = 3
packReply = 4 packReply = 4
packCancel = 6
// 2.2.1.7 Attention: https://msdn.microsoft.com/en-us/library/dd341449.aspx
// 4.19.2 Out-of-Band Attention Signal: https://msdn.microsoft.com/en-us/library/dd305167.aspx
packAttention = 6
packBulkLoadBCP = 7 packBulkLoadBCP = 7
packTransMgrReq = 14 packTransMgrReq = 14
packNormal = 15 packNormal = 15
@ -119,7 +130,7 @@ type tdsSession struct {
columns []columnStruct columns []columnStruct
tranid uint64 tranid uint64
logFlags uint64 logFlags uint64
log *Logger log optionalLogger
routedServer string routedServer string
routedPort uint16 routedPort uint16
} }
@ -131,6 +142,7 @@ const (
logSQL = 8 logSQL = 8
logParams = 16 logParams = 16
logTransaction = 32 logTransaction = 32
logDebug = 64
) )
type columnStruct struct { type columnStruct struct {
@ -490,6 +502,11 @@ func readBVarChar(r io.Reader) (res string, err error) {
if err != nil { if err != nil {
return "", err return "", err
} }
// A zero length could be returned, return an empty string
if numchars == 0 {
return "", nil
}
return readUcs2(r, int(numchars)) return readUcs2(r, int(numchars))
} }
@ -588,7 +605,7 @@ func (hdr transDescrHdr) pack() (res []byte) {
} }
func writeAllHeaders(w io.Writer, headers []headerStruct) (err error) { func writeAllHeaders(w io.Writer, headers []headerStruct) (err error) {
// calculatint total length // Calculating total length.
var totallen uint32 = 4 var totallen uint32 = 4
for _, hdr := range headers { for _, hdr := range headers {
totallen += 4 + 2 + uint32(len(hdr.data)) totallen += 4 + 2 + uint32(len(hdr.data))
@ -616,9 +633,7 @@ func writeAllHeaders(w io.Writer, headers []headerStruct) (err error) {
return nil return nil
} }
func sendSqlBatch72(buf *tdsBuffer, func sendSqlBatch72(buf *tdsBuffer, sqltext string, headers []headerStruct) (err error) {
sqltext string,
headers []headerStruct) (err error) {
buf.BeginPacket(packSQLBatch) buf.BeginPacket(packSQLBatch)
if err = writeAllHeaders(buf, headers); err != nil { if err = writeAllHeaders(buf, headers); err != nil {
@ -632,6 +647,13 @@ func sendSqlBatch72(buf *tdsBuffer,
return buf.FinishPacket() return buf.FinishPacket()
} }
// 2.2.1.7 Attention: https://msdn.microsoft.com/en-us/library/dd341449.aspx
// 4.19.2 Out-of-Band Attention Signal: https://msdn.microsoft.com/en-us/library/dd305167.aspx
func sendAttention(buf *tdsBuffer) error {
buf.BeginPacket(packAttention)
return buf.FinishPacket()
}
type connectParams struct { type connectParams struct {
logFlags uint64 logFlags uint64
port uint64 port uint64
@ -654,6 +676,7 @@ type connectParams struct {
typeFlags uint8 typeFlags uint8
failOverPartner string failOverPartner string
failOverPort uint64 failOverPort uint64
packetSize uint16
} }
func splitConnectionString(dsn string) (res map[string]string) { func splitConnectionString(dsn string) (res map[string]string) {
@ -677,9 +700,241 @@ func splitConnectionString(dsn string) (res map[string]string) {
return res return res
} }
// Splits a URL in the ODBC format
func splitConnectionStringOdbc(dsn string) (map[string]string, error) {
res := map[string]string{}
type parserState int
const (
// Before the start of a key
parserStateBeforeKey parserState = iota
// Inside a key
parserStateKey
// Beginning of a value. May be bare or braced
parserStateBeginValue
// Inside a bare value
parserStateBareValue
// Inside a braced value
parserStateBracedValue
// A closing brace inside a braced value.
// May be the end of the value or an escaped closing brace, depending on the next character
parserStateBracedValueClosingBrace
// After a value. Next character should be a semicolon or whitespace.
parserStateEndValue
)
var state = parserStateBeforeKey
var key string
var value string
for i, c := range dsn {
switch state {
case parserStateBeforeKey:
switch {
case c == '=':
return res, fmt.Errorf("Unexpected character = at index %d. Expected start of key or semi-colon or whitespace.", i)
case !unicode.IsSpace(c) && c != ';':
state = parserStateKey
key += string(c)
}
case parserStateKey:
switch c {
case '=':
key = normalizeOdbcKey(key)
if len(key) == 0 {
return res, fmt.Errorf("Unexpected end of key at index %d.", i)
}
state = parserStateBeginValue
case ';':
// Key without value
key = normalizeOdbcKey(key)
if len(key) == 0 {
return res, fmt.Errorf("Unexpected end of key at index %d.", i)
}
res[key] = value
key = ""
value = ""
state = parserStateBeforeKey
default:
key += string(c)
}
case parserStateBeginValue:
switch {
case c == '{':
state = parserStateBracedValue
case c == ';':
// Empty value
res[key] = value
key = ""
state = parserStateBeforeKey
case unicode.IsSpace(c):
// Ignore whitespace
default:
state = parserStateBareValue
value += string(c)
}
case parserStateBareValue:
if c == ';' {
res[key] = strings.TrimRightFunc(value, unicode.IsSpace)
key = ""
value = ""
state = parserStateBeforeKey
} else {
value += string(c)
}
case parserStateBracedValue:
if c == '}' {
state = parserStateBracedValueClosingBrace
} else {
value += string(c)
}
case parserStateBracedValueClosingBrace:
if c == '}' {
// Escaped closing brace
value += string(c)
state = parserStateBracedValue
continue
}
// End of braced value
res[key] = value
key = ""
value = ""
// This character is the first character past the end,
// so it needs to be parsed like the parserStateEndValue state.
state = parserStateEndValue
switch {
case c == ';':
state = parserStateBeforeKey
case unicode.IsSpace(c):
// Ignore whitespace
default:
return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i)
}
case parserStateEndValue:
switch {
case c == ';':
state = parserStateBeforeKey
case unicode.IsSpace(c):
// Ignore whitespace
default:
return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i)
}
}
}
switch state {
case parserStateBeforeKey: // Okay
case parserStateKey: // Unfinished key. Treat as key without value.
key = normalizeOdbcKey(key)
if len(key) == 0 {
return res, fmt.Errorf("Unexpected end of key at index %d.", len(dsn))
}
res[key] = value
case parserStateBeginValue: // Empty value
res[key] = value
case parserStateBareValue:
res[key] = strings.TrimRightFunc(value, unicode.IsSpace)
case parserStateBracedValue:
return res, fmt.Errorf("Unexpected end of braced value at index %d.", len(dsn))
case parserStateBracedValueClosingBrace: // End of braced value
res[key] = value
case parserStateEndValue: // Okay
}
return res, nil
}
// Normalizes the given string as an ODBC-format key
func normalizeOdbcKey(s string) string {
return strings.ToLower(strings.TrimRightFunc(s, unicode.IsSpace))
}
// Splits a URL of the form sqlserver://username:password@host/instance?param1=value&param2=value
func splitConnectionStringURL(dsn string) (map[string]string, error) {
res := map[string]string{}
u, err := url.Parse(dsn)
if err != nil {
return res, err
}
if u.Scheme != "sqlserver" {
return res, fmt.Errorf("scheme %s is not recognized", u.Scheme)
}
if u.User != nil {
res["user id"] = u.User.Username()
p, exists := u.User.Password()
if exists {
res["password"] = p
}
}
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
host = u.Host
}
if len(u.Path) > 0 {
res["server"] = host + "\\" + u.Path[1:]
} else {
res["server"] = host
}
if len(port) > 0 {
res["port"] = port
}
query := u.Query()
for k, v := range query {
if len(v) > 1 {
return res, fmt.Errorf("key %s provided more than once", k)
}
res[strings.ToLower(k)] = v[0]
}
return res, nil
}
func parseConnectParams(dsn string) (connectParams, error) { func parseConnectParams(dsn string) (connectParams, error) {
params := splitConnectionString(dsn)
var p connectParams var p connectParams
var params map[string]string
if strings.HasPrefix(dsn, "odbc:") {
parameters, err := splitConnectionStringOdbc(dsn[len("odbc:"):])
if err != nil {
return p, err
}
params = parameters
} else if strings.HasPrefix(dsn, "sqlserver://") {
parameters, err := splitConnectionStringURL(dsn)
if err != nil {
return p, err
}
params = parameters
} else {
params = splitConnectionString(dsn)
}
strlog, ok := params["log"] strlog, ok := params["log"]
if ok { if ok {
var err error var err error
@ -712,7 +967,32 @@ func parseConnectParams(dsn string) (connectParams, error) {
} }
} }
p.dial_timeout = 5 * time.Second // https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option
// Default packet size remains at 4096 bytes
p.packetSize = 4096
strpsize, ok := params["packet size"]
if ok {
var err error
psize, err := strconv.ParseUint(strpsize, 0, 16)
if err != nil {
f := "Invalid packet size '%v': %v"
return p, fmt.Errorf(f, strpsize, err.Error())
}
// Ensure packet size falls within the TDS protocol range of 512 to 32767 bytes
// NOTE: Encrypted connections have a maximum size of 16383 bytes. If you request
// a higher packet size, the server will respond with an ENVCHANGE request to
// alter the packet size to 16383 bytes.
p.packetSize = uint16(psize)
if p.packetSize < 512 {
p.packetSize = 512
} else if p.packetSize > 32767 {
p.packetSize = 32767
}
}
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
p.dial_timeout = 15 * time.Second
p.conn_timeout = 30 * time.Second p.conn_timeout = 30 * time.Second
strconntimeout, ok := params["connection timeout"] strconntimeout, ok := params["connection timeout"]
if ok { if ok {
@ -732,8 +1012,12 @@ func parseConnectParams(dsn string) (connectParams, error) {
} }
p.dial_timeout = time.Duration(timeout) * time.Second p.dial_timeout = time.Duration(timeout) * time.Second
} }
keepAlive, ok := params["keepalive"]
if ok { // default keep alive should be 30 seconds according to spec:
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
p.keepAlive = 30 * time.Second
if keepAlive, ok := params["keepalive"]; ok {
timeout, err := strconv.ParseUint(keepAlive, 0, 16) timeout, err := strconv.ParseUint(keepAlive, 0, 16)
if err != nil { if err != nil {
f := "Invalid keepAlive value '%s': %s" f := "Invalid keepAlive value '%s': %s"
@ -743,7 +1027,7 @@ func parseConnectParams(dsn string) (connectParams, error) {
} }
encrypt, ok := params["encrypt"] encrypt, ok := params["encrypt"]
if ok { if ok {
if strings.ToUpper(encrypt) == "DISABLE" { if strings.EqualFold(encrypt, "DISABLE") {
p.disableEncryption = true p.disableEncryption = true
} else { } else {
var err error var err error
@ -819,7 +1103,7 @@ func parseConnectParams(dsn string) (connectParams, error) {
return p, nil return p, nil
} }
type Auth interface { type auth interface {
InitialBytes() ([]byte, error) InitialBytes() ([]byte, error)
NextBytes([]byte) ([]byte, error) NextBytes([]byte) ([]byte, error)
Free() Free()
@ -828,7 +1112,7 @@ type Auth interface {
// SQL Server AlwaysOn Availability Group Listeners are bound by DNS to a // SQL Server AlwaysOn Availability Group Listeners are bound by DNS to a
// list of IP addresses. So if there is more than one, try them all and // list of IP addresses. So if there is more than one, try them all and
// use the first one that allows a connection. // use the first one that allows a connection.
func dialConnection(p connectParams) (conn net.Conn, err error) { func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err error) {
var ips []net.IP var ips []net.IP
ips, err = net.LookupIP(p.host) ips, err = net.LookupIP(p.host)
if err != nil { if err != nil {
@ -839,9 +1123,9 @@ func dialConnection(p connectParams) (conn net.Conn, err error) {
ips = []net.IP{ip} ips = []net.IP{ip}
} }
if len(ips) == 1 { if len(ips) == 1 {
d := createDialer(p) d := createDialer(&p)
addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(p.port))) addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(p.port)))
conn, err = d.Dial("tcp", addr) conn, err = d.Dial(ctx, addr)
} else { } else {
//Try Dials in parallel to avoid waiting for timeouts. //Try Dials in parallel to avoid waiting for timeouts.
@ -850,9 +1134,9 @@ func dialConnection(p connectParams) (conn net.Conn, err error) {
portStr := strconv.Itoa(int(p.port)) portStr := strconv.Itoa(int(p.port))
for _, ip := range ips { for _, ip := range ips {
go func(ip net.IP) { go func(ip net.IP) {
d := createDialer(p) d := createDialer(&p)
addr := net.JoinHostPort(ip.String(), portStr) addr := net.JoinHostPort(ip.String(), portStr)
conn, err := d.Dial("tcp", addr) conn, err := d.Dial(ctx, addr)
if err == nil { if err == nil {
connChan <- conn connChan <- conn
} else { } else {
@ -887,16 +1171,15 @@ func dialConnection(p connectParams) (conn net.Conn, err error) {
f := "Unable to open tcp connection with host '%v:%v': %v" f := "Unable to open tcp connection with host '%v:%v': %v"
return nil, fmt.Errorf(f, p.host, p.port, err.Error()) return nil, fmt.Errorf(f, p.host, p.port, err.Error())
} }
return conn, err return conn, err
} }
func connect(p connectParams) (res *tdsSession, err error) { func connect(ctx context.Context, log optionalLogger, p connectParams) (res *tdsSession, err error) {
res = nil res = nil
// if instance is specified use instance resolution service // if instance is specified use instance resolution service
if p.instance != "" { if p.instance != "" {
p.instance = strings.ToUpper(p.instance) p.instance = strings.ToUpper(p.instance)
instances, err := getInstances(p.host) instances, err := getInstances(ctx, p.host)
if err != nil { if err != nil {
f := "Unable to get instances from Sql Server Browser on host %v: %v" f := "Unable to get instances from Sql Server Browser on host %v: %v"
return nil, fmt.Errorf(f, p.host, err.Error()) return nil, fmt.Errorf(f, p.host, err.Error())
@ -914,16 +1197,17 @@ func connect(p connectParams) (res *tdsSession, err error) {
} }
initiate_connection: initiate_connection:
conn, err := dialConnection(p) conn, err := dialConnection(ctx, p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
toconn := NewTimeoutConn(conn, p.conn_timeout) toconn := NewTimeoutConn(conn, p.conn_timeout)
outbuf := newTdsBuffer(4096, toconn) outbuf := newTdsBuffer(p.packetSize, toconn)
sess := tdsSession{ sess := tdsSession{
buf: outbuf, buf: outbuf,
log: log,
logFlags: p.logFlags, logFlags: p.logFlags,
} }
@ -969,8 +1253,7 @@ initiate_connection:
if p.certificate != "" { if p.certificate != "" {
pem, err := ioutil.ReadFile(p.certificate) pem, err := ioutil.ReadFile(p.certificate)
if err != nil { if err != nil {
f := "Cannot read certificate '%s': %s" return nil, fmt.Errorf("Cannot read certificate %q: %v", p.certificate, err)
return nil, fmt.Errorf(f, p.certificate, err.Error())
} }
certs := x509.NewCertPool() certs := x509.NewCertPool()
certs.AppendCertsFromPEM(pem) certs.AppendCertsFromPEM(pem)
@ -980,15 +1263,20 @@ initiate_connection:
config.InsecureSkipVerify = true config.InsecureSkipVerify = true
} }
config.ServerName = p.hostInCertificate config.ServerName = p.hostInCertificate
// fix for https://github.com/denisenkom/go-mssqldb/issues/166
// Go implementation of TLS payload size heuristic algorithm splits single TDS package to multiple TCP segments,
// while SQL Server seems to expect one TCP segment per encrypted TDS package.
// Setting DynamicRecordSizingDisabled to true disables that algorithm and uses 16384 bytes per TLS package
config.DynamicRecordSizingDisabled = true
outbuf.transport = conn outbuf.transport = conn
toconn.buf = outbuf toconn.buf = outbuf
tlsConn := tls.Client(toconn, &config) tlsConn := tls.Client(toconn, &config)
err = tlsConn.Handshake() err = tlsConn.Handshake()
toconn.buf = nil toconn.buf = nil
outbuf.transport = tlsConn outbuf.transport = tlsConn
if err != nil { if err != nil {
f := "TLS Handshake failed: %s" return nil, fmt.Errorf("TLS Handshake failed: %v", err)
return nil, fmt.Errorf(f, err.Error())
} }
if encrypt == encryptOff { if encrypt == encryptOff {
outbuf.afterFirst = func() { outbuf.afterFirst = func() {
@ -999,7 +1287,7 @@ initiate_connection:
login := login{ login := login{
TDSVersion: verTDS74, TDSVersion: verTDS74,
PacketSize: uint32(len(outbuf.buf)), PacketSize: uint32(outbuf.PackageSize()),
Database: p.database, Database: p.database,
OptionFlags2: fODBC, // to get unlimited TEXTSIZE OptionFlags2: fODBC, // to get unlimited TEXTSIZE
HostName: p.workstation, HostName: p.workstation,
@ -1028,7 +1316,7 @@ initiate_connection:
var sspi_msg []byte var sspi_msg []byte
continue_login: continue_login:
tokchan := make(chan tokenStruct, 5) tokchan := make(chan tokenStruct, 5)
go processResponse(&sess, tokchan) go processResponse(context.Background(), &sess, tokchan, nil)
success := false success := false
for tok := range tokchan { for tok := range tokchan {
switch token := tok.(type) { switch token := tok.(type) {
@ -1042,6 +1330,10 @@ continue_login:
sess.loginAck = token sess.loginAck = token
case error: case error:
return nil, fmt.Errorf("Login error: %s", token.Error()) return nil, fmt.Errorf("Login error: %s", token.Error())
case doneStruct:
if token.isError() {
return nil, fmt.Errorf("Login error: %s", token.getError())
}
} }
} }
if sspi_msg != nil { if sspi_msg != nil {

@ -1,30 +1,40 @@
package mssql package mssql
import ( import (
"context"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"io" "io"
"net"
"strconv" "strconv"
"strings" "strings"
) )
//go:generate stringer -type token
type token byte
// token ids // token ids
const ( const (
tokenReturnStatus = 121 // 0x79 tokenReturnStatus token = 121 // 0x79
tokenColMetadata = 129 // 0x81 tokenColMetadata token = 129 // 0x81
tokenOrder = 169 // 0xA9 tokenOrder token = 169 // 0xA9
tokenError = 170 // 0xAA tokenError token = 170 // 0xAA
tokenInfo = 171 // 0xAB tokenInfo token = 171 // 0xAB
tokenLoginAck = 173 // 0xad tokenReturnValue token = 0xAC
tokenRow = 209 // 0xd1 tokenLoginAck token = 173 // 0xad
tokenNbcRow = 210 // 0xd2 tokenRow token = 209 // 0xd1
tokenEnvChange = 227 // 0xE3 tokenNbcRow token = 210 // 0xd2
tokenSSPI = 237 // 0xED tokenEnvChange token = 227 // 0xE3
tokenDone = 253 // 0xFD tokenSSPI token = 237 // 0xED
tokenDoneProc = 254 tokenDone token = 253 // 0xFD
tokenDoneInProc = 255 tokenDoneProc token = 254
tokenDoneInProc token = 255
) )
// done flags // done flags
// https://msdn.microsoft.com/en-us/library/dd340421.aspx
const ( const (
doneFinal = 0 doneFinal = 0
doneMore = 1 doneMore = 1
@ -59,6 +69,13 @@ const (
envRouting = 20 envRouting = 20
) )
// COLMETADATA flags
// https://msdn.microsoft.com/en-us/library/dd357363.aspx
const (
colFlagNullable = 1
// TODO implement more flags
)
// interface for all tokens // interface for all tokens
type tokenStruct interface{} type tokenStruct interface{}
@ -70,6 +87,19 @@ type doneStruct struct {
Status uint16 Status uint16
CurCmd uint16 CurCmd uint16
RowCount uint64 RowCount uint64
errors []Error
}
func (d doneStruct) isError() bool {
return d.Status&doneError != 0 || len(d.errors) > 0
}
func (d doneStruct) getError() Error {
if len(d.errors) > 0 {
return d.errors[len(d.errors)-1]
} else {
return Error{Message: "Request failed but didn't provide reason"}
}
} }
type doneInProcStruct doneStruct type doneInProcStruct doneStruct
@ -120,27 +150,23 @@ func processEnvChg(sess *tdsSession) {
badStreamPanic(err) badStreamPanic(err)
} }
case envTypLanguage: case envTypLanguage:
//currently ignored // currently ignored
// old value
_, err = readBVarChar(r)
if err != nil {
badStreamPanic(err)
}
// new value // new value
_, err = readBVarChar(r) if _, err = readBVarChar(r); err != nil {
if err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
case envTypCharset:
//currently ignored
// old value // old value
_, err = readBVarChar(r) if _, err = readBVarChar(r); err != nil {
if err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
case envTypCharset:
// currently ignored
// new value // new value
_, err = readBVarChar(r) if _, err = readBVarChar(r); err != nil {
if err != nil { badStreamPanic(err)
}
// old value
if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
case envTypPacketSize: case envTypPacketSize:
@ -156,38 +182,55 @@ func processEnvChg(sess *tdsSession) {
if err != nil { if err != nil {
badStreamPanicf("Invalid Packet size value returned from server (%s): %s", packetsize, err.Error()) badStreamPanicf("Invalid Packet size value returned from server (%s): %s", packetsize, err.Error())
} }
if len(sess.buf.buf) != packetsizei { sess.buf.ResizeBuffer(packetsizei)
newbuf := make([]byte, packetsizei)
copy(newbuf, sess.buf.buf)
sess.buf.buf = newbuf
}
case envSortId: case envSortId:
// currently ignored // currently ignored
// old value, should be 0 // new value
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
// new value // old value, should be 0
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
case envSortFlags: case envSortFlags:
// currently ignored // currently ignored
// old value, should be 0 // new value
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
// new value // old value, should be 0
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
case envSqlCollation: case envSqlCollation:
// currently ignored // currently ignored
// old value var collationSize uint8
if _, err = readBVarChar(r); err != nil { err = binary.Read(r, binary.LittleEndian, &collationSize)
if err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
// new value
// SQL Collation data should contain 5 bytes in length
if collationSize != 5 {
badStreamPanicf("Invalid SQL Collation size value returned from server: %s", collationSize)
}
// 4 bytes, contains: LCID ColFlags Version
var info uint32
err = binary.Read(r, binary.LittleEndian, &info)
if err != nil {
badStreamPanic(err)
}
// 1 byte, contains: sortID
var sortID uint8
err = binary.Read(r, binary.LittleEndian, &sortID)
if err != nil {
badStreamPanic(err)
}
// old value, should be 0
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
@ -226,21 +269,21 @@ func processEnvChg(sess *tdsSession) {
sess.tranid = 0 sess.tranid = 0
case envEnlistDTC: case envEnlistDTC:
// currently ignored // currently ignored
// old value // new value, should be 0
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
// new value, should be 0 // old value
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
case envDefectTran: case envDefectTran:
// currently ignored // currently ignored
// old value, should be 0 // new value
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
// new value // old value, should be 0
if _, err = readBVarChar(r); err != nil { if _, err = readBVarChar(r); err != nil {
badStreamPanic(err) badStreamPanic(err)
} }
@ -358,6 +401,7 @@ func parseOrder(r *tdsBuffer) (res orderStruct) {
return res return res
} }
// https://msdn.microsoft.com/en-us/library/dd340421.aspx
func parseDone(r *tdsBuffer) (res doneStruct) { func parseDone(r *tdsBuffer) (res doneStruct) {
res.Status = r.uint16() res.Status = r.uint16()
res.CurCmd = r.uint16() res.CurCmd = r.uint16()
@ -365,6 +409,7 @@ func parseDone(r *tdsBuffer) (res doneStruct) {
return res return res
} }
// https://msdn.microsoft.com/en-us/library/dd340553.aspx
func parseDoneInProc(r *tdsBuffer) (res doneInProcStruct) { func parseDoneInProc(r *tdsBuffer) (res doneInProcStruct) {
res.Status = r.uint16() res.Status = r.uint16()
res.CurCmd = r.uint16() res.CurCmd = r.uint16()
@ -473,26 +518,57 @@ func parseInfo(r *tdsBuffer) (res Error) {
return return
} }
func processResponse(sess *tdsSession, ch chan tokenStruct) { // https://msdn.microsoft.com/en-us/library/dd303881.aspx
func parseReturnValue(r *tdsBuffer) (nv namedValue) {
/*
ParamOrdinal
ParamName
Status
UserType
Flags
TypeInfo
CryptoMetadata
Value
*/
r.uint16()
nv.Name = r.BVarChar()
r.byte()
r.uint32() // UserType (uint16 prior to 7.2)
r.uint16()
ti := readTypeInfo(r)
nv.Value = ti.Reader(&ti, r)
return
}
func processSingleResponse(sess *tdsSession, ch chan tokenStruct, outs map[string]interface{}) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
if sess.logFlags&logErrors != 0 {
sess.log.Printf("ERROR: Intercepted panic %v", err)
}
ch <- err ch <- err
} }
close(ch) close(ch)
}() }()
packet_type, err := sess.buf.BeginRead() packet_type, err := sess.buf.BeginRead()
if err != nil { if err != nil {
if sess.logFlags&logErrors != 0 {
sess.log.Printf("ERROR: BeginRead failed %v", err)
}
ch <- err ch <- err
return return
} }
if packet_type != packReply { if packet_type != packReply {
badStreamPanicf("invalid response packet type, expected REPLY, actual: %d", packet_type) badStreamPanic(fmt.Errorf("unexpected packet type in reply: got %v, expected %v", packet_type, packReply))
} }
var columns []columnStruct var columns []columnStruct
var lastError Error errs := make([]Error, 0, 5)
var failed bool
for { for {
token := sess.buf.byte() token := token(sess.buf.byte())
if sess.logFlags&logDebug != 0 {
sess.log.Printf("got token %v", token)
}
switch token { switch token {
case tokenSSPI: case tokenSSPI:
ch <- parseSSPIMsg(sess.buf) ch <- parseSSPIMsg(sess.buf)
@ -514,18 +590,17 @@ func processResponse(sess *tdsSession, ch chan tokenStruct) {
ch <- done ch <- done
case tokenDone, tokenDoneProc: case tokenDone, tokenDoneProc:
done := parseDone(sess.buf) done := parseDone(sess.buf)
if sess.logFlags&logRows != 0 && done.Status&doneCount != 0 { done.errors = errs
sess.log.Printf("(%d row(s) affected)\n", done.RowCount) if sess.logFlags&logDebug != 0 {
} sess.log.Printf("got DONE or DONEPROC status=%d", done.Status)
if done.Status&doneError != 0 || failed {
ch <- lastError
return
} }
if done.Status&doneSrvError != 0 { if done.Status&doneSrvError != 0 {
lastError.Message = "Server Error" ch <- errors.New("SQL Server had internal error")
ch <- lastError
return return
} }
if sess.logFlags&logRows != 0 && done.Status&doneCount != 0 {
sess.log.Printf("(%d row(s) affected)\n", done.RowCount)
}
ch <- done ch <- done
if done.Status&doneMore == 0 { if done.Status&doneMore == 0 {
return return
@ -544,18 +619,210 @@ func processResponse(sess *tdsSession, ch chan tokenStruct) {
case tokenEnvChange: case tokenEnvChange:
processEnvChg(sess) processEnvChg(sess)
case tokenError: case tokenError:
lastError = parseError72(sess.buf) err := parseError72(sess.buf)
failed = true if sess.logFlags&logDebug != 0 {
sess.log.Printf("got ERROR %d %s", err.Number, err.Message)
}
errs = append(errs, err)
if sess.logFlags&logErrors != 0 { if sess.logFlags&logErrors != 0 {
sess.log.Println(lastError.Message) sess.log.Println(err.Message)
} }
case tokenInfo: case tokenInfo:
info := parseInfo(sess.buf) info := parseInfo(sess.buf)
if sess.logFlags&logDebug != 0 {
sess.log.Printf("got INFO %d %s", info.Number, info.Message)
}
if sess.logFlags&logMessages != 0 { if sess.logFlags&logMessages != 0 {
sess.log.Println(info.Message) sess.log.Println(info.Message)
} }
case tokenReturnValue:
nv := parseReturnValue(sess.buf)
if len(nv.Name) > 0 {
name := nv.Name[1:] // Remove the leading "@".
if ov, has := outs[name]; has {
err = scanIntoOut(nv.Value, ov)
if err != nil {
fmt.Println("scan error", err)
ch <- err
}
}
}
default:
badStreamPanic(fmt.Errorf("unknown token type returned: %v", token))
}
}
}
func scanIntoOut(fromServer, scanInto interface{}) error {
switch fs := fromServer.(type) {
case int64:
switch si := scanInto.(type) {
case *int64:
*si = fs
default: default:
badStreamPanicf("Unknown token type: %d", token) return fmt.Errorf("unsupported scan into type %[1]T for server type %[2]T", scanInto, fromServer)
}
return nil
case string:
switch si := scanInto.(type) {
case *string:
*si = fs
default:
return fmt.Errorf("unsupported scan into type %[1]T for server type %[2]T", scanInto, fromServer)
}
return nil
}
return fmt.Errorf("unsupported type from server %[1]T=%[1]v", fromServer)
}
type parseRespIter byte
const (
parseRespIterContinue parseRespIter = iota // Continue parsing current token.
parseRespIterNext // Fetch the next token.
parseRespIterDone // Done with parsing the response.
)
type parseRespState byte
const (
parseRespStateNormal parseRespState = iota // Normal response state.
parseRespStateCancel // Query is canceled, wait for server to confirm.
parseRespStateClosing // Waiting for tokens to come through.
)
type parseResp struct {
sess *tdsSession
ctxDone <-chan struct{}
state parseRespState
cancelError error
}
func (ts *parseResp) sendAttention(ch chan tokenStruct) parseRespIter {
if err := sendAttention(ts.sess.buf); err != nil {
ts.dlogf("failed to send attention signal %v", err)
ch <- err
return parseRespIterDone
}
ts.state = parseRespStateCancel
return parseRespIterContinue
}
func (ts *parseResp) dlog(msg string) {
if ts.sess.logFlags&logDebug != 0 {
ts.sess.log.Println(msg)
}
}
func (ts *parseResp) dlogf(f string, v ...interface{}) {
if ts.sess.logFlags&logDebug != 0 {
ts.sess.log.Printf(f, v...)
}
}
func (ts *parseResp) iter(ctx context.Context, ch chan tokenStruct, tokChan chan tokenStruct) parseRespIter {
switch ts.state {
default:
panic("unknown state")
case parseRespStateNormal:
select {
case tok, ok := <-tokChan:
if !ok {
ts.dlog("response finished")
return parseRespIterDone
}
if err, ok := tok.(net.Error); ok && err.Timeout() {
ts.cancelError = err
ts.dlog("got timeout error, sending attention signal to server")
return ts.sendAttention(ch)
}
// Pass the token along.
ch <- tok
return parseRespIterContinue
case <-ts.ctxDone:
ts.ctxDone = nil
ts.dlog("got cancel message, sending attention signal to server")
return ts.sendAttention(ch)
}
case parseRespStateCancel: // Read all responses until a DONE or error is received.Auth
select {
case tok, ok := <-tokChan:
if !ok {
ts.dlog("response finished but waiting for attention ack")
return parseRespIterNext
}
switch tok := tok.(type) {
default:
// Ignore all other tokens while waiting.
// The TDS spec says other tokens may arrive after an attention
// signal is sent. Ignore these tokens and continue looking for
// a DONE with attention confirm mark.
case doneStruct:
if tok.Status&doneAttn != 0 {
ts.dlog("got cancellation confirmation from server")
if ts.cancelError != nil {
ch <- ts.cancelError
ts.cancelError = nil
} else {
ch <- ctx.Err()
}
return parseRespIterDone
}
// If an error happens during cancel, pass it along and just stop.
// We are uncertain to receive more tokens.
case error:
ch <- tok
ts.state = parseRespStateClosing
}
return parseRespIterContinue
case <-ts.ctxDone:
ts.ctxDone = nil
ts.state = parseRespStateClosing
return parseRespIterContinue
}
case parseRespStateClosing: // Wait for current token chan to close.
if _, ok := <-tokChan; !ok {
ts.dlog("response finished")
return parseRespIterDone
}
return parseRespIterContinue
}
}
func processResponse(ctx context.Context, sess *tdsSession, ch chan tokenStruct, outs map[string]interface{}) {
ts := &parseResp{
sess: sess,
ctxDone: ctx.Done(),
}
defer func() {
// Ensure any remaining error is piped through
// or the query may look like it executed when it actually failed.
if ts.cancelError != nil {
ch <- ts.cancelError
ts.cancelError = nil
}
close(ch)
}()
// Loop over multiple responses.
for {
ts.dlog("initiating response reading")
tokChan := make(chan tokenStruct)
go processSingleResponse(sess, tokChan, outs)
// Loop over multiple tokens in response.
tokensLoop:
for {
switch ts.iter(ctx, ch, tokChan) {
case parseRespIterContinue:
// Nothing, continue to next token.
case parseRespIterNext:
break tokensLoop
case parseRespIterDone:
return
}
} }
} }
} }

@ -0,0 +1,53 @@
// Code generated by "stringer -type token"; DO NOT EDIT
package mssql
import "fmt"
const (
_token_name_0 = "tokenReturnStatus"
_token_name_1 = "tokenColMetadata"
_token_name_2 = "tokenOrdertokenErrortokenInfo"
_token_name_3 = "tokenLoginAck"
_token_name_4 = "tokenRowtokenNbcRow"
_token_name_5 = "tokenEnvChange"
_token_name_6 = "tokenSSPI"
_token_name_7 = "tokenDonetokenDoneProctokenDoneInProc"
)
var (
_token_index_0 = [...]uint8{0, 17}
_token_index_1 = [...]uint8{0, 16}
_token_index_2 = [...]uint8{0, 10, 20, 29}
_token_index_3 = [...]uint8{0, 13}
_token_index_4 = [...]uint8{0, 8, 19}
_token_index_5 = [...]uint8{0, 14}
_token_index_6 = [...]uint8{0, 9}
_token_index_7 = [...]uint8{0, 9, 22, 37}
)
func (i token) String() string {
switch {
case i == 121:
return _token_name_0
case i == 129:
return _token_name_1
case 169 <= i && i <= 171:
i -= 169
return _token_name_2[_token_index_2[i]:_token_index_2[i+1]]
case i == 173:
return _token_name_3
case 209 <= i && i <= 210:
i -= 209
return _token_name_4[_token_index_4[i]:_token_index_4[i+1]]
case i == 227:
return _token_name_5
case i == 237:
return _token_name_6
case 253 <= i && i <= 255:
i -= 253
return _token_name_7[_token_index_7[i]:_token_index_7[i+1]]
default:
return fmt.Sprintf("token(%d)", i)
}
}

@ -1,6 +1,7 @@
package mssql
// Transaction Manager requests // Transaction Manager requests
// http://msdn.microsoft.com/en-us/library/dd339887.aspx // http://msdn.microsoft.com/en-us/library/dd339887.aspx
package mssql
import ( import (
"encoding/binary" "encoding/binary"
@ -16,7 +17,18 @@ const (
tmSaveXact = 9 tmSaveXact = 9
) )
func sendBeginXact(buf *tdsBuffer, headers []headerStruct, isolation uint8, type isoLevel uint8
const (
isolationUseCurrent isoLevel = 0
isolationReadUncommited = 1
isolationReadCommited = 2
isolationRepeatableRead = 3
isolationSerializable = 4
isolationSnapshot = 5
)
func sendBeginXact(buf *tdsBuffer, headers []headerStruct, isolation isoLevel,
name string) (err error) { name string) (err error) {
buf.BeginPacket(packTransMgrReq) buf.BeginPacket(packTransMgrReq)
writeAllHeaders(buf, headers) writeAllHeaders(buf, headers)

@ -6,8 +6,11 @@ import (
"fmt" "fmt"
"io" "io"
"math" "math"
"reflect"
"strconv" "strconv"
"time" "time"
"github.com/denisenkom/go-mssqldb/internal/cp"
) )
// fixed-length data types // fixed-length data types
@ -66,6 +69,9 @@ const (
typeNText = 0x63 typeNText = 0x63
typeVariant = 0x62 typeVariant = 0x62
) )
const PLP_NULL = 0xFFFFFFFFFFFFFFFF
const UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFE
const PLP_TERMINATOR = 0x00000000
// TYPE_INFO rule // TYPE_INFO rule
// http://msdn.microsoft.com/en-us/library/dd358284.aspx // http://msdn.microsoft.com/en-us/library/dd358284.aspx
@ -75,11 +81,32 @@ type typeInfo struct {
Scale uint8 Scale uint8
Prec uint8 Prec uint8
Buffer []byte Buffer []byte
Collation collation Collation cp.Collation
UdtInfo udtInfo
XmlInfo xmlInfo
Reader func(ti *typeInfo, r *tdsBuffer) (res interface{}) Reader func(ti *typeInfo, r *tdsBuffer) (res interface{})
Writer func(w io.Writer, ti typeInfo, buf []byte) (err error) Writer func(w io.Writer, ti typeInfo, buf []byte) (err error)
} }
// Common Language Runtime (CLR) Instances
// http://msdn.microsoft.com/en-us/library/dd357962.aspx
type udtInfo struct {
//MaxByteSize uint32
DBName string
SchemaName string
TypeName string
AssemblyQualifiedName string
}
// XML Values
// http://msdn.microsoft.com/en-us/library/dd304764.aspx
type xmlInfo struct {
SchemaPresent uint8
DBName string
OwningSchema string
XmlSchemaCollection string
}
func readTypeInfo(r *tdsBuffer) (res typeInfo) { func readTypeInfo(r *tdsBuffer) (res typeInfo) {
res.TypeId = r.byte() res.TypeId = r.byte()
switch res.TypeId { switch res.TypeId {
@ -114,7 +141,8 @@ func writeTypeInfo(w io.Writer, ti *typeInfo) (err error) {
switch ti.TypeId { switch ti.TypeId {
case typeNull, typeInt1, typeBit, typeInt2, typeInt4, typeDateTim4, case typeNull, typeInt1, typeBit, typeInt2, typeInt4, typeDateTim4,
typeFlt4, typeMoney, typeDateTime, typeFlt8, typeMoney4, typeInt8: typeFlt4, typeMoney, typeDateTime, typeFlt8, typeMoney4, typeInt8:
// those are fixed length types // those are fixed length
ti.Writer = writeFixedType
default: // all others are VARLENTYPE default: // all others are VARLENTYPE
err = writeVarLen(w, ti) err = writeVarLen(w, ti)
if err != nil { if err != nil {
@ -124,19 +152,25 @@ func writeTypeInfo(w io.Writer, ti *typeInfo) (err error) {
return return
} }
func writeFixedType(w io.Writer, ti typeInfo, buf []byte) (err error) {
_, err = w.Write(buf)
return
}
func writeVarLen(w io.Writer, ti *typeInfo) (err error) { func writeVarLen(w io.Writer, ti *typeInfo) (err error) {
switch ti.TypeId { switch ti.TypeId {
case typeDateN: case typeDateN:
ti.Writer = writeByteLenType
case typeTimeN, typeDateTime2N, typeDateTimeOffsetN: case typeTimeN, typeDateTime2N, typeDateTimeOffsetN:
if err = binary.Write(w, binary.LittleEndian, ti.Scale); err != nil { if err = binary.Write(w, binary.LittleEndian, ti.Scale); err != nil {
return return
} }
ti.Writer = writeByteLenType ti.Writer = writeByteLenType
case typeGuid, typeIntN, typeDecimal, typeNumeric, case typeIntN, typeDecimal, typeNumeric,
typeBitN, typeDecimalN, typeNumericN, typeFltN, typeBitN, typeDecimalN, typeNumericN, typeFltN,
typeMoneyN, typeDateTimeN, typeChar, typeMoneyN, typeDateTimeN, typeChar,
typeVarChar, typeBinary, typeVarBinary: typeVarChar, typeBinary, typeVarBinary:
// byle len types // byle len types
if ti.Size > 0xff { if ti.Size > 0xff {
panic("Invalid size for BYLELEN_TYPE") panic("Invalid size for BYLELEN_TYPE")
@ -156,6 +190,14 @@ func writeVarLen(w io.Writer, ti *typeInfo) (err error) {
} }
} }
ti.Writer = writeByteLenType ti.Writer = writeByteLenType
case typeGuid:
if !(ti.Size == 0x10 || ti.Size == 0x00) {
panic("Invalid size for BYLELEN_TYPE")
}
if err = binary.Write(w, binary.LittleEndian, uint8(ti.Size)); err != nil {
return
}
ti.Writer = writeByteLenType
case typeBigVarBin, typeBigVarChar, typeBigBinary, typeBigChar, case typeBigVarBin, typeBigVarChar, typeBigBinary, typeBigChar,
typeNVarChar, typeNChar, typeXml, typeUdt: typeNVarChar, typeNChar, typeXml, typeUdt:
// short len types // short len types
@ -176,14 +218,19 @@ func writeVarLen(w io.Writer, ti *typeInfo) (err error) {
return return
} }
case typeXml: case typeXml:
var schemapresent uint8 = 0 if err = binary.Write(w, binary.LittleEndian, ti.XmlInfo.SchemaPresent); err != nil {
if err = binary.Write(w, binary.LittleEndian, schemapresent); err != nil {
return return
} }
} }
case typeText, typeImage, typeNText, typeVariant: case typeText, typeImage, typeNText, typeVariant:
// LONGLEN_TYPE // LONGLEN_TYPE
panic("LONGLEN_TYPE not implemented") if err = binary.Write(w, binary.LittleEndian, uint32(ti.Size)); err != nil {
return
}
if err = writeCollation(w, ti.Collation); err != nil {
return
}
ti.Writer = writeLongLenType
default: default:
panic("Invalid type") panic("Invalid type")
} }
@ -207,7 +254,7 @@ func decodeDateTime(buf []byte) time.Time {
0, 0, secs, ns, time.UTC) 0, 0, secs, ns, time.UTC)
} }
func readFixedType(ti *typeInfo, r *tdsBuffer) (res interface{}) { func readFixedType(ti *typeInfo, r *tdsBuffer) interface{} {
r.ReadFull(ti.Buffer) r.ReadFull(ti.Buffer)
buf := ti.Buffer buf := ti.Buffer
switch ti.TypeId { switch ti.TypeId {
@ -241,12 +288,7 @@ func readFixedType(ti *typeInfo, r *tdsBuffer) (res interface{}) {
panic("shoulnd't get here") panic("shoulnd't get here")
} }
func writeFixedType(w io.Writer, ti typeInfo, buf []byte) (err error) { func readByteLenType(ti *typeInfo, r *tdsBuffer) interface{} {
_, err = w.Write(buf)
return
}
func readByteLenType(ti *typeInfo, r *tdsBuffer) (res interface{}) {
size := r.byte() size := r.byte()
if size == 0 { if size == 0 {
return nil return nil
@ -305,6 +347,10 @@ func readByteLenType(ti *typeInfo, r *tdsBuffer) (res interface{}) {
default: default:
badStreamPanicf("Invalid size for MONEYNTYPE") badStreamPanicf("Invalid size for MONEYNTYPE")
} }
case typeDateTim4:
return decodeDateTim4(buf)
case typeDateTime:
return decodeDateTime(buf)
case typeDateTimeN: case typeDateTimeN:
switch len(buf) { switch len(buf) {
case 4: case 4:
@ -341,7 +387,7 @@ func writeByteLenType(w io.Writer, ti typeInfo, buf []byte) (err error) {
return return
} }
func readShortLenType(ti *typeInfo, r *tdsBuffer) (res interface{}) { func readShortLenType(ti *typeInfo, r *tdsBuffer) interface{} {
size := r.uint16() size := r.uint16()
if size == 0xffff { if size == 0xffff {
return nil return nil
@ -384,7 +430,7 @@ func writeShortLenType(w io.Writer, ti typeInfo, buf []byte) (err error) {
return return
} }
func readLongLenType(ti *typeInfo, r *tdsBuffer) (res interface{}) { func readLongLenType(ti *typeInfo, r *tdsBuffer) interface{} {
// information about this format can be found here: // information about this format can be found here:
// http://msdn.microsoft.com/en-us/library/dd304783.aspx // http://msdn.microsoft.com/en-us/library/dd304783.aspx
// and here: // and here:
@ -415,10 +461,51 @@ func readLongLenType(ti *typeInfo, r *tdsBuffer) (res interface{}) {
} }
panic("shoulnd't get here") panic("shoulnd't get here")
} }
func writeLongLenType(w io.Writer, ti typeInfo, buf []byte) (err error) {
//textptr
err = binary.Write(w, binary.LittleEndian, byte(0x10))
if err != nil {
return
}
err = binary.Write(w, binary.LittleEndian, uint64(0xFFFFFFFFFFFFFFFF))
if err != nil {
return
}
err = binary.Write(w, binary.LittleEndian, uint64(0xFFFFFFFFFFFFFFFF))
if err != nil {
return
}
//timestamp?
err = binary.Write(w, binary.LittleEndian, uint64(0xFFFFFFFFFFFFFFFF))
if err != nil {
return
}
err = binary.Write(w, binary.LittleEndian, uint32(ti.Size))
if err != nil {
return
}
_, err = w.Write(buf)
return
}
func readCollation(r *tdsBuffer) (res cp.Collation) {
res.LcidAndFlags = r.uint32()
res.SortId = r.byte()
return
}
func writeCollation(w io.Writer, col cp.Collation) (err error) {
if err = binary.Write(w, binary.LittleEndian, col.LcidAndFlags); err != nil {
return
}
err = binary.Write(w, binary.LittleEndian, col.SortId)
return
}
// reads variant value // reads variant value
// http://msdn.microsoft.com/en-us/library/dd303302.aspx // http://msdn.microsoft.com/en-us/library/dd303302.aspx
func readVariantType(ti *typeInfo, r *tdsBuffer) (res interface{}) { func readVariantType(ti *typeInfo, r *tdsBuffer) interface{} {
size := r.int32() size := r.int32()
if size == 0 { if size == 0 {
return nil return nil
@ -510,14 +597,14 @@ func readVariantType(ti *typeInfo, r *tdsBuffer) (res interface{}) {
// partially length prefixed stream // partially length prefixed stream
// http://msdn.microsoft.com/en-us/library/dd340469.aspx // http://msdn.microsoft.com/en-us/library/dd340469.aspx
func readPLPType(ti *typeInfo, r *tdsBuffer) (res interface{}) { func readPLPType(ti *typeInfo, r *tdsBuffer) interface{} {
size := r.uint64() size := r.uint64()
var buf *bytes.Buffer var buf *bytes.Buffer
switch size { switch size {
case 0xffffffffffffffff: case PLP_NULL:
// null // null
return nil return nil
case 0xfffffffffffffffe: case UNKNOWN_PLP_LEN:
// size unknown // size unknown
buf = bytes.NewBuffer(make([]byte, 0, 1000)) buf = bytes.NewBuffer(make([]byte, 0, 1000))
default: default:
@ -548,15 +635,16 @@ func readPLPType(ti *typeInfo, r *tdsBuffer) (res interface{}) {
} }
func writePLPType(w io.Writer, ti typeInfo, buf []byte) (err error) { func writePLPType(w io.Writer, ti typeInfo, buf []byte) (err error) {
if err = binary.Write(w, binary.LittleEndian, uint64(len(buf))); err != nil { if err = binary.Write(w, binary.LittleEndian, uint64(UNKNOWN_PLP_LEN)); err != nil {
return return
} }
for { for {
chunksize := uint32(len(buf)) chunksize := uint32(len(buf))
if err = binary.Write(w, binary.LittleEndian, chunksize); err != nil { if chunksize == 0 {
err = binary.Write(w, binary.LittleEndian, uint32(PLP_TERMINATOR))
return return
} }
if chunksize == 0 { if err = binary.Write(w, binary.LittleEndian, chunksize); err != nil {
return return
} }
if _, err = w.Write(buf[:chunksize]); err != nil { if _, err = w.Write(buf[:chunksize]); err != nil {
@ -606,19 +694,27 @@ func readVarLen(ti *typeInfo, r *tdsBuffer) {
} }
ti.Reader = readByteLenType ti.Reader = readByteLenType
case typeXml: case typeXml:
schemapresent := r.byte() ti.XmlInfo.SchemaPresent = r.byte()
if schemapresent != 0 { if ti.XmlInfo.SchemaPresent != 0 {
// just ignore this for now
// dbname // dbname
r.BVarChar() ti.XmlInfo.DBName = r.BVarChar()
// owning schema // owning schema
r.BVarChar() ti.XmlInfo.OwningSchema = r.BVarChar()
// xml schema collection // xml schema collection
r.UsVarChar() ti.XmlInfo.XmlSchemaCollection = r.UsVarChar()
} }
ti.Reader = readPLPType ti.Reader = readPLPType
case typeUdt:
ti.Size = int(r.uint16())
ti.UdtInfo.DBName = r.BVarChar()
ti.UdtInfo.SchemaName = r.BVarChar()
ti.UdtInfo.TypeName = r.BVarChar()
ti.UdtInfo.AssemblyQualifiedName = r.UsVarChar()
ti.Buffer = make([]byte, ti.Size)
ti.Reader = readPLPType
case typeBigVarBin, typeBigVarChar, typeBigBinary, typeBigChar, case typeBigVarBin, typeBigVarChar, typeBigBinary, typeBigChar,
typeNVarChar, typeNChar, typeUdt: typeNVarChar, typeNChar:
// short len types // short len types
ti.Size = int(r.uint16()) ti.Size = int(r.uint16())
switch ti.TypeId { switch ti.TypeId {
@ -701,7 +797,8 @@ func decodeDecimal(prec uint8, scale uint8, buf []byte) []byte {
// http://msdn.microsoft.com/en-us/library/ee780895.aspx // http://msdn.microsoft.com/en-us/library/ee780895.aspx
func decodeDateInt(buf []byte) (days int) { func decodeDateInt(buf []byte) (days int) {
return int(buf[0]) + int(buf[1])*256 + int(buf[2])*256*256 days = int(buf[0]) + int(buf[1])*256 + int(buf[2])*256*256
return
} }
func decodeDate(buf []byte) time.Time { func decodeDate(buf []byte) time.Time {
@ -767,8 +864,8 @@ func dateTime2(t time.Time) (days int32, ns int64) {
return return
} }
func decodeChar(col collation, buf []byte) string { func decodeChar(col cp.Collation, buf []byte) string {
return charset2utf8(col, buf) return cp.CharsetToUTF8(col, buf)
} }
func decodeUcs2(buf []byte) string { func decodeUcs2(buf []byte) string {
@ -787,12 +884,127 @@ func decodeXml(ti typeInfo, buf []byte) string {
return decodeUcs2(buf) return decodeUcs2(buf)
} }
func decodeUdt(ti typeInfo, buf []byte) int { func decodeUdt(ti typeInfo, buf []byte) []byte {
panic("Not implemented") return buf
}
// makes go/sql type instance as described below
// It should return
// the value type that can be used to scan types into. For example, the database
// column type "bigint" this should return "reflect.TypeOf(int64(0))".
func makeGoLangScanType(ti typeInfo) reflect.Type {
switch ti.TypeId {
case typeInt1:
return reflect.TypeOf(int64(0))
case typeInt2:
return reflect.TypeOf(int64(0))
case typeInt4:
return reflect.TypeOf(int64(0))
case typeInt8:
return reflect.TypeOf(int64(0))
case typeFlt4:
return reflect.TypeOf(float64(0))
case typeIntN:
switch ti.Size {
case 1:
return reflect.TypeOf(int64(0))
case 2:
return reflect.TypeOf(int64(0))
case 4:
return reflect.TypeOf(int64(0))
case 8:
return reflect.TypeOf(int64(0))
default:
panic("invalid size of INTNTYPE")
}
case typeFlt8:
return reflect.TypeOf(float64(0))
case typeFltN:
switch ti.Size {
case 4:
return reflect.TypeOf(float64(0))
case 8:
return reflect.TypeOf(float64(0))
default:
panic("invalid size of FLNNTYPE")
}
case typeBigVarBin:
return reflect.TypeOf([]byte{})
case typeVarChar:
return reflect.TypeOf("")
case typeNVarChar:
return reflect.TypeOf("")
case typeBit, typeBitN:
return reflect.TypeOf(true)
case typeDecimalN, typeNumericN:
return reflect.TypeOf([]byte{})
case typeMoney, typeMoney4, typeMoneyN:
switch ti.Size {
case 4:
return reflect.TypeOf([]byte{})
case 8:
return reflect.TypeOf([]byte{})
default:
panic("invalid size of MONEYN")
}
case typeDateTim4:
return reflect.TypeOf(time.Time{})
case typeDateTime:
return reflect.TypeOf(time.Time{})
case typeDateTimeN:
switch ti.Size {
case 4:
return reflect.TypeOf(time.Time{})
case 8:
return reflect.TypeOf(time.Time{})
default:
panic("invalid size of DATETIMEN")
}
case typeDateTime2N:
return reflect.TypeOf(time.Time{})
case typeDateN:
return reflect.TypeOf(time.Time{})
case typeTimeN:
return reflect.TypeOf(time.Time{})
case typeDateTimeOffsetN:
return reflect.TypeOf(time.Time{})
case typeBigVarChar:
return reflect.TypeOf("")
case typeBigChar:
return reflect.TypeOf("")
case typeNChar:
return reflect.TypeOf("")
case typeGuid:
return reflect.TypeOf([]byte{})
case typeXml:
return reflect.TypeOf("")
case typeText:
return reflect.TypeOf("")
case typeNText:
return reflect.TypeOf("")
case typeImage:
return reflect.TypeOf([]byte{})
case typeBigBinary:
return reflect.TypeOf([]byte{})
case typeVariant:
return reflect.TypeOf(nil)
default:
panic(fmt.Sprintf("not implemented makeDecl for type %d", ti.TypeId))
}
} }
func makeDecl(ti typeInfo) string { func makeDecl(ti typeInfo) string {
switch ti.TypeId { switch ti.TypeId {
case typeNull:
// maybe we should use something else here
// this is tested in TestNull
return "nvarchar(1)"
case typeInt1:
return "tinyint"
case typeInt2:
return "smallint"
case typeInt4:
return "int"
case typeInt8: case typeInt8:
return "bigint" return "bigint"
case typeFlt4: case typeFlt4:
@ -821,24 +1033,415 @@ func makeDecl(ti typeInfo) string {
default: default:
panic("invalid size of FLNNTYPE") panic("invalid size of FLNNTYPE")
} }
case typeDecimal, typeDecimalN:
return fmt.Sprintf("decimal(%d, %d)", ti.Prec, ti.Scale)
case typeNumeric, typeNumericN:
return fmt.Sprintf("numeric(%d, %d)", ti.Prec, ti.Scale)
case typeMoney4:
return "smallmoney"
case typeMoney:
return "money"
case typeMoneyN:
switch ti.Size {
case 4:
return "smallmoney"
case 8:
return "money"
default:
panic("invalid size of MONEYNTYPE")
}
case typeBigVarBin: case typeBigVarBin:
if ti.Size > 8000 || ti.Size == 0 { if ti.Size > 8000 || ti.Size == 0 {
return fmt.Sprintf("varbinary(max)") return "varbinary(max)"
} else { } else {
return fmt.Sprintf("varbinary(%d)", ti.Size) return fmt.Sprintf("varbinary(%d)", ti.Size)
} }
case typeNChar:
return fmt.Sprintf("nchar(%d)", ti.Size/2)
case typeBigChar, typeChar:
return fmt.Sprintf("char(%d)", ti.Size)
case typeBigVarChar, typeVarChar:
if ti.Size > 4000 || ti.Size == 0 {
return fmt.Sprintf("varchar(max)")
} else {
return fmt.Sprintf("varchar(%d)", ti.Size)
}
case typeNVarChar: case typeNVarChar:
if ti.Size > 8000 || ti.Size == 0 { if ti.Size > 8000 || ti.Size == 0 {
return fmt.Sprintf("nvarchar(max)") return "nvarchar(max)"
} else { } else {
return fmt.Sprintf("nvarchar(%d)", ti.Size/2) return fmt.Sprintf("nvarchar(%d)", ti.Size/2)
} }
case typeBit, typeBitN: case typeBit, typeBitN:
return "bit" return "bit"
case typeDateTimeN: case typeDateN:
return "date"
case typeDateTim4:
return "smalldatetime"
case typeDateTime:
return "datetime" return "datetime"
case typeDateTimeN:
switch ti.Size {
case 4:
return "smalldatetime"
case 8:
return "datetime"
default:
panic("invalid size of DATETIMNTYPE")
}
case typeDateTime2N:
return fmt.Sprintf("datetime2(%d)", ti.Scale)
case typeDateTimeOffsetN: case typeDateTimeOffsetN:
return fmt.Sprintf("datetimeoffset(%d)", ti.Scale) return fmt.Sprintf("datetimeoffset(%d)", ti.Scale)
case typeText:
return "text"
case typeNText:
return "ntext"
case typeUdt:
return ti.UdtInfo.TypeName
case typeGuid:
return "uniqueidentifier"
default:
panic(fmt.Sprintf("not implemented makeDecl for type %#x", ti.TypeId))
}
}
// makes go/sql type name as described below
// RowsColumnTypeDatabaseTypeName may be implemented by Rows. It should return the
// database system type name without the length. Type names should be uppercase.
// Examples of returned types: "VARCHAR", "NVARCHAR", "VARCHAR2", "CHAR", "TEXT",
// "DECIMAL", "SMALLINT", "INT", "BIGINT", "BOOL", "[]BIGINT", "JSONB", "XML",
// "TIMESTAMP".
func makeGoLangTypeName(ti typeInfo) string {
switch ti.TypeId {
case typeInt1:
return "TINYINT"
case typeInt2:
return "SMALLINT"
case typeInt4:
return "INT"
case typeInt8:
return "BIGINT"
case typeFlt4:
return "REAL"
case typeIntN:
switch ti.Size {
case 1:
return "TINYINT"
case 2:
return "SMALLINT"
case 4:
return "INT"
case 8:
return "BIGINT"
default:
panic("invalid size of INTNTYPE")
}
case typeFlt8:
return "FLOAT"
case typeFltN:
switch ti.Size {
case 4:
return "REAL"
case 8:
return "FLOAT"
default:
panic("invalid size of FLNNTYPE")
}
case typeBigVarBin:
return "VARBINARY"
case typeVarChar:
return "VARCHAR"
case typeNVarChar:
return "NVARCHAR"
case typeBit, typeBitN:
return "BIT"
case typeDecimalN, typeNumericN:
return "DECIMAL"
case typeMoney, typeMoney4, typeMoneyN:
switch ti.Size {
case 4:
return "SMALLMONEY"
case 8:
return "MONEY"
default:
panic("invalid size of MONEYN")
}
case typeDateTim4:
return "SMALLDATETIME"
case typeDateTime:
return "DATETIME"
case typeDateTimeN:
switch ti.Size {
case 4:
return "SMALLDATETIME"
case 8:
return "DATETIME"
default:
panic("invalid size of DATETIMEN")
}
case typeDateTime2N:
return "DATETIME2"
case typeDateN:
return "DATE"
case typeTimeN:
return "TIME"
case typeDateTimeOffsetN:
return "DATETIMEOFFSET"
case typeBigVarChar:
return "VARCHAR"
case typeBigChar:
return "CHAR"
case typeNChar:
return "NCHAR"
case typeGuid:
return "UNIQUEIDENTIFIER"
case typeXml:
return "XML"
case typeText:
return "TEXT"
case typeNText:
return "NTEXT"
case typeImage:
return "IMAGE"
case typeVariant:
return "SQL_VARIANT"
case typeBigBinary:
return "BINARY"
default:
panic(fmt.Sprintf("not implemented makeDecl for type %d", ti.TypeId))
}
}
// makes go/sql type length as described below
// It should return the length
// of the column type if the column is a variable length type. If the column is
// not a variable length type ok should return false.
// If length is not limited other than system limits, it should return math.MaxInt64.
// The following are examples of returned values for various types:
// TEXT (math.MaxInt64, true)
// varchar(10) (10, true)
// nvarchar(10) (10, true)
// decimal (0, false)
// int (0, false)
// bytea(30) (30, true)
func makeGoLangTypeLength(ti typeInfo) (int64, bool) {
switch ti.TypeId {
case typeInt1:
return 0, false
case typeInt2:
return 0, false
case typeInt4:
return 0, false
case typeInt8:
return 0, false
case typeFlt4:
return 0, false
case typeIntN:
switch ti.Size {
case 1:
return 0, false
case 2:
return 0, false
case 4:
return 0, false
case 8:
return 0, false
default:
panic("invalid size of INTNTYPE")
}
case typeFlt8:
return 0, false
case typeFltN:
switch ti.Size {
case 4:
return 0, false
case 8:
return 0, false
default:
panic("invalid size of FLNNTYPE")
}
case typeBit, typeBitN:
return 0, false
case typeDecimalN, typeNumericN:
return 0, false
case typeMoney, typeMoney4, typeMoneyN:
switch ti.Size {
case 4:
return 0, false
case 8:
return 0, false
default:
panic("invalid size of MONEYN")
}
case typeDateTim4, typeDateTime:
return 0, false
case typeDateTimeN:
switch ti.Size {
case 4:
return 0, false
case 8:
return 0, false
default:
panic("invalid size of DATETIMEN")
}
case typeDateTime2N:
return 0, false
case typeDateN:
return 0, false
case typeTimeN:
return 0, false
case typeDateTimeOffsetN:
return 0, false
case typeBigVarBin:
if ti.Size == 0xffff {
return 2147483645, true
} else {
return int64(ti.Size), true
}
case typeVarChar:
return int64(ti.Size), true
case typeBigVarChar:
if ti.Size == 0xffff {
return 2147483645, true
} else {
return int64(ti.Size), true
}
case typeBigChar:
return int64(ti.Size), true
case typeNVarChar:
if ti.Size == 0xffff {
return 2147483645 / 2, true
} else {
return int64(ti.Size) / 2, true
}
case typeNChar:
return int64(ti.Size) / 2, true
case typeGuid:
return 0, false
case typeXml:
return 1073741822, true
case typeText:
return 2147483647, true
case typeNText:
return 1073741823, true
case typeImage:
return 2147483647, true
case typeVariant:
return 0, false
case typeBigBinary:
return 0, false
default:
panic(fmt.Sprintf("not implemented makeDecl for type %d", ti.TypeId))
}
}
// makes go/sql type precision and scale as described below
// It should return the length
// of the column type if the column is a variable length type. If the column is
// not a variable length type ok should return false.
// If length is not limited other than system limits, it should return math.MaxInt64.
// The following are examples of returned values for various types:
// TEXT (math.MaxInt64, true)
// varchar(10) (10, true)
// nvarchar(10) (10, true)
// decimal (0, false)
// int (0, false)
// bytea(30) (30, true)
func makeGoLangTypePrecisionScale(ti typeInfo) (int64, int64, bool) {
switch ti.TypeId {
case typeInt1:
return 0, 0, false
case typeInt2:
return 0, 0, false
case typeInt4:
return 0, 0, false
case typeInt8:
return 0, 0, false
case typeFlt4:
return 0, 0, false
case typeIntN:
switch ti.Size {
case 1:
return 0, 0, false
case 2:
return 0, 0, false
case 4:
return 0, 0, false
case 8:
return 0, 0, false
default:
panic("invalid size of INTNTYPE")
}
case typeFlt8:
return 0, 0, false
case typeFltN:
switch ti.Size {
case 4:
return 0, 0, false
case 8:
return 0, 0, false
default:
panic("invalid size of FLNNTYPE")
}
case typeBit, typeBitN:
return 0, 0, false
case typeDecimalN, typeNumericN:
return int64(ti.Prec), int64(ti.Scale), true
case typeMoney, typeMoney4, typeMoneyN:
switch ti.Size {
case 4:
return 0, 0, false
case 8:
return 0, 0, false
default:
panic("invalid size of MONEYN")
}
case typeDateTim4, typeDateTime:
return 0, 0, false
case typeDateTimeN:
switch ti.Size {
case 4:
return 0, 0, false
case 8:
return 0, 0, false
default:
panic("invalid size of DATETIMEN")
}
case typeDateTime2N:
return 0, 0, false
case typeDateN:
return 0, 0, false
case typeTimeN:
return 0, 0, false
case typeDateTimeOffsetN:
return 0, 0, false
case typeBigVarBin:
return 0, 0, false
case typeVarChar:
return 0, 0, false
case typeBigVarChar:
return 0, 0, false
case typeBigChar:
return 0, 0, false
case typeNVarChar:
return 0, 0, false
case typeNChar:
return 0, 0, false
case typeGuid:
return 0, 0, false
case typeXml:
return 0, 0, false
case typeText:
return 0, 0, false
case typeNText:
return 0, 0, false
case typeImage:
return 0, 0, false
case typeVariant:
return 0, 0, false
case typeBigBinary:
return 0, 0, false
default: default:
panic(fmt.Sprintf("not implemented makeDecl for type %d", ti.TypeId)) panic(fmt.Sprintf("not implemented makeDecl for type %d", ti.TypeId))
} }

@ -0,0 +1,74 @@
package mssql
import (
"database/sql/driver"
"encoding/hex"
"errors"
"fmt"
)
type UniqueIdentifier [16]byte
func (u *UniqueIdentifier) Scan(v interface{}) error {
reverse := func(b []byte) {
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
}
switch vt := v.(type) {
case []byte:
if len(vt) != 16 {
return errors.New("mssql: invalid UniqueIdentifier length")
}
var raw UniqueIdentifier
copy(raw[:], vt)
reverse(raw[0:4])
reverse(raw[4:6])
reverse(raw[6:8])
*u = raw
return nil
case string:
if len(vt) != 36 {
return errors.New("mssql: invalid UniqueIdentifier string length")
}
b := []byte(vt)
for i, c := range b {
switch c {
case '-':
b = append(b[:i], b[i+1:]...)
}
}
_, err := hex.Decode(u[:], []byte(b))
return err
default:
return fmt.Errorf("mssql: cannot convert %T to UniqueIdentifier", v)
}
}
func (u UniqueIdentifier) Value() (driver.Value, error) {
reverse := func(b []byte) {
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
}
raw := make([]byte, len(u))
copy(raw, u[:])
reverse(raw[0:4])
reverse(raw[4:6])
reverse(raw[6:8])
return raw, nil
}
func (u UniqueIdentifier) String() string {
return fmt.Sprintf("%X-%X-%X-%X-%X", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
}

@ -85,8 +85,9 @@ github.com/couchbase/vellum/utf8
github.com/couchbaselabs/go-couchbase github.com/couchbaselabs/go-couchbase
# github.com/davecgh/go-spew v1.1.1 # github.com/davecgh/go-spew v1.1.1
github.com/davecgh/go-spew/spew github.com/davecgh/go-spew/spew
# github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952 => github.com/denisenkom/go-mssqldb v0.0.0-20161128230840-e32ca5036449 # github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952 => github.com/denisenkom/go-mssqldb v0.0.0-20180314172330-6a30f4e59a44
github.com/denisenkom/go-mssqldb github.com/denisenkom/go-mssqldb
github.com/denisenkom/go-mssqldb/internal/cp
# github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac # github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac
github.com/dgrijalva/jwt-go github.com/dgrijalva/jwt-go
# github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 # github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712

Loading…
Cancel
Save