// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.Data.Common; using System.IO; using System.Linq; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts; using Microsoft.SqlTools.Utility; using ConnectionType = Microsoft.SqlTools.ServiceLayer.Connection.ConnectionType; using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location; using Microsoft.SqlServer.Management.Sdk.Sfc; using System.Text; using System.Data; using System.Diagnostics; using Range = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Range; namespace Microsoft.SqlTools.ServiceLayer.Scripting { internal partial class Scripter { private bool error; private string errorMessage; private ServerConnection serverConnection; private ConnectionInfo connectionInfo; private Database database; private string tempPath; // Dictionary that holds the object name (as appears on the TSQL create statement) private Dictionary sqlObjectTypes = new Dictionary(); private Dictionary sqlObjectTypesFromQuickInfo = new Dictionary(); private Dictionary targetDatabaseEngineEditionMap = new Dictionary(); private Dictionary serverVersionMap = new Dictionary(); private Dictionary objectScriptMap = new Dictionary(); internal Scripter() {} /// /// Initialize a Peek Definition helper object /// /// SMO Server connection internal Scripter(ServerConnection serverConnection, ConnectionInfo connInfo) { this.serverConnection = serverConnection; this.connectionInfo = connInfo; this.tempPath = FileUtilities.GetPeekDefinitionTempFolder(); Initialize(); } internal Database Database { get { if (this.database == null) { if (this.serverConnection != null && !string.IsNullOrEmpty(this.serverConnection.DatabaseName)) { try { // Reuse existing connection Server server = new Server(this.serverConnection); // The default database name is the database name of the server connection string dbName = this.serverConnection.DatabaseName; if (this.connectionInfo != null) { // If there is a query DbConnection, use that connection to get the database name // This is preferred since it has the most current database name (in case of database switching) DbConnection connection; if (connectionInfo.TryGetConnection(ConnectionType.Query, out connection)) { if (!string.IsNullOrEmpty(connection.Database)) { dbName = connection.Database; } } } this.database = new Database(server, dbName); this.database.Refresh(); } catch (ConnectionFailureException cfe) { Logger.Write(TraceEventType.Error, "Exception at PeekDefinition Database.get() : " + cfe.Message); this.error = true; this.errorMessage = (connectionInfo != null && connectionInfo.IsCloud) ? SR.PeekDefinitionAzureError(cfe.Message) : SR.PeekDefinitionError(cfe.Message); return null; } catch (Exception ex) { Logger.Write(TraceEventType.Error, "Exception at PeekDefinition Database.get() : " + ex.Message); this.error = true; this.errorMessage = SR.PeekDefinitionError(ex.Message); return null; } } } return this.database; } } /// /// Add the given type, scriptgetter and the typeName string to the respective dictionaries /// private void AddSupportedType(DeclarationType type, string typeName, string quickInfoType, Type smoObjectType) { sqlObjectTypes.Add(type, typeName); if (!string.IsNullOrEmpty(quickInfoType)) { sqlObjectTypesFromQuickInfo.Add(quickInfoType.ToLowerInvariant(), typeName); } } /// /// Get the script of the selected token based on the type of the token /// /// /// /// /// Location object of the script file internal DefinitionResult GetScript(ParseResult parseResult, Position position, IMetadataDisplayInfoProvider metadataDisplayInfoProvider, string tokenText, string schemaName) { int parserLine = position.Line; int parserColumn = position.Character; // Get DeclarationItems from The Intellisense Resolver for the selected token. The type of the selected token is extracted from the declarationItem. IEnumerable declarationItems = GetCompletionsForToken(parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); if (declarationItems != null && declarationItems.Count() > 0) { foreach (Declaration declarationItem in declarationItems) { if (declarationItem.Title == null) { continue; } if (this.Database == null) { return GetDefinitionErrorResult(SR.PeekDefinitionDatabaseError); } StringComparison caseSensitivity = this.Database.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; // if declarationItem matches the selected token, script SMO using that type if (declarationItem.Title.Equals(tokenText, caseSensitivity)) { return GetDefinitionUsingDeclarationType(declarationItem.Type, declarationItem.DatabaseQualifiedName, tokenText, schemaName); } } } else { // if no declarationItem matched the selected token, we try to find the type of the token using QuickInfo.Text string quickInfoText = GetQuickInfoForToken(parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); return GetDefinitionUsingQuickInfoText(quickInfoText, tokenText, schemaName); } // no definition found return GetDefinitionErrorResult(SR.PeekDefinitionNoResultsError); } /// /// Script an object using the type extracted from quickInfo Text /// /// the text from the quickInfo for the selected token /// The text of the selected token /// Schema name /// internal DefinitionResult GetDefinitionUsingQuickInfoText(string quickInfoText, string tokenText, string schemaName) { if (this.Database == null) { return GetDefinitionErrorResult(SR.PeekDefinitionDatabaseError); } StringComparison caseSensitivity = this.Database.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; string tokenType = GetTokenTypeFromQuickInfo(quickInfoText, tokenText, caseSensitivity); if (tokenType != null) { if (sqlObjectTypesFromQuickInfo.ContainsKey(tokenType.ToLowerInvariant())) { // With SqlLogin authentication, the defaultSchema property throws an Exception when accessed. // This workaround ensures that a schema name is present by attempting // to get the schema name from the declaration item. // If all fails, the default schema name is assumed to be "dbo" if ((connectionInfo != null && connectionInfo.ConnectionDetails.AuthenticationType.Equals(Constants.SqlLoginAuthenticationType)) && string.IsNullOrEmpty(schemaName)) { string fullObjectName = this.GetFullObjectNameFromQuickInfo(quickInfoText, tokenText, caseSensitivity); schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); } Location[] locations = GetSqlObjectDefinition( tokenText, schemaName, sqlObjectTypesFromQuickInfo[tokenType.ToLowerInvariant()] ); DefinitionResult result = new DefinitionResult { IsErrorResult = this.error, Message = this.errorMessage, Locations = locations }; return result; } else { // If a type was found but is not in sqlScriptGettersFromQuickInfo, then the type is not supported return GetDefinitionErrorResult(SR.PeekDefinitionTypeNotSupportedError); } } // no definition found return GetDefinitionErrorResult(SR.PeekDefinitionNoResultsError); } /// /// Script a object using the type extracted from declarationItem /// /// The Declaration object that matched with the selected token /// The text of the selected token /// Schema name /// internal DefinitionResult GetDefinitionUsingDeclarationType(DeclarationType type, string databaseQualifiedName, string tokenText, string schemaName) { if (sqlObjectTypes.ContainsKey(type)) { // With SqlLogin authentication, the defaultSchema property throws an Exception when accessed. // This workaround ensures that a schema name is present by attempting // to get the schema name from the declaration item. // If all fails, the default schema name is assumed to be "dbo" if ((connectionInfo != null && connectionInfo.ConnectionDetails.AuthenticationType.Equals(Constants.SqlLoginAuthenticationType)) && string.IsNullOrEmpty(schemaName)) { string fullObjectName = databaseQualifiedName; schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); } Location[] locations = GetSqlObjectDefinition( tokenText, schemaName, sqlObjectTypes[type] ); DefinitionResult result = new DefinitionResult { IsErrorResult = this.error, Message = this.errorMessage, Locations = locations }; return result; } // If a type was found but is not in sqlScriptGetters, then the type is not supported return GetDefinitionErrorResult(SR.PeekDefinitionTypeNotSupportedError); } /// /// 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( string objectName, string schemaName, string objectType) { // script file destination string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName)) : Path.Combine(this.tempPath, string.Format("{0}.sql", objectName)); SmoScriptingOperation operation = InitScriptOperation(objectName, schemaName, objectType); operation.Execute(); string script = operation.ScriptText; bool objectFound = false; int createStatementLineNumber = 0; File.WriteAllText(tempFileName, script); string[] lines = File.ReadAllLines(tempFileName); int lineCount = 0; string createSyntax = null; if (objectScriptMap.ContainsKey(objectType.ToLower())) { createSyntax = string.Format("CREATE"); foreach (string line in lines) { if (LineContainsObject(line, objectName, createSyntax)) { createStatementLineNumber = lineCount; objectFound = true; break; } lineCount++; } } if (objectFound) { Location[] locations = GetLocationFromFile(tempFileName, createStatementLineNumber); return locations; } else { this.error = true; this.errorMessage = SR.PeekDefinitionNoResultsError; return null; } } #region Helper Methods /// /// Return schema name from the full name of the database. If schema is missing return dbo as schema name. /// /// The full database qualified name(database.schema.object) /// Object name /// Schema name internal string GetSchemaFromDatabaseQualifiedName(string fullObjectName, string objectName) { if (!string.IsNullOrEmpty(fullObjectName)) { string[] tokens = fullObjectName.Split('.'); for (int i = tokens.Length - 1; i > 0; i--) { if (tokens[i].Equals(objectName)) { return tokens[i - 1]; } } } return "dbo"; } /// /// Convert a file to a location array containing a location object as expected by the extension /// internal Location[] GetLocationFromFile(string tempFileName, int lineNumber) { // Get absolute Uri based on uri format. This works around a dotnetcore URI bug for linux paths. if (Path.DirectorySeparatorChar.Equals('/')) { tempFileName = "file:" + tempFileName; } else { tempFileName = new Uri(tempFileName).AbsoluteUri; } // Create a location array containing the tempFile Uri, as expected by VSCode. Location[] locations = new[] { new Location { Uri = tempFileName, Range = new Range { Start = new Position { Line = lineNumber, Character = 0}, End = new Position { Line = lineNumber + 1, Character = 0} } } }; return locations; } /// /// Helper method to create definition error result object /// /// Error message /// DefinitionResult internal DefinitionResult GetDefinitionErrorResult(string errorMessage) { return new DefinitionResult { IsErrorResult = true, Message = errorMessage, Locations = null }; } /// /// Return full object name(database.schema.objectName) from the quickInfo text("type database.schema.objectName") /// /// QuickInfo Text for this token /// Token Text /// StringComparison enum /// internal string GetFullObjectNameFromQuickInfo(string quickInfoText, string tokenText, StringComparison caseSensitivity) { if (string.IsNullOrEmpty(quickInfoText) || string.IsNullOrEmpty(tokenText)) { return null; } // extract full object name from quickInfo text string[] tokens = quickInfoText.Split(' '); List tokenList = tokens.Where(el => el.IndexOf(tokenText, caseSensitivity) >= 0).ToList(); return (tokenList?.Count() > 0) ? tokenList[0] : null; } /// /// Return token type from the quickInfo text("type database.schema.objectName") /// /// QuickInfo Text for this token /// /// StringComparison enum /// internal string GetTokenTypeFromQuickInfo(string quickInfoText, string tokenText, StringComparison caseSensitivity) { if (string.IsNullOrEmpty(quickInfoText) || string.IsNullOrEmpty(tokenText)) { return null; } // extract string denoting the token type from quickInfo text string[] tokens = quickInfoText.Split(' '); List indexList = tokens.Select((s, i) => new { i, s }).Where(el => (el.s).IndexOf(tokenText, caseSensitivity) >= 0).Select(el => el.i).ToList(); return (indexList?.Count() > 0) ? String.Join(" ", tokens.Take(indexList[0])) : null; } /// /// Wrapper method that calls Resolver.GetQuickInfo /// internal string GetQuickInfoForToken(ParseResult parseResult, int parserLine, int parserColumn, IMetadataDisplayInfoProvider metadataDisplayInfoProvider) { if (parseResult == null || metadataDisplayInfoProvider == null) { return null; } Babel.CodeObjectQuickInfo quickInfo = Resolver.GetQuickInfo( parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); return quickInfo?.Text; } /// /// Wrapper method that calls Resolver.FindCompletions /// /// /// /// /// /// internal IEnumerable GetCompletionsForToken(ParseResult parseResult, int parserLine, int parserColumn, IMetadataDisplayInfoProvider metadataDisplayInfoProvider) { if (parseResult == null || metadataDisplayInfoProvider == null) { return null; } return Resolver.FindCompletions( parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); } /// /// Wrapper method that calls Resolver.FindCompletions /// /// /// /// /// /// internal SmoScriptingOperation InitScriptOperation(string objectName, string schemaName, string objectType) { // object that has to be scripted ScriptingObject scriptingObject = new ScriptingObject { Name = objectName, Schema = schemaName, Type = objectType }; // scripting options ScriptOptions options = new ScriptOptions { ScriptCreateDrop = "ScriptCreate", TypeOfDataToScript = "SchemaOnly", ScriptStatistics = "ScriptStatsNone", TargetDatabaseEngineEdition = GetTargetDatabaseEngineEdition(), TargetDatabaseEngineType = GetTargetDatabaseEngineType(), ScriptCompatibilityOption = GetScriptCompatibilityOption(), ScriptExtendedProperties = false, ScriptUseDatabase = false, IncludeIfNotExists = false, GenerateScriptForDependentObjects = false, IncludeDescriptiveHeaders = false, ScriptCheckConstraints = false, ScriptChangeTracking = false, ScriptDataCompressionOptions = false, ScriptForeignKeys = false, ScriptFullTextIndexes = false, ScriptIndexes = false, ScriptPrimaryKeys = false, ScriptTriggers = false, UniqueKeys = false }; List objectList = new List(); objectList.Add(scriptingObject); // create parameters for the scripting operation ScriptingParams parameters = new ScriptingParams { ConnectionString = ConnectionService.BuildConnectionString(this.connectionInfo.ConnectionDetails), ScriptingObjects = objectList, ScriptOptions = options, ScriptDestination = "ToEditor" }; return new ScriptAsScriptingOperation(parameters, serverConnection); } internal string GetTargetDatabaseEngineEdition() { DatabaseEngineEdition dbEngineEdition = this.serverConnection.DatabaseEngineEdition; string dbEngineEditionString; targetDatabaseEngineEditionMap.TryGetValue(dbEngineEdition, out dbEngineEditionString); return (dbEngineEditionString != null) ? dbEngineEditionString : "SqlServerEnterpriseEdition"; } internal string GetScriptCompatibilityOption() { int serverVersion = this.serverConnection.ServerVersion.Major; string dbEngineTypeString = serverVersionMap[serverVersion]; return (dbEngineTypeString != null) ? dbEngineTypeString : "Script140Compat"; } internal string GetTargetDatabaseEngineType() { return connectionInfo.IsCloud ? "SqlAzure" : "SingleInstance"; } internal bool LineContainsObject(string line, string objectName, string createSyntax) { if (line.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0 && line.IndexOf(objectName, StringComparison.OrdinalIgnoreCase) >=0) { return true; } return false; } internal static class ScriptingGlobals { /// /// Left delimiter for an named object /// public const char LeftDelimiter = '['; /// /// right delimiter for a named object /// public const char RightDelimiter = ']'; } internal static class ScriptingUtils { /// /// Quote the name of a given sql object. /// /// object /// quoted object name internal static string QuoteObjectName(string sqlObject) { return QuoteObjectName(sqlObject, ']'); } /// /// Quotes the name of a given sql object /// /// object /// quote to use /// internal static string QuoteObjectName(string sqlObject, char quote) { int len = sqlObject.Length; StringBuilder result = new StringBuilder(sqlObject.Length); for (int i = 0; i < len; i++) { if (sqlObject[i] == quote) { result.Append(quote); } result.Append(sqlObject[i]); } return result.ToString(); } /// /// Returns the value whether the server supports XTP or not s internal static bool IsXTPSupportedOnServer(Server server) { bool isXTPSupported = false; if (server.ConnectionContext.ExecuteScalar("SELECT SERVERPROPERTY('IsXTPSupported')") != DBNull.Value) { isXTPSupported = server.IsXTPSupported; } return isXTPSupported; } } internal static string SelectAllValuesFromTransmissionQueue(Urn urn) { string script = string.Empty; StringBuilder selectQuery = new StringBuilder(); /* SELECT TOP *, casted_message_body = CASE MESSAGE_TYPE_NAME WHEN 'X' THEN CAST(MESSAGE_BODY AS NVARCHAR(MAX)) ELSE MESSAGE_BODY END FROM [new].[sys].[transmission_queue] */ selectQuery.Append("SELECT TOP (1000) "); selectQuery.Append("*, casted_message_body = \r\nCASE message_type_name WHEN 'X' \r\n THEN CAST(message_body AS NVARCHAR(MAX)) \r\n ELSE message_body \r\nEND \r\n"); // from clause selectQuery.Append("FROM "); Urn dbUrn = urn; // database while (dbUrn.Parent != null && dbUrn.Type != "Database") { dbUrn = dbUrn.Parent; } selectQuery.AppendFormat("{0}{1}{2}", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(dbUrn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); //SYS selectQuery.AppendFormat(".{0}sys{1}", ScriptingGlobals.LeftDelimiter, ScriptingGlobals.RightDelimiter); //TRANSMISSION QUEUE selectQuery.AppendFormat(".{0}transmission_queue{1}", ScriptingGlobals.LeftDelimiter, ScriptingGlobals.RightDelimiter); script = selectQuery.ToString(); return script; } internal static string SelectAllValues(Urn urn) { string script = string.Empty; StringBuilder selectQuery = new StringBuilder(); selectQuery.Append("SELECT TOP (1000) "); selectQuery.Append("*, casted_message_body = \r\nCASE message_type_name WHEN 'X' \r\n THEN CAST(message_body AS NVARCHAR(MAX)) \r\n ELSE message_body \r\nEND \r\n"); // from clause selectQuery.Append("FROM "); Urn dbUrn = urn; // database while (dbUrn.Parent != null && dbUrn.Type != "Database") { dbUrn = dbUrn.Parent; } selectQuery.AppendFormat("{0}{1}{2}", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(dbUrn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); // schema selectQuery.AppendFormat(".{0}{1}{2}", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(urn.GetAttribute("Schema"), ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); // object selectQuery.AppendFormat(".{0}{1}{2}", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(urn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); //Adding no lock in the end. selectQuery.AppendFormat(" WITH(NOLOCK)"); script = selectQuery.ToString(); return script; } internal DataTable GetColumnNames(Server server, Urn urn, bool isDw) { List filterExpressions = new List(); if (server.Version.Major >= 10) { // We don't have to include sparce columns as all the sparce columns data. // Can be obtain from column set columns. filterExpressions.Add("@IsSparse=0"); } // Check if we're called for EDIT for SQL2016+/Sterling+. // We need to omit temporal columns if such are present on this table. if (server.Version.Major >= 13 || (DatabaseEngineType.SqlAzureDatabase == server.DatabaseEngineType && server.Version.Major >= 12)) { // We're called in order to generate a list of columns for EDIT TOP N rows. // Don't return auto-generated, auto-populated, read-only temporal columns. filterExpressions.Add("@GeneratedAlwaysType=0"); } // Check if we're called for EDIT for SQL2022+/Sterling+. // We need to omit dropped ledger columns if such are present if (server.Version.Major >= 16 || (DatabaseEngineType.SqlAzureDatabase == server.DatabaseEngineType && server.Version.Major >= 12)) { filterExpressions.Add("@IsDroppedLedgerColumn=0"); } // Check if we're called for SQL2017/Sterling+. // We need to omit graph internal columns if such are present on this table. if (server.Version.Major >= 14 || (DatabaseEngineType.SqlAzureDatabase == server.DatabaseEngineType && !isDw)) { // from Smo.GraphType: // 0 = None // 1 = GraphId // 2 = GraphIdComputed // 3 = GraphFromId // 4 = GraphFromObjId // 5 = GraphFromIdComputed // 6 = GraphToId // 7 = GraphToObjId // 8 = GraphToIdComputed // // We only want to show types 0, 2, 5, and 8: filterExpressions.Add("(@GraphType=0 or @GraphType=2 or @GraphType=5 or @GraphType=8)"); } Request request = new Request(); // If we have any filters on the columns, add them. if (filterExpressions.Count > 0) { request.Urn = String.Format("{0}/Column[{1}]", urn.ToString(), string.Join(" and ", filterExpressions.ToArray())); } else { request.Urn = String.Format("{0}/Column", urn.ToString()); } request.Fields = new String[] { "Name" }; // get the columns in the order they were created OrderBy order = new OrderBy(); order.Dir = OrderBy.Direction.Asc; order.Field = "ID"; request.OrderByList = new OrderBy[] { order }; Enumerator en = new Enumerator(); // perform the query. DataTable dt = null; EnumResult result = en.Process(server.ConnectionContext, request); if (result.Type == ResultType.DataTable) { dt = result; } else { dt = ((DataSet)result).Tables[0]; } return dt; } internal string SelectFromTableOrView(Server server, Urn urn, bool isDw) { DataTable dt = GetColumnNames(server, urn, isDw); StringBuilder selectQuery = new StringBuilder(); // build the first line if (dt != null && dt.Rows.Count > 0) { selectQuery.Append("SELECT TOP (1000) "); // first column selectQuery.AppendFormat("{0}{1}{2}\r\n", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(dt.Rows[0][0] as string, ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); // add all other columns on separate lines. Make the names align. for (int i = 1; i < dt.Rows.Count; i++) { selectQuery.AppendFormat(" ,{0}{1}{2}\r\n", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(dt.Rows[i][0] as string, ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); } } else { selectQuery.Append("SELECT TOP (1000) * "); } // from clause selectQuery.Append(" FROM "); if(server.ServerType != DatabaseEngineType.SqlAzureDatabase) { // Azure doesn't allow qualifying object names with the DB, so only add it on if we're not in Azure database URN Urn dbUrn = urn.Parent; selectQuery.AppendFormat("{0}{1}{2}.", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(dbUrn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); } // schema selectQuery.AppendFormat("{0}{1}{2}.", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(urn.GetAttribute("Schema"), ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); // object selectQuery.AppendFormat("{0}{1}{2}", ScriptingGlobals.LeftDelimiter, ScriptingUtils.QuoteObjectName(urn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter), ScriptingGlobals.RightDelimiter); // In Hekaton M5, if it's a memory optimized table, we need to provide SNAPSHOT hint for SELECT. if (urn.Type.Equals("Table") && ScriptingUtils.IsXTPSupportedOnServer(server)) { try { Table table = (Table)server.GetSmoObject(urn); table.Refresh(); if (table.IsMemoryOptimized) { selectQuery.Append(" WITH (SNAPSHOT)"); } } catch (Exception ex) { // log any exceptions determining if InMemory, but don't treat as fatal exception Logger.Write(TraceEventType.Error, "Could not determine if is InMemory table " + ex.ToString()); } } return selectQuery.ToString(); } #endregion } }