Add table and view metadata messges (#296)

* Add SMO table property lookup event

* Fix a couple bugs in column metadata

* Add GetView metadata message
This commit is contained in:
Karl Burtram
2017-03-28 16:30:46 +00:00
committed by GitHub
parent f7036f3f73
commit a1aa3f8b4c
7 changed files with 483 additions and 3 deletions

View File

@@ -0,0 +1,112 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Data;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Metadata
{
/// <summary>
/// ColumnMetadata class
/// </summary>
public class ColumnMetadata
{
/// <summary>
/// Constructs a simple edit column metadata provider
/// </summary>
public ColumnMetadata()
{
HasExtendedProperties = false;
}
#region Basic Properties (properties provided by SMO)
/// <summary>
/// If set, this is a string representation of the default value. If set to null, then the
/// column does not have a default value.
/// </summary>
public string DefaultValue { get; set; }
/// <summary>
/// Escaped identifier for the name of the column
/// </summary>
public string EscapedName { get; set; }
/// <summary>
/// Whether or not the column is computed
/// </summary>
public bool IsComputed { get; set; }
/// <summary>
/// Whether or not the column is deterministically computed
/// </summary>
public bool IsDeterministic { get; set; }
/// <summary>
/// Whether or not the column is an identity column
/// </summary>
public bool IsIdentity { get; set; }
/// <summary>
/// The ordinal ID of the column
/// </summary>
public int Ordinal { get; set; }
#endregion
#region Extended Properties (properties provided by SqlClient)
// public DbColumnWrapper DbColumn { get; private set; }
/// <summary>
/// Whether or not the column has extended properties
/// </summary>
public bool HasExtendedProperties { get; private set; }
/// <summary>
/// Whether or not the column is calculated on the server side. This could be a computed
/// column or a identity column.
/// </summary>
public bool? IsCalculated { get; private set; }
/// <summary>
/// Whether or not the column is used in a key to uniquely identify a row
/// </summary>
public bool? IsKey { get; private set; }
/// <summary>
/// Whether or not the column can be trusted for uniqueness
/// </summary>
public bool? IsTrustworthyForUniqueness { get; private set; }
#endregion
/// <summary>
/// Extracts extended column properties from the database columns from SQL Client
/// </summary>
/// <param name="dbColumn">The column information provided by SQL Client</param>
public void Extend(DbColumnWrapper dbColumn)
{
Validate.IsNotNull(nameof(dbColumn), dbColumn);
// DbColumn = dbColumn;
// A column is trustworthy for uniqueness if it can be updated or it has an identity
// property. If both of these are false (eg, timestamp) we can't trust it to uniquely
// identify a row in the table
IsTrustworthyForUniqueness = dbColumn.IsUpdatable || dbColumn.IsIdentity.HasTrue();
// A key column is determined by whether it is a key
IsKey = dbColumn.IsKey;
// A column is calculated if it is identity, computed, or otherwise not updatable
IsCalculated = IsIdentity || IsComputed || !dbColumn.IsUpdatable;
// Mark the column as extended
HasExtendedProperties = true;
}
}
}

View File

@@ -0,0 +1,85 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Metadata
{
/// <summary>
/// Provides metadata about the table or view being edited
/// </summary>
public class TableMetadata
{
/// <summary>
/// Constructs a simple edit table metadata provider
/// </summary>
public TableMetadata()
{
HasExtendedProperties = false;
}
#region Basic Properties (properties provided by SMO)
/// <summary>
/// List of columns in the object being edited
/// </summary>
public ColumnMetadata[] Columns { get; set; }
/// <summary>
/// Full escaped multipart identifier for the object being edited
/// </summary>
public string EscapedMultipartName { get; set; }
/// <summary>
/// Whether or not the object being edited is memory optimized
/// </summary>
public bool IsMemoryOptimized { get; set; }
#endregion
#region Extended Properties (properties provided by SqlClient)
/// <summary>
/// Whether or not the table has had extended properties added to it
/// </summary>
public bool HasExtendedProperties { get; private set; }
/// <summary>
/// List of columns that are used to uniquely identify a row
/// </summary>
public ColumnMetadata[] KeyColumns { get; private set; }
#endregion
/// <summary>
/// Extracts extended column properties from the database columns from SQL Client
/// </summary>
/// <param name="dbColumnWrappers">The column information provided by SQL Client</param>
public void Extend(DbColumnWrapper[] dbColumnWrappers)
{
Validate.IsNotNull(nameof(dbColumnWrappers), dbColumnWrappers);
// Iterate over the column wrappers and improve the columns we have
for (int i = 0; i < Columns.Length; i++)
{
Columns[i].Extend(dbColumnWrappers[i]);
}
// Determine what the key columns are
KeyColumns = Columns.Where(c => c.IsKey.HasTrue()).ToArray();
if (KeyColumns.Length == 0)
{
// We didn't find any explicit key columns. Instead, we'll use all columns that are
// trustworthy for uniqueness (usually all the columns)
KeyColumns = Columns.Where(c => c.IsTrustworthyForUniqueness.HasTrue()).ToArray();
}
// Mark that the table is now extended
HasExtendedProperties = true;
}
}
}

View File

@@ -0,0 +1,33 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Metadata.Contracts
{
public class TableMetadataParams
{
public string OwnerUri { get; set; }
public string Schema { get; set; }
public string ObjectName { get; set; }
}
public class TableMetadataResult
{
public ColumnMetadata[] Columns { get; set; }
}
/// <summary>
/// Retreive metadata for the table described in the TableMetadataParams value
/// </summary>
public class TableMetadataRequest
{
public static readonly
RequestType<TableMetadataParams, TableMetadataResult> Type =
RequestType<TableMetadataParams, TableMetadataResult>.Create("metadata/table");
}
}

View File

@@ -0,0 +1,21 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Metadata.Contracts
{
/// <summary>
/// Retreive metadata for the view described in the TableMetadataParams value.
/// This message reuses the table metadata params and result since the exchanged
/// data is the same.
/// </summary>
public class ViewMetadataRequest
{
public static readonly
RequestType<TableMetadataParams, TableMetadataResult> Type =
RequestType<TableMetadataParams, TableMetadataResult>.Create("metadata/view");
}
}

View File

@@ -53,6 +53,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Metadata
public void InitializeService(ServiceHost serviceHost)
{
serviceHost.SetRequestHandler(MetadataListRequest.Type, HandleMetadataListRequest);
serviceHost.SetRequestHandler(TableMetadataRequest.Type, HandleGetTableRequest);
serviceHost.SetRequestHandler(ViewMetadataRequest.Type, HandleGetViewRequest);
}
/// <summary>
@@ -87,6 +89,62 @@ namespace Microsoft.SqlTools.ServiceLayer.Metadata
}
}
/// <summary>
/// Handle a table metadata query request
/// </summary>
internal static async Task HandleGetTableRequest(
TableMetadataParams metadataParams,
RequestContext<TableMetadataResult> requestContext)
{
await HandleGetTableOrViewRequest(metadataParams, "table", requestContext);
}
/// <summary>
/// Handle a view metadata query request
/// </summary>
internal static async Task HandleGetViewRequest(
TableMetadataParams metadataParams,
RequestContext<TableMetadataResult> requestContext)
{
await HandleGetTableOrViewRequest(metadataParams, "view", requestContext);
}
/// <summary>
/// Handle a table pr view metadata query request
/// </summary>
private static async Task HandleGetTableOrViewRequest(
TableMetadataParams metadataParams,
string objectType,
RequestContext<TableMetadataResult> requestContext)
{
try
{
ConnectionInfo connInfo;
MetadataService.ConnectionServiceInstance.TryFindConnection(
metadataParams.OwnerUri,
out connInfo);
ColumnMetadata[] metadata = null;
if (connInfo != null)
{
SqlConnection sqlConn = OpenMetadataConnection(connInfo);
TableMetadata table = new SmoMetadataFactory().GetObjectMetadata(
sqlConn, metadataParams.Schema,
metadataParams.ObjectName, objectType);
metadata = table.Columns;
}
await requestContext.SendResult(new TableMetadataResult
{
Columns = metadata
});
}
catch (Exception ex)
{
await requestContext.SendError(ex.ToString());
}
}
/// <summary>
/// Create a SqlConnection to use for querying metadata
/// </summary>
@@ -162,6 +220,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Metadata
}
}
}
}
}
}
}

