Repository: denisenkom/go-mssqldb
Pull request #559 (2020-02-24 10:29:08)

Empty Strings Instead Of Nulls Using Bulk.
Fiх for #555 

codecov[bot]: # [Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559?src=pr&el=h1) Report
> Merging [#559](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559?src=pr&el=desc) into [master](https://codecov.io/gh/denisenkom/go-mssqldb/commit/bbfc9a55622eeb8e47808e0f7270efcf5ae3dce2?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559/graphs/tree.svg?width=650&token=MoMB2u5avp&height=150&src=pr)](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #559      +/-   ##
==========================================
+ Coverage   68.72%   68.75%   +0.02%     
==========================================
  Files          23       23              
  Lines        5190     5194       +4     
==========================================
+ Hits         3567     3571       +4     
  Misses       1410     1410              
  Partials      213      213
```


| [Impacted Files](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [types.go](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559/diff?src=pr&el=tree#diff-dHlwZXMuZ28=) | `77.37% <100%> (+0.07%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559?src=pr&el=footer). Last update [bbfc9a5...2f831b5](https://codecov.io/gh/denisenkom/go-mssqldb/pull/559?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml
new file mode 100644
index 0000000..97b897a
--- /dev/null
+++ b/.github/workflows/reviewdog.yml
@@ -0,0 +1,14 @@
+name: reviewdog
+on: [pull_request]
+jobs:
+  golangci-lint:
+    name: runner / golangci-lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out code into the Go module directory
+        uses: actions/checkout@v2
+      - name: golangci-lint
+        uses: reviewdog/action-golangci-lint@v1
+        with:
+          level: warning
+          reporter: github-pr-review
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..959cd5e
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,10 @@
+linters:
+  enable:
+    # basic go linters
+    - gofmt
+    - golint
+    - govet
+
+    # sql related linters
+    - rowserrcheck
+    - sqlclosecheck
diff --git a/README.md b/README.md
index 46e4eea..94d87fe 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ Other supported formats are listed below.
 
 ### Common parameters:
 
-* `user id` - enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\User format. On Windows, if user id is empty or missing Single-Sign-On is used.
+* `user id` - enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\User format. On Windows, if user id is empty or missing Single-Sign-On is used. The user domain sensitive to the case which is defined in the connection string.
 * `password`
 * `database`
 * `connection timeout` - in seconds (default is 0 for no timeout), set to 0 for no timeout. Recommended to set to 0 and use context to manage query and connection timeouts.
diff --git a/appveyor.yml b/appveyor.yml
index c4d2bb0..dfcb62d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,6 +1,7 @@
 version: 1.0.{build}
 
-os: Windows Server 2012 R2
+image:
+  - Visual Studio 2015
 
 clone_folder: c:\gopath\src\github.com\denisenkom\go-mssqldb
 
@@ -9,21 +10,36 @@ environment:
   HOST: localhost
   SQLUSER: sa
   SQLPASSWORD: Password12!
-  DATABASE: test  
-  GOVERSION: 111
+  DATABASE: test
+  GOVERSION: 113
   matrix:
     - GOVERSION: 18
-      SQLINSTANCE: SQL2016
+      SQLINSTANCE: SQL2017
     - GOVERSION: 19
-      SQLINSTANCE: SQL2016
+      SQLINSTANCE: SQL2017
     - GOVERSION: 110
-      SQLINSTANCE: SQL2016
+      SQLINSTANCE: SQL2017
     - GOVERSION: 111
-      SQLINSTANCE: SQL2016
+      SQLINSTANCE: SQL2017
+    - GOVERSION: 112
+      SQLINSTANCE: SQL2017
+    - SQLINSTANCE: SQL2017
+    - SQLINSTANCE: SQL2016
     - SQLINSTANCE: SQL2014
     - SQLINSTANCE: SQL2012SP1
     - SQLINSTANCE: SQL2008R2SP2
- 
+
+    #  Go 1.14+ and SQL2019 are available on the Visual Studio 2019 image only
+    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
+      GOVERSION: 114
+      SQLINSTANCE: SQL2019
+    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
+      GOVERSION: 115
+      SQLINSTANCE: SQL2019
+    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
+      GOVERSION: 115
+      SQLINSTANCE: SQL2017
+
 install:
   - set GOROOT=c:\go%GOVERSION%
   - set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
@@ -35,15 +51,14 @@ build_script:
   - go build
 
 before_test:
-  # setup SQL Server 
-  - ps: | 
+  # 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 -cpu 4 -coverprofile=coverage.txt -covermode=atomic
diff --git a/buf.go b/buf.go
index ba39b40..07bfb9b 100644
--- a/buf.go
+++ b/buf.go
@@ -48,8 +48,8 @@ type tdsBuffer struct {
 func newTdsBuffer(bufsize uint16, transport io.ReadWriteCloser) *tdsBuffer {
 	return &tdsBuffer{
 		packetSize: int(bufsize),
-		wbuf:       make([]byte, 1<<16),
-		rbuf:       make([]byte, 1<<16),
+		wbuf:       make([]byte, bufsize),
+		rbuf:       make([]byte, bufsize),
 		rpos:       8,
 		transport:  transport,
 	}
diff --git a/bulkcopy.go b/bulkcopy.go
index 1d5eacb..d0cb8bc 100644
--- a/bulkcopy.go
+++ b/bulkcopy.go
@@ -44,8 +44,9 @@ type BulkOptions struct {
 type DataValue interface{}
 
 const (
-	sqlDateFormat = "2006-01-02"
-	sqlTimeFormat = "2006-01-02 15:04:05.999999999Z07:00"
+	sqlDateFormat     = "2006-01-02"
+	sqlDateTimeFormat = "2006-01-02 15:04:05.999999999Z07:00"
+	sqlTimeFormat     = "15:04:05.9999999"
 )
 
 func (cn *Conn) CreateBulk(table string, columns []string) (_ *Bulk) {
@@ -421,7 +422,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
 			res.ti.Size = len(res.buffer)
 		case string:
 			var t time.Time
-			if t, err = time.Parse(sqlTimeFormat, val); err != nil {
+			if t, err = time.Parse(sqlDateTimeFormat, val); err != nil {
 				return res, fmt.Errorf("bulk: unable to convert string to date: %v", err)
 			}
 			res.buffer = encodeDateTime2(t, int(col.ti.Scale))
@@ -437,7 +438,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
 			res.ti.Size = len(res.buffer)
 		case string:
 			var t time.Time
-			if t, err = time.Parse(sqlTimeFormat, val); err != nil {
+			if t, err = time.Parse(sqlDateTimeFormat, val); err != nil {
 				return res, fmt.Errorf("bulk: unable to convert string to date: %v", err)
 			}
 			res.buffer = encodeDateTimeOffset(t, int(col.ti.Scale))
@@ -468,7 +469,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
 		case time.Time:
 			t = val
 		case string:
-			if t, err = time.Parse(sqlTimeFormat, val); err != nil {
+			if t, err = time.Parse(sqlDateTimeFormat, val); err != nil {
 				return res, fmt.Errorf("bulk: unable to convert string to date: %v", err)
 			}
 		default:
@@ -485,7 +486,22 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
 		} else {
 			err = fmt.Errorf("mssql: invalid size of column %d", col.ti.Size)
 		}
-
+	case typeTimeN:
+		var t time.Time
+		switch val := val.(type) {
+		case time.Time:
+			res.buffer = encodeTime(val.Hour(), val.Minute(), val.Second(), val.Nanosecond(), int(col.ti.Scale))
+			res.ti.Size = len(res.buffer)
+		case string:
+			if t, err = time.Parse(sqlTimeFormat, val); err != nil {
+				return res, fmt.Errorf("bulk: unable to convert string to time: %v", err)
+			}
+			res.buffer = encodeTime(t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), int(col.ti.Scale))
+			res.ti.Size = len(res.buffer)
+		default:
+			err = fmt.Errorf("mssql: invalid type for time column: %T %s", val, val)
+			return
+		}
 	// case typeMoney, typeMoney4, typeMoneyN:
 	case typeDecimal, typeDecimalN, typeNumeric, typeNumericN:
 		prec := col.ti.Prec
diff --git a/mssql.go b/mssql.go
index a74bc7e..f947d09 100644
--- a/mssql.go
+++ b/mssql.go
@@ -204,17 +204,21 @@ func (c *Conn) simpleProcessResp(ctx context.Context) error {
 	tokchan := make(chan tokenStruct, 5)
 	go processResponse(ctx, c.sess, tokchan, c.outs)
 	c.clearOuts()
+
+	var err error
 	for tok := range tokchan {
 		switch token := tok.(type) {
 		case doneStruct:
-			if token.isError() {
-				return c.checkBadConn(token.getError())
+			if token.isError() && err == nil {
+				err = c.checkBadConn(token.getError())
 			}
 		case error:
-			return c.checkBadConn(token)
+			if err == nil {
+				err = c.checkBadConn(token)
+			}
 		}
 	}
-	return nil
+	return err
 }
 
 func (c *Conn) Commit() error {
@@ -285,7 +289,7 @@ func (c *Conn) begin(ctx context.Context, tdsIsolation isoLevel) (tx driver.Tx,
 	}
 	tx, err = c.processBeginResponse(ctx)
 	if err != nil {
-		return nil, c.checkBadConn(err)
+		return nil, err
 	}
 	return
 }
@@ -617,12 +621,22 @@ loop:
 		case doneStruct:
 			if token.isError() {
 				cancel()
+
+				// make sure tokchan is closed
+				for range tokchan {
+				}
+
 				return nil, s.c.checkBadConn(token.getError())
 			}
 		case ReturnStatus:
 			s.c.setReturnStatus(token)
 		case error:
 			cancel()
+
+			// make sure tokchan is closed
+			for range tokchan {
+			}
+
 			return nil, s.c.checkBadConn(token)
 		}
 	}
@@ -662,15 +676,20 @@ func (s *Stmt) processExec(ctx context.Context) (res driver.Result, err error) {
 			if token.Status&doneCount != 0 {
 				rowCount += int64(token.RowCount)
 			}
-			if token.isError() {
-				return nil, token.getError()
+			if token.isError() && err == nil {
+				err = token.getError()
 			}
 		case ReturnStatus:
 			s.c.setReturnStatus(token)
 		case error:
-			return nil, token
+			if err == nil {
+				err = token
+			}
 		}
 	}
+	if err != nil {
+		return nil, err
+	}
 	return &Result{s.c, rowCount}, nil
 }
 
@@ -686,9 +705,12 @@ type Rows struct {
 
 func (rc *Rows) Close() error {
 	rc.cancel()
-	for _ = range rc.tokchan {
+
+	// make sure tokchan is closed
+	for range rc.tokchan {
 	}
 	rc.tokchan = nil
+
 	return nil
 }
 
diff --git a/ntlm.go b/ntlm.go
index 3ba48ff..ea9148a 100644
--- a/ntlm.go
+++ b/ntlm.go
@@ -217,11 +217,11 @@ func hmacMD5(passwordHash, data []byte) []byte {
 	return hmacEntity.Sum(nil)
 }
 
-func getNTLMv2AndLMv2ResponsePayloads(target, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) {
+func getNTLMv2AndLMv2ResponsePayloads(userDomain, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) {
 	// NTLMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response
 
 	ntlmHash := ntlmHashNoPadding(password)
-	usernameAndTargetBytes := utf16le(strings.ToUpper(username) + target)
+	usernameAndTargetBytes := utf16le(strings.ToUpper(username) + userDomain)
 	ntlmV2Hash := hmacMD5(ntlmHash, usernameAndTargetBytes)
 	targetInfoLength := len(targetInfoFields)
 	blob := make([]byte, 32+targetInfoLength)
@@ -234,7 +234,7 @@ func getNTLMv2AndLMv2ResponsePayloads(target, username, password string, challen
 	binary.BigEndian.PutUint32(blob[28+targetInfoLength:], 0x00000000)
 	challengeLength := len(challenge)
 	blobLength := len(blob)
-	challengeAndBlob := make([]byte, challengeLength + blobLength)
+	challengeAndBlob := make([]byte, challengeLength+blobLength)
 	copy(challengeAndBlob[:challengeLength], challenge[:])
 	copy(challengeAndBlob[challengeLength:], blob)
 	hashedChallenge := hmacMD5(ntlmV2Hash, challengeAndBlob)
@@ -251,18 +251,18 @@ func getNTLMv2AndLMv2ResponsePayloads(target, username, password string, challen
 	return
 }
 
-func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password string) (lm, nt []byte, err error) {
+func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password, userDom string) (lm, nt []byte, err error) {
 	nonce := clientChallenge()
 
 	// Official specification: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
 	// Unofficial walk through referenced by https://www.freetds.org/userguide/domains.htm: http://davenport.sourceforge.net/ntlm.html
 	if (flags & _NEGOTIATE_TARGET_INFO) != 0 {
-		targetInfoFields, target, err := getNTLMv2TargetInfoFields(message)
+		targetInfoFields, err := getNTLMv2TargetInfoFields(message)
 		if err != nil {
 			return lm, nt, err
 		}
 
-		nt, lm = getNTLMv2AndLMv2ResponsePayloads(target, username, password, challenge, nonce, targetInfoFields, time.Now())
+		nt, lm = getNTLMv2AndLMv2ResponsePayloads(userDom, username, password, challenge, nonce, targetInfoFields, time.Now())
 
 		return lm, nt, nil
 	}
@@ -276,36 +276,31 @@ func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8
 	return lm, nt, nil
 }
 
-func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, target string, err error) {
+func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, err error) {
 	type2MessageError := "mssql: while parsing NTLMv2 type 2 message, length %d too small for offset %d"
 	type2MessageLength := len(type2Message)
 	if type2MessageLength < 20 {
-		return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, 20)
+		return nil, fmt.Errorf(type2MessageError, type2MessageLength, 20)
 	}
 
 	targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16])
 	targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20])
 	endOfOffset := int(targetNameOffset + uint32(targetNameAllocated))
 	if type2MessageLength < endOfOffset {
-		return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
-	}
-
-	target, err = ucs22str(type2Message[targetNameOffset:targetNameOffset+uint32(targetNameAllocated)])
-	if err != nil {
-		return nil, target, err
+		return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
 	}
 
 	targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44])
 	targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48])
 	endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated))
 	if type2MessageLength < endOfOffset {
-		return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
+		return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
 	}
 
 	targetInformationBytes := make([]byte, targetInformationAllocated)
 	copy(targetInformationBytes, type2Message[targetInformationDataOffset:targetInformationDataOffset+uint32(targetInformationAllocated)])
 
