// // 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.IO; using System.Collections.Generic; using System.Collections.Specialized; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { /// /// Peek Definition/ Go to definition implementation /// Script sql objects and write create scripts to file /// internal class PeekDefinition { private ConnectionInfo connectionInfo; private string tempPath; internal delegate StringCollection ScriptGetter(string objectName, string schemaName); // Dictionary that holds the script getter for each type private Dictionary sqlScriptGetters = new Dictionary(); // Dictionary that holds the object name (as appears on the TSQL create statement) private Dictionary sqlObjectTypes = new Dictionary(); private Database Database { get { if (this.connectionInfo.SqlConnection != null) { Server server = new Server(this.connectionInfo.SqlConnection.DataSource); return server.Databases[this.connectionInfo.SqlConnection.Database]; } return null; } } internal PeekDefinition(ConnectionInfo connInfo) { this.connectionInfo = connInfo; DirectoryInfo tempScriptDirectory = Directory.CreateDirectory(Path.GetTempPath() + "mssql_definition"); this.tempPath = tempScriptDirectory.FullName; Initialize(); } /// /// Add getters for each sql object supported by peek definition /// private void Initialize() { //Add script getters for each sql object //Add tables to supported types AddSupportedType(DeclarationType.Table, GetTableScripts, "Table"); //Add views to supported types AddSupportedType(DeclarationType.View, GetViewScripts, "view"); //Add stored procedures to supported types AddSupportedType(DeclarationType.StoredProcedure, GetStoredProcedureScripts, "Procedure"); } /// /// Add the given type, scriptgetter and the typeName string to the respective dictionaries /// private void AddSupportedType(DeclarationType type, ScriptGetter scriptGetter, string typeName) { sqlScriptGetters.Add(type, scriptGetter); sqlObjectTypes.Add(type, typeName); } /// /// Convert a file to a location array containing a location object as expected by the extension /// private Location[] GetLocationFromFile(string tempFileName, int lineNumber) { Location[] locations = new[] { new Location { Uri = new Uri(tempFileName).AbsoluteUri, Range = new Range { Start = new Position { Line = lineNumber, Character = 1}, End = new Position { Line = lineNumber + 1, Character = 1} } } }; return locations; } /// /// Get line number for the create statement /// private int GetStartOfCreate(string script, string createString) { string[] lines = script.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++) { if (lines[lineNumber].IndexOf( createString, StringComparison.OrdinalIgnoreCase) >= 0) { return lineNumber; } } return 0; } /// /// Get the script of the selected token based on the type of the token /// /// /// /// /// Location object of the script file internal Location[] GetScript(IEnumerable declarationItems, string tokenText, string schemaName) { foreach (Declaration declarationItem in declarationItems) { if (declarationItem.Title == null) { continue; } if (declarationItem.Title.Equals(tokenText)) { // Script object using SMO based on type DeclarationType type = declarationItem.Type; if (sqlScriptGetters.ContainsKey(type) && sqlObjectTypes.ContainsKey(type)) { return GetSqlObjectDefinition( sqlScriptGetters[type], tokenText, schemaName, sqlObjectTypes[type] ); } return null; } } return null; } /// /// Script a table using SMO /// /// Table name /// Schema name /// String collection of scripts internal StringCollection GetTableScripts(string tableName, string schemaName) { return (schemaName != null) ? Database?.Tables[tableName, schemaName]?.Script() : Database?.Tables[tableName]?.Script(); } /// /// Script a view using SMO /// /// View name /// Schema name /// String collection of scripts internal StringCollection GetViewScripts(string viewName, string schemaName) { return (schemaName != null) ? Database?.Views[viewName, schemaName]?.Script() : Database?.Views[viewName]?.Script(); } /// /// Script a stored procedure using SMO /// /// Stored Procedure name /// Schema Name /// String collection of scripts internal StringCollection GetStoredProcedureScripts(string viewName, string schemaName) { return (schemaName != null) ? Database?.StoredProcedures[viewName, schemaName]?.Script() : Database?.StoredProcedures[viewName]?.Script(); } /// /// Script a object using SMO and write to a file. /// /// Function that returns the SMO scripts for an object /// SQL object name /// Schema name or null /// Type of SQL object /// Location object representing URI and range of the script file internal Location[] GetSqlObjectDefinition( ScriptGetter sqlScriptGetter, string objectName, string schemaName, string objectType) { StringCollection scripts = sqlScriptGetter(objectName, schemaName); string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName)) : Path.Combine(this.tempPath, string.Format("{0}.sql", objectName)); if (scripts != null) { int lineNumber = 0; using (StreamWriter scriptFile = new StreamWriter(File.Open(tempFileName, FileMode.Create, FileAccess.ReadWrite))) { foreach (string script in scripts) { string createSyntax = string.Format("CREATE {0}", objectType); if (script.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0) { scriptFile.WriteLine(script); lineNumber = GetStartOfCreate(script, createSyntax); } } } return GetLocationFromFile(tempFileName, lineNumber); } return null; } } }