mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
Merge branch 'dev' into feature/componentizeServiceHost
This commit is contained in:
15
nuget.config
Normal file
15
nuget.config
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration >
|
||||||
|
|
||||||
|
<config>
|
||||||
|
<add key="DefaultPushSource" value="http://SQLISNuget/DS-SSMS/api/v2/package" />
|
||||||
|
</config>
|
||||||
|
|
||||||
|
<packageSources>
|
||||||
|
|
||||||
|
<!-- Add the SSMS repo for private requirements -->
|
||||||
|
<add key="SQLDS - SSMS" value="http://SQLISNuget/DS-SSMS/nuget/" />
|
||||||
|
|
||||||
|
</packageSources>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
63
src/ServiceHost/Connection/ConnectionMessages.cs
Normal file
63
src/ServiceHost/Connection/ConnectionMessages.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Message format for the initial connection request
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionDetails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection server name
|
||||||
|
/// </summary>
|
||||||
|
public string ServerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection database name
|
||||||
|
/// </summary>
|
||||||
|
public string DatabaseName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection user name
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection password
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message format for the connection result response
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection id
|
||||||
|
/// </summary>
|
||||||
|
public int ConnectionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets any connection error messages
|
||||||
|
/// </summary>
|
||||||
|
public string Messages { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connect request mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<ConnectionDetails, ConnectionResult> Type =
|
||||||
|
RequestType<ConnectionDetails, ConnectionResult>.Create("connection/connect");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
207
src/ServiceHost/Connection/ConnectionService.cs
Normal file
207
src/ServiceHost/Connection/ConnectionService.cs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for the Connection Management services
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionService
|
||||||
|
{
|
||||||
|
#region Singleton Instance Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
private static Lazy<ConnectionService> instance
|
||||||
|
= new Lazy<ConnectionService>(() => new ConnectionService());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
public static ConnectionService Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return instance.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor is private since it's a singleton class
|
||||||
|
/// </summary>
|
||||||
|
private ConnectionService()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The SQL connection factory object
|
||||||
|
/// </summary>
|
||||||
|
private ISqlConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current connection id that was previously used
|
||||||
|
/// </summary>
|
||||||
|
private int maxConnectionId = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Active connections lazy dictionary instance
|
||||||
|
/// </summary>
|
||||||
|
private Lazy<Dictionary<int, ISqlConnection>> activeConnections
|
||||||
|
= new Lazy<Dictionary<int, ISqlConnection>>(()
|
||||||
|
=> new Dictionary<int, ISqlConnection>());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback for onconnection handler
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sqlConnection"></param>
|
||||||
|
public delegate Task OnConnectionHandler(ISqlConnection sqlConnection);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of onconnection handlers
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<OnConnectionHandler> onConnectionActivities = new List<OnConnectionHandler>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the active connection map
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<int, ISqlConnection> ActiveConnections
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return activeConnections.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the SQL connection factory instance
|
||||||
|
/// </summary>
|
||||||
|
public ISqlConnectionFactory ConnectionFactory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.connectionFactory == null)
|
||||||
|
{
|
||||||
|
this.connectionFactory = new SqlConnectionFactory();
|
||||||
|
}
|
||||||
|
return this.connectionFactory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test constructor that injects dependency interfaces
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testFactory"></param>
|
||||||
|
public ConnectionService(ISqlConnectionFactory testFactory)
|
||||||
|
{
|
||||||
|
this.connectionFactory = testFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Open a connection with the specified connection details
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionDetails"></param>
|
||||||
|
public ConnectionResult Connect(ConnectionDetails connectionDetails)
|
||||||
|
{
|
||||||
|
// build the connection string from the input parameters
|
||||||
|
string connectionString = BuildConnectionString(connectionDetails);
|
||||||
|
|
||||||
|
// create a sql connection instance
|
||||||
|
ISqlConnection connection = this.ConnectionFactory.CreateSqlConnection();
|
||||||
|
|
||||||
|
// open the database
|
||||||
|
connection.OpenDatabaseConnection(connectionString);
|
||||||
|
|
||||||
|
// map the connection id to the connection object for future lookups
|
||||||
|
this.ActiveConnections.Add(++maxConnectionId, connection);
|
||||||
|
|
||||||
|
// invoke callback notifications
|
||||||
|
foreach (var activity in this.onConnectionActivities)
|
||||||
|
{
|
||||||
|
activity(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the connection result
|
||||||
|
return new ConnectionResult()
|
||||||
|
{
|
||||||
|
ConnectionId = maxConnectionId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(ServiceHost serviceHost)
|
||||||
|
{
|
||||||
|
// Register request and event handlers with the Service Host
|
||||||
|
serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new method to be called when the onconnection request is submitted
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="activity"></param>
|
||||||
|
public void RegisterOnConnectionTask(OnConnectionHandler activity)
|
||||||
|
{
|
||||||
|
onConnectionActivities.Add(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Request Handlers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle new connection requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionDetails"></param>
|
||||||
|
/// <param name="requestContext"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected async Task HandleConnectRequest(
|
||||||
|
ConnectionDetails connectionDetails,
|
||||||
|
RequestContext<ConnectionResult> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleConnectRequest");
|
||||||
|
|
||||||
|
// open connection base on request details
|
||||||
|
ConnectionResult result = ConnectionService.Instance.Connect(connectionDetails);
|
||||||
|
|
||||||
|
await requestContext.SendResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Helpers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build a connection string from a connection details instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionDetails"></param>
|
||||||
|
private string BuildConnectionString(ConnectionDetails connectionDetails)
|
||||||
|
{
|
||||||
|
SqlConnectionStringBuilder connectionBuilder = new SqlConnectionStringBuilder();
|
||||||
|
connectionBuilder["Data Source"] = connectionDetails.ServerName;
|
||||||
|
connectionBuilder["Integrated Security"] = false;
|
||||||
|
connectionBuilder["User Id"] = connectionDetails.UserName;
|
||||||
|
connectionBuilder["Password"] = connectionDetails.Password;
|
||||||
|
connectionBuilder["Initial Catalog"] = connectionDetails.DatabaseName;
|
||||||
|
return connectionBuilder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/ServiceHost/Connection/ISqlConnection.cs
Normal file
34
src/ServiceHost/Connection/ISqlConnection.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for the SQL Connection factory
|
||||||
|
/// </summary>
|
||||||
|
public interface ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new SQL Connection object
|
||||||
|
/// </summary>
|
||||||
|
ISqlConnection CreateSqlConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for the SQL Connection wrapper
|
||||||
|
/// </summary>
|
||||||
|
public interface ISqlConnection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Open a connection to the provided connection string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionString"></param>
|
||||||
|
void OpenDatabaseConnection(string connectionString);
|
||||||
|
|
||||||
|
IEnumerable<string> GetServerObjects();
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/ServiceHost/Connection/SqlConnection.cs
Normal file
72
src/ServiceHost/Connection/SqlConnection.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory class to create SqlClientConnections
|
||||||
|
/// The purpose of the factory is to make it easier to mock out the database
|
||||||
|
/// in 'offline' unit test scenarios.
|
||||||
|
/// </summary>
|
||||||
|
public class SqlConnectionFactory : ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new SqlClientConnection object
|
||||||
|
/// </summary>
|
||||||
|
public ISqlConnection CreateSqlConnection()
|
||||||
|
{
|
||||||
|
return new SqlClientConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper class that implements ISqlConnection and hosts a SqlConnection.
|
||||||
|
/// This wrapper exists primarily for decoupling to support unit testing.
|
||||||
|
/// </summary>
|
||||||
|
public class SqlClientConnection : ISqlConnection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// the underlying SQL connection
|
||||||
|
/// </summary>
|
||||||
|
private SqlConnection connection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens a SqlConnection using provided connection string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionString"></param>
|
||||||
|
public void OpenDatabaseConnection(string connectionString)
|
||||||
|
{
|
||||||
|
this.connection = new SqlConnection(connectionString);
|
||||||
|
this.connection.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of database server schema objects
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerable<string> GetServerObjects()
|
||||||
|
{
|
||||||
|
// Select the values from sys.tables to give a super basic
|
||||||
|
// autocomplete experience. This will be replaced by SMO.
|
||||||
|
SqlCommand command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT name FROM sys.tables";
|
||||||
|
command.CommandTimeout = 15;
|
||||||
|
command.CommandType = CommandType.Text;
|
||||||
|
var reader = command.ExecuteReader();
|
||||||
|
|
||||||
|
List<string> results = new List<string>();
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
results.Add(reader[0].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/ServiceHost/LanguageServices/AutoCompleteService.cs
Normal file
123
src/ServiceHost/LanguageServices/AutoCompleteService.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for Autocomplete functionality
|
||||||
|
/// </summary>
|
||||||
|
public class AutoCompleteService
|
||||||
|
{
|
||||||
|
#region Singleton Instance Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
private static Lazy<AutoCompleteService> instance
|
||||||
|
= new Lazy<AutoCompleteService>(() => new AutoCompleteService());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
public static AutoCompleteService Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return instance.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default, parameterless constructor.
|
||||||
|
/// TODO: Figure out how to make this truely singleton even with dependency injection for tests
|
||||||
|
/// </summary>
|
||||||
|
public AutoCompleteService()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current autocomplete candidate list
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> AutoCompleteList { get; private set; }
|
||||||
|
|
||||||
|
public void InitializeService(ServiceHost serviceHost)
|
||||||
|
{
|
||||||
|
// Register a callback for when a connection is created
|
||||||
|
ConnectionService.Instance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the cached autocomplete candidate list when the user connects to a database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connection"></param>
|
||||||
|
public async Task UpdateAutoCompleteCache(ISqlConnection connection)
|
||||||
|
{
|
||||||
|
AutoCompleteList = connection.GetServerObjects();
|
||||||
|
await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the completion item list for the current text position
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="textDocumentPosition"></param>
|
||||||
|
public CompletionItem[] GetCompletionItems(TextDocumentPosition textDocumentPosition)
|
||||||
|
{
|
||||||
|
var completions = new List<CompletionItem>();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
// the completion list will be null is user not connected to server
|
||||||
|
if (this.AutoCompleteList != null)
|
||||||
|
{
|
||||||
|
foreach (var autoCompleteItem in this.AutoCompleteList)
|
||||||
|
{
|
||||||
|
// convert the completion item candidates into CompletionItems
|
||||||
|
completions.Add(new CompletionItem()
|
||||||
|
{
|
||||||
|
Label = autoCompleteItem,
|
||||||
|
Kind = CompletionItemKind.Keyword,
|
||||||
|
Detail = autoCompleteItem + " details",
|
||||||
|
Documentation = autoCompleteItem + " documentation",
|
||||||
|
TextEdit = new TextEdit
|
||||||
|
{
|
||||||
|
NewText = autoCompleteItem,
|
||||||
|
Range = new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = textDocumentPosition.Position.Line,
|
||||||
|
Character = textDocumentPosition.Position.Character
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = textDocumentPosition.Position.Line,
|
||||||
|
Character = textDocumentPosition.Position.Character + 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// only show 50 items
|
||||||
|
if (++i == 50)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completions.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
@@ -14,6 +15,8 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.WorkspaceServices;
|
using Microsoft.SqlTools.ServiceLayer.WorkspaceServices;
|
||||||
using Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
|
using Location = Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts.Location;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
{
|
{
|
||||||
@@ -35,8 +38,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default, parameterless constructor.
|
/// Default, parameterless constructor.
|
||||||
|
/// TODO: Figure out how to make this truely singleton even with dependency injection for tests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private LanguageService()
|
public LanguageService()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +66,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private SqlToolsContext Context { get; set; }
|
private SqlToolsContext Context { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cached parse result from previous incremental parse
|
||||||
|
/// </summary>
|
||||||
|
private ParseResult prevParseResult;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
public void InitializeService(ServiceHost serviceHost, SqlToolsContext context)
|
public void InitializeService(ServiceHost serviceHost, SqlToolsContext context)
|
||||||
{
|
{
|
||||||
// Register the requests that this service will handle
|
// Register the requests that this service will handle
|
||||||
@@ -91,6 +102,48 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
Context = context;
|
Context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of semantic diagnostic marks for the provided script file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scriptFile"></param>
|
||||||
|
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
||||||
|
{
|
||||||
|
// parse current SQL file contents to retrieve a list of errors
|
||||||
|
ParseOptions parseOptions = new ParseOptions();
|
||||||
|
ParseResult parseResult = Parser.IncrementalParse(
|
||||||
|
scriptFile.Contents,
|
||||||
|
prevParseResult,
|
||||||
|
parseOptions);
|
||||||
|
|
||||||
|
// save previous result for next incremental parse
|
||||||
|
this.prevParseResult = parseResult;
|
||||||
|
|
||||||
|
// build a list of SQL script file markers from the errors
|
||||||
|
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||||
|
foreach (var error in parseResult.Errors)
|
||||||
|
{
|
||||||
|
markers.Add(new ScriptFileMarker()
|
||||||
|
{
|
||||||
|
Message = error.Message,
|
||||||
|
Level = ScriptFileMarkerLevel.Error,
|
||||||
|
ScriptRegion = new ScriptRegion()
|
||||||
|
{
|
||||||
|
File = scriptFile.FilePath,
|
||||||
|
StartLineNumber = error.Start.LineNumber,
|
||||||
|
StartColumnNumber = error.Start.ColumnNumber,
|
||||||
|
StartOffset = 0,
|
||||||
|
EndLineNumber = error.End.LineNumber,
|
||||||
|
EndColumnNumber = error.End.ColumnNumber,
|
||||||
|
EndOffset = 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return markers.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Request Handlers
|
#region Request Handlers
|
||||||
|
|
||||||
private static async Task HandleDefinitionRequest(
|
private static async Task HandleDefinitionRequest(
|
||||||
@@ -204,32 +257,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
#region Private Helpers
|
#region Private Helpers
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of semantic diagnostic marks for the provided script file
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFile"></param>
|
|
||||||
private ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
|
||||||
{
|
|
||||||
// the commented out snippet is an example of how to create a error marker
|
|
||||||
// semanticMarkers = new ScriptFileMarker[1];
|
|
||||||
// semanticMarkers[0] = new ScriptFileMarker()
|
|
||||||
// {
|
|
||||||
// Message = "Error message",
|
|
||||||
// Level = ScriptFileMarkerLevel.Error,
|
|
||||||
// ScriptRegion = new ScriptRegion()
|
|
||||||
// {
|
|
||||||
// File = scriptFile.FilePath,
|
|
||||||
// StartLineNumber = 2,
|
|
||||||
// StartColumnNumber = 2,
|
|
||||||
// StartOffset = 0,
|
|
||||||
// EndLineNumber = 4,
|
|
||||||
// EndColumnNumber = 10,
|
|
||||||
// EndOffset = 0
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
return new ScriptFileMarker[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs script diagnostics on changed files
|
/// Runs script diagnostics on changed files
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
|
|
||||||
// Initialize the services that will be hosted here
|
// Initialize the services that will be hosted here
|
||||||
WorkspaceService<SqlToolsSettings>.Instance.InitializeService(serviceHost);
|
WorkspaceService<SqlToolsSettings>.Instance.InitializeService(serviceHost);
|
||||||
|
AutoCompleteService.Instance.InitializeService(serviceHost);
|
||||||
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
|
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
|
||||||
|
|
||||||
// Start the service
|
// Start the service
|
||||||
|
|||||||
@@ -100,6 +100,13 @@ namespace Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts
|
|||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a default constructor for testing
|
||||||
|
/// </summary>
|
||||||
|
public ScriptFile()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new ScriptFile instance by reading file contents from
|
/// Creates a new ScriptFile instance by reading file contents from
|
||||||
/// the given TextReader.
|
/// the given TextReader.
|
||||||
@@ -421,11 +428,11 @@ namespace Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts
|
|||||||
return new BufferRange(startPosition, endPosition);
|
return new BufferRange(startPosition, endPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
/// <summary>
|
||||||
|
/// Set the script files contents
|
||||||
#region Private Methods
|
/// </summary>
|
||||||
|
/// <param name="fileContents"></param>
|
||||||
private void SetFileContents(string fileContents)
|
public void SetFileContents(string fileContents)
|
||||||
{
|
{
|
||||||
// Split the file contents into lines and trim
|
// Split the file contents into lines and trim
|
||||||
// any carriage returns from the strings.
|
// any carriage returns from the strings.
|
||||||
@@ -439,6 +446,10 @@ namespace Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts
|
|||||||
this.ParseFileContents();
|
this.ParseFileContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses the current file contents to get the AST, tokens,
|
/// Parses the current file contents to get the AST, tokens,
|
||||||
/// and parse errors.
|
/// and parse errors.
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ namespace Microsoft.SqlTools.ServiceLayer.WorkspaceServices
|
|||||||
get { return instance.Value; }
|
get { return instance.Value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private WorkspaceService()
|
/// <summary>
|
||||||
|
/// Default, parameterless constructor.
|
||||||
|
/// TODO: Figure out how to make this truely singleton even with dependency injection for tests
|
||||||
|
/// </summary>
|
||||||
|
public WorkspaceService()
|
||||||
{
|
{
|
||||||
ConfigChangeCallbacks = new List<ConfigChangeCallback>();
|
ConfigChangeCallbacks = new List<ConfigChangeCallback>();
|
||||||
TextDocChangeCallbacks = new List<TextDocChangeCallback>();
|
TextDocChangeCallbacks = new List<TextDocChangeCallback>();
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
"emitEntryPoint": true
|
"emitEntryPoint": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Newtonsoft.Json": "9.0.1"
|
"Newtonsoft.Json": "9.0.1",
|
||||||
|
"Microsoft.SqlServer.SqlParser": "140.1.3",
|
||||||
|
"System.Data.Common": "4.1.0",
|
||||||
|
"System.Data.SqlClient": "4.1.0"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"netcoreapp1.0": {
|
"netcoreapp1.0": {
|
||||||
|
|||||||
60
test/ServiceHost.Test/Connection/ConnectionServiceTests.cs
Normal file
60
test/ServiceHost.Test/Connection/ConnectionServiceTests.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.Test.Utility;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the ServiceHost Connection Service tests
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionServiceTests
|
||||||
|
{
|
||||||
|
#region "Connection tests"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that the SQL parser correctly detects errors in text
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void ConnectToDatabaseTest()
|
||||||
|
{
|
||||||
|
// connect to a database instance
|
||||||
|
var connectionResult =
|
||||||
|
TestObjects.GetTestConnectionService()
|
||||||
|
.Connect(TestObjects.GetTestConnectionDetails());
|
||||||
|
|
||||||
|
// verify that a valid connection id was returned
|
||||||
|
Assert.True(connectionResult.ConnectionId > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that the SQL parser correctly detects errors in text
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void OnConnectionCallbackHandlerTest()
|
||||||
|
{
|
||||||
|
bool callbackInvoked = false;
|
||||||
|
|
||||||
|
// setup connection service with callback
|
||||||
|
var connectionService = TestObjects.GetTestConnectionService();
|
||||||
|
connectionService.RegisterOnConnectionTask(
|
||||||
|
(sqlConnection) => {
|
||||||
|
callbackInvoked = true;
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// connect to a database instance
|
||||||
|
var connectionResult = connectionService.Connect(TestObjects.GetTestConnectionDetails());
|
||||||
|
|
||||||
|
// verify that a valid connection id was returned
|
||||||
|
Assert.True(callbackInvoked);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
124
test/ServiceHost.Test/LanguageServer/LanguageServiceTests.cs
Normal file
124
test/ServiceHost.Test/LanguageServer/LanguageServiceTests.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.Test.Utility;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the ServiceHost Language Service tests
|
||||||
|
/// </summary>
|
||||||
|
public class LanguageServiceTests
|
||||||
|
{
|
||||||
|
#region "Diagnostics tests"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that the SQL parser correctly detects errors in text
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void ParseSelectStatementWithoutErrors()
|
||||||
|
{
|
||||||
|
// sql statement with no errors
|
||||||
|
const string sqlWithErrors = "SELECT * FROM sys.objects";
|
||||||
|
|
||||||
|
// get the test service
|
||||||
|
LanguageService service = TestObjects.GetTestLanguageService();
|
||||||
|
|
||||||
|
// parse the sql statement
|
||||||
|
var scriptFile = new ScriptFile();
|
||||||
|
scriptFile.SetFileContents(sqlWithErrors);
|
||||||
|
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||||
|
|
||||||
|
// verify there are no errors
|
||||||
|
Assert.Equal(0, fileMarkers.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that the SQL parser correctly detects errors in text
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void ParseSelectStatementWithError()
|
||||||
|
{
|
||||||
|
// sql statement with errors
|
||||||
|
const string sqlWithErrors = "SELECT *** FROM sys.objects";
|
||||||
|
|
||||||
|
// get test service
|
||||||
|
LanguageService service = TestObjects.GetTestLanguageService();
|
||||||
|
|
||||||
|
// parse sql statement
|
||||||
|
var scriptFile = new ScriptFile();
|
||||||
|
scriptFile.SetFileContents(sqlWithErrors);
|
||||||
|
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||||
|
|
||||||
|
// verify there is one error
|
||||||
|
Assert.Equal(1, fileMarkers.Length);
|
||||||
|
|
||||||
|
// verify the position of the error
|
||||||
|
Assert.Equal(9, fileMarkers[0].ScriptRegion.StartColumnNumber);
|
||||||
|
Assert.Equal(1, fileMarkers[0].ScriptRegion.StartLineNumber);
|
||||||
|
Assert.Equal(10, fileMarkers[0].ScriptRegion.EndColumnNumber);
|
||||||
|
Assert.Equal(1, fileMarkers[0].ScriptRegion.EndLineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that the SQL parser correctly detects errors in text
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void ParseMultilineSqlWithErrors()
|
||||||
|
{
|
||||||
|
// multiline sql with errors
|
||||||
|
const string sqlWithErrors =
|
||||||
|
"SELECT *** FROM sys.objects;\n" +
|
||||||
|
"GO\n" +
|
||||||
|
"SELECT *** FROM sys.objects;\n";
|
||||||
|
|
||||||
|
// get test service
|
||||||
|
LanguageService service = TestObjects.GetTestLanguageService();
|
||||||
|
|
||||||
|
// parse sql
|
||||||
|
var scriptFile = new ScriptFile();
|
||||||
|
scriptFile.SetFileContents(sqlWithErrors);
|
||||||
|
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||||
|
|
||||||
|
// verify there are two errors
|
||||||
|
Assert.Equal(2, fileMarkers.Length);
|
||||||
|
|
||||||
|
// check position of first error
|
||||||
|
Assert.Equal(9, fileMarkers[0].ScriptRegion.StartColumnNumber);
|
||||||
|
Assert.Equal(1, fileMarkers[0].ScriptRegion.StartLineNumber);
|
||||||
|
Assert.Equal(10, fileMarkers[0].ScriptRegion.EndColumnNumber);
|
||||||
|
Assert.Equal(1, fileMarkers[0].ScriptRegion.EndLineNumber);
|
||||||
|
|
||||||
|
// check position of second error
|
||||||
|
Assert.Equal(9, fileMarkers[1].ScriptRegion.StartColumnNumber);
|
||||||
|
Assert.Equal(3, fileMarkers[1].ScriptRegion.StartLineNumber);
|
||||||
|
Assert.Equal(10, fileMarkers[1].ScriptRegion.EndColumnNumber);
|
||||||
|
Assert.Equal(3, fileMarkers[1].ScriptRegion.EndLineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Autocomplete Tests"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that the SQL parser correctly detects errors in text
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void AutocompleteTest()
|
||||||
|
{
|
||||||
|
var autocompleteService = TestObjects.GetAutoCompleteService();
|
||||||
|
var connectionService = TestObjects.GetTestConnectionService();
|
||||||
|
var connectionResult = connectionService.Connect(TestObjects.GetTestConnectionDetails());
|
||||||
|
var sqlConnection = connectionService.ActiveConnections[connectionResult.ConnectionId];
|
||||||
|
autocompleteService.UpdateAutoCompleteCache(sqlConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
108
test/ServiceHost.Test/Utility/TestObjects.cs
Normal file
108
test/ServiceHost.Test/Utility/TestObjects.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
//#define USE_LIVE_CONNECTION
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.Test.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the ServiceHost Connection Service tests
|
||||||
|
/// </summary>
|
||||||
|
public class TestObjects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a test connection service
|
||||||
|
/// </summary>
|
||||||
|
public static ConnectionService GetTestConnectionService()
|
||||||
|
{
|
||||||
|
#if !USE_LIVE_CONNECTION
|
||||||
|
// use mock database connection
|
||||||
|
return new ConnectionService(new TestSqlConnectionFactory());
|
||||||
|
#else
|
||||||
|
// connect to a real server instance
|
||||||
|
return ConnectionService.Instance;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a test connection details object
|
||||||
|
/// </summary>
|
||||||
|
public static ConnectionDetails GetTestConnectionDetails()
|
||||||
|
{
|
||||||
|
return new ConnectionDetails()
|
||||||
|
{
|
||||||
|
UserName = "sa",
|
||||||
|
Password = "Yukon900",
|
||||||
|
DatabaseName = "AdventureWorks2016CTP3_2",
|
||||||
|
ServerName = "sqltools11"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a test language service instance
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static LanguageService GetTestLanguageService()
|
||||||
|
{
|
||||||
|
return new LanguageService();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a test autocomplete service instance
|
||||||
|
/// </summary>
|
||||||
|
public static AutoCompleteService GetAutoCompleteService()
|
||||||
|
{
|
||||||
|
return AutoCompleteService.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a test sql connection factory instance
|
||||||
|
/// </summary>
|
||||||
|
public static ISqlConnectionFactory GetTestSqlConnectionFactory()
|
||||||
|
{
|
||||||
|
#if !USE_LIVE_CONNECTION
|
||||||
|
// use mock database connection
|
||||||
|
return new TestSqlConnectionFactory();
|
||||||
|
#else
|
||||||
|
// connect to a real server instance
|
||||||
|
return ConnectionService.Instance.ConnectionFactory;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test mock class for SqlConnection wrapper
|
||||||
|
/// </summary>
|
||||||
|
public class TestSqlConnection : ISqlConnection
|
||||||
|
{
|
||||||
|
public void OpenDatabaseConnection(string connectionString)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetServerObjects()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test mock class for SqlConnection factory
|
||||||
|
/// </summary>
|
||||||
|
public class TestSqlConnectionFactory : ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
public ISqlConnection CreateSqlConnection()
|
||||||
|
{
|
||||||
|
return new TestSqlConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,11 +6,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Newtonsoft.Json": "9.0.1",
|
"Newtonsoft.Json": "9.0.1",
|
||||||
"System.Runtime.Serialization.Primitives": "4.1.1",
|
"System.Runtime.Serialization.Primitives": "4.1.1",
|
||||||
|
"System.Data.Common": "4.1.0",
|
||||||
|
"System.Data.SqlClient": "4.1.0",
|
||||||
"xunit": "2.1.0",
|
"xunit": "2.1.0",
|
||||||
"dotnet-test-xunit": "1.0.0-rc2-192208-24",
|
"dotnet-test-xunit": "1.0.0-rc2-192208-24",
|
||||||
"ServiceHost": {
|
"ServiceHost": {
|
||||||
"target": "project"
|
"target": "project"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"testRunner": "xunit",
|
"testRunner": "xunit",
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
|||||||
Reference in New Issue
Block a user