-	return targetInformationBytes, target, nil
+	return targetInformationBytes, nil
 }
 
 func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation, username string) ([]byte, error) {
@@ -389,7 +384,7 @@ func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) {
 	copy(challenge[:], bytes[24:32])
 	flags := binary.LittleEndian.Uint32(bytes[20:24])
 	if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 {
-		lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password)
+		lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password, auth.Domain)
 		if err != nil {
 			return nil, err
 		}
diff --git a/tds.go b/tds.go
index 832c4fd..e3d65dc 100644
--- a/tds.go
+++ b/tds.go
@@ -838,6 +838,12 @@ func connect(ctx context.Context, c *Connector, log optionalLogger, p connectPar
 		defer cancel()
 	}
 	// if instance is specified use instance resolution service
+	if p.instance != "" && p.port != 0 {
+		// both instance name and port specified
+		// when port is specified instance name is not used
+		// you should not provide instance name when you provide port
+		log.Println("WARN: You specified both instance name and port in the connection string, port will be used and instance name will be ignored");
+	}
 	if p.instance != "" && p.port == 0 {
 		p.instance = strings.ToUpper(p.instance)
 		d := c.getDialer(&p)
@@ -1016,6 +1022,11 @@ initiate_connection:
 				if token.isError() {
 					return nil, fmt.Errorf("Login error: %s", token.getError())
 				}
+
+				// make sure tokchan is closed
+				for range tokchan {
+				}
+
 				goto loginEnd
 			}
 		}
diff --git a/token.go b/token.go
index 25385e8..71a9ac1 100644
--- a/token.go
+++ b/token.go
@@ -814,8 +814,16 @@ func processResponse(ctx context.Context, sess *tdsSession, ch chan tokenStruct,
 			case parseRespIterContinue:
 				// Nothing, continue to next token.
 			case parseRespIterNext:
+				// make sure tokChan is closed
+				for range tokChan {
+				}
+
 				break tokensLoop
 			case parseRespIterDone:
+				// make sure tokChan is closed
+				for range tokChan {
+				}
+
 				return
 			}
 		}
