mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-19 01:25:40 -05:00
Adding star expression expansion (#1270)
This commit is contained in:
@@ -9,11 +9,15 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Metadata;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Management;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
@@ -728,5 +732,131 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
return help;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Give suggestions for sql star expansion.
|
||||
/// </summary>
|
||||
/// <param name="scriptDocumentInfo">Document info containing the current cursor position</param>
|
||||
/// <returns>Completion item array containing the expanded star suggestion</returns>
|
||||
public static CompletionItem[] ExpandSqlStarExpression(ScriptDocumentInfo scriptDocumentInfo)
|
||||
{
|
||||
//Fetching the star expression node in sql script.
|
||||
SqlSelectStarExpression selectStarExpression = AutoCompleteHelper.TryGetSelectStarStatement(scriptDocumentInfo.ScriptParseInfo.ParseResult.Script, scriptDocumentInfo);
|
||||
if (selectStarExpression == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Getting SQL object identifier for star expressions like a.*
|
||||
SqlObjectIdentifier starObjectIdentifier = null;
|
||||
if (selectStarExpression.Children.Any())
|
||||
{
|
||||
starObjectIdentifier = (SqlObjectIdentifier)selectStarExpression.Children.ElementAt(0);
|
||||
}
|
||||
|
||||
List<ITabular> boundedTableList = selectStarExpression.BoundTables.ToList();
|
||||
|
||||
IList<string> columnNames = new List<string>();
|
||||
|
||||
/*
|
||||
We include table names in 2 conditions.
|
||||
1. When there are multiple tables to avoid column ambiguity
|
||||
2. When there is single table with an alias
|
||||
*/
|
||||
bool includeTableName = boundedTableList.Count > 1 || (boundedTableList.Count == 1 && boundedTableList[0] != boundedTableList[0].Unaliased);
|
||||
|
||||
// Handing case for object identifiers where the column names will contain the identifier for eg: a.* becomes a.column_name
|
||||
if (starObjectIdentifier != null)
|
||||
{
|
||||
string objectIdentifierName = starObjectIdentifier.ObjectName.ToString();
|
||||
ITabular relatedTable = boundedTableList.Single(t => t.Name == objectIdentifierName);
|
||||
columnNames = relatedTable.Columns.Select(c => String.Format("{0}.{1}", Utils.MakeSqlBracket(objectIdentifierName), Utils.MakeSqlBracket(c.Name))).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var table in boundedTableList)
|
||||
{
|
||||
foreach (var column in table.Columns)
|
||||
{
|
||||
if (includeTableName)
|
||||
{
|
||||
columnNames.Add($"{Utils.MakeSqlBracket(table.Name)}.{Utils.MakeSqlBracket(column.Name)}"); // Including table names in case of multiple tables to avoid column ambiguity errors.
|
||||
}
|
||||
else
|
||||
{
|
||||
columnNames.Add(Utils.MakeSqlBracket(column.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (columnNames == null || columnNames.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var insertText = String.Join(String.Format(",{0}", Environment.NewLine), columnNames.ToArray()); // Adding a new line after every column name
|
||||
var completionItems = new CompletionItem[] {
|
||||
new CompletionItem
|
||||
{
|
||||
InsertText = insertText,
|
||||
Label = insertText,
|
||||
Detail = insertText,
|
||||
Kind = CompletionItemKind.Text,
|
||||
/*
|
||||
Vscode/ADS only shows completion items that match the text present in the editor. However, in case of star expansion that is never going to happen as columns names are different than '*'.
|
||||
Therefore adding an explicit filterText that contains the original star expression to trick vscode/ADS into showing this suggestion item.
|
||||
*/
|
||||
FilterText = selectStarExpression.Sql,
|
||||
Preselect = true,
|
||||
TextEdit = new TextEdit {
|
||||
NewText = insertText,
|
||||
Range = new Range {
|
||||
Start = new Position{
|
||||
Line = scriptDocumentInfo.StartLine,
|
||||
Character = selectStarExpression.StartLocation.ColumnNumber - 1
|
||||
},
|
||||
End = new Position {
|
||||
Line = scriptDocumentInfo.StartLine,
|
||||
Character = selectStarExpression.EndLocation.ColumnNumber - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
public static SqlSelectStarExpression TryGetSelectStarStatement(SqlCodeObject currentNode, ScriptDocumentInfo scriptDocumentInfo)
|
||||
{
|
||||
if(currentNode == null || scriptDocumentInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Checking if the current node is a sql select star expression.
|
||||
if (currentNode is SqlSelectStarExpression)
|
||||
{
|
||||
return currentNode as SqlSelectStarExpression;
|
||||
}
|
||||
|
||||
// Visiting children to get the the sql select star expression.
|
||||
foreach (SqlCodeObject child in currentNode.Children)
|
||||
{
|
||||
// Visiting only those children where the cursor is present.
|
||||
int childStartLineNumber = child.StartLocation.LineNumber - 1;
|
||||
int childEndLineNumber = child.EndLocation.LineNumber - 1;
|
||||
SqlSelectStarExpression childStarExpression = TryGetSelectStarStatement(child, scriptDocumentInfo);
|
||||
if ((childStartLineNumber < scriptDocumentInfo.StartLine ||
|
||||
childStartLineNumber == scriptDocumentInfo.StartLine && child.StartLocation.ColumnNumber <= scriptDocumentInfo.StartColumn) &&
|
||||
(childEndLineNumber > scriptDocumentInfo.StartLine ||
|
||||
childEndLineNumber == scriptDocumentInfo.StartLine && child.EndLocation.ColumnNumber >= scriptDocumentInfo.EndColumn) &&
|
||||
childStarExpression != null)
|
||||
{
|
||||
return childStarExpression;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// Main class for Language Service functionality including anything that requires knowledge of
|
||||
/// the language to perform, such as definitions, intellisense, etc.
|
||||
/// </summary>
|
||||
public class LanguageService: IDisposable
|
||||
public class LanguageService : IDisposable
|
||||
{
|
||||
#region Singleton Instance Implementation
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
if (workspaceServiceInstance == null)
|
||||
{
|
||||
workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance;
|
||||
workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance;
|
||||
}
|
||||
return workspaceServiceInstance;
|
||||
}
|
||||
@@ -407,7 +407,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
if (result != null && result.Errors.Count() == 0)
|
||||
{
|
||||
syntaxResult.Parseable = true;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
syntaxResult.Parseable = false;
|
||||
string[] errorMessages = new string[result.Errors.Count()];
|
||||
@@ -558,7 +559,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
};
|
||||
}
|
||||
|
||||
// turn off this code until needed (10/28/2016)
|
||||
// turn off this code until needed (10/28/2016)
|
||||
#if false
|
||||
private async Task HandleReferencesRequest(
|
||||
ReferencesParams referencesParams,
|
||||
@@ -796,19 +797,19 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = connInfo.OwnerUri});
|
||||
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = rebuildParams.OwnerUri});
|
||||
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = rebuildParams.OwnerUri });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, "Unknown error " + ex.ToString());
|
||||
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = rebuildParams.OwnerUri});
|
||||
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = rebuildParams.OwnerUri });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,14 +874,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
Validate.IsNotNull(nameof(changeParams), changeParams);
|
||||
Validate.IsNotNull(nameof(changeParams), changeParams.Uri);
|
||||
bool shouldBlock = false;
|
||||
if (SQL_LANG.Equals(changeParams.Language, StringComparison.OrdinalIgnoreCase)) {
|
||||
if (SQL_LANG.Equals(changeParams.Language, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
shouldBlock = !ServiceHost.ProviderName.Equals(changeParams.Flavor, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
if (SQL_CMD_LANG.Equals(changeParams.Language, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
shouldBlock = true; // the provider will continue to be mssql
|
||||
}
|
||||
if (shouldBlock) {
|
||||
if (shouldBlock)
|
||||
{
|
||||
this.nonMssqlUriMap.AddOrUpdate(changeParams.Uri, true, (k, oldValue) => true);
|
||||
if (CurrentWorkspace.ContainsFile(changeParams.Uri))
|
||||
{
|
||||
@@ -893,8 +896,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
this.nonMssqlUriMap.TryRemove(changeParams.Uri, out value);
|
||||
// should rebuild intellisense when re-considering as sql
|
||||
RebuildIntelliSenseParams param = new RebuildIntelliSenseParams { OwnerUri = changeParams.Uri };
|
||||
await HandleRebuildIntelliSenseNotification(param, eventContext);
|
||||
}
|
||||
await HandleRebuildIntelliSenseNotification(param, eventContext);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -939,27 +942,27 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
if (connInfo == null || !parseInfo.IsConnected)
|
||||
{
|
||||
// parse on separate thread so stack size can be increased
|
||||
var parseThread = new Thread(() =>
|
||||
// parse on separate thread so stack size can be increased
|
||||
var parseThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
// parse current SQL file contents to retrieve a list of errors
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
this.DefaultParseOptions);
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
this.DefaultParseOptions);
|
||||
|
||||
parseInfo.ParseResult = parseResult;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
parseInfo.ParseResult = parseResult;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Log the exception but don't rethrow it to prevent parsing errors from crashing SQL Tools Service
|
||||
Logger.Write(TraceEventType.Error, string.Format("An unexpected error occured while parsing: {0}", e.ToString()));
|
||||
}
|
||||
}, ConnectedBindingQueue.QueueThreadStackSize);
|
||||
parseThread.Start();
|
||||
parseThread.Join();
|
||||
}
|
||||
}, ConnectedBindingQueue.QueueThreadStackSize);
|
||||
parseThread.Start();
|
||||
parseThread.Join();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1003,7 +1006,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
return null;
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1019,7 +1022,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Write(TraceEventType.Warning, "Binding metadata lock timeout in ParseAndBind");
|
||||
Logger.Write(TraceEventType.Warning, "Binding metadata lock timeout in ParseAndBind");
|
||||
}
|
||||
|
||||
return parseInfo.ParseResult;
|
||||
@@ -1062,7 +1065,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue);
|
||||
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = info.OwnerUri});
|
||||
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1249,7 +1252,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
||||
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1656,6 +1659,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
this.currentCompletionParseInfo = scriptParseInfo;
|
||||
resultCompletionItems = result.CompletionItems;
|
||||
|
||||
// Expanding star expressions in query
|
||||
CompletionItem[] starExpansionSuggestion = AutoCompleteHelper.ExpandSqlStarExpression(scriptDocumentInfo);
|
||||
if (starExpansionSuggestion != null)
|
||||
{
|
||||
return starExpansionSuggestion;
|
||||
}
|
||||
|
||||
// if there are no completions then provide the default list
|
||||
if (resultCompletionItems == null)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Assessment" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Migration.Assessment" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Management.SqlParser"/>
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
<PackageReference Include="Microsoft.SqlServer.TransactSql.ScriptDom.NRT">
|
||||
<Aliases>ASAScriptDom</Aliases>
|
||||
|
||||
Reference in New Issue
Block a user