View File

@@ -0,0 +1,122 @@
//
// 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.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Metadata
{
/// <summary>
/// Interface for a factory that generates metadata for an object to edit
/// </summary>
public interface IMetadataFactory
{
/// <summary>
/// Generates a edit-ready metadata object
/// </summary>
/// <param name="connection">Connection to use for getting metadata</param>
/// <param name="objectName">Name of the object to return metadata for</param>
/// <param name="objectType">Type of the object to return metadata for</param>
/// <returns>Metadata about the object requested</returns>
TableMetadata GetObjectMetadata(DbConnection connection, string schemaName, string objectName, string objectType);
}
/// <summary>
/// Factory that generates metadata using a combination of SMO and SqlClient metadata
/// </summary>
public class SmoMetadataFactory : IMetadataFactory
{
/// <summary>
/// Generates a edit-ready metadata object using SMO
/// </summary>
/// <param name="connection">Connection to use for getting metadata</param>
/// <param name="objectName">Name of the object to return metadata for</param>
/// <param name="objectType">Type of the object to return metadata for</param>
/// <returns>Metadata about the object requested</returns>
public TableMetadata GetObjectMetadata(DbConnection connection, string schemaName, string objectName, string objectType)
{
// 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
Server server = new Server(new ServerConnection(sqlConn));
Database database = server.Databases[sqlConn.Database];
TableViewTableTypeBase smoResult;
switch (objectType.ToLowerInvariant())
{
case "table":
Table table = string.IsNullOrEmpty(schemaName) ? new Table(database, objectName) : new Table(database, objectName, schemaName);
table.Refresh();
smoResult = table;
break;
case "view":
View view = string.IsNullOrEmpty(schemaName) ? new View(database, objectName) : new View(database, objectName, schemaName);
view.Refresh();
smoResult = view;
break;
default:
throw new ArgumentOutOfRangeException(nameof(objectType), SR.EditDataUnsupportedObjectType(objectType));
}
if (smoResult == null)
{
throw new ArgumentOutOfRangeException(nameof(objectName), SR.EditDataObjectMetadataNotFound);
}
// Generate the edit column metadata
List<ColumnMetadata> editColumns = new List<ColumnMetadata>();
for (int i = 0; i < smoResult.Columns.Count; i++)
{
Column smoColumn = smoResult.Columns[i];
// The default value may be escaped
string defaultValue = smoColumn.DefaultConstraint == null
? null
: SqlScriptFormatter.UnwrapLiteral(smoColumn.DefaultConstraint.Text);
ColumnMetadata column = new ColumnMetadata
{
DefaultValue = defaultValue,
EscapedName = SqlScriptFormatter.FormatIdentifier(smoColumn.Name),
Ordinal = i,
};
editColumns.Add(column);
}
// Only tables can be memory-optimized
Table smoTable = smoResult as Table;
bool isMemoryOptimized = smoTable != null && smoTable.IsMemoryOptimized;
// Escape the parts of the name
string[] objectNameParts = {smoResult.Schema, smoResult.Name};
string escapedMultipartName = SqlScriptFormatter.FormatMultipartIdentifier(objectNameParts);
return new TableMetadata
{
Columns = editColumns.ToArray(),
EscapedMultipartName = escapedMultipartName,
IsMemoryOptimized = isMemoryOptimized,
};
}
}
}