diff --git a/tvp_go19.go b/tvp_go19.go
index 64e5e21..d3890af 100644
--- a/tvp_go19.go
+++ b/tvp_go19.go
@@ -4,6 +4,7 @@ package mssql
 
 import (
 	"bytes"
+	"database/sql"
 	"encoding/binary"
 	"errors"
 	"fmt"
@@ -97,6 +98,9 @@ func (tvp TVP) encode(schema, name string, columnStr []columnStruct, tvpFieldInd
 		for columnStrIdx, fieldIdx := range tvpFieldIndexes {
 			field := refStr.Field(fieldIdx)
 			tvpVal := field.Interface()
+			if tvp.verifyStandardTypeOnNull(buf, tvpVal) {
+				continue
+			}
 			valOf := reflect.ValueOf(tvpVal)
 			elemKind := field.Kind()
 			if elemKind == reflect.Ptr && valOf.IsNil() {
@@ -155,7 +159,7 @@ func (tvp TVP) columnTypes() ([]columnStruct, []int, error) {
 			defaultValues = append(defaultValues, v.Interface())
 			continue
 		}
-		defaultValues = append(defaultValues, reflect.Zero(field.Type).Interface())
+		defaultValues = append(defaultValues, tvp.createZeroType(reflect.Zero(field.Type).Interface()))
 	}
 
 	if columnCount-len(tvpFieldIndexes) == columnCount {
@@ -209,19 +213,23 @@ func getSchemeAndName(tvpName string) (string, string, error) {
 	}
 	splitVal := strings.Split(tvpName, ".")
 	if len(splitVal) > 2 {
-		return "", "", errors.New("wrong tvp name")
+		return "", "", ErrorObjectName
 	}
+	const (
+		openSquareBrackets  = "["
+		closeSquareBrackets = "]"
+	)
 	if len(splitVal) == 2 {
 		res := make([]string, 2)
 		for key, value := range splitVal {
-			tmp := strings.Replace(value, "[", "", -1)
-			tmp = strings.Replace(tmp, "]", "", -1)
+			tmp := strings.Replace(value, openSquareBrackets, "", -1)
+			tmp = strings.Replace(tmp, closeSquareBrackets, "", -1)
 			res[key] = tmp
 		}
 		return res[0], res[1], nil
 	}
-	tmp := strings.Replace(splitVal[0], "[", "", -1)
-	tmp = strings.Replace(tmp, "]", "", -1)
+	tmp := strings.Replace(splitVal[0], openSquareBrackets, "", -1)
+	tmp = strings.Replace(tmp, closeSquareBrackets, "", -1)
 
 	return "", tmp, nil
 }
@@ -229,3 +237,56 @@ func getSchemeAndName(tvpName string) (string, string, error) {
 func getCountSQLSeparators(str string) int {
 	return strings.Count(str, sqlSeparator)
 }
+
+// verify types https://golang.org/pkg/database/sql/
+func (tvp TVP) createZeroType(fieldVal interface{}) interface{} {
+	const (
+		defaultBool    = false
+		defaultFloat64 = float64(0)
+		defaultInt64   = int64(0)
+		defaultString  = ""
+	)
+
+	switch fieldVal.(type) {
+	case sql.NullBool:
+		return defaultBool
+	case sql.NullFloat64:
+		return defaultFloat64
+	case sql.NullInt64:
+		return defaultInt64
+	case sql.NullString:
+		return defaultString
+	}
+	return fieldVal
+}
+
+// verify types https://golang.org/pkg/database/sql/
+func (tvp TVP) verifyStandardTypeOnNull(buf *bytes.Buffer, tvpVal interface{}) bool {
+	const (
+		defaultNull = uint8(0)
+	)
+
+	switch val := tvpVal.(type) {
+	case sql.NullBool:
+		if !val.Valid {
+			binary.Write(buf, binary.LittleEndian, defaultNull)
+			return true
+		}
+	case sql.NullFloat64:
+		if !val.Valid {
+			binary.Write(buf, binary.LittleEndian, defaultNull)
+			return true
+		}
+	case sql.NullInt64:
+		if !val.Valid {
+			binary.Write(buf, binary.LittleEndian, defaultNull)
+			return true
+		}
+	case sql.NullString:
+		if !val.Valid {
+			binary.Write(buf, binary.LittleEndian, uint64(_PLP_NULL))
+			return true
+		}
+	}
+	return false
+}
diff --git a/types.go b/types.go
index b6e7fb2..0099f7b 100644
--- a/types.go
+++ b/types.go
@@ -690,6 +690,10 @@ func readPLPType(ti *typeInfo, r *tdsBuffer) interface{} {
 }
 
 func writePLPType(w io.Writer, ti typeInfo, buf []byte) (err error) {
+	if buf == nil {
+		err = binary.Write(w, binary.LittleEndian, uint64(_PLP_NULL))
+		return
+	}
 	if err = binary.Write(w, binary.LittleEndian, uint64(_UNKNOWN_PLP_LEN)); err != nil {
 		return
 	}


Test patch:
diff --git a/bulkcopy_test.go b/bulkcopy_test.go
index 9ceefea..f9c9e57 100644
--- a/bulkcopy_test.go
+++ b/bulkcopy_test.go
@@ -29,7 +29,11 @@ func TestBulkcopy(t *testing.T) {
 	uid := []byte{0x6F, 0x96, 0x19, 0xFF, 0x8B, 0x86, 0xD0, 0x11, 0xB4, 0x2D, 0x00, 0xC0, 0x4F, 0xC9, 0x64, 0xFF}
 	testValues := []testValue{
 		{"test_nvarchar", "ab©ĎéⒻghïjklmnopqЯ☀tuvwxyz", nil},
+		{"test_nvarchar_max", "ab©ĎéⒻghïjklmnopqЯ☀tuvwxyz", nil},
+		{"test_nvarchar_max_nil", nil, nil},
 		{"test_varchar", "abcdefg", nil},
+		{"test_varchar_max", "abcdefg", nil},
+		{"test_varchar_max_nil", nil, nil},
 		{"test_char", "abcdefg   ", nil},
 		{"test_nchar", "abcdefg   ", nil},
 		{"test_text", "abcdefg", nil},
@@ -51,6 +55,8 @@ func TestBulkcopy(t *testing.T) {
 		{"test_datetimeoffset_7", time.Date(2010, 11, 12, 13, 14, 15, 123000000, time.UTC), nil},
 		{"test_date", time.Date(2010, 11, 12, 0, 0, 0, 0, time.UTC), nil},
 		{"test_date_2", "2015-06-07", time.Date(2015, 6, 7, 0, 0, 0, 0, time.UTC)},
+		{"test_time", time.Date(2010, 11, 12, 13, 14, 15, 123000000, time.UTC), time.Date(1, 1, 1, 13, 14, 15, 123000000, time.UTC)},
+		{"test_time_2", "13:14:15.1230000", time.Date(1, 1, 1, 13, 14, 15, 123000000, time.UTC)},
 		{"test_tinyint", 255, nil},
 		{"test_smallint", 32767, nil},
 		{"test_smallintn", nil, nil},
@@ -108,6 +114,10 @@ func TestBulkcopy(t *testing.T) {
 	t.Log("Preparing copy in statement")
 
 	stmt, err := conn.PrepareContext(ctx, CopyIn(tableName, BulkOptions{}, columns...))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer stmt.Close()
 
 	for i := 0; i < 10; i++ {
 		t.Logf("Executing copy in statement %d time with %d values", i+1, len(values))
@@ -205,9 +215,11 @@ func setupTable(ctx context.Context, t *testing.T, conn *sql.Conn, tableName str
 	[test_nvarchar] [nvarchar](50) NULL,
 	[test_nvarchar_4000] [nvarchar](4000) NULL,
 	[test_nvarchar_max] [nvarchar](max) NULL,
+	[test_nvarchar_max_nil] [nvarchar](max) NULL,
 	[test_varchar] [varchar](50) NULL,
 	[test_varchar_8000] [varchar](8000) NULL,
 	[test_varchar_max] [varchar](max) NULL,
+	[test_varchar_max_nil] [varchar](max) NULL,
 	[test_char] [char](10) NULL,
 	[test_nchar] [nchar](10) NULL,
 	[test_text] [text] NULL,
@@ -229,6 +241,8 @@ func setupTable(ctx context.Context, t *testing.T, conn *sql.Conn, tableName str
 	[test_datetimeoffset_7] [datetimeoffset](7) NULL,
 	[test_date] [date] NULL,
 	[test_date_2] [date] NULL,
+	[test_time] [time](7) NULL,
+	[test_time_2] [time](7) NULL,
 	[test_smallmoney] [smallmoney] NULL,
 	[test_money] [money] NULL,
 	[test_tinyint] [tinyint] NULL,
diff --git a/datetimeoffset_example_test.go b/datetimeoffset_example_test.go
index 186f819..097a372 100644
--- a/datetimeoffset_example_test.go
+++ b/datetimeoffset_example_test.go
@@ -9,8 +9,8 @@ import (
 	"log"
 	"time"
 
+	mssql "github.com/denisenkom/go-mssqldb"
 	"github.com/golang-sql/civil"
-	"github.com/denisenkom/go-mssqldb"
 )
 
 // This example shows how to insert and retrieve date and time types data
@@ -49,6 +49,8 @@ func insertDateTime(db *sql.DB) {
 	if err != nil {
 		log.Fatal(err)
 	}
+	defer stmt.Close()
+
 	tin, err := time.Parse(time.RFC3339, "2006-01-02T22:04:05.787-07:00")
 	if err != nil {
 		log.Fatal(err)
diff --git a/lastinsertid_example_test.go b/lastinsertid_example_test.go
index 578fedb..760bc1c 100644
--- a/lastinsertid_example_test.go
+++ b/lastinsertid_example_test.go
@@ -55,6 +55,7 @@ func ExampleLastInsertId() {
 	if err != nil {
 		log.Fatal(err)
 	}
+	defer rows.Close()
 	var lastInsertId1 int64
 	for rows.Next() {
 		rows.Scan(&lastInsertId1)
diff --git a/ntlm_test.go b/ntlm_test.go
index a85317e..607d2c2 100644
--- a/ntlm_test.go
+++ b/ntlm_test.go
@@ -81,7 +81,7 @@ func TestNTLMV2Response(t *testing.T) {
 	target := "DOMAIN"
 	username := "user"
 	password := "SecREt01"
-	challenge := [8]byte{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
+	challenge := [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
 	targetInformationBlock, _ := hex.DecodeString("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000")
 	nonceBytes, _ := hex.DecodeString("ffffff0011223344")
 	var nonce [8]byte
@@ -105,16 +105,11 @@ func TestNTLMV2Response(t *testing.T) {
 
 func TestGetNTLMv2TargetInfoFields(t *testing.T) {
 	type2Message, _ := hex.DecodeString("4e544c4d53535000020000000600060038000000058289026999bc21067c77f40000000000000000ac00ac003e0000000a0039380000000f4600570042000200060046005700420001000c00590037004100410041003400040022006000700065002e00610058006e0071006e0070006e00650074002e0063006f006d00030030007900370041004100410034002e006000700065002e00610058006e0071006e0070006e00650074002e0063006f006d00050024006100610058006d002e00610058006e0071006e0070006e00650074002e0063006f006d00070008007d9647e8aed6d50100000000")
-	info, target, err := getNTLMv2TargetInfoFields(type2Message)
+	info, err := getNTLMv2TargetInfoFields(type2Message)
 	if err != nil {
 		t.Errorf("got:\n%e\nexpected:\nnil", err)
 	}
 
-	expectedTarget := "FWB"
-	if target != expectedTarget {
-		t.Errorf("got:\n%s\nexpected:\n%s", target, expectedTarget)
-	}
-
 	expectedResponseLength := 172
 	responseLength := len(info)
 	if responseLength != expectedResponseLength {
@@ -124,7 +119,7 @@ func TestGetNTLMv2TargetInfoFields(t *testing.T) {
 
 func TestGetNTLMv2TargetInfoFieldsInvalidMessage(t *testing.T) {
 	type2Message, _ := hex.DecodeString("4e544c4d53535000020000000600060038000000058289026999bc21067c77f40000000000000000ac00ac003e0000000a0039380000000f4600570042000200060046005700420001000c00590037004100410041003400040022006000700065002e00610058006e0071006e0070006e00650074002e0063006f006d00030030007900370041004100410034002e006000700065002e00610058006e0071006e0070006e00650074002e0063006f006d00050024006100610058006d002e00610058006e0071006e0070006e00650074002e0063006f006d00070008007")
-	_, _, err := getNTLMv2TargetInfoFields(type2Message)
+	_, err := getNTLMv2TargetInfoFields(type2Message)
 	if err == nil {
 		t.Error("expected to get an error")
 	}
diff --git a/queries_go110_test.go b/queries_go110_test.go
index debb636..b1e1340 100644
--- a/queries_go110_test.go
+++ b/queries_go110_test.go
@@ -207,6 +207,8 @@ func TestReturnStatusWithQuery(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	defer rows.Close()
+
 	var str string
 	for rows.Next() {
 		err = rows.Scan(&str)
diff --git a/queries_go19_test.go b/queries_go19_test.go
index e3addd0..9c585e2 100644
--- a/queries_go19_test.go
+++ b/queries_go19_test.go
@@ -60,6 +60,7 @@ END;
 		if err != nil {
 			t.Error(err)
 		}
+		defer rows.Close()
 		// reading first row
 		if !rows.Next() {
 			t.Error("Next returned false")
@@ -947,10 +948,10 @@ with
 					}
 					return
 				}
+				defer rows.Close()
 				for rows.Next() {
 					// Nothing.
 				}
-				rows.Close()
 			})
 		}
 	}
diff --git a/queries_test.go b/queries_test.go
index 3625788..e325633 100644
--- a/queries_test.go
+++ b/queries_test.go
@@ -585,6 +585,7 @@ func TestTwoQueries(t *testing.T) {
 	if err != nil {
 		t.Fatal("First exec failed", err)
 	}
+	defer rows.Close()
 	if !rows.Next() {
 		t.Fatal("First query didn't return row")
 	}
@@ -595,10 +596,14 @@ func TestTwoQueries(t *testing.T) {
 	if i != 1 {
 		t.Fatalf("Wrong value returned %d, should be 1", i)
 	}
+	if rows.Next() {
+		t.Fatal("First query returns too many rows")
+	}
 
 	if rows, err = conn.Query("select 2"); err != nil {
 		t.Fatal("Second query failed", err)
 	}
+	defer rows.Close()
 	if !rows.Next() {
 		t.Fatal("Second query didn't return row")
 	}
@@ -608,14 +613,18 @@ func TestTwoQueries(t *testing.T) {
 	if i != 2 {
 		t.Fatalf("Wrong value returned %d, should be 2", i)
 	}
+	if rows.Next() {
+		t.Fatal("Second query returns too many rows")
+	}
 }
 
 func TestError(t *testing.T) {
 	conn := open(t)
 	defer conn.Close()
 
-	_, err := conn.Query("exec bad")
+	row, err := conn.Query("exec bad")
 	if err == nil {
+		defer row.Close()
 		t.Fatal("Query should fail")
 	}
 
@@ -637,6 +646,7 @@ func TestQueryNoRows(t *testing.T) {
 	if rows, err = conn.Query("create table #abc (fld int)"); err != nil {
 		t.Fatal("Query failed", err)
 	}
+	defer rows.Close()
 	if rows.Next() {
 		t.Fatal("Query shoulnd't return any rows")
 	}
@@ -689,6 +699,7 @@ func TestOrderBy(t *testing.T) {
 	if err != nil {
 		t.Fatal("Query failed", err)
 	}
+	defer rows.Close()
 
 	for rows.Next() {
 		var fld1 int32
@@ -872,7 +883,9 @@ func TestUniqueIdentifierParam(t *testing.T) {
 func TestBigQuery(t *testing.T) {
 	conn := open(t)
 	defer conn.Close()
-	rows, err := conn.Query(`WITH n(n) AS
+
+	func() {
+		rows, err := conn.Query(`WITH n(n) AS
 		(
 		    SELECT 1
 		    UNION ALL
@@ -880,13 +893,15 @@ func TestBigQuery(t *testing.T) {
 		)
 		SELECT n, @@version FROM n ORDER BY n
 		OPTION (MAXRECURSION 10000);`)
-	if err != nil {
-		t.Fatal("cannot exec query", err)
-	}
-	rows.Next()
-	rows.Close()
+		if err != nil {
+			t.Fatal("cannot exec query", err)
+		}
+		defer rows.Close()
+		rows.Next()
+	}()
+
 	var res int
-	err = conn.QueryRow("select 0").Scan(&res)
+	err := conn.QueryRow("select 0").Scan(&res)
 	if err != nil {
 		t.Fatal("cannot scan value", err)
 	}
@@ -928,6 +943,7 @@ func TestIgnoreEmptyResults(t *testing.T) {
 	if err != nil {
 		t.Fatal("Query failed", err.Error())
 	}
+	defer rows.Close()
 	if !rows.Next() {
 		t.Fatal("Query didn't return row")
 	}
@@ -1031,17 +1047,20 @@ func TestConnectionClosing(t *testing.T) {
 			return
 		}
 
-		stmt, err := pool.Query("select 1")
-		if err != nil {
-			t.Fatalf("Query failed with unexpected error %s", err)
-		}
-		for stmt.Next() {
-			var val interface{}
-			err := stmt.Scan(&val)
+		func() {
+			rows, err := pool.Query("select 1")
 			if err != nil {
 				t.Fatalf("Query failed with unexpected error %s", err)
 			}
-		}
+			defer rows.Close()
+			for rows.Next() {
+				var val interface{}
+				err := rows.Scan(&val)
+				if err != nil {
+					t.Fatalf("Query failed with unexpected error %s", err)
+				}
+			}
+		}()
 	}
 }
 
@@ -1463,6 +1482,7 @@ func TestColumnTypeIntrospection(t *testing.T) {
 		if err != nil {
 			t.Fatalf("Query failed with unexpected error %s", err)
 		}
+		defer rows.Close()
 		ct, err := rows.ColumnTypes()
 		if err != nil {
 			t.Fatalf("Query failed with unexpected error %s", err)
@@ -1527,6 +1547,7 @@ func TestColumnIntrospection(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Query failed with unexpected error %s", err)
 	}
+	defer rows.Close()
 
 	ct, err := rows.ColumnTypes()
 	if err != nil {
@@ -1586,19 +1607,27 @@ func TestContext(t *testing.T) {
 		t.Errorf("BeginTx failed with unexpected error %s", err)
 		return
 	}
-	rows, err := tx.QueryContext(ctx, "DBCC USEROPTIONS")
-	properties := make(map[string]string)
-	for rows.Next() {
-		var name, value string
-		if err = rows.Scan(&name, &value); err != nil {
-			t.Errorf("Scan failed with unexpected error %s", err)
-		}
-		properties[name] = value
-	}
+	defer tx.Rollback()
 
-	if properties["isolation level"] != "serializable" {
-		t.Errorf("Expected isolation level to be serializable but it is %s", properties["isolation level"])
-	}
+	// check the isolation level
+	func() {
+		rows, err := tx.QueryContext(ctx, "DBCC USEROPTIONS")
+		if err != nil {
+			t.Fatal(err)
+		}
+		defer rows.Close()
+		properties := make(map[string]string)
+		for rows.Next() {
+			var name, value string
+			if err = rows.Scan(&name, &value); err != nil {
+				t.Errorf("Scan failed with unexpected error %s", err)
+			}
+			properties[name] = value
+		}
+		if properties["isolation level"] != "serializable" {
+			t.Errorf("Expected isolation level to be serializable but it is %s", properties["isolation level"])
+		}
+	}()
 
 	row := tx.QueryRowContext(ctx, "select 1")
 	var val int64
@@ -1615,11 +1644,12 @@ func TestContext(t *testing.T) {
 		return
 	}
 
-	_, err = tx.PrepareContext(ctx, "select 1")
+	stmt, err := tx.PrepareContext(ctx, "select 1")
 	if err != nil {
 		t.Errorf("PrepareContext failed with unexpected error %s", err)
 		return
 	}
+	defer stmt.Close()
 }
 
 func TestBeginTxtReadOnlyNotSupported(t *testing.T) {
@@ -1664,6 +1694,7 @@ func TestConn_BeginTx(t *testing.T) {
 	if err != nil {
 		t.Fatal("select failed with error", err)
 	}
+	defer rows.Close()
 	values := []int64{}
 	for rows.Next() {
 		var val int64
@@ -1797,6 +1828,7 @@ func TestQueryCancelLowLevel(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Query failed with error %v", err)
 	}
+	defer rows.Close()
 
 	values := []driver.Value{nil}
 	err = rows.Next(values)
diff --git a/tds_test.go b/tds_test.go
index e725a66..9341829 100644
--- a/tds_test.go
+++ b/tds_test.go
@@ -145,6 +145,11 @@ func TestSendSqlBatch(t *testing.T) {
 
 	ch := make(chan tokenStruct, 5)
 	go processResponse(context.Background(), conn, ch, nil)
+	defer func() {
+		// make share ch is closed
+		for range ch {
+		}
+	}()
 
 	var lastRow []interface{}
 loop:
@@ -174,7 +179,7 @@ loop:
 	}
 }
 
-func checkConnStr(t *testing.T) {
+func checkConnStr(t testing.TB) {
 	if len(os.Getenv("SQLSERVER_DSN")) > 0 {
 		return
 	}
@@ -186,7 +191,7 @@ func checkConnStr(t *testing.T) {
 
 // makeConnStr returns a URL struct so it may be modified by various
 // tests before used as a DSN.
-func makeConnStr(t *testing.T) *url.URL {
+func makeConnStr(t testing.TB) *url.URL {
 	dsn := os.Getenv("SQLSERVER_DSN")
 	if len(dsn) > 0 {
 		parsed, err := url.Parse(dsn)
@@ -213,7 +218,7 @@ func makeConnStr(t *testing.T) *url.URL {
 }
 
 type testLogger struct {
-	t *testing.T
+	t testing.TB
 }
 
 func (l testLogger) Printf(format string, v ...interface{}) {
@@ -227,12 +232,18 @@ func (l testLogger) Println(v ...interface{}) {
 func TestConnect(t *testing.T) {
 	checkConnStr(t)
 	SetLogger(testLogger{t})
-	conn, err := sql.Open("mssql", makeConnStr(t).String())
+	conn, err := sql.Open("mssql", os.Getenv("SQLSERVER_DSN"))
 	if err != nil {
 		t.Error("Open connection failed:", err.Error())
 		return
 	}
 	defer conn.Close()
+	row := conn.QueryRow("select 1")
+	var val int
+	err = row.Scan(&val)
+	if err != nil {
+		t.Error("Scan failed:", err.Error())
+	}
 }
 
 func simpleQuery(conn *sql.DB, t *testing.T) (stmt *sql.Stmt) {
@@ -332,20 +343,22 @@ func TestMultipleQueryClose(t *testing.T) {
 	}
 	defer stmt.Close()
 
-	rows, err := stmt.Query()
-	if err != nil {
-		t.Error("Query failed:", err.Error())
-		return
-	}
-	rows.Close()
+	func() {
+		rows, err := stmt.Query()
+		if err != nil {
+			t.Fatal("Query failed:", err.Error())
+		}
+		defer rows.Close()
+	}()
 
-	rows, err = stmt.Query()
-	if err != nil {
-		t.Error("Query failed:", err.Error())
-		return
-	}
-	defer rows.Close()
-	checkSimpleQuery(rows, t)
+	func() {
+		rows, err := stmt.Query()
+		if err != nil {
+			t.Fatal("Query failed:", err.Error())
+		}
+		defer rows.Close()
+		checkSimpleQuery(rows, t)
+	}()
 }
 
 func TestPing(t *testing.T) {
@@ -534,3 +547,81 @@ func TestReadBVarByte(t *testing.T) {
 		t.Error("readUsVarByte should fail on short buffer, but it didn't")
 	}
 }
+
+func BenchmarkPacketSize(b *testing.B) {
+	checkConnStr(b)
+	p, err := parseConnectParams(makeConnStr(b).String())
+	if err != nil {
+		b.Error("parseConnectParams failed:", err.Error())
+		return
+	}
+
+	benchmarks := []struct {
+		name       string
+		packetSize uint16
+	}{
+		{name: "PacketSize 2048", packetSize: 2048},
+		{name: "PacketSize 4096", packetSize: 4096},
+		{name: "PacketSize 8192", packetSize: 8192},
+		{name: "PacketSize 16384", packetSize: 16384},
+	}
+
+	for _, bm := range benchmarks {
+		b.Run(bm.name, func(b *testing.B) {
+			for i := 0; i < b.N; i++ {
+				p.packetSize = bm.packetSize
+				runBatch(b, p)
+			}
+		})
+	}
+
+}
+
+func runBatch(t testing.TB, p connectParams) {
+	conn, err := connect(context.Background(), nil, optionalLogger{testLogger{t}}, p)
+	if err != nil {
+		t.Error("Open connection failed:", err.Error())
+		return
+	}
+	defer conn.buf.transport.Close()
+
+	headers := []headerStruct{
+		{hdrtype: dataStmHdrTransDescr,
+			data: transDescrHdr{0, 1}.pack()},
+	}
+	err = sendSqlBatch72(conn.buf, "select 1", headers, true)
+	if err != nil {
+		t.Error("Sending sql batch failed", err.Error())
+		return
+	}
+
+	ch := make(chan tokenStruct, 5)
+	go processResponse(context.Background(), conn, ch, nil)
+
+	var lastRow []interface{}
+loop:
+	for tok := range ch {
+		switch token := tok.(type) {
+		case doneStruct:
+			break loop
+		case []columnStruct:
+			conn.columns = token
+		case []interface{}:
+			lastRow = token
+		default:
+			t.Log("unknown token", tok)
+		}
+	}
+
+	if len(lastRow) == 0 {
+		t.Fatal("expected row but no row set")
+	}
+
+	switch value := lastRow[0].(type) {
+	case int32:
+		if value != 1 {
+			t.Error("Invalid value returned, should be 1", value)
+			return
+		}
+	}
+}
diff --git a/tvp_go19_db_test.go b/tvp_go19_db_test.go
index 6cf4264..6703ce5 100644
--- a/tvp_go19_db_test.go
+++ b/tvp_go19_db_test.go
@@ -11,123 +11,412 @@ import (
 	"time"
 )
 
-const (
-	crateSchema = `create schema TestTVPSchema;`
+func TestTVPGoSQLTypesWithStandardType(t *testing.T) {
+	checkConnStr(t)
+	SetLogger(testLogger{t})
 
-	dropSchema = `drop schema TestTVPSchema;`
+	c := makeConnStr(t).String()
+	db, err := sql.Open("sqlserver", c)
+	if err != nil {
+		t.Fatalf("failed to open driver sqlserver")
+	}
+	defer db.Close()
 
-	createTVP = `
-		CREATE TYPE TestTVPSchema.exempleTVP AS TABLE
-		(
-			message	NVARCHAR(100)
-		)`
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	sqltextcreatetable := `
+		CREATE TYPE tvpGoSQLTypesWithStandardType AS TABLE (
+			p_bit               BIT,
+			p_bitNull           BIT,	
+			s_bit               BIT,
+			s_bitNull           BIT,	
+			p_float64           FLOAT,
+			p_floatNull64       FLOAT,
+			s_float64           FLOAT,
+			s_floatNull64       FLOAT,
+			p_bigint            BIGINT,
+			p_bigintNull        BIGINT,
+			s_bigint            BIGINT,
+			s_bigintNull        BIGINT,
+			p_nvarchar 			NVARCHAR(100),
+			p_nvarcharNull 		NVARCHAR(100),
+			s_nvarchar 			NVARCHAR(100),
+			s_nvarcharNull 		NVARCHAR(100)		
+		); `
 
-	dropTVP = `DROP TYPE TestTVPSchema.exempleTVP;`
+	sqltextdroptable := `DROP TYPE tvpGoSQLTypesWithStandardType;`
 
-	procedureWithTVP = `	
-	CREATE PROCEDURE ExecTVP
-		@param1 TestTVPSchema.exempleTVP READONLY
+	sqltextcreatesp := `
+	CREATE PROCEDURE spwithtvpGoSQLTypesWithStandardType
+		@param1 tvpGoSQLTypesWithStandardType READONLY,
+		@param2 tvpGoSQLTypesWithStandardType READONLY,
+		@param3 NVARCHAR(10)
 	AS   
 	BEGIN
 		SET NOCOUNT ON; 
 		SELECT * FROM @param1;
-	END;
-	`
+		SELECT * FROM @param2;
+		SELECT @param3;
+	END;`
 
-	dropProcedure = `drop PROCEDURE ExecTVP`
+	type TvpGoSQLTypes struct {
+		PBool        sql.NullBool
+		PBoolNull    sql.NullBool
+		SBool        bool
+		SBoolNull    *bool
+		PFloat64     sql.NullFloat64
+		PFloat64Null sql.NullFloat64
+		SFloat64     float64
+		SFloat64Null *float64
+		PInt64       sql.NullInt64
+		PInt64Null   sql.NullInt64
+		SInt64       int64
+		SInt64Null   *int64
+		PString      sql.NullString
+		PStringNull  sql.NullString
+		SString      string
+		SStringNull  *string
+	}
 
-	execTvp = `exec ExecTVP @param1;`
-)
+	sqltextdropsp := `DROP PROCEDURE spwithtvpGoSQLTypesWithStandardType;`
+
+	_, err = db.ExecContext(ctx, sqltextcreatetable)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer db.ExecContext(ctx, sqltextdroptable)
+
+	_, err = db.ExecContext(ctx, sqltextcreatesp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer db.ExecContext(ctx, sqltextdropsp)
+
+	nvarchar := "bbb"
+	i64 := int64(4)
+	bTrue := true
+	floatValue64 := 0.123
+	param1 := []TvpGoSQLTypes{
+		{
+			PBool: sql.NullBool{
+				Bool:  true,
+				Valid: true,
+			},
+			PBoolNull: sql.NullBool{},
+			PFloat64: sql.NullFloat64{
+				Float64: 14.33,
+				Valid:   true,
+			},
+			PFloat64Null: sql.NullFloat64{},
+			PInt64: sql.NullInt64{
+				Int64: 777,
+				Valid: true,
+			},
+			PInt64Null: sql.NullInt64{},
+			PString: sql.NullString{
+				String: "test=tvp",
+				Valid:  true,
+			},
+			PStringNull: sql.NullString{},
+		},
+		{
+			PBool:        sql.NullBool{},
+			PBoolNull:    sql.NullBool{},
+			SBool:        true,
+			SBoolNull:    nil,
+			PFloat64:     sql.NullFloat64{},
+			PFloat64Null: sql.NullFloat64{},
+			SFloat64:     1.1,
+			SFloat64Null: nil,
+			PInt64:       sql.NullInt64{},
+			PInt64Null:   sql.NullInt64{},
+			SInt64:       1,
+			SInt64Null:   nil,
+			PString:      sql.NullString{},
+			PStringNull:  sql.NullString{},
+			SString:      "any",
+			SStringNull:  nil,
+		},
+		{
+			PBool: sql.NullBool{
+				Bool:  false,
+				Valid: true,
+			},
+			PBoolNull: sql.NullBool{},
+			SBool:     true,
+			SBoolNull: &bTrue,
+			PFloat64: sql.NullFloat64{
+				Float64: 1.2,
+				Valid:   true,
+			},
+			PFloat64Null: sql.NullFloat64{},
+			SFloat64:     3.4,
+			SFloat64Null: &floatValue64,
+			PInt64: sql.NullInt64{
+				Int64: 32423,
+				Valid: true,
+			},
+			PInt64Null: sql.NullInt64{},
+			SInt64:     9738847389,
+			SInt64Null: &i64,
+			PString: sql.NullString{
+				String: "jifdijrgio",
+				Valid:  true,
+			},
+			PStringNull: sql.NullString{},
+			SString:     "geniefkl123",
+			SStringNull: &nvarchar,
+		},
+	}
+
+	tvpType := TVP{
+		TypeName: "tvpGoSQLTypesWithStandardType",
+		Value:    param1,
+	}
+	tvpTypeEmpty := TVP{
+		TypeName: "tvpGoSQLTypesWithStandardType",
+		Value:    []TvpGoSQLTypes{},
+	}
+
+	rows, err := db.QueryContext(ctx,
+		"exec spwithtvpGoSQLTypesWithStandardType @param1, @param2, @param3",
+		sql.Named("param1", tvpType),
+		sql.Named("param2", tvpTypeEmpty),
+		sql.Named("param3", "test"),
+	)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var result1 []TvpGoSQLTypes
+	for rows.Next() {
+		var val TvpGoSQLTypes
+		err := rows.Scan(
+			&val.PBool,
+			&val.PBoolNull,
+			&val.SBool,
+			&val.SBoolNull,
+
+			&val.PFloat64,
+			&val.PFloat64Null,
+			&val.SFloat64,
+			&val.SFloat64Null,
+			&val.PInt64,
+			&val.PInt64Null,
+			&val.SInt64,
+			&val.SInt64Null,
+			&val.PString,
+			&val.PStringNull,
+			&val.SString,
+			&val.SStringNull,
+		)
+		if err != nil {
+			t.Fatalf("scan failed with error: %s", err)
+		}
+
+		result1 = append(result1, val)
+	}
+
+	if !reflect.DeepEqual(param1, result1) {
+		t.Logf("expected: %+v", param1)
+		t.Logf("actual: %+v", result1)
+		t.Errorf("first resultset did not match param1")
+	}
+
+	if !rows.NextResultSet() {
+		t.Errorf("second resultset did not exist")
+	}
 
-type TvptableRow struct {
-	PBinary       []byte            `db:"p_binary"`
-	PVarchar      string            `db:"p_varchar"`
-	PVarcharNull  *string           `db:"p_varcharNull"`
-	PNvarchar     string            `db:"p_nvarchar"`
-	PNvarcharNull *string           `db:"p_nvarcharNull"`
-	PID           UniqueIdentifier  `db:"p_id"`
-	PIDNull       *UniqueIdentifier `db:"p_idNull"`
-	PVarbinary    []byte            `db:"p_varbinary"`
-	PTinyint      int8              `db:"p_tinyint"`
-	PTinyintNull  *int8             `db:"p_tinyintNull"`
-	PSmallint     int16             `db:"p_smallint"`
-	PSmallintNull *int16            `db:"p_smallintNull"`
-	PInt          int32             `db:"p_int"`
-	PIntNull      *int32            `db:"p_intNull"`
-	PBigint       int64             `db:"p_bigint"`
-	PBigintNull   *int64            `db:"p_bigintNull"`
-	PBit          bool              `db:"p_bit"`
-	PBitNull      *bool             `db:"p_bitNull"`
-	PFloat32      float32           `db:"p_float32"`
-	PFloatNull32  *float32          `db:"p_floatNull32"`
-	PFloat64      float64           `db:"p_float64"`
-	PFloatNull64  *float64          `db:"p_floatNull64"`
-	DTime         time.Time         `db:"p_timeNull"`
-	DTimeNull     *time.Time        `db:"p_time"`
-	Pint          int               `db:"pInt"`
-	PintNull      *int              `db:"pIntNull"`
+	if rows.Next() {
+		t.Errorf("second resultset was not empty")
+	}
+
+	if !rows.NextResultSet() {
+		t.Errorf("third resultset did not exist")
+	}
+
+	if !rows.Next() {
+		t.Errorf("third resultset was empty")
+	}
+
+	var result3 string
+	if err := rows.Scan(&result3); err != nil {
+		t.Errorf("error scanning third result set: %s", err)
+	}
+	if result3 != "test" {
+		t.Errorf("third result set had wrong value expected: %s actual: %s", "test", result3)
+	}
 }
 
-type TvptableRowWithSkipTag struct {
-	PBinary           []byte            `db:"p_binary"`
-	SkipPBinary       []byte            `json:"-"`
-	PVarchar          string            `db:"p_varchar"`
-	SkipPVarchar      string            `tvp:"-"`
-	PVarcharNull      *string           `db:"p_varcharNull"`
-	SkipPVarcharNull  *string           `json:"-" tvp:"-"`
-	PNvarchar         string            `db:"p_nvarchar"`
-	SkipPNvarchar     string            `json:"-"`
-	PNvarcharNull     *string           `db:"p_nvarcharNull"`
-	SkipPNvarcharNull *string           `json:"-"`
-	PID               UniqueIdentifier  `db:"p_id"`
-	SkipPID           UniqueIdentifier  `json:"-"`
-	PIDNull           *UniqueIdentifier `db:"p_idNull"`
-	SkipPIDNull       *UniqueIdentifier `tvp:"-"`
-	PVarbinary        []byte            `db:"p_varbinary"`
-	SkipPVarbinary    []byte            `json:"-" tvp:"-"`
-	PTinyint          int8              `db:"p_tinyint"`
-	SkipPTinyint      int8              `tvp:"-"`
-	PTinyintNull      *int8             `db:"p_tinyintNull"`
-	SkipPTinyintNull  *int8             `tvp:"-" json:"any"`
-	PSmallint         int16             `db:"p_smallint"`
-	SkipPSmallint     int16             `json:"-"`
-	PSmallintNull     *int16            `db:"p_smallintNull"`
-	SkipPSmallintNull *int16            `tvp:"-"`
-	PInt              int32             `db:"p_int"`
-	SkipPInt          int32             `json:"-"`
-	PIntNull          *int32            `db:"p_intNull"`
-	SkipPIntNull      *int32            `tvp:"-"`
-	PBigint           int64             `db:"p_bigint"`
-	SkipPBigint       int64             `tvp:"-"`
-	PBigintNull       *int64            `db:"p_bigintNull"`
-	SkipPBigintNull   *int64            `json:"any" tvp:"-"`
-	PBit              bool              `db:"p_bit"`
-	SkipPBit          bool              `json:"-"`
-	PBitNull          *bool             `db:"p_bitNull"`
-	SkipPBitNull      *bool             `json:"-"`
-	PFloat32          float32           `db:"p_float32"`
-	SkipPFloat32      float32           `tvp:"-"`
-	PFloatNull32      *float32          `db:"p_floatNull32"`
-	SkipPFloatNull32  *float32          `tvp:"-"`
-	PFloat64          float64           `db:"p_float64"`
-	SkipPFloat64      float64           `tvp:"-"`
-	PFloatNull64      *float64          `db:"p_floatNull64"`
-	SkipPFloatNull64  *float64          `tvp:"-"`
-	DTime             time.Time         `db:"p_timeNull"`
-	SkipDTime         time.Time         `tvp:"-"`
-	DTimeNull         *time.Time        `db:"p_time"`
-	SkipDTimeNull     *time.Time        `tvp:"-"`
-	Pint              int               `db:"p_int_null"`
-	SkipPint          int               `tvp:"-"`
-	PintNull          *int              `db:"p_int_"`
-	SkipPintNull      *int              `tvp:"-"`
+func TestTVPGoSQLTypes(t *testing.T) {
+	checkConnStr(t)
+	SetLogger(testLogger{t})
+
+	c := makeConnStr(t).String()
+	db, err := sql.Open("sqlserver", c)
+	if err != nil {
+		t.Fatalf("failed to open driver sqlserver")
+	}
+	defer db.Close()
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	sqltextcreatetable := `
+		CREATE TYPE tvpGoSQLTypes AS TABLE (
+			p_bit               BIT,
+			p_bitNull           BIT,	
+			p_float64           FLOAT,
+			p_floatNull64       FLOAT,
+			p_bigint            BIGINT,
+			p_bigintNull        BIGINT,
+			p_nvarchar 			NVARCHAR(100),
+			p_nvarcharNull 		NVARCHAR(100)		
+		); `
+
+	sqltextdroptable := `DROP TYPE tvpGoSQLTypes;`
+
+	sqltextcreatesp := `
+	CREATE PROCEDURE spwithtvpGoSQLTypes
+		@param1 tvpGoSQLTypes READONLY,
+		@param2 tvpGoSQLTypes READONLY,
+		@param3 NVARCHAR(10)
+	AS   
+	BEGIN
+		SET NOCOUNT ON; 
+		SELECT * FROM @param1;
+		SELECT * FROM @param2;
+		SELECT @param3;
+	END;`
+
+	type TvpGoSQLTypes struct {
+		PBool        sql.NullBool
+		PBoolNull    sql.NullBool
+		PFloat64     sql.NullFloat64
+		PFloat64Null sql.NullFloat64
+		PInt64       sql.NullInt64
+		PInt64Null   sql.NullInt64
+		PString      sql.NullString
+		PStringNull  sql.NullString
+	}
+
+	sqltextdropsp := `DROP PROCEDURE spwithtvpGoSQLTypes;`
+
+	_, err = db.ExecContext(ctx, sqltextcreatetable)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer db.ExecContext(ctx, sqltextdroptable)
+
+	_, err = db.ExecContext(ctx, sqltextcreatesp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer db.ExecContext(ctx, sqltextdropsp)
+	param1 := []TvpGoSQLTypes{
+		{
+			PBool: sql.NullBool{
+				Bool:  true,
+				Valid: true,
+			},
+			PBoolNull: sql.NullBool{},
+			PFloat64: sql.NullFloat64{
+				Float64: 14.33,
+				Valid:   true,
+			},
+			PFloat64Null: sql.NullFloat64{},
+			PInt64: sql.NullInt64{
+				Int64: 777,
+				Valid: true,
+			},
+			PInt64Null: sql.NullInt64{},
+			PString: sql.NullString{
+				String: "test=tvp",
+				Valid:  true,
+			},
+			PStringNull: sql.NullString{},
+		},
+	}
+
+	tvpType := TVP{
+		TypeName: "tvpGoSQLTypes",
+		Value:    param1,
+	}
+	tvpTypeEmpty := TVP{
+		TypeName: "tvpGoSQLTypes",
+		Value:    []TvpGoSQLTypes{},
+	}
+
+	rows, err := db.QueryContext(ctx,
+		"exec spwithtvpGoSQLTypes @param1, @param2, @param3",
+		sql.Named("param1", tvpType),
+		sql.Named("param2", tvpTypeEmpty),
+		sql.Named("param3", "test"),
+	)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var result1 []TvpGoSQLTypes
+	for rows.Next() {
+		var val TvpGoSQLTypes
+		err := rows.Scan(
+			&val.PBool,
+			&val.PBoolNull,
+			&val.PFloat64,
+			&val.PFloat64Null,
+			&val.PInt64,
+			&val.PInt64Null,
+			&val.PString,
+			&val.PStringNull,
+		)
+		if err != nil {
+			t.Fatalf("scan failed with error: %s", err)
+		}
+
+		result1 = append(result1, val)
+	}
+
+	if !reflect.DeepEqual(param1, result1) {
+		t.Logf("expected: %+v", param1)
+		t.Logf("actual: %+v", result1)
+		t.Errorf("first resultset did not match param1")
+	}
+
+	if !rows.NextResultSet() {
+		t.Errorf("second resultset did not exist")
+	}
+
+	if rows.Next() {
+		t.Errorf("second resultset was not empty")
+	}
+
+	if !rows.NextResultSet() {
+		t.Errorf("third resultset did not exist")
+	}
+
+	if !rows.Next() {
+		t.Errorf("third resultset was empty")
+	}
+
+	var result3 string
+	if err := rows.Scan(&result3); err != nil {
+		t.Errorf("error scanning third result set: %s", err)
+	}
+	if result3 != "test" {
+		t.Errorf("third result set had wrong value expected: %s actual: %s", "test", result3)
+	}
 }
 
 func TestTVP(t *testing.T) {
 	checkConnStr(t)
 	SetLogger(testLogger{t})
 
-	db, err := sql.Open("sqlserver", makeConnStr(t).String())
+	c := makeConnStr(t).String()
+	db, err := sql.Open("sqlserver", c)
 	if err != nil {
 		t.Fatalf("failed to open driver sqlserver")
 	}
@@ -182,10 +471,36 @@ func TestTVP(t *testing.T) {
 		SELECT @param3;
 	END;`
 
-	sqltextdropsp := `DROP PROCEDURE spwithtvp;`
+	type TvptableRow struct {
+		PBinary       []byte            `db:"p_binary"`
+		PVarchar      string            `db:"p_varchar"`
+		PVarcharNull  *string           `db:"p_varcharNull"`
+		PNvarchar     string            `db:"p_nvarchar"`
+		PNvarcharNull *string           `db:"p_nvarcharNull"`
+		PID           UniqueIdentifier  `db:"p_id"`
+		PIDNull       *UniqueIdentifier `db:"p_idNull"`
+		PVarbinary    []byte            `db:"p_varbinary"`
+		PTinyint      int8              `db:"p_tinyint"`
+		PTinyintNull  *int8             `db:"p_tinyintNull"`
+		PSmallint     int16             `db:"p_smallint"`
+		PSmallintNull *int16            `db:"p_smallintNull"`
+		PInt          int32             `db:"p_int"`
+		PIntNull      *int32            `db:"p_intNull"`
+		PBigint       int64             `db:"p_bigint"`
+		PBigintNull   *int64            `db:"p_bigintNull"`
+		PBit          bool              `db:"p_bit"`
+		PBitNull      *bool             `db:"p_bitNull"`
+		PFloat32      float32           `db:"p_float32"`
+		PFloatNull32  *float32          `db:"p_floatNull32"`
+		PFloat64      float64           `db:"p_float64"`
+		PFloatNull64  *float64          `db:"p_floatNull64"`
+		DTime         time.Time         `db:"p_timeNull"`
+		DTimeNull     *time.Time        `db:"p_time"`
+		Pint          int               `db:"pInt"`
+		PintNull      *int              `db:"pIntNull"`
+	}
 
-	db.ExecContext(ctx, sqltextdropsp)
-	db.ExecContext(ctx, sqltextdroptable)
+	sqltextdropsp := `DROP PROCEDURE spwithtvp;`
 
 	_, err = db.ExecContext(ctx, sqltextcreatetable)
 	if err != nil {
@@ -209,7 +524,9 @@ func TestTVP(t *testing.T) {
 	bFalse := false
 	floatValue64 := 0.123
 	floatValue32 := float32(-10.123)
-	timeNow := time.Now().UTC()
+	// SQL Server's datetime2 has maximum precision of 7 digits, so for end-to-end
+	// equality comparison we must not test with any finer resolution than 100 nsec.
+	datetime2Value := time.Date(2020, 8, 26, 23, 59, 39, 100, time.UTC)
 	param1 := []TvptableRow{
 		{
 			PBinary:    []byte("ccc"),
@@ -224,7 +541,7 @@ func TestTVP(t *testing.T) {
 			PBit:       bFalse,
 			PFloat32:   floatValue32,
 			PFloat64:   floatValue64,
-			DTime:      timeNow,
+			DTime:      datetime2Value,
 			Pint:       355,
 		},
 		{
@@ -255,8 +572,8 @@ func TestTVP(t *testing.T) {
 			PBitNull:      &bFalse,
 			PFloatNull32:  &floatValue32,
 			PFloatNull64:  &floatValue64,
-			DTime:         timeNow,
-			DTimeNull:     &timeNow,
+			DTime:         datetime2Value,
+			DTimeNull:     &datetime2Value,
 			PintNull:      &i,
 		},
 		{
@@ -281,7 +598,7 @@ func TestTVP(t *testing.T) {
 			PBitNull:      &bFalse,
 			PFloatNull32:  &floatValue32,
 			PFloatNull64:  &floatValue64,
-			DTimeNull:     &timeNow,
+			DTimeNull:     &datetime2Value,
 			PintNull:      &i,
 		},
 	}
@@ -301,10 +618,10 @@ func TestTVP(t *testing.T) {
 		sql.Named("param2", tvpTypeEmpty),
 		sql.Named("param3", "test"),
 	)
-
 	if err != nil {
 		t.Fatal(err)
 	}
+	defer rows.Close()
 
 	var result1 []TvptableRow
 	for rows.Next() {
@@ -433,9 +750,63 @@ func TestTVP_WithTag(t *testing.T) {
 		SELECT * FROM @param2;
 		SELECT @param3;
 	END;`
-
 	sqltextdropsp := `DROP PROCEDURE spwithtvp;`
 
+	type TvptableRowWithSkipTag struct {
+		PBinary           []byte            `db:"p_binary"`
+		SkipPBinary       []byte            `json:"-"`
+		PVarchar          string            `db:"p_varchar"`
+		SkipPVarchar      string            `tvp:"-"`
+		PVarcharNull      *string           `db:"p_varcharNull"`
+		SkipPVarcharNull  *string           `json:"-" tvp:"-"`
+		PNvarchar         string            `db:"p_nvarchar"`
+		SkipPNvarchar     string            `json:"-"`
+		PNvarcharNull     *string           `db:"p_nvarcharNull"`
+		SkipPNvarcharNull *string           `json:"-"`
+		PID               UniqueIdentifier  `db:"p_id"`
+		SkipPID           UniqueIdentifier  `json:"-"`
+		PIDNull           *UniqueIdentifier `db:"p_idNull"`
+		SkipPIDNull       *UniqueIdentifier `tvp:"-"`
+		PVarbinary        []byte            `db:"p_varbinary"`
+		SkipPVarbinary    []byte            `json:"-" tvp:"-"`
+		PTinyint          int8              `db:"p_tinyint"`
+		SkipPTinyint      int8              `tvp:"-"`
+		PTinyintNull      *int8             `db:"p_tinyintNull"`
+		SkipPTinyintNull  *int8             `tvp:"-" json:"any"`
+		PSmallint         int16             `db:"p_smallint"`
+		SkipPSmallint     int16             `json:"-"`
+		PSmallintNull     *int16            `db:"p_smallintNull"`
+		SkipPSmallintNull *int16            `tvp:"-"`
+		PInt              int32             `db:"p_int"`
+		SkipPInt          int32             `json:"-"`
+		PIntNull          *int32            `db:"p_intNull"`
+		SkipPIntNull      *int32            `tvp:"-"`
+		PBigint           int64             `db:"p_bigint"`
+		SkipPBigint       int64             `tvp:"-"`
+		PBigintNull       *int64            `db:"p_bigintNull"`
+		SkipPBigintNull   *int64            `json:"any" tvp:"-"`
+		PBit              bool              `db:"p_bit"`
+		SkipPBit          bool              `json:"-"`
+		PBitNull          *bool             `db:"p_bitNull"`
+		SkipPBitNull      *bool             `json:"-"`
+		PFloat32          float32           `db:"p_float32"`
+		SkipPFloat32      float32           `tvp:"-"`
+		PFloatNull32      *float32          `db:"p_floatNull32"`
+		SkipPFloatNull32  *float32          `tvp:"-"`
+		PFloat64          float64           `db:"p_float64"`
+		SkipPFloat64      float64           `tvp:"-"`
+		PFloatNull64      *float64          `db:"p_floatNull64"`
+		SkipPFloatNull64  *float64          `tvp:"-"`
+		DTime             time.Time         `db:"p_timeNull"`
+		SkipDTime         time.Time         `tvp:"-"`
+		DTimeNull         *time.Time        `db:"p_time"`
+		SkipDTimeNull     *time.Time        `tvp:"-"`
+		Pint              int               `db:"p_int_null"`
+		SkipPint          int               `tvp:"-"`
+		PintNull          *int              `db:"p_int_"`
+		SkipPintNull      *int              `tvp:"-"`
+	}
+
 	db.ExecContext(ctx, sqltextdropsp)
 	db.ExecContext(ctx, sqltextdroptable)
 
@@ -462,7 +833,9 @@ func TestTVP_WithTag(t *testing.T) {
 	bFalse := false
 	floatValue64 := 0.123
 	floatValue32 := float32(-10.123)
-	timeNow := time.Now().UTC()
+	// SQL Server's datetime2 has maximum precision of 7 digits, so for end-to-end
+	// equality comparison we must not test with any finer resolution than 100 nsec.
+	datetime2Value := time.Date(2020, 8, 26, 23, 59, 39, 100, time.UTC)
 	param1 := []TvptableRowWithSkipTag{
 		{
 			PBinary:    []byte("ccc"),
@@ -477,7 +850,7 @@ func TestTVP_WithTag(t *testing.T) {
 			PBit:       bFalse,
 			PFloat32:   floatValue32,
 			PFloat64:   floatValue64,
-			DTime:      timeNow,
+			DTime:      datetime2Value,
 			Pint:       i,
 			PintNull:   &i,
 		},
@@ -510,8 +883,8 @@ func TestTVP_WithTag(t *testing.T) {
 			PBitNull:      &bFalse,
 			PFloatNull32:  &floatValue32,
 			PFloatNull64:  &floatValue64,
-			DTime:         timeNow,
-			DTimeNull:     &timeNow,
+			DTime:         datetime2Value,
+			DTimeNull:     &datetime2Value,
 			Pint:          969,
 		},
 		{
@@ -536,7 +909,7 @@ func TestTVP_WithTag(t *testing.T) {
 			PBitNull:      &bFalse,
 			PFloatNull32:  &floatValue32,
 			PFloatNull64:  &floatValue64,
-			DTimeNull:     &timeNow,
+			DTimeNull:     &datetime2Value,
 			PintNull:      &i,
 		},
 	}
@@ -556,10 +929,10 @@ func TestTVP_WithTag(t *testing.T) {
 		sql.Named("param2", tvpTypeEmpty),
 		sql.Named("param3", "test"),
 	)
-
 	if err != nil {
 		t.Fatal(err)
 	}
+	defer rows.Close()
 
 	var result1 []TvptableRowWithSkipTag
 	for rows.Next() {
@@ -635,6 +1008,34 @@ type TvpExample struct {
 }
 
 func TestTVPSchema(t *testing.T) {
+	const (
+		crateSchema = `create schema TestTVPSchema;`
+
+		dropSchema = `drop schema TestTVPSchema;`
+
+		createTVP = `
+		CREATE TYPE TestTVPSchema.exempleTVP AS TABLE
+		(
+			message	NVARCHAR(100)
+		)`
+
+		dropTVP = `DROP TYPE TestTVPSchema.exempleTVP;`
+
+		procedureWithTVP = `	
+	CREATE PROCEDURE ExecTVP
+		@param1 TestTVPSchema.exempleTVP READONLY
+	AS   
+	BEGIN
+		SET NOCOUNT ON; 
+		SELECT * FROM @param1;
+	END;
+	`
+
+		dropProcedure = `drop PROCEDURE ExecTVP`
+
+		execTvp = `exec ExecTVP @param1;`
+	)
+
 	checkConnStr(t)
 	SetLogger(testLogger{t})
 
@@ -686,17 +1087,16 @@ func TestTVPSchema(t *testing.T) {
 		sql.Named("param1", tvpType),
 	)
 	if err != nil {
-		log.Println(err)
-		return
+		t.Fatal(err)
 	}
+	defer rows.Close()
 
 	tvpResult := make([]TvpExample, 0)
 	for rows.Next() {
 		tvpExemple := TvpExample{}
 		err = rows.Scan(&tvpExemple.Message)
 		if err != nil {
-			log.Println(err)
-			return
+			t.Fatal(err)
 		}
 		tvpResult = append(tvpResult, tvpExemple)
 	}