Files
sqltoolsservice/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScripterCore.cs
Jordan Hays 8bdf1d8451 Dropped Ledger Columns folders for ledger primary tables (#1688)
* dropped ledger columns strings, nodetypes, definition

* work in progress

* WIP- dropped columns folder showing for all tables

* Dropped Ledger Column folder only for primary ledger tables

* cleaning up

* undoing changes to kusto layer

* formatting

* undoing formatting

* undoing formatting

* removing IsLedger property and instead filtering on NodeTypeId

* updating column path test
2022-09-14 17:43:39 -07:00

843 lines
37 KiB
C#

//
// 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<DeclarationType, string> sqlObjectTypes = new Dictionary<DeclarationType, string>();
private Dictionary<string, string> sqlObjectTypesFromQuickInfo = new Dictionary<string, string>();
private Dictionary<DatabaseEngineEdition, string> targetDatabaseEngineEditionMap = new Dictionary<DatabaseEngineEdition, string>();
private Dictionary<int, string> serverVersionMap = new Dictionary<int, string>();
private Dictionary<string, string> objectScriptMap = new Dictionary<string, string>();
internal Scripter() {}
/// <summary>
/// Initialize a Peek Definition helper object
/// </summary>
/// <param name="serverConnection">SMO Server connection</param>
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;
}
}
/// <summary>
/// Add the given type, scriptgetter and the typeName string to the respective dictionaries
/// </summary>
private void AddSupportedType(DeclarationType type, string typeName, string quickInfoType, Type smoObjectType)
{
sqlObjectTypes.Add(type, typeName);
if (!string.IsNullOrEmpty(quickInfoType))
{
sqlObjectTypesFromQuickInfo.Add(quickInfoType.ToLowerInvariant(), typeName);
}
}
/// <summary>
/// Get the script of the selected token based on the type of the token
/// </summary>
/// <param name="declarationItems"></param>
/// <param name="tokenText"></param>
/// <param name="schemaName"></param>
/// <returns>Location object of the script file</returns>
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<Declaration> 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);
}
/// <summary>
/// Script an object using the type extracted from quickInfo Text
/// </summary>
/// <param name="quickInfoText">the text from the quickInfo for the selected token</param>
/// <param name="tokenText">The text of the selected token</param>
/// <param name="schemaName">Schema name</param>
/// <returns></returns>
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);
}
/// <summary>
/// Script a object using the type extracted from declarationItem
/// </summary>
/// <param name="declarationItem">The Declaration object that matched with the selected token</param>
/// <param name="tokenText">The text of the selected token</param>
/// <param name="schemaName">Schema name</param>
/// <returns></returns>
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);
}
/// <summary>
/// Script a object using SMO and write to a file.
/// </summary>
/// <param name="sqlScriptGetter">Function that returns the SMO scripts for an object</param>
/// <param name="objectName">SQL object name</param>
/// <param name="schemaName">Schema name or null</param>
/// <param name="objectType">Type of SQL object</param>
/// <returns>Location object representing URI and range of the script file</returns>
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
/// <summary>
/// Return schema name from the full name of the database. If schema is missing return dbo as schema name.
/// </summary>
/// <param name="fullObjectName"> The full database qualified name(database.schema.object)</param>
/// <param name="objectName"> Object name</param>
/// <returns>Schema name</returns>
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";
}
/// <summary>
/// Convert a file to a location array containing a location object as expected by the extension
/// </summary>
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;
}
/// <summary>
/// Helper method to create definition error result object
/// </summary>
/// <param name="errorMessage">Error message</param>
/// <returns> DefinitionResult</returns>
internal DefinitionResult GetDefinitionErrorResult(string errorMessage)
{
return new DefinitionResult
{
IsErrorResult = true,
Message = errorMessage,
Locations = null
};
}
/// <summary>
/// Return full object name(database.schema.objectName) from the quickInfo text("type database.schema.objectName")
/// </summary>
/// <param name="quickInfoText">QuickInfo Text for this token</param>
/// <param name="tokenText">Token Text</param>
/// <param name="caseSensitivity">StringComparison enum</param>
/// <returns></returns>
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<string> tokenList = tokens.Where(el => el.IndexOf(tokenText, caseSensitivity) >= 0).ToList();
return (tokenList?.Count() > 0) ? tokenList[0] : null;
}
/// <summary>
/// Return token type from the quickInfo text("type database.schema.objectName")
/// </summary>
/// <param name="quickInfoText">QuickInfo Text for this token</param>
/// <param name="tokenText"Token Text></param>
/// <param name="caseSensitivity">StringComparison enum</param>
/// <returns></returns>
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<int> 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;
}
/// <summary>
/// Wrapper method that calls Resolver.GetQuickInfo
/// </summary>
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;
}
/// <summary>
/// Wrapper method that calls Resolver.FindCompletions
/// </summary>
/// <param name="parseResult"></param>
/// <param name="parserLine"></param>
/// <param name="parserColumn"></param>
/// <param name="metadataDisplayInfoProvider"></param>
/// <returns></returns>
internal IEnumerable<Declaration> GetCompletionsForToken(ParseResult parseResult, int parserLine, int parserColumn, IMetadataDisplayInfoProvider metadataDisplayInfoProvider)
{
if (parseResult == null || metadataDisplayInfoProvider == null)
{
return null;
}
return Resolver.FindCompletions(
parseResult, parserLine, parserColumn, metadataDisplayInfoProvider);
}
/// <summary>
/// Wrapper method that calls Resolver.FindCompletions
/// </summary>
/// <param name="objectName"></param>
/// <param name="schemaName"></param>
/// <param name="objectType"></param>
/// <param name="tempFileName"></param>
/// <returns></returns>
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<ScriptingObject> objectList = new List<ScriptingObject>();
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
{
/// <summary>
/// Left delimiter for an named object
/// </summary>
public const char LeftDelimiter = '[';
/// <summary>
/// right delimiter for a named object
/// </summary>
public const char RightDelimiter = ']';
}
internal static class ScriptingUtils
{
/// <summary>
/// Quote the name of a given sql object.
/// </summary>
/// <param name="sqlObject">object</param>
/// <returns>quoted object name</returns>
internal static string QuoteObjectName(string sqlObject)
{
return QuoteObjectName(sqlObject, ']');
}
/// <summary>
/// Quotes the name of a given sql object
/// </summary>
/// <param name="sqlObject">object</param>
/// <param name="quote">quote to use</param>
/// <returns></returns>
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();
}
/// <summary>
/// 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<string> filterExpressions = new List<string>();
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
}
}