diff --git a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs index 44b71b9a..6a1bbdbd 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Threading.Tasks; using Microsoft.SqlTools.Credentials; using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Hosting; @@ -112,12 +113,23 @@ namespace Microsoft.SqlTools.ServiceLayer provider.RegisterSingleService(service.ServiceType, service); } + ServiceHost serviceHost = host as ServiceHost; foreach (IHostedService service in provider.GetServices()) { // Initialize all hosted services, and register them in the service provider for their requested // service type. This ensures that when searching for the ConnectionService you can get it without // searching for an IHostedService of type ConnectionService service.InitializeService(host); + + IDisposable disposable = service as IDisposable; + if (serviceHost != null && disposable != null) + { + serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) => + { + disposable.Dispose(); + await Task.FromResult(0); + }); + } } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs index 9db6faf4..4d8be8ce 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs @@ -46,7 +46,17 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes // some nodes may need to set it NodeValue = value; } - + + private object buildingMetadataLock = new object(); + + /// + /// Event which tells if MetadataProvider is built fully or not + /// + public object BuildingMetadataLock + { + get { return this.buildingMetadataLock; } + } + /// /// Value describing this node /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs index 2f360cfc..ee9d3d5f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs @@ -17,6 +17,7 @@ using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel; @@ -32,16 +33,19 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer /// The APIs used for this are modeled closely on the VSCode TreeExplorerNodeProvider API. /// [Export(typeof(IHostedService))] - public class ObjectExplorerService : HostedService, IComposableService, IHostedService + public class ObjectExplorerService : HostedService, IComposableService, IHostedService, IDisposable { internal const string uriPrefix = "objectexplorer://"; - + // Instance of the connection service, used to get the connection info for a given owner URI private ConnectionService connectionService; private IProtocolEndpoint serviceHost; private ConcurrentDictionary sessionMap; private readonly Lazy>> applicableNodeChildFactories; private IMultiServiceProvider serviceProvider; + private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue(); + private const int PrepopulateBindTimeout = 10000; + /// /// This timeout limits the amount of time that object explorer tasks can take to complete @@ -83,8 +87,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer { return new ReadOnlyCollection(sessionMap.Keys.ToList()); } - } - + } + /// /// As an , this will be set whenever the service is initialized /// via an @@ -198,7 +202,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer return true; } }; - await HandleRequestAsync(expandNode, context, "HandleExpandRequest"); + await HandleRequestAsync(expandNode, context, "HandleExpandRequest"); } internal async Task HandleRefreshRequest(RefreshParams refreshParams, RequestContext context) @@ -280,7 +284,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer } } - private void RunCreateSessionTask(ConnectionDetails connectionDetails, string uri) + private void RunCreateSessionTask(ConnectionDetails connectionDetails, string uri) { Logger.Write(LogLevel.Normal, "Creating OE session"); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); @@ -288,9 +292,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer { Task task = CreateSessionAsync(connectionDetails, uri, cancellationTokenSource.Token); CreateSessionTask = task; - Task.Run(async () => + Task.Run(async () => { - ObjectExplorerTaskResult result = await RunTaskWithTimeout(task, + ObjectExplorerTaskResult result = await RunTaskWithTimeout(task, settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout); if (result != null && !result.IsCompleted) @@ -338,7 +342,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer RootNode = session.Root.ToNodeInfo(), SessionId = uri, ErrorMessage = session.ErrorMessage - + }; await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response); return response; @@ -351,21 +355,54 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer { return await Task.Factory.StartNew(() => { - NodeInfo[] nodes = null; - TreeNode node = session.Root.FindNodeByPath(nodePath); - if(node != null) + return QueueExpandNodeRequest(session, nodePath, forceRefresh); + }); + } + internal ExpandResponse QueueExpandNodeRequest(ObjectExplorerSession session, string nodePath, bool forceRefresh = false) + { + NodeInfo[] nodes = null; + TreeNode node = session.Root.FindNodeByPath(nodePath); + ExpandResponse response = new ExpandResponse { Nodes = new NodeInfo[] { }, ErrorMessage = node.ErrorMessage, SessionId = session.Uri, NodePath = nodePath }; + + if (node != null && Monitor.TryEnter(node.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) + { + try { - if (forceRefresh) + QueueItem queueItem = bindingQueue.QueueBindingOperation( + key: session.Uri, + bindingTimeout: PrepopulateBindTimeout, + waitForLockTimeout: PrepopulateBindTimeout, + bindOperation: (bindingContext, cancelToken) => + { + + if (forceRefresh) + { + nodes = node.Refresh().Select(x => x.ToNodeInfo()).ToArray(); + } + else + { + nodes = node.Expand().Select(x => x.ToNodeInfo()).ToArray(); + } + response.Nodes = nodes; + response.ErrorMessage = node.ErrorMessage; + return response; + }); + + queueItem.ItemProcessed.WaitOne(); + if (queueItem.GetResultAsT() != null) { - nodes = node.Refresh().Select(x => x.ToNodeInfo()).ToArray(); - } - else - { - nodes = node.Expand().Select(x => x.ToNodeInfo()).ToArray(); + response = queueItem.GetResultAsT(); } } - return new ExpandResponse { Nodes = nodes, ErrorMessage = node.ErrorMessage, SessionId = session.Uri, NodePath = nodePath }; - }); + catch + { + } + finally + { + Monitor.Exit(node.BuildingMetadataLock); + } + } + return response; } /// @@ -590,6 +627,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer public Exception Exception { get; set; } } + public void Dispose() + { + if (bindingQueue != null) + { + bindingQueue.Dispose(); + } + } + internal class ObjectExplorerSession { private ConnectionService connectionService; diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoColumnCustomNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoColumnCustomNode.cs index a5f561d4..ed7b4274 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoColumnCustomNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoColumnCustomNode.cs @@ -128,6 +128,15 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel } typeName += ")"; break; + case SqlDataType.Numeric: + case SqlDataType.Decimal: + typeName += $"({dataType.NumericPrecision},{dataType.NumericScale})"; + break; + case SqlDataType.DateTime2: + case SqlDataType.Time: + case SqlDataType.DateTimeOffset: + typeName += $"({dataType.NumericScale})"; + break; } } return typeName; diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTableCustomNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTableCustomNode.cs index 7b9032b8..08710c8e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTableCustomNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTableCustomNode.cs @@ -29,6 +29,26 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel return string.Empty; } + + public override string GetNodeSubType(object context) + { + try + { + Table table = context as Table; + if (table != null && table.TemporalType != TableTemporalType.None) + { + return "Temporal"; + } + // return string.Empty; + + } + catch + { + //Ignore the exception and just not change create custom name + } + + return string.Empty; + } } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/TreeNodeDefinition.xml b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/TreeNodeDefinition.xml index d307a9b5..b1bfc31a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/TreeNodeDefinition.xml +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/TreeNodeDefinition.xml @@ -77,6 +77,7 @@ +