mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 09:59:48 -05:00
The main goal of this feature is to enable a command that will 1) Generate a parameterized command for each edit that is in the session 2) Execute that command against the server 3) Update the cached results of the table/view that's being edited with the committed changes (including computed/identity columns) There's some secret sauce in here where I cheated around worrying about gaps in the updated results. This was accomplished by implementing an IComparable for row edit objects that ensures deletes are the *last* actions to occur and that they occur from the bottom of the list up (highest row ID to lowest). Thus, all other actions that are dependent on the row ID are performed first, then the largest row ID is deleted, then next largest, etc. Nevertheless, by the end of a commit the associated ResultSet is still the source of truth. It is expected that the results grid will need updating once changes are committed. Also worth noting, although this pull request supports a "many edits, one commit" approach, it will work just fine for a "one edit, one commit" approach. * WIP * Adding basic commit support. Deletions work! * Nailing down the commit logic, insert commits work! * Updates work! * Fixing bug in DbColumnWrapper IsReadOnly setting * Comments * ResultSet unit tests, fixing issue with seeking in mock writers * Unit tests for RowCreate commands * Unit tests for RowDelete * RowUpdate unit tests * Session and edit base tests * Fixing broken unit tests * Moving constants to constants file * Addressing code review feedback * Fixes from merge issues, string consts * Removing ad-hoc code * fixing as per @abist requests * Fixing a couple more issues
215 lines
9.9 KiB
C#
215 lines
9.9 KiB
C#
//
|
|
// 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 Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
|
{
|
|
public class CellUpdateTests
|
|
{
|
|
[Fact]
|
|
public void NullColumnTest()
|
|
{
|
|
// If: I attempt to create a CellUpdate with a null column
|
|
// Then: I should get an exception thrown
|
|
Assert.Throws<ArgumentNullException>(() => new CellUpdate(null, string.Empty));
|
|
}
|
|
|
|
[Fact]
|
|
public void NullStringValueTest()
|
|
{
|
|
// If: I attempt to create a CellUpdate with a null string value
|
|
// Then: I should get an exception thrown
|
|
Assert.Throws<ArgumentNullException>(() => new CellUpdate(GetWrapper<string>("ntext"), null));
|
|
}
|
|
|
|
[Fact]
|
|
public void NullStringTest()
|
|
{
|
|
// If: I attempt to create a CellUpdate to set it to NULL (with mixed cases)
|
|
const string nullString = "NULL";
|
|
DbColumnWrapper col = GetWrapper<string>("ntext");
|
|
CellUpdate cu = new CellUpdate(col, nullString);
|
|
|
|
// Then: The value should be a DBNull and the string value should be the same as what
|
|
// was given
|
|
Assert.IsType<DBNull>(cu.Value);
|
|
Assert.Equal(DBNull.Value, cu.Value);
|
|
Assert.Equal(nullString, cu.ValueAsString);
|
|
Assert.Equal(col, cu.Column);
|
|
}
|
|
|
|
[Fact]
|
|
public void NullTextStringTest()
|
|
{
|
|
// If: I attempt to create a CellUpdate with the text 'NULL' (with mixed case)
|
|
DbColumnWrapper col = GetWrapper<string>("ntext");
|
|
CellUpdate cu = new CellUpdate(col, "'NULL'");
|
|
|
|
// Then: The value should be NULL
|
|
Assert.IsType<string>(cu.Value);
|
|
Assert.Equal("NULL", cu.Value);
|
|
Assert.Equal("'NULL'", cu.ValueAsString);
|
|
Assert.Equal(col, cu.Column);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ByteArrayTestParams))]
|
|
public void ByteArrayTest(string strValue, byte[] expectedValue, string expectedString)
|
|
{
|
|
// If: I attempt to create a CellUpdate for a binary column
|
|
DbColumnWrapper col = GetWrapper<byte[]>("binary");
|
|
CellUpdate cu = new CellUpdate(col, strValue);
|
|
|
|
// Then: The value should be a binary and should match the expected data
|
|
Assert.IsType<byte[]>(cu.Value);
|
|
Assert.Equal(expectedValue, cu.Value);
|
|
Assert.Equal(expectedString, cu.ValueAsString);
|
|
Assert.Equal(col, cu.Column);
|
|
}
|
|
|
|
public static IEnumerable<object> ByteArrayTestParams
|
|
{
|
|
get
|
|
{
|
|
// All zero tests
|
|
yield return new object[] {"00000000", new byte[] {0x00}, "0x00"}; // Base10
|
|
yield return new object[] {"0x000000", new byte[] {0x00, 0x00, 0x00}, "0x000000"}; // Base16
|
|
yield return new object[] {"0x000", new byte[] {0x00, 0x00}, "0x0000"}; // Base16, odd
|
|
|
|
// Single byte tests
|
|
yield return new object[] {"50", new byte[] {0x32}, "0x32"}; // Base10
|
|
yield return new object[] {"050", new byte[] {0x32}, "0x32"}; // Base10, leading zeros
|
|
yield return new object[] {"0xF0", new byte[] {0xF0}, "0xF0"}; // Base16
|
|
yield return new object[] {"0x0F", new byte[] {0x0F}, "0x0F"}; // Base16, leading zeros
|
|
yield return new object[] {"0xF", new byte[] {0x0F}, "0x0F"}; // Base16, odd
|
|
|
|
// Two byte tests
|
|
yield return new object[] {"1000", new byte[] {0x03, 0xE8}, "0x03E8"}; // Base10
|
|
yield return new object[] {"01000", new byte[] {0x03, 0xE8}, "0x03E8"}; // Base10, leading zeros
|
|
yield return new object[] {"0xF001", new byte[] {0xF0, 0x01}, "0xF001"}; // Base16
|
|
yield return new object[] {"0x0F10", new byte[] {0x0F, 0x10}, "0x0F10"}; // Base16, leading zeros
|
|
yield return new object[] {"0xF10", new byte[] {0x0F, 0x10}, "0x0F10"}; // Base16, odd
|
|
|
|
// Three byte tests
|
|
yield return new object[] {"100000", new byte[] {0x01, 0x86, 0xA0}, "0x0186A0"}; // Base10
|
|
yield return new object[] {"0100000", new byte[] {0x01, 0x86, 0xA0}, "0x0186A0"}; // Base10, leading zeros
|
|
yield return new object[] {"0x101010", new byte[] {0x10, 0x10, 0x10}, "0x101010"}; // Base16
|
|
yield return new object[] {"0x010101", new byte[] {0x01, 0x01, 0x01}, "0x010101"}; // Base16, leading zeros
|
|
yield return new object[] {"0x10101", new byte[] {0x01, 0x01, 0x01}, "0x010101"}; // Base16, odd
|
|
|
|
// Four byte tests
|
|
yield return new object[] {"20000000", new byte[] {0x01, 0x31, 0x2D, 0x00}, "0x01312D00"}; // Base10
|
|
yield return new object[] {"020000000", new byte[] {0x01, 0x31, 0x2D, 0x00}, "0x01312D00"}; // Base10, leading zeros
|
|
yield return new object[] {"0xF0F00101", new byte[] {0xF0, 0xF0, 0x01, 0x01}, "0xF0F00101"}; // Base16
|
|
yield return new object[] {"0x0F0F1010", new byte[] {0x0F, 0x0F, 0x10, 0x10}, "0x0F0F1010"}; // Base16, leading zeros
|
|
yield return new object[] {"0xF0F1010", new byte[] {0x0F, 0x0F, 0x10, 0x10}, "0x0F0F1010"}; // Base16, odd
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ByteArrayInvalidFormatTest()
|
|
{
|
|
// If: I attempt to create a CellUpdate for a binary column
|
|
// Then: It should throw an exception
|
|
DbColumnWrapper col = GetWrapper<byte[]>("binary");
|
|
Assert.Throws<FormatException>(() => new CellUpdate(col, "this is totally invalid"));
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(BoolTestParams))]
|
|
public void BoolTest(string input, bool output, string outputString)
|
|
{
|
|
// If: I attempt to create a CellUpdate for a boolean column
|
|
DbColumnWrapper col = GetWrapper<bool>("bit");
|
|
CellUpdate cu = new CellUpdate(col, input);
|
|
|
|
// Then: The value should match what was expected
|
|
Assert.IsType<bool>(cu.Value);
|
|
Assert.Equal(output, cu.Value);
|
|
Assert.Equal(outputString, cu.ValueAsString);
|
|
Assert.Equal(col, cu.Column);
|
|
}
|
|
|
|
public static IEnumerable<object> BoolTestParams
|
|
{
|
|
get
|
|
{
|
|
yield return new object[] {"1", true, bool.TrueString};
|
|
yield return new object[] {"0", false, bool.FalseString};
|
|
yield return new object[] {bool.TrueString, true, bool.TrueString};
|
|
yield return new object[] {bool.FalseString, false, bool.FalseString};
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void BoolInvalidFormatTest()
|
|
{
|
|
// If: I create a CellUpdate for a bool column and provide an invalid numeric value
|
|
// Then: It should throw an exception
|
|
DbColumnWrapper col = GetWrapper<bool>("bit");
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => new CellUpdate(col, "12345"));
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(RoundTripTestParams))]
|
|
public void RoundTripTest(DbColumnWrapper col, object obj)
|
|
{
|
|
// Setup: Figure out the test string
|
|
string testString = obj.ToString();
|
|
|
|
// If: I attempt to create a CellUpdate
|
|
CellUpdate cu = new CellUpdate(col, testString);
|
|
|
|
// Then: The value and type should match what we put in
|
|
Assert.IsType(col.DataType, cu.Value);
|
|
Assert.Equal(obj, cu.Value);
|
|
Assert.Equal(testString, cu.ValueAsString);
|
|
Assert.Equal(col, cu.Column);
|
|
}
|
|
|
|
public static IEnumerable<object> RoundTripTestParams
|
|
{
|
|
get
|
|
{
|
|
yield return new object[] {GetWrapper<Guid>("uniqueidentifier"), Guid.NewGuid()};
|
|
yield return new object[] {GetWrapper<TimeSpan>("time"), new TimeSpan(0, 1, 20, 0, 123)};
|
|
yield return new object[] {GetWrapper<DateTime>("datetime"), new DateTime(2016, 04, 25, 9, 45, 0)};
|
|
yield return new object[]
|
|
{
|
|
GetWrapper<DateTimeOffset>("datetimeoffset"),
|
|
new DateTimeOffset(2016, 04, 25, 9, 45, 0, TimeSpan.FromHours(8))
|
|
};
|
|
yield return new object[] {GetWrapper<long>("bigint"), 1000L};
|
|
yield return new object[] {GetWrapper<decimal>("decimal"), new decimal(3.14)};
|
|
yield return new object[] {GetWrapper<int>("int"), 1000};
|
|
yield return new object[] {GetWrapper<short>("smallint"), (short) 1000};
|
|
yield return new object[] {GetWrapper<byte>("tinyint"), (byte) 5};
|
|
yield return new object[] {GetWrapper<double>("float"), 3.14d};
|
|
yield return new object[] {GetWrapper<float>("real"), 3.14f};
|
|
}
|
|
}
|
|
|
|
private static DbColumnWrapper GetWrapper<T>(string dataTypeName)
|
|
{
|
|
return new DbColumnWrapper(new CellUpdateTestDbColumn(typeof(T), dataTypeName));
|
|
}
|
|
|
|
private class CellUpdateTestDbColumn : DbColumn
|
|
{
|
|
public CellUpdateTestDbColumn(Type dataType, string dataTypeName)
|
|
{
|
|
DataType = dataType;
|
|
DataTypeName = dataTypeName;
|
|
}
|
|
}
|
|
}
|
|
}
|