From 3fe6e330fed375feb60c6401027fb6cec1a8b2c5 Mon Sep 17 00:00:00 2001 From: Mitchell Sternke Date: Tue, 30 Aug 2016 17:31:34 -0700 Subject: [PATCH] Added support for most sql connection string properties --- .../Connection/ConnectionService.cs | 115 +++++++++++++++++- .../Connection/Contracts/ConnectionDetails.cs | 113 ++++++++++++++++- .../Contracts/ConnectionDetailsExtensions.cs | 24 +++- .../Connection/ConnectionServiceTests.cs | 50 +++++++- 4 files changed, 298 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index a2dd97db..198813e8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -353,10 +353,123 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection connectionBuilder["Integrated Security"] = false; connectionBuilder["User Id"] = connectionDetails.UserName; connectionBuilder["Password"] = connectionDetails.Password; - if( !String.IsNullOrEmpty(connectionDetails.DatabaseName) ) + + // Check for any optional parameters + if (!String.IsNullOrEmpty(connectionDetails.DatabaseName)) { connectionBuilder["Initial Catalog"] = connectionDetails.DatabaseName; } + if (!String.IsNullOrEmpty(connectionDetails.AuthenticationType)) + { + switch(connectionDetails.AuthenticationType) + { + case "Integrated": + connectionBuilder.IntegratedSecurity = true; + break; + case "SqlLogin": + connectionBuilder.IntegratedSecurity = false; + break; + default: + throw new ArgumentException("Invalid value \"" + connectionDetails.AuthenticationType + "\" for AuthenticationType. Valid values are \"Integrated\" and \"SqlLogin\"."); + } + } + if (connectionDetails.Encrypt.HasValue) + { + connectionBuilder.Encrypt = connectionDetails.Encrypt.Value; + } + if (connectionDetails.TrustServerCertificate.HasValue) + { + connectionBuilder.TrustServerCertificate = connectionDetails.TrustServerCertificate.Value; + } + if (connectionDetails.PersistSecurityInfo.HasValue) + { + connectionBuilder.PersistSecurityInfo = connectionDetails.PersistSecurityInfo.Value; + } + if (connectionDetails.ConnectTimeout.HasValue) + { + connectionBuilder.ConnectTimeout = connectionDetails.ConnectTimeout.Value; + } + if (connectionDetails.ConnectRetryCount.HasValue) + { + connectionBuilder.ConnectRetryCount = connectionDetails.ConnectRetryCount.Value; + } + if (connectionDetails.ConnectRetryInterval.HasValue) + { + connectionBuilder.ConnectRetryInterval = connectionDetails.ConnectRetryInterval.Value; + } + if (!String.IsNullOrEmpty(connectionDetails.ApplicationName)) + { + connectionBuilder.ApplicationName = connectionDetails.ApplicationName; + } + if (!String.IsNullOrEmpty(connectionDetails.WorkstationId)) + { + connectionBuilder.WorkstationID = connectionDetails.WorkstationId; + } + if (!String.IsNullOrEmpty(connectionDetails.ApplicationIntent)) + { + ApplicationIntent intent; + switch (connectionDetails.ApplicationIntent) + { + case "ReadOnly": + intent = ApplicationIntent.ReadOnly; + break; + case "ReadWrite": + intent = ApplicationIntent.ReadWrite; + break; + default: + throw new ArgumentException("Invalid value \"" + connectionDetails.ApplicationIntent + "\" for ApplicationIntent. Valid values are \"ReadWrite\" and \"ReadOnly\"."); + } + connectionBuilder.ApplicationIntent = intent; + } + if (!String.IsNullOrEmpty(connectionDetails.CurrentLanguage)) + { + connectionBuilder.CurrentLanguage = connectionDetails.CurrentLanguage; + } + if (connectionDetails.Pooling.HasValue) + { + connectionBuilder.Pooling = connectionDetails.Pooling.Value; + } + if (connectionDetails.MaxPoolSize.HasValue) + { + connectionBuilder.MaxPoolSize = connectionDetails.MaxPoolSize.Value; + } + if (connectionDetails.MinPoolSize.HasValue) + { + connectionBuilder.MinPoolSize = connectionDetails.MinPoolSize.Value; + } + if (connectionDetails.LoadBalanceTimeout.HasValue) + { + connectionBuilder.LoadBalanceTimeout = connectionDetails.LoadBalanceTimeout.Value; + } + if (connectionDetails.Replication.HasValue) + { + connectionBuilder.Replication = connectionDetails.Replication.Value; + } + if (!String.IsNullOrEmpty(connectionDetails.AttachDbFilename)) + { + connectionBuilder.AttachDBFilename = connectionDetails.AttachDbFilename; + } + if (!String.IsNullOrEmpty(connectionDetails.FailoverPartner)) + { + connectionBuilder.FailoverPartner = connectionDetails.FailoverPartner; + } + if (connectionDetails.MultiSubnetFailover.HasValue) + { + connectionBuilder.MultiSubnetFailover = connectionDetails.MultiSubnetFailover.Value; + } + if (connectionDetails.MultipleActiveResultSets.HasValue) + { + connectionBuilder.MultipleActiveResultSets = connectionDetails.MultipleActiveResultSets.Value; + } + if (connectionDetails.PacketSize.HasValue) + { + connectionBuilder.PacketSize = connectionDetails.PacketSize.Value; + } + if (!String.IsNullOrEmpty(connectionDetails.TypeSystemVersion)) + { + connectionBuilder.TypeSystemVersion = connectionDetails.TypeSystemVersion; + } + return connectionBuilder.ToString(); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs index 0acac867..ce1c6208 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs @@ -8,6 +8,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts /// /// Message format for the initial connection request /// + /// + /// If this contract is ever changed, be sure to update ConnectionDetailsExtensions methods. + /// public class ConnectionDetails : ConnectionSummary { /// @@ -16,6 +19,114 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts /// public string Password { get; set; } - // TODO Handle full set of properties + /// + /// Gets or sets the authentication to use. + /// + public string AuthenticationType { get; set; } + + /// + /// Gets or sets a Boolean value that indicates whether SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed. + /// + public bool? Encrypt { get; set; } + + /// + /// Gets or sets a value that indicates whether the channel will be encrypted while bypassing walking the certificate chain to validate trust. + /// + public bool? TrustServerCertificate { get; set; } + + /// + /// Gets or sets a Boolean value that indicates if security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state. + /// + public bool? PersistSecurityInfo { get; set; } + + /// + /// Gets or sets the length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error. + /// + public int? ConnectTimeout { get; set; } + + /// + /// The number of reconnections attempted after identifying that there was an idle connection failure. + /// + public int? ConnectRetryCount { get; set; } + + /// + /// Amount of time (in seconds) between each reconnection attempt after identifying that there was an idle connection failure. + /// + public int? ConnectRetryInterval { get; set; } + + /// + /// Gets or sets the name of the application associated with the connection string. + /// + public string ApplicationName { get; set; } + + /// + /// Gets or sets the name of the workstation connecting to SQL Server. + /// + public string WorkstationId { get; set; } + + /// + /// Declares the application workload type when connecting to a database in an SQL Server Availability Group. + /// + public string ApplicationIntent { get; set; } + + /// + /// Gets or sets the SQL Server Language record name. + /// + public string CurrentLanguage { get; set; } + + /// + /// Gets or sets a Boolean value that indicates whether the connection will be pooled or explicitly opened every time that the connection is requested. + /// + public bool? Pooling { get; set; } + + /// + /// Gets or sets the maximum number of connections allowed in the connection pool for this specific connection string. + /// + public int? MaxPoolSize { get; set; } + + /// + /// Gets or sets the minimum number of connections allowed in the connection pool for this specific connection string. + /// + public int? MinPoolSize { get; set; } + + /// + /// Gets or sets the minimum time, in seconds, for the connection to live in the connection pool before being destroyed. + /// + public int? LoadBalanceTimeout { get; set; } + + /// + /// Gets or sets a Boolean value that indicates whether replication is supported using the connection. + /// + public bool? Replication { get; set; } + + /// + /// Gets or sets a string that contains the name of the primary data file. This includes the full path name of an attachable database. + /// + public string AttachDbFilename { get; set; } + + /// + /// Gets or sets the name or address of the partner server to connect to if the primary server is down. + /// + public string FailoverPartner { get; set; } + + /// + /// If your application is connecting to an AlwaysOn availability group (AG) on different subnets, setting MultiSubnetFailover=true provides faster detection of and connection to the (currently) active server. + /// + public bool? MultiSubnetFailover { get; set; } + + /// + /// When true, an application can maintain multiple active result sets (MARS). + /// + public bool? MultipleActiveResultSets { get; set; } + + /// + /// Gets or sets the size in bytes of the network packets used to communicate with an instance of SQL Server. + /// + public int? PacketSize { get; set; } + + /// + /// Gets or sets a string value that indicates the type system the application expects. + /// + public string TypeSystemVersion { get; set; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs index de278dbc..106fa06e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs @@ -20,7 +20,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts ServerName = details.ServerName, DatabaseName = details.DatabaseName, UserName = details.UserName, - Password = details.Password + Password = details.Password, + AuthenticationType = details.AuthenticationType, + Encrypt = details.Encrypt, + TrustServerCertificate = details.TrustServerCertificate, + PersistSecurityInfo = details.PersistSecurityInfo, + ConnectTimeout = details.ConnectTimeout, + ConnectRetryCount = details.ConnectRetryCount, + ConnectRetryInterval = details.ConnectRetryInterval, + ApplicationName = details.ApplicationName, + WorkstationId = details.WorkstationId, + ApplicationIntent = details.ApplicationIntent, + CurrentLanguage = details.CurrentLanguage, + Pooling = details.Pooling, + MaxPoolSize = details.MaxPoolSize, + MinPoolSize = details.MinPoolSize, + LoadBalanceTimeout = details.LoadBalanceTimeout, + Replication = details.Replication, + AttachDbFilename = details.AttachDbFilename, + FailoverPartner = details.FailoverPartner, + MultiSubnetFailover = details.MultiSubnetFailover, + MultipleActiveResultSets = details.MultipleActiveResultSets, + PacketSize = details.PacketSize, + TypeSystemVersion = details.TypeSystemVersion }; } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs index b6888b0a..1c3cc8d9 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs @@ -7,10 +7,10 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Reflection; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; -using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.Test.Utility; using Moq; @@ -197,6 +197,54 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.NotEqual(String.Empty, connectionResult.Messages); } + /// + /// Verify that optional parameters can be built into a connection string for connecting. + /// + [Theory] + [InlineDataAttribute("AuthenticationType", "Integrated")] + [InlineDataAttribute("AuthenticationType", "SqlLogin")] + [InlineDataAttribute("Encrypt", true)] + [InlineDataAttribute("Encrypt", false)] + [InlineDataAttribute("TrustServerCertificate", true)] + [InlineDataAttribute("TrustServerCertificate", false)] + [InlineDataAttribute("PersistSecurityInfo", true)] + [InlineDataAttribute("PersistSecurityInfo", false)] + [InlineDataAttribute("ConnectTimeout", 15)] + [InlineDataAttribute("ConnectRetryCount", 1)] + [InlineDataAttribute("ConnectRetryInterval", 10)] + [InlineDataAttribute("ApplicationName", "vscode-mssql")] + [InlineDataAttribute("WorkstationId", "mycomputer")] + [InlineDataAttribute("ApplicationIntent", "ReadWrite")] + [InlineDataAttribute("ApplicationIntent", "ReadOnly")] + [InlineDataAttribute("CurrentLanguage", "test")] + [InlineDataAttribute("Pooling", false)] + [InlineDataAttribute("Pooling", true)] + [InlineDataAttribute("MaxPoolSize", 100)] + [InlineDataAttribute("MinPoolSize", 0)] + [InlineDataAttribute("LoadBalanceTimeout", 0)] + [InlineDataAttribute("Replication", true)] + [InlineDataAttribute("Replication", false)] + [InlineDataAttribute("AttachDbFilename", "myfile")] + [InlineDataAttribute("FailoverPartner", "partner")] + [InlineDataAttribute("MultiSubnetFailover", true)] + [InlineDataAttribute("MultiSubnetFailover", false)] + [InlineDataAttribute("MultipleActiveResultSets", false)] + [InlineDataAttribute("MultipleActiveResultSets", true)] + [InlineDataAttribute("PacketSize", 8192)] + [InlineDataAttribute("TypeSystemVersion", "Latest")] + public void ConnectingWithOptionalParametersBuildsConnectionString(string propertyName, object propertyValue) + { + // Create a test connection details object and set the property to a specific value + ConnectionDetails details = TestObjects.GetTestConnectionDetails(); + PropertyInfo info = details.GetType().GetProperty(propertyName); + info.SetValue(details, propertyValue); + + // Test that a connection string can be created without exceptions + string connectionString = ConnectionService.BuildConnectionString(details); + Assert.NotNull(connectionString); + Assert.NotEmpty(connectionString); + } + /// /// Verify that the SQL parser correctly detects errors in text ///