//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
using System;
using System.Collections.Generic;
using System.Data.Common;
using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.EditData
{
///
/// Factory that generates metadata using a combination of SMO and SqlClient metadata
///
public class SmoEditMetadataFactory : IEditMetadataFactory
{
///
/// Generates a edit-ready metadata object using SMO
///
/// Connection to use for getting metadata
/// Split and unwrapped name parts
/// Type of the object to return metadata for
/// Metadata about the object requested
public EditTableMetadata GetObjectMetadata(DbConnection connection, string[] objectNamedParts, string objectType)
{
Validate.IsNotNull(nameof(objectNamedParts), objectNamedParts);
if (objectNamedParts.Length <= 0)
{
throw new ArgumentNullException(nameof(objectNamedParts), SR.EditDataMetadataObjectNameRequired);
}
if (objectNamedParts.Length > 2)
{
throw new InvalidOperationException(SR.EditDataMetadataTooManyIdentifiers);
}
// Get a connection to the database for SMO purposes
SqlConnection sqlConn = connection as SqlConnection;
if (sqlConn == null)
{
// It's not actually a SqlConnection, so let's try a reliable SQL connection
ReliableSqlConnection reliableConn = connection as ReliableSqlConnection;
if (reliableConn == null)
{
// If we don't have connection we can use with SMO, just give up on using SMO
return null;
}
// We have a reliable connection, use the underlying connection
sqlConn = reliableConn.GetUnderlyingConnection();
}
// Connect with SMO and get the metadata for the table
ServerConnection serverConnection;
if (sqlConn.AccessToken == null)
{
serverConnection = new ServerConnection(sqlConn);
}
else
{
serverConnection = new ServerConnection(sqlConn, new AzureAccessToken(sqlConn.AccessToken));
}
Server server = new Server(serverConnection);
Database db = new Database(server, sqlConn.Database);
TableViewTableTypeBase smoResult;
switch (objectType.ToLowerInvariant())
{
case "table":
smoResult = objectNamedParts.Length == 1
? new Table(db, objectNamedParts[0]) // No schema provided
: new Table(db, objectNamedParts[1], objectNamedParts[0]); // Schema provided
break;
case "view":
smoResult = objectNamedParts.Length == 1
? new View(db, objectNamedParts[0]) // No schema provided
: new View(db, objectNamedParts[1], objectNamedParts[0]); // Schema provided
break;
default:
throw new ArgumentOutOfRangeException(nameof(objectType), SR.EditDataUnsupportedObjectType(objectType));
}
// A bug in SMO makes it necessary to call refresh to attain certain properties (such as IsMemoryOptimized)
smoResult.Refresh();
if (smoResult.State != SqlSmoState.Existing)
{
throw new ArgumentOutOfRangeException(nameof(objectNamedParts), SR.EditDataObjectNotFound);
}
// Filter out dropped ledger columns from the list of columns to be returned
// and grab the specific column properties checked below
smoResult.Columns.ClearAndInitialize("[(@IsDroppedLedgerColumn=0)]", new [] { nameof(Column.DataType), nameof(Column.DefaultConstraintName) });
// Generate the edit column metadata
List editColumns = new List();
for (int i = 0; i < smoResult.Columns.Count; i++)
{
Column smoColumn = smoResult.Columns[i];
string defaultValue = null;
try
{
// The default value may be escaped
defaultValue = string.IsNullOrEmpty(smoColumn.DefaultConstraintName)
? null
: FromSqlScript.UnwrapLiteral(smoColumn.DefaultConstraint.Text);
}
catch (PropertyCannotBeRetrievedException)
{
// This exception will be thrown when the user doesn't have view definition privilege,
// we can ignore it and use null as the default value;
}
EditColumnMetadata column = new EditColumnMetadata
{
DefaultValue = defaultValue,
EscapedName = ToSqlScript.FormatIdentifier(smoColumn.Name),
Ordinal = i,
IsHierarchyId = smoColumn.DataType.SqlDataType == SqlDataType.HierarchyId,
};
editColumns.Add(column);
}
// Only tables can be memory-optimized
Table smoTable = smoResult as Table;
bool isMemoryOptimized = false;
// TODO: Remove IsSupported check once SMO fixes broken IsMemoryOptimized scenario (TFS #10871823)
if (smoTable != null)
{
isMemoryOptimized = smoTable.IsSupportedProperty("IsMemoryOptimized") && smoTable.IsMemoryOptimized;
}
// Escape the parts of the name
string[] objectNameParts = { smoResult.Schema, smoResult.Name };
string escapedMultipartName = ToSqlScript.FormatMultipartIdentifier(objectNameParts);
return new EditTableMetadata
{
Columns = editColumns.ToArray(),
EscapedMultipartName = escapedMultipartName,
IsMemoryOptimized = isMemoryOptimized
};
}
}
}