diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditColumnMetadata.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditColumnMetadata.cs
index 54739ce5..71a3bce9 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditColumnMetadata.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditColumnMetadata.cs
@@ -36,6 +36,22 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
///
public string EscapedName { get; set; }
+ ///
+ /// The column's expression for select statement
+ ///
+ public string ExpressionForSelectStatement
+ {
+ get
+ {
+ return IsHierarchyId ? string.Format("{0}.ToString() AS {0}", EscapedName) : EscapedName;
+ }
+ }
+
+ ///
+ /// Whether or not the column's data type is HierarchyId
+ ///
+ public bool IsHierarchyId { get; set; }
+
///
/// Whether or not the column is computed
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs
index ec29013f..e83d042c 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs
@@ -8,6 +8,7 @@ using System.Collections.Concurrent;
using System.Data.Common;
using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol;
+using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting;
@@ -149,11 +150,12 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
internal async Task HandleInitializeRequest(EditInitializeParams initParams,
RequestContext requestContext)
{
- Func executionFailureHandler = (e) => SendSessionReadyEvent(requestContext, initParams.OwnerUri, false, e.Message);
- Func executionSuccessHandler = () => SendSessionReadyEvent(requestContext, initParams.OwnerUri, true, null);
+ InitializeEditRequestContext context = new InitializeEditRequestContext(requestContext);
+ Func executionFailureHandler = (e) => SendSessionReadyEvent(context, initParams.OwnerUri, false, e.Message);
+ Func executionSuccessHandler = () => SendSessionReadyEvent(context, initParams.OwnerUri, true, null);
EditSession.Connector connector = () => connectionService.GetOrOpenConnection(initParams.OwnerUri, ConnectionType.Edit, alwaysPersistSecurity: true);
- EditSession.QueryRunner queryRunner = q => SessionInitializeQueryRunner(initParams.OwnerUri, requestContext, q);
+ EditSession.QueryRunner queryRunner = q => SessionInitializeQueryRunner(initParams.OwnerUri, context, q);
try
{
@@ -169,6 +171,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
throw new InvalidOperationException(SR.EditDataSessionAlreadyExists);
}
+ context.ResultSetHandler = (ResultSetEventParams resultSetEventParams) => { session.UpdateColumnInformationWithMetadata(resultSetEventParams.ResultSetSummary.ColumnInfo); };
+
// Initialize the session
session.Initialize(initParams, connector, queryRunner, executionSuccessHandler, executionFailureHandler);
@@ -214,7 +218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
await requestContext.SendResult(result);
}
- catch(Exception e)
+ catch (Exception e)
{
await requestContext.SendError(e.Message);
}
@@ -341,4 +345,29 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
#endregion
}
+
+ ///
+ /// Context for InitializeEditRequest, to provide a way to update the result set before sending it to UI.
+ ///
+ internal class InitializeEditRequestContext : IEventSender
+ {
+ private RequestContext _context;
+
+ public Action ResultSetHandler { get; set; }
+
+ public InitializeEditRequestContext(RequestContext context)
+ {
+ this._context = context;
+ }
+
+ public Task SendEvent(EventType eventType, TParams eventParams)
+ {
+ if (eventParams is ResultSetEventParams && this.ResultSetHandler != null)
+ {
+ this.ResultSetHandler(eventParams as ResultSetEventParams);
+ }
+ return _context.SendEvent(eventType, eventParams);
+ }
+
+ }
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs
index 299db9fe..91206df8 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs
@@ -152,7 +152,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
///
public static void CheckResultsForInvalidColumns(ResultSet results, string tableName)
{
- if(SchemaContainsMultipleItems(results.Columns, col => col.BaseCatalogName)
+ if (SchemaContainsMultipleItems(results.Columns, col => col.BaseCatalogName)
|| SchemaContainsMultipleItems(results.Columns, col => col.BaseSchemaName)
|| SchemaContainsMultipleItems(results.Columns, col => col.BaseTableName))
{
@@ -168,7 +168,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
throw new InvalidOperationException(SR.EditDataAliasesNotSupported);
}
- if (col.IsExpression.HasTrue())
+ // We have changed HierarchyId column to an expression so that it can be displayed properly
+ if (!col.IsHierarchyId && col.IsExpression.HasTrue())
{
throw new InvalidOperationException(SR.EditDataExpressionsNotSupported);
}
@@ -480,6 +481,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
// Step 3) Setup the internal state
associatedResultSet = ValidateQueryForSession(state.Query);
+ UpdateColumnInformationWithMetadata(associatedResultSet.Columns);
CheckResultsForInvalidColumns(associatedResultSet, initParams.ObjectName);
NextRowId = associatedResultSet.RowCount;
@@ -499,7 +501,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
public static string[] GetEditTargetName(EditInitializeParams initParams)
{
return initParams.SchemaName != null
- ? new [] { initParams.SchemaName, initParams.ObjectName }
+ ? new[] { initParams.SchemaName, initParams.ObjectName }
: FromSqlScript.DecodeMultipartIdentifier(initParams.ObjectName);
}
@@ -508,7 +510,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
try
{
// @TODO: Add support for transactional commits
-
+
// Trust the RowEdit to sort itself appropriately
var editOperations = EditCache.Values.ToList();
editOperations.Sort();
@@ -554,7 +556,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
}
// Using the columns we know, add them to the query
- var columns = metadata.Columns.Select(col => col.EscapedName);
+ var columns = metadata.Columns.Select(col => col.ExpressionForSelectStatement);
var columnClause = string.Join(", ", columns);
queryBuilder.Append(columnClause);
@@ -590,6 +592,20 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
EditCache.TryRemove(rowId, out removedRow);
}
+ internal void UpdateColumnInformationWithMetadata(DbColumnWrapper[] columns)
+ {
+ if (columns == null || this.objectMetadata == null)
+ {
+ return;
+ }
+
+ foreach (DbColumnWrapper col in columns)
+ {
+ var columnMetadata = objectMetadata.Columns.FirstOrDefault(cm => { return cm.EscapedName == ToSqlScript.FormatIdentifier(col.ColumnName); });
+ col.IsHierarchyId = columnMetadata != null && columnMetadata.IsHierarchyId;
+ }
+ }
+
#endregion
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/SmoEditMetadataFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/SmoEditMetadataFactory.cs
index 319f1fcd..8f56c737 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/SmoEditMetadataFactory.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/SmoEditMetadataFactory.cs
@@ -118,6 +118,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
DefaultValue = defaultValue,
EscapedName = ToSqlScript.FormatIdentifier(smoColumn.Name),
Ordinal = i,
+ IsHierarchyId = smoColumn.DataType.SqlDataType == SqlDataType.HierarchyId,
};
editColumns.Add(column);
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs
index a1ebd2d4..7fc9eac2 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs
@@ -43,7 +43,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
: base(rowId, associatedResultSet, associatedMetadata)
{
newCells = new CellUpdate[AssociatedResultSet.Columns.Length];
-
+
// Process the default cell values. If the column is calculated, then the value is a placeholder
DefaultValues = AssociatedObjectMetadata.Columns.Select((col, index) => col.IsCalculated.HasTrue()
? SR.EditDataComputedColumnPlaceholder
@@ -61,7 +61,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// provided cell update during commit
///
public string[] DefaultValues { get; }
-
+
#region Public Methods
///
@@ -96,14 +96,21 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
List inValues = new List();
List inParameters = new List();
List selectColumns = new List();
- for(int i = 0; i < AssociatedObjectMetadata.Columns.Length; i++)
+ for (int i = 0; i < AssociatedObjectMetadata.Columns.Length; i++)
{
DbColumnWrapper column = AssociatedResultSet.Columns[i];
EditColumnMetadata metadata = AssociatedObjectMetadata.Columns[i];
CellUpdate cell = newCells[i];
-
+
// Add the output columns regardless of whether the column is read only
- outClauseColumnNames.Add($"inserted.{metadata.EscapedName}");
+ if (metadata.IsHierarchyId)
+ {
+ outClauseColumnNames.Add($"inserted.{metadata.EscapedName}.ToString() {metadata.EscapedName}");
+ }
+ else
+ {
+ outClauseColumnNames.Add($"inserted.{metadata.EscapedName}");
+ }
declareColumns.Add($"{metadata.EscapedName} {ToSqlScript.FormatColumnType(column, useSemanticEquivalent: true)}");
selectColumns.Add(metadata.EscapedName);
@@ -112,16 +119,25 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
{
continue;
}
-
+
// Add the input column
inColumnNames.Add(metadata.EscapedName);
-
+
// Add the input values as parameters
string paramName = $"@Value{RowId}_{i}";
- inValues.Add(paramName);
- inParameters.Add(new SqlParameter(paramName, column.SqlDbType) {Value = cell.Value});
+
+ if (metadata.IsHierarchyId)
+ {
+ inValues.Add($"CONVERT(hierarchyid,{paramName})");
+ }
+ else
+ {
+ inValues.Add(paramName);
+ }
+
+ inParameters.Add(new SqlParameter(paramName, column.SqlDbType) { Value = cell.Value });
}
-
+
// Put everything together into a single query
// Step 1) Build a temp table for inserting output values into
string tempTableName = $"@Insert{RowId}Output";
@@ -130,32 +146,32 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
// Step 2) Build the insert statement
string joinedOutClauseNames = string.Join(", ", outClauseColumnNames);
string insertStatement = inValues.Count > 0
- ? string.Format(InsertOutputValuesStatement,
+ ? string.Format(InsertOutputValuesStatement,
AssociatedObjectMetadata.EscapedMultipartName,
- string.Join(", ", inColumnNames),
+ string.Join(", ", inColumnNames),
joinedOutClauseNames,
tempTableName,
string.Join(", ", inValues))
- : string.Format(InsertOutputDefaultStatement,
+ : string.Format(InsertOutputDefaultStatement,
AssociatedObjectMetadata.EscapedMultipartName,
joinedOutClauseNames,
tempTableName);
// Step 3) Build the select statement
string selectStatement = string.Format(SelectStatement, string.Join(", ", selectColumns), tempTableName);
-
+
// Step 4) Put it all together into a results object
StringBuilder query = new StringBuilder();
query.AppendLine(declareStatement);
query.AppendLine(insertStatement);
query.Append(selectStatement);
-
+
// Build the command
DbCommand command = connection.CreateCommand();
command.CommandText = query.ToString();
command.CommandType = CommandType.Text;
command.Parameters.AddRange(inParameters.ToArray());
-
+
return command;
}
@@ -168,7 +184,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
{
// Get edit cells for each
EditCell[] editCells = newCells.Select(GetEditCell).ToArray();
-
+
return new EditRow
{
Id = RowId,
@@ -190,23 +206,23 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
{
DbColumnWrapper column = AssociatedResultSet.Columns[i];
CellUpdate cell = newCells[i];
-
+
// Continue if we're not inserting a value for this column
if (!IsCellValueProvided(column, cell, DefaultValues[i]))
{
continue;
}
-
+
// Column is provided
inColumns.Add(AssociatedObjectMetadata.Columns[i].EscapedName);
inValues.Add(ToSqlScript.FormatValue(cell.AsDbCellValue, column));
}
-
+
// Build the insert statement
return inValues.Count > 0
- ? string.Format(InsertScriptValuesStatement,
+ ? string.Format(InsertScriptValuesStatement,
AssociatedObjectMetadata.EscapedMultipartName,
- string.Join(", ", inColumns),
+ string.Join(", ", inColumns),
string.Join(", ", inValues))
: string.Format(InsertScriptDefaultStatement, AssociatedObjectMetadata.EscapedMultipartName);
}
@@ -225,7 +241,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
newCells[columnId] = null;
return new EditRevertCellResult
{
- IsRowDirty = true,
+ IsRowDirty = true,
Cell = GetEditCell(null, columnId)
};
}
@@ -277,7 +293,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
{
return false;
}
-
+
// Make sure a value was provided for the cell
if (cell == null)
{
@@ -286,14 +302,14 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
{
throw new InvalidOperationException(SR.EditDataCreateScriptMissingValue(column.ColumnName));
}
-
+
// There is a default value (or omitting the value is fine), so trust the db will apply it correctly
return false;
}
return true;
}
-
+
private EditCell GetEditCell(CellUpdate cell, int index)
{
DbCellValue dbCell;
diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs
index c5e5888e..073e574b 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs
@@ -97,32 +97,46 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
for (int i = 0; i < AssociatedObjectMetadata.Columns.Length; i++)
{
EditColumnMetadata metadata = AssociatedObjectMetadata.Columns[i];
-
+
// Add the output columns regardless of whether the column is read only
declareColumns.Add($"{metadata.EscapedName} {ToSqlScript.FormatColumnType(metadata.DbColumn, useSemanticEquivalent: true)}");
- outClauseColumns.Add($"inserted.{metadata.EscapedName}");
+ if (metadata.IsHierarchyId)
+ {
+ outClauseColumns.Add($"inserted.{metadata.EscapedName}.ToString() {metadata.EscapedName}");
+ }
+ else
+ {
+ outClauseColumns.Add($"inserted.{metadata.EscapedName}");
+ }
selectColumns.Add(metadata.EscapedName);
-
+
// If we have a new value for the column, proccess it now
CellUpdate cellUpdate;
if (cellUpdates.TryGetValue(i, out cellUpdate))
{
string paramName = $"@Value{RowId}_{i}";
- setComponents.Add($"{metadata.EscapedName} = {paramName}");
- inParameters.Add(new SqlParameter(paramName, AssociatedResultSet.Columns[i].SqlDbType) {Value = cellUpdate.Value});
+ if (metadata.IsHierarchyId)
+ {
+ setComponents.Add($"{metadata.EscapedName} = CONVERT(hierarchyid,{paramName})");
+ }
+ else
+ {
+ setComponents.Add($"{metadata.EscapedName} = {paramName}");
+ }
+ inParameters.Add(new SqlParameter(paramName, AssociatedResultSet.Columns[i].SqlDbType) { Value = cellUpdate.Value });
}
}
-
+
// Put everything together into a single query
// Step 1) Build a temp table for inserting output values into
string tempTableName = $"@Update{RowId}Output";
string declareStatement = string.Format(DeclareStatement, tempTableName, string.Join(", ", declareColumns));
-
+
// Step 2) Build the update statement
WhereClause whereClause = GetWhereClause(true);
-
- string updateStatementFormat = AssociatedObjectMetadata.IsMemoryOptimized
- ? UpdateOutputMemOptimized
+
+ string updateStatementFormat = AssociatedObjectMetadata.IsMemoryOptimized
+ ? UpdateOutputMemOptimized
: UpdateOutput;
string updateStatement = string.Format(updateStatementFormat,
AssociatedObjectMetadata.EscapedMultipartName,
@@ -135,10 +149,10 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
string validateScript = string.Format(CultureInfo.InvariantCulture, validateUpdateOnlyOneRow,
AssociatedObjectMetadata.EscapedMultipartName,
whereClause.CommandText);
-
+
// Step 3) Build the select statement
string selectStatement = string.Format(SelectStatement, string.Join(", ", selectColumns), tempTableName);
-
+
// Step 4) Put it all together into a results object
StringBuilder query = new StringBuilder();
query.AppendLine(declareStatement);
@@ -146,7 +160,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
query.AppendLine(updateStatement);
query.AppendLine(selectStatement);
query.Append("END");
-
+
// Build the command
DbCommand command = connection.CreateCommand();
command.CommandText = query.ToString();
@@ -198,7 +212,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
return $"{formattedColumnName} = {formattedValue}";
});
string setClause = string.Join(", ", setComponents);
-
+
// Put everything together into a single query
string whereClause = GetWhereClause(false).CommandText;
string updateStatementFormat = AssociatedObjectMetadata.IsMemoryOptimized
@@ -247,7 +261,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
public override EditUpdateCellResult SetCell(int columnId, string newValue)
{
// Validate the value and convert to object
- ValidateColumnIsUpdatable(columnId);
+ ValidateColumnIsUpdatable(columnId);
CellUpdate update = new CellUpdate(AssociatedResultSet.Columns[columnId], newValue);
// If the value is the same as the old value, we shouldn't make changes
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs
index dd60564e..6389b8ce 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs
@@ -199,8 +199,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
object assemblyQualifiedName = column.UdtAssemblyQualifiedName;
const string hierarchyId = "MICROSOFT.SQLSERVER.TYPES.SQLHIERARCHYID";
- if (assemblyQualifiedName != null &&
- string.Equals(assemblyQualifiedName.ToString(), hierarchyId, StringComparison.OrdinalIgnoreCase))
+ if (assemblyQualifiedName != null
+ && assemblyQualifiedName.ToString().StartsWith(hierarchyId, StringComparison.OrdinalIgnoreCase))
{
DataType = typeof(SqlBinary);
}
@@ -259,6 +259,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
///
public SqlDbType SqlDbType { get; private set; }
+ ///
+ /// Whther this is a HierarchyId column
+ ///
+ public bool IsHierarchyId { get; set; }
+
///
/// Whether or not the column is an XML Reader type.
///
@@ -286,10 +291,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
///
///
/// Logic taken from SSDT determination of updatable columns
+ /// Special treatment for HierarchyId since we are using an Expression for HierarchyId column and expression column is readonly.
///
- public bool IsUpdatable => !IsAutoIncrement.HasTrue() &&
- !IsReadOnly.HasTrue() &&
- !IsSqlXmlType;
+ public bool IsUpdatable => (!IsAutoIncrement.HasTrue() &&
+ !IsReadOnly.HasTrue() &&
+ !IsSqlXmlType) || IsHierarchyId;
#endregion