// // 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.Data.Common; using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.EditData.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement { /// /// Represents a row that should be deleted. This will generate a DELETE statement /// public sealed class RowDelete : RowEditBase { private const string DeleteStatement = "DELETE FROM {0} {1}"; private const string DeleteMemoryOptimizedStatement = "DELETE FROM {0} WITH(SNAPSHOT) {1}"; /// /// Constructs a new RowDelete object /// /// Internal ID of the row to be deleted /// Result set that is being edited /// Improved metadata of the object being edited public RowDelete(long rowId, ResultSet associatedResultSet, EditTableMetadata associatedMetadata) : base(rowId, associatedResultSet, associatedMetadata) { } /// /// Sort ID for a RowDelete object. Setting to 2 ensures that these are the LAST changes /// to be committed /// protected override int SortId => 2; /// /// Applies the changes to the associated result set after successfully executing the /// change on the database /// /// /// Reader returned from the execution of the command to insert a new row. Should NOT /// contain any rows. /// public override Task ApplyChanges(DbDataReader dataReader) { // Take the result set and remove the row from it AssociatedResultSet.RemoveRow(RowId); return Task.FromResult(0); } /// /// Generates a command for deleting the selected row /// /// public override DbCommand GetCommand(DbConnection connection) { Validate.IsNotNull(nameof(connection), connection); // Return a SqlCommand with formatted with the parameters from the where clause WhereClause where = GetWhereClause(true); string commandText = GetCommandText(where.CommandText); DbCommand command = connection.CreateCommand(); command.CommandText = commandText; command.Parameters.AddRange(where.Parameters.ToArray()); return command; } /// /// Generates a edit row that represents a row pending deletion. All the original cells are /// intact but the state is dirty. /// /// Original, cached cell contents /// EditRow that is pending deletion public override EditRow GetEditRow(DbCellValue[] cachedRow) { Validate.IsNotNull(nameof(cachedRow), cachedRow); return new EditRow { Id = RowId, Cells = cachedRow.Select(cell => new EditCell(cell, true)).ToArray(), State = EditRow.EditRowState.DirtyDelete }; } /// /// Generates a DELETE statement to delete this row /// /// String of the DELETE statement public override string GetScript() { return GetCommandText(GetWhereClause(false).CommandText); } /// /// This method should not be called. A cell cannot be reverted on a row that is pending /// deletion. /// /// Ordinal of the column to update public override EditRevertCellResult RevertCell(int columnId) { throw new InvalidOperationException(SR.EditDataDeleteSetCell); } /// /// This method should not be called. A cell cannot be updated on a row that is pending /// deletion. /// /// Always thrown /// Ordinal of the column to update /// New value for the cell public override EditUpdateCellResult SetCell(int columnId, string newValue) { throw new InvalidOperationException(SR.EditDataDeleteSetCell); } protected override int CompareToSameType(RowEditBase rowEdit) { // We want to sort by row ID *IN REVERSE* to make sure we delete from the bottom first. // If we delete from the top first, it will change IDs, making all subsequent deletes // off by one or more! return RowId.CompareTo(rowEdit.RowId) * -1; } private string GetCommandText(string whereText) { string formatString = AssociatedObjectMetadata.IsMemoryOptimized ? DeleteMemoryOptimizedStatement : DeleteStatement; return string.Format(CultureInfo.InvariantCulture, formatString, AssociatedObjectMetadata.EscapedMultipartName, whereText); } } }