Repository: denisenkom/go-mssqldb
Pull request #713 (2022-01-02 17:33:35)

Fix bug with unsighted types
Data type 0x26 has an invalid data length or metadata length.
#713 


Patch:
diff --git a/.gitignore b/.gitignore
index 1f8b088..3988a3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,5 @@ coverage.json
 coverage.txt
 coverage.xml
 testresults.xml
+.azureconnstr
 
diff --git a/README.md b/README.md
index 92e74b6..9a2a9bb 100644
--- a/README.md
+++ b/README.md
@@ -112,30 +112,43 @@ Other supported formats are listed below.
     * `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"
 
-### Azure Active Directory authentication - preview
+### Azure Active Directory authentication
+
+Azure Active Directory authentication uses temporary authentication tokens to authenticate.
+The `mssql` package does not provide an implementation to obtain tokens: instead, import the `azuread` package and use driver name `azuresql`. This driver uses [azidentity](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#section-readme) to acquire tokens using a variety of credential types.
+
+The credential type is determined by the new `fedauth` connection string parameter.
+
+* `fedauth=ActiveDirectoryServicePrincipal` or `fedauth=ActiveDirectoryApplication` - authenticates using an Azure Active Directory application client ID and client secret or certificate. Implemented using [ClientSecretCredential or CertificateCredential](https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity#authenticating-service-principals)
+  * `clientcertpath=<path to certificate file>;password=<certificate password>` or
+  * `password=<client secret>`
+  * `user id=<application id>[@tenantid]` Note the `@tenantid` component can be omitted if the server's tenant is the same as the application's tenant.
+* `fedauth=ActiveDirectoryPassword` - authenticates using a user name and password.
+  * `user id=username@domain`
+  * `password=<password>`
+  * `applicationclientid=<application id>` - This guid identifies an Azure Active Directory enterprise application that the AAD admin has approved for accessing Azure SQL database resources in the tenant. This driver does not have an associated application id of its own.
+* `fedauth=ActiveDirectoryDefault` - authenticates using a chained set of credentials. The chain is built from EnvironmentCredential -> ManagedIdentityCredential->AzureCLICredential.  See [DefaultAzureCredential docs](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication#configure-defaultazurecredential) for instructions on setting up your host environment to use it. Using this option allows you to have the same connection string in a service deployment as on your interactive development machine.
+* `fedauth=ActiveDirectoryManagedIdentity` or `fedauth=ActiveDirectoryMSI` - authenticates using a system-assigned or user-assigned Azure Managed Identity.
+  * `user id=<identity id>` - optional id of user-assigned managed identity. If empty, system-assigned managed identity is used.
+* `fedauth=ActiveDirectoryInteractive` - authenticates using credentials acquired from an external web browser. Only suitable for use with human interaction.
+  * `applicationclientid=<application id>` - This guid identifies an Azure Active Directory enterprise application that the AAD admin has approved for accessing Azure SQL database resources in the tenant. This driver does not have an associated application id of its own.
 
-The configuration of functionality might change in the future.
+```go
 
