mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
Merge pull request #34 from Microsoft/feature/testSmo
Enable SMO-based autocomplete
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -13,6 +13,7 @@ project.lock.json
|
|||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
*.exe
|
||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
[Dd]ebug/
|
[Dd]ebug/
|
||||||
@@ -29,7 +30,13 @@ msbuild.log
|
|||||||
msbuild.err
|
msbuild.err
|
||||||
msbuild.wrn
|
msbuild.wrn
|
||||||
|
|
||||||
|
# code coverage artifacts
|
||||||
|
coverage.xml
|
||||||
|
node_modules
|
||||||
|
packages
|
||||||
|
reports
|
||||||
|
opencovertests.xml
|
||||||
|
sqltools.xml
|
||||||
|
|
||||||
# Cross building rootfs
|
# Cross building rootfs
|
||||||
cross/rootfs/
|
cross/rootfs/
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Data;
|
|||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Data.SqlClient;
|
using System.Data.SqlClient;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
@@ -480,5 +481,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
|||||||
|
|
||||||
return connectionBuilder.ToString();
|
return connectionBuilder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
|||||||
DocumentHighlightProvider = true,
|
DocumentHighlightProvider = true,
|
||||||
DocumentSymbolProvider = true,
|
DocumentSymbolProvider = true,
|
||||||
WorkspaceSymbolProvider = true,
|
WorkspaceSymbolProvider = true,
|
||||||
HoverProvider = true,
|
|
||||||
CompletionProvider = new CompletionOptions
|
CompletionProvider = new CompletionOptions
|
||||||
{
|
{
|
||||||
ResolveProvider = true,
|
ResolveProvider = true,
|
||||||
|
|||||||
@@ -5,11 +5,20 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Data.SqlClient;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
@@ -40,19 +49,14 @@ 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
|
/// Internal constructor for use in test cases only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AutoCompleteService()
|
internal AutoCompleteService()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
// Dictionary of unique intellisense caches for each Connection
|
|
||||||
private Dictionary<ConnectionSummary, IntellisenseCache> caches =
|
|
||||||
new Dictionary<ConnectionSummary, IntellisenseCache>(new ConnectionSummaryComparer());
|
|
||||||
private Object cachesLock = new Object(); // Used when we insert/remove something from the cache dictionary
|
|
||||||
|
|
||||||
private ConnectionService connectionService = null;
|
private ConnectionService connectionService = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -77,6 +81,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
public void InitializeService(ServiceHost serviceHost)
|
public void InitializeService(ServiceHost serviceHost)
|
||||||
{
|
{
|
||||||
|
// Register auto-complete request handler
|
||||||
|
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
|
||||||
|
|
||||||
// Register a callback for when a connection is created
|
// Register a callback for when a connection is created
|
||||||
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
|
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
|
||||||
|
|
||||||
@@ -84,12 +91,29 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Intellisense cache count access for testing.
|
/// Auto-complete completion provider request callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal int GetCacheCount()
|
/// <param name="textDocumentPosition"></param>
|
||||||
|
/// <param name="requestContext"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static async Task HandleCompletionRequest(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
RequestContext<CompletionItem[]> requestContext)
|
||||||
{
|
{
|
||||||
return caches.Count;
|
// get the current list of completion items and return to client
|
||||||
|
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
||||||
|
textDocumentPosition.TextDocument.Uri);
|
||||||
|
|
||||||
|
ConnectionInfo connInfo;
|
||||||
|
ConnectionService.Instance.TryFindConnection(
|
||||||
|
scriptFile.ClientFilePath,
|
||||||
|
out connInfo);
|
||||||
|
|
||||||
|
var completionItems = Instance.GetCompletionItems(
|
||||||
|
textDocumentPosition, scriptFile, connInfo);
|
||||||
|
|
||||||
|
await requestContext.SendResult(completionItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -99,47 +123,152 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
|
public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
|
||||||
{
|
{
|
||||||
await Task.Run( () =>
|
// currently this method is disabled, but we need to reimplement now that the
|
||||||
{
|
// implementation of the 'cache' has changed.
|
||||||
lock(cachesLock)
|
await Task.FromResult(0);
|
||||||
{
|
|
||||||
IntellisenseCache cache;
|
|
||||||
if( caches.TryGetValue(summary, out cache) )
|
|
||||||
{
|
|
||||||
cache.ReferenceCount--;
|
|
||||||
|
|
||||||
// Remove unused caches
|
|
||||||
if( cache.ReferenceCount == 0 )
|
|
||||||
{
|
|
||||||
caches.Remove(summary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update the cached autocomplete candidate list when the user connects to a database
|
/// Update the cached autocomplete candidate list when the user connects to a database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info"></param>
|
/// <param name="info"></param>
|
||||||
public async Task UpdateAutoCompleteCache(ConnectionInfo info)
|
public async Task UpdateAutoCompleteCache(ConnectionInfo info)
|
||||||
{
|
{
|
||||||
if (info != null)
|
await Task.Run( () =>
|
||||||
{
|
{
|
||||||
IntellisenseCache cache;
|
if (!LanguageService.Instance.ScriptParseInfoMap.ContainsKey(info.OwnerUri))
|
||||||
lock(cachesLock)
|
|
||||||
{
|
{
|
||||||
if(!caches.TryGetValue(info.ConnectionDetails, out cache))
|
var sqlConn = info.SqlConnection as SqlConnection;
|
||||||
|
if (sqlConn != null)
|
||||||
{
|
{
|
||||||
cache = new IntellisenseCache(info.Factory, info.ConnectionDetails);
|
var srvConn = new ServerConnection(sqlConn);
|
||||||
caches[cache.DatabaseInfo] = cache;
|
var displayInfoProvider = new MetadataDisplayInfoProvider();
|
||||||
}
|
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
||||||
cache.ReferenceCount++;
|
var binder = BinderProvider.CreateBinder(metadataProvider);
|
||||||
}
|
|
||||||
|
|
||||||
await cache.UpdateCache();
|
LanguageService.Instance.ScriptParseInfoMap.Add(info.OwnerUri,
|
||||||
|
new ScriptParseInfo()
|
||||||
|
{
|
||||||
|
Binder = binder,
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
MetadataDisplayInfoProvider = displayInfoProvider
|
||||||
|
});
|
||||||
|
|
||||||
|
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(info.OwnerUri);
|
||||||
|
|
||||||
|
LanguageService.Instance.ParseAndBind(scriptFile, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the position of the previous delimeter for autocomplete token replacement.
|
||||||
|
/// SQL Parser may have similar functionality in which case we'll delete this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sql"></param>
|
||||||
|
/// <param name="startRow"></param>
|
||||||
|
/// <param name="startColumn"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(sql))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int prevLineColumns = 0;
|
||||||
|
for (int i = 0; i < startRow; ++i)
|
||||||
|
{
|
||||||
|
while (sql[prevLineColumns] != '\n' && prevLineColumns < sql.Length)
|
||||||
|
{
|
||||||
|
++prevLineColumns;
|
||||||
|
}
|
||||||
|
++prevLineColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
startColumn += prevLineColumns;
|
||||||
|
|
||||||
|
if (startColumn - 1 < sql.Length)
|
||||||
|
{
|
||||||
|
while (--startColumn >= prevLineColumns)
|
||||||
|
{
|
||||||
|
if (sql[startColumn] == ' '
|
||||||
|
|| sql[startColumn] == '\t'
|
||||||
|
|| sql[startColumn] == '\n'
|
||||||
|
|| sql[startColumn] == '.'
|
||||||
|
|| sql[startColumn] == '+'
|
||||||
|
|| sql[startColumn] == '-'
|
||||||
|
|| sql[startColumn] == '*'
|
||||||
|
|| sql[startColumn] == '>'
|
||||||
|
|| sql[startColumn] == '<'
|
||||||
|
|| sql[startColumn] == '='
|
||||||
|
|| sql[startColumn] == '/'
|
||||||
|
|| sql[startColumn] == '%')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return startColumn + 1 - prevLineColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a reparse and bind is required to provide autocomplete
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info"></param>
|
||||||
|
/// <returns>TEMP: Currently hard-coded to false for perf</returns>
|
||||||
|
private bool RequiresReparse(ScriptParseInfo info)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a list of Declaration objects to CompletionItem objects
|
||||||
|
/// since VS Code expects CompletionItems but SQL Parser works with Declarations
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="suggestions"></param>
|
||||||
|
/// <param name="cursorRow"></param>
|
||||||
|
/// <param name="cursorColumn"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private CompletionItem[] ConvertDeclarationsToCompletionItems(
|
||||||
|
IEnumerable<Declaration> suggestions,
|
||||||
|
int row,
|
||||||
|
int startColumn,
|
||||||
|
int endColumn)
|
||||||
|
{
|
||||||
|
List<CompletionItem> completions = new List<CompletionItem>();
|
||||||
|
foreach (var autoCompleteItem in suggestions)
|
||||||
|
{
|
||||||
|
// convert the completion item candidates into CompletionItems
|
||||||
|
completions.Add(new CompletionItem()
|
||||||
|
{
|
||||||
|
Label = autoCompleteItem.Title,
|
||||||
|
Kind = CompletionItemKind.Keyword,
|
||||||
|
Detail = autoCompleteItem.Title,
|
||||||
|
Documentation = autoCompleteItem.Description,
|
||||||
|
TextEdit = new TextEdit
|
||||||
|
{
|
||||||
|
NewText = autoCompleteItem.Title,
|
||||||
|
Range = new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = startColumn
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = endColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -147,22 +276,48 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// This method does not await cache builds since it expects to return quickly
|
/// This method does not await cache builds since it expects to return quickly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="textDocumentPosition"></param>
|
/// <param name="textDocumentPosition"></param>
|
||||||
public CompletionItem[] GetCompletionItems(TextDocumentPosition textDocumentPosition)
|
public CompletionItem[] GetCompletionItems(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
ScriptFile scriptFile,
|
||||||
|
ConnectionInfo connInfo)
|
||||||
{
|
{
|
||||||
// Try to find a cache for the document's backing connection (if available)
|
string filePath = textDocumentPosition.TextDocument.Uri;
|
||||||
// If we have a connection but no cache, we don't care - assuming the OnConnect and OnDisconnect listeners
|
|
||||||
// behave well, there should be a cache for any actively connected document. This also helps skip documents
|
// Take a reference to the list at a point in time in case we update and replace the list
|
||||||
// that are not backed by a SQL connection
|
if (connInfo == null
|
||||||
ConnectionInfo info;
|
|| !LanguageService.Instance.ScriptParseInfoMap.ContainsKey(textDocumentPosition.TextDocument.Uri))
|
||||||
IntellisenseCache cache;
|
|
||||||
if (ConnectionServiceInstance.TryFindConnection(textDocumentPosition.TextDocument.Uri, out info)
|
|
||||||
&& caches.TryGetValue((ConnectionSummary)info.ConnectionDetails, out cache))
|
|
||||||
{
|
{
|
||||||
return cache.GetAutoCompleteItems(textDocumentPosition).ToArray();
|
return new CompletionItem[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CompletionItem[0];
|
// reparse and bind the SQL statement if needed
|
||||||
}
|
var scriptParseInfo = LanguageService.Instance.ScriptParseInfoMap[textDocumentPosition.TextDocument.Uri];
|
||||||
|
if (RequiresReparse(scriptParseInfo))
|
||||||
|
{
|
||||||
|
LanguageService.Instance.ParseAndBind(scriptFile, connInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptParseInfo.ParseResult == null)
|
||||||
|
{
|
||||||
|
return new CompletionItem[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the completion list from SQL Parser
|
||||||
|
var suggestions = Resolver.FindCompletions(
|
||||||
|
scriptParseInfo.ParseResult,
|
||||||
|
textDocumentPosition.Position.Line + 1,
|
||||||
|
textDocumentPosition.Position.Character + 1,
|
||||||
|
scriptParseInfo.MetadataDisplayInfoProvider);
|
||||||
|
|
||||||
|
// convert the suggestion list to the VS Code format
|
||||||
|
return ConvertDeclarationsToCompletionItems(
|
||||||
|
suggestions,
|
||||||
|
textDocumentPosition.Position.Line,
|
||||||
|
PositionOfPrevDelimeter(
|
||||||
|
scriptFile.Contents,
|
||||||
|
textDocumentPosition.Position.Line,
|
||||||
|
textDocumentPosition.Position.Character),
|
||||||
|
textDocumentPosition.Position.Character);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
//
|
|
||||||
// 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.Common;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|
||||||
{
|
|
||||||
internal class IntellisenseCache
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// connection used to query for intellisense info
|
|
||||||
/// </summary>
|
|
||||||
private DbConnection connection;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Number of documents (URI's) that are using the cache for the same database.
|
|
||||||
/// The autocomplete service uses this to remove unreferenced caches.
|
|
||||||
/// </summary>
|
|
||||||
public int ReferenceCount { get; set; }
|
|
||||||
|
|
||||||
public IntellisenseCache(ISqlConnectionFactory connectionFactory, ConnectionDetails connectionDetails)
|
|
||||||
{
|
|
||||||
ReferenceCount = 0;
|
|
||||||
DatabaseInfo = connectionDetails.Clone();
|
|
||||||
|
|
||||||
// TODO error handling on this. Intellisense should catch or else the service should handle
|
|
||||||
connection = connectionFactory.CreateSqlConnection(ConnectionService.BuildConnectionString(connectionDetails));
|
|
||||||
connection.Open();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used to identify a database for which this cache is used
|
|
||||||
/// </summary>
|
|
||||||
public ConnectionSummary DatabaseInfo
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current autocomplete candidate list
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> AutoCompleteList { get; private set; }
|
|
||||||
|
|
||||||
public async Task UpdateCache()
|
|
||||||
{
|
|
||||||
DbCommand command = connection.CreateCommand();
|
|
||||||
command.CommandText = "SELECT name FROM sys.tables";
|
|
||||||
command.CommandTimeout = 15;
|
|
||||||
command.CommandType = CommandType.Text;
|
|
||||||
var reader = await command.ExecuteReaderAsync();
|
|
||||||
|
|
||||||
List<string> results = new List<string>();
|
|
||||||
while (await reader.ReadAsync())
|
|
||||||
{
|
|
||||||
results.Add(reader[0].ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoCompleteList = results;
|
|
||||||
await Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CompletionItem> GetAutoCompleteItems(TextDocumentPosition textDocumentPosition)
|
|
||||||
{
|
|
||||||
List<CompletionItem> completions = new List<CompletionItem>();
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
// Take a reference to the list at a point in time in case we update and replace the list
|
|
||||||
var suggestions = AutoCompleteList;
|
|
||||||
// the completion list will be null is user not connected to server
|
|
||||||
if (this.AutoCompleteList != null)
|
|
||||||
{
|
|
||||||
|
|
||||||
foreach (var autoCompleteItem in suggestions)
|
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.Common;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
@@ -19,6 +18,9 @@ using System.Linq;
|
|||||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
{
|
{
|
||||||
@@ -33,6 +35,17 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
private static readonly Lazy<LanguageService> instance = new Lazy<LanguageService>(() => new LanguageService());
|
private static readonly Lazy<LanguageService> instance = new Lazy<LanguageService>(() => new LanguageService());
|
||||||
|
|
||||||
|
private Lazy<Dictionary<string, ScriptParseInfo>> scriptParseInfoMap
|
||||||
|
= new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>());
|
||||||
|
|
||||||
|
internal Dictionary<string, ScriptParseInfo> ScriptParseInfoMap
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.scriptParseInfoMap.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static LanguageService Instance
|
public static LanguageService Instance
|
||||||
{
|
{
|
||||||
get { return instance.Value; }
|
get { return instance.Value; }
|
||||||
@@ -67,21 +80,20 @@ 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
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the Language Service instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceHost"></param>
|
||||||
|
/// <param name="context"></param>
|
||||||
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
|
||||||
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
|
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
|
||||||
serviceHost.SetRequestHandler(ReferencesRequest.Type, HandleReferencesRequest);
|
serviceHost.SetRequestHandler(ReferencesRequest.Type, HandleReferencesRequest);
|
||||||
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
|
|
||||||
serviceHost.SetRequestHandler(CompletionResolveRequest.Type, HandleCompletionResolveRequest);
|
serviceHost.SetRequestHandler(CompletionResolveRequest.Type, HandleCompletionResolveRequest);
|
||||||
serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
|
serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
|
||||||
serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
|
serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
|
||||||
@@ -109,21 +121,69 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
Context = context;
|
Context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the SQL text and binds it to the SMO metadata provider if connected
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="sqlText"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||||
|
{
|
||||||
|
ScriptParseInfo parseInfo = null;
|
||||||
|
if (this.ScriptParseInfoMap.ContainsKey(scriptFile.ClientFilePath))
|
||||||
|
{
|
||||||
|
parseInfo = this.ScriptParseInfoMap[scriptFile.ClientFilePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse current SQL file contents to retrieve a list of errors
|
||||||
|
ParseOptions parseOptions = new ParseOptions();
|
||||||
|
ParseResult parseResult = Parser.IncrementalParse(
|
||||||
|
scriptFile.Contents,
|
||||||
|
parseInfo != null ? parseInfo.ParseResult : null,
|
||||||
|
parseOptions);
|
||||||
|
|
||||||
|
// save previous result for next incremental parse
|
||||||
|
if (parseInfo != null)
|
||||||
|
{
|
||||||
|
parseInfo.ParseResult = parseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connInfo != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<ParseResult> parseResults = new List<ParseResult>();
|
||||||
|
parseResults.Add(parseResult);
|
||||||
|
parseInfo.Binder.Bind(
|
||||||
|
parseResults,
|
||||||
|
connInfo.ConnectionDetails.DatabaseName,
|
||||||
|
BindMode.Batch);
|
||||||
|
}
|
||||||
|
catch (ConnectionException)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||||
|
}
|
||||||
|
catch (SqlParserInternalBinderError)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResult;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of semantic diagnostic marks for the provided script file
|
/// Gets a list of semantic diagnostic marks for the provided script file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scriptFile"></param>
|
/// <param name="scriptFile"></param>
|
||||||
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
||||||
{
|
{
|
||||||
// parse current SQL file contents to retrieve a list of errors
|
ConnectionInfo connInfo;
|
||||||
ParseOptions parseOptions = new ParseOptions();
|
ConnectionService.Instance.TryFindConnection(
|
||||||
ParseResult parseResult = Parser.IncrementalParse(
|
scriptFile.ClientFilePath,
|
||||||
scriptFile.Contents,
|
out connInfo);
|
||||||
prevParseResult,
|
|
||||||
parseOptions);
|
|
||||||
|
|
||||||
// save previous result for next incremental parse
|
var parseResult = ParseAndBind(scriptFile, connInfo);
|
||||||
this.prevParseResult = parseResult;
|
|
||||||
|
|
||||||
// build a list of SQL script file markers from the errors
|
// build a list of SQL script file markers from the errors
|
||||||
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||||
@@ -169,17 +229,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
await Task.FromResult(true);
|
await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleCompletionRequest(
|
|
||||||
TextDocumentPosition textDocumentPosition,
|
|
||||||
RequestContext<CompletionItem[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleCompletionRequest");
|
|
||||||
|
|
||||||
// get the current list of completion items and return to client
|
|
||||||
var completionItems = AutoCompleteService.Instance.GetCompletionItems(textDocumentPosition);
|
|
||||||
await requestContext.SendResult(completionItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task HandleCompletionResolveRequest(
|
private static async Task HandleCompletionResolveRequest(
|
||||||
CompletionItem completionItem,
|
CompletionItem completionItem,
|
||||||
RequestContext<CompletionItem> requestContext)
|
RequestContext<CompletionItem> requestContext)
|
||||||
@@ -249,7 +298,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
await Task.FromResult(true);
|
await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles text document change events
|
/// Handles text document change events
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -449,7 +497,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
Message = scriptFileMarker.Message,
|
Message = scriptFileMarker.Message,
|
||||||
Range = new Range
|
Range = new Range
|
||||||
{
|
{
|
||||||
// TODO: What offsets should I use?
|
|
||||||
Start = new Position
|
Start = new Position
|
||||||
{
|
{
|
||||||
Line = scriptFileMarker.ScriptRegion.StartLineNumber - 1,
|
Line = scriptFileMarker.ScriptRegion.StartLineNumber - 1,
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for storing cached metadata regarding a parsed SQL file
|
||||||
|
/// </summary>
|
||||||
|
internal class ScriptParseInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the SMO binder for schema-aware intellisense
|
||||||
|
/// </summary>
|
||||||
|
public IBinder Binder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the previous SQL parse result
|
||||||
|
/// </summary>
|
||||||
|
public ParseResult ParseResult { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or set the SMO metadata provider that's bound to the current connection
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public SmoMetadataProvider MetadataProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the SMO metadata display info provider
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,9 +34,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
public string FilePath { get; private set; }
|
public string FilePath { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path which the editor client uses to identify this file.
|
/// Gets or sets the path which the editor client uses to identify this file.
|
||||||
|
/// Setter for testing purposes only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientFilePath { get; private set; }
|
public string ClientFilePath { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a boolean that determines whether
|
/// Gets or sets a boolean that determines whether
|
||||||
@@ -52,7 +53,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
public bool IsInMemory { get; private set; }
|
public bool IsInMemory { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a string containing the full contents of the file.
|
/// Gets or sets a string containing the full contents of the file.
|
||||||
|
/// Setter for testing purposes only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Contents
|
public string Contents
|
||||||
{
|
{
|
||||||
@@ -60,6 +62,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
{
|
{
|
||||||
return string.Join("\r\n", this.FileLines);
|
return string.Join("\r\n", this.FileLines);
|
||||||
}
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.FileLines = value != null ? value.Split('\n') : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -103,8 +109,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a default constructor for testing
|
/// Add a default constructor for testing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ScriptFile()
|
internal ScriptFile()
|
||||||
{
|
{
|
||||||
|
ClientFilePath = "test.sql";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -9,7 +9,12 @@
|
|||||||
"Newtonsoft.Json": "9.0.1",
|
"Newtonsoft.Json": "9.0.1",
|
||||||
"Microsoft.SqlServer.SqlParser": "140.1.5",
|
"Microsoft.SqlServer.SqlParser": "140.1.5",
|
||||||
"System.Data.Common": "4.1.0",
|
"System.Data.Common": "4.1.0",
|
||||||
"System.Data.SqlClient": "4.1.0"
|
"System.Data.SqlClient": "4.1.0",
|
||||||
|
"Microsoft.SqlServer.Smo": "140.1.5",
|
||||||
|
"System.Security.SecureString": "4.0.0",
|
||||||
|
"System.Collections.Specialized": "4.0.1",
|
||||||
|
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||||
|
"System.Diagnostics.TraceSource": "4.0.0"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"netcoreapp1.0": {
|
"netcoreapp1.0": {
|
||||||
|
|||||||
55
test/CodeCoverage/ReplaceText.vbs
Normal file
55
test/CodeCoverage/ReplaceText.vbs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
' ReplaceText.vbs
|
||||||
|
' Copied from answer at http://stackoverflow.com/questions/1115508/batch-find-and-edit-lines-in-txt-file
|
||||||
|
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
Const ForAppending = 8
|
||||||
|
Const TristateFalse = 0 ' the value for ASCII
|
||||||
|
Const Overwrite = True
|
||||||
|
|
||||||
|
Const WindowsFolder = 0
|
||||||
|
Const SystemFolder = 1
|
||||||
|
Const TemporaryFolder = 2
|
||||||
|
|
||||||
|
Dim FileSystem
|
||||||
|
Dim Filename, OldText, NewText
|
||||||
|
Dim OriginalFile, TempFile, Line
|
||||||
|
Dim TempFilename
|
||||||
|
|
||||||
|
If WScript.Arguments.Count = 3 Then
|
||||||
|
Filename = WScript.Arguments.Item(0)
|
||||||
|
OldText = WScript.Arguments.Item(1)
|
||||||
|
NewText = WScript.Arguments.Item(2)
|
||||||
|
Else
|
||||||
|
Wscript.Echo "Usage: ReplaceText.vbs <Filename> <OldText> <NewText>"
|
||||||
|
Wscript.Quit
|
||||||
|
End If
|
||||||
|
|
||||||
|
Set FileSystem = CreateObject("Scripting.FileSystemObject")
|
||||||
|
Dim tempFolder: tempFolder = FileSystem.GetSpecialFolder(TemporaryFolder)
|
||||||
|
TempFilename = FileSystem.GetTempName
|
||||||
|
|
||||||
|
If FileSystem.FileExists(TempFilename) Then
|
||||||
|
FileSystem.DeleteFile TempFilename
|
||||||
|
End If
|
||||||
|
|
||||||
|
Set TempFile = FileSystem.CreateTextFile(TempFilename, Overwrite, TristateFalse)
|
||||||
|
Set OriginalFile = FileSystem.OpenTextFile(Filename)
|
||||||
|
|
||||||
|
Do Until OriginalFile.AtEndOfStream
|
||||||
|
Line = OriginalFile.ReadLine
|
||||||
|
|
||||||
|
If InStr(Line, OldText) > 0 Then
|
||||||
|
Line = Replace(Line, OldText, NewText)
|
||||||
|
End If
|
||||||
|
|
||||||
|
TempFile.WriteLine(Line)
|
||||||
|
Loop
|
||||||
|
|
||||||
|
OriginalFile.Close
|
||||||
|
TempFile.Close
|
||||||
|
|
||||||
|
FileSystem.DeleteFile Filename
|
||||||
|
FileSystem.MoveFile TempFilename, Filename
|
||||||
|
|
||||||
|
Wscript.Quit
|
||||||
25
test/CodeCoverage/codecoverage.bat
Normal file
25
test/CodeCoverage/codecoverage.bat
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
SET WORKINGDIR=%~dp0
|
||||||
|
|
||||||
|
REM clean-up results from previous run
|
||||||
|
RMDIR %WORKINGDIR%reports\ /S /Q
|
||||||
|
DEL %WORKINGDIR%coverage.xml
|
||||||
|
MKDIR reports
|
||||||
|
|
||||||
|
REM backup current project.json
|
||||||
|
COPY /Y %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json.BAK
|
||||||
|
|
||||||
|
REM switch PDB type to Full since that is required by OpenCover for now
|
||||||
|
REM we should remove this step on OpenCover supports portable PDB
|
||||||
|
cscript /nologo ReplaceText.vbs %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json portable full
|
||||||
|
|
||||||
|
REM rebuild the SqlToolsService project
|
||||||
|
dotnet build %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json
|
||||||
|
|
||||||
|
REM run the tests through OpenCover and generate a report
|
||||||
|
"%WORKINGDIR%packages\OpenCover.4.6.519\tools\OpenCover.Console.exe" -register:user -target:dotnet.exe -targetargs:"test %WORKINGDIR%..\Microsoft.SqlTools.ServiceLayer.Test\project.json" -oldstyle -filter:"+[Microsoft.SqlTools.*]* -[xunit*]*" -output:coverage.xml -searchdirs:%WORKINGDIR%..\Microsoft.SqlTools.ServiceLayer.Test\bin\Debug\netcoreapp1.0
|
||||||
|
"%WORKINGDIR%packages\ReportGenerator.2.4.5.0\tools\ReportGenerator.exe" "-reports:coverage.xml" "-targetdir:%WORKINGDIR%\reports"
|
||||||
|
|
||||||
|
REM restore original project.json
|
||||||
|
COPY /Y %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json.BAK %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json
|
||||||
|
DEL %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json.BAK
|
||||||
|
EXIT
|
||||||
107
test/CodeCoverage/gulpfile.js
Normal file
107
test/CodeCoverage/gulpfile.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
var gulp = require('gulp');
|
||||||
|
var del = require('del');
|
||||||
|
var request = require('request');
|
||||||
|
var fs = require('fs');
|
||||||
|
var gutil = require('gulp-util');
|
||||||
|
var through = require('through2');
|
||||||
|
var cproc = require('child_process');
|
||||||
|
var os = require('os');
|
||||||
|
|
||||||
|
function nugetRestoreArgs(nupkg, options) {
|
||||||
|
var args = new Array();
|
||||||
|
if (os.platform() != 'win32') {
|
||||||
|
args.push('./nuget.exe');
|
||||||
|
}
|
||||||
|
args.push('restore');
|
||||||
|
args.push(nupkg);
|
||||||
|
|
||||||
|
var withValues = [
|
||||||
|
'source',
|
||||||
|
'configFile',
|
||||||
|
'packagesDirectory',
|
||||||
|
'solutionDirectory',
|
||||||
|
'msBuildVersion'
|
||||||
|
];
|
||||||
|
|
||||||
|
var withoutValues = [
|
||||||
|
'noCache',
|
||||||
|
'requireConsent',
|
||||||
|
'disableParallelProcessing'
|
||||||
|
];
|
||||||
|
|
||||||
|
withValues.forEach(function(prop) {
|
||||||
|
var value = options[prop];
|
||||||
|
if(value) {
|
||||||
|
args.push('-' + prop);
|
||||||
|
args.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
withoutValues.forEach(function(prop) {
|
||||||
|
var value = options[prop];
|
||||||
|
if(value) {
|
||||||
|
args.push('-' + prop);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
args.push('-noninteractive');
|
||||||
|
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
function nugetRestore(options) {
|
||||||
|
options = options || {};
|
||||||
|
options.nuget = options.nuget || './nuget.exe';
|
||||||
|
if (os.platform() != 'win32') {
|
||||||
|
options.nuget = 'mono';
|
||||||
|
}
|
||||||
|
|
||||||
|
return through.obj(function(file, encoding, done) {
|
||||||
|
var args = nugetRestoreArgs(file.path, options);
|
||||||
|
cproc.execFile(options.nuget, args, function(err, stdout) {
|
||||||
|
if (err) {
|
||||||
|
throw new gutil.PluginError('gulp-nuget', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
gutil.log(stdout.trim());
|
||||||
|
done(null, file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
gulp.task('ext:nuget-download', function(done) {
|
||||||
|
if(fs.existsSync('nuget.exe')) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
|
||||||
|
request.get('http://nuget.org/nuget.exe')
|
||||||
|
.pipe(fs.createWriteStream('nuget.exe'))
|
||||||
|
.on('close', done);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('ext:nuget-restore', function() {
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
configFile: './nuget.config',
|
||||||
|
packagesDirectory: './packages'
|
||||||
|
};
|
||||||
|
|
||||||
|
return gulp.src('./packages.config')
|
||||||
|
.pipe(nugetRestore(options));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('ext:code-coverage', function(done) {
|
||||||
|
cproc.execFile('cmd.exe', [ '/c', 'codecoverage.bat' ], function(err, stdout) {
|
||||||
|
if (err) {
|
||||||
|
throw new gutil.PluginError('ext:code-coverage', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
gutil.log(stdout.trim());
|
||||||
|
});
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('test', gulp.series('ext:nuget-download', 'ext:nuget-restore', 'ext:code-coverage'));
|
||||||
|
|
||||||
|
gulp.task('default', gulp.series('test'));
|
||||||
8
test/CodeCoverage/nuget.config
Normal file
8
test/CodeCoverage/nuget.config
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration >
|
||||||
|
<packageSources>
|
||||||
|
<add key="https://www.nuget.org/api/v2/" value="https://www.nuget.org/api/v2/" />
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
|
|
||||||
16
test/CodeCoverage/package.json
Normal file
16
test/CodeCoverage/package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "sqltoolsservice",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "SQL Tools Service Layer",
|
||||||
|
"main": "gulpfile.js",
|
||||||
|
"dependencies": {
|
||||||
|
"gulp": "github:gulpjs/gulp#4.0",
|
||||||
|
"del": "^2.2.1",
|
||||||
|
"gulp-hub": "frankwallis/gulp-hub#registry-init",
|
||||||
|
"gulp-install": "^0.6.0",
|
||||||
|
"request": "^2.73.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"author": "Microsoft",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
5
test/CodeCoverage/packages.config
Normal file
5
test/CodeCoverage/packages.config
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="OpenCover" version="4.6.519" />
|
||||||
|
<package id="ReportGenerator" version="2.4.5" />
|
||||||
|
</packages>
|
||||||
@@ -3,13 +3,26 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.Smo;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
using Microsoft.SqlTools.Test.Utility;
|
using Microsoft.SqlTools.Test.Utility;
|
||||||
@@ -137,6 +150,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
|
|
||||||
#region "Autocomplete Tests"
|
#region "Autocomplete Tests"
|
||||||
|
|
||||||
|
// This test currently requires a live database connection to initialize
|
||||||
|
// SMO connected metadata provider. Since we don't want a live DB dependency
|
||||||
|
// in the CI unit tests this scenario is currently disabled.
|
||||||
|
//[Fact]
|
||||||
|
public void AutoCompleteFindCompletions()
|
||||||
|
{
|
||||||
|
TextDocumentPosition textDocument;
|
||||||
|
ConnectionInfo connInfo;
|
||||||
|
ScriptFile scriptFile;
|
||||||
|
Common.GetAutoCompleteTestObjects(out textDocument, out scriptFile, out connInfo);
|
||||||
|
|
||||||
|
textDocument.Position.Character = 7;
|
||||||
|
scriptFile.Contents = "select ";
|
||||||
|
|
||||||
|
var autoCompleteService = AutoCompleteService.Instance;
|
||||||
|
var completions = autoCompleteService.GetCompletionItems(
|
||||||
|
textDocument,
|
||||||
|
scriptFile,
|
||||||
|
connInfo);
|
||||||
|
|
||||||
|
Assert.True(completions.Length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a mock db command that returns a predefined result set
|
/// Creates a mock db command that returns a predefined result set
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -164,160 +200,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
return connectionMock.Object;
|
return connectionMock.Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verify that the autocomplete service returns tables for the current connection as suggestions
|
|
||||||
/// </summary>
|
|
||||||
[Fact]
|
|
||||||
public void TablesAreReturnedAsAutocompleteSuggestions()
|
|
||||||
{
|
|
||||||
// Result set for the query of database tables
|
|
||||||
Dictionary<string, string>[] data =
|
|
||||||
{
|
|
||||||
new Dictionary<string, string> { {"name", "master" } },
|
|
||||||
new Dictionary<string, string> { {"name", "model" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
var mockFactory = new Mock<ISqlConnectionFactory>();
|
|
||||||
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
|
||||||
.Returns(CreateMockDbConnection(new[] {data}));
|
|
||||||
|
|
||||||
var connectionService = new ConnectionService(mockFactory.Object);
|
|
||||||
var autocompleteService = new AutoCompleteService();
|
|
||||||
autocompleteService.ConnectionServiceInstance = connectionService;
|
|
||||||
autocompleteService.InitializeService(Microsoft.SqlTools.ServiceLayer.Hosting.ServiceHost.Instance);
|
|
||||||
|
|
||||||
// Open a connection
|
|
||||||
// The cache should get updated as part of this
|
|
||||||
ConnectParams connectionRequest = TestObjects.GetTestConnectionParams();
|
|
||||||
var connectionResult = connectionService.Connect(connectionRequest);
|
|
||||||
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
||||||
|
|
||||||
// Check that there is one cache created in the auto complete service
|
|
||||||
Assert.Equal(1, autocompleteService.GetCacheCount());
|
|
||||||
|
|
||||||
// Check that we get table suggestions for an autocomplete request
|
|
||||||
TextDocumentPosition position = new TextDocumentPosition();
|
|
||||||
position.TextDocument = new TextDocumentIdentifier();
|
|
||||||
position.TextDocument.Uri = connectionRequest.OwnerUri;
|
|
||||||
position.Position = new Position();
|
|
||||||
position.Position.Line = 1;
|
|
||||||
position.Position.Character = 1;
|
|
||||||
var items = autocompleteService.GetCompletionItems(position);
|
|
||||||
Assert.Equal(2, items.Length);
|
|
||||||
Assert.Equal("master", items[0].Label);
|
|
||||||
Assert.Equal("model", items[1].Label);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verify that only one intellisense cache is created for two documents using
|
|
||||||
/// the autocomplete service when they share a common connection.
|
|
||||||
/// </summary>
|
|
||||||
[Fact]
|
|
||||||
public void OnlyOneCacheIsCreatedForTwoDocumentsWithSameConnection()
|
|
||||||
{
|
|
||||||
var connectionService = new ConnectionService(TestObjects.GetTestSqlConnectionFactory());
|
|
||||||
var autocompleteService = new AutoCompleteService();
|
|
||||||
autocompleteService.ConnectionServiceInstance = connectionService;
|
|
||||||
autocompleteService.InitializeService(Microsoft.SqlTools.ServiceLayer.Hosting.ServiceHost.Instance);
|
|
||||||
|
|
||||||
// Open two connections
|
|
||||||
ConnectParams connectionRequest1 = TestObjects.GetTestConnectionParams();
|
|
||||||
connectionRequest1.OwnerUri = "file:///my/first/file.sql";
|
|
||||||
ConnectParams connectionRequest2 = TestObjects.GetTestConnectionParams();
|
|
||||||
connectionRequest2.OwnerUri = "file:///my/second/file.sql";
|
|
||||||
var connectionResult1 = connectionService.Connect(connectionRequest1);
|
|
||||||
Assert.NotEmpty(connectionResult1.ConnectionId);
|
|
||||||
var connectionResult2 = connectionService.Connect(connectionRequest2);
|
|
||||||
Assert.NotEmpty(connectionResult2.ConnectionId);
|
|
||||||
|
|
||||||
// Verify that only one intellisense cache is created to service both URI's
|
|
||||||
Assert.Equal(1, autocompleteService.GetCacheCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verify that two different intellisense caches and corresponding autocomplete
|
|
||||||
/// suggestions are provided for two documents with different connections.
|
|
||||||
/// </summary>
|
|
||||||
[Fact]
|
|
||||||
public void TwoCachesAreCreatedForTwoDocumentsWithDifferentConnections()
|
|
||||||
{
|
|
||||||
const string testDb1 = "my_db";
|
|
||||||
const string testDb2 = "my_other_db";
|
|
||||||
|
|
||||||
// Result set for the query of database tables
|
|
||||||
Dictionary<string, string>[] data1 =
|
|
||||||
{
|
|
||||||
new Dictionary<string, string> { {"name", "master" } },
|
|
||||||
new Dictionary<string, string> { {"name", "model" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
Dictionary<string, string>[] data2 =
|
|
||||||
{
|
|
||||||
new Dictionary<string, string> { {"name", "master" } },
|
|
||||||
new Dictionary<string, string> { {"name", "my_table" } },
|
|
||||||
new Dictionary<string, string> { {"name", "my_other_table" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
var mockFactory = new Mock<ISqlConnectionFactory>();
|
|
||||||
mockFactory.Setup(factory => factory.CreateSqlConnection(It.Is<string>(x => x.Contains(testDb1))))
|
|
||||||
.Returns(CreateMockDbConnection(new[] {data1}));
|
|
||||||
mockFactory.Setup(factory => factory.CreateSqlConnection(It.Is<string>(x => x.Contains(testDb2))))
|
|
||||||
.Returns(CreateMockDbConnection(new[] {data2}));
|
|
||||||
|
|
||||||
var connectionService = new ConnectionService(mockFactory.Object);
|
|
||||||
var autocompleteService = new AutoCompleteService();
|
|
||||||
autocompleteService.ConnectionServiceInstance = connectionService;
|
|
||||||
autocompleteService.InitializeService(Microsoft.SqlTools.ServiceLayer.Hosting.ServiceHost.Instance);
|
|
||||||
|
|
||||||
// Open connections
|
|
||||||
// The cache should get updated as part of this
|
|
||||||
ConnectParams connectionRequest = TestObjects.GetTestConnectionParams();
|
|
||||||
connectionRequest.OwnerUri = "file:///my/first/sql/file.sql";
|
|
||||||
connectionRequest.Connection.DatabaseName = testDb1;
|
|
||||||
var connectionResult = connectionService.Connect(connectionRequest);
|
|
||||||
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
||||||
|
|
||||||
// Check that there is one cache created in the auto complete service
|
|
||||||
Assert.Equal(1, autocompleteService.GetCacheCount());
|
|
||||||
|
|
||||||
// Open second connection
|
|
||||||
ConnectParams connectionRequest2 = TestObjects.GetTestConnectionParams();
|
|
||||||
connectionRequest2.OwnerUri = "file:///my/second/sql/file.sql";
|
|
||||||
connectionRequest2.Connection.DatabaseName = testDb2;
|
|
||||||
var connectionResult2 = connectionService.Connect(connectionRequest2);
|
|
||||||
Assert.NotEmpty(connectionResult2.ConnectionId);
|
|
||||||
|
|
||||||
// Check that there are now two caches in the auto complete service
|
|
||||||
Assert.Equal(2, autocompleteService.GetCacheCount());
|
|
||||||
|
|
||||||
// Check that we get 2 different table suggestions for autocomplete requests
|
|
||||||
TextDocumentPosition position = new TextDocumentPosition();
|
|
||||||
position.TextDocument = new TextDocumentIdentifier();
|
|
||||||
position.TextDocument.Uri = connectionRequest.OwnerUri;
|
|
||||||
position.Position = new Position();
|
|
||||||
position.Position.Line = 1;
|
|
||||||
position.Position.Character = 1;
|
|
||||||
|
|
||||||
var items = autocompleteService.GetCompletionItems(position);
|
|
||||||
Assert.Equal(2, items.Length);
|
|
||||||
Assert.Equal("master", items[0].Label);
|
|
||||||
Assert.Equal("model", items[1].Label);
|
|
||||||
|
|
||||||
TextDocumentPosition position2 = new TextDocumentPosition();
|
|
||||||
position2.TextDocument = new TextDocumentIdentifier();
|
|
||||||
position2.TextDocument.Uri = connectionRequest2.OwnerUri;
|
|
||||||
position2.Position = new Position();
|
|
||||||
position2.Position.Line = 1;
|
|
||||||
position2.Position.Character = 1;
|
|
||||||
|
|
||||||
var items2 = autocompleteService.GetCompletionItems(position2);
|
|
||||||
Assert.Equal(3, items2.Length);
|
|
||||||
Assert.Equal("master", items2[0].Label);
|
|
||||||
Assert.Equal("my_table", items2[1].Label);
|
|
||||||
Assert.Equal("my_other_table", items2[2].Label);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,23 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Data.SqlClient;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Moq.Protected;
|
using Moq.Protected;
|
||||||
|
|
||||||
@@ -36,6 +43,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
|
|
||||||
public const int StandardColumns = 5;
|
public const int StandardColumns = 5;
|
||||||
|
|
||||||
|
public static string TestServer { get; set; }
|
||||||
|
|
||||||
|
public static string TestDatabase { get; set; }
|
||||||
|
|
||||||
|
static Common()
|
||||||
|
{
|
||||||
|
TestServer = "sqltools11";
|
||||||
|
TestDatabase = "master";
|
||||||
|
}
|
||||||
|
|
||||||
public static Dictionary<string, string>[] StandardTestData
|
public static Dictionary<string, string>[] StandardTestData
|
||||||
{
|
{
|
||||||
get { return GetTestData(StandardRows, StandardColumns); }
|
get { return GetTestData(StandardRows, StandardColumns); }
|
||||||
@@ -122,8 +139,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
{
|
{
|
||||||
UserName = "sa",
|
UserName = "sa",
|
||||||
Password = "Yukon900",
|
Password = "Yukon900",
|
||||||
DatabaseName = "AdventureWorks2016CTP3_2",
|
DatabaseName = Common.TestDatabase,
|
||||||
ServerName = "sqltools11"
|
ServerName = Common.TestServer
|
||||||
};
|
};
|
||||||
|
|
||||||
return new ConnectionInfo(CreateMockFactory(data, throwOnRead), OwnerUri, connDetails);
|
return new ConnectionInfo(CreateMockFactory(data, throwOnRead), OwnerUri, connDetails);
|
||||||
@@ -133,6 +150,45 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
|
|
||||||
#region Service Mocking
|
#region Service Mocking
|
||||||
|
|
||||||
|
public static void GetAutoCompleteTestObjects(
|
||||||
|
out TextDocumentPosition textDocument,
|
||||||
|
out ScriptFile scriptFile,
|
||||||
|
out ConnectionInfo connInfo
|
||||||
|
)
|
||||||
|
{
|
||||||
|
textDocument = new TextDocumentPosition();
|
||||||
|
textDocument.TextDocument = new TextDocumentIdentifier();
|
||||||
|
textDocument.TextDocument.Uri = Common.OwnerUri;
|
||||||
|
textDocument.Position = new Position();
|
||||||
|
textDocument.Position.Line = 0;
|
||||||
|
textDocument.Position.Character = 0;
|
||||||
|
|
||||||
|
connInfo = Common.CreateTestConnectionInfo(null, false);
|
||||||
|
|
||||||
|
var srvConn = GetServerConnection(connInfo);
|
||||||
|
var displayInfoProvider = new MetadataDisplayInfoProvider();
|
||||||
|
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
||||||
|
var binder = BinderProvider.CreateBinder(metadataProvider);
|
||||||
|
|
||||||
|
LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri,
|
||||||
|
new ScriptParseInfo()
|
||||||
|
{
|
||||||
|
Binder = binder,
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
MetadataDisplayInfoProvider = displayInfoProvider
|
||||||
|
});
|
||||||
|
|
||||||
|
scriptFile = new ScriptFile();
|
||||||
|
scriptFile.ClientFilePath = textDocument.TextDocument.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerConnection GetServerConnection(ConnectionInfo connection)
|
||||||
|
{
|
||||||
|
string connectionString = ConnectionService.BuildConnectionString(connection.ConnectionDetails);
|
||||||
|
var sqlConnection = new SqlConnection(connectionString);
|
||||||
|
return new ServerConnection(sqlConnection);
|
||||||
|
}
|
||||||
|
|
||||||
public static ConnectionDetails GetTestConnectionDetails()
|
public static ConnectionDetails GetTestConnectionDetails()
|
||||||
{
|
{
|
||||||
return new ConnectionDetails
|
return new ConnectionDetails
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
"System.Runtime.Serialization.Primitives": "4.1.1",
|
"System.Runtime.Serialization.Primitives": "4.1.1",
|
||||||
"System.Data.Common": "4.1.0",
|
"System.Data.Common": "4.1.0",
|
||||||
"System.Data.SqlClient": "4.1.0",
|
"System.Data.SqlClient": "4.1.0",
|
||||||
|
"Microsoft.SqlServer.Smo": "140.1.5",
|
||||||
|
"System.Security.SecureString": "4.0.0",
|
||||||
|
"System.Collections.Specialized": "4.0.1",
|
||||||
|
"System.ComponentModel.TypeConverter": "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",
|
||||||
"moq": "4.6.36-alpha",
|
"moq": "4.6.36-alpha",
|
||||||
|
|||||||
Reference in New Issue
Block a user