mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-18 09:35:38 -05:00
* Make nullable warnings a per file opt-in * Remove unneeded compiler directives * Remove compiler directive for User Data
208 lines
7.7 KiB
C#
208 lines
7.7 KiB
C#
//
|
|
// 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.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
|
|
{
|
|
/// <summary>
|
|
/// An error indicating that a delete action will delete multiple rows.
|
|
/// </summary>
|
|
public class EditDataDeleteException : Exception
|
|
{
|
|
public EditDataDeleteException()
|
|
{
|
|
}
|
|
|
|
public EditDataDeleteException(string message)
|
|
: base(message)
|
|
{
|
|
}
|
|
|
|
public EditDataDeleteException(string message, Exception inner)
|
|
: base(message, inner)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a row that should be deleted. This will generate a DELETE statement
|
|
/// </summary>
|
|
public sealed class RowDelete : RowEditBase
|
|
{
|
|
private const string DeleteStatement = "DELETE FROM {0} {1}";
|
|
private const string DeleteMemoryOptimizedStatement = "DELETE FROM {0} WITH(SNAPSHOT) {1}";
|
|
private const string VerifyStatement = "SELECT COUNT (*) FROM ";
|
|
|
|
/// <summary>
|
|
/// Constructs a new RowDelete object
|
|
/// </summary>
|
|
/// <param name="rowId">Internal ID of the row to be deleted</param>
|
|
/// <param name="associatedResultSet">Result set that is being edited</param>
|
|
/// <param name="associatedMetadata">Improved metadata of the object being edited</param>
|
|
public RowDelete(long rowId, ResultSet associatedResultSet, EditTableMetadata associatedMetadata)
|
|
: base(rowId, associatedResultSet, associatedMetadata)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sort ID for a RowDelete object. Setting to 2 ensures that these are the LAST changes
|
|
/// to be committed
|
|
/// </summary>
|
|
protected override int SortId => 2;
|
|
|
|
/// <summary>
|
|
/// Applies the changes to the associated result set after successfully executing the
|
|
/// change on the database
|
|
/// </summary>
|
|
/// <param name="dataReader">
|
|
/// Reader returned from the execution of the command to insert a new row. Should NOT
|
|
/// contain any rows.
|
|
/// </param>
|
|
public override Task ApplyChanges(DbDataReader dataReader)
|
|
{
|
|
// Take the result set and remove the row from it
|
|
AssociatedResultSet.RemoveRow(RowId);
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a command for deleting the selected row
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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);
|
|
string verifyText = GetVerifyText(where.CommandText);
|
|
if (HasDuplicateRows(where, verifyText, connection))
|
|
{
|
|
throw new EditDataDeleteException("Cannot delete: Action will delete more than one row");
|
|
}
|
|
|
|
DbCommand command = connection.CreateCommand();
|
|
command.CommandText = commandText;
|
|
command.Parameters.AddRange(where.Parameters.ToArray());
|
|
|
|
return command;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs a query using the where clause to determine if duplicates are found (causes issues when deleting).
|
|
/// If duplicates are found, the check returns true, else it returns false;
|
|
/// </summary>
|
|
private bool HasDuplicateRows(WhereClause where, string input, DbConnection connection)
|
|
{
|
|
using (DbCommand command = connection.CreateCommand())
|
|
{
|
|
command.CommandText = input;
|
|
command.Parameters.AddRange(where.Parameters.ToArray());
|
|
try
|
|
{
|
|
return (Convert.ToInt32(command.ExecuteScalar())) > 1;
|
|
}
|
|
finally
|
|
{
|
|
command.Parameters.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a edit row that represents a row pending deletion. All the original cells are
|
|
/// intact but the state is dirty.
|
|
/// </summary>
|
|
/// <param name="cachedRow">Original, cached cell contents</param>
|
|
/// <returns>EditRow that is pending deletion</returns>
|
|
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
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a DELETE statement to delete this row
|
|
/// </summary>
|
|
/// <returns>String of the DELETE statement</returns>
|
|
public override string GetScript()
|
|
{
|
|
return GetCommandText(GetWhereClause(false).CommandText);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a WHERE statement to verify the row delete is unique.
|
|
/// </summary>
|
|
/// <returns>String of the WHERE statement</returns>
|
|
public string GetVerifyScript()
|
|
{
|
|
return GetVerifyText(GetWhereClause(false).CommandText);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method should not be called. A cell cannot be reverted on a row that is pending
|
|
/// deletion.
|
|
/// </summary>
|
|
/// <param name="columnId">Ordinal of the column to update</param>
|
|
public override EditRevertCellResult RevertCell(int columnId)
|
|
{
|
|
throw new InvalidOperationException(SR.EditDataDeleteSetCell);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method should not be called. A cell cannot be updated on a row that is pending
|
|
/// deletion.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">Always thrown</exception>
|
|
/// <param name="columnId">Ordinal of the column to update</param>
|
|
/// <param name="newValue">New value for the cell</param>
|
|
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 GetVerifyText(string whereText)
|
|
{
|
|
return $"{VerifyStatement}{AssociatedObjectMetadata.EscapedMultipartName} {whereText}";
|
|
}
|
|
|
|
private string GetCommandText(string whereText)
|
|
{
|
|
string formatString = AssociatedObjectMetadata.IsMemoryOptimized
|
|
? DeleteMemoryOptimizedStatement
|
|
: DeleteStatement;
|
|
|
|
return string.Format(CultureInfo.InvariantCulture, formatString,
|
|
AssociatedObjectMetadata.EscapedMultipartName, whereText);
|
|
}
|
|
}
|
|
}
|