-Azure Active Directory (AAD) access tokens are relatively short lived and need to be
-valid when a new connection is made. Authentication is supported using a callback func that
-provides a fresh and valid token using a connector:
+import (
+  "database/sql"
+  "net/url"
 
-``` go
+  // Import the Azure AD driver module (also imports the regular driver package)
+  "github.com/denisenkom/go-mssqldb/azuread"
+)
 
-conn, err := mssql.NewAccessTokenConnector(
-	"Server=test.database.windows.net;Database=testdb",
-	tokenProvider)
-if err != nil {
-	// handle errors in DSN
+func ConnectWithMSI() (*sql.DB, error) {
+  return sql.Open(azuread.DriverName, "sqlserver://azuresql.database.windows.net?database=yourdb&fedauth=ActiveDirectoryMSI")
 }
-db := sql.OpenDB(conn)
 
 ```
 
-Where `tokenProvider` is a function that returns a fresh access token or an error. None of these statements
-actually trigger the retrieval of a token, this happens when the first statment is issued and a connection
-is created.
-
 ## Executing Stored Procedures
 
 To run a stored procedure, set the query text to the procedure name:
@@ -306,6 +319,8 @@ Example:
     env SQLSERVER_DSN=sqlserver://user:pass@hostname/instance?database=test1 go test
 ```
 
+`AZURESERVER_DSN` environment variable provides the connection string for Azure Active Directory-based authentication. If it's not set the AAD test will be skipped.
+
 ## Deprecated
 
 These features still exist in the driver, but they are are deprecated.
diff --git a/accesstokenconnector.go b/accesstokenconnector.go
index 80213d1..7e9f95c 100644
--- a/accesstokenconnector.go
+++ b/accesstokenconnector.go
@@ -22,7 +22,7 @@ func NewAccessTokenConnector(dsn string, tokenProvider func() (string, error)) (
 	}
 
 	conn.fedAuthRequired = true
-	conn.fedAuthLibrary = fedAuthLibrarySecurityToken
+	conn.fedAuthLibrary = FedAuthLibrarySecurityToken
 	conn.securityTokenProvider = func(ctx context.Context) (string, error) {
 		return tokenProvider()
 	}
diff --git a/azuread/configuration.go b/azuread/configuration.go
new file mode 100644
index 0000000..a6e30e9
--- /dev/null
+++ b/azuread/configuration.go
@@ -0,0 +1,192 @@
+package azuread
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	mssql "github.com/denisenkom/go-mssqldb"
+	"github.com/denisenkom/go-mssqldb/msdsn"
+)
+
+const (
+	ActiveDirectoryDefault     = "ActiveDirectoryDefault"
+	ActiveDirectoryIntegrated  = "ActiveDirectoryIntegrated"
+	ActiveDirectoryPassword    = "ActiveDirectoryPassword"
+	ActiveDirectoryInteractive = "ActiveDirectoryInteractive"
+	// ActiveDirectoryMSI is a synonym for ActiveDirectoryManagedIdentity
+	ActiveDirectoryMSI             = "ActiveDirectoryMSI"
+	ActiveDirectoryManagedIdentity = "ActiveDirectoryManagedIdentity"
+	// ActiveDirectoryApplication is a synonym for ActiveDirectoryServicePrincipal
+	ActiveDirectoryApplication      = "ActiveDirectoryApplication"
+	ActiveDirectoryServicePrincipal = "ActiveDirectoryServicePrincipal"
+	scopeDefaultSuffix              = "/.default"
+)
+
+type azureFedAuthConfig struct {
+	adalWorkflow byte
+	mssqlConfig  msdsn.Config
+	// The detected federated authentication library
+	fedAuthLibrary  int
+	fedAuthWorkflow string
+	// Service principal logins
+	clientID        string
+	tenantID        string
+	clientSecret    string
+	certificatePath string
+
+	// AD password/managed identity/interactive
+	user                string
+	password            string
+	applicationClientID string
+}
+
+// parse returns a config based on an msdsn-style connection string
+func parse(dsn string) (*azureFedAuthConfig, error) {
+	mssqlConfig, params, err := msdsn.Parse(dsn)
+	if err != nil {
+		return nil, err
+	}
+	config := &azureFedAuthConfig{
+		fedAuthLibrary: mssql.FedAuthLibraryReserved,
+		mssqlConfig:    mssqlConfig,
+	}
+
+	err = config.validateParameters(params)
+	if err != nil {
+		return nil, err
+	}
+
+	return config, nil
+}
+
+func (p *azureFedAuthConfig) validateParameters(params map[string]string) error {
+
+	fedAuthWorkflow, _ := params["fedauth"]
+	if fedAuthWorkflow == "" {
+		return nil
+	}
+
+	p.fedAuthLibrary = mssql.FedAuthLibraryADAL
+
+	p.applicationClientID, _ = params["applicationclientid"]
+
+	switch {
+	case strings.EqualFold(fedAuthWorkflow, ActiveDirectoryPassword):
+		if p.applicationClientID == "" {
+			return errors.New("applicationclientid parameter is required for " + ActiveDirectoryPassword)
+		}
+		p.adalWorkflow = mssql.FedAuthADALWorkflowPassword
+		p.user, _ = params["user id"]
+		p.password, _ = params["password"]
+	case strings.EqualFold(fedAuthWorkflow, ActiveDirectoryIntegrated):
+		// Active Directory Integrated authentication is not fully supported:
+		// you can only use this by also implementing an a token provider
+		// and supplying it via ActiveDirectoryTokenProvider in the Connection.
+		p.adalWorkflow = mssql.FedAuthADALWorkflowIntegrated
+	case strings.EqualFold(fedAuthWorkflow, ActiveDirectoryManagedIdentity) || strings.EqualFold(fedAuthWorkflow, ActiveDirectoryMSI):
+		// When using MSI, to request a specific client ID or user-assigned identity,
+		// provide the ID in the "user id" parameter
+		p.adalWorkflow = mssql.FedAuthADALWorkflowMSI
+		p.clientID, _ = splitTenantAndClientID(params["user id"])
+	case strings.EqualFold(fedAuthWorkflow, ActiveDirectoryApplication) || strings.EqualFold(fedAuthWorkflow, ActiveDirectoryServicePrincipal):
+		p.adalWorkflow = mssql.FedAuthADALWorkflowPassword
+		// Split the clientID@tenantID format
+		// If no tenant is provided we'll use the one from the server
+		p.clientID, p.tenantID = splitTenantAndClientID(params["user id"])
+		if p.clientID == "" {
+			return errors.New("Must provide 'client id[@tenant id]' as username parameter when using ActiveDirectoryApplication authentication")
+		}
+
+		p.clientSecret, _ = params["password"]
+
+		p.certificatePath, _ = params["clientcertpath"]
+
+		if p.certificatePath == "" && p.clientSecret == "" {
+			return errors.New("Must provide 'password' parameter when using ActiveDirectoryApplication authentication without cert/key credentials")
+		}
+	case strings.EqualFold(fedAuthWorkflow, ActiveDirectoryDefault):
+		p.adalWorkflow = mssql.FedAuthADALWorkflowPassword
+	case strings.EqualFold(fedAuthWorkflow, ActiveDirectoryInteractive):
+		if p.applicationClientID == "" {
+			return errors.New("applicationclientid parameter is required for " + ActiveDirectoryInteractive)
+		}
+		p.adalWorkflow = mssql.FedAuthADALWorkflowPassword
+		// user is an optional login hint
+		p.user, _ = params["user id"]
+		// we don't really have a password but we need to use some value.
+		p.adalWorkflow = mssql.FedAuthADALWorkflowPassword
+
+	default:
+		return fmt.Errorf("Invalid federated authentication type '%s': expected one of %+v",
+			fedAuthWorkflow,
+			[]string{ActiveDirectoryApplication, ActiveDirectoryServicePrincipal, ActiveDirectoryDefault, ActiveDirectoryIntegrated, ActiveDirectoryInteractive, ActiveDirectoryManagedIdentity, ActiveDirectoryMSI, ActiveDirectoryPassword})
+	}
+	p.fedAuthWorkflow = fedAuthWorkflow
+	return nil
+}
+
+func splitTenantAndClientID(user string) (string, string) {
+	// Split the user name into client id and tenant id at the @ symbol
+	at := strings.IndexRune(user, '@')
+	if at < 1 || at >= (len(user)-1) {
+		return user, ""
+	}
+
+	return user[0:at], user[at+1:]
+}
+
+func splitAuthorityAndTenant(authorityUrl string) (string, string) {
+	separatorIndex := strings.LastIndex(authorityUrl, "/")
+	tenant := authorityUrl[separatorIndex+1:]
+	authority := authorityUrl[:separatorIndex]
+	return authority, tenant
+}
+
+func (p *azureFedAuthConfig) provideActiveDirectoryToken(ctx context.Context, serverSPN, stsURL string) (string, error) {
+	var cred azcore.TokenCredential
+	var err error
+	authority, tenant := splitAuthorityAndTenant(stsURL)
+	// client secret connection strings may override the server tenant
+	if p.tenantID != "" {
+		tenant = p.tenantID
+	}
+	scope := stsURL
+	if !strings.HasSuffix(serverSPN, scopeDefaultSuffix) {
+		scope = strings.TrimRight(serverSPN, "/") + scopeDefaultSuffix
+	}
+
+	switch p.fedAuthWorkflow {
+	case ActiveDirectoryServicePrincipal, ActiveDirectoryApplication:
+		switch {
+		case p.certificatePath != "":
+			cred, err = azidentity.NewClientCertificateCredential(tenant, p.clientID, p.certificatePath, &azidentity.ClientCertificateCredentialOptions{Password: p.clientSecret})
+		default:
+			cred, err = azidentity.NewClientSecretCredential(tenant, p.clientID, p.clientSecret, nil)
+		}
+	case ActiveDirectoryPassword:
+		cred, err = azidentity.NewUsernamePasswordCredential(tenant, p.applicationClientID, p.user, p.password, nil)
+	case ActiveDirectoryMSI, ActiveDirectoryManagedIdentity:
+		cred, err = azidentity.NewManagedIdentityCredential(p.clientID, nil)
+	case ActiveDirectoryInteractive:
+		cred, err = azidentity.NewInteractiveBrowserCredential(&azidentity.InteractiveBrowserCredentialOptions{AuthorityHost: authority, ClientID: p.applicationClientID})
+
+	default:
+		// Integrated just uses Default until azidentity adds Windows-specific authentication
+		cred, err = azidentity.NewDefaultAzureCredential(nil)
+	}
+
+	if err != nil {
+		return "", err
+	}
+	opts := policy.TokenRequestOptions{Scopes: []string{scope}}
+	tk, err := cred.GetToken(ctx, opts)
+	if err != nil {
+		return "", err
+	}
+	return tk.Token, err
+}
diff --git a/azuread/driver.go b/azuread/driver.go
new file mode 100644
index 0000000..fa41757
--- /dev/null
+++ b/azuread/driver.go
@@ -0,0 +1,54 @@
+package azuread
+
+import (
+	"context"
+	"database/sql"
+	"database/sql/driver"
+
+	mssql "github.com/denisenkom/go-mssqldb"
+)
+
+// DriverName is the name used to register the driver
+const DriverName = "azuresql"
+
+func init() {
+	sql.Register(DriverName, &Driver{})
+}
+
+// Driver wraps the underlying MSSQL driver, but configures the Azure AD token provider
+type Driver struct {
+}
+
+// Open returns a new connection to the database.
+func (d *Driver) Open(dsn string) (driver.Conn, error) {
+	c, err := NewConnector(dsn)
+	if err != nil {
+		return nil, err
+	}
+
+	return c.Connect(context.Background())
+}
+
+// NewConnector creates a new connector from a DSN.
+// The returned connector may be used with sql.OpenDB.
+func NewConnector(dsn string) (*mssql.Connector, error) {
+
+	config, err := parse(dsn)
+	if err != nil {
+		return nil, err
+	}
+	return newConnectorConfig(config)
+}
+
+// newConnectorConfig creates a Connector from config.
+func newConnectorConfig(config *azureFedAuthConfig) (*mssql.Connector, error) {
+	if config.fedAuthLibrary == mssql.FedAuthLibraryADAL {
+		return mssql.NewActiveDirectoryTokenConnector(
+			config.mssqlConfig, config.adalWorkflow,
+			func(ctx context.Context, serverSPN, stsURL string) (string, error) {
+				return config.provideActiveDirectoryToken(ctx, serverSPN, stsURL)
+			},
+		)
+	}
+	return mssql.NewConnectorConfig(config.mssqlConfig), nil
+}
diff --git a/fedauth.go b/fedauth.go
index 459c664..cd74cb5 100644
--- a/fedauth.go
+++ b/fedauth.go
@@ -9,31 +9,31 @@ import (
 
 // Federated authentication library affects the login data structure and message sequence.
 const (
-	// fedAuthLibraryLiveIDCompactToken specifies the Microsoft Live ID Compact Token authentication scheme
-	fedAuthLibraryLiveIDCompactToken = 0x00
+	// FedAuthLibraryLiveIDCompactToken specifies the Microsoft Live ID Compact Token authentication scheme
+	FedAuthLibraryLiveIDCompactToken = 0x00
 
-	// fedAuthLibrarySecurityToken specifies a token-based authentication where the token is available
+	// FedAuthLibrarySecurityToken specifies a token-based authentication where the token is available
 	// without additional information provided during the login sequence.
-	fedAuthLibrarySecurityToken = 0x01
+	FedAuthLibrarySecurityToken = 0x01
 
-	// fedAuthLibraryADAL specifies a token-based authentication where a token is obtained during the
+	// FedAuthLibraryADAL specifies a token-based authentication where a token is obtained during the
 	// login sequence using the server SPN and STS URL provided by the server during login.
-	fedAuthLibraryADAL = 0x02
+	FedAuthLibraryADAL = 0x02
 
-	// fedAuthLibraryReserved is used to indicate that no federated authentication scheme applies.
-	fedAuthLibraryReserved = 0x7F
+	// FedAuthLibraryReserved is used to indicate that no federated authentication scheme applies.
+	FedAuthLibraryReserved = 0x7F
 )
 
 // Federated authentication ADAL workflow affects the mechanism used to authenticate.
 const (
-	// fedAuthADALWorkflowPassword uses a username/password to obtain a token from Active Directory
-	fedAuthADALWorkflowPassword = 0x01
+	// FedAuthADALWorkflowPassword uses a username/password to obtain a token from Active Directory
+	FedAuthADALWorkflowPassword = 0x01
 
 	// fedAuthADALWorkflowPassword uses the Windows identity to obtain a token from Active Directory
-	fedAuthADALWorkflowIntegrated = 0x02
+	FedAuthADALWorkflowIntegrated = 0x02
 
-	// fedAuthADALWorkflowMSI uses the managed identity service to obtain a token
-	fedAuthADALWorkflowMSI = 0x03
+	// FedAuthADALWorkflowMSI uses the managed identity service to obtain a token
+	FedAuthADALWorkflowMSI = 0x03
 )
 
 // newSecurityTokenConnector creates a new connector from a Config and a token provider.
@@ -41,14 +41,14 @@ const (
 // service specified and obtain the appropriate token, or return an error
 // to indicate why a token is not available.
 // The returned connector may be used with sql.OpenDB.
-func newSecurityTokenConnector(config msdsn.Config, tokenProvider func(ctx context.Context) (string, error)) (*Connector, error) {
+func NewSecurityTokenConnector(config msdsn.Config, tokenProvider func(ctx context.Context) (string, error)) (*Connector, error) {
 	if tokenProvider == nil {
 		return nil, errors.New("mssql: tokenProvider cannot be nil")
 	}
 
 	conn := NewConnectorConfig(config)
 	conn.fedAuthRequired = true
-	conn.fedAuthLibrary = fedAuthLibrarySecurityToken
+	conn.fedAuthLibrary = FedAuthLibrarySecurityToken
 	conn.securityTokenProvider = tokenProvider
 
 	return conn, nil
@@ -63,14 +63,14 @@ func newSecurityTokenConnector(config msdsn.Config, tokenProvider func(ctx conte
 // to indicate why a token is not available.
 //
 // The returned connector may be used with sql.OpenDB.
-func newActiveDirectoryTokenConnector(config msdsn.Config, adalWorkflow byte, tokenProvider func(ctx context.Context, serverSPN, stsURL string) (string, error)) (*Connector, error) {
+func NewActiveDirectoryTokenConnector(config msdsn.Config, adalWorkflow byte, tokenProvider func(ctx context.Context, serverSPN, stsURL string) (string, error)) (*Connector, error) {
 	if tokenProvider == nil {
 		return nil, errors.New("mssql: tokenProvider cannot be nil")
 	}
 
 	conn := NewConnectorConfig(config)
 	conn.fedAuthRequired = true
-	conn.fedAuthLibrary = fedAuthLibraryADAL
+	conn.fedAuthLibrary = FedAuthLibraryADAL
 	conn.fedAuthADALWorkflow = adalWorkflow
 	conn.adalTokenProvider = tokenProvider
 
diff --git a/go.mod b/go.mod
index ebc02ab..d0d623e 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,8 @@ module github.com/denisenkom/go-mssqldb
 go 1.11
 
 require (
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0
 	github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
-	golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
+	golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
 )
diff --git a/go.sum b/go.sum
index 1887801..6763061 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,42 @@
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 h1:lhSJz9RMbJcTgxifR1hUNJnn6CNYtbgEDtQV22/9RBA=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0 h1:OYa9vmRX2XC5GXRAzeggG12sF/z5D9Ahtdm9EJ00WN4=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 h1:v9p9TfTbf7AwNb5NYQt7hI41IfPoLFiFkLtb+bmGjT0=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
+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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tds.go b/tds.go
index 311d81a..fbc0d14 100644
--- a/tds.go
+++ b/tds.go
@@ -386,7 +386,7 @@ func (e *featureExtFedAuth) toBytes() []byte {
 	var d []byte
 
 	switch e.FedAuthLibrary {
-	case fedAuthLibrarySecurityToken:
+	case FedAuthLibrarySecurityToken:
 		d = make([]byte, 5)
 		d[0] = options
 
@@ -400,7 +400,7 @@ func (e *featureExtFedAuth) toBytes() []byte {
 			d = append(d, e.Nonce...)
 		}
 
-	case fedAuthLibraryADAL:
+	case FedAuthLibraryADAL:
 		d = []byte{options, e.ADALWorkflow}
 	}
 
@@ -930,7 +930,7 @@ func preparePreloginFields(p msdsn.Config, fe *featureExtFedAuth) map[uint8][]by
 		preloginMARS:       {0}, // MARS disabled
 	}
 
-	if fe.FedAuthLibrary != fedAuthLibraryReserved {
+	if fe.FedAuthLibrary != FedAuthLibraryReserved {
 		fields[preloginFEDAUTHREQUIRED] = []byte{1}
 	}
 
@@ -948,7 +948,7 @@ func interpretPreloginResponse(p msdsn.Config, fe *featureExtFedAuth, fields map
 
 		// We need to be able to echo the value back to the server
 		fe.FedAuthEcho = fedAuthSupport[0] != 0
-	} else if fe.FedAuthLibrary != fedAuthLibraryReserved {
+	} else if fe.FedAuthLibrary != FedAuthLibraryReserved {
 		return 0, fmt.Errorf("federated authentication is not supported by the server")
 	}
 
@@ -987,7 +987,7 @@ func prepareLogin(ctx context.Context, c *Connector, p msdsn.Config, logger Cont
 		TypeFlags:    typeFlags,
 	}
 	switch {
-	case fe.FedAuthLibrary == fedAuthLibrarySecurityToken:
+	case fe.FedAuthLibrary == FedAuthLibrarySecurityToken:
 		if uint64(p.LogFlags)&logDebug != 0 {
 			logger.Log(ctx, msdsn.LogDebug, "Starting federated authentication using security token")
 		}
@@ -1002,7 +1002,7 @@ func prepareLogin(ctx context.Context, c *Connector, p msdsn.Config, logger Cont
 
 		l.FeatureExt.Add(fe)
 
-	case fe.FedAuthLibrary == fedAuthLibraryADAL:
+	case fe.FedAuthLibrary == FedAuthLibraryADAL:
 		if uint64(p.LogFlags)&logDebug != 0 {
 			logger.Log(ctx, msdsn.LogDebug, "Starting federated authentication using ADAL")
 		}
@@ -1103,7 +1103,7 @@ initiate_connection:
 	}
 
 	fedAuth := &featureExtFedAuth{
-		FedAuthLibrary: fedAuthLibraryReserved,
+		FedAuthLibrary: FedAuthLibraryReserved,
 	}
 	if c.fedAuthRequired {
 		fedAuth.FedAuthLibrary = c.fedAuthLibrary
diff --git a/tvp_go19.go b/tvp_go19.go
index d3890af..32485a1 100644
--- a/tvp_go19.go
+++ b/tvp_go19.go
@@ -105,7 +105,8 @@ func (tvp TVP) encode(schema, name string, columnStr []columnStruct, tvpFieldInd
 			elemKind := field.Kind()
 			if elemKind == reflect.Ptr && valOf.IsNil() {
 				switch tvpVal.(type) {
-				case *bool, *time.Time, *int8, *int16, *int32, *int64, *float32, *float64, *int:
+				case *bool, *time.Time, *int8, *int16, *int32, *int64, *float32, *float64, *int,
+					*uint8, *uint16, *uint32, *uint64, *uint:
 					binary.Write(buf, binary.LittleEndian, uint8(0))
 					continue
 				default:


Test patch:
diff --git a/.pipelines/TestSql2017.yml b/.pipelines/TestSql2017.yml
index 5422daa..2a052e9 100644
--- a/.pipelines/TestSql2017.yml
+++ b/.pipelines/TestSql2017.yml
@@ -2,6 +2,8 @@ pool:
    vmImage: 'ubuntu-latest'
 
 trigger: none
+variables:
+  TESTPASSWORD: $(SQLPASSWORD)
 
 steps: 
 - task: GoTool@0
@@ -39,24 +41,26 @@ steps:
     arguments: 'github.com/AlekSi/gocov-xml@latest'
     workingDirectory: '$(System.DefaultWorkingDirectory)'
 
-#Your build pipeline references an undefined variables named SQLPASSWORD and HOST. 
+#Your build pipeline references an undefined variables named SQLPASSWORD and AZURESERVER_DSN. 
 #Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972
 
 - task: Docker@2
   displayName: 'Run SQL 2017 docker image'
   inputs:
     command: run
-    arguments: '-m 2GB -e ACCEPT_EULA=1 -d --name sql2017 -p:1433:1433 -e SA_PASSWORD=$(SQLPASSWORD) mcr.microsoft.com/mssql/server:2017-latest'
+    arguments: '-m 2GB -e ACCEPT_EULA=1 -d --name sql2017 -p:1433:1433 -e SA_PASSWORD=$(TESTPASSWORD) mcr.microsoft.com/mssql/server:2017-latest'
 
 - script: |
-    ~/go/bin/gotestsum --junitfile testresults.xml -- -coverprofile=coverage.txt -covermode count
+    ~/go/bin/gotestsum --junitfile testresults.xml -- -coverprofile=coverage.txt -covermode count ./...
     ~/go/bin/gocov convert coverage.txt > coverage.json
     ~/go/bin/gocov-xml < coverage.json > coverage.xml
     mkdir coverage
   workingDirectory: '$(Build.SourcesDirectory)'
   displayName: 'run tests'
   env:
-    SQLPASSWORD: $(SQLPASSWORD)
+    SQLSERVER_DSN: 'server=.;user id=sa;password=$(TESTPASSWORD)'
+    AZURESERVER_DSN: $(AZURESERVER_DSN)
+
   continueOnError: true
 - task: PublishTestResults@2
   displayName: "Publish junit-style results"
diff --git a/azuread/azuread_test.go b/azuread/azuread_test.go
new file mode 100644
index 0000000..ba7de67
--- /dev/null
+++ b/azuread/azuread_test.go
@@ -0,0 +1,63 @@
+package azuread
+
+import (
+	"bufio"
+	"database/sql"
+	"io"
+	"os"
+	"testing"
+
+	mssql "github.com/denisenkom/go-mssqldb"
+)
+
+func TestAzureSqlAuth(t *testing.T) {
+	mssqlConfig := testConnParams(t)
+
+	conn, err := newConnectorConfig(mssqlConfig)
+	if err != nil {
+		t.Fatalf("Unable to get a connector: %v", err)
+	}
+	db := sql.OpenDB(conn)
+	row := db.QueryRow("select 100, suser_sname()")
+	var val int
+	var user string
+	err = row.Scan(&val, &user)
+	if err != nil {
+		t.Fatalf("Unable to query the db: %v", err)
+	}
+	if val != 100 {
+		t.Fatalf("Got wrong value from query. Expected:100, Got: %d", val)
+	}
+	t.Logf("Got suser_sname value %s", user)
+
+}
+
+// returns parsed connection parameters derived from
+// environment variables
+func testConnParams(t testing.TB) *azureFedAuthConfig {
+	dsn := os.Getenv("AZURESERVER_DSN")
+	const logFlags = 127
+	if dsn == "" {
+		// try loading connection string from file
+		f, err := os.Open(".azureconnstr")
+		if err == nil {
+			rdr := bufio.NewReader(f)
+			dsn, err = rdr.ReadString('\n')
+			if err != io.EOF && err != nil {
+				t.Fatal(err)
+			}
+		}
+	}
+	if dsn == "" {
+		t.Skip("no azure database connection string. set AZURESERVER_DSN environment variable or create .azureconnstr file")
+	}
+	config, err := parse(dsn)
+	if err != nil {
+		t.Skip("error parsing connection string ")
+	}
+	if config.fedAuthLibrary == mssql.FedAuthLibraryReserved {
+		t.Skip("Skipping azure test due to missing fedauth parameter ")
+	}
+	config.mssqlConfig.LogFlags = logFlags
+	return config
+}
diff --git a/azuread/configuration_test.go b/azuread/configuration_test.go
new file mode 100644
index 0000000..c58b8b8
--- /dev/null
+++ b/azuread/configuration_test.go
@@ -0,0 +1,116 @@
+package azuread
+
+import (
+	"testing"
+
+	mssql "github.com/denisenkom/go-mssqldb"
+	"github.com/denisenkom/go-mssqldb/msdsn"
+)
+
+func TestValidateParameters(t *testing.T) {
+	passphrase := "somesecret"
+	certificatepath := "/user/cert/cert.pfx"
+	appid := "applicationclientid=someguid"
+	certprop := "clientcertpath=" + certificatepath
+	tests := []struct {
+		name     string
+		dsn      string
+		expected *azureFedAuthConfig
+	}{
+		{
+			name:     "no fed auth configured",
+			dsn:      "server=someserver",
+			expected: &azureFedAuthConfig{fedAuthLibrary: mssql.FedAuthLibraryReserved},
+		},
+		{
+			name: "application with cert/key",
+			dsn:  `sqlserver://service-principal-id%40tenant-id:somesecret@someserver.database.windows.net?fedauth=ActiveDirectoryApplication&` + certprop + "&" + appid,
+			expected: &azureFedAuthConfig{
+				fedAuthLibrary:      mssql.FedAuthLibraryADAL,
+				clientID:            "service-principal-id",
+				tenantID:            "tenant-id",
+				certificatePath:     certificatepath,
+				clientSecret:        passphrase,
+				adalWorkflow:        mssql.FedAuthADALWorkflowPassword,
+				fedAuthWorkflow:     ActiveDirectoryApplication,
+				applicationClientID: "someguid",
+			},
+		},
+		{
+			name: "application with cert/key missing tenant id",
+			dsn:  "server=someserver.database.windows.net;fedauth=ActiveDirectoryApplication;user id=service-principal-id;password=somesecret;" + certprop + ";" + appid,
+			expected: &azureFedAuthConfig{
+				fedAuthLibrary:      mssql.FedAuthLibraryADAL,
+				clientID:            "service-principal-id",
+				certificatePath:     certificatepath,
+				clientSecret:        passphrase,
+				adalWorkflow:        mssql.FedAuthADALWorkflowPassword,
+				fedAuthWorkflow:     ActiveDirectoryApplication,
+				applicationClientID: "someguid",
+			},
+		},
+		{
+			name: "application with secret",
+			dsn:  "server=someserver.database.windows.net;fedauth=ActiveDirectoryServicePrincipal;user id=service-principal-id@tenant-id;password=somesecret;",
+			expected: &azureFedAuthConfig{
+				clientID:        "service-principal-id",
+				tenantID:        "tenant-id",
+				clientSecret:    passphrase,
+				adalWorkflow:    mssql.FedAuthADALWorkflowPassword,
+				fedAuthWorkflow: ActiveDirectoryServicePrincipal,
+			},
+		},
+		{
+			name: "user with password",
+			dsn:  "server=someserver.database.windows.net;fedauth=ActiveDirectoryPassword;user id=azure-ad-user@example.com;password=somesecret;" + appid,
+			expected: &azureFedAuthConfig{
+				adalWorkflow:        mssql.FedAuthADALWorkflowPassword,
+				user:                "azure-ad-user@example.com",
+				password:            passphrase,
+				applicationClientID: "someguid",
+				fedAuthWorkflow:     ActiveDirectoryPassword,
+			},
+		},
+		{
+			name: "managed identity without client id",
+			dsn:  "server=someserver.database.windows.net;fedauth=ActiveDirectoryMSI",
+			expected: &azureFedAuthConfig{
+				adalWorkflow:    mssql.FedAuthADALWorkflowMSI,
+				fedAuthWorkflow: ActiveDirectoryMSI,
+			},
+		},
+		{
+			name: "managed identity with client id",
+			dsn:  "server=someserver.database.windows.net;fedauth=ActiveDirectoryManagedIdentity;user id=identity-client-id",
+			expected: &azureFedAuthConfig{
+				adalWorkflow:    mssql.FedAuthADALWorkflowMSI,
+				clientID:        "identity-client-id",
+				fedAuthWorkflow: ActiveDirectoryManagedIdentity,
+			},
+		},
+	}
+	for _, tst := range tests {
+		config, err := parse(tst.dsn)
+		if tst.expected == nil {
+			if err == nil {
+				t.Errorf("No error returned when error expected in test case '%s'", tst.name)
+			}
+			continue
+		}
+		if err != nil {
+			t.Errorf("Error returned when none expected in test case '%s': %v", tst.name, err)
+			continue
+		}
+		if tst.expected.fedAuthLibrary != mssql.FedAuthLibraryReserved {
+			if tst.expected.fedAuthLibrary == 0 {
+				tst.expected.fedAuthLibrary = mssql.FedAuthLibraryADAL
+			}
+		}
+		// mssqlConfig is not idempotent due to pointers in it, plus we aren't testing its correctness here
+		config.mssqlConfig = msdsn.Config{}
+		if *config != *tst.expected {
+			t.Errorf("Captured parameters do not match in test case '%s'. Expected:%+v, Actual:%+v", tst.name, tst.expected, config)
+		}
+	}
+
+}
diff --git a/tds_login_test.go b/tds_login_test.go
index f0ff173..08f3e3e 100644
--- a/tds_login_test.go
+++ b/tds_login_test.go
@@ -166,7 +166,7 @@ func TestLoginWithSecurityTokenAuth(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	conn, err := newSecurityTokenConnector(config,
+	conn, err := NewSecurityTokenConnector(config,
 		func(ctx context.Context) (string, error) {
 			return "<token>", nil
 		},
@@ -227,9 +227,9 @@ func TestLoginWithADALUsernamePasswordAuth(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	conn, err := newActiveDirectoryTokenConnector(
+	conn, err := NewActiveDirectoryTokenConnector(
 		config,
-		fedAuthADALWorkflowPassword,
+		FedAuthADALWorkflowPassword,
 		func(ctx context.Context, serverSPN, stsURL string) (string, error) {
 			return "<token>", nil
 		},
@@ -301,9 +301,9 @@ func TestLoginWithADALManagedIdentityAuth(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	conn, err := newActiveDirectoryTokenConnector(
+	conn, err := NewActiveDirectoryTokenConnector(
 		config,
-		fedAuthADALWorkflowMSI,
+		FedAuthADALWorkflowMSI,
 		func(ctx context.Context, serverSPN, stsURL string) (string, error) {
 			return "<token>", nil
 		},
diff --git a/tds_test.go b/tds_test.go
index 2a0e4b6..bfce86c 100644
--- a/tds_test.go
+++ b/tds_test.go
@@ -41,7 +41,7 @@ func TestConstantsDefined(t *testing.T) {
 	}
 
 	for _, i := range []int{
-		fedAuthLibraryLiveIDCompactToken, fChangePassword, fSendYukonBinaryXML,
+		FedAuthLibraryLiveIDCompactToken, fChangePassword, fSendYukonBinaryXML,
 	} {
 		if i < 0 {
 			t.Fail()
@@ -120,7 +120,7 @@ func TestSendLoginWithFeatureExt(t *testing.T) {
 		ClientLCID:     0x204,
 	}
 	login.FeatureExt.Add(&featureExtFedAuth{
-		FedAuthLibrary: fedAuthLibrarySecurityToken,
+		FedAuthLibrary: FedAuthLibrarySecurityToken,
 		FedAuthToken:   "fedauthtoken",
 	})
 	err := sendLogin(buf, &login)
diff --git a/tvp_go19_db_test.go b/tvp_go19_db_test.go
index 510a4a0..a862698 100644
--- a/tvp_go19_db_test.go
+++ b/tvp_go19_db_test.go
@@ -1,3 +1,4 @@
+//go:build go1.9
 // +build go1.9
 
 package mssql
@@ -1161,3 +1162,189 @@ func TestTVPObject(t *testing.T) {
 		})
 	}
 }
+
+// fix pointer uint in tvp https://github.com/denisenkom/go-mssqldb/issues/703
+func TestTVPUnsigned(t *testing.T) {
+	checkConnStr(t)
+	tl := testLogger{t: t}
+	defer tl.StopLogging()
+	SetLogger(&tl)
+
+	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 unsignedTvpTableTypes AS TABLE
+		(
+			p_tinyint 			TINYINT,
+			p_tinyintNull 		TINYINT,
+			p_smallint          SMALLINT,
+			p_smallintNull      SMALLINT,
+			p_int               INT,
+			p_intNull           INT,
+			p_bigint            BIGINT,
+			p_bigintNull        BIGINT,
+			pInt              	INT,
+			pIntNull          	INT
+		); `
+
+	sqltextdroptable := `DROP TYPE unsignedTvpTableTypes;`
+
+	sqltextcreatesp := `
+	CREATE PROCEDURE spwithtvpUnsigned
+		@param1 unsignedTvpTableTypes READONLY,
+		@param2 unsignedTvpTableTypes READONLY,
+		@param3 NVARCHAR(10)
+	AS   
+	BEGIN
+		SET NOCOUNT ON; 
+		SELECT * FROM @param1;
+		SELECT * FROM @param2;
+		SELECT @param3;
+	END;`
+
+	type TvptableRow struct {
+		PTinyint      uint8   `db:"p_tinyint"`
+		PTinyintNull  *uint8  `db:"p_tinyintNull"`
+		PSmallint     uint16  `db:"p_smallint"`
+		PSmallintNull *uint16 `db:"p_smallintNull"`
+		PInt          uint32  `db:"p_int"`
+		PIntNull      *uint32 `db:"p_intNull"`
+		PBigint       uint64  `db:"p_bigint"`
+		PBigintNull   *uint64 `db:"p_bigintNull"`
+		Pint          uint    `db:"pInt"`
+		PintNull      *uint   `db:"pIntNull"`
+	}
+
+	sqltextdropsp := `DROP PROCEDURE spwithtvpUnsigned;`
+
+	_, 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)
+	i8 := uint8(1)
+	i16 := uint16(2)
+	i32 := uint32(3)
+	i64 := uint64(4)
+	i := uint(5)
+	param1 := []TvptableRow{
+		{
+			PTinyint:  i8,
+			PSmallint: i16,
+			PInt:      i32,
+			PBigint:   i64,
+			Pint:      355,
+		},
+		{
+			PTinyint:  5,
+			PSmallint: 16000,
+			PInt:      20000000,
+			PBigint:   2000000020000000,
+			Pint:      455,
+		},
+		{
+			PTinyintNull:  &i8,
+			PSmallintNull: &i16,
+			PIntNull:      &i32,
+			PBigintNull:   &i64,
+			PintNull:      &i,
+		},
+		{
+			PTinyint:      5,
+			PSmallint:     16000,
+			PInt:          20000000,
+			PBigint:       2000000020000000,
+			PTinyintNull:  &i8,
+			PSmallintNull: &i16,
+			PIntNull:      &i32,
+			PBigintNull:   &i64,
+			PintNull:      &i,
+		},
+	}
+
+	tvpType := TVP{
+		TypeName: "unsignedTvpTableTypes",
+		Value:    param1,
+	}
+	tvpTypeEmpty := TVP{
+		TypeName: "unsignedTvpTableTypes",
+		Value:    []TvptableRow{},
+	}
+
+	rows, err := db.QueryContext(ctx,
+		"exec spwithtvpUnsigned @param1, @param2, @param3",
+		sql.Named("param1", tvpType),
+		sql.Named("param2", tvpTypeEmpty),
+		sql.Named("param3", "test"),
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer rows.Close()
+
+	var result1 []TvptableRow
+	for rows.Next() {
+		var val TvptableRow
+		err := rows.Scan(
+			&val.PTinyint,
+			&val.PTinyintNull,
+			&val.PSmallint,
+			&val.PSmallintNull,
+			&val.PInt,
+			&val.PIntNull,
+			&val.PBigint,
+			&val.PBigintNull,
+			&val.Pint,
+			&val.PintNull,
+		)
+		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)
+	}
+}