Copied dotnet code from CosmosDB repository

This commit is contained in:
David Noble
2019-08-20 11:58:29 -07:00
parent b0e89b0dda
commit 31f3bc828b
201 changed files with 37803 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" culture="neutral" publicKeyToken="b03f5f7f11d50a3a" />
<bindingRedirect oldVersion="0.0.0.0-4.0.4.0" newVersion="4.0.4.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -0,0 +1,59 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
internal static class ArrayAssert
{
public static void AreEqual<T>(T[] expected, T[] actual)
{
if (expected == null)
{
Assert.IsNull(actual);
return;
}
Assert.IsNotNull(actual);
Assert.AreEqual(expected.Length, actual.Length);
for (int i = 0; i < expected.Length; i++)
{
Assert.AreEqual(expected[i], actual[i]);
}
}
public static void AreEqual<T>(T[] expected, T[] actual, string message)
{
if (expected == null)
{
Assert.IsNull(actual, message);
return;
}
Assert.IsNotNull(actual, message);
Assert.AreEqual(expected.Length, actual.Length, message);
for (int i = 0; i < expected.Length; i++)
{
Assert.AreEqual(expected[i], actual[i], message);
}
}
public static void AreEqual<T>(T[] expected, T[] actual, string message, params object[] parameters)
{
if (expected == null)
{
Assert.IsNull(actual, message, parameters);
return;
}
Assert.IsNotNull(actual, message, parameters);
Assert.AreEqual(expected.Length, actual.Length, message, parameters);
for (int i = 0; i < expected.Length; i++)
{
Assert.AreEqual(expected[i], actual[i], message, parameters);
}
}
}
}

View File

@@ -0,0 +1,137 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
internal static class AssertThrowsException
{
/// <summary>
/// Tests whether the code specified by delegate <paramref name="action" /> throws exact given
/// exception of type <typeparamref name="T" /> (and not of derived type) and throws <code>
/// AssertFailedException
/// </code> if code does not throws exception or throws exception of type other than
/// <typeparamref name="T" />.
/// </summary>
/// <param name="action">Delegate to code to be tested and which is expected to throw exception.</param>
/// <typeparam name="T">Type of exception expected to be thrown.</typeparam>
/// <exception cref="T:Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException">
/// Thrown if
/// <paramref name="action" /> does not throws exception of type <typeparamref name="T" />.
/// </exception>
/// <returns>The exception that was thrown.</returns>
public static T ThrowsException<T>(Action action)
where T : Exception
{
return AssertThrowsException.ThrowsException<T>(action, string.Empty, null);
}
/// <summary>
/// Tests whether the code specified by delegate <paramref name="action" /> throws exact given
/// exception of type <typeparamref name="T" /> (and not of derived type) and throws <code>
/// AssertFailedException
/// </code> if code does not throws exception or throws exception of type other than
/// <typeparamref name="T" />.
/// </summary>
/// <param name="action">Delegate to code to be tested and which is expected to throw exception.</param>
/// <param name="message">
/// The message to include in the exception when <paramref name="action" /> does
/// not throws exception of type <typeparamref name="T" />.
/// </param>
/// <typeparam name="T">Type of exception expected to be thrown.</typeparam>
/// <exception cref="T:Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException">
/// Thrown if
/// <paramref name="action" /> does not throws exception of type <typeparamref name="T" />.
/// </exception>
/// <returns>The exception that was thrown.</returns>
public static T ThrowsException<T>(Action action, string message)
where T : Exception
{
return AssertThrowsException.ThrowsException<T>(action, message, null);
}
/// <summary>
/// Tests whether the code specified by delegate <paramref name="action" /> throws exact given
/// exception of type <typeparamref name="T" /> (and not of derived type) and throws <code>
/// AssertFailedException
/// </code> if code does not throws exception or throws exception of type other than
/// <typeparamref name="T" />.
/// </summary>
/// <param name="action">Delegate to code to be tested and which is expected to throw exception.</param>
/// <param name="message">
/// The message to include in the exception when <paramref name="action" /> does
/// not throws exception of type <typeparamref name="T" />.
/// </param>
/// <param name="parameters">An array of parameters to use when formatting <paramref name="message" />.</param>
/// <typeparam name="T">Type of exception expected to be thrown.</typeparam>
/// <exception cref="T:Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException">
/// Thrown if
/// <paramref name="action" /> does not throws exception of type <typeparamref name="T" />.
/// </exception>
/// <returns>The exception that was thrown.</returns>
public static T ThrowsException<T>(Action action, string message, params object[] parameters)
where T : Exception
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
try
{
action();
}
catch (Exception ex)
{
if (typeof(T) != ex.GetType())
{
Assert.Fail(
string.Format(
"Threw exception {2}, but exception {1} was expected. {0}\nException Message: {3}\nStack Trace: {4}",
(object)AssertThrowsException.ReplaceNulls(message),
(object)typeof(T).Name,
(object)ex.GetType().Name,
(object)ex.Message,
(object)ex.StackTrace),
parameters);
}
return (T)ex;
}
Assert.Fail(
string.Format(
"No exception thrown. {1} exception was expected. {0}",
AssertThrowsException.ReplaceNulls(message),
typeof(T).Name),
parameters);
return default;
}
/// <summary>
/// Safely converts an object to a string, handling null values and null characters. Null
/// values are converted to "(null)". Null characters are converted to "\\0".
/// </summary>
/// <param name="input">The object to convert to a string.</param>
/// <returns>The converted string.</returns>
internal static string ReplaceNulls(object input)
{
string input1 = input?.ToString();
if (input1 == null)
{
return "(null)";
}
return Assert.ReplaceNullChars(input1);
}
}
}

View File

@@ -0,0 +1,494 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Public Fields
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
[TestClass]
[SuppressMessage("Naming", "DontUseVarForVariableTypes", Justification = "The types here are anonymous.")]
[DeploymentItem(CrossVersioningUnitTests.SchemaFile, "TestData")]
[DeploymentItem(CrossVersioningUnitTests.ExpectedFile, "TestData")]
public sealed class CrossVersioningUnitTests
{
private const string SchemaFile = @"TestData\CrossVersioningSchema.json";
private const string ExpectedFile = @"TestData\CrossVersioningExpected.json";
private static readonly DateTime SampleDateTime = DateTime.Parse("2018-08-14 02:05:00.0000000");
private static readonly Guid SampleGuid = Guid.Parse("{2A9C25B9-922E-4611-BB0A-244A9496503C}");
private static readonly Float128 SampleFloat128 = new Float128(0, 42);
private static readonly UnixDateTime SampleUnixDateTime = new UnixDateTime(42);
private static readonly MongoDbObjectId SampleMongoDbObjectId = new MongoDbObjectId(704643072U, 0); // 42 in big-endian
private Namespace schema;
private LayoutResolver resolver;
private Expected expected;
[TestInitialize]
public void ParseNamespace()
{
string json = File.ReadAllText(CrossVersioningUnitTests.SchemaFile);
this.schema = Namespace.Parse(json);
json = File.ReadAllText(CrossVersioningUnitTests.ExpectedFile);
this.expected = JsonConvert.DeserializeObject<Expected>(json);
this.resolver = new LayoutResolverNamespace(this.schema);
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionWriteFixed()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Fixed").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<WriteRowDispatcher>(layout, this.resolver);
d.LayoutCodeSwitch("null");
d.LayoutCodeSwitch("bool", value: true);
d.LayoutCodeSwitch("int8", value: (sbyte)-86);
d.LayoutCodeSwitch("int16", value: (short)-21846);
d.LayoutCodeSwitch("int32", value: -1431655766);
d.LayoutCodeSwitch("int64", value: -6148914691236517206L);
d.LayoutCodeSwitch("uint8", value: (byte)0xAA);
d.LayoutCodeSwitch("uint16", value: (ushort)0xAAAA);
d.LayoutCodeSwitch("uint32", value: 0xAAAAAAAA);
d.LayoutCodeSwitch("uint64", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("float32", value: 1.0F / 3.0F);
d.LayoutCodeSwitch("float64", value: 1.0 / 3.0);
d.LayoutCodeSwitch("float128", value: CrossVersioningUnitTests.SampleFloat128);
d.LayoutCodeSwitch("decimal", value: 1.0M / 3.0M);
d.LayoutCodeSwitch("datetime", value: CrossVersioningUnitTests.SampleDateTime);
d.LayoutCodeSwitch("unixdatetime", value: CrossVersioningUnitTests.SampleUnixDateTime);
d.LayoutCodeSwitch("guid", value: CrossVersioningUnitTests.SampleGuid);
d.LayoutCodeSwitch("mongodbobjectid", value: CrossVersioningUnitTests.SampleMongoDbObjectId);
d.LayoutCodeSwitch("utf8", value: "abc");
d.LayoutCodeSwitch("binary", value: new[] { (byte)0, (byte)1, (byte)2 });
Assert.AreEqual(this.expected.CrossVersionFixed, d.RowToHex());
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionReadFixed()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Fixed").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.ReadFrom<ReadRowDispatcher>(this.resolver, this.expected.CrossVersionFixed);
d.LayoutCodeSwitch("null");
d.LayoutCodeSwitch("bool", value: true);
d.LayoutCodeSwitch("int8", value: (sbyte)-86);
d.LayoutCodeSwitch("int16", value: (short)-21846);
d.LayoutCodeSwitch("int32", value: -1431655766);
d.LayoutCodeSwitch("int64", value: -6148914691236517206L);
d.LayoutCodeSwitch("uint8", value: (byte)0xAA);
d.LayoutCodeSwitch("uint16", value: (ushort)0xAAAA);
d.LayoutCodeSwitch("uint32", value: 0xAAAAAAAA);
d.LayoutCodeSwitch("uint64", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("float32", value: 1.0F / 3.0F);
d.LayoutCodeSwitch("float64", value: 1.0 / 3.0);
d.LayoutCodeSwitch("float128", value: CrossVersioningUnitTests.SampleFloat128);
d.LayoutCodeSwitch("decimal", value: 1.0M / 3.0M);
d.LayoutCodeSwitch("datetime", value: CrossVersioningUnitTests.SampleDateTime);
d.LayoutCodeSwitch("unixdatetime", value: CrossVersioningUnitTests.SampleUnixDateTime);
d.LayoutCodeSwitch("guid", value: CrossVersioningUnitTests.SampleGuid);
d.LayoutCodeSwitch("mongodbobjectid", value: CrossVersioningUnitTests.SampleMongoDbObjectId);
d.LayoutCodeSwitch("utf8", value: "abc");
d.LayoutCodeSwitch("binary", value: new[] { (byte)0, (byte)1, (byte)2 });
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionWriteNullFixed()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Fixed").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<WriteRowDispatcher>(layout, this.resolver);
Assert.AreEqual(this.expected.CrossVersionNullFixed, d.RowToHex());
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionReadNullFixed()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Fixed").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<NullRowDispatcher>(layout, this.resolver);
d.LayoutCodeSwitch("null");
d.LayoutCodeSwitch("bool");
d.LayoutCodeSwitch("int8");
d.LayoutCodeSwitch("int16");
d.LayoutCodeSwitch("int32");
d.LayoutCodeSwitch("int64");
d.LayoutCodeSwitch("uint8");
d.LayoutCodeSwitch("uint16");
d.LayoutCodeSwitch("uint32");
d.LayoutCodeSwitch("uint64");
d.LayoutCodeSwitch("float32");
d.LayoutCodeSwitch("float64");
d.LayoutCodeSwitch("float128");
d.LayoutCodeSwitch("decimal");
d.LayoutCodeSwitch("datetime");
d.LayoutCodeSwitch("unixdatetime");
d.LayoutCodeSwitch("guid");
d.LayoutCodeSwitch("mongodbobjectid");
d.LayoutCodeSwitch("utf8");
d.LayoutCodeSwitch("binary");
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionWriteVariable()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Variable").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<WriteRowDispatcher>(layout, this.resolver);
d.LayoutCodeSwitch("varint", value: -6148914691236517206L);
d.LayoutCodeSwitch("varuint", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("utf8", value: "abc");
d.LayoutCodeSwitch("binary", value: new[] { (byte)0, (byte)1, (byte)2 });
Assert.AreEqual(this.expected.CrossVersionVariable, d.RowToHex());
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionReadVariable()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Variable").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.ReadFrom<ReadRowDispatcher>(this.resolver, this.expected.CrossVersionVariable);
d.LayoutCodeSwitch("varint", value: -6148914691236517206L);
d.LayoutCodeSwitch("varuint", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("utf8", value: "abc");
d.LayoutCodeSwitch("binary", value: new[] { (byte)0, (byte)1, (byte)2 });
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionWriteNullVariable()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Variable").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<WriteRowDispatcher>(layout, this.resolver);
Assert.AreEqual(this.expected.CrossVersionNullVariable, d.RowToHex());
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionReadNullVariable()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Variable").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<NullRowDispatcher>(layout, this.resolver);
d.LayoutCodeSwitch("varint");
d.LayoutCodeSwitch("varuint");
d.LayoutCodeSwitch("utf8");
d.LayoutCodeSwitch("binary");
}
[TestMethod]
[Owner("jthunter")]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Test code.")]
public void CrossVersionWriteSparse()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Sparse").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<WriteRowDispatcher>(layout, this.resolver);
d.LayoutCodeSwitch("null");
d.LayoutCodeSwitch("bool", value: true);
d.LayoutCodeSwitch("int8", value: (sbyte)-86);
d.LayoutCodeSwitch("int16", value: (short)-21846);
d.LayoutCodeSwitch("int32", value: -1431655766);
d.LayoutCodeSwitch("int64", value: -6148914691236517206L);
d.LayoutCodeSwitch("uint8", value: (byte)0xAA);
d.LayoutCodeSwitch("uint16", value: (ushort)0xAAAA);
d.LayoutCodeSwitch("uint32", value: 0xAAAAAAAA);
d.LayoutCodeSwitch("uint64", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("float32", value: 1.0F / 3.0F);
d.LayoutCodeSwitch("float64", value: 1.0 / 3.0);
d.LayoutCodeSwitch("float128", value: CrossVersioningUnitTests.SampleFloat128);
d.LayoutCodeSwitch("decimal", value: 1.0M / 3.0M);
d.LayoutCodeSwitch("datetime", value: CrossVersioningUnitTests.SampleDateTime);
d.LayoutCodeSwitch("unixdatetime", value: CrossVersioningUnitTests.SampleUnixDateTime);
d.LayoutCodeSwitch("guid", value: CrossVersioningUnitTests.SampleGuid);
d.LayoutCodeSwitch("mongodbobjectid", value: CrossVersioningUnitTests.SampleMongoDbObjectId);
d.LayoutCodeSwitch("utf8", value: "abc");
d.LayoutCodeSwitch("binary", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("array_t<int8>", value: new sbyte[] { -86, -86, -86 });
d.LayoutCodeSwitch("array_t<array_t<float32>>", value: new[] { new float[] { 1, 2, 3 }, new float[] { 1, 2, 3 } });
d.LayoutCodeSwitch("array_t<utf8>", value: new[] { "abc", "def", "hij" });
d.LayoutCodeSwitch("tuple<varint,int64>", value: Tuple.Create(-6148914691236517206L, -6148914691236517206L));
d.LayoutCodeSwitch("tuple<null,tuple<int8,int8>>", value: Tuple.Create(NullValue.Default, Tuple.Create((sbyte)-86, (sbyte)-86)));
d.LayoutCodeSwitch("tuple<bool,udt>", value: Tuple.Create(false, new Point(1, 2)));
d.LayoutCodeSwitch("set_t<utf8>", value: new[] { "abc", "efg", "xzy" });
d.LayoutCodeSwitch("set_t<array_t<int8>>", value: new[] { new sbyte[] { 1, 2, 3 }, new sbyte[] { 4, 5, 6 }, new sbyte[] { 7, 8, 9 } });
d.LayoutCodeSwitch("set_t<set_t<int32>>", value: new[] { new[] { 1, 2, 3 }, new[] { 4, 5, 6 }, new[] { 7, 8, 9 } });
d.LayoutCodeSwitch("set_t<udt>", value: new[] { new Point(1, 2), new Point(3, 4), new Point(5, 6) });
d.LayoutCodeSwitch("map_t<utf8,utf8>", value: new[] { Tuple.Create("Mark", "Luke"), Tuple.Create("Harrison", "Han") });
d.LayoutCodeSwitch(
"map_t<int8,array_t<int8>>",
value: new[] { Tuple.Create((sbyte)1, new sbyte[] { 1, 2, 3 }), Tuple.Create((sbyte)2, new sbyte[] { 4, 5, 6 }) });
d.LayoutCodeSwitch(
"map_t<int16,map_t<int32,int32>>",
value: new[]
{
Tuple.Create((short)1, new[] { Tuple.Create(1, 2), Tuple.Create(3, 4) }),
Tuple.Create((short)2, new[] { Tuple.Create(5, 6), Tuple.Create(7, 8) }),
});
d.LayoutCodeSwitch(
"map_t<float64,udt>",
value: new[]
{
Tuple.Create(1.0, new Point(1, 2)),
Tuple.Create(2.0, new Point(3, 4)),
Tuple.Create(3.0, new Point(5, 6)),
});
Assert.AreEqual(this.expected.CrossVersionSparse, d.RowToHex());
}
[TestMethod]
[Owner("jthunter")]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Test code.")]
public void CrossVersionReadSparse()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Sparse").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.ReadFrom<ReadRowDispatcher>(this.resolver, this.expected.CrossVersionSparse);
d.LayoutCodeSwitch("null");
d.LayoutCodeSwitch("bool", value: true);
d.LayoutCodeSwitch("int8", value: (sbyte)-86);
d.LayoutCodeSwitch("int16", value: (short)-21846);
d.LayoutCodeSwitch("int32", value: -1431655766);
d.LayoutCodeSwitch("int64", value: -6148914691236517206L);
d.LayoutCodeSwitch("uint8", value: (byte)0xAA);
d.LayoutCodeSwitch("uint16", value: (ushort)0xAAAA);
d.LayoutCodeSwitch("uint32", value: 0xAAAAAAAA);
d.LayoutCodeSwitch("uint64", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("float32", value: 1.0F / 3.0F);
d.LayoutCodeSwitch("float64", value: 1.0 / 3.0);
d.LayoutCodeSwitch("float128", value: CrossVersioningUnitTests.SampleFloat128);
d.LayoutCodeSwitch("decimal", value: 1.0M / 3.0M);
d.LayoutCodeSwitch("datetime", value: CrossVersioningUnitTests.SampleDateTime);
d.LayoutCodeSwitch("unixdatetime", value: CrossVersioningUnitTests.SampleUnixDateTime);
d.LayoutCodeSwitch("guid", value: CrossVersioningUnitTests.SampleGuid);
d.LayoutCodeSwitch("mongodbobjectid", value: CrossVersioningUnitTests.SampleMongoDbObjectId);
d.LayoutCodeSwitch("utf8", value: "abc");
d.LayoutCodeSwitch("binary", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("array_t<int8>", value: new sbyte[] { -86, -86, -86 });
d.LayoutCodeSwitch("array_t<array_t<float32>>", value: new[] { new float[] { 1, 2, 3 }, new float[] { 1, 2, 3 } });
d.LayoutCodeSwitch("array_t<utf8>", value: new[] { "abc", "def", "hij" });
d.LayoutCodeSwitch("tuple<varint,int64>", value: Tuple.Create(-6148914691236517206L, -6148914691236517206L));
d.LayoutCodeSwitch("tuple<null,tuple<int8,int8>>", value: Tuple.Create(NullValue.Default, Tuple.Create((sbyte)-86, (sbyte)-86)));
d.LayoutCodeSwitch("tuple<bool,udt>", value: Tuple.Create(false, new Point(1, 2)));
d.LayoutCodeSwitch("set_t<utf8>", value: new[] { "abc", "efg", "xzy" });
d.LayoutCodeSwitch("set_t<array_t<int8>>", value: new[] { new sbyte[] { 1, 2, 3 }, new sbyte[] { 4, 5, 6 }, new sbyte[] { 7, 8, 9 } });
d.LayoutCodeSwitch("set_t<set_t<int32>>", value: new[] { new[] { 1, 2, 3 }, new[] { 4, 5, 6 }, new[] { 7, 8, 9 } });
d.LayoutCodeSwitch("set_t<udt>", value: new[] { new Point(1, 2), new Point(3, 4), new Point(5, 6) });
d.LayoutCodeSwitch("map_t<utf8,utf8>", value: new[] { Tuple.Create("Mark", "Luke"), Tuple.Create("Harrison", "Han") });
d.LayoutCodeSwitch(
"map_t<int8,array_t<int8>>",
value: new[] { Tuple.Create((sbyte)1, new sbyte[] { 1, 2, 3 }), Tuple.Create((sbyte)2, new sbyte[] { 4, 5, 6 }) });
d.LayoutCodeSwitch(
"map_t<int16,map_t<int32,int32>>",
value: new[]
{
Tuple.Create((short)1, new[] { Tuple.Create(1, 2), Tuple.Create(3, 4) }),
Tuple.Create((short)2, new[] { Tuple.Create(5, 6), Tuple.Create(7, 8) }),
});
d.LayoutCodeSwitch(
"map_t<float64,udt>",
value: new[]
{
Tuple.Create(2.0, new Point(3, 4)),
Tuple.Create(3.0, new Point(5, 6)),
Tuple.Create(1.0, new Point(1, 2)),
});
}
[TestMethod]
[Owner("jthunter")]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Test code.")]
public void CrossVersionDeleteSparse()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Sparse").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.ReadFrom<DeleteRowDispatcher>(this.resolver, this.expected.CrossVersionSparse);
d.LayoutCodeSwitch("null");
d.LayoutCodeSwitch("bool", value: true);
d.LayoutCodeSwitch("int8", value: (sbyte)-86);
d.LayoutCodeSwitch("int16", value: (short)-21846);
d.LayoutCodeSwitch("int32", value: -1431655766);
d.LayoutCodeSwitch("int64", value: -6148914691236517206L);
d.LayoutCodeSwitch("uint8", value: (byte)0xAA);
d.LayoutCodeSwitch("uint16", value: (ushort)0xAAAA);
d.LayoutCodeSwitch("uint32", value: 0xAAAAAAAA);
d.LayoutCodeSwitch("uint64", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("float32", value: 1.0F / 3.0F);
d.LayoutCodeSwitch("float64", value: 1.0 / 3.0);
d.LayoutCodeSwitch("float128", value: CrossVersioningUnitTests.SampleFloat128);
d.LayoutCodeSwitch("decimal", value: 1.0M / 3.0M);
d.LayoutCodeSwitch("datetime", value: CrossVersioningUnitTests.SampleDateTime);
d.LayoutCodeSwitch("unixdatetime", value: CrossVersioningUnitTests.SampleUnixDateTime);
d.LayoutCodeSwitch("guid", value: CrossVersioningUnitTests.SampleGuid);
d.LayoutCodeSwitch("mongodbobjectid", value: CrossVersioningUnitTests.SampleMongoDbObjectId);
d.LayoutCodeSwitch("utf8", value: "abc");
d.LayoutCodeSwitch("binary", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("array_t<int8>", value: new sbyte[] { -86, -86, -86 });
d.LayoutCodeSwitch("array_t<array_t<float32>>", value: new[] { new float[] { 1, 2, 3 }, new float[] { 1, 2, 3 } });
d.LayoutCodeSwitch("array_t<utf8>", value: new[] { "abc", "def", "hij" });
d.LayoutCodeSwitch("tuple<varint,int64>", value: Tuple.Create(-6148914691236517206L, -6148914691236517206L));
d.LayoutCodeSwitch("tuple<null,tuple<int8,int8>>", value: Tuple.Create(NullValue.Default, Tuple.Create((sbyte)-86, (sbyte)-86)));
d.LayoutCodeSwitch("tuple<bool,udt>", value: Tuple.Create(false, new Point(1, 2)));
d.LayoutCodeSwitch("set_t<utf8>", value: new[] { "abc", "efg", "xzy" });
d.LayoutCodeSwitch("set_t<array_t<int8>>", value: new[] { new sbyte[] { 1, 2, 3 }, new sbyte[] { 4, 5, 6 }, new sbyte[] { 7, 8, 9 } });
d.LayoutCodeSwitch("set_t<set_t<int32>>", value: new[] { new[] { 1, 2, 3 }, new[] { 4, 5, 6 }, new[] { 7, 8, 9 } });
d.LayoutCodeSwitch("set_t<udt>", value: new[] { new Point(1, 2), new Point(3, 4), new Point(5, 6) });
d.LayoutCodeSwitch("map_t<utf8,utf8>", value: new[] { Tuple.Create("Mark", "Luke"), Tuple.Create("Harrison", "Han") });
d.LayoutCodeSwitch(
"map_t<int8,array_t<int8>>",
value: new[] { Tuple.Create((sbyte)1, new sbyte[] { 1, 2, 3 }), Tuple.Create((sbyte)2, new sbyte[] { 4, 5, 6 }) });
d.LayoutCodeSwitch(
"map_t<int16,map_t<int32,int32>>",
value: new[]
{
Tuple.Create((short)1, new[] { Tuple.Create(1, 2), Tuple.Create(3, 4) }),
Tuple.Create((short)2, new[] { Tuple.Create(5, 6), Tuple.Create(7, 8) }),
});
d.LayoutCodeSwitch(
"map_t<float64,udt>",
value: new[]
{
Tuple.Create(1.0, new Point(1, 2)),
Tuple.Create(2.0, new Point(3, 4)),
Tuple.Create(3.0, new Point(5, 6)),
});
Assert.AreEqual(this.expected.CrossVersionNullSparse, d.RowToHex());
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionWriteNullSparse()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Sparse").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<WriteRowDispatcher>(layout, this.resolver);
Assert.AreEqual(this.expected.CrossVersionNullSparse, d.RowToHex());
}
[TestMethod]
[Owner("jthunter")]
public void CrossVersionReadNullSparse()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Sparse").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.ReadFrom<NullRowDispatcher>(this.resolver, this.expected.CrossVersionNullSparse);
d.LayoutCodeSwitch("null");
d.LayoutCodeSwitch("bool");
d.LayoutCodeSwitch("int8");
d.LayoutCodeSwitch("int16");
d.LayoutCodeSwitch("int32");
d.LayoutCodeSwitch("int64");
d.LayoutCodeSwitch("uint8");
d.LayoutCodeSwitch("uint16");
d.LayoutCodeSwitch("uint32");
d.LayoutCodeSwitch("uint64");
d.LayoutCodeSwitch("float32");
d.LayoutCodeSwitch("float64");
d.LayoutCodeSwitch("float128");
d.LayoutCodeSwitch("decimal");
d.LayoutCodeSwitch("datetime");
d.LayoutCodeSwitch("unixdatetime");
d.LayoutCodeSwitch("guid");
d.LayoutCodeSwitch("mongodbobjectid");
d.LayoutCodeSwitch("utf8");
d.LayoutCodeSwitch("binary");
}
private sealed class Point : IDispatchable
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Point point && this.Equals(point);
}
public override int GetHashCode()
{
unchecked
{
return (this.X.GetHashCode() * 397) ^ this.Y.GetHashCode();
}
}
void IDispatchable.Dispatch(ref RowOperationDispatcher dispatcher, ref RowCursor scope)
{
dispatcher.LayoutCodeSwitch(ref scope, "x", value: this.X);
dispatcher.LayoutCodeSwitch(ref scope, "y", value: this.Y);
}
private bool Equals(Point other)
{
return this.X == other.X && this.Y == other.Y;
}
}
[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Instantiated through Reflection.")]
private sealed class Expected
{
public string CrossVersionFixed { get; set; }
public string CrossVersionNullFixed { get; set; }
public string CrossVersionVariable { get; set; }
public string CrossVersionNullVariable { get; set; }
public string CrossVersionSparse { get; set; }
public string CrossVersionNullSparse { get; set; }
}
}
}

View File

@@ -0,0 +1,481 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema;
using Microsoft.VisualStudio.TestTools.UnitTesting;
// ReSharper disable once StringLiteralTypo
[TestClass]
[SuppressMessage("Naming", "DontUseVarForVariableTypes", Justification = "The types here are anonymous.")]
[DeploymentItem(@"TestData\CustomerSchema.json", "TestData")]
public sealed class CustomerExampleUnitTests
{
private readonly Hotel hotelExample = new Hotel()
{
Id = "The-Westin-St-John-Resort-Villas-1187",
Name = "The Westin St. John Resort Villas",
Phone = "+1 340-693-8000",
Address = new Address
{
Street = "300B Chocolate Hole",
City = "Great Cruz Bay",
State = "VI",
PostalCode = new PostalCode
{
Zip = 00830,
Plus4 = 0001,
},
},
};
private Namespace customerSchema;
private LayoutResolver customerResolver;
private Layout hotelLayout;
private Layout guestLayout;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(@"TestData\CustomerSchema.json");
this.customerSchema = Namespace.Parse(json);
this.customerResolver = new LayoutResolverNamespace(this.customerSchema);
this.hotelLayout = this.customerResolver.Resolve(this.customerSchema.Schemas.Find(x => x.Name == "Hotels").SchemaId);
this.guestLayout = this.customerResolver.Resolve(this.customerSchema.Schemas.Find(x => x.Name == "Guests").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void CreateHotel()
{
RowBuffer row = new RowBuffer(0);
row.InitLayout(HybridRowVersion.V1, this.hotelLayout, this.customerResolver);
Hotel h1 = this.hotelExample;
RowCursor root = RowCursor.Create(ref row);
this.WriteHotel(ref row, ref root, h1);
root = RowCursor.Create(ref row);
Hotel h2 = this.ReadHotel(ref row, ref root);
Assert.AreEqual(h1, h2);
}
[TestMethod]
[Owner("jthunter")]
public void FrozenHotel()
{
RowBuffer row = new RowBuffer(0);
row.InitLayout(HybridRowVersion.V1, this.hotelLayout, this.customerResolver);
Hotel h1 = this.hotelExample;
RowCursor root = RowCursor.Create(ref row);
this.WriteHotel(ref row, ref root, h1);
root = RowCursor.Create(ref row);
ResultAssert.InsufficientPermissions(this.PartialUpdateHotelAddress(ref row, ref root, new Address { Street = "300B Brownie Way" }));
}
[TestMethod]
[Owner("jthunter")]
public void CreateGuest()
{
RowBuffer row = new RowBuffer(1024 * 1024);
row.InitLayout(HybridRowVersion.V1, this.guestLayout, this.customerResolver);
Guest g1 = new Guest()
{
Id = Guid.Parse("64d9d6d3-fd6b-4556-8c6e-d960a7ece7b9"),
FirstName = "John",
LastName = "Adams",
Title = "President of the United States",
PhoneNumbers = new List<string> { "(202) 456-1111" },
ConfirmNumber = "(202) 456-1111",
Emails = new SortedSet<string> { "president@whitehouse.gov" },
Addresses = new Dictionary<string, Address>
{
["home"] = new Address
{
Street = "1600 Pennsylvania Avenue NW",
City = "Washington, D.C.",
State = "DC",
PostalCode = new PostalCode
{
Zip = 20500,
Plus4 = 0001,
},
},
},
};
RowCursor rc1 = RowCursor.Create(ref row);
this.WriteGuest(ref row, ref rc1, g1);
RowCursor rc2 = RowCursor.Create(ref row);
Guest g2 = this.ReadGuest(ref row, ref rc2);
Assert.AreEqual(g1, g2);
// Append an item to an existing list.
RowCursor rc3 = RowCursor.Create(ref row);
int index = this.AppendGuestEmail(ref row, ref rc3, "vice_president@whitehouse.gov");
Assert.AreEqual(1, index);
g1.Emails.Add("vice_president@whitehouse.gov");
RowCursor rc4 = RowCursor.Create(ref row);
g2 = this.ReadGuest(ref row, ref rc4);
Assert.AreEqual(g1, g2);
// Prepend an item to an existing list.
RowCursor rc5 = RowCursor.Create(ref row);
index = this.PrependGuestEmail(ref row, ref rc5, "ex_president@whitehouse.gov");
Assert.AreEqual(0, index);
g1.Emails = new SortedSet<string> { "ex_president@whitehouse.gov", "president@whitehouse.gov", "vice_president@whitehouse.gov" };
RowCursor rc6 = RowCursor.Create(ref row);
g2 = this.ReadGuest(ref row, ref rc6);
Assert.AreEqual(g1, g2);
// InsertAt an item to an existing list.
RowCursor rc7 = RowCursor.Create(ref row);
index = this.InsertAtGuestEmail(ref row, ref rc7, 1, "future_president@whitehouse.gov");
Assert.AreEqual(1, index);
g1.Emails = new SortedSet<string>
{
"ex_president@whitehouse.gov",
"future_president@whitehouse.gov",
"president@whitehouse.gov",
"vice_president@whitehouse.gov",
};
RowCursor rc8 = RowCursor.Create(ref row);
g2 = this.ReadGuest(ref row, ref rc8);
Assert.AreEqual(g1, g2);
}
private static Address ReadAddress(ref RowBuffer row, ref RowCursor addressScope)
{
Address a = new Address();
Layout addressLayout = addressScope.Layout;
Assert.IsTrue(addressLayout.TryFind("street", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref addressScope, c, out a.Street));
Assert.IsTrue(addressLayout.TryFind("city", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref addressScope, c, out a.City));
Assert.IsTrue(addressLayout.TryFind("state", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadFixed(ref row, ref addressScope, c, out a.State));
Assert.IsTrue(addressLayout.TryFind("postal_code", out c));
addressScope.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUDT>().ReadScope(ref row, ref addressScope, out RowCursor postalCodeScope));
a.PostalCode = CustomerExampleUnitTests.ReadPostalCode(ref row, ref postalCodeScope);
addressScope.Skip(ref row, ref postalCodeScope);
return a;
}
private static PostalCode ReadPostalCode(ref RowBuffer row, ref RowCursor postalCodeScope)
{
Layout postalCodeLayout = postalCodeScope.Layout;
PostalCode pc = new PostalCode();
Assert.IsTrue(postalCodeLayout.TryFind("zip", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutInt32>().ReadFixed(ref row, ref postalCodeScope, c, out pc.Zip));
Assert.IsTrue(postalCodeLayout.TryFind("plus4", out c));
postalCodeScope.Find(ref row, c.Path);
if (c.TypeAs<LayoutInt16>().ReadSparse(ref row, ref postalCodeScope, out short plus4) == Result.Success)
{
pc.Plus4 = plus4;
}
return pc;
}
private void WriteHotel(ref RowBuffer row, ref RowCursor root, Hotel h)
{
Assert.IsTrue(this.hotelLayout.TryFind("hotel_id", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, h.Id));
Assert.IsTrue(this.hotelLayout.TryFind("name", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, h.Name));
Assert.IsTrue(this.hotelLayout.TryFind("phone", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, h.Phone));
Assert.IsTrue(this.hotelLayout.TryFind("address", out c));
root.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUDT>().WriteScope(ref row, ref root, c.TypeArgs, out RowCursor addressScope));
this.WriteAddress(ref row, ref addressScope, c.TypeArgs, h.Address);
root.Skip(ref row, ref addressScope);
}
private void WriteGuest(ref RowBuffer row, ref RowCursor root, Guest g)
{
Assert.IsTrue(this.guestLayout.TryFind("guest_id", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutGuid>().WriteFixed(ref row, ref root, c, g.Id));
Assert.IsTrue(this.guestLayout.TryFind("first_name", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, g.FirstName));
Assert.IsTrue(this.guestLayout.TryFind("last_name", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, g.LastName));
Assert.IsTrue(this.guestLayout.TryFind("title", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, g.Title));
Assert.IsTrue(this.guestLayout.TryFind("confirm_number", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, g.ConfirmNumber));
if (g.Emails != null)
{
Assert.IsTrue(this.guestLayout.TryFind("emails", out c));
root.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref root, c.TypeArgs, out RowCursor emailScope));
foreach (string email in g.Emails)
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref emailScope, email));
Assert.IsFalse(emailScope.MoveNext(ref row));
}
root.Skip(ref row, ref emailScope);
}
if (g.PhoneNumbers != null)
{
Assert.IsTrue(this.guestLayout.TryFind("phone_numbers", out c));
root.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref root, c.TypeArgs, out RowCursor phoneNumbersScope));
foreach (string phone in g.PhoneNumbers)
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref phoneNumbersScope, phone));
Assert.IsFalse(phoneNumbersScope.MoveNext(ref row));
}
root.Skip(ref row, ref phoneNumbersScope);
}
if (g.Addresses != null)
{
Assert.IsTrue(this.guestLayout.TryFind("addresses", out c));
root.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref root, c.TypeArgs, out RowCursor addressesScope));
TypeArgument tupleType = c.TypeAs<LayoutUniqueScope>().FieldType(ref addressesScope);
TypeArgument t0 = tupleType.TypeArgs[0];
TypeArgument t1 = tupleType.TypeArgs[1];
foreach (KeyValuePair<string, Address> pair in g.Addresses)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(
tupleType.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref tempCursor, c.TypeArgs, out RowCursor tupleScope));
ResultAssert.IsSuccess(t0.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tupleScope, pair.Key));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(
t1.TypeAs<LayoutUDT>().WriteScope(ref row, ref tupleScope, t1.TypeArgs, out RowCursor addressScope));
this.WriteAddress(ref row, ref addressScope, t1.TypeArgs, pair.Value);
Assert.IsFalse(tupleScope.MoveNext(ref row, ref addressScope));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref addressesScope, ref tempCursor));
}
root.Skip(ref row, ref addressesScope);
}
}
private int AppendGuestEmail(ref RowBuffer row, ref RowCursor root, string email)
{
Assert.IsTrue(this.guestLayout.TryFind("emails", out LayoutColumn c));
root.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref root, out RowCursor emailScope));
Assert.IsFalse(emailScope.MoveTo(ref row, int.MaxValue));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref emailScope, email));
return emailScope.Index;
}
private int PrependGuestEmail(ref RowBuffer row, ref RowCursor root, string email)
{
Assert.IsTrue(this.guestLayout.TryFind("emails", out LayoutColumn c));
root.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref root, out RowCursor emailScope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref emailScope, email, UpdateOptions.InsertAt));
return emailScope.Index;
}
private int InsertAtGuestEmail(ref RowBuffer row, ref RowCursor root, int i, string email)
{
Assert.IsTrue(this.guestLayout.TryFind("emails", out LayoutColumn c));
root.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref root, out RowCursor emailScope));
Assert.IsTrue(emailScope.MoveTo(ref row, i));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref emailScope, email, UpdateOptions.InsertAt));
return emailScope.Index;
}
private void WriteAddress(ref RowBuffer row, ref RowCursor addressScope, TypeArgumentList typeArgs, Address a)
{
Layout addressLayout = this.customerResolver.Resolve(typeArgs.SchemaId);
Assert.IsTrue(addressLayout.TryFind("street", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref addressScope, c, a.Street));
Assert.IsTrue(addressLayout.TryFind("city", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref addressScope, c, a.City));
Assert.IsTrue(addressLayout.TryFind("state", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteFixed(ref row, ref addressScope, c, a.State));
Assert.IsTrue(addressLayout.TryFind("postal_code", out c));
addressScope.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUDT>().WriteScope(ref row, ref addressScope, c.TypeArgs, out RowCursor postalCodeScope));
this.WritePostalCode(ref row, ref postalCodeScope, c.TypeArgs, a.PostalCode);
addressScope.Skip(ref row, ref postalCodeScope);
}
private Result PartialUpdateHotelAddress(ref RowBuffer row, ref RowCursor root, Address a)
{
Assert.IsTrue(this.hotelLayout.TryFind("address", out LayoutColumn c));
root.Find(ref row, c.Path);
Result r = c.TypeAs<LayoutUDT>().ReadScope(ref row, ref root, out RowCursor addressScope);
if (r != Result.Success)
{
return r;
}
Layout addressLayout = addressScope.Layout;
if (a.Street != null)
{
Assert.IsTrue(addressLayout.TryFind("street", out c));
r = c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref addressScope, c, a.Street);
if (r != Result.Success)
{
return r;
}
}
if (a.City != null)
{
Assert.IsTrue(addressLayout.TryFind("city", out c));
r = c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref addressScope, c, a.City);
if (r != Result.Success)
{
return r;
}
}
if (a.State != null)
{
Assert.IsTrue(addressLayout.TryFind("state", out c));
r = c.TypeAs<LayoutUtf8>().WriteFixed(ref row, ref addressScope, c, a.State);
if (r != Result.Success)
{
return r;
}
}
if (a.PostalCode != null)
{
Assert.IsTrue(addressLayout.TryFind("postal_code", out c));
addressScope.Find(ref row, c.Path);
r = c.TypeAs<LayoutUDT>().WriteScope(ref row, ref addressScope, c.TypeArgs, out RowCursor postalCodeScope);
if (r != Result.Success)
{
return r;
}
this.WritePostalCode(ref row, ref postalCodeScope, c.TypeArgs, a.PostalCode);
}
return Result.Success;
}
private void WritePostalCode(ref RowBuffer row, ref RowCursor postalCodeScope, TypeArgumentList typeArgs, PostalCode pc)
{
Layout postalCodeLayout = this.customerResolver.Resolve(typeArgs.SchemaId);
Assert.IsNotNull(postalCodeLayout);
Assert.IsTrue(postalCodeLayout.TryFind("zip", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutInt32>().WriteFixed(ref row, ref postalCodeScope, c, pc.Zip));
if (pc.Plus4.HasValue)
{
Assert.IsTrue(postalCodeLayout.TryFind("plus4", out c));
postalCodeScope.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutInt16>().WriteSparse(ref row, ref postalCodeScope, pc.Plus4.Value));
}
}
private Hotel ReadHotel(ref RowBuffer row, ref RowCursor root)
{
Hotel h = new Hotel();
Assert.IsTrue(this.hotelLayout.TryFind("hotel_id", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out h.Id));
Assert.IsTrue(this.hotelLayout.TryFind("name", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out h.Name));
Assert.IsTrue(this.hotelLayout.TryFind("phone", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out h.Phone));
Assert.IsTrue(this.hotelLayout.TryFind("address", out c));
Assert.IsTrue(c.Type.Immutable);
root.Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUDT>().ReadScope(ref row, ref root, out RowCursor addressScope));
Assert.IsTrue(addressScope.Immutable);
h.Address = CustomerExampleUnitTests.ReadAddress(ref row, ref addressScope);
root.Skip(ref row, ref addressScope);
return h;
}
private Guest ReadGuest(ref RowBuffer row, ref RowCursor root)
{
Guest g = new Guest();
Assert.IsTrue(this.guestLayout.TryFind("guest_id", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutGuid>().ReadFixed(ref row, ref root, c, out g.Id));
Assert.IsTrue(this.guestLayout.TryFind("first_name", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out g.FirstName));
Assert.IsTrue(this.guestLayout.TryFind("last_name", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out g.LastName));
Assert.IsTrue(this.guestLayout.TryFind("title", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out g.Title));
Assert.IsTrue(this.guestLayout.TryFind("confirm_number", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out g.ConfirmNumber));
Assert.IsTrue(this.guestLayout.TryFind("emails", out c));
root.Clone(out RowCursor emailScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref emailScope, out emailScope) == Result.Success)
{
g.Emails = new SortedSet<string>();
while (emailScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref emailScope, out string item));
g.Emails.Add(item);
}
}
Assert.IsTrue(this.guestLayout.TryFind("phone_numbers", out c));
root.Clone(out RowCursor phoneNumbersScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref phoneNumbersScope, out phoneNumbersScope) == Result.Success)
{
g.PhoneNumbers = new List<string>();
while (phoneNumbersScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref phoneNumbersScope, out string item));
g.PhoneNumbers.Add(item);
}
}
Assert.IsTrue(this.guestLayout.TryFind("addresses", out c));
root.Clone(out RowCursor addressesScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedMap>().ReadScope(ref row, ref addressesScope, out addressesScope) == Result.Success)
{
TypeArgument tupleType = LayoutType.TypedMap.FieldType(ref addressesScope);
TypeArgument t0 = tupleType.TypeArgs[0];
TypeArgument t1 = tupleType.TypeArgs[1];
g.Addresses = new Dictionary<string, Address>();
RowCursor pairScope = default;
while (addressesScope.MoveNext(ref row, ref pairScope))
{
ResultAssert.IsSuccess(tupleType.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref addressesScope, out pairScope));
Assert.IsTrue(pairScope.MoveNext(ref row));
ResultAssert.IsSuccess(t0.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref pairScope, out string key));
Assert.IsTrue(pairScope.MoveNext(ref row));
ResultAssert.IsSuccess(t1.TypeAs<LayoutUDT>().ReadScope(ref row, ref pairScope, out RowCursor addressScope));
Address value = CustomerExampleUnitTests.ReadAddress(ref row, ref addressScope);
g.Addresses.Add(key, value);
Assert.IsFalse(pairScope.MoveNext(ref row, ref addressScope));
}
}
return g;
}
}
}

View File

@@ -0,0 +1,51 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Fields should be private
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema
{
internal sealed class Address
{
public string Street;
public string City;
public string State;
public PostalCode PostalCode;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Address && this.Equals((Address)obj);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Street != null ? this.Street.GetHashCode() : 0;
hashCode = (hashCode * 397) ^ (this.City != null ? this.City.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.State != null ? this.State.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.PostalCode != null ? this.PostalCode.GetHashCode() : 0);
return hashCode;
}
}
private bool Equals(Address other)
{
return string.Equals(this.Street, other.Street) &&
string.Equals(this.City, other.City) &&
string.Equals(this.State, other.State) &&
object.Equals(this.PostalCode, other.PostalCode);
}
}
}

View File

@@ -0,0 +1,104 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema
{
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
internal static class AddressSerializer
{
public static Result Write(ref RowWriter writer, TypeArgument typeArg, Address obj)
{
Result r;
if (obj.Street != null)
{
r = writer.WriteString("street", obj.Street);
if (r != Result.Success)
{
return r;
}
}
if (obj.City != null)
{
r = writer.WriteString("city", obj.City);
if (r != Result.Success)
{
return r;
}
}
if (obj.State != null)
{
r = writer.WriteString("state", obj.State);
if (r != Result.Success)
{
return r;
}
}
if (obj.PostalCode != null)
{
r = writer.WriteScope("postal_code", PostalCodeSerializer.TypeArg, obj.PostalCode, PostalCodeSerializer.Write);
if (r != Result.Success)
{
return r;
}
}
return Result.Success;
}
public static Result Read(ref RowReader reader, out Address obj)
{
obj = new Address();
while (reader.Read())
{
Result r;
switch (reader.Path)
{
case "street":
r = reader.ReadString(out obj.Street);
if (r != Result.Success)
{
return r;
}
break;
case "city":
r = reader.ReadString(out obj.City);
if (r != Result.Success)
{
return r;
}
break;
case "state":
r = reader.ReadString(out obj.State);
if (r != Result.Success)
{
return r;
}
break;
case "postal_code":
r = reader.ReadScope(
obj,
(ref RowReader child, Address parent) =>
PostalCodeSerializer.Read(ref child, out parent.PostalCode));
if (r != Result.Success)
{
return r;
}
break;
}
}
return Result.Success;
}
}
}

View File

@@ -0,0 +1,103 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Fields should be private
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema
{
using System;
using System.Collections.Generic;
using System.Linq;
internal sealed class Guest
{
public Guid Id;
public string FirstName;
public string LastName;
public string Title;
public ISet<string> Emails;
public IList<string> PhoneNumbers;
public IDictionary<string, Address> Addresses;
public string ConfirmNumber;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Guest && this.Equals((Guest)obj);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Id.GetHashCode();
hashCode = (hashCode * 397) ^ (this.FirstName != null ? this.FirstName.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.LastName != null ? this.LastName.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.Title != null ? this.Title.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.Emails != null ? this.Emails.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.PhoneNumbers != null ? this.PhoneNumbers.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.Addresses != null ? this.Addresses.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.ConfirmNumber != null ? this.ConfirmNumber.GetHashCode() : 0);
return hashCode;
}
}
private static bool DictionaryEquals<TKey, TValue>(IDictionary<TKey, TValue> left, IDictionary<TKey, TValue> right)
{
if (left == right)
{
return true;
}
if ((left == null) || (right == null))
{
return false;
}
if (left.Count != right.Count)
{
return false;
}
foreach (KeyValuePair<TKey, TValue> p in left)
{
TValue value;
if (!right.TryGetValue(p.Key, out value))
{
return false;
}
if (!p.Value.Equals(value))
{
return false;
}
}
return true;
}
private bool Equals(Guest other)
{
return this.Id.Equals(other.Id) &&
string.Equals(this.FirstName, other.FirstName) &&
string.Equals(this.LastName, other.LastName) &&
string.Equals(this.Title, other.Title) &&
string.Equals(this.ConfirmNumber, other.ConfirmNumber) &&
((this.Emails == other.Emails) ||
((this.Emails != null) && (other.Emails != null) && this.Emails.SetEquals(other.Emails))) &&
((this.PhoneNumbers == other.PhoneNumbers) ||
((this.PhoneNumbers != null) && (other.PhoneNumbers != null) && this.PhoneNumbers.SequenceEqual(other.PhoneNumbers))) &&
Guest.DictionaryEquals(this.Addresses, other.Addresses);
}
}
}

View File

@@ -0,0 +1,51 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Fields should be private
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema
{
internal sealed class Hotel
{
public string Id;
public string Name;
public string Phone;
public Address Address;
public bool Equals(Hotel other)
{
return string.Equals(this.Id, other.Id) &&
string.Equals(this.Name, other.Name) &&
string.Equals(this.Phone, other.Phone) &&
object.Equals(this.Address, other.Address);
}
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Hotel && this.Equals((Hotel)obj);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Id?.GetHashCode() ?? 0;
hashCode = (hashCode * 397) ^ (this.Name?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Phone?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Address?.GetHashCode() ?? 0);
return hashCode;
}
}
}
}

View File

@@ -0,0 +1,42 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Fields should be private
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema
{
internal sealed class PostalCode
{
public int Zip;
public short? Plus4;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is PostalCode && this.Equals((PostalCode)obj);
}
public override int GetHashCode()
{
unchecked
{
return (this.Zip * 397) ^ this.Plus4.GetHashCode();
}
}
private bool Equals(PostalCode other)
{
return this.Zip == other.Zip && this.Plus4 == other.Plus4;
}
}
}

View File

@@ -0,0 +1,68 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Fields should be private
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema
{
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
internal static class PostalCodeSerializer
{
public static TypeArgument TypeArg = new TypeArgument(LayoutType.UDT, new TypeArgumentList(new SchemaId(1)));
public static Result Write(ref RowWriter writer, TypeArgument typeArg, PostalCode obj)
{
Result r;
r = writer.WriteInt32("zip", obj.Zip);
if (r != Result.Success)
{
return r;
}
if (obj.Plus4.HasValue)
{
r = writer.WriteInt16("plus4", obj.Plus4.Value);
if (r != Result.Success)
{
return r;
}
}
return Result.Success;
}
public static Result Read(ref RowReader reader, out PostalCode obj)
{
obj = new PostalCode();
while (reader.Read())
{
Result r;
switch (reader.Path)
{
case "zip":
r = reader.ReadInt32(out obj.Zip);
if (r != Result.Success)
{
return r;
}
break;
case "plus4":
r = reader.ReadInt16(out short value);
if (r != Result.Success)
{
return r;
}
obj.Plus4 = value;
break;
}
}
return Result.Success;
}
}
}

View File

@@ -0,0 +1,9 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
[assembly:
System.Diagnostics.CodeAnalysis.SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1515:Single-line comment should be preceded by blank line",
Justification = "Refactor")]

View File

@@ -0,0 +1,102 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.Internal
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Internal;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class MurmurHash3UnitTests
{
private static readonly (ulong low, ulong high)[] Expected = new[]
{
(0x56F1549659CBEE1AUL, 0xCEB3EE124C3E3855UL),
(0xFE84B58886F9D717UL, 0xD24C5DE024F5EA6BUL),
(0x89F6250648BB11BFUL, 0x95595FB9D4CF58B0UL),
(0xC76AFDB39EDC6262UL, 0xB9286AF4FADAF497UL),
(0xC2CB4D9B3C9C247EUL, 0xB465D40116B8B7A2UL),
(0x317178F5B26D0B35UL, 0x1D564F53E2E468ADUL),
(0xE8D75F7C05F43F09UL, 0xA81CEA052AE92D6FUL),
(0x8F837665508C08A8UL, 0x2A74E6E47E5497BCUL),
(0x609778FDA1AFD731UL, 0x3EB1A0E3BFC653E4UL),
(0x0F59B8965FA49D1AUL, 0xCB3BC158243A5DEEUL),
(0x7A6D0AC9C98F5908UL, 0xBC93D3042C3E7178UL),
(0x863FE5AEBA9A3DFAUL, 0xDF42416658CB87C5UL),
(0xDB4C82337C8FB216UL, 0xCA7616B64ABF6B3DUL),
(0x0049223177425B48UL, 0x25510D7246BC3C2CUL),
(0x31AC129B24F82CABUL, 0xCD7174C2040E9834UL),
(0xCE39465288116345UL, 0x1CE6A26BA2E9E67DUL),
(0xD2BE55791E13DB17UL, 0xCF30BF3D93B3A9FAUL),
(0x43E323DD0F079145UL, 0xF06721555571ABBAUL),
(0xB0CE9F170A96F5BCUL, 0x18EE95960369D702UL),
(0xBFFAF6BEBC84A2A9UL, 0xE0612B6FC0C9D502UL),
(0x33E2D699697BC2DAUL, 0xB7E9CD6313DE05EEUL),
(0xCBFD7D8DA2A962BFUL, 0xCF4C281A7750E88AUL),
(0xBD8D863F83863088UL, 0x01AFFBDE3D405D35UL),
(0xBA2E05DF3328C7DBUL, 0x9620867ADDFE6579UL),
(0xC57BD1FB63CA0947UL, 0xE1391F8454D4EA9FUL),
(0x6AB710460A5BF9BAUL, 0x11D7E13FBEF63775UL),
(0x55C2C7C95F41C483UL, 0xA4DCC9F547A89563UL),
(0x8AA5A2031027F216UL, 0x1653FC7AD6CC6104UL),
(0xAD8A899FF093D9A5UL, 0x0EB26F6D1CCEB258UL),
(0xA3B6D57EBEB965D1UL, 0xE8078FCC5D8C2E3EUL),
(0x91ABF587B38224F6UL, 0x35899665A8A9252CUL),
(0xF05B1AF0487EE2D4UL, 0x5D7496C1665DDE12UL),
};
[TestMethod]
[Owner("jthunter")]
public void Hash128Check()
{
// Generate deterministic data for which the MurmurHash3 is known (see Expected).
Random rand = new Random(42);
byte[][] samples = new byte[MurmurHash3UnitTests.Expected.Length][];
for (int i = 0; i < samples.Length; i++)
{
int sampleLength = rand.Next(10 * 1024);
samples[i] = new byte[sampleLength];
rand.NextBytes(samples[i]);
}
// Warm up the loop and verify correctness.
for (int i = 0; i < samples.Length; i++)
{
byte[] sample = samples[i];
(ulong low, ulong high) = MurmurHash3.Hash128(sample, (0, 0));
Console.WriteLine($"(0x{high:X16}UL, 0x{low:X16}UL),");
Assert.AreEqual(MurmurHash3UnitTests.Expected[i].high, high);
Assert.AreEqual(MurmurHash3UnitTests.Expected[i].low, low);
}
// Measure performance.
long ticks = MurmurHash3UnitTests.MeasureLoop(samples);
Console.WriteLine($"MurmurHash3: {ticks}");
}
[MethodImpl(MethodImplOptions.NoInlining)]
[SuppressMessage("Microsoft.Reliability", "CA2001:Avoid calling problematic methods", Justification = "Perf Benchmark")]
private static long MeasureLoop(byte[][] samples)
{
const int outerLoopCount = 10000;
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
GC.Collect();
watch.Start();
for (int j = 0; j < outerLoopCount; j++)
{
foreach (byte[] sample in samples)
{
MurmurHash3.Hash128(sample, (0, 0));
}
}
watch.Stop();
return watch.ElapsedTicks;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class LayoutTypeUnitTests
{
[TestMethod]
[Owner("jthunter")]
public void LayoutTypeTest()
{
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Boolean);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Int8);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Int16);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Int32);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Int64);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.UInt8);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.UInt16);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.UInt32);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.UInt64);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.VarInt);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.VarUInt);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Float32);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Float64);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Decimal);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Null);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Boolean);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.DateTime);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Guid);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Utf8);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Binary);
LayoutTypeUnitTests.TestLayoutTypeApi(LayoutType.Object);
}
private static void TestLayoutTypeApi(LayoutType t)
{
Assert.IsNotNull(t.Name);
Assert.IsFalse(string.IsNullOrWhiteSpace(t.Name));
Assert.AreNotSame(null, t.IsFixed, t.Name);
Assert.AreNotSame(null, t.AllowVariable, t.Name);
Assert.AreNotSame(null, t.IsBool, t.Name);
Assert.AreNotSame(null, t.IsNull, t.Name);
Assert.AreNotSame(null, t.IsVarint, t.Name);
Assert.IsTrue(t.Size >= 0, t.Name);
Assert.AreNotEqual(LayoutCode.Invalid, t.LayoutCode, t.Name);
}
}
}

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<ProjectGuid>{DC93CAA3-9732-46D4-ACBF-D69EFC3F6511}</ProjectGuid>
<OutputType>Library</OutputType>
<SigningType>Test</SigningType>
<RootNamespace>Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit</RootNamespace>
<AssemblyName>Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit</AssemblyName>
<TargetFramework>netcoreapp2.2</TargetFramework>
<EnforceTestOwnership>True</EnforceTestOwnership>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Build.props))\build.props" />
<PropertyGroup>
<QTestType>MsTest_Latest</QTestType>
<QTestDotNetFramework>FrameworkCore20</QTestDotNetFramework>
<QTestDotNetCoreRuntimeArchitecture>X64</QTestDotNetCoreRuntimeArchitecture>
<QTestDirToDeploy>$(OutDir)</QTestDirToDeploy>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.VisualStudio.TestPlatform" />
<PackageReference Include="VisualStudio.UnitTest.Corext" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
<PackageReference Include="System.ValueTuple" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HybridRow\Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj" />
<ProjectReference Include="..\HybridRowGenerator\Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj" />
<ProjectReference Include="..\..\Core\Core\Microsoft.Azure.Cosmos.Core.csproj" />
<None Include="TestData\BatchApiSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\CoverageSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\CrossVersioningExpected.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\SchemaHashCoverageSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\TaggedApiSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\ReaderSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\TaggedApiSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\CrossVersioningSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\CustomerSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\MovieSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\NullableSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\TodoSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\TagSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\PerfCounterSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,437 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
// ReSharper disable StringLiteralTypo
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[DeploymentItem(NullableUnitTests.SchemaFile, "TestData")]
public sealed class NullableUnitTests
{
private const string SchemaFile = @"TestData\NullableSchema.json";
private const int InitialRowSize = 2 * 1024 * 1024;
private Namespace schema;
private LayoutResolver resolver;
private Layout layout;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(NullableUnitTests.SchemaFile);
this.schema = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.schema);
this.layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Nullables").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void CreateNullables()
{
RowBuffer row = new RowBuffer(NullableUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
Nullables t1 = new Nullables
{
NullBool = new List<bool?> { true, false, null },
NullArray = new List<float?> { 1.2F, null, 3.0F },
NullSet = new List<string> { null, "abc", "def" },
NullTuple = new List<(int?, long?)>
{
(1, 2), (null, 3), (4, null),
(null, null),
},
NullMap = new Dictionary<Guid, byte?>
{
{ Guid.Parse("{00000000-0000-0000-0000-000000000000}"), 1 },
{ Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"), 20 },
{ Guid.Parse("{7499C40E-7077-45C1-AE5F-3E384966B3B9}"), null },
},
};
this.WriteNullables(ref row, ref RowCursor.Create(ref row, out RowCursor _), t1);
Nullables t2 = this.ReadNullables(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(t1, t2);
}
private static Result WriteNullable<TValue>(
ref RowBuffer row,
ref RowCursor scope,
TypeArgument itemType,
TValue? item,
out RowCursor nullableScope)
where TValue : struct
{
return NullableUnitTests.WriteNullableImpl(ref row, ref scope, itemType, item.HasValue, item ?? default, out nullableScope);
}
private static Result WriteNullable<TValue>(
ref RowBuffer row,
ref RowCursor scope,
TypeArgument itemType,
TValue item,
out RowCursor nullableScope)
where TValue : class
{
return NullableUnitTests.WriteNullableImpl(ref row, ref scope, itemType, item != null, item, out nullableScope);
}
private static Result WriteNullableImpl<TValue>(
ref RowBuffer row,
ref RowCursor scope,
TypeArgument itemType,
bool hasValue,
TValue item,
out RowCursor nullableScope)
{
Result r = itemType.TypeAs<LayoutNullable>()
.WriteScope(ref row, ref scope, itemType.TypeArgs, hasValue, out nullableScope);
if (r != Result.Success)
{
return r;
}
if (hasValue)
{
r = itemType.TypeArgs[0].Type.TypeAs<LayoutType<TValue>>().WriteSparse(ref row, ref nullableScope, item);
if (r != Result.Success)
{
return r;
}
}
return Result.Success;
}
private static Result ReadNullable<TValue>(
ref RowBuffer row,
ref RowCursor scope,
TypeArgument itemType,
out TValue? item,
out RowCursor nullableScope)
where TValue : struct
{
Result r = NullableUnitTests.ReadNullableImpl(ref row, ref scope, itemType, out TValue value, out nullableScope);
if ((r != Result.Success) && (r != Result.NotFound))
{
item = null;
return r;
}
item = (r == Result.NotFound) ? (TValue?)null : value;
return Result.Success;
}
private static Result ReadNullable<TValue>(
ref RowBuffer row,
ref RowCursor scope,
TypeArgument itemType,
out TValue item,
out RowCursor nullableScope)
where TValue : class
{
Result r = NullableUnitTests.ReadNullableImpl(ref row, ref scope, itemType, out item, out nullableScope);
return (r == Result.NotFound) ? Result.Success : r;
}
private static Result ReadNullableImpl<TValue>(
ref RowBuffer row,
ref RowCursor scope,
TypeArgument itemType,
out TValue item,
out RowCursor nullableScope)
{
Result r = itemType.Type.TypeAs<LayoutNullable>().ReadScope(ref row, ref scope, out nullableScope);
if (r != Result.Success)
{
item = default;
return r;
}
if (nullableScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(LayoutNullable.HasValue(ref row, ref nullableScope));
return itemType.TypeArgs[0].Type.TypeAs<LayoutType<TValue>>().ReadSparse(ref row, ref nullableScope, out item);
}
ResultAssert.NotFound(LayoutNullable.HasValue(ref row, ref nullableScope));
item = default;
return Result.NotFound;
}
private void WriteNullables(ref RowBuffer row, ref RowCursor root, Nullables value)
{
LayoutColumn c;
if (value.NullBool != null)
{
Assert.IsTrue(this.layout.TryFind("nullbool", out c));
root.Clone(out RowCursor outerScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref outerScope, c.TypeArgs, out outerScope));
foreach (bool? item in value.NullBool)
{
ResultAssert.IsSuccess(NullableUnitTests.WriteNullable(ref row, ref outerScope, c.TypeArgs[0], item, out RowCursor innerScope));
Assert.IsFalse(outerScope.MoveNext(ref row, ref innerScope));
}
}
if (value.NullArray != null)
{
Assert.IsTrue(this.layout.TryFind("nullarray", out c));
root.Clone(out RowCursor outerScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref outerScope, c.TypeArgs, out outerScope));
foreach (float? item in value.NullArray)
{
ResultAssert.IsSuccess(NullableUnitTests.WriteNullable(ref row, ref outerScope, c.TypeArgs[0], item, out RowCursor innerScope));
Assert.IsFalse(outerScope.MoveNext(ref row, ref innerScope));
}
}
if (value.NullSet != null)
{
Assert.IsTrue(this.layout.TryFind("nullset", out c));
root.Clone(out RowCursor outerScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedSet>().WriteScope(ref row, ref outerScope, c.TypeArgs, out outerScope));
foreach (string item in value.NullSet)
{
RowCursor.CreateForAppend(ref row, out RowCursor temp).Find(ref row, string.Empty);
ResultAssert.IsSuccess(NullableUnitTests.WriteNullable(ref row, ref temp, c.TypeArgs[0], item, out RowCursor _));
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedSet>().MoveField(ref row, ref outerScope, ref temp));
}
}
if (value.NullTuple != null)
{
Assert.IsTrue(this.layout.TryFind("nulltuple", out c));
root.Clone(out RowCursor outerScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref outerScope, c.TypeArgs, out outerScope));
foreach ((int? item1, long? item2) in value.NullTuple)
{
TypeArgument tupleType = c.TypeArgs[0];
ResultAssert.IsSuccess(
tupleType.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref outerScope, tupleType.TypeArgs, out RowCursor tupleScope));
ResultAssert.IsSuccess(
NullableUnitTests.WriteNullable(ref row, ref tupleScope, tupleType.TypeArgs[0], item1, out RowCursor nullableScope));
Assert.IsTrue(tupleScope.MoveNext(ref row, ref nullableScope));
ResultAssert.IsSuccess(NullableUnitTests.WriteNullable(ref row, ref tupleScope, tupleType.TypeArgs[1], item2, out nullableScope));
Assert.IsFalse(tupleScope.MoveNext(ref row, ref nullableScope));
Assert.IsFalse(outerScope.MoveNext(ref row, ref tupleScope));
}
}
if (value.NullMap != null)
{
Assert.IsTrue(this.layout.TryFind("nullmap", out c));
root.Clone(out RowCursor outerScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref outerScope, c.TypeArgs, out outerScope));
foreach ((Guid key, byte? itemValue) in value.NullMap)
{
TypeArgument tupleType = c.TypeAs<LayoutUniqueScope>().FieldType(ref outerScope);
RowCursor.CreateForAppend(ref row, out RowCursor temp).Find(ref row, string.Empty);
ResultAssert.IsSuccess(
tupleType.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref temp, tupleType.TypeArgs, out RowCursor tupleScope));
Guid? itemKey = key.Equals(Guid.Empty) ? (Guid?)null : key;
ResultAssert.IsSuccess(
NullableUnitTests.WriteNullable(ref row, ref tupleScope, tupleType.TypeArgs[0], itemKey, out RowCursor nullableScope));
Assert.IsTrue(tupleScope.MoveNext(ref row, ref nullableScope));
ResultAssert.IsSuccess(
NullableUnitTests.WriteNullable(ref row, ref tupleScope, tupleType.TypeArgs[1], itemValue, out nullableScope));
Assert.IsFalse(tupleScope.MoveNext(ref row, ref nullableScope));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref outerScope, ref temp));
}
}
}
private Nullables ReadNullables(ref RowBuffer row, ref RowCursor root)
{
Nullables value = new Nullables();
Assert.IsTrue(this.layout.TryFind("nullbool", out LayoutColumn c));
root.Clone(out RowCursor scope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref scope, out scope) == Result.Success)
{
value.NullBool = new List<bool?>();
RowCursor nullableScope = default;
while (scope.MoveNext(ref row, ref nullableScope))
{
ResultAssert.IsSuccess(NullableUnitTests.ReadNullable(ref row, ref scope, c.TypeArgs[0], out bool? item, out nullableScope));
value.NullBool.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("nullarray", out c));
root.Clone(out scope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref scope, out scope) == Result.Success)
{
value.NullArray = new List<float?>();
RowCursor nullableScope = default;
while (scope.MoveNext(ref row, ref nullableScope))
{
ResultAssert.IsSuccess(NullableUnitTests.ReadNullable(ref row, ref scope, c.TypeArgs[0], out float? item, out nullableScope));
value.NullArray.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("nullset", out c));
root.Clone(out scope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedSet>().ReadScope(ref row, ref scope, out scope) == Result.Success)
{
value.NullSet = new List<string>();
RowCursor nullableScope = default;
while (scope.MoveNext(ref row, ref nullableScope))
{
ResultAssert.IsSuccess(NullableUnitTests.ReadNullable(ref row, ref scope, c.TypeArgs[0], out string item, out nullableScope));
value.NullSet.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("nulltuple", out c));
root.Clone(out scope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref scope, out scope) == Result.Success)
{
value.NullTuple = new List<(int?, long?)>();
RowCursor tupleScope = default;
TypeArgument tupleType = c.TypeArgs[0];
while (scope.MoveNext(ref row, ref tupleScope))
{
ResultAssert.IsSuccess(tupleType.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref scope, out tupleScope));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(
NullableUnitTests.ReadNullable(ref row, ref tupleScope, tupleType.TypeArgs[0], out int? item1, out RowCursor nullableScope));
Assert.IsTrue(tupleScope.MoveNext(ref row, ref nullableScope));
ResultAssert.IsSuccess(
NullableUnitTests.ReadNullable(ref row, ref tupleScope, tupleType.TypeArgs[1], out long? item2, out nullableScope));
Assert.IsFalse(tupleScope.MoveNext(ref row, ref nullableScope));
value.NullTuple.Add((item1, item2));
}
}
Assert.IsTrue(this.layout.TryFind("nullmap", out c));
root.Clone(out scope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref scope, out scope) == Result.Success)
{
value.NullMap = new Dictionary<Guid, byte?>();
RowCursor tupleScope = default;
TypeArgument tupleType = c.TypeAs<LayoutUniqueScope>().FieldType(ref scope);
while (scope.MoveNext(ref row, ref tupleScope))
{
ResultAssert.IsSuccess(tupleType.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref scope, out tupleScope));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(
NullableUnitTests.ReadNullable(
ref row,
ref tupleScope,
tupleType.TypeArgs[0],
out Guid? itemKey,
out RowCursor nullableScope));
Assert.IsTrue(tupleScope.MoveNext(ref row, ref nullableScope));
ResultAssert.IsSuccess(
NullableUnitTests.ReadNullable(ref row, ref tupleScope, tupleType.TypeArgs[1], out byte? itemValue, out nullableScope));
Assert.IsFalse(tupleScope.MoveNext(ref row, ref nullableScope));
value.NullMap.Add(itemKey ?? Guid.Empty, itemValue);
}
}
return value;
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class Nullables
{
public List<bool?> NullBool;
public List<string> NullSet;
public List<float?> NullArray;
public List<(int?, long?)> NullTuple;
public Dictionary<Guid, byte?> NullMap;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Nullables nullables && this.Equals(nullables);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = 0;
hashCode = (hashCode * 397) ^ (this.NullBool?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.NullSet?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.NullArray?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.NullTuple?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.NullMap?.GetHashCode() ?? 0);
return hashCode;
}
}
private static bool MapEquals<TKey, TValue>(Dictionary<TKey, TValue> left, Dictionary<TKey, TValue> right)
{
if (left.Count != right.Count)
{
return false;
}
foreach (KeyValuePair<TKey, TValue> item in left)
{
if (!right.TryGetValue(item.Key, out TValue value))
{
return false;
}
if (!item.Value.Equals(value))
{
return false;
}
}
return true;
}
private bool Equals(Nullables other)
{
return (object.ReferenceEquals(this.NullBool, other.NullBool) ||
((this.NullBool != null) && (other.NullBool != null) && this.NullBool.SequenceEqual(other.NullBool))) &&
(object.ReferenceEquals(this.NullSet, other.NullSet) ||
((this.NullSet != null) && (other.NullSet != null) && this.NullSet.SequenceEqual(other.NullSet))) &&
(object.ReferenceEquals(this.NullArray, other.NullArray) ||
((this.NullArray != null) && (other.NullArray != null) && this.NullArray.SequenceEqual(other.NullArray))) &&
(object.ReferenceEquals(this.NullTuple, other.NullTuple) ||
((this.NullTuple != null) && (other.NullTuple != null) && this.NullTuple.SequenceEqual(other.NullTuple))) &&
(object.ReferenceEquals(this.NullMap, other.NullMap) ||
((this.NullMap != null) && (other.NullMap != null) && Nullables.MapEquals(this.NullMap, other.NullMap)));
}
}
}
}

View File

@@ -0,0 +1,38 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Extension methods for computing permutations of <see cref="IEnumerable{T}"/>.
/// </summary>
public static class PermuteExtensions
{
/// <summary>Generate all permutations of a given enumerable.</summary>
public static IEnumerable<IEnumerable<T>> Permute<T>(this IEnumerable<T> list)
{
int start = 0;
foreach (T element in list)
{
int index = start;
T[] first = { element };
IEnumerable<T> rest = list.Where((s, i) => i != index);
if (!rest.Any())
{
yield return first;
}
foreach (IEnumerable<T> sub in rest.Permute())
{
yield return first.Concat(sub);
}
start++;
}
}
}
}

View File

@@ -0,0 +1,19 @@
// <copyright file="AssemblyInfo.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("DC93CAA3-9732-46D4-ACBF-D69EFC3F6511")]

View File

@@ -0,0 +1,66 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class RandomGeneratorUnitTests
{
[TestMethod]
[Owner("jthunter")]
public void RangeTest()
{
int seed = 42;
RandomGenerator rand = new RandomGenerator(new Random(seed));
ulong l1 = rand.NextUInt64();
ulong l2 = rand.NextUInt64();
Assert.AreNotEqual(l1, l2);
Console.WriteLine("Check full range of min/max for ushort.");
for (int min = 0; min <= ushort.MaxValue; min++)
{
ushort i1 = rand.NextUInt16((ushort)min, ushort.MaxValue);
Assert.IsTrue(i1 >= min);
}
Console.WriteLine("Check ushort range of min/max for uint.");
for (uint min = 0; min <= (uint)ushort.MaxValue; min++)
{
uint i1 = rand.NextUInt32(min, (uint)ushort.MaxValue);
Assert.IsTrue(i1 >= min);
Assert.IsTrue(i1 <= ushort.MaxValue);
}
bool seenMax = false;
bool seenMin = false;
const ushort maxUShortRange = 10;
Console.WriteLine("Check inclusivity for ushort.");
while (!(seenMax && seenMin))
{
ushort i1 = rand.NextUInt16(ushort.MinValue, maxUShortRange);
seenMin = seenMin || i1 == ushort.MinValue;
seenMax = seenMax || i1 == maxUShortRange;
Assert.IsTrue(i1 <= maxUShortRange);
}
seenMax = false;
seenMin = false;
Console.WriteLine("Check inclusivity for short.");
const short minShortRange = -10;
const short maxShortRange = 10;
while (!(seenMax && seenMin))
{
short i1 = rand.NextInt16(minShortRange, maxShortRange);
seenMin = seenMin || i1 == -10;
seenMax = seenMax || i1 == 10;
Assert.IsTrue(i1 >= minShortRange);
Assert.IsTrue(i1 <= maxShortRange);
}
}
}
}

View File

@@ -0,0 +1,198 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Fields should be private
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema;
using Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[DeploymentItem(RecordIOUnitTests.SchemaFile, "TestData")]
public class RecordIOUnitTests
{
private const string SchemaFile = @"TestData\CustomerSchema.json";
private const int InitialRowSize = 0;
private Namespace ns;
private LayoutResolver resolver;
private Layout addressLayout;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(RecordIOUnitTests.SchemaFile);
this.ns = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.ns);
this.addressLayout = this.resolver.Resolve(this.ns.Schemas.Find(x => x.Name == "Address").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void LoadSchema()
{
LayoutResolver systemResolver = SystemSchema.LayoutResolver;
Layout segmentLayout = systemResolver.Resolve(SystemSchema.SegmentSchemaId);
Assert.AreEqual(segmentLayout.Name, "Segment");
Assert.AreEqual(segmentLayout.SchemaId, SystemSchema.SegmentSchemaId);
Layout recordLayout = systemResolver.Resolve(SystemSchema.RecordSchemaId);
Assert.AreEqual(recordLayout.Name, "Record");
Assert.AreEqual(recordLayout.SchemaId, SystemSchema.RecordSchemaId);
}
[TestMethod]
[Owner("jthunter")]
public async Task RoundTripAsync()
{
Address[] addresses =
{
new Address
{
Street = "300B Chocolate Hole",
City = "Great Cruz Bay",
State = "VI",
PostalCode = new PostalCode
{
Zip = 00830,
Plus4 = 0001,
},
},
new Address
{
Street = "1 Microsoft Way",
City = "Redmond",
State = "WA",
PostalCode = new PostalCode
{
Zip = 98052,
},
},
};
string sampleComment = "hello there";
string sampleSDL = "some SDL";
using (Stream stm = new MemoryStream())
{
// Create a reusable, resizable buffer.
MemorySpanResizer<byte> resizer = new MemorySpanResizer<byte>(RecordIOUnitTests.InitialRowSize);
// Write a RecordIO stream.
Result r = await stm.WriteRecordIOAsync(
new Segment(sampleComment, sampleSDL),
(long index, out ReadOnlyMemory<byte> body) =>
{
body = default;
if (index >= addresses.Length)
{
return Result.Success;
}
return this.WriteAddress(resizer, addresses[index], out body);
});
// Read a RecordIO stream.
List<Address> addressesRead = new List<Address>();
stm.Position = 0;
resizer = new MemorySpanResizer<byte>(1);
r = await stm.ReadRecordIOAsync(
record =>
{
Assert.IsFalse(record.IsEmpty);
r = this.ReadAddress(record, out Address obj);
ResultAssert.IsSuccess(r);
addressesRead.Add(obj);
return Result.Success;
},
segment =>
{
Assert.IsFalse(segment.IsEmpty);
r = this.ReadSegment(segment, out Segment obj);
ResultAssert.IsSuccess(r);
Assert.AreEqual(obj.Comment, sampleComment);
Assert.AreEqual(obj.SDL, sampleSDL);
return Result.Success;
},
resizer);
ResultAssert.IsSuccess(r);
// Check that the values all round-tripped.
Assert.AreEqual(addresses.Length, addressesRead.Count);
for (int i = 0; i < addresses.Length; i++)
{
Assert.AreEqual(addresses[i], addressesRead[i]);
}
}
}
private Result WriteAddress(MemorySpanResizer<byte> resizer, Address obj, out ReadOnlyMemory<byte> buffer)
{
RowBuffer row = new RowBuffer(RecordIOUnitTests.InitialRowSize, resizer);
row.InitLayout(HybridRowVersion.V1, this.addressLayout, this.resolver);
Result r = RowWriter.WriteBuffer(ref row, obj, AddressSerializer.Write);
if (r != Result.Success)
{
buffer = default;
return r;
}
buffer = resizer.Memory.Slice(0, row.Length);
return Result.Success;
}
private Result ReadAddress(Memory<byte> buffer, out Address obj)
{
RowBuffer row = new RowBuffer(buffer.Span, HybridRowVersion.V1, this.resolver);
RowReader reader = new RowReader(ref row);
// Use the reader to dump to the screen.
Result r = DiagnosticConverter.ReaderToString(ref reader, out string str);
if (r != Result.Success)
{
obj = default;
return r;
}
Console.WriteLine(str);
// Reset the reader and materialize the object.
reader = new RowReader(ref row);
return AddressSerializer.Read(ref reader, out obj);
}
private Result ReadSegment(Memory<byte> buffer, out Segment obj)
{
RowBuffer row = new RowBuffer(buffer.Span, HybridRowVersion.V1, SystemSchema.LayoutResolver);
RowReader reader = new RowReader(ref row);
// Use the reader to dump to the screen.
Result r = DiagnosticConverter.ReaderToString(ref reader, out string str);
if (r != Result.Success)
{
obj = default;
return r;
}
Console.WriteLine(str);
// Reset the reader and materialize the object.
reader = new RowReader(ref row);
return SegmentSerializer.Read(ref reader, out obj);
}
}
}

View File

@@ -0,0 +1,101 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
internal static class ResultAssert
{
public static void IsSuccess(Result actual)
{
Assert.AreEqual(Result.Success, actual);
}
public static void IsSuccess(Result actual, string message)
{
Assert.AreEqual(Result.Success, actual, message);
}
public static void IsSuccess(Result actual, string message, params object[] parameters)
{
Assert.AreEqual(Result.Success, actual, message, parameters);
}
public static void NotFound(Result actual)
{
Assert.AreEqual(Result.NotFound, actual);
}
public static void NotFound(Result actual, string message)
{
Assert.AreEqual(Result.NotFound, actual, message);
}
public static void NotFound(Result actual, string message, params object[] parameters)
{
Assert.AreEqual(Result.NotFound, actual, message, parameters);
}
public static void Exists(Result actual)
{
Assert.AreEqual(Result.Exists, actual);
}
public static void Exists(Result actual, string message)
{
Assert.AreEqual(Result.Exists, actual, message);
}
public static void Exists(Result actual, string message, params object[] parameters)
{
Assert.AreEqual(Result.Exists, actual, message, parameters);
}
public static void TypeMismatch(Result actual)
{
Assert.AreEqual(Result.TypeMismatch, actual);
}
public static void TypeMismatch(Result actual, string message)
{
Assert.AreEqual(Result.TypeMismatch, actual, message);
}
public static void TypeMismatch(Result actual, string message, params object[] parameters)
{
Assert.AreEqual(Result.TypeMismatch, actual, message, parameters);
}
public static void InsufficientPermissions(Result actual)
{
Assert.AreEqual(Result.InsufficientPermissions, actual);
}
public static void InsufficientPermissions(Result actual, string message)
{
Assert.AreEqual(Result.InsufficientPermissions, actual, message);
}
public static void InsufficientPermissions(Result actual, string message, params object[] parameters)
{
Assert.AreEqual(Result.InsufficientPermissions, actual, message, parameters);
}
public static void TypeConstraint(Result actual)
{
Assert.AreEqual(Result.TypeConstraint, actual);
}
public static void TypeConstraint(Result actual, string message)
{
Assert.AreEqual(Result.TypeConstraint, actual, message);
}
public static void TypeConstraint(Result actual, string message, params object[] parameters)
{
Assert.AreEqual(Result.TypeConstraint, actual, message, parameters);
}
}
}

View File

@@ -0,0 +1,60 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System.Diagnostics.CodeAnalysis;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class RowBufferUnitTests
{
[TestMethod]
[Owner("jthunter")]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1139:UseLiteralsSuffixNotationInsteadOfCasting", Justification = "Explicit")]
public void VarIntTest()
{
// Brute force test all signed 16-bit values.
for (int i = short.MinValue; i <= short.MaxValue; i++)
{
short s = (short)i;
this.RoundTripVarInt(s);
}
// Test boundary conditions for larger values.
this.RoundTripVarInt(0);
this.RoundTripVarInt(int.MinValue);
this.RoundTripVarInt(unchecked((int)0x80000000ul));
this.RoundTripVarInt(unchecked((int)0x7FFFFFFFul));
this.RoundTripVarInt(int.MaxValue);
this.RoundTripVarInt(long.MinValue);
this.RoundTripVarInt(unchecked((long)0x8000000000000000ul));
this.RoundTripVarInt(unchecked((long)0x7FFFFFFFFFFFFFFFul));
this.RoundTripVarInt(long.MaxValue);
}
private void RoundTripVarInt(short s)
{
ulong encoded = RowBuffer.RotateSignToLsb(s);
long decoded = RowBuffer.RotateSignToMsb(encoded);
short t = unchecked((short)decoded);
Assert.AreEqual(s, t, "Value: {0}", s);
}
private void RoundTripVarInt(int s)
{
ulong encoded = RowBuffer.RotateSignToLsb(s);
long decoded = RowBuffer.RotateSignToMsb(encoded);
int t = unchecked((int)decoded);
Assert.AreEqual(s, t, "Value: {0}", s);
}
private void RoundTripVarInt(long s)
{
ulong encoded = RowBuffer.RotateSignToLsb(s);
long decoded = RowBuffer.RotateSignToMsb(encoded);
Assert.AreEqual(s, decoded, "Value: {0}", s);
}
}
}

View File

@@ -0,0 +1,890 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1402 // FileMayOnlyContainASingleType
#pragma warning disable SA1201 // OrderingRules
#pragma warning disable SA1401 // Public Fields
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
internal ref struct RowOperationDispatcher
{
public readonly LayoutResolver Resolver;
public RowBuffer Row;
private const int InitialRowSize = 2 * 1024 * 1024;
private readonly IDispatcher dispatcher;
private RowOperationDispatcher(IDispatcher dispatcher, Layout layout, LayoutResolver resolver)
{
this.dispatcher = dispatcher;
this.Row = new RowBuffer(RowOperationDispatcher.InitialRowSize);
this.Resolver = resolver;
this.Row.InitLayout(HybridRowVersion.V1, layout, this.Resolver);
}
private RowOperationDispatcher(IDispatcher dispatcher, LayoutResolver resolver, string expected)
{
this.dispatcher = dispatcher;
this.Row = new RowBuffer(RowOperationDispatcher.InitialRowSize);
this.Resolver = resolver;
byte[] bytes = ByteConverter.ToBytes(expected);
this.Row.ReadFrom(bytes, HybridRowVersion.V1, this.Resolver);
}
public static RowOperationDispatcher Create<TDispatcher>(Layout layout, LayoutResolver resolver)
where TDispatcher : struct, IDispatcher
{
return new RowOperationDispatcher(default(TDispatcher), layout, resolver);
}
public static RowOperationDispatcher ReadFrom<TDispatcher>(LayoutResolver resolver, string expected)
where TDispatcher : struct, IDispatcher
{
return new RowOperationDispatcher(default(TDispatcher), resolver, expected);
}
public string RowToHex()
{
using (MemoryStream stm = new MemoryStream())
{
this.Row.WriteTo(stm);
ReadOnlyMemory<byte> bytes = stm.GetBuffer().AsMemory(0, (int)stm.Position);
return ByteConverter.ToHex(bytes.Span);
}
}
public RowReader GetReader()
{
return new RowReader(ref this.Row);
}
public void LayoutCodeSwitch(
string path = null,
LayoutType type = null,
TypeArgumentList typeArgs = default,
object value = null)
{
RowCursor root = RowCursor.Create(ref this.Row);
this.LayoutCodeSwitch(ref root, path, type, typeArgs, value);
}
public void LayoutCodeSwitch(
ref RowCursor scope,
string path = null,
LayoutType type = null,
TypeArgumentList typeArgs = default,
object value = null)
{
LayoutColumn col = null;
if (type == null)
{
Assert.IsNotNull(path);
Assert.IsTrue(scope.Layout.TryFind(path, out col));
Assert.IsNotNull(col);
type = col.Type;
typeArgs = col.TypeArgs;
}
if ((path != null) && (col == null || col.Storage == StorageKind.Sparse))
{
scope.Find(ref this.Row, path);
}
switch (type.LayoutCode)
{
case LayoutCode.Null:
this.dispatcher.Dispatch<LayoutNull, NullValue>(ref this, ref scope, col, type, NullValue.Default);
break;
case LayoutCode.Boolean:
this.dispatcher.Dispatch<LayoutBoolean, bool>(
ref this,
ref scope,
col,
type,
(bool?)value ?? default);
break;
case LayoutCode.Int8:
this.dispatcher.Dispatch<LayoutInt8, sbyte>(
ref this,
ref scope,
col,
type,
(sbyte?)value ?? default);
break;
case LayoutCode.Int16:
this.dispatcher.Dispatch<LayoutInt16, short>(
ref this,
ref scope,
col,
type,
(short?)value ?? default);
break;
case LayoutCode.Int32:
this.dispatcher.Dispatch<LayoutInt32, int>(ref this, ref scope, col, type, (int?)value ?? default);
break;
case LayoutCode.Int64:
this.dispatcher.Dispatch<LayoutInt64, long>(ref this, ref scope, col, type, (long?)value ?? default);
break;
case LayoutCode.UInt8:
this.dispatcher.Dispatch<LayoutUInt8, byte>(ref this, ref scope, col, type, (byte?)value ?? default);
break;
case LayoutCode.UInt16:
this.dispatcher.Dispatch<LayoutUInt16, ushort>(
ref this,
ref scope,
col,
type,
(ushort?)value ?? default);
break;
case LayoutCode.UInt32:
this.dispatcher.Dispatch<LayoutUInt32, uint>(ref this, ref scope, col, type, (uint?)value ?? default);
break;
case LayoutCode.UInt64:
this.dispatcher.Dispatch<LayoutUInt64, ulong>(
ref this,
ref scope,
col,
type,
(ulong?)value ?? default);
break;
case LayoutCode.VarInt:
this.dispatcher.Dispatch<LayoutVarInt, long>(ref this, ref scope, col, type, (long?)value ?? default);
break;
case LayoutCode.VarUInt:
this.dispatcher.Dispatch<LayoutVarUInt, ulong>(
ref this,
ref scope,
col,
type,
(ulong?)value ?? default);
break;
case LayoutCode.Float32:
this.dispatcher.Dispatch<LayoutFloat32, float>(
ref this,
ref scope,
col,
type,
(float?)value ?? default);
break;
case LayoutCode.Float64:
this.dispatcher.Dispatch<LayoutFloat64, double>(
ref this,
ref scope,
col,
type,
(double?)value ?? default);
break;
case LayoutCode.Float128:
this.dispatcher.Dispatch<LayoutFloat128, Float128>(
ref this,
ref scope,
col,
type,
(Float128?)value ?? default);
break;
case LayoutCode.Decimal:
this.dispatcher.Dispatch<LayoutDecimal, decimal>(
ref this,
ref scope,
col,
type,
(decimal?)value ?? default);
break;
case LayoutCode.DateTime:
this.dispatcher.Dispatch<LayoutDateTime, DateTime>(
ref this,
ref scope,
col,
type,
(DateTime?)value ?? default);
break;
case LayoutCode.UnixDateTime:
this.dispatcher.Dispatch<LayoutUnixDateTime, UnixDateTime>(
ref this,
ref scope,
col,
type,
(UnixDateTime?)value ?? default);
break;
case LayoutCode.Guid:
this.dispatcher.Dispatch<LayoutGuid, Guid>(ref this, ref scope, col, type, (Guid?)value ?? default);
break;
case LayoutCode.MongoDbObjectId:
this.dispatcher.Dispatch<LayoutMongoDbObjectId, MongoDbObjectId>(
ref this,
ref scope,
col,
type,
(MongoDbObjectId?)value ?? default);
break;
case LayoutCode.Utf8:
this.dispatcher.Dispatch<LayoutUtf8, string>(
ref this,
ref scope,
col,
type,
(string)value);
break;
case LayoutCode.Binary:
this.dispatcher.Dispatch<LayoutBinary, byte[]>(
ref this,
ref scope,
col,
type,
(byte[])value);
break;
case LayoutCode.ObjectScope:
case LayoutCode.ImmutableObjectScope:
this.dispatcher.DispatchObject(ref this, ref scope);
break;
case LayoutCode.TypedArrayScope:
case LayoutCode.ImmutableTypedArrayScope:
this.dispatcher.DispatchArray(ref this, ref scope, type, typeArgs, value);
break;
case LayoutCode.TypedSetScope:
case LayoutCode.ImmutableTypedSetScope:
this.dispatcher.DispatchSet(ref this, ref scope, type, typeArgs, value);
break;
case LayoutCode.TypedMapScope:
case LayoutCode.ImmutableTypedMapScope:
this.dispatcher.DispatchMap(ref this, ref scope, type, typeArgs, value);
break;
case LayoutCode.TupleScope:
case LayoutCode.ImmutableTupleScope:
case LayoutCode.TypedTupleScope:
case LayoutCode.ImmutableTypedTupleScope:
case LayoutCode.TaggedScope:
case LayoutCode.ImmutableTaggedScope:
case LayoutCode.Tagged2Scope:
case LayoutCode.ImmutableTagged2Scope:
this.dispatcher.DispatchTuple(ref this, ref scope, type, typeArgs, value);
break;
case LayoutCode.NullableScope:
this.dispatcher.DispatchNullable(ref this, ref scope, type, typeArgs, value);
break;
case LayoutCode.Schema:
case LayoutCode.ImmutableSchema:
this.dispatcher.DispatchUDT(ref this, ref scope, type, typeArgs, value);
break;
default:
Contract.Assert(false, $"Unknown type will be ignored: {type.LayoutCode}");
break;
}
}
}
internal interface IDispatcher
{
void Dispatch<TLayout, TValue>(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutColumn col,
LayoutType t,
TValue value = default)
where TLayout : LayoutType<TValue>;
void DispatchObject(ref RowOperationDispatcher dispatcher, ref RowCursor scope);
void DispatchArray(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value);
void DispatchTuple(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value);
void DispatchNullable(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value);
void DispatchSet(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value);
void DispatchMap(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value);
void DispatchUDT(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType type,
TypeArgumentList typeArgs,
object value);
}
internal interface IDispatchable
{
void Dispatch(ref RowOperationDispatcher dispatcher, ref RowCursor scope);
}
internal struct WriteRowDispatcher : IDispatcher
{
public void Dispatch<TLayout, TValue>(
ref RowOperationDispatcher dispatcher,
ref RowCursor field,
LayoutColumn col,
LayoutType t,
TValue value = default)
where TLayout : LayoutType<TValue>
{
switch (col?.Storage)
{
case StorageKind.Fixed:
ResultAssert.IsSuccess(t.TypeAs<TLayout>().WriteFixed(ref dispatcher.Row, ref field, col, value));
break;
case StorageKind.Variable:
ResultAssert.IsSuccess(t.TypeAs<TLayout>().WriteVariable(ref dispatcher.Row, ref field, col, value));
break;
default:
ResultAssert.IsSuccess(t.TypeAs<TLayout>().WriteSparse(ref dispatcher.Row, ref field, value));
break;
}
}
public void DispatchObject(ref RowOperationDispatcher dispatcher, ref RowCursor scope)
{
}
public void DispatchArray(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(
t.TypeAs<LayoutTypedArray>().WriteScope(ref dispatcher.Row, ref scope, typeArgs, out RowCursor arrayScope));
IList items = (IList)value;
foreach (object item in items)
{
dispatcher.LayoutCodeSwitch(ref arrayScope, null, typeArgs[0].Type, typeArgs[0].TypeArgs, item);
arrayScope.MoveNext(ref dispatcher.Row);
}
}
public void DispatchTuple(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count >= 2);
ResultAssert.IsSuccess(
t.TypeAs<LayoutIndexedScope>().WriteScope(ref dispatcher.Row, ref scope, typeArgs, out RowCursor tupleScope));
for (int i = 0; i < typeArgs.Count; i++)
{
PropertyInfo valueAccessor = value.GetType().GetProperty($"Item{i + 1}");
dispatcher.LayoutCodeSwitch(
ref tupleScope,
null,
typeArgs[i].Type,
typeArgs[i].TypeArgs,
valueAccessor.GetValue(value));
tupleScope.MoveNext(ref dispatcher.Row);
}
}
public void DispatchNullable(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(
t.TypeAs<LayoutNullable>()
.WriteScope(ref dispatcher.Row, ref scope, typeArgs, value != null, out RowCursor nullableScope));
if (value != null)
{
dispatcher.LayoutCodeSwitch(ref nullableScope, null, typeArgs[0].Type, typeArgs[0].TypeArgs, value);
}
}
public void DispatchSet(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedSet>().WriteScope(ref dispatcher.Row, ref scope, typeArgs, out RowCursor setScope));
IList items = (IList)value;
foreach (object item in items)
{
string elmPath = Guid.NewGuid().ToString();
RowCursor.CreateForAppend(ref dispatcher.Row, out RowCursor tempCursor);
dispatcher.LayoutCodeSwitch(ref tempCursor, elmPath, typeArgs[0].Type, typeArgs[0].TypeArgs, item);
// Move item into the set.
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedSet>().MoveField(ref dispatcher.Row, ref setScope, ref tempCursor));
}
}
public void DispatchMap(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 2);
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedMap>().WriteScope(ref dispatcher.Row, ref scope, typeArgs, out RowCursor mapScope));
TypeArgument fieldType = t.TypeAs<LayoutUniqueScope>().FieldType(ref mapScope);
IList pairs = (IList)value;
foreach (object pair in pairs)
{
string elmPath = Guid.NewGuid().ToString();
RowCursor.CreateForAppend(ref dispatcher.Row, out RowCursor tempCursor);
dispatcher.LayoutCodeSwitch(ref tempCursor, elmPath, fieldType.Type, fieldType.TypeArgs, pair);
// Move item into the map.
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedMap>().MoveField(ref dispatcher.Row, ref mapScope, ref tempCursor));
}
}
public void DispatchUDT(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
ResultAssert.IsSuccess(t.TypeAs<LayoutUDT>().WriteScope(ref dispatcher.Row, ref scope, typeArgs, out RowCursor udtScope));
IDispatchable valueDispatcher = value as IDispatchable;
Assert.IsNotNull(valueDispatcher);
valueDispatcher.Dispatch(ref dispatcher, ref udtScope);
}
}
internal struct ReadRowDispatcher : IDispatcher
{
public void Dispatch<TLayout, TValue>(
ref RowOperationDispatcher dispatcher,
ref RowCursor root,
LayoutColumn col,
LayoutType t,
TValue expected = default)
where TLayout : LayoutType<TValue>
{
TValue value;
switch (col?.Storage)
{
case StorageKind.Fixed:
ResultAssert.IsSuccess(t.TypeAs<TLayout>().ReadFixed(ref dispatcher.Row, ref root, col, out value));
break;
case StorageKind.Variable:
ResultAssert.IsSuccess(t.TypeAs<TLayout>().ReadVariable(ref dispatcher.Row, ref root, col, out value));
break;
default:
ResultAssert.IsSuccess(t.TypeAs<TLayout>().ReadSparse(ref dispatcher.Row, ref root, out value));
break;
}
if (typeof(TValue).IsArray)
{
CollectionAssert.AreEqual((ICollection)expected, (ICollection)value);
}
else
{
Assert.AreEqual(expected, value);
}
}
public void DispatchObject(ref RowOperationDispatcher dispatcher, ref RowCursor scope)
{
}
public void DispatchArray(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(
t.TypeAs<LayoutTypedArray>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor arrayScope));
int i = 0;
IList items = (IList)value;
while (arrayScope.MoveNext(ref dispatcher.Row))
{
dispatcher.LayoutCodeSwitch(
ref arrayScope,
null,
typeArgs[0].Type,
typeArgs[0].TypeArgs,
items[i++]);
}
}
public void DispatchTuple(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count >= 2);
ResultAssert.IsSuccess(
t.TypeAs<LayoutIndexedScope>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor tupleScope));
for (int i = 0; i < typeArgs.Count; i++)
{
tupleScope.MoveNext(ref dispatcher.Row);
PropertyInfo valueAccessor = value.GetType().GetProperty($"Item{i + 1}");
dispatcher.LayoutCodeSwitch(
ref tupleScope,
null,
typeArgs[i].Type,
typeArgs[i].TypeArgs,
valueAccessor.GetValue(value));
}
}
public void DispatchNullable(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(
t.TypeAs<LayoutNullable>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor nullableScope));
if (value != null)
{
ResultAssert.IsSuccess(LayoutNullable.HasValue(ref dispatcher.Row, ref nullableScope));
nullableScope.MoveNext(ref dispatcher.Row);
dispatcher.LayoutCodeSwitch(ref nullableScope, null, typeArgs[0].Type, typeArgs[0].TypeArgs, value);
}
else
{
ResultAssert.NotFound(LayoutNullable.HasValue(ref dispatcher.Row, ref nullableScope));
}
}
public void DispatchSet(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedSet>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor setScope));
int i = 0;
IList items = (IList)value;
while (setScope.MoveNext(ref dispatcher.Row))
{
dispatcher.LayoutCodeSwitch(
ref setScope,
null,
typeArgs[0].Type,
typeArgs[0].TypeArgs,
items[i++]);
}
}
public void DispatchMap(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 2);
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedMap>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor mapScope));
int i = 0;
IList items = (IList)value;
while (mapScope.MoveNext(ref dispatcher.Row))
{
dispatcher.LayoutCodeSwitch(
ref mapScope,
null,
LayoutType.TypedTuple,
typeArgs,
items[i++]);
}
}
public void DispatchUDT(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
ResultAssert.IsSuccess(t.TypeAs<LayoutUDT>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor udtScope));
IDispatchable valueDispatcher = value as IDispatchable;
Assert.IsNotNull(valueDispatcher);
valueDispatcher.Dispatch(ref dispatcher, ref udtScope);
}
}
internal struct DeleteRowDispatcher : IDispatcher
{
public void Dispatch<TLayout, TValue>(
ref RowOperationDispatcher dispatcher,
ref RowCursor root,
LayoutColumn col,
LayoutType t,
TValue value = default)
where TLayout : LayoutType<TValue>
{
ResultAssert.IsSuccess(t.TypeAs<TLayout>().DeleteSparse(ref dispatcher.Row, ref root));
}
public void DispatchObject(ref RowOperationDispatcher dispatcher, ref RowCursor scope)
{
}
public void DispatchArray(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(
t.TypeAs<LayoutTypedArray>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor arrayScope));
if (!arrayScope.Immutable)
{
IList items = (IList)value;
foreach (object item in items)
{
Assert.IsTrue(arrayScope.MoveNext(ref dispatcher.Row));
dispatcher.LayoutCodeSwitch(ref arrayScope, null, typeArgs[0].Type, typeArgs[0].TypeArgs, item);
}
}
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedArray>().DeleteScope(ref dispatcher.Row, ref scope));
}
public void DispatchTuple(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count >= 2);
ResultAssert.IsSuccess(t.TypeAs<LayoutIndexedScope>().DeleteScope(ref dispatcher.Row, ref scope));
}
public void DispatchNullable(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(t.TypeAs<LayoutNullable>().DeleteScope(ref dispatcher.Row, ref scope));
}
public void DispatchSet(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 1);
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedSet>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor setScope));
if (!setScope.Immutable)
{
IList items = (IList)value;
foreach (object item in items)
{
Assert.IsTrue(setScope.MoveNext(ref dispatcher.Row));
dispatcher.LayoutCodeSwitch(ref setScope, null, typeArgs[0].Type, typeArgs[0].TypeArgs, item);
}
}
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedSet>().DeleteScope(ref dispatcher.Row, ref scope));
}
public void DispatchMap(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
Contract.Requires(typeArgs.Count == 2);
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedMap>().ReadScope(ref dispatcher.Row, ref scope, out RowCursor mapScope));
if (!mapScope.Immutable)
{
IList items = (IList)value;
foreach (object item in items)
{
Assert.IsTrue(mapScope.MoveNext(ref dispatcher.Row));
dispatcher.LayoutCodeSwitch(ref mapScope, null, LayoutType.TypedTuple, typeArgs, item);
}
}
ResultAssert.IsSuccess(t.TypeAs<LayoutTypedMap>().DeleteScope(ref dispatcher.Row, ref scope));
}
public void DispatchUDT(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
ResultAssert.IsSuccess(t.TypeAs<LayoutUDT>().DeleteScope(ref dispatcher.Row, ref scope));
}
}
internal struct NullRowDispatcher : IDispatcher
{
public void Dispatch<TLayout, TValue>(
ref RowOperationDispatcher dispatcher,
ref RowCursor root,
LayoutColumn col,
LayoutType t,
TValue expected = default)
where TLayout : LayoutType<TValue>
{
switch (col?.Storage)
{
case StorageKind.Fixed:
ResultAssert.NotFound(t.TypeAs<TLayout>().ReadFixed(ref dispatcher.Row, ref root, col, out TValue _));
break;
case StorageKind.Variable:
ResultAssert.NotFound(t.TypeAs<TLayout>().ReadVariable(ref dispatcher.Row, ref root, col, out TValue _));
break;
default:
ResultAssert.NotFound(t.TypeAs<TLayout>().ReadSparse(ref dispatcher.Row, ref root, out TValue _));
break;
}
}
public void DispatchObject(ref RowOperationDispatcher dispatcher, ref RowCursor scope)
{
}
public void DispatchArray(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
}
public void DispatchTuple(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
}
public void DispatchNullable(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
}
public void DispatchSet(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
}
public void DispatchMap(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
}
public void DispatchUDT(
ref RowOperationDispatcher dispatcher,
ref RowCursor scope,
LayoutType t,
TypeArgumentList typeArgs,
object value)
{
}
}
}

View File

@@ -0,0 +1,353 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Test code.")]
[DeploymentItem(RowReaderUnitTests.SchemaFile, "TestData")]
public sealed class RowReaderUnitTests
{
private const string SchemaFile = @"TestData\ReaderSchema.json";
private static readonly DateTime SampleDateTime = DateTime.Parse("2018-08-14 02:05:00.0000000");
private static readonly Guid SampleGuid = Guid.Parse("{2A9C25B9-922E-4611-BB0A-244A9496503C}");
private static readonly Float128 SampleFloat128 = new Float128(0, 42);
private static readonly UnixDateTime SampleUnixDateTime = new UnixDateTime(42);
private static readonly MongoDbObjectId SampleMongoDbObjectId = new MongoDbObjectId(0, 42);
private Namespace schema;
private LayoutResolver resolver;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(RowReaderUnitTests.SchemaFile);
this.schema = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.schema);
}
[TestMethod]
[Owner("jthunter")]
public void ReadMixed()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Mixed").SchemaId);
Assert.IsNotNull(layout);
RowOperationDispatcher d = RowOperationDispatcher.Create<WriteRowDispatcher>(layout, this.resolver);
d.LayoutCodeSwitch("null");
d.LayoutCodeSwitch("bool", value: true);
d.LayoutCodeSwitch("int8", value: (sbyte)-86);
d.LayoutCodeSwitch("int16", value: (short)-21846);
d.LayoutCodeSwitch("int32", value: -1431655766);
d.LayoutCodeSwitch("int64", value: -6148914691236517206L);
d.LayoutCodeSwitch("uint8", value: (byte)0xAA);
d.LayoutCodeSwitch("uint16", value: (ushort)0xAAAA);
d.LayoutCodeSwitch("uint32", value: 0xAAAAAAAA);
d.LayoutCodeSwitch("uint64", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("float32", value: 1.0F / 3.0F);
d.LayoutCodeSwitch("float64", value: 1.0 / 3.0);
d.LayoutCodeSwitch("float128", value: RowReaderUnitTests.SampleFloat128);
d.LayoutCodeSwitch("decimal", value: 1.0M / 3.0M);
d.LayoutCodeSwitch("datetime", value: RowReaderUnitTests.SampleDateTime);
d.LayoutCodeSwitch("unixdatetime", value: RowReaderUnitTests.SampleUnixDateTime);
d.LayoutCodeSwitch("guid", value: RowReaderUnitTests.SampleGuid);
d.LayoutCodeSwitch("mongodbobjectid", value: RowReaderUnitTests.SampleMongoDbObjectId);
d.LayoutCodeSwitch("utf8", value: "abc");
d.LayoutCodeSwitch("utf8_span", value: "abc");
d.LayoutCodeSwitch("binary", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("binary_span", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("var_varint", value: -6148914691236517206L);
d.LayoutCodeSwitch("var_varuint", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("var_utf8", value: "abc");
d.LayoutCodeSwitch("var_utf8_span", value: "abc");
d.LayoutCodeSwitch("var_binary", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("var_binary_span", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("sparse_null");
d.LayoutCodeSwitch("sparse_bool", value: true);
d.LayoutCodeSwitch("sparse_int8", value: (sbyte)-86);
d.LayoutCodeSwitch("sparse_int16", value: (short)-21846);
d.LayoutCodeSwitch("sparse_int32", value: -1431655766);
d.LayoutCodeSwitch("sparse_int64", value: -6148914691236517206L);
d.LayoutCodeSwitch("sparse_uint8", value: (byte)0xAA);
d.LayoutCodeSwitch("sparse_uint16", value: (ushort)0xAAAA);
d.LayoutCodeSwitch("sparse_uint32", value: 0xAAAAAAAA);
d.LayoutCodeSwitch("sparse_uint64", value: 0xAAAAAAAAAAAAAAAAL);
d.LayoutCodeSwitch("sparse_float32", value: 1.0F / 3.0F);
d.LayoutCodeSwitch("sparse_float64", value: 1.0 / 3.0);
d.LayoutCodeSwitch("sparse_float128", value: RowReaderUnitTests.SampleFloat128);
d.LayoutCodeSwitch("sparse_decimal", value: 1.0M / 3.0M);
d.LayoutCodeSwitch("sparse_datetime", value: RowReaderUnitTests.SampleDateTime);
d.LayoutCodeSwitch("sparse_unixdatetime", value: RowReaderUnitTests.SampleUnixDateTime);
d.LayoutCodeSwitch("sparse_guid", value: RowReaderUnitTests.SampleGuid);
d.LayoutCodeSwitch("sparse_mongodbobjectid", value: RowReaderUnitTests.SampleMongoDbObjectId);
d.LayoutCodeSwitch("sparse_utf8", value: "abc");
d.LayoutCodeSwitch("sparse_utf8_span", value: "abc");
d.LayoutCodeSwitch("sparse_binary", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("sparse_binary_span", value: new[] { (byte)0, (byte)1, (byte)2 });
d.LayoutCodeSwitch("array_t<int8>", value: new sbyte[] { -86, -86, -86 });
d.LayoutCodeSwitch("array_t<array_t<float32>>", value: new[] { new float[] { 1, 2, 3 }, new float[] { 1, 2, 3 } });
d.LayoutCodeSwitch("array_t<utf8>", value: new[] { "abc", "def", "hij" });
d.LayoutCodeSwitch("tuple<varint,int64>", value: Tuple.Create(-6148914691236517206L, -6148914691236517206L));
d.LayoutCodeSwitch("tuple<null,tuple<int8,int8>>", value: Tuple.Create(NullValue.Default, Tuple.Create((sbyte)-86, (sbyte)-86)));
d.LayoutCodeSwitch("tuple<bool,udt>", value: Tuple.Create(false, new Point(1, 2)));
d.LayoutCodeSwitch("nullable<int32,int64>", value: Tuple.Create(default(int?), (long?)123L));
d.LayoutCodeSwitch("tagged<utf8>", value: Tuple.Create((byte)3, "hello"));
d.LayoutCodeSwitch("tagged<bool,utf8>", value: Tuple.Create((byte)5, true, "bye"));
d.LayoutCodeSwitch("set_t<utf8>", value: new[] { "abc", "efg", "xzy" });
d.LayoutCodeSwitch("set_t<array_t<int8>>", value: new[] { new sbyte[] { 1, 2, 3 }, new sbyte[] { 4, 5, 6 }, new sbyte[] { 7, 8, 9 } });
d.LayoutCodeSwitch("set_t<set_t<int32>>", value: new[] { new[] { 1, 2, 3 }, new[] { 4, 5, 6 }, new[] { 7, 8, 9 } });
d.LayoutCodeSwitch("set_t<udt>", value: new[] { new Point(1, 2), new Point(3, 4), new Point(5, 6) });
d.LayoutCodeSwitch("map_t<utf8,utf8>", value: new[] { Tuple.Create("Mark", "Luke"), Tuple.Create("Harrison", "Han") });
d.LayoutCodeSwitch(
"map_t<int8,array_t<int8>>",
value: new[] { Tuple.Create((sbyte)1, new sbyte[] { 1, 2, 3 }), Tuple.Create((sbyte)2, new sbyte[] { 4, 5, 6 }) });
d.LayoutCodeSwitch(
"map_t<int16,map_t<int32,int32>>",
value: new[]
{
Tuple.Create((short)1, new[] { Tuple.Create(1, 2), Tuple.Create(3, 4) }),
Tuple.Create((short)2, new[] { Tuple.Create(5, 6), Tuple.Create(7, 8) }),
});
d.LayoutCodeSwitch(
"map_t<float64,udt>",
value: new[]
{
Tuple.Create(1.0, new Point(1, 2)),
Tuple.Create(2.0, new Point(3, 4)),
Tuple.Create(3.0, new Point(5, 6)),
});
RowReader reader = d.GetReader();
Assert.AreEqual(reader.Length, d.Row.Length);
RowReaderUnitTests.PrintReader(ref reader, 0);
}
[TestMethod]
[Owner("jthunter")]
public void ReadScopes()
{
MemorySpanResizer<byte> resizer = new MemorySpanResizer<byte>(0);
RowBuffer row = new RowBuffer(0, resizer);
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Mixed").SchemaId);
row.InitLayout(HybridRowVersion.V1, layout, this.resolver);
ResultAssert.IsSuccess(RowWriter.WriteBuffer(ref row, 2, RowReaderUnitTests.WriteNestedDocument));
RowReader rowReader = new RowReader(ref row);
ResultAssert.IsSuccess(RowReaderUnitTests.ReadNestedDocumentDelegate(ref rowReader, 0));
rowReader = new RowReader(ref row);
ResultAssert.IsSuccess(RowReaderUnitTests.ReadNestedDocumentNonDelegate(ref rowReader, 0));
rowReader = new RowReader(ref row);
ResultAssert.IsSuccess(RowReaderUnitTests.ReadNestedDocumentNonDelegateWithSkipScope(ref rowReader, 0));
// SkipScope not okay after advancing parent
rowReader = new RowReader(ref row);
Assert.IsTrue(rowReader.Read());
Assert.AreEqual(rowReader.Type.LayoutCode, LayoutCode.ObjectScope);
RowReader nestedScope = rowReader.ReadScope();
ResultAssert.IsSuccess(RowReaderUnitTests.ReadNestedDocumentDelegate(ref nestedScope, 0));
Assert.IsTrue(rowReader.Read());
Result result = rowReader.SkipScope(ref nestedScope);
Assert.AreNotEqual(Result.Success, result);
}
internal static void PrintReader(ref RowReader reader, int indent)
{
string str;
ResultAssert.IsSuccess(DiagnosticConverter.ReaderToString(ref reader, out str));
Console.WriteLine(str);
}
private static Result WriteNestedDocument(ref RowWriter writer, TypeArgument typeArgument, int level)
{
TypeArgument tupleArgument = new TypeArgument(
LayoutType.Tuple,
new TypeArgumentList(new[]
{
new TypeArgument(LayoutType.Int32),
new TypeArgument(LayoutType.Int32),
new TypeArgument(LayoutType.Int32),
}));
Result WriteTuple(ref RowWriter tupleWriter, TypeArgument tupleTypeArgument, int unused)
{
ResultAssert.IsSuccess(tupleWriter.WriteInt32(null, 1));
ResultAssert.IsSuccess(tupleWriter.WriteInt32(null, 2));
ResultAssert.IsSuccess(tupleWriter.WriteInt32(null, 3));
return Result.Success;
}
if (level == 0)
{
ResultAssert.IsSuccess(writer.WriteScope("x", tupleArgument, 0, WriteTuple));
return Result.Success;
}
ResultAssert.IsSuccess(writer.WriteScope("a", new TypeArgument(LayoutType.Object), level - 1, RowReaderUnitTests.WriteNestedDocument));
ResultAssert.IsSuccess(writer.WriteScope("x", tupleArgument, 0, WriteTuple));
ResultAssert.IsSuccess(writer.WriteScope("b", new TypeArgument(LayoutType.Object), level - 1, RowReaderUnitTests.WriteNestedDocument));
ResultAssert.IsSuccess(writer.WriteScope("y", tupleArgument, 0, WriteTuple));
ResultAssert.IsSuccess(writer.WriteScope("c", new TypeArgument(LayoutType.Object), level - 1, RowReaderUnitTests.WriteNestedDocument));
return Result.Success;
}
private static Result ReadNestedDocumentDelegate(ref RowReader reader, int context)
{
while (reader.Read())
{
switch (reader.Type.LayoutCode)
{
case LayoutCode.TupleScope:
{
ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadTuplePartial));
break;
}
case LayoutCode.ObjectScope:
{
ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadNestedDocumentDelegate));
break;
}
}
}
return Result.Success;
}
private static Result ReadNestedDocumentNonDelegate(ref RowReader reader, int context)
{
while (reader.Read())
{
switch (reader.Type.LayoutCode)
{
case LayoutCode.TupleScope:
{
RowReader nested = reader.ReadScope();
ResultAssert.IsSuccess(RowReaderUnitTests.ReadTuplePartial(ref nested, 0));
break;
}
case LayoutCode.ObjectScope:
{
RowReader nested = reader.ReadScope();
ResultAssert.IsSuccess(RowReaderUnitTests.ReadNestedDocumentNonDelegate(ref nested, 0));
ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadNestedDocumentDelegate));
break;
}
}
}
return Result.Success;
}
private static Result ReadNestedDocumentNonDelegateWithSkipScope(ref RowReader reader, int context)
{
while (reader.Read())
{
switch (reader.Type.LayoutCode)
{
case LayoutCode.TupleScope:
{
RowReader nested = reader.ReadScope();
ResultAssert.IsSuccess(RowReaderUnitTests.ReadTuplePartial(ref nested, 0));
ResultAssert.IsSuccess(reader.SkipScope(ref nested));
break;
}
case LayoutCode.ObjectScope:
{
RowReader nested = reader.ReadScope();
ResultAssert.IsSuccess(RowReaderUnitTests.ReadNestedDocumentNonDelegate(ref nested, 0));
ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadNestedDocumentDelegate));
ResultAssert.IsSuccess(reader.SkipScope(ref nested));
break;
}
}
}
return Result.Success;
}
private static Result ReadTuplePartial(ref RowReader reader, int unused)
{
// Read only part of our tuple
Assert.IsTrue(reader.Read());
Assert.IsTrue(reader.Read());
return Result.Success;
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
internal sealed class Point : IDispatchable, IRowSerializable
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Point && this.Equals((Point)obj);
}
public override int GetHashCode()
{
unchecked
{
return (this.X.GetHashCode() * 397) ^ this.Y.GetHashCode();
}
}
Result IRowSerializable.Write(ref RowWriter writer, TypeArgument typeArg)
{
Result result = writer.WriteInt32("x", this.X);
if (result != Result.Success)
{
return result;
}
return writer.WriteInt32("y", this.Y);
}
void IDispatchable.Dispatch(ref RowOperationDispatcher dispatcher, ref RowCursor scope)
{
dispatcher.LayoutCodeSwitch(ref scope, "x", value: this.X);
dispatcher.LayoutCodeSwitch(ref scope, "y", value: this.Y);
}
private bool Equals(Point other)
{
return this.X == other.X && this.Y == other.Y;
}
}
}
}

View File

@@ -0,0 +1,553 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Test code.")]
[DeploymentItem(RowWriterUnitTests.SchemaFile, "TestData")]
public sealed class RowWriterUnitTests
{
private const int InitialRowSize = 2 * 1024 * 1024;
private const string SchemaFile = @"TestData\ReaderSchema.json";
private static readonly DateTime SampleDateTime = DateTime.Parse("2018-08-14 02:05:00.0000000");
private static readonly Guid SampleGuid = Guid.Parse("{2A9C25B9-922E-4611-BB0A-244A9496503C}");
private static readonly Float128 SampleFloat128 = new Float128(0, 42);
private static readonly UnixDateTime SampleUnixDateTime = new UnixDateTime(42);
private static readonly MongoDbObjectId SampleMongoDbObjectId = new MongoDbObjectId(0, 42);
private Namespace schema;
private LayoutResolver resolver;
[TestInitialize]
public void TestInitialize()
{
string json = File.ReadAllText(RowWriterUnitTests.SchemaFile);
this.schema = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.schema);
}
[TestMethod]
[Owner("jthunter")]
public void WriteMixed()
{
Layout layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Mixed").SchemaId);
Assert.IsNotNull(layout);
RowBuffer row = new RowBuffer(RowWriterUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, layout, this.resolver);
int writerLength = 0;
ResultAssert.IsSuccess(
RowWriter.WriteBuffer(
ref row,
null,
(ref RowWriter writer, TypeArgument rootTypeArg, object ignored) =>
{
ResultAssert.IsSuccess(writer.WriteNull("null"));
ResultAssert.IsSuccess(writer.WriteBool("bool", true));
ResultAssert.IsSuccess(writer.WriteInt8("int8", (sbyte)-86));
ResultAssert.IsSuccess(writer.WriteInt16("int16", (short)-21846));
ResultAssert.IsSuccess(writer.WriteInt32("int32", -1431655766));
ResultAssert.IsSuccess(writer.WriteInt64("int64", -6148914691236517206L));
ResultAssert.IsSuccess(writer.WriteUInt8("uint8", (byte)0xAA));
ResultAssert.IsSuccess(writer.WriteUInt16("uint16", (ushort)0xAAAA));
ResultAssert.IsSuccess(writer.WriteUInt32("uint32", 0xAAAAAAAA));
ResultAssert.IsSuccess(writer.WriteUInt64("uint64", 0xAAAAAAAAAAAAAAAAL));
ResultAssert.IsSuccess(writer.WriteFloat32("float32", 1.0F / 3.0F));
ResultAssert.IsSuccess(writer.WriteFloat64("float64", 1.0 / 3.0));
ResultAssert.IsSuccess(writer.WriteFloat128("float128", RowWriterUnitTests.SampleFloat128));
ResultAssert.IsSuccess(writer.WriteDecimal("decimal", 1.0M / 3.0M));
ResultAssert.IsSuccess(writer.WriteDateTime("datetime", RowWriterUnitTests.SampleDateTime));
ResultAssert.IsSuccess(writer.WriteUnixDateTime("unixdatetime", RowWriterUnitTests.SampleUnixDateTime));
ResultAssert.IsSuccess(writer.WriteGuid("guid", RowWriterUnitTests.SampleGuid));
ResultAssert.IsSuccess(writer.WriteMongoDbObjectId("mongodbobjectid", RowWriterUnitTests.SampleMongoDbObjectId));
ResultAssert.IsSuccess(writer.WriteString("utf8", "abc"));
ResultAssert.IsSuccess(writer.WriteString("utf8_span", Utf8Span.TranscodeUtf16("abc")));
ResultAssert.IsSuccess(writer.WriteBinary("binary", new[] { (byte)0, (byte)1, (byte)2 }));
ResultAssert.IsSuccess(writer.WriteBinary("binary_span", new[] { (byte)0, (byte)1, (byte)2 }.AsSpan()));
ResultAssert.IsSuccess(
writer.WriteBinary("binary_sequence", new ReadOnlySequence<byte>(new[] { (byte)0, (byte)1, (byte)2 })));
ResultAssert.IsSuccess(writer.WriteVarInt("var_varint", -6148914691236517206L));
ResultAssert.IsSuccess(writer.WriteVarUInt("var_varuint", 0xAAAAAAAAAAAAAAAAL));
ResultAssert.IsSuccess(writer.WriteString("var_utf8", "abc"));
ResultAssert.IsSuccess(writer.WriteString("var_utf8_span", Utf8Span.TranscodeUtf16("abc")));
ResultAssert.IsSuccess(writer.WriteBinary("var_binary", new[] { (byte)0, (byte)1, (byte)2 }));
ResultAssert.IsSuccess(writer.WriteBinary("var_binary_span", new[] { (byte)0, (byte)1, (byte)2 }.AsSpan()));
ResultAssert.IsSuccess(
writer.WriteBinary("var_binary_sequence", new ReadOnlySequence<byte>(new[] { (byte)0, (byte)1, (byte)2 })));
ResultAssert.IsSuccess(writer.WriteNull("sparse_null"));
ResultAssert.IsSuccess(writer.WriteBool("sparse_bool", true));
ResultAssert.IsSuccess(writer.WriteInt8("sparse_int8", (sbyte)-86));
ResultAssert.IsSuccess(writer.WriteInt16("sparse_int16", (short)-21846));
ResultAssert.IsSuccess(writer.WriteInt32("sparse_int32", -1431655766));
ResultAssert.IsSuccess(writer.WriteInt64("sparse_int64", -6148914691236517206L));
ResultAssert.IsSuccess(writer.WriteUInt8("sparse_uint8", (byte)0xAA));
ResultAssert.IsSuccess(writer.WriteUInt16("sparse_uint16", (ushort)0xAAAA));
ResultAssert.IsSuccess(writer.WriteUInt32("sparse_uint32", 0xAAAAAAAA));
ResultAssert.IsSuccess(writer.WriteUInt64("sparse_uint64", 0xAAAAAAAAAAAAAAAAL));
ResultAssert.IsSuccess(writer.WriteFloat32("sparse_float32", 1.0F / 3.0F));
ResultAssert.IsSuccess(writer.WriteFloat64("sparse_float64", 1.0 / 3.0));
ResultAssert.IsSuccess(writer.WriteFloat128("sparse_float128", RowWriterUnitTests.SampleFloat128));
ResultAssert.IsSuccess(writer.WriteDecimal("sparse_decimal", 1.0M / 3.0M));
ResultAssert.IsSuccess(writer.WriteDateTime("sparse_datetime", RowWriterUnitTests.SampleDateTime));
ResultAssert.IsSuccess(writer.WriteUnixDateTime("sparse_unixdatetime", RowWriterUnitTests.SampleUnixDateTime));
ResultAssert.IsSuccess(writer.WriteGuid("sparse_guid", RowWriterUnitTests.SampleGuid));
ResultAssert.IsSuccess(writer.WriteMongoDbObjectId("sparse_mongodbobjectid", RowWriterUnitTests.SampleMongoDbObjectId));
ResultAssert.IsSuccess(writer.WriteString("sparse_utf8", "abc"));
ResultAssert.IsSuccess(writer.WriteString("sparse_utf8_span", Utf8Span.TranscodeUtf16("abc")));
ResultAssert.IsSuccess(writer.WriteBinary("sparse_binary", new[] { (byte)0, (byte)1, (byte)2 }));
ResultAssert.IsSuccess(writer.WriteBinary("sparse_binary_span", new[] { (byte)0, (byte)1, (byte)2 }.AsSpan()));
ResultAssert.IsSuccess(
writer.WriteBinary("sparse_binary_sequence", new ReadOnlySequence<byte>(new[] { (byte)0, (byte)1, (byte)2 })));
LayoutColumn col;
Assert.IsTrue(layout.TryFind("array_t<int8>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new sbyte[] { -86, -87, -88 },
(ref RowWriter writer2, TypeArgument typeArg, sbyte[] values) =>
{
foreach (sbyte value in values)
{
ResultAssert.IsSuccess(writer2.WriteInt8(null, value));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("array_t<array_t<float32>>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[] { new float[] { 1, 2, 3 }, new float[] { 1, 2, 3 } },
(ref RowWriter writer2, TypeArgument typeArg, float[][] values) =>
{
foreach (float[] value in values)
{
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
typeArg.TypeArgs[0],
value,
(ref RowWriter writer3, TypeArgument typeArg2, float[] values2) =>
{
foreach (float value2 in values2)
{
ResultAssert.IsSuccess(writer3.WriteFloat32(null, value2));
}
return Result.Success;
}));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("array_t<utf8>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[] { "abc", "def", "hij" },
(ref RowWriter writer2, TypeArgument typeArg, string[] values) =>
{
foreach (string value in values)
{
ResultAssert.IsSuccess(writer2.WriteString(null, value));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("tuple<varint,int64>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
Tuple.Create(-6148914691236517206L, -6148914691236517206L),
(ref RowWriter writer2, TypeArgument typeArg, Tuple<long, long> values) =>
{
ResultAssert.IsSuccess(writer2.WriteVarInt(null, values.Item1));
ResultAssert.IsSuccess(writer2.WriteInt64(null, values.Item2));
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("tuple<null,tuple<int8,int8>>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
Tuple.Create(NullValue.Default, Tuple.Create((sbyte)-86, (sbyte)-86)),
(ref RowWriter writer2, TypeArgument typeArg, Tuple<NullValue, Tuple<sbyte, sbyte>> values) =>
{
ResultAssert.IsSuccess(writer2.WriteNull(null));
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
typeArg.TypeArgs[1],
values.Item2,
(ref RowWriter writer3, TypeArgument typeArg2, Tuple<sbyte, sbyte> values2) =>
{
ResultAssert.IsSuccess(writer3.WriteInt8(null, values2.Item1));
ResultAssert.IsSuccess(writer3.WriteInt8(null, values2.Item2));
return Result.Success;
}));
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("tuple<bool,udt>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
Tuple.Create(false, new RowReaderUnitTests.Point(1, 2)),
(ref RowWriter writer2, TypeArgument typeArg, Tuple<bool, RowReaderUnitTests.Point> values) =>
{
ResultAssert.IsSuccess(writer2.WriteBool(null, values.Item1));
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
typeArg.TypeArgs[1],
values.Item2,
(ref RowWriter writer3, TypeArgument typeArg2, IRowSerializable values2) =>
values2.Write(ref writer3, typeArg2)));
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("nullable<int32,int64>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
Tuple.Create(default(int?), (long?)123L),
(ref RowWriter writer2, TypeArgument typeArg, Tuple<int?, long?> values) =>
{
RowWriter.WriterFunc<int?> f0 = null;
if (values.Item1.HasValue)
{
f0 = (ref RowWriter writer3, TypeArgument typeArg2, int? value) => writer3.WriteInt32(null, value.Value);
}
ResultAssert.IsSuccess(writer2.WriteScope(null, typeArg.TypeArgs[0], values.Item1, f0));
RowWriter.WriterFunc<long?> f1 = null;
if (values.Item2.HasValue)
{
f1 = (ref RowWriter writer3, TypeArgument typeArg2, long? value) => writer3.WriteInt64(null, value.Value);
}
ResultAssert.IsSuccess(writer2.WriteScope(null, typeArg.TypeArgs[1], values.Item2, f1));
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("tagged<utf8>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
Tuple.Create((byte)3, "hello"),
(ref RowWriter writer2, TypeArgument typeArg, Tuple<byte, string> values) =>
{
ResultAssert.IsSuccess(writer2.WriteUInt8(null, values.Item1));
ResultAssert.IsSuccess(writer2.WriteString(null, values.Item2));
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("tagged<bool,utf8>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
Tuple.Create((byte)5, true, "bye"),
(ref RowWriter writer2, TypeArgument typeArg, Tuple<byte, bool, string> values) =>
{
ResultAssert.IsSuccess(writer2.WriteUInt8(null, values.Item1));
ResultAssert.IsSuccess(writer2.WriteBool(null, values.Item2));
ResultAssert.IsSuccess(writer2.WriteString(null, values.Item3));
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("set_t<utf8>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[] { "abc", "xzy", "efg" },
(ref RowWriter writer2, TypeArgument typeArg, string[] values) =>
{
foreach (string value in values)
{
ResultAssert.IsSuccess(writer2.WriteString(null, value));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("set_t<array_t<int8>>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[] { new sbyte[] { 7, 8, 9 }, new sbyte[] { 4, 5, 6 }, new sbyte[] { 1, 2, 3 } },
(ref RowWriter writer2, TypeArgument typeArg, sbyte[][] values) =>
{
foreach (sbyte[] value in values)
{
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
typeArg.TypeArgs[0],
value,
(ref RowWriter writer3, TypeArgument typeArg2, sbyte[] values2) =>
{
foreach (sbyte value2 in values2)
{
ResultAssert.IsSuccess(writer3.WriteInt8(null, value2));
}
return Result.Success;
}));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("set_t<set_t<int32>>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[] { new[] { 4, 5, 6 }, new[] { 7, 8, 9 }, new[] { 1, 2, 3 } },
(ref RowWriter writer2, TypeArgument typeArg, int[][] values) =>
{
foreach (int[] value in values)
{
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
typeArg.TypeArgs[0],
value,
(ref RowWriter writer3, TypeArgument typeArg2, int[] values2) =>
{
foreach (int value2 in values2)
{
ResultAssert.IsSuccess(writer3.WriteInt32(null, value2));
}
return Result.Success;
}));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("set_t<udt>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[] { new RowReaderUnitTests.Point(1, 2), new RowReaderUnitTests.Point(3, 4), new RowReaderUnitTests.Point(5, 6) },
(ref RowWriter writer2, TypeArgument typeArg, RowReaderUnitTests.Point[] values) =>
{
foreach (RowReaderUnitTests.Point value in values)
{
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
typeArg.TypeArgs[0],
value,
(ref RowWriter writer3, TypeArgument typeArg2, IRowSerializable values2) =>
values2.Write(ref writer3, typeArg2)));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("map_t<utf8,utf8>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[] { Tuple.Create("Harrison", "Han"), Tuple.Create("Mark", "Luke") },
(ref RowWriter writer2, TypeArgument typeArg, Tuple<string, string>[] values) =>
{
foreach (Tuple<string, string> value in values)
{
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
new TypeArgument(LayoutType.TypedTuple, typeArg.TypeArgs),
value,
(ref RowWriter writer3, TypeArgument typeArg2, Tuple<string, string> values2) =>
{
ResultAssert.IsSuccess(writer3.WriteString(null, values2.Item1));
ResultAssert.IsSuccess(writer3.WriteString(null, values2.Item2));
return Result.Success;
}));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("map_t<int8,array_t<int8>>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[] { Tuple.Create((sbyte)2, new sbyte[] { 4, 5, 6 }), Tuple.Create((sbyte)1, new sbyte[] { 1, 2, 3 }) },
(ref RowWriter writer2, TypeArgument typeArg, Tuple<sbyte, sbyte[]>[] values) =>
{
foreach (Tuple<sbyte, sbyte[]> value in values)
{
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
new TypeArgument(LayoutType.TypedTuple, typeArg.TypeArgs),
value,
(ref RowWriter writer3, TypeArgument typeArg2, Tuple<sbyte, sbyte[]> values2) =>
{
ResultAssert.IsSuccess(writer3.WriteInt8(null, values2.Item1));
ResultAssert.IsSuccess(
writer3.WriteScope(
null,
typeArg2.TypeArgs[1],
values2.Item2,
(ref RowWriter writer4, TypeArgument typeArg3, sbyte[] values3) =>
{
foreach (sbyte value3 in values3)
{
ResultAssert.IsSuccess(writer4.WriteInt8(null, value3));
}
return Result.Success;
}));
return Result.Success;
}));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("map_t<int16,map_t<int32,int32>>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[]
{
Tuple.Create((short)2, new[] { Tuple.Create(7, 8), Tuple.Create(5, 6) }),
Tuple.Create((short)1, new[] { Tuple.Create(3, 4), Tuple.Create(1, 2) }),
},
(ref RowWriter writer2, TypeArgument typeArg, Tuple<short, Tuple<int, int>[]>[] values) =>
{
foreach (Tuple<short, Tuple<int, int>[]> value in values)
{
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
new TypeArgument(LayoutType.TypedTuple, typeArg.TypeArgs),
value,
(ref RowWriter writer3, TypeArgument typeArg2, Tuple<short, Tuple<int, int>[]> values2) =>
{
ResultAssert.IsSuccess(writer3.WriteInt16(null, values2.Item1));
ResultAssert.IsSuccess(
writer3.WriteScope(
null,
typeArg2.TypeArgs[1],
values2.Item2,
(ref RowWriter writer4, TypeArgument typeArg3, Tuple<int, int>[] values3) =>
{
foreach (Tuple<int, int> value3 in values3)
{
ResultAssert.IsSuccess(
writer4.WriteScope(
null,
new TypeArgument(LayoutType.TypedTuple, typeArg3.TypeArgs),
value3,
(ref RowWriter writer5, TypeArgument typeArg4, Tuple<int, int> values4) =>
{
ResultAssert.IsSuccess(writer5.WriteInt32(null, values4.Item1));
ResultAssert.IsSuccess(writer5.WriteInt32(null, values4.Item2));
return Result.Success;
}));
}
return Result.Success;
}));
return Result.Success;
}));
}
return Result.Success;
}));
Assert.IsTrue(layout.TryFind("map_t<float64,udt>", out col));
ResultAssert.IsSuccess(
writer.WriteScope(
col.Path,
col.TypeArg,
new[]
{
Tuple.Create(1.0, new RowReaderUnitTests.Point(1, 2)),
Tuple.Create(2.0, new RowReaderUnitTests.Point(3, 4)),
Tuple.Create(3.0, new RowReaderUnitTests.Point(5, 6)),
},
(ref RowWriter writer2, TypeArgument typeArg, Tuple<double, RowReaderUnitTests.Point>[] values) =>
{
foreach (Tuple<double, RowReaderUnitTests.Point> value in values)
{
ResultAssert.IsSuccess(
writer2.WriteScope(
null,
new TypeArgument(LayoutType.TypedTuple, typeArg.TypeArgs),
value,
(ref RowWriter writer3, TypeArgument typeArg2, Tuple<double, RowReaderUnitTests.Point> values2) =>
{
ResultAssert.IsSuccess(writer3.WriteFloat64(null, values2.Item1));
ResultAssert.IsSuccess(
writer3.WriteScope(
null,
typeArg2.TypeArgs[1],
values2.Item2,
(ref RowWriter writer4, TypeArgument typeArg3, IRowSerializable values3) =>
values3.Write(ref writer4, typeArg3)));
return Result.Success;
}));
}
return Result.Success;
}));
// Save the RowWriter length after everything is written for later comparison.
writerLength = writer.Length;
return Result.Success;
}));
RowReader reader = new RowReader(ref row);
Assert.AreEqual(reader.Length, writerLength);
RowReaderUnitTests.PrintReader(ref reader, 0);
}
}
}

View File

@@ -0,0 +1,249 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
[TestClass]
[DeploymentItem(SchemaHashUnitTests.SchemaFile, "TestData")]
public class SchemaHashUnitTests
{
private const string SchemaFile = @"TestData\SchemaHashCoverageSchema.json";
private Namespace ns;
private Schema tableSchema;
[TestInitialize]
public void InitializeSuite()
{
string json = File.ReadAllText(SchemaHashUnitTests.SchemaFile);
this.ns = Namespace.Parse(json);
this.tableSchema = this.ns.Schemas.Find(s => s.Name == "Table");
}
[TestMethod]
[Owner("jthunter")]
public void SchemaHashCompileTest()
{
Layout layout = this.tableSchema.Compile(this.ns);
Assert.IsNotNull(layout);
}
[TestMethod]
[Owner("jthunter")]
public void SchemaHashTest()
{
(ulong low, ulong high) hash = SchemaHash.ComputeHash(this.ns, this.tableSchema);
Assert.AreNotEqual((0, 0), hash);
(ulong low, ulong high) hash2 = SchemaHash.ComputeHash(this.ns, this.tableSchema, (1, 1));
Assert.AreNotEqual(hash, hash2);
// Test clone are the same.
Schema clone = SchemaHashUnitTests.Clone(this.tableSchema);
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreEqual(hash, hash2);
// Test Schema changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Name = "something else";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreEqual(hash, hash2); // Name not part of the hash
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Comment = "something else";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreEqual(hash, hash2); // Comment not part of the hash
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Version = (SchemaLanguageVersion)1;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreEqual(hash, hash2); // Encoding version not part of the hash
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.SchemaId = new SchemaId(42);
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Type = TypeKind.Int8;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Options changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Options.EnablePropertyLevelTimestamp = !clone.Options.EnablePropertyLevelTimestamp;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Options.DisallowUnschematized = !clone.Options.DisallowUnschematized;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Partition Keys changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.PartitionKeys[0].Path = "something else";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Primary Sort Keys changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.PrimarySortKeys[0].Path = "something else";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.PrimarySortKeys[0].Direction = SortDirection.Descending;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Static Keys changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.StaticKeys[0].Path = "something else";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Properties changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Properties[0].Comment = "something else";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreEqual(hash, hash2); // Comment not part of the hash
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Properties[0].Path = "something else";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Properties[0].PropertyType.ApiType = "something else";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Properties[0].PropertyType.Type = TypeKind.Binary;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
clone = SchemaHashUnitTests.Clone(this.tableSchema);
clone.Properties[0].PropertyType.Nullable = !clone.Properties[0].PropertyType.Nullable;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Primitive Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[0].PropertyType as PrimitivePropertyType).Length = 42;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[0].PropertyType as PrimitivePropertyType).Storage = StorageKind.Variable;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Scope Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[1].PropertyType as ScopePropertyType).Immutable = !(clone.Properties[1].PropertyType as ScopePropertyType).Immutable;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Array Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[1].PropertyType as ArrayPropertyType).Items = clone.Properties[0].PropertyType;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Object Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[2].PropertyType as ObjectPropertyType).Properties[0] = clone.Properties[0];
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Map Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[3].PropertyType as MapPropertyType).Keys = clone.Properties[0].PropertyType;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[3].PropertyType as MapPropertyType).Values = clone.Properties[0].PropertyType;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Set Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[4].PropertyType as SetPropertyType).Items = clone.Properties[0].PropertyType;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Tagged Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[5].PropertyType as TaggedPropertyType).Items[0] = clone.Properties[0].PropertyType;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test Tuple Property Type changes
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[6].PropertyType as TuplePropertyType).Items[0] = clone.Properties[0].PropertyType;
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Test UDT Property Type changes
try
{
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[7].PropertyType as UdtPropertyType).Name = "some non-existing UDT name";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.Fail("Should have thrown an exception.");
}
catch (Exception ex)
{
Assert.IsNotNull(ex);
}
try
{
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[7].PropertyType as UdtPropertyType).Name = "Table";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.Fail("Should have thrown an exception.");
}
catch (Exception ex)
{
Assert.IsNotNull(ex);
}
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[7].PropertyType as UdtPropertyType).Name = "Table";
(clone.Properties[7].PropertyType as UdtPropertyType).SchemaId = new SchemaId(2);
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreNotEqual(hash, hash2);
// Renaming an UDT is not a breaking change as long as the SchemaId has not changed.
this.ns.Schemas[0].Name = "RenameActualUDT";
clone = SchemaHashUnitTests.Clone(this.tableSchema);
(clone.Properties[7].PropertyType as UdtPropertyType).Name = "RenameActualUDT";
hash2 = SchemaHash.ComputeHash(this.ns, clone);
Assert.AreEqual(hash, hash2);
}
private static Schema Clone(Schema s)
{
JsonSerializerSettings settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented,
CheckAdditionalContent = true,
};
string json = JsonConvert.SerializeObject(s, settings);
return JsonConvert.DeserializeObject<Schema>(json, settings);
}
}
}

View File

@@ -0,0 +1,47 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
[TestClass]
public class SchemaIdUnitTests
{
[TestMethod]
[Owner("jthunter")]
public void SchemaIdTest()
{
SchemaId a = new SchemaId(1);
SchemaId b = new SchemaId(2);
SchemaId c = default(SchemaId);
Assert.AreEqual(1, a.Id);
Assert.AreEqual(2, b.Id);
Assert.AreEqual(SchemaId.Invalid, c);
Assert.AreNotEqual(2, a.Id);
Assert.AreNotEqual(a, b);
Assert.IsTrue(a == a);
Assert.IsTrue(a != b);
Assert.IsFalse(a.Equals(null));
Assert.AreEqual(a.GetHashCode(), new SchemaId(1).GetHashCode());
Assert.AreNotEqual(a.GetHashCode(), new SchemaId(-1).GetHashCode());
string json = JsonConvert.SerializeObject(a);
Assert.AreEqual("1", json);
Assert.AreEqual("1", a.ToString());
Assert.AreEqual(a, JsonConvert.DeserializeObject<SchemaId>(json));
json = JsonConvert.SerializeObject(b);
Assert.AreEqual("2", json);
Assert.AreEqual("2", b.ToString());
Assert.AreEqual(b, JsonConvert.DeserializeObject<SchemaId>(json));
json = JsonConvert.SerializeObject(c);
Assert.AreEqual("0", json);
Assert.AreEqual("0", c.ToString());
Assert.AreEqual(c, JsonConvert.DeserializeObject<SchemaId>(json));
}
}
}

View File

@@ -0,0 +1,603 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
[TestClass]
[SuppressMessage("Naming", "DontUseVarForVariableTypes", Justification = "The types here are anonymous.")]
public class SchemaUnitTests
{
[TestMethod]
[Owner("jthunter")]
public void ParseNamespace()
{
// Test empty schemas.
{
string[] emptyNamespaceJsonInput =
{
@"{ }",
@"{'schemas': [ ] }",
@"{'name': null }",
@"{'name': null, 'schemas': null }",
@"{'version': 'v1', 'name': null, 'schemas': null }",
};
foreach (string json in emptyNamespaceJsonInput)
{
Namespace n1 = Namespace.Parse(json);
Assert.IsNull(n1.Name, "Got: {0}, Json: {1}", n1.Name, json);
Assert.IsNotNull(n1.Schemas, "Json: {0}", json);
Assert.AreEqual(0, n1.Schemas.Count, "Got: {0}, Json: {1}", n1.Schemas, json);
Assert.AreEqual(SchemaLanguageVersion.V1, n1.Version);
}
}
// Test simple schemas and schema options.
{
string json = @"{'name': 'myschema', 'schemas': null }";
Namespace n1 = Namespace.Parse(json);
Assert.AreEqual("myschema", n1.Name, "Json: {0}", json);
Assert.IsNotNull(n1.Schemas, "Json: {0}", json);
// Version defaults propertly when NOT specified.
Assert.AreEqual(SchemaLanguageVersion.V1, n1.Version);
}
{
string json = @"{'name': 'myschema', 'schemas': [
{'version': 'v1', 'name': 'emptyTable', 'id': -1, 'type': 'schema',
'options': { 'disallowUnschematized': true }, 'properties': null } ] }";
Namespace n1 = Namespace.Parse(json);
Assert.AreEqual("myschema", n1.Name, "Json: {0}", json);
Assert.AreEqual(1, n1.Schemas.Count, "Json: {0}", json);
Assert.AreEqual("emptyTable", n1.Schemas[0].Name, "Json: {0}", json);
Assert.AreEqual(new SchemaId(-1), n1.Schemas[0].SchemaId, "Json: {0}", json);
Assert.AreEqual(TypeKind.Schema, n1.Schemas[0].Type, "Json: {0}", json);
Assert.AreEqual(true, n1.Schemas[0].Options.DisallowUnschematized, "Json: {0}", json);
Assert.IsNotNull(n1.Schemas[0].Properties.Count, "Json: {0}", json);
Assert.AreEqual(0, n1.Schemas[0].Properties.Count, "Json: {0}", json);
Assert.AreEqual(SchemaLanguageVersion.V1, n1.Version);
}
// Test basic schema with primitive columns.
{
string json = @"{'name': 'myschema', 'schemas': [
{'name': 'myUDT', 'id': 1, 'type': 'schema', 'options': { 'disallowUnschematized': false },
'properties': [
{ 'path': 'a', 'type': { 'type': 'int8', 'storage': 'fixed' }},
{ 'path': 'b', 'type': { 'type': 'utf8', 'storage': 'variable' }}
] }
] }";
Namespace n1 = Namespace.Parse(json);
Assert.AreEqual(1, n1.Schemas.Count, "Json: {0}", json);
Assert.AreEqual("myUDT", n1.Schemas[0].Name, "Json: {0}", json);
Assert.AreEqual(new SchemaId(1), n1.Schemas[0].SchemaId, "Json: {0}", json);
Assert.AreEqual(TypeKind.Schema, n1.Schemas[0].Type, "Json: {0}", json);
Assert.AreEqual(false, n1.Schemas[0].Options.DisallowUnschematized, "Json: {0}", json);
Assert.AreEqual(2, n1.Schemas[0].Properties.Count, "Json: {0}", json);
var expectedProps = new[]
{
new { Path = "a", Type = TypeKind.Int8, Storage = StorageKind.Fixed },
new { Path = "b", Type = TypeKind.Utf8, Storage = StorageKind.Variable },
};
for (int i = 0; i < n1.Schemas[0].Properties.Count; i++)
{
Property p = n1.Schemas[0].Properties[i];
Assert.AreEqual(expectedProps[i].Path, p.Path, "Json: {0}", json);
Assert.AreEqual(expectedProps[i].Type, p.PropertyType.Type, "Json: {0}", json);
PrimitivePropertyType sp = (PrimitivePropertyType)p.PropertyType;
Assert.AreEqual(expectedProps[i].Storage, sp.Storage, "Json: {0}", json);
}
}
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(@"TestData\CoverageSchema.json", "TestData")]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(@"TestData\CoverageSchema.json");
Namespace n1 = Namespace.Parse(json);
JsonSerializerSettings settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented,
};
string json2 = JsonConvert.SerializeObject(n1, settings);
Namespace n2 = Namespace.Parse(json2);
string json3 = JsonConvert.SerializeObject(n2, settings);
Assert.AreEqual(json2, json3);
}
[TestMethod]
[Owner("jthunter")]
public void ParseSchemaPrimitives()
{
// Test all primitive column types.
dynamic[] expectedSchemas =
{
new { Json = @"{'type': 'bool', 'storage': 'fixed'}", Type = TypeKind.Boolean },
new { Json = @"{'type': 'int8', 'storage': 'fixed'}", Type = TypeKind.Int8 },
new { Json = @"{'type': 'int16', 'storage': 'fixed'}", Type = TypeKind.Int16 },
new { Json = @"{'type': 'int32', 'storage': 'fixed'}", Type = TypeKind.Int32 },
new { Json = @"{'type': 'int64', 'storage': 'fixed'}", Type = TypeKind.Int64 },
new { Json = @"{'type': 'uint8', 'storage': 'fixed'}", Type = TypeKind.UInt8 },
new { Json = @"{'type': 'uint16', 'storage': 'fixed'}", Type = TypeKind.UInt16 },
new { Json = @"{'type': 'uint32', 'storage': 'fixed'}", Type = TypeKind.UInt32 },
new { Json = @"{'type': 'uint64', 'storage': 'fixed'}", Type = TypeKind.UInt64 },
new { Json = @"{'type': 'float32', 'storage': 'fixed'}", Type = TypeKind.Float32 },
new { Json = @"{'type': 'float64', 'storage': 'fixed'}", Type = TypeKind.Float64 },
new { Json = @"{'type': 'float128', 'storage': 'fixed'}", Type = TypeKind.Float128 },
new { Json = @"{'type': 'decimal', 'storage': 'fixed'}", Type = TypeKind.Decimal },
new { Json = @"{'type': 'datetime', 'storage': 'fixed'}", Type = TypeKind.DateTime },
new { Json = @"{'type': 'unixdatetime', 'storage': 'fixed'}", Type = TypeKind.UnixDateTime },
new { Json = @"{'type': 'guid', 'storage': 'fixed'}", Type = TypeKind.Guid },
new { Json = @"{'type': 'mongodbobjectid', 'storage': 'fixed'}", Type = TypeKind.MongoDbObjectId },
new { Json = @"{'type': 'varint', 'storage': 'variable'}", Type = TypeKind.VarInt },
new { Json = @"{'type': 'varuint', 'storage': 'variable'}", Type = TypeKind.VarUInt },
new { Json = @"{'type': 'utf8', 'storage': 'fixed', 'length': 2}", Type = TypeKind.Utf8, Len = 2 },
new { Json = @"{'type': 'binary', 'storage': 'fixed', 'length': 2}", Type = TypeKind.Binary, Len = 2 },
new { Json = @"{'type': 'utf8', 'storage': 'variable', 'length': 100}", Type = TypeKind.Utf8, Len = 100 },
new { Json = @"{'type': 'binary', 'storage': 'variable', 'length': 100}", Type = TypeKind.Binary, Len = 100 },
new { Json = @"{'type': 'utf8', 'sparse': 'variable', 'length': 1000}", Type = TypeKind.Utf8, Len = 1000 },
new { Json = @"{'type': 'binary', 'sparse': 'variable', 'length': 1000}", Type = TypeKind.Binary, Len = 1000 },
};
foreach (dynamic expected in expectedSchemas)
{
string columnSchema = $"{{'path': 'a', 'type': {expected.Json}}}";
string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{columnSchema}]}}";
Schema s = Schema.Parse(tableSchema);
Assert.AreEqual(1, s.Properties.Count, "Json: {0}", expected.Json);
Property p = s.Properties[0];
Assert.AreEqual(expected.Type, p.PropertyType.Type, "Json: {0}", expected.Json);
PrimitivePropertyType sp = (PrimitivePropertyType)p.PropertyType;
switch (p.PropertyType.Type)
{
case TypeKind.Utf8:
case TypeKind.Binary:
switch (sp.Storage)
{
case StorageKind.Fixed:
case StorageKind.Variable:
case StorageKind.Sparse:
Assert.AreEqual(expected.Len, sp.Length, "Json: {0}", expected.Json);
break;
default:
Assert.Fail("Json: {0}", expected.Json);
break;
}
break;
}
}
}
[TestMethod]
[Owner("jthunter")]
public void ParseSchemaArray()
{
// Test array types include nested arrays.
dynamic[] expectedSchemas =
{
new { Json = @"{'type': 'int8' }", Type = TypeKind.Int8 },
new { Json = @"{'type': 'array', 'items': {'type': 'int32'}}", Type = TypeKind.Int32 },
new { Json = @"{'type': 'object', 'properties': null}", Len = 0 },
new { Json = @"{'type': 'schema', 'name': 'myUDT'}", Name = "myUDT" },
};
foreach (dynamic expected in expectedSchemas)
{
string arrayColumnSchema = $"{{'path': 'a', 'type': {{'type': 'array', 'items': {expected.Json} }} }}";
string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{arrayColumnSchema}] }}";
Schema s = Schema.Parse(tableSchema);
Assert.AreEqual(1, s.Properties.Count, "Json: {0}", expected.Json);
Property p = s.Properties[0];
Assert.AreEqual(TypeKind.Array, p.PropertyType.Type, "Json: {0}", expected.Json);
ArrayPropertyType pt = (ArrayPropertyType)p.PropertyType;
Assert.IsNotNull(pt.Items, "Json: {0}", expected.Json);
switch (pt.Items.Type)
{
case TypeKind.Array:
ArrayPropertyType subArray = (ArrayPropertyType)pt.Items;
Assert.AreEqual(expected.Type, subArray.Items.Type, "Json: {0}", expected.Json);
break;
case TypeKind.Object:
ObjectPropertyType subObj = (ObjectPropertyType)pt.Items;
Assert.AreEqual(expected.Len, subObj.Properties.Count, "Json: {0}", expected.Json);
break;
case TypeKind.Schema:
UdtPropertyType subRow = (UdtPropertyType)pt.Items;
Assert.AreEqual(expected.Name, subRow.Name, "Json: {0}", expected.Json);
break;
default:
Assert.AreEqual(expected.Type, pt.Items.Type, "Json: {0}", expected.Json);
break;
}
}
}
[TestMethod]
[Owner("jthunter")]
public void ParseSchemaObject()
{
// Test object types include nested objects.
dynamic[] expectedSchemas =
{
new { Json = @"{'path': 'b', 'type': {'type': 'int8', 'storage': 'fixed'}}", Type = TypeKind.Int8 },
new { Json = @"{'path': 'b', 'type': {'type': 'array', 'items': {'type': 'int32'}}}", Type = TypeKind.Int32 },
new { Json = @"{'path': 'b', 'type': {'type': 'object', 'properties': [{'path': 'c', 'type': {'type': 'bool'}}]}}", Len = 1 },
new { Json = @"{'path': 'b', 'type': {'type': 'schema', 'name': 'myUDT'}}", Name = "myUDT" },
};
foreach (dynamic expected in expectedSchemas)
{
string objectColumnSchema = $"{{'path': 'a', 'type': {{'type': 'object', 'properties': [{expected.Json}] }} }}";
string tableSchema = $"{{'name': 'table', 'id': -2, 'type': 'schema', 'properties': [{objectColumnSchema}] }}";
try
{
Schema s = Schema.Parse(tableSchema);
Assert.AreEqual(1, s.Properties.Count, "Json: {0}", expected.Json);
Property pa = s.Properties[0];
Assert.AreEqual(TypeKind.Object, pa.PropertyType.Type, "Json: {0}", expected.Json);
ObjectPropertyType pt = (ObjectPropertyType)pa.PropertyType;
Assert.AreEqual(1, pt.Properties.Count, "Json: {0}", expected.Json);
Property pb = pt.Properties[0];
switch (pb.PropertyType.Type)
{
case TypeKind.Array:
ArrayPropertyType subArray = (ArrayPropertyType)pb.PropertyType;
Assert.AreEqual(expected.Type, subArray.Items.Type, "Json: {0}", expected.Json);
break;
case TypeKind.Object:
ObjectPropertyType subObj = (ObjectPropertyType)pb.PropertyType;
Assert.AreEqual(expected.Len, subObj.Properties.Count, "Json: {0}", expected.Json);
break;
case TypeKind.Schema:
UdtPropertyType subRow = (UdtPropertyType)pb.PropertyType;
Assert.AreEqual(expected.Name, subRow.Name, "Json: {0}", expected.Json);
break;
default:
Assert.AreEqual(expected.Type, pb.PropertyType.Type, "Json: {0}", expected.Json);
break;
}
}
catch (Exception ex)
{
Assert.Fail("Exception: {0}, Json: {1}", ex, expected.Json);
}
}
}
[TestMethod]
[Owner("jthunter")]
public void ParseSchemaPartitionPrimaryKeys()
{
// Test parsing both partition and primary sort keys.
var expectedSchemas = new[]
{
new
{
JsonPK = @"{'path': 'a'}",
JsonCK = @"{'path': 'b', 'direction': 'desc'}, {'path': 'c'}",
PK = new[] { "a" },
CK = new[] { new { Path = "b", Dir = SortDirection.Descending }, new { Path = "c", Dir = SortDirection.Ascending } },
},
};
foreach (var expected in expectedSchemas)
{
string tableSchema = $@"{{
'name': 'table',
'id': -3,
'type': 'schema',
'properties': [
{{'path': 'a', 'type': {{'type': 'int8', 'storage': 'fixed'}}}},
{{'path': 'b', 'type': {{'type': 'utf8', 'storage': 'variable', 'length': 2}}}},
{{'path': 'c', 'type': {{'type': 'datetime', 'storage': 'fixed'}}}},
],
'partitionkeys': [{expected.JsonPK}],
'primarykeys': [{expected.JsonCK}]
}}";
try
{
Schema s = Schema.Parse(tableSchema);
Assert.AreEqual(3, s.Properties.Count, "PK: {0}, CK: {1}", expected.JsonPK, expected.JsonCK);
for (int i = 0; i < s.PartitionKeys.Count; i++)
{
Assert.AreEqual(expected.PK[i], s.PartitionKeys[i].Path, "PK: {0}, CK: {1}", expected.JsonPK, expected.JsonCK);
}
for (int i = 0; i < s.PrimarySortKeys.Count; i++)
{
Assert.AreEqual(expected.CK[i].Path, s.PrimarySortKeys[i].Path, "PK: {0}, CK: {1}", expected.JsonPK, expected.JsonCK);
Assert.AreEqual(expected.CK[i].Dir, s.PrimarySortKeys[i].Direction, "PK: {0}, CK: {1}", expected.JsonPK, expected.JsonCK);
}
}
catch (Exception ex)
{
Assert.Fail("Exception: {0}, PK: {1}, CK: {2}", ex, expected.JsonPK, expected.JsonCK);
}
}
}
[TestMethod]
[Owner("vahemesw")]
public void ParseSchemaStaticKeys()
{
var expectedSchemas = new[]
{
new
{
NumberOfPaths = 1,
JsonStaticKeys = @"{'path': 'c'}",
StaticKeys = new[] { new { Path = "c" } },
},
new
{
NumberOfPaths = 2,
JsonStaticKeys = @"{'path': 'c'}, {'path': 'd'}",
StaticKeys = new[] { new { Path = "c" }, new { Path = "d" } },
},
};
foreach (var expected in expectedSchemas)
{
string tableSchema = $@"{{
'name': 'table',
'id': -3,
'type': 'schema',
'properties': [
{{'path': 'a', 'type': {{'type': 'int8', 'storage': 'fixed'}}}},
{{'path': 'b', 'type': {{'type': 'utf8', 'storage': 'variable', 'length': 2}}}},
{{'path': 'c', 'type': {{'type': 'datetime', 'storage': 'fixed'}}}},
{{'path': 'd', 'type': {{'type': 'int8', 'storage': 'fixed'}}}},
],
'partitionkeys': [{{'path': 'a'}}],
'primarykeys': [{{'path': 'b', 'direction': 'desc'}}],
'statickeys': [{expected.JsonStaticKeys}]
}}";
try
{
Schema s = Schema.Parse(tableSchema);
Assert.AreEqual(expected.NumberOfPaths, s.StaticKeys.Count);
for (int i = 0; i < s.StaticKeys.Count; i++)
{
Assert.AreEqual(expected.StaticKeys[i].Path, s.StaticKeys[i].Path);
}
}
catch (Exception ex)
{
Assert.Fail("Exception: {0}, Caught exception when deserializing the schema", ex);
}
}
}
[TestMethod]
[Owner("jthunter")]
public void ParseSchemaApiType()
{
// Test api type specifications include elements of complex types.
var expectedSchemas = new[]
{
new { Json = @"{'type': 'int64', 'apitype': 'counter'}", ApiType = "counter" },
new { Json = @"{'type': 'array', 'items': {'type': 'int64', 'apitype': 'timestamp'}}", ApiType = "timestamp" },
};
foreach (var expected in expectedSchemas)
{
string columnSchema = $"{{'path': 'a', 'type': {expected.Json}}}";
string tableSchema = $"{{'name': 'table', 'id': -4, 'type': 'schema', 'properties': [{columnSchema}]}}";
Schema s = Schema.Parse(tableSchema);
Assert.AreEqual(1, s.Properties.Count, "Json: {0}", expected.Json);
Property p = s.Properties[0];
switch (p.PropertyType.Type)
{
case TypeKind.Array:
ArrayPropertyType subArray = (ArrayPropertyType)p.PropertyType;
Assert.AreEqual(expected.ApiType, subArray.Items.ApiType, "Json: {0}", expected.Json);
break;
default:
Assert.AreEqual(expected.ApiType, p.PropertyType.ApiType, "Json: {0}", expected.Json);
break;
}
}
}
[TestMethod]
[Owner("jthunter")]
public void SchemaRef()
{
NamespaceParserTest[] tests = new NamespaceParserTest[]
{
new NamespaceParserTest
{
Name = "SchemaNameOnlyRef",
Json = @"{'schemas': [
{ 'name': 'A', 'id': 1, 'type': 'schema'},
{ 'name': 'B', 'id': 2, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'schema', 'name': 'A'}}
]}
]}",
},
new NamespaceParserTest
{
Name = "SchemaNameAndIdRef",
Json = @"{'schemas': [
{ 'name': 'A', 'id': 1, 'type': 'schema'},
{ 'name': 'B', 'id': 2, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'schema', 'name': 'A', 'id': 1}}
]}
]}",
},
new NamespaceParserTest
{
Name = "SchemaMultipleVersionNameAndIdRef",
Json = @"{'schemas': [
{ 'name': 'A', 'id': 1, 'type': 'schema'},
{ 'name': 'B', 'id': 2, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'schema', 'name': 'A', 'id': 3}}
]},
{ 'name': 'A', 'id': 3, 'type': 'schema'}
]}",
},
};
foreach (NamespaceParserTest t in tests)
{
Console.WriteLine(t.Name);
Namespace.Parse(t.Json);
}
}
[TestMethod]
[Owner("jthunter")]
public void NegativeNamespaceParser()
{
NamespaceParserTest[] tests = new NamespaceParserTest[]
{
new NamespaceParserTest
{
Name = "InvalidId",
Json = @"{'schemas': [{ 'name': 'A', 'id': 0, 'type': 'schema'}]}",
},
new NamespaceParserTest
{
Name = "InvalidNameEmpty",
Json = @"{'schemas': [{ 'name': '', 'id': 1, 'type': 'schema'}]}",
},
new NamespaceParserTest
{
Name = "InvalidNameWhitespace",
Json = @"{'schemas': [{ 'name': ' ', 'id': 1, 'type': 'schema'}]}",
},
new NamespaceParserTest
{
Name = "DuplicateId",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema'}, { 'name': 'B', 'id': 1, 'type': 'schema'} ]}",
},
new NamespaceParserTest
{
Name = "DuplicatePropertyName",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'bool'}},
{'path': 'b', 'type': {'type': 'int8'}},
]}]}",
},
new NamespaceParserTest
{
Name = "MissingPK",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'partitionkeys': [{'path': 'b'}]}]}",
},
new NamespaceParserTest
{
Name = "MissingPS",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'primarykeys': [{'path': 'b'}]}]}",
},
new NamespaceParserTest
{
Name = "MissingStaticKey",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'statickeys': [{'path': 'b'}]}]}",
},
new NamespaceParserTest
{
Name = "InvalidPropertyName",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'properties': [{'path': '', 'type': {'type': 'bool'}}]}]}",
},
new NamespaceParserTest
{
Name = "InvalidLength",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'utf8', 'length': -1}}
]}]}",
},
new NamespaceParserTest
{
Name = "InvalidStorage",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'array', 'items': {'type': 'utf8', 'storage': 'fixed'}}}
]}]}",
},
new NamespaceParserTest
{
Name = "DuplicateObjectProperties",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'object', 'properties': [
{'path': 'c', 'type': {'type': 'bool'}},
{'path': 'c', 'type': {'type': 'int8'}}
]}}
]}]}",
},
new NamespaceParserTest
{
Name = "MissingUDTName",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'schema', 'name': 'B'}}
]}]}",
},
new NamespaceParserTest
{
Name = "MissingUDTId",
Json = @"{'schemas': [{ 'name': 'A', 'id': 1, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'schema', 'name': 'A', 'id': 3}}
]}]}",
},
new NamespaceParserTest
{
Name = "MismatchedSchemaRef",
Json = @"{'schemas': [
{ 'name': 'A', 'id': 1, 'type': 'schema'},
{ 'name': 'B', 'id': 2, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'schema', 'name': 'A', 'id': 2}}
]}
]}",
},
new NamespaceParserTest
{
Name = "AmbiguousSchemaRef",
Json = @"{'schemas': [
{ 'name': 'A', 'id': 1, 'type': 'schema'},
{ 'name': 'B', 'id': 2, 'type': 'schema', 'properties': [
{'path': 'b', 'type': {'type': 'schema', 'name': 'A'}}
]},
{ 'name': 'A', 'id': 3, 'type': 'schema'}
]}",
},
};
foreach (NamespaceParserTest t in tests)
{
Console.WriteLine(t.Name);
AssertThrowsException.ThrowsException<SchemaException>(() => Namespace.Parse(t.Json));
}
}
private struct NamespaceParserTest
{
public string Name { get; set; }
public string Json { get; set; }
}
}
}

View File

@@ -0,0 +1,317 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Fields should be private
#pragma warning disable SA1201 // Elements should appear in the correct order
#pragma warning disable SA1204 // Elements should appear in the correct order
#pragma warning disable CA1034 // Nested types should not be visible
#pragma warning disable CA1051 // Do not declare visible instance fields
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[DeploymentItem(SerializerUnitTest.SchemaFile, "TestData")]
public sealed class SerializerUnitTest
{
private const string SchemaFile = @"TestData\BatchApiSchema.json";
private const int InitialRowSize = 2 * 1024 * 1024;
private Namespace schema;
private LayoutResolver resolver;
private Layout layout;
[TestInitialize]
public void InitTestSuite()
{
string json = File.ReadAllText(SerializerUnitTest.SchemaFile);
this.schema = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.schema);
this.layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "BatchRequest").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void CreateBatchRequest()
{
BatchRequest request = new BatchRequest()
{
Operations = new List<BatchOperation>()
{
new BatchOperation()
{
OperationType = 3,
Headers = new BatchRequestHeaders()
{
SampleRequestHeader = 12345L,
},
ResourceType = 1,
ResourcePath = "/some/url/path",
ResourceBody = new byte[] { 1, 2, 3 },
},
new BatchOperation()
{
OperationType = 2,
Headers = new BatchRequestHeaders()
{
SampleRequestHeader = 98746L,
},
ResourceType = 2,
ResourcePath = "/some/other/url/path",
ResourceBody = new byte[] { 3, 2, 1 },
},
},
};
// Write the request by serializing it to a row.
RowBuffer row = new RowBuffer(SerializerUnitTest.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
Result r = RowWriter.WriteBuffer(ref row, request, BatchRequestSerializer.Write);
Assert.AreEqual(Result.Success, r);
Console.WriteLine("Length of serialized row: {0}", row.Length);
// Read the row back again.
RowReader reader = new RowReader(ref row);
r = BatchRequestSerializer.Read(ref reader, out BatchRequest _);
Assert.AreEqual(Result.Success, r);
// Dump the materialized request to the console.
reader = new RowReader(ref row);
r = DiagnosticConverter.ReaderToString(ref reader, out string dumpStr);
Assert.AreEqual(Result.Success, r);
Console.WriteLine(dumpStr);
}
public sealed class BatchRequestHeaders
{
public long SampleRequestHeader;
}
public sealed class BatchOperation
{
public int OperationType;
public BatchRequestHeaders Headers;
public int ResourceType;
public string ResourcePath;
public byte[] ResourceBody;
}
public sealed class BatchRequest
{
public List<BatchOperation> Operations;
}
public sealed class BatchResponseHeaders
{
public string SampleResponseHeader;
}
public sealed class BatchOperationResponse
{
public int StatusCode;
public BatchResponseHeaders Headers;
public byte[] ResourceBody;
}
public sealed class BatchResponse
{
public List<BatchOperationResponse> Operations;
}
public static class BatchRequestHeadersSerializer
{
public static readonly TypeArgument TypeArg = new TypeArgument(LayoutType.UDT, new TypeArgumentList(new SchemaId(1)));
public static Result Write(ref RowWriter writer, TypeArgument typeArg, BatchRequestHeaders header)
{
Result r = writer.WriteInt64("sampleRequestHeader", header.SampleRequestHeader);
if (r != Result.Success)
{
return r;
}
return Result.Success;
}
public static Result Read(ref RowReader reader, out BatchRequestHeaders header)
{
BatchRequestHeaders retval = new BatchRequestHeaders();
header = default;
while (reader.Read())
{
switch (reader.Path)
{
case "sampleRequestHeader":
Result r = reader.ReadInt64(out retval.SampleRequestHeader);
if (r != Result.Success)
{
return r;
}
break;
}
}
header = retval;
return Result.Success;
}
}
public static class BatchOperationSerializer
{
public static readonly TypeArgument TypeArg = new TypeArgument(LayoutType.UDT, new TypeArgumentList(new SchemaId(2)));
public static Result Write(ref RowWriter writer, TypeArgument typeArg, BatchOperation operation)
{
Result r = writer.WriteInt32("operationType", operation.OperationType);
if (r != Result.Success)
{
return r;
}
r = writer.WriteScope("headers", BatchRequestHeadersSerializer.TypeArg, operation.Headers, BatchRequestHeadersSerializer.Write);
if (r != Result.Success)
{
return r;
}
r = writer.WriteInt32("resourceType", operation.ResourceType);
if (r != Result.Success)
{
return r;
}
r = writer.WriteString("resourcePath", operation.ResourcePath);
if (r != Result.Success)
{
return r;
}
r = writer.WriteBinary("resourceBody", operation.ResourceBody);
if (r != Result.Success)
{
return r;
}
return Result.Success;
}
public static Result Read(ref RowReader reader, out BatchOperation operation)
{
BatchOperation retval = new BatchOperation();
operation = default;
while (reader.Read())
{
Result r;
switch (reader.Path)
{
case "operationType":
r = reader.ReadInt32(out retval.OperationType);
if (r != Result.Success)
{
return r;
}
break;
case "headers":
r = reader.ReadScope(
retval,
(ref RowReader child, BatchOperation parent) =>
BatchRequestHeadersSerializer.Read(ref child, out parent.Headers));
if (r != Result.Success)
{
return r;
}
break;
case "resourceType":
r = reader.ReadInt32(out retval.ResourceType);
if (r != Result.Success)
{
return r;
}
break;
case "resourcePath":
r = reader.ReadString(out retval.ResourcePath);
if (r != Result.Success)
{
return r;
}
break;
case "resourceBody":
r = reader.ReadBinary(out retval.ResourceBody);
if (r != Result.Success)
{
return r;
}
break;
}
}
operation = retval;
return Result.Success;
}
}
public static class BatchRequestSerializer
{
public static readonly TypeArgument OperationsTypeArg = new TypeArgument(
LayoutType.TypedArray,
new TypeArgumentList(new[] { BatchOperationSerializer.TypeArg }));
public static Result Write(ref RowWriter writer, TypeArgument typeArg, BatchRequest request)
{
return writer.WriteScope(
"operations",
BatchRequestSerializer.OperationsTypeArg,
request.Operations,
(ref RowWriter writer2, TypeArgument typeArg2, List<BatchOperation> operations) =>
{
foreach (BatchOperation operation in operations)
{
Result r = writer2.WriteScope(null, BatchOperationSerializer.TypeArg, operation, BatchOperationSerializer.Write);
if (r != Result.Success)
{
return r;
}
}
return Result.Success;
});
}
public static Result Read(ref RowReader reader, out BatchRequest request)
{
Assert.IsTrue(reader.Read());
Contract.Assert(reader.Path == "operations");
Result r = reader.ReadList(BatchOperationSerializer.Read, out List<BatchOperation> operations);
if (r != Result.Success)
{
request = default;
return r;
}
request = new BatchRequest()
{
Operations = operations,
};
return Result.Success;
}
}
}
}

View File

@@ -0,0 +1,145 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[DeploymentItem(TaggedUnitTests.SchemaFile, "TestData")]
public sealed class TaggedUnitTests
{
private const string SchemaFile = @"TestData\TaggedApiSchema.json";
private const int InitialRowSize = 2 * 1024 * 1024;
private Namespace schema;
private LayoutResolver resolver;
private Layout layout;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(TaggedUnitTests.SchemaFile);
this.schema = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.schema);
this.layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "TaggedApi").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void CreateTaggedApi()
{
RowBuffer row = new RowBuffer(TaggedUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
TaggedApi c1 = new TaggedApi()
{
Tag1 = ((byte)1, "hello"),
Tag2 = ((byte)2, 28, 1974L),
};
this.WriteTaggedApi(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1);
TaggedApi c2 = this.ReadTaggedApi(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(c1, c2);
}
private void WriteTaggedApi(ref RowBuffer row, ref RowCursor root, TaggedApi pc)
{
Assert.IsTrue(this.layout.TryFind("tag1", out LayoutColumn c));
root.Clone(out RowCursor tag1Scope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref tag1Scope, c.TypeArgs, out tag1Scope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUInt8>().WriteSparse(ref row, ref tag1Scope, pc.Tag1.Item1));
Assert.IsTrue(tag1Scope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tag1Scope, pc.Tag1.Item2));
Assert.IsTrue(this.layout.TryFind("tag2", out c));
root.Clone(out RowCursor tag2Scope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref tag2Scope, c.TypeArgs, out tag2Scope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUInt8>().WriteSparse(ref row, ref tag2Scope, pc.Tag2.Item1));
Assert.IsTrue(tag2Scope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs<LayoutInt32>().WriteSparse(ref row, ref tag2Scope, pc.Tag2.Item2));
Assert.IsTrue(tag2Scope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[2].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref tag2Scope, pc.Tag2.Item3));
}
private TaggedApi ReadTaggedApi(ref RowBuffer row, ref RowCursor root)
{
TaggedApi pc = new TaggedApi();
Assert.IsTrue(this.layout.TryFind("tag1", out LayoutColumn c));
Assert.IsTrue(c.Type.Immutable);
root.Clone(out RowCursor tag1Scope).Find(ref row, c.Path);
if (c.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref tag1Scope, out tag1Scope) == Result.Success)
{
Assert.IsTrue(tag1Scope.Immutable);
Assert.IsTrue(tag1Scope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUInt8>().ReadSparse(ref row, ref tag1Scope, out byte apiCode));
Assert.IsTrue(tag1Scope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref tag1Scope, out string str));
pc.Tag1 = (apiCode, str);
}
Assert.IsTrue(this.layout.TryFind("tag2", out c));
Assert.IsFalse(c.Type.Immutable);
root.Clone(out RowCursor tag2Scope).Find(ref row, c.Path);
if (c.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref tag2Scope, out tag2Scope) == Result.Success)
{
Assert.IsFalse(tag2Scope.Immutable);
Assert.IsTrue(tag2Scope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUInt8>().ReadSparse(ref row, ref tag2Scope, out byte apiCode));
Assert.IsTrue(tag2Scope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs<LayoutInt32>().ReadSparse(ref row, ref tag2Scope, out int val1));
Assert.IsTrue(tag2Scope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[2].Type.TypeAs<LayoutInt64>().ReadSparse(ref row, ref tag2Scope, out long val2));
pc.Tag2 = (apiCode, val1, val2);
}
return pc;
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class TaggedApi
{
public (byte, string) Tag1;
public (byte, int, long) Tag2;
// ReSharper disable once MemberCanBePrivate.Local
public bool Equals(TaggedApi other)
{
return object.Equals(this.Tag1, other.Tag1) &&
object.Equals(this.Tag2, other.Tag2);
}
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is TaggedApi taggedApi && this.Equals(taggedApi);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = 0;
hashCode = (hashCode * 397) ^ this.Tag1.GetHashCode();
hashCode = (hashCode * 397) ^ this.Tag2.GetHashCode();
return hashCode;
}
}
}
}
}

View File

@@ -0,0 +1,96 @@
{
"name": "Microsoft.Azure.Cosmos.BatchApi",
"version": "v1",
"schemas": [
{
"name": "BatchRequestHeaders",
"id": 1,
"type": "schema",
"properties":
[
{
"path": "sampleRequestHeader",
"type": { "type": "int64", "storage": "fixed" }
}
]
},
{
"name": "BatchOperation",
"id": 2,
"type": "schema",
"properties":
[
{
"path": "operationType",
"type": { "type": "int32", "storage": "fixed" }
}, {
"path": "headers",
"type": { "type": "schema", "name": "BatchRequestHeaders" }
}, {
"path": "resourceType",
"type": { "type": "int32", "storage": "fixed" }
}, {
"path": "resourcePath",
"type": { "type": "utf8", "storage": "variable", "length": 1024 }
}, {
"path": "resourceBody",
"type": { "type": "binary", "storage": "variable" }
}
]
},
{
"name": "BatchRequest",
"id": 3,
"type": "schema",
"properties":
[
{
"path": "operations",
"type": { "type": "array", "items": { "type": "schema", "name": "BatchOperation" } }
}
]
},
{
"name": "BatchResponseHeaders",
"id": 4,
"type": "schema",
"properties":
[
{
"path": "sampleResponseHeader",
"type": { "type": "utf8", "storage": "variable", "length": 1024 }
}
]
},
{
"name": "BatchOperationResponse",
"id": 5,
"type": "schema",
"properties":
[
{
"path": "statusCode",
"type": { "type": "int32", "storage": "fixed" }
}, {
"path": "headers",
"type": { "type": "schema", "name": "BatchResponseHeaders" }
}, {
"path": "resourceBody",
"type": { "type": "binary", "storage": "variable" }
}
]
},
{
"name": "BatchResponse",
"id": 6,
"type": "schema",
"properties":
[
{
"path": "operations",
"type": { "type": "array", "items": { "type": "schema", "name": "BatchOperationResponse" } }
}
]
}
]
}

View File

@@ -0,0 +1,211 @@
{
"schemas": [
{
"name": "myUDT", // Question: what should the namespace/structure of schema identifiers.
"id": 1,
"type": "schema", // Optional: implied at the top-level (only "schema" types can be defined at the root of schemas)
"options": {
"disallowUnschematized": false // Optional: defaults to false
},
"properties": [
{
"path": "a",
"type": {
"type": "int8",
"storage": "fixed"
}
},
{
"path": "b",
"type": {
"type": "utf8",
"storage": "variable"
}
}
]
},
{
"name": "someTable",
"id": -1,
"options": {
"disallowUnschematized": true
},
"properties": [
{
"path": "myBool",
"comment": "A sample fixed boolean column",
"type": {
"type": "bool",
"storage": "fixed"
}
},
{
"path": "myInt8",
"comment": "A sample fixed 8-byte integer column",
"type": {
"type": "int8",
"storage": "fixed"
}
},
{
"path": "nested.x",
"comment": "A sample nested integer column",
"type": {
"type": "int32",
"storage": "fixed"
}
},
{
"path": "nested.y",
"comment": "A sample nested float column",
"type": {
"type": "float32",
"storage": "fixed"
}
},
{
"path": "nested.deeper.z",
"comment": "A sample deeper nested double column",
"type": {
"type": "float64",
"storage": "fixed"
}
},
{
"path": "State",
"comment": "A sample fixed 2-byte UTF-8 encoded text column",
"type": {
"type": "utf8",
"storage": "fixed",
"length": 2
}
},
{
"path": "myString",
"comment": "A sample variable length UTF-8 encoded text column (up to 127 bytes)",
"type": {
"type": "utf8",
"storage": "variable"
}
},
{
"path": "lob",
"comment": "A sample extended UTF-8 encoded text column (up to 2M bytes)",
"type": {
"type": "utf8",
"storage": "sparse"
}
},
{
"path": "canbelob",
"comment":
"A sample extended UTF-8 encoded text column (up to 2M bytes) that stores variable if 'small' (<127 bytes), but sparse if 'large'",
"type": {
"type": "utf8",
"storage": "variable"
}
},
{
"path": "primitiveArray",
"comment": "A sample array of primitives (4-byte ints)",
"type": { "type": "array", "items": { "type": "int32" } }
},
{
"path": "shreddedArray[0]",
"comment": "A sample fixed-length array of primitives",
"type": { "type": "int32" }
},
{
"path": "shreddedArray[1]",
"comment": "A sample fixed-length array of primitives",
"type": { "type": "int32" }
},
{
"path": "nestedArray",
"comment": "A sample array of nested arrays",
"type": {
"type": "array",
"items": {
"type": "array",
"items": { "type": "int32" }
}
}
},
{
"path": "nestedNestedArray",
"comment": "A sample array of nested nested arrays",
"type": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "array",
"items": { "type": "int32" }
}
}
}
},
{
"path": "arrayOfObject",
"comment": "A sample array of semi-structured objects",
"type": {
"type": "array",
"items": {
"type": "object",
"properties": [
{
"path": "a",
"type": { "type": "int8" }
},
{
"path": "b",
"type": { "type": "utf8" }
}
]
}
}
},
{
"path": "arrayOfAny",
"comment": "A sample heterogenous array",
"type": {
"type": "array",
"items": { "type": "any" }
}
},
{
"path": "arrayOfUDT",
"comment": "A sample array of schematized rows",
"type": {
"type": "array",
"items": {
"type": "schema",
"name": "myUDT" // see definition above - should this be called $ref or ref or something?
}
}
},
{
"path": "nestedObject",
"comment": "A sample nested objects",
"type": {
"type": "object",
"properties": [
{
"path": "a",
"type": {
"type": "int8"
}
},
{
"path": "b",
"type": {
"type": "utf8"
}
}
]
}
}
]
}
]
}

View File

@@ -0,0 +1,8 @@
{
"CrossVersionFixed": "8101000000FFFF1FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA3E555555555555D53F2A00000000000000000000000000000000001C00CA44C50A55555505CB00B714006E39578A01D6082A00000000000000B9259C2A2E921146BB0A244A9496503C2A0000000000000000000000616263000102",
"CrossVersionNullFixed": "810100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"CrossVersionVariable": "81020000000FADD5AAD5AAD5AAD5AA01AAD5AAD5AAD5AAD5AA010361626303000102",
"CrossVersionNullVariable": "810200000000",
"CrossVersionSparse": "8103000000010103020503AA0604AAAA0705AAAAAAAA0806AAAAAAAAAAAAAAAA0907AA0A08AAAA0B09AAAAAAAA0C0AAAAAAAAAAAAAAAAA0F0BABAAAA3E100C555555555555D53F160D2A000000000000000000000000000000110E00001C00CA44C50A55555505CB00B714120F006E39578A01D60817102A000000000000001311B9259C2A2E921146BB0A244A9496503C18122A000000000000000000000014130361626315140300010222051503000000AAAAAA22220F1602000000030000000000803F0000004000004040030000000000803F00000040000040402214170300000003616263036465660368696A26020D0818ADD5AAD5AAD5AAD5AA01AAAAAAAAAAAAAAAA260201260205051901AAAA26020344040000001A02030100000002000000462E141B03000000036162630365666703787A792E22051C030000000300000001020303000000040506030000000708092E2E071D030000000300000001000000020000000300000003000000040000000500000006000000030000000700000008000000090000002E44040000001E030000000301000000020000004603030000000400000046030500000006000000462A14141F02000000044D61726B044C756B65084861727269736F6E0348616E2A0522052002000000010300000001020302030000000405062A062A0707210200000001000200000001000000020000000300000004000000020002000000050000000600000007000000080000002A1044040000002203000000000000000000004003030000000400000046000000000000084003050000000600000046000000000000F03F03010000000200000046",
"CrossVersionNullSparse": "8103000000"
}

View File

@@ -0,0 +1,127 @@
// Set of types used in the cross versioning tests.
{
"schemas": [
{ "name": "Fixed", "id": 1, "type": "schema",
"properties": [
{ "path": "null", "type": { "type": "null", "storage": "fixed" } },
{ "path": "bool", "type": { "type": "bool", "storage": "fixed" } },
{ "path": "int8", "type": { "type": "int8", "storage": "fixed" } },
{ "path": "int16", "type": { "type": "int16", "storage": "fixed" } },
{ "path": "int32", "type": { "type": "int32", "storage": "fixed" } },
{ "path": "int64", "type": { "type": "int64", "storage": "fixed" } },
{ "path": "uint8", "type": { "type": "uint8", "storage": "fixed" } },
{ "path": "uint16", "type": { "type": "uint16", "storage": "fixed" } },
{ "path": "uint32", "type": { "type": "uint32", "storage": "fixed" } },
{ "path": "uint64", "type": { "type": "uint64", "storage": "fixed" } },
{ "path": "float32", "type": { "type": "float32", "storage": "fixed" } },
{ "path": "float64", "type": { "type": "float64", "storage": "fixed" } },
{ "path": "float128", "type": { "type": "float128", "storage": "fixed" } },
{ "path": "decimal", "type": { "type": "decimal", "storage": "fixed" } },
{ "path": "datetime", "type": { "type": "datetime", "storage": "fixed" } },
{ "path": "unixdatetime", "type": { "type": "unixdatetime", "storage": "fixed" } },
{ "path": "guid", "type": { "type": "guid", "storage": "fixed" } },
{ "path": "mongodbobjectid", "type": { "type": "mongodbobjectid", "storage": "fixed" } },
{ "path": "utf8", "type": { "type": "utf8", "storage": "fixed", "length": 3 } },
{ "path": "binary", "type": { "type": "binary", "storage": "fixed", "length": 3 } }
]},
{ "name": "Variable", "id": 2, "type": "schema",
"properties": [
{ "path": "varint", "type": { "type": "varint", "storage": "variable" } },
{ "path": "varuint", "type": { "type": "varuint", "storage": "variable" } },
{ "path": "utf8", "type": { "type": "utf8", "storage": "variable"} },
{ "path": "binary", "type": { "type": "binary", "storage": "variable" } }
]},
{ "name": "Sparse", "id": 3, "type": "schema",
"properties": [
{ "path": "null", "type": { "type": "null" } },
{ "path": "bool", "type": { "type": "bool" } },
{ "path": "int8", "type": { "type": "int8" } },
{ "path": "int16", "type": { "type": "int16" } },
{ "path": "int32", "type": { "type": "int32" } },
{ "path": "int64", "type": { "type": "int64" } },
{ "path": "uint8", "type": { "type": "uint8" } },
{ "path": "uint16", "type": { "type": "uint16" } },
{ "path": "uint32", "type": { "type": "uint32" } },
{ "path": "uint64", "type": { "type": "uint64" } },
{ "path": "float32", "type": { "type": "float32" } },
{ "path": "float64", "type": { "type": "float64" } },
{ "path": "float128", "type": { "type": "float128" } },
{ "path": "decimal", "type": { "type": "decimal" } },
{ "path": "datetime", "type": { "type": "datetime" } },
{ "path": "unixdatetime", "type": { "type": "unixdatetime" } },
{ "path": "guid", "type": { "type": "guid" } },
{ "path": "mongodbobjectid", "type": { "type": "mongodbobjectid" } },
{ "path": "utf8", "type": { "type": "utf8" } },
{ "path": "binary", "type": { "type": "binary" } },
{ "path": "array_t<int8>", "type": {
"type": "array",
"items": { "type": "int8", "nullable": false }
} },
{ "path": "array_t<array_t<float32>>", "type": {
"type": "array",
"items": { "type": "array", "nullable": false, "items": { "type": "float32", "nullable": false } }
} },
{ "path": "array_t<utf8>", "type": { "type": "array", "items": { "type": "utf8", "nullable": false } } },
{ "path": "tuple<varint,int64>", "type": {
"type": "tuple",
"items": [ { "type": "varint", "nullable": false }, { "type": "int64", "nullable": false }]
} },
{ "path": "tuple<null,tuple<int8,int8>>", "type": {
"type": "tuple", "items": [
{ "type": "null", "nullable": false },
{ "type": "tuple", "nullable": false, "items": [ { "type": "int8", "nullable": false }, { "type": "int8", "nullable": false } ] }
]}},
{ "path": "tuple<bool,udt>", "type": {
"type": "tuple", "items": [
{ "type": "bool", "nullable": false },
{ "type": "schema", "name": "Point", "nullable": false}
]}},
{ "path": "set_t<utf8>", "type": {
"type": "set",
"items": { "type": "utf8", "nullable": false }
} },
{ "path": "set_t<array_t<int8>>", "type": {
"type": "set",
"items": { "type": "array", "nullable": false, "items": { "type": "int8", "nullable": false } }
} },
{ "path": "set_t<set_t<int32>>", "type": {
"type": "set",
"items": { "type": "set", "nullable": false, "items": { "type": "int32", "nullable": false } }
} },
{ "path": "set_t<udt>", "type": {
"type": "set",
"items": { "type": "schema", "name": "Point", "nullable": false}
} },
{ "path": "map_t<utf8,utf8>", "type": {
"type": "map",
"keys": { "type": "utf8", "nullable": false },
"values": { "type": "utf8", "nullable": false }
} },
{ "path": "map_t<int8,array_t<int8>>", "type": {
"type": "map",
"keys": { "type": "int8", "nullable": false },
"values": { "type": "array", "nullable": false, "items": { "type": "int8", "nullable": false } }
} },
{ "path": "map_t<int16,map_t<int32,int32>>", "type": {
"type": "map",
"keys": { "type": "int16", "nullable": false },
"values": {
"type": "map",
"nullable": false,
"keys": { "type": "int32", "nullable": false },
"values": { "type": "int32", "nullable": false }
}
} },
{ "path": "map_t<float64,udt>", "type": {
"type": "map",
"keys": { "type": "float64", "nullable": false },
"values": { "type": "schema", "name": "Point", "nullable": false}
} }
]},
{ "name": "Point", "id": 4, "type": "schema",
"properties": [
{ "path": "x", "type": { "type": "int32", "storage": "fixed" } },
{ "path": "y", "type": { "type": "int32", "storage": "fixed" } }
]}
]
}

View File

@@ -0,0 +1,76 @@
// Partial implementation of Cassandra Hotel Schema described here::
// https://www.oreilly.com/ideas/cassandra-data-modeling
{
"name": "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema",
"schemas": [
{
"name": "PostalCode",
"id": 1,
"type": "schema",
"properties": [
{ "path": "zip", "type": { "type": "int32", "storage": "fixed" } },
{ "path": "plus4", "type": { "type": "int16", "storage": "sparse" } }
]
},
{
"name": "Address",
"id": 2,
"type": "schema",
"properties": [
{ "path": "street", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "city", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "state", "type": { "type": "utf8", "storage": "fixed", "length": 2 } },
{ "path": "postal_code", "type": { "type": "schema", "name": "PostalCode" } }
]
},
{
"name": "Hotels",
"id": 3,
"type": "schema",
"partitionkeys": [{ "path": "hotel_id" }],
"properties": [
{ "path": "hotel_id", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "name", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "phone", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "address", "type": { "type": "schema", "name": "Address", "immutable": true } }
]
},
{
"name": "Available_Rooms_By_Hotel_Date",
"id": 4,
"type": "schema",
"partitionkeys": [{ "path": "hotel_id" }],
"primarykeys": [{ "path": "date" }, { "path": "room_number", "direction": "desc" }],
"properties": [
{ "path": "hotel_id", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "date", "type": { "type": "datetime", "storage": "fixed" } },
{ "path": "room_number", "type": { "type": "uint8", "storage": "fixed" } },
{ "path": "is_available", "type": { "type": "bool" } }
]
},
{
"name": "Guests",
"id": 5,
"type": "schema",
"partitionkeys": [{ "path": "guest_id" }],
"primarykeys": [{ "path": "first_name" }, { "path": "phone_numbers", "direction": "desc" }],
"properties": [
{ "path": "guest_id", "type": { "type": "guid", "storage": "fixed" } },
{ "path": "first_name", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "last_name", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "title", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "emails", "type": { "type": "array", "items": { "type": "utf8", "nullable": false } } },
{ "path": "phone_numbers", "type": { "type": "array", "items": { "type": "utf8", "nullable": false } } },
{
"path": "addresses",
"type": {
"type": "map",
"keys": { "type": "utf8", "nullable": false },
"values": { "type": "schema", "name": "Address", "immutable": true, "nullable": false }
}
},
{ "path": "confirm_number", "type": { "type": "utf8", "storage": "variable" } }
]
}
]
}

View File

@@ -0,0 +1,40 @@
// Todo demo schema that utilizes typed maps.
{
"schemas": [
{ "name": "Movie", "id": 1, "type": "schema",
"properties": [
{ "path": "cast", "type": {
"type": "map",
"keys": { "type": "utf8", "nullable": false },
"values": { "type": "utf8", "nullable": false }
} },
{ "path": "stats", "type": {
"type": "map",
"keys": { "type": "guid", "nullable": false },
"values": { "type": "float64", "nullable": false }
} },
{ "path": "related", "type": {
"comment": "map: actor -> { map: moveId -> roleName }",
"type": "map",
"keys": { "type": "utf8", "nullable": false },
"values": {
"type": "map",
"nullable": false,
"keys": { "type": "int64", "nullable": false },
"values": { "type": "utf8", "nullable": false }
}
} },
{ "path": "revenue", "type": {
"comment": "map: releaseDate -> Earnings }",
"type": "map",
"keys": { "type": "datetime", "nullable": false },
"values": { "type": "schema", "name": "Earnings", "nullable": false }
} }
]},
{ "name": "Earnings", "id": 2, "type": "schema",
"properties": [
{ "path": "domestic", "type": { "type": "decimal", "storage": "fixed" } },
{ "path": "worldwide", "type": { "type": "decimal", "storage": "fixed" } }
]}
]
}

View File

@@ -0,0 +1,26 @@
// Demo schema that utilizes nullable typed scopes.
{
"schemas": [
{
"name": "Nullables",
"id": 1,
"type": "schema",
"properties": [
{ "path": "nullbool", "type": { "type": "array", "items": { "type": "bool" } } },
{ "path": "nullset", "type": { "type": "set", "items": { "type": "utf8" } } },
{ "path": "nullarray", "type": { "type": "array", "items": { "type": "float32" } } },
{
"path": "nulltuple",
"type": {
"type": "array",
"items": { "type": "tuple", "nullable": false, "items": [{ "type": "int32" }, { "type": "int64" }] }
}
},
{
"path": "nullmap",
"type": { "type": "map", "keys": { "type": "guid" }, "values": { "type": "uint8" } }
}
]
}
]
}

View File

@@ -0,0 +1,72 @@
// Performance Counter demo schema that utilizes tuples.
{
"schemas": [
{
"name": "Coord",
"id": 2,
"type": "schema",
"properties": [
{ "path": "lat", "type": { "type": "int64", "storage": "fixed" } },
{ "path": "lng", "type": { "type": "int64", "storage": "fixed" } }
]
},
{
"name": "Counters",
"id": 1,
"type": "schema",
"partitionkeys": [{ "path": "name" }],
"properties": [
{ "path": "name", "type": { "type": "utf8", "storage": "variable" } },
{
"path": "value",
"type": {
"type": "tuple",
"immutable": true,
"items": [{ "type": "utf8", "nullable": false }, { "type": "int64", "nullable": false }]
}
},
{
"path": "minmeanmax",
"type": {
"type": "tuple",
"immutable": true,
"items": [
{ "type": "utf8", "nullable": false },
{
"type": "tuple",
"nullable": false,
"items": [
{ "type": "int64", "nullable": false },
{ "type": "int64", "nullable": false },
{ "type": "int64", "nullable": false }
]
}
]
}
},
{
"path": "coord",
"type": {
"type": "tuple",
"immutable": true,
"items": [{ "type": "utf8", "nullable": false }, { "type": "schema", "name": "Coord", "nullable": false }]
}
}
]
},
{
"name": "CounterSet",
"id": 3,
"type": "schema",
"properties": [
{
"path": "history",
"type": {
"type": "array",
"items": { "type": "schema", "name": "Counters", "nullable": false }
}
}
]
}
]
}

View File

@@ -0,0 +1,143 @@
// Set of types used in the IO tests.
{
"schemas": [
{ "name": "Mixed", "id": 1, "type": "schema",
"properties": [
{ "path": "null", "type": { "type": "null", "storage": "fixed" } },
{ "path": "bool", "type": { "type": "bool", "storage": "fixed" } },
{ "path": "int8", "type": { "type": "int8", "storage": "fixed" } },
{ "path": "int16", "type": { "type": "int16", "storage": "fixed" } },
{ "path": "int32", "type": { "type": "int32", "storage": "fixed" } },
{ "path": "int64", "type": { "type": "int64", "storage": "fixed" } },
{ "path": "uint8", "type": { "type": "uint8", "storage": "fixed" } },
{ "path": "uint16", "type": { "type": "uint16", "storage": "fixed" } },
{ "path": "uint32", "type": { "type": "uint32", "storage": "fixed" } },
{ "path": "uint64", "type": { "type": "uint64", "storage": "fixed" } },
{ "path": "float32", "type": { "type": "float32", "storage": "fixed" } },
{ "path": "float64", "type": { "type": "float64", "storage": "fixed" } },
{ "path": "float128", "type": { "type": "float128", "storage": "fixed" } },
{ "path": "decimal", "type": { "type": "decimal", "storage": "fixed" } },
{ "path": "datetime", "type": { "type": "datetime", "storage": "fixed" } },
{ "path": "unixdatetime", "type": { "type": "unixdatetime", "storage": "fixed" } },
{ "path": "guid", "type": { "type": "guid", "storage": "fixed" } },
{ "path": "mongodbobjectid", "type": { "type": "mongodbobjectid", "storage": "fixed" } },
{ "path": "utf8", "type": { "type": "utf8", "storage": "fixed", "length": 3 } },
{ "path": "utf8_span", "type": { "type": "utf8", "storage": "fixed", "length": 3 } },
{ "path": "binary", "type": { "type": "binary", "storage": "fixed", "length": 3 } },
{ "path": "binary_span", "type": { "type": "binary", "storage": "fixed", "length": 3 } },
{ "path": "binary_sequence", "type": { "type": "binary", "storage": "fixed", "length": 3 } },
{ "path": "var_varint", "type": { "type": "varint", "storage": "variable" } },
{ "path": "var_varuint", "type": { "type": "varuint", "storage": "variable" } },
{ "path": "var_utf8", "type": { "type": "utf8", "storage": "variable"} },
{ "path": "var_utf8_span", "type": { "type": "utf8", "storage": "variable"} },
{ "path": "var_binary", "type": { "type": "binary", "storage": "variable" } },
{ "path": "var_binary_span", "type": { "type": "binary", "storage": "variable" } },
{ "path": "var_binary_sequence", "type": { "type": "binary", "storage": "variable" } },
{ "path": "sparse_null", "type": { "type": "null" } },
{ "path": "sparse_bool", "type": { "type": "bool" } },
{ "path": "sparse_int8", "type": { "type": "int8" } },
{ "path": "sparse_int16", "type": { "type": "int16" } },
{ "path": "sparse_int32", "type": { "type": "int32" } },
{ "path": "sparse_int64", "type": { "type": "int64" } },
{ "path": "sparse_uint8", "type": { "type": "uint8" } },
{ "path": "sparse_uint16", "type": { "type": "uint16" } },
{ "path": "sparse_uint32", "type": { "type": "uint32" } },
{ "path": "sparse_uint64", "type": { "type": "uint64" } },
{ "path": "sparse_float32", "type": { "type": "float32" } },
{ "path": "sparse_float64", "type": { "type": "float64" } },
{ "path": "sparse_float128", "type": { "type": "float128" } },
{ "path": "sparse_decimal", "type": { "type": "decimal" } },
{ "path": "sparse_datetime", "type": { "type": "datetime" } },
{ "path": "sparse_unixdatetime", "type": { "type": "unixdatetime" } },
{ "path": "sparse_guid", "type": { "type": "guid" } },
{ "path": "sparse_mongodbobjectid", "type": { "type": "mongodbobjectid" } },
{ "path": "sparse_utf8", "type": { "type": "utf8" } },
{ "path": "sparse_utf8_span", "type": { "type": "utf8" } },
{ "path": "sparse_binary", "type": { "type": "binary" } },
{ "path": "sparse_binary_span", "type": { "type": "binary" } },
{ "path": "sparse_binary_sequence", "type": { "type": "binary" } },
{ "path": "array_t<int8>", "type": {
"type": "array",
"items": { "type": "int8", "nullable": false }
} },
{ "path": "array_t<array_t<float32>>", "type": {
"type": "array",
"items": { "type": "array", "nullable": false, "items": { "type": "float32", "nullable": false } }
} },
{ "path": "array_t<utf8>", "type": { "type": "array", "items": { "type": "utf8", "nullable": false } } },
{ "path": "tuple<varint,int64>", "type": {
"type": "tuple",
"items": [ { "type": "varint", "nullable": false }, { "type": "int64", "nullable": false }]
} },
{ "path": "tuple<null,tuple<int8,int8>>", "type": {
"type": "tuple", "items": [
{ "type": "null", "nullable": false },
{ "type": "tuple", "nullable": false, "items": [ { "type": "int8", "nullable": false }, { "type": "int8", "nullable": false } ] }
]}},
{ "path": "tuple<bool,udt>", "type": {
"type": "tuple", "items": [
{ "type": "bool", "nullable": false },
{ "type": "schema", "name": "Point", "nullable": false}
]}},
{ "path": "nullable<int32,int64>", "type": {
"type": "tuple",
"items": [ { "type": "int32", "nullable": true }, { "type": "int64", "nullable": true }]
} },
{ "path": "tagged<utf8>", "type": {
"type": "tagged", "items": [
{ "type": "utf8", "nullable": false }
]}},
{ "path": "tagged<bool,utf8>", "type": {
"type": "tagged", "items": [
{ "type": "bool", "nullable": false },
{ "type": "utf8", "nullable": false }
]}},
{ "path": "set_t<utf8>", "type": {
"type": "set",
"items": { "type": "utf8", "nullable": false }
} },
{ "path": "set_t<array_t<int8>>", "type": {
"type": "set",
"items": { "type": "array", "nullable": false, "items": { "type": "int8", "nullable": false } }
} },
{ "path": "set_t<set_t<int32>>", "type": {
"type": "set",
"items": { "type": "set", "nullable": false, "items": { "type": "int32", "nullable": false } }
} },
{ "path": "set_t<udt>", "type": {
"type": "set",
"items": { "type": "schema", "name": "Point", "nullable": false}
} },
{ "path": "map_t<utf8,utf8>", "type": {
"type": "map",
"keys": { "type": "utf8", "nullable": false },
"values": { "type": "utf8", "nullable": false }
} },
{ "path": "map_t<int8,array_t<int8>>", "type": {
"type": "map",
"keys": { "type": "int8", "nullable": false },
"values": { "type": "array", "nullable": false, "items": { "type": "int8", "nullable": false } }
} },
{ "path": "map_t<int16,map_t<int32,int32>>", "type": {
"type": "map",
"keys": { "type": "int16", "nullable": false },
"values": {
"type": "map",
"nullable": false,
"keys": { "type": "int32", "nullable": false },
"values": { "type": "int32", "nullable": false }
}
} },
{ "path": "map_t<float64,udt>", "type": {
"type": "map",
"keys": { "type": "float64", "nullable": false },
"values": { "type": "schema", "name": "Point", "nullable": false}
} }
]},
{ "name": "Point", "id": 4, "type": "schema",
"properties": [
{ "path": "x", "type": { "type": "int32", "storage": "fixed" } },
{ "path": "y", "type": { "type": "int32", "storage": "fixed" } }
]}
]
}

View File

@@ -0,0 +1,171 @@
{
"name": "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.SchemaHashTest",
"schemas": [
{
"version": "v1",
"comment": "Some UDT definition",
"name": "UDT",
"id": 1,
"type": "schema",
"properties": [
{
"path": "item1",
"type": {
"length": 0,
"storage": "fixed",
"type": "int32",
"nullable": false
}
},
{
"path": "item2",
"type": {
"length": 10,
"storage": "variable",
"type": "utf8"
}
}
],
"partitionkeys": [],
"primarykeys": []
},
{
"version": "v1",
"comment": "Some table definition",
"name": "Table",
"id": 2,
"options": {
"disallowUnschematized": true,
"enablePropertyLevelTimestamp": true
},
"type": "schema",
"properties": [
{
"path": "fixed",
"type": {
"length": 0,
"storage": "fixed",
"apitype": "myfixed",
"type": "int32"
}
},
{
"path": "array",
"type": {
"items": {
"length": 0,
"storage": "sparse",
"type": "int8"
},
"immutable": true,
"type": "array"
}
},
{
"path": "obj",
"type": {
"properties": [
{
"path": "nested",
"type": {
"length": 0,
"storage": "sparse",
"type": "int32"
}
}
],
"immutable": false,
"type": "object"
}
},
{
"path": "map",
"type": {
"keys": {
"length": 0,
"storage": "sparse",
"type": "int8"
},
"values": {
"length": 0,
"storage": "sparse",
"type": "int8"
},
"immutable": false,
"type": "map"
}
},
{
"path": "set",
"type": {
"items": {
"length": 0,
"storage": "sparse",
"type": "int8"
},
"immutable": false,
"type": "set"
}
},
{
"path": "tagged",
"type": {
"items": [
{
"length": 0,
"storage": "sparse",
"type": "int32"
}
],
"immutable": false,
"type": "tagged"
}
},
{
"path": "tuple",
"type": {
"items": [
{
"length": 0,
"storage": "sparse",
"type": "int32"
},
{
"length": 0,
"storage": "sparse",
"type": "float32"
}
],
"immutable": false,
"type": "tuple"
}
},
{
"path": "udt",
"type": {
"name": "UDT",
"id": 1,
"immutable": false,
"type": "schema"
}
}
],
"partitionkeys": [
{
"path": "fixed"
}
],
"primarykeys": [
{
"path": "fixed",
"direction": "asc"
}
],
"statickeys": [
{
"path": "fixed"
}
]
}
]
}

View File

@@ -0,0 +1,28 @@
// Tag demo schema that utilizes typed arrays.
{
"schemas": [
{ "name": "Tagged", "id": 1, "type": "schema",
"properties": [
{ "path": "title", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "tags", "type": { "type": "array",
"items": { "type": "utf8", "nullable": false } } },
{ "path": "options", "type": { "type": "array",
"items": { "type": "int32", "nullable": true } } },
{ "path": "ratings", "type": { "type": "array",
"items": { "type": "array", "nullable": false, "items": { "type": "float64", "nullable": false } } } },
{ "path": "similars", "type": { "type": "array",
"items": { "type": "schema", "name": "SimilarMatch", "nullable": false } } },
{ "path": "priority", "type": { "type": "array",
"items": { "type": "tuple", "nullable": false,
"items": [
{ "type": "utf8", "nullable": false },
{ "type": "int64", "nullable": false }
]}}}
]},
{ "name": "SimilarMatch", "id": 2, "type": "schema",
"properties": [
{ "path": "thumbprint", "type": { "type": "utf8", "storage": "fixed", "length": 18 } },
{ "path": "score", "type": { "type": "float64", "storage": "fixed" } }
]}
]
}

View File

@@ -0,0 +1,28 @@
// Tagged demo schema that utilizes tagged types.
{
"schemas": [
{
"name": "TaggedApi",
"id": 1,
"type": "schema",
"properties": [
{
"path": "tag1",
"type": {
"type": "tagged",
"immutable": true,
"items": [{ "type": "utf8", "nullable": false }]
}
},
{
"path": "tag2",
"type": {
"type": "tagged",
"immutable": false,
"items": [{ "type": "int32", "nullable": false }, { "type": "int64", "nullable": false }]
}
}
]
}
]
}

View File

@@ -0,0 +1,75 @@
// Todo demo schema that utilizes typed sets.
{
"schemas": [
{
"name": "Todo",
"id": 1,
"type": "schema",
"properties": [
{ "path": "attendees", "type": { "type": "set", "items": { "type": "utf8", "nullable": false } } },
{ "path": "projects", "type": { "type": "set", "items": { "type": "guid", "nullable": false } } },
{ "path": "checkboxes", "type": { "type": "set", "items": { "type": "bool", "nullable": false } } },
{
"path": "prices",
"type": {
"type": "set",
"items": {
"type": "set",
"immutable": true,
"nullable": false,
"items": { "type": "float32", "nullable": false }
}
}
},
{
"path": "nested",
"type": {
"type": "set",
"items": {
"type": "set",
"immutable": true,
"nullable": false,
"items":
{
"type": "set",
"immutable": true,
"nullable": false,
"items": {
"type": "int32",
"nullable": false
}
}
}
}
},
{
"path": "shopping",
"type": { "type": "set", "items": { "type": "schema", "name": "ShoppingItem", "nullable": false } }
},
{
"path": "work",
"type": {
"type": "set",
"items": {
"type": "tuple",
"nullable": false,
"items": [
{ "type": "bool", "nullable": false },
{ "type": "varuint", "nullable": false }
]
}
}
}
]
},
{
"name": "ShoppingItem",
"id": 2,
"type": "schema",
"properties": [
{ "path": "label", "type": { "type": "utf8", "storage": "variable" } },
{ "path": "count", "type": { "type": "uint8", "storage": "fixed" } }
]
}
]
}

View File

@@ -0,0 +1,546 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
// ReSharper disable once StringLiteralTypo
[TestClass]
[SuppressMessage("Naming", "DontUseVarForVariableTypes", Justification = "The types here are anonymous.")]
[DeploymentItem(@"TestData\PerfCounterSchema.json", "TestData")]
public sealed class TupleUnitTests
{
private const int InitialRowSize = 2 * 1024 * 1024;
private readonly PerfCounter counterExample = new PerfCounter()
{
Name = "RowInserts",
Value = Tuple.Create("units", 12046L),
};
private Namespace counterSchema;
private LayoutResolver countersResolver;
private Layout countersLayout;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(@"TestData\PerfCounterSchema.json");
this.counterSchema = Namespace.Parse(json);
this.countersResolver = new LayoutResolverNamespace(this.counterSchema);
this.countersLayout = this.countersResolver.Resolve(this.counterSchema.Schemas.Find(x => x.Name == "Counters").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void CreateCounter()
{
RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver);
PerfCounter c1 = this.counterExample;
this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1);
PerfCounter c2 = this.ReadCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(c1, c2);
}
[TestMethod]
[Owner("jthunter")]
public void VerifyTypeConstraintsCounter()
{
RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver);
PerfCounter c1 = this.counterExample;
this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1);
Assert.IsTrue(this.countersLayout.TryFind("value", out LayoutColumn c));
RowCursor.Create(ref row, out RowCursor valueScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, c.TypeArgs, out valueScope));
ResultAssert.TypeConstraint(LayoutType.Boolean.WriteSparse(ref row, ref valueScope, true));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref valueScope, "millis"));
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.TypeConstraint(LayoutType.Float32.WriteSparse(ref row, ref valueScope, 0.1F));
ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref valueScope, 100L));
}
[TestMethod]
[Owner("jthunter")]
public void PreventInsertsAndDeletesInFixedArityCounter()
{
RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver);
PerfCounter c1 = this.counterExample;
this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1);
Assert.IsTrue(this.countersLayout.TryFind("value", out LayoutColumn c));
RowCursor.Create(ref row, out RowCursor valueScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, c.TypeArgs, out valueScope));
RowCursor.Create(ref row, out RowCursor valueScope2).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref valueScope2, out valueScope2));
Assert.AreEqual(valueScope.AsReadOnly(out RowCursor _).ScopeType, valueScope2.ScopeType);
Assert.AreEqual(valueScope.AsReadOnly(out RowCursor _).start, valueScope2.start);
Assert.AreEqual(valueScope.AsReadOnly(out RowCursor _).Immutable, valueScope2.Immutable);
ResultAssert.TypeConstraint(
c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref valueScope, "millis", UpdateOptions.InsertAt));
ResultAssert.TypeConstraint(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().DeleteSparse(ref row, ref valueScope));
Assert.IsFalse(valueScope.MoveTo(ref row, 2));
}
[TestMethod]
[Owner("jthunter")]
public void CreateMinMeanMaxCounter()
{
RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver);
PerfCounter c1 = new PerfCounter()
{
Name = "RowInserts",
MinMaxValue = Tuple.Create("units", Tuple.Create(12L, 542L, 12046L)),
};
this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1);
PerfCounter c2 = this.ReadCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(c1, c2);
}
[TestMethod]
[Owner("jthunter")]
public void CreateCoordCounter()
{
RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver);
PerfCounter c1 = new PerfCounter()
{
Name = "CoordInserts",
Coord = Tuple.Create("units", new Coord { Lat = 12L, Lng = 40L }),
};
this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1);
PerfCounter c2 = this.ReadCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(c1, c2);
}
[TestMethod]
[Owner("jthunter")]
public void VerifyTypeConstraintsMinMeanMaxCounter()
{
RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver);
PerfCounter c1 = new PerfCounter()
{
Name = "RowInserts",
MinMaxValue = Tuple.Create("units", Tuple.Create(12L, 542L, 12046L)),
};
this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1);
// ReSharper disable once StringLiteralTypo
Assert.IsTrue(this.countersLayout.TryFind("minmeanmax", out LayoutColumn c));
RowCursor.Create(ref row, out RowCursor valueScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, c.TypeArgs, out valueScope));
ResultAssert.TypeConstraint(LayoutType.DateTime.WriteSparse(ref row, ref valueScope, DateTime.Now));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref valueScope, "secs"));
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.TypeConstraint(LayoutType.Decimal.WriteSparse(ref row, ref valueScope, 12M));
TypeArgument mmmType = c.TypeArgs[1];
// Invalid because not a tuple type.
ResultAssert.TypeConstraint(
mmmType.Type.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, TypeArgumentList.Empty, out RowCursor mmmScope));
// Invalid because is a tuple type but with the wrong parameters.
ResultAssert.TypeConstraint(
mmmType.Type.TypeAs<LayoutIndexedScope>()
.WriteScope(
ref row,
ref valueScope,
new TypeArgumentList(
new[]
{
new TypeArgument(LayoutType.Boolean),
new TypeArgument(LayoutType.Int64),
}),
out mmmScope));
// Invalid because is a tuple type but with the wrong arity.
ResultAssert.TypeConstraint(
mmmType.Type.TypeAs<LayoutIndexedScope>()
.WriteScope(
ref row,
ref valueScope,
new TypeArgumentList(
new[]
{
new TypeArgument(LayoutType.Utf8),
}),
out mmmScope));
ResultAssert.IsSuccess(
mmmType.Type.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, mmmType.TypeArgs, out mmmScope));
ResultAssert.TypeConstraint(LayoutType.Binary.WriteSparse(ref row, ref valueScope, new byte[] { 1, 2, 3 }));
ResultAssert.IsSuccess(mmmType.TypeArgs[0].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref mmmScope, 1L));
Assert.IsTrue(mmmScope.MoveNext(ref row));
ResultAssert.IsSuccess(mmmType.TypeArgs[1].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref mmmScope, 2L));
Assert.IsTrue(mmmScope.MoveNext(ref row));
ResultAssert.IsSuccess(mmmType.TypeArgs[2].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref mmmScope, 3L));
}
[TestMethod]
[Owner("jthunter")]
public void VerifyTypeConstraintsCoordCounter()
{
RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver);
PerfCounter c1 = new PerfCounter()
{
Name = "RowInserts",
Coord = Tuple.Create("units", new Coord { Lat = 12L, Lng = 40L }),
};
this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1);
Assert.IsTrue(this.countersLayout.TryFind("coord", out LayoutColumn c));
RowCursor.Create(ref row, out RowCursor valueScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, c.TypeArgs, out valueScope));
ResultAssert.TypeConstraint(LayoutType.DateTime.WriteSparse(ref row, ref valueScope, DateTime.Now));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref valueScope, "mins"));
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.TypeConstraint(LayoutType.Int8.WriteSparse(ref row, ref valueScope, 42));
TypeArgument coordType = c.TypeArgs[1];
// Invalid because is a UDT but the wrong type.
ResultAssert.TypeConstraint(
coordType.Type.TypeAs<LayoutUDT>()
.WriteScope(
ref row,
ref valueScope,
new TypeArgumentList(this.countersLayout.SchemaId),
out RowCursor _));
}
[TestMethod]
[Owner("jthunter")]
public void DownwardDelegateWriteScope()
{
RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize);
Layout layout = this.countersResolver.Resolve(this.counterSchema.Schemas.Find(x => x.Name == "CounterSet").SchemaId);
row.InitLayout(HybridRowVersion.V1, layout, this.countersResolver);
Assert.IsTrue(layout.TryFind("history", out LayoutColumn col));
Assert.IsTrue(layout.Tokenizer.TryFindToken(col.Path, out StringToken historyToken));
RowCursor.Create(ref row, out RowCursor history).Find(ref row, historyToken);
int ctx = 1; // ignored
ResultAssert.IsSuccess(
LayoutType.TypedArray.WriteScope(
ref row,
ref history,
col.TypeArgs,
ctx,
(ref RowBuffer row2, ref RowCursor arrCur, int ctx2) =>
{
for (int i = 0; i < 5; i++)
{
ResultAssert.IsSuccess(
LayoutType.UDT.WriteScope(
ref row2,
ref arrCur,
arrCur.ScopeTypeArgs[0].TypeArgs,
i,
(ref RowBuffer row3, ref RowCursor udtCur, int ctx3) =>
{
Assert.IsTrue(udtCur.Layout.TryFind("minmeanmax", out LayoutColumn col3));
ResultAssert.IsSuccess(LayoutType.TypedTuple.WriteScope(
ref row3,
ref udtCur.Find(ref row3, col3.Path),
col3.TypeArgs,
ctx3,
(ref RowBuffer row4, ref RowCursor tupCur, int ctx4) =>
{
if (ctx4 > 0)
{
ResultAssert.IsSuccess(LayoutType.Utf8.WriteSparse(ref row4, ref tupCur, "abc"));
}
if (ctx4 > 1)
{
Assert.IsTrue(tupCur.MoveNext(ref row4));
ResultAssert.IsSuccess(
LayoutType.TypedTuple.WriteScope(
ref row4,
ref tupCur,
tupCur.ScopeTypeArgs[1].TypeArgs,
ctx4,
(ref RowBuffer row5, ref RowCursor tupCur2, int ctx5) =>
{
if (ctx5 > 1)
{
ResultAssert.IsSuccess(LayoutType.Int64.WriteSparse(ref row5, ref tupCur2, ctx5));
}
if (ctx5 > 2)
{
Assert.IsTrue(tupCur2.MoveNext(ref row5));
ResultAssert.IsSuccess(LayoutType.Int64.WriteSparse(ref row5, ref tupCur2, ctx5));
}
if (ctx5 > 3)
{
Assert.IsTrue(tupCur2.MoveNext(ref row5));
ResultAssert.IsSuccess(LayoutType.Int64.WriteSparse(ref row5, ref tupCur2, ctx5));
}
return Result.Success;
}));
}
return Result.Success;
}));
return Result.Success;
}));
Assert.IsFalse(arrCur.MoveNext(ref row2));
}
return Result.Success;
}));
}
private void WriteCounter(ref RowBuffer row, ref RowCursor root, PerfCounter pc)
{
Assert.IsTrue(this.countersLayout.TryFind("name", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, pc.Name));
if (pc.Value != null)
{
Assert.IsTrue(this.countersLayout.TryFind("value", out c));
root.Clone(out RowCursor valueScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, c.TypeArgs, out valueScope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref valueScope, pc.Value.Item1));
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref valueScope, pc.Value.Item2));
}
if (pc.MinMaxValue != null)
{
// ReSharper disable once StringLiteralTypo
Assert.IsTrue(this.countersLayout.TryFind("minmeanmax", out c));
root.Clone(out RowCursor valueScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, c.TypeArgs, out valueScope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref valueScope, pc.MinMaxValue.Item1));
Assert.IsTrue(valueScope.MoveNext(ref row));
TypeArgument mmmType = c.TypeArgs[1];
ResultAssert.IsSuccess(
mmmType.Type.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, mmmType.TypeArgs, out RowCursor mmmScope));
ResultAssert.IsSuccess(
mmmType.TypeArgs[0].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref mmmScope, pc.MinMaxValue.Item2.Item1));
Assert.IsTrue(mmmScope.MoveNext(ref row));
ResultAssert.IsSuccess(
mmmType.TypeArgs[1].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref mmmScope, pc.MinMaxValue.Item2.Item2));
Assert.IsTrue(mmmScope.MoveNext(ref row));
ResultAssert.IsSuccess(
mmmType.TypeArgs[2].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref mmmScope, pc.MinMaxValue.Item2.Item3));
}
if (pc.Coord != null)
{
Assert.IsTrue(this.countersLayout.TryFind("coord", out c));
root.Clone(out RowCursor valueScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutIndexedScope>().WriteScope(ref row, ref valueScope, c.TypeArgs, out valueScope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref valueScope, pc.Coord.Item1));
Assert.IsTrue(valueScope.MoveNext(ref row));
TypeArgument mmmType = c.TypeArgs[1];
ResultAssert.IsSuccess(
mmmType.Type.TypeAs<LayoutUDT>().WriteScope(ref row, ref valueScope, mmmType.TypeArgs, out RowCursor coordScope));
TupleUnitTests.WriteCoord(ref row, ref coordScope, mmmType.TypeArgs, pc.Coord.Item2);
}
}
private PerfCounter ReadCounter(ref RowBuffer row, ref RowCursor root)
{
PerfCounter pc = new PerfCounter();
Assert.IsTrue(this.countersLayout.TryFind("name", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out pc.Name));
Assert.IsTrue(this.countersLayout.TryFind("value", out c));
Assert.IsTrue(c.Type.Immutable);
root.Clone(out RowCursor valueScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref valueScope, out valueScope) == Result.Success)
{
Assert.IsTrue(valueScope.Immutable);
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref valueScope, out string units));
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs<LayoutInt64>().ReadSparse(ref row, ref valueScope, out long metric));
pc.Value = Tuple.Create(units, metric);
}
// ReSharper disable once StringLiteralTypo
Assert.IsTrue(this.countersLayout.TryFind("minmeanmax", out c));
Assert.IsTrue(c.Type.Immutable);
root.Clone(out valueScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref valueScope, out valueScope) == Result.Success)
{
Assert.IsTrue(valueScope.Immutable);
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref valueScope, out string units));
Assert.IsTrue(valueScope.MoveNext(ref row));
TypeArgument mmmType = c.TypeArgs[1];
ResultAssert.IsSuccess(
mmmType.Type.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref valueScope, out RowCursor mmmScope));
Assert.IsTrue(mmmScope.Immutable);
Assert.IsTrue(mmmScope.MoveNext(ref row));
ResultAssert.IsSuccess(mmmType.TypeArgs[0].Type.TypeAs<LayoutInt64>().ReadSparse(ref row, ref mmmScope, out long min));
Assert.IsTrue(mmmScope.MoveNext(ref row));
ResultAssert.IsSuccess(mmmType.TypeArgs[1].Type.TypeAs<LayoutInt64>().ReadSparse(ref row, ref mmmScope, out long mean));
Assert.IsTrue(mmmScope.MoveNext(ref row));
ResultAssert.IsSuccess(mmmType.TypeArgs[2].Type.TypeAs<LayoutInt64>().ReadSparse(ref row, ref mmmScope, out long max));
pc.MinMaxValue = Tuple.Create(units, Tuple.Create(min, mean, max));
}
Assert.IsTrue(this.countersLayout.TryFind("coord", out c));
Assert.IsTrue(c.Type.Immutable);
root.Clone(out valueScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutIndexedScope>().ReadScope(ref row, ref valueScope, out valueScope) == Result.Success)
{
Assert.IsTrue(valueScope.Immutable);
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref valueScope, out string units));
Assert.IsTrue(valueScope.MoveNext(ref row));
ResultAssert.IsSuccess(
c.TypeArgs[1].Type.TypeAs<LayoutUDT>().ReadScope(ref row, ref valueScope, out RowCursor coordScope));
pc.Coord = Tuple.Create(units, TupleUnitTests.ReadCoord(ref row, ref coordScope));
}
return pc;
}
private static void WriteCoord(ref RowBuffer row, ref RowCursor coordScope, TypeArgumentList typeArgs, Coord cd)
{
Layout coordLayout = row.Resolver.Resolve(typeArgs.SchemaId);
Assert.IsTrue(coordLayout.TryFind("lat", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutInt64>().WriteFixed(ref row, ref coordScope, c, cd.Lat));
Assert.IsTrue(coordLayout.TryFind("lng", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutInt64>().WriteFixed(ref row, ref coordScope, c, cd.Lng));
}
private static Coord ReadCoord(ref RowBuffer row, ref RowCursor coordScope)
{
Layout coordLayout = coordScope.Layout;
Coord cd = new Coord();
Assert.IsTrue(coordLayout.TryFind("lat", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutInt64>().ReadFixed(ref row, ref coordScope, c, out cd.Lat));
Assert.IsTrue(coordLayout.TryFind("lng", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutInt64>().ReadFixed(ref row, ref coordScope, c, out cd.Lng));
return cd;
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class PerfCounter
{
public string Name;
public Tuple<string, long> Value;
public Tuple<string, Tuple<long, long, long>> MinMaxValue;
// ReSharper disable once MemberHidesStaticFromOuterClass
public Tuple<string, Coord> Coord;
// ReSharper disable once MemberCanBePrivate.Local
public bool Equals(PerfCounter other)
{
return string.Equals(this.Name, other.Name) &&
object.Equals(this.Value, other.Value) &&
object.Equals(this.MinMaxValue, other.MinMaxValue) &&
object.Equals(this.Coord, other.Coord);
}
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is PerfCounter counter && this.Equals(counter);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Name?.GetHashCode() ?? 0;
hashCode = (hashCode * 397) ^ (this.Value?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.MinMaxValue?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Coord?.GetHashCode() ?? 0);
return hashCode;
}
}
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class Coord
{
public long Lat;
public long Lng;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Coord coord && this.Equals(coord);
}
public override int GetHashCode()
{
unchecked
{
return (this.Lat.GetHashCode() * 397) ^ this.Lng.GetHashCode();
}
}
private bool Equals(Coord other)
{
return this.Lat == other.Lat && this.Lng == other.Lng;
}
}
}
}

View File

@@ -0,0 +1,413 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
// ReSharper disable StringLiteralTypo
// ReSharper disable IdentifierTypo
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[DeploymentItem(TypedArrayUnitTests.SchemaFile, "TestData")]
public sealed class TypedArrayUnitTests
{
private const string SchemaFile = @"TestData\TagSchema.json";
private const int InitialRowSize = 2 * 1024 * 1024;
private Namespace counterSchema;
private LayoutResolver resolver;
private Layout layout;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(TypedArrayUnitTests.SchemaFile);
this.counterSchema = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.counterSchema);
this.layout = this.resolver.Resolve(this.counterSchema.Schemas.Find(x => x.Name == "Tagged").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void CreateTags()
{
RowBuffer row = new RowBuffer(TypedArrayUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
Tagged t1 = new Tagged
{
Title = "Thriller",
Tags = new List<string> { "classic", "Post-disco", "funk" },
Options = new List<int?> { 8, null, 9 },
Ratings = new List<List<double>>
{
new List<double> { 1.2, 3.0 },
new List<double> { 4.1, 5.7 },
new List<double> { 7.3, 8.12, 9.14 },
},
Similars = new List<SimilarMatch>
{
new SimilarMatch { Thumbprint = "TRABACN128F425B784", Score = 0.87173699999999998 },
new SimilarMatch { Thumbprint = "TRJYGLF12903CB4952", Score = 0.75105200000000005 },
new SimilarMatch { Thumbprint = "TRWJMMB128F429D550", Score = 0.50866100000000003 },
},
Priority = new List<Tuple<string, long>>
{
Tuple.Create("80's", 100L),
Tuple.Create("classics", 100L),
Tuple.Create("pop", 50L),
},
};
this.WriteTagged(ref row, ref RowCursor.Create(ref row, out RowCursor _), t1);
Tagged t2 = this.ReadTagged(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(t1, t2);
}
private void WriteTagged(ref RowBuffer row, ref RowCursor root, Tagged value)
{
Assert.IsTrue(this.layout.TryFind("title", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref root, c, value.Title));
if (value.Tags != null)
{
Assert.IsTrue(this.layout.TryFind("tags", out c));
root.Clone(out RowCursor tagsScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref tagsScope, c.TypeArgs, out tagsScope));
foreach (string item in value.Tags)
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tagsScope, item));
Assert.IsFalse(tagsScope.MoveNext(ref row));
}
}
if (value.Options != null)
{
Assert.IsTrue(this.layout.TryFind("options", out c));
root.Clone(out RowCursor optionsScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref optionsScope, c.TypeArgs, out optionsScope));
foreach (int? item in value.Options)
{
TypeArgument itemType = c.TypeArgs[0];
ResultAssert.IsSuccess(
itemType.Type.TypeAs<LayoutNullable>()
.WriteScope(ref row, ref optionsScope, itemType.TypeArgs, item.HasValue, out RowCursor nullableScope));
if (item.HasValue)
{
ResultAssert.IsSuccess(
itemType.TypeArgs[0].Type.TypeAs<LayoutInt32>().WriteSparse(ref row, ref nullableScope, item.Value));
}
Assert.IsFalse(optionsScope.MoveNext(ref row, ref nullableScope));
}
}
if (value.Ratings != null)
{
Assert.IsTrue(this.layout.TryFind("ratings", out c));
root.Clone(out RowCursor ratingsScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref ratingsScope, c.TypeArgs, out ratingsScope));
foreach (List<double> item in value.Ratings)
{
Assert.IsTrue(item != null);
TypeArgument innerType = c.TypeArgs[0];
LayoutTypedArray innerLayout = innerType.Type.TypeAs<LayoutTypedArray>();
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref ratingsScope, innerType.TypeArgs, out RowCursor innerScope));
foreach (double innerItem in item)
{
LayoutFloat64 itemLayout = innerType.TypeArgs[0].Type.TypeAs<LayoutFloat64>();
ResultAssert.IsSuccess(itemLayout.WriteSparse(ref row, ref innerScope, innerItem));
Assert.IsFalse(innerScope.MoveNext(ref row));
}
Assert.IsFalse(ratingsScope.MoveNext(ref row, ref innerScope));
}
}
if (value.Similars != null)
{
Assert.IsTrue(this.layout.TryFind("similars", out c));
root.Clone(out RowCursor similarsScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref similarsScope, c.TypeArgs, out similarsScope));
foreach (SimilarMatch item in value.Similars)
{
TypeArgument innerType = c.TypeArgs[0];
LayoutUDT innerLayout = innerType.Type.TypeAs<LayoutUDT>();
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref similarsScope, innerType.TypeArgs, out RowCursor matchScope));
TypedArrayUnitTests.WriteSimilarMatch(ref row, ref matchScope, innerType.TypeArgs, item);
Assert.IsFalse(similarsScope.MoveNext(ref row, ref matchScope));
}
}
if (value.Priority != null)
{
Assert.IsTrue(this.layout.TryFind("priority", out c));
root.Clone(out RowCursor priorityScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutTypedArray>().WriteScope(ref row, ref priorityScope, c.TypeArgs, out priorityScope));
foreach (Tuple<string, long> item in value.Priority)
{
TypeArgument innerType = c.TypeArgs[0];
LayoutIndexedScope innerLayout = innerType.Type.TypeAs<LayoutIndexedScope>();
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref priorityScope, innerType.TypeArgs, out RowCursor tupleScope));
ResultAssert.IsSuccess(innerType.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tupleScope, item.Item1));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(innerType.TypeArgs[1].Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref tupleScope, item.Item2));
Assert.IsFalse(priorityScope.MoveNext(ref row, ref tupleScope));
}
}
}
private Tagged ReadTagged(ref RowBuffer row, ref RowCursor root)
{
Tagged value = new Tagged();
Assert.IsTrue(this.layout.TryFind("title", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref root, c, out value.Title));
Assert.IsTrue(this.layout.TryFind("tags", out c));
root.Clone(out RowCursor tagsScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref tagsScope, out tagsScope) == Result.Success)
{
value.Tags = new List<string>();
while (tagsScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref tagsScope, out string item));
value.Tags.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("options", out c));
root.Clone(out RowCursor optionsScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref optionsScope, out optionsScope) == Result.Success)
{
value.Options = new List<int?>();
while (optionsScope.MoveNext(ref row))
{
TypeArgument itemType = c.TypeArgs[0];
ResultAssert.IsSuccess(
itemType.Type.TypeAs<LayoutNullable>()
.ReadScope(ref row, ref optionsScope, out RowCursor nullableScope));
if (nullableScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(LayoutNullable.HasValue(ref row, ref nullableScope));
ResultAssert.IsSuccess(
itemType.TypeArgs[0].Type.TypeAs<LayoutInt32>().ReadSparse(ref row, ref nullableScope, out int itemValue));
value.Options.Add(itemValue);
}
else
{
ResultAssert.NotFound(LayoutNullable.HasValue(ref row, ref nullableScope));
value.Options.Add(null);
}
}
}
Assert.IsTrue(this.layout.TryFind("ratings", out c));
root.Clone(out RowCursor ratingsScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref ratingsScope, out ratingsScope) == Result.Success)
{
value.Ratings = new List<List<double>>();
TypeArgument innerType = c.TypeArgs[0];
LayoutTypedArray innerLayout = innerType.Type.TypeAs<LayoutTypedArray>();
RowCursor innerScope = default;
while (ratingsScope.MoveNext(ref row, ref innerScope))
{
List<double> item = new List<double>();
ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref ratingsScope, out innerScope));
while (innerScope.MoveNext(ref row))
{
LayoutFloat64 itemLayout = innerType.TypeArgs[0].Type.TypeAs<LayoutFloat64>();
ResultAssert.IsSuccess(itemLayout.ReadSparse(ref row, ref innerScope, out double innerItem));
item.Add(innerItem);
}
value.Ratings.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("similars", out c));
root.Clone(out RowCursor similarsScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref similarsScope, out similarsScope) == Result.Success)
{
value.Similars = new List<SimilarMatch>();
while (similarsScope.MoveNext(ref row))
{
TypeArgument innerType = c.TypeArgs[0];
LayoutUDT innerLayout = innerType.Type.TypeAs<LayoutUDT>();
ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref similarsScope, out RowCursor matchScope));
SimilarMatch item = TypedArrayUnitTests.ReadSimilarMatch(ref row, ref matchScope);
value.Similars.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("priority", out c));
root.Clone(out RowCursor priorityScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutTypedArray>().ReadScope(ref row, ref priorityScope, out priorityScope) == Result.Success)
{
value.Priority = new List<Tuple<string, long>>();
RowCursor tupleScope = default;
while (priorityScope.MoveNext(ref row, ref tupleScope))
{
TypeArgument innerType = c.TypeArgs[0];
LayoutIndexedScope innerLayout = innerType.Type.TypeAs<LayoutIndexedScope>();
ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref priorityScope, out tupleScope));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(
innerType.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref tupleScope, out string item1));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(
innerType.TypeArgs[1].Type.TypeAs<LayoutInt64>().ReadSparse(ref row, ref tupleScope, out long item2));
value.Priority.Add(Tuple.Create(item1, item2));
}
}
return value;
}
private static void WriteSimilarMatch(ref RowBuffer row, ref RowCursor matchScope, TypeArgumentList typeArgs, SimilarMatch m)
{
Layout matchLayout = row.Resolver.Resolve(typeArgs.SchemaId);
Assert.IsTrue(matchLayout.TryFind("thumbprint", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteFixed(ref row, ref matchScope, c, m.Thumbprint));
Assert.IsTrue(matchLayout.TryFind("score", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutFloat64>().WriteFixed(ref row, ref matchScope, c, m.Score));
}
private static SimilarMatch ReadSimilarMatch(ref RowBuffer row, ref RowCursor matchScope)
{
Layout matchLayout = matchScope.Layout;
SimilarMatch m = new SimilarMatch();
Assert.IsTrue(matchLayout.TryFind("thumbprint", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadFixed(ref row, ref matchScope, c, out m.Thumbprint));
Assert.IsTrue(matchLayout.TryFind("score", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutFloat64>().ReadFixed(ref row, ref matchScope, c, out m.Score));
return m;
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class Tagged
{
public string Title;
public List<string> Tags;
public List<int?> Options;
public List<List<double>> Ratings;
public List<SimilarMatch> Similars;
public List<Tuple<string, long>> Priority;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Tagged tagged && this.Equals(tagged);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Title?.GetHashCode() ?? 0;
hashCode = (hashCode * 397) ^ (this.Tags?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Options?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Ratings?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Similars?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Priority?.GetHashCode() ?? 0);
return hashCode;
}
}
private static bool NestedSequenceEquals<T>(List<List<T>> left, List<List<T>> right)
{
if (left.Count != right.Count)
{
return false;
}
for (int i = 0; i < left.Count; i++)
{
if (!left[i].SequenceEqual(right[i]))
{
return false;
}
}
return true;
}
private bool Equals(Tagged other)
{
return string.Equals(this.Title, other.Title) &&
(object.ReferenceEquals(this.Tags, other.Tags) ||
((this.Tags != null) && (other.Tags != null) && this.Tags.SequenceEqual(other.Tags))) &&
(object.ReferenceEquals(this.Options, other.Options) ||
((this.Options != null) && (other.Options != null) && this.Options.SequenceEqual(other.Options))) &&
(object.ReferenceEquals(this.Ratings, other.Ratings) ||
((this.Ratings != null) && (other.Ratings != null) && Tagged.NestedSequenceEquals(this.Ratings, other.Ratings))) &&
(object.ReferenceEquals(this.Similars, other.Similars) ||
((this.Similars != null) && (other.Similars != null) && this.Similars.SequenceEqual(other.Similars))) &&
(object.ReferenceEquals(this.Priority, other.Priority) ||
((this.Priority != null) && (other.Priority != null) && this.Priority.SequenceEqual(other.Priority)));
}
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class SimilarMatch
{
public string Thumbprint;
public double Score;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is SimilarMatch match && this.Equals(match);
}
public override int GetHashCode()
{
unchecked
{
return (this.Thumbprint.GetHashCode() * 397) ^ this.Score.GetHashCode();
}
}
private bool Equals(SimilarMatch other)
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
return this.Thumbprint == other.Thumbprint && this.Score == other.Score;
}
}
}
}

View File

@@ -0,0 +1,704 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[DeploymentItem(TypedMapUnitTests.SchemaFile, "TestData")]
public sealed class TypedMapUnitTests
{
private const string SchemaFile = @"TestData\MovieSchema.json";
private const int InitialRowSize = 2 * 1024 * 1024;
private Namespace counterSchema;
private LayoutResolver resolver;
private Layout layout;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(TypedMapUnitTests.SchemaFile);
this.counterSchema = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.counterSchema);
this.layout = this.resolver.Resolve(this.counterSchema.Schemas.Find(x => x.Name == "Movie").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void CreateMovies()
{
RowBuffer row = new RowBuffer(TypedMapUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
// ReSharper disable StringLiteralTypo
Movie t1 = new Movie
{
Cast = new Dictionary<string, string> { { "Mark", "Luke" }, { "Harrison", "Han" }, { "Carrie", "Leia" } },
Stats = new Dictionary<Guid, double>
{
{ Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"), 11000000.00 },
{ Guid.Parse("{7499C40E-7077-45C1-AE5F-3E384966B3B9}"), 1554475.00 },
},
Related = new Dictionary<string, Dictionary<long, string>>
{
{ "Mark", new Dictionary<long, string> { { 103359, "Joker" }, { 131646, "Merlin" } } },
{ "Harrison", new Dictionary<long, string> { { 0082971, "Indy" }, { 83658, "Deckard" } } },
},
Revenue = new Dictionary<DateTime, Earnings>
{
{ DateTime.Parse("05/25/1977"), new Earnings { Domestic = 307263857M, Worldwide = 100000M } },
{ DateTime.Parse("08/13/1982"), new Earnings { Domestic = 15476285M, Worldwide = 200000M } },
{ DateTime.Parse("01/31/1997"), new Earnings { Domestic = 138257865M, Worldwide = 300000M } },
},
};
// ReSharper restore StringLiteralTypo
this.WriteMovie(ref row, ref RowCursor.Create(ref row, out RowCursor _), t1);
Movie t2 = this.ReadMovie(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(t1, t2);
}
[TestMethod]
[Owner("jthunter")]
public void PreventUpdatesInNonUpdatableScope()
{
RowBuffer row = new RowBuffer(TypedMapUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
RowCursor root = RowCursor.Create(ref row);
// Write a map and then try to write directly into it.
Assert.IsTrue(this.layout.TryFind("cast", out LayoutColumn c));
root.Clone(out RowCursor mapScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref mapScope, c.TypeArgs, out mapScope));
ResultAssert.InsufficientPermissions(
TypedMapUnitTests.WriteKeyValue(ref row, ref mapScope, c.TypeArgs, KeyValuePair.Create("Mark", "Joker")));
root.Clone(out RowCursor tempCursor).Find(ref row, "cast.0");
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, KeyValuePair.Create("Mark", "Joker")));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor));
root.Clone(out tempCursor).Find(ref row, "cast.0");
ResultAssert.NotFound(TypedMapUnitTests.ReadKeyValue(ref row, ref tempCursor, c.TypeArgs, out KeyValuePair<string, string> _));
// Write a map of maps, successfully insert an empty map into it, and then try to write directly to the inner map.
Assert.IsTrue(this.layout.TryFind("related", out c));
root.Clone(out mapScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref mapScope, c.TypeArgs, out mapScope));
LayoutIndexedScope tupleLayout = c.TypeAs<LayoutUniqueScope>().FieldType(ref mapScope).TypeAs<LayoutIndexedScope>();
root.Clone(out tempCursor).Find(ref row, "related.0");
ResultAssert.IsSuccess(tupleLayout.WriteScope(ref row, ref tempCursor, c.TypeArgs, out RowCursor tupleScope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tupleScope, "Mark"));
Assert.IsTrue(tupleScope.MoveNext(ref row));
TypeArgument valueType = c.TypeArgs[1];
LayoutUniqueScope valueLayout = valueType.Type.TypeAs<LayoutUniqueScope>();
ResultAssert.IsSuccess(valueLayout.WriteScope(ref row, ref tupleScope, valueType.TypeArgs, out RowCursor innerScope));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor));
Assert.IsTrue(mapScope.MoveNext(ref row));
ResultAssert.IsSuccess(tupleLayout.ReadScope(ref row, ref mapScope, out tupleScope));
Assert.IsTrue(tupleScope.MoveNext(ref row));
// Skip key.
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(valueLayout.ReadScope(ref row, ref tupleScope, out innerScope));
TypeArgument itemType = valueType.TypeArgs[0];
Assert.IsFalse(innerScope.MoveNext(ref row));
ResultAssert.InsufficientPermissions(itemType.Type.TypeAs<LayoutInt64>().WriteSparse(ref row, ref innerScope, 1));
ResultAssert.InsufficientPermissions(itemType.Type.TypeAs<LayoutInt64>().DeleteSparse(ref row, ref innerScope));
}
[TestMethod]
[Owner("jthunter")]
public void PreventUniquenessViolations()
{
RowBuffer row = new RowBuffer(TypedMapUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
RowCursor root = RowCursor.Create(ref row);
Movie t1 = new Movie
{
Cast = new Dictionary<string, string> { { "Mark", "Luke" } },
Stats = new Dictionary<Guid, double>
{
{ Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"), 11000000.00 },
},
Related = new Dictionary<string, Dictionary<long, string>>
{
{ "Mark", new Dictionary<long, string> { { 103359, "Joker" } } },
},
Revenue = new Dictionary<DateTime, Earnings>
{
{ DateTime.Parse("05/25/1977"), new Earnings { Domestic = 307263857M, Worldwide = 100000M } },
},
};
RowCursor rc1 = RowCursor.Create(ref row);
this.WriteMovie(ref row, ref rc1, t1);
// Attempt to insert duplicate items in existing sets.
Assert.IsTrue(this.layout.TryFind("cast", out LayoutColumn c));
root.Clone(out RowCursor mapScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref mapScope, out mapScope));
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, KeyValuePair.Create("Mark", "Luke")));
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor, UpdateOptions.Insert));
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.NotFound(TypedMapUnitTests.ReadKeyValue(ref row, ref tempCursor, c.TypeArgs, out KeyValuePair<string, string> _));
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(
TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, KeyValuePair.Create("Mark", "Joker")));
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor, UpdateOptions.Insert));
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.NotFound(TypedMapUnitTests.ReadKeyValue(ref row, ref tempCursor, c.TypeArgs, out KeyValuePair<string, string> _));
Assert.IsTrue(this.layout.TryFind("stats", out c));
root.Clone(out mapScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref mapScope, out mapScope));
KeyValuePair<Guid, double> pair = KeyValuePair.Create(Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"), 11000000.00);
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, pair));
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor, UpdateOptions.Insert));
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.NotFound(TypedMapUnitTests.ReadKeyValue(ref row, ref tempCursor, c.TypeArgs, out pair));
}
[TestMethod]
[Owner("jthunter")]
public void FindInMap()
{
RowBuffer row = new RowBuffer(TypedMapUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
RowCursor root = RowCursor.Create(ref row);
Movie t1 = new Movie
{
Cast = new Dictionary<string, string> { { "Mark", "Luke" }, { "Harrison", "Han" }, { "Carrie", "Leia" } },
};
RowCursor rc1 = RowCursor.Create(ref row);
this.WriteMovie(ref row, ref rc1, t1);
// Attempt to find each item in turn.
Assert.IsTrue(this.layout.TryFind("cast", out LayoutColumn c));
root.Clone(out RowCursor mapScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref mapScope, out mapScope));
foreach (string key in t1.Cast.Keys)
{
KeyValuePair<string, string> pair = new KeyValuePair<string, string>(key, "map lookup matches only on key");
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, pair));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref mapScope, ref tempCursor, out RowCursor findScope));
ResultAssert.IsSuccess(
TypedMapUnitTests.ReadKeyValue(ref row, ref findScope, c.TypeArgs, out KeyValuePair<string, string> foundPair));
Assert.AreEqual(key, foundPair.Key, $"Failed to find t1.Cast[{key}]");
}
}
[TestMethod]
[Owner("jthunter")]
public void UpdateInMap()
{
RowBuffer row = new RowBuffer(TypedMapUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
RowCursor root = RowCursor.Create(ref row);
List<string> expected = new List<string> { "Mark", "Harrison", "Carrie", };
foreach (IEnumerable<string> permutation in expected.Permute())
{
Movie t1 = new Movie
{
Cast = new Dictionary<string, string> { { "Mark", "Luke" }, { "Harrison", "Han" }, { "Carrie", "Leia" } },
};
this.WriteMovie(ref row, ref root.Clone(out RowCursor _), t1);
// Attempt to find each item in turn and then delete it.
Assert.IsTrue(this.layout.TryFind("cast", out LayoutColumn c));
root.Clone(out RowCursor mapScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref mapScope, out mapScope));
foreach (string key in permutation)
{
// Verify it is already there.
KeyValuePair<string, string> pair = new KeyValuePair<string, string>(key, "map lookup matches only on key");
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, pair));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref mapScope, ref tempCursor, out RowCursor findScope));
// Insert it again with update.
KeyValuePair<string, string> updatePair = new KeyValuePair<string, string>(key, "update value");
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, updatePair));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor, UpdateOptions.Update));
// Verify that the value was updated.
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, pair));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref mapScope, ref tempCursor, out findScope));
ResultAssert.IsSuccess(
TypedMapUnitTests.ReadKeyValue(ref row, ref findScope, c.TypeArgs, out KeyValuePair<string, string> foundPair));
Assert.AreEqual(key, foundPair.Key);
Assert.AreEqual(updatePair.Value, foundPair.Value);
// Insert it again with upsert.
updatePair = new KeyValuePair<string, string>(key, "upsert value");
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, updatePair));
// ReSharper disable once RedundantArgumentDefaultValue
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor, UpdateOptions.Upsert));
// Verify that the value was upserted.
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, pair));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref mapScope, ref tempCursor, out findScope));
ResultAssert.IsSuccess(TypedMapUnitTests.ReadKeyValue(ref row, ref findScope, c.TypeArgs, out foundPair));
Assert.AreEqual(key, foundPair.Key);
Assert.AreEqual(updatePair.Value, foundPair.Value);
// Insert it again with insert (fail: exists).
updatePair = new KeyValuePair<string, string>(key, "insert value");
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, updatePair));
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor, UpdateOptions.Insert));
// Insert it again with insert at (fail: disallowed).
updatePair = new KeyValuePair<string, string>(key, "insertAt value");
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, updatePair));
ResultAssert.TypeConstraint(
c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref mapScope, ref tempCursor, UpdateOptions.InsertAt));
}
}
}
[TestMethod]
[Owner("jthunter")]
public void FindAndDelete()
{
RowBuffer row = new RowBuffer(TypedMapUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
RowCursor root = RowCursor.Create(ref row);
List<string> expected = new List<string> { "Mark", "Harrison", "Carrie", };
foreach (IEnumerable<string> permutation in expected.Permute())
{
Movie t1 = new Movie
{
Cast = new Dictionary<string, string> { { "Mark", "Luke" }, { "Harrison", "Han" }, { "Carrie", "Leia" } },
};
this.WriteMovie(ref row, ref root.Clone(out RowCursor _), t1);
// Attempt to find each item in turn and then delete it.
Assert.IsTrue(this.layout.TryFind("cast", out LayoutColumn c));
root.Clone(out RowCursor mapScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref mapScope, out mapScope));
foreach (string key in permutation)
{
KeyValuePair<string, string> pair = new KeyValuePair<string, string>(key, "map lookup matches only on key");
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, pair));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref mapScope, ref tempCursor, out RowCursor findScope));
TypeArgument tupleType = c.TypeAs<LayoutUniqueScope>().FieldType(ref mapScope);
ResultAssert.IsSuccess(tupleType.TypeAs<LayoutIndexedScope>().DeleteScope(ref row, ref findScope));
}
}
}
private static Result WriteKeyValue<TKey, TValue>(
ref RowBuffer row,
ref RowCursor scope,
TypeArgumentList typeArgs,
KeyValuePair<TKey, TValue> pair)
{
LayoutIndexedScope tupleLayout = LayoutType.TypedTuple;
Result r = tupleLayout.WriteScope(ref row, ref scope, typeArgs, out RowCursor tupleScope);
if (r != Result.Success)
{
return r;
}
r = typeArgs[0].Type.TypeAs<LayoutType<TKey>>().WriteSparse(ref row, ref tupleScope, pair.Key);
if (r != Result.Success)
{
return r;
}
tupleScope.MoveNext(ref row);
r = typeArgs[1].Type.TypeAs<LayoutType<TValue>>().WriteSparse(ref row, ref tupleScope, pair.Value);
if (r != Result.Success)
{
return r;
}
return Result.Success;
}
private static Result ReadKeyValue<TKey, TValue>(
ref RowBuffer row,
ref RowCursor scope,
TypeArgumentList typeArgs,
out KeyValuePair<TKey, TValue> pair)
{
pair = default;
LayoutIndexedScope tupleLayout = LayoutType.TypedTuple;
Result r = tupleLayout.ReadScope(ref row, ref scope, out RowCursor tupleScope);
if (r != Result.Success)
{
return r;
}
tupleScope.MoveNext(ref row);
r = typeArgs[0].Type.TypeAs<LayoutType<TKey>>().ReadSparse(ref row, ref tupleScope, out TKey key);
if (r != Result.Success)
{
return r;
}
tupleScope.MoveNext(ref row);
r = typeArgs[1].Type.TypeAs<LayoutType<TValue>>().ReadSparse(ref row, ref tupleScope, out TValue value);
if (r != Result.Success)
{
return r;
}
pair = new KeyValuePair<TKey, TValue>(key, value);
return Result.Success;
}
private void WriteMovie(ref RowBuffer row, ref RowCursor root, Movie value)
{
LayoutColumn c;
if (value.Cast != null)
{
Assert.IsTrue(this.layout.TryFind("cast", out c));
root.Clone(out RowCursor castScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref castScope, c.TypeArgs, out castScope));
foreach (KeyValuePair<string, string> item in value.Cast)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, item));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref castScope, ref tempCursor));
}
}
if (value.Stats != null)
{
Assert.IsTrue(this.layout.TryFind("stats", out c));
root.Clone(out RowCursor statsScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref statsScope, c.TypeArgs, out statsScope));
foreach (KeyValuePair<Guid, double> item in value.Stats)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor, c.TypeArgs, item));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref statsScope, ref tempCursor));
}
}
if (value.Related != null)
{
Assert.IsTrue(this.layout.TryFind("related", out c));
root.Clone(out RowCursor relatedScoped).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref relatedScoped, c.TypeArgs, out relatedScoped));
foreach (KeyValuePair<string, Dictionary<long, string>> item in value.Related)
{
Assert.IsTrue(item.Value != null);
LayoutIndexedScope tupleLayout = LayoutType.TypedTuple;
root.Clone(out RowCursor tempCursor1).Find(ref row, "related.0");
ResultAssert.IsSuccess(tupleLayout.WriteScope(ref row, ref tempCursor1, c.TypeArgs, out RowCursor tupleScope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tupleScope, item.Key));
Assert.IsTrue(tupleScope.MoveNext(ref row));
TypeArgument valueType = c.TypeArgs[1];
LayoutUniqueScope valueLayout = valueType.Type.TypeAs<LayoutUniqueScope>();
ResultAssert.IsSuccess(valueLayout.WriteScope(ref row, ref tupleScope, valueType.TypeArgs, out RowCursor innerScope));
foreach (KeyValuePair<long, string> innerItem in item.Value)
{
root.Clone(out RowCursor tempCursor2).Find(ref row, "related.0.0");
ResultAssert.IsSuccess(TypedMapUnitTests.WriteKeyValue(ref row, ref tempCursor2, valueType.TypeArgs, innerItem));
ResultAssert.IsSuccess(valueLayout.MoveField(ref row, ref innerScope, ref tempCursor2));
}
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref relatedScoped, ref tempCursor1));
}
}
if (value.Revenue != null)
{
Assert.IsTrue(this.layout.TryFind("revenue", out c));
root.Clone(out RowCursor revenueScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref revenueScope, c.TypeArgs, out revenueScope));
foreach (KeyValuePair<DateTime, Earnings> item in value.Revenue)
{
Assert.IsTrue(item.Value != null);
LayoutIndexedScope tupleLayout = LayoutType.TypedTuple;
root.Clone(out RowCursor tempCursor1).Find(ref row, "revenue.0");
ResultAssert.IsSuccess(tupleLayout.WriteScope(ref row, ref tempCursor1, c.TypeArgs, out RowCursor tupleScope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutDateTime>().WriteSparse(ref row, ref tupleScope, item.Key));
Assert.IsTrue(tupleScope.MoveNext(ref row));
TypeArgument valueType = c.TypeArgs[1];
LayoutUDT valueLayout = valueType.Type.TypeAs<LayoutUDT>();
ResultAssert.IsSuccess(valueLayout.WriteScope(ref row, ref tupleScope, valueType.TypeArgs, out RowCursor itemScope));
TypedMapUnitTests.WriteEarnings(ref row, ref itemScope, valueType.TypeArgs, item.Value);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref revenueScope, ref tempCursor1));
}
}
}
private Movie ReadMovie(ref RowBuffer row, ref RowCursor root)
{
Movie value = new Movie();
Assert.IsTrue(this.layout.TryFind("cast", out LayoutColumn c));
root.Clone(out RowCursor castScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref castScope, out castScope) == Result.Success)
{
value.Cast = new Dictionary<string, string>();
while (castScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(TypedMapUnitTests.ReadKeyValue(ref row, ref castScope, c.TypeArgs, out KeyValuePair<string, string> item));
value.Cast.Add(item.Key, item.Value);
}
}
Assert.IsTrue(this.layout.TryFind("stats", out c));
root.Clone(out RowCursor statsScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref statsScope, out statsScope) == Result.Success)
{
value.Stats = new Dictionary<Guid, double>();
while (statsScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(TypedMapUnitTests.ReadKeyValue(ref row, ref statsScope, c.TypeArgs, out KeyValuePair<Guid, double> item));
value.Stats.Add(item.Key, item.Value);
}
}
Assert.IsTrue(this.layout.TryFind("related", out c));
root.Clone(out RowCursor relatedScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref relatedScope, out relatedScope) == Result.Success)
{
value.Related = new Dictionary<string, Dictionary<long, string>>();
TypeArgument keyType = c.TypeArgs[0];
TypeArgument valueType = c.TypeArgs[1];
LayoutUtf8 keyLayout = keyType.Type.TypeAs<LayoutUtf8>();
LayoutUniqueScope valueLayout = valueType.Type.TypeAs<LayoutUniqueScope>();
while (relatedScope.MoveNext(ref row))
{
LayoutIndexedScope tupleLayout = LayoutType.TypedTuple;
ResultAssert.IsSuccess(tupleLayout.ReadScope(ref row, ref relatedScope, out RowCursor tupleScope));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(keyLayout.ReadSparse(ref row, ref tupleScope, out string itemKey));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(valueLayout.ReadScope(ref row, ref tupleScope, out RowCursor itemValueScope));
Dictionary<long, string> itemValue = new Dictionary<long, string>();
while (itemValueScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(
TypedMapUnitTests.ReadKeyValue(
ref row,
ref itemValueScope,
valueType.TypeArgs,
out KeyValuePair<long, string> innerItem));
itemValue.Add(innerItem.Key, innerItem.Value);
}
value.Related.Add(itemKey, itemValue);
}
}
Assert.IsTrue(this.layout.TryFind("revenue", out c));
root.Clone(out RowCursor revenueScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref revenueScope, out revenueScope) == Result.Success)
{
value.Revenue = new Dictionary<DateTime, Earnings>();
TypeArgument keyType = c.TypeArgs[0];
TypeArgument valueType = c.TypeArgs[1];
LayoutDateTime keyLayout = keyType.Type.TypeAs<LayoutDateTime>();
LayoutUDT valueLayout = valueType.Type.TypeAs<LayoutUDT>();
while (revenueScope.MoveNext(ref row))
{
LayoutIndexedScope tupleLayout = LayoutType.TypedTuple;
ResultAssert.IsSuccess(tupleLayout.ReadScope(ref row, ref revenueScope, out RowCursor tupleScope));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(keyLayout.ReadSparse(ref row, ref tupleScope, out DateTime itemKey));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(valueLayout.ReadScope(ref row, ref tupleScope, out RowCursor itemValueScope));
Earnings itemValue = TypedMapUnitTests.ReadEarnings(ref row, ref itemValueScope);
value.Revenue.Add(itemKey, itemValue);
}
}
return value;
}
private static void WriteEarnings(ref RowBuffer row, ref RowCursor udtScope, TypeArgumentList typeArgs, Earnings m)
{
Layout udt = row.Resolver.Resolve(typeArgs.SchemaId);
Assert.IsTrue(udt.TryFind("domestic", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutDecimal>().WriteFixed(ref row, ref udtScope, c, m.Domestic));
Assert.IsTrue(udt.TryFind("worldwide", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutDecimal>().WriteFixed(ref row, ref udtScope, c, m.Worldwide));
}
private static Earnings ReadEarnings(ref RowBuffer row, ref RowCursor udtScope)
{
Layout udt = udtScope.Layout;
Earnings m = new Earnings();
Assert.IsTrue(udt.TryFind("domestic", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutDecimal>().ReadFixed(ref row, ref udtScope, c, out m.Domestic));
Assert.IsTrue(udt.TryFind("worldwide", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutDecimal>().ReadFixed(ref row, ref udtScope, c, out m.Worldwide));
return m;
}
private static class KeyValuePair
{
public static KeyValuePair<TKey, TValue> Create<TKey, TValue>(TKey key, TValue value)
{
return new KeyValuePair<TKey, TValue>(key, value);
}
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class Movie
{
public Dictionary<string, string> Cast;
public Dictionary<Guid, double> Stats;
public Dictionary<string, Dictionary<long, string>> Related;
public Dictionary<DateTime, Earnings> Revenue;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Movie movie && this.Equals(movie);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = 0;
hashCode = (hashCode * 397) ^ (this.Cast?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Stats?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Related?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Revenue?.GetHashCode() ?? 0);
return hashCode;
}
}
private static bool NestedMapEquals<TKey1, TKey2, TValue>(
Dictionary<TKey1, Dictionary<TKey2, TValue>> left,
Dictionary<TKey1, Dictionary<TKey2, TValue>> right)
{
if (left.Count != right.Count)
{
return false;
}
foreach (KeyValuePair<TKey1, Dictionary<TKey2, TValue>> item in left)
{
if (!right.TryGetValue(item.Key, out Dictionary<TKey2, TValue> value))
{
return false;
}
if (!Movie.MapEquals(item.Value, value))
{
return false;
}
}
return true;
}
private static bool MapEquals<TKey, TValue>(Dictionary<TKey, TValue> left, Dictionary<TKey, TValue> right)
{
if (left.Count != right.Count)
{
return false;
}
foreach (KeyValuePair<TKey, TValue> item in left)
{
if (!right.TryGetValue(item.Key, out TValue value))
{
return false;
}
if (!item.Value.Equals(value))
{
return false;
}
}
return true;
}
private bool Equals(Movie other)
{
return (object.ReferenceEquals(this.Cast, other.Cast) ||
((this.Cast != null) && (other.Cast != null) && Movie.MapEquals(this.Cast, other.Cast))) &&
(object.ReferenceEquals(this.Stats, other.Stats) ||
((this.Stats != null) && (other.Stats != null) && Movie.MapEquals(this.Stats, other.Stats))) &&
(object.ReferenceEquals(this.Related, other.Related) ||
((this.Related != null) && (other.Related != null) && Movie.NestedMapEquals(this.Related, other.Related))) &&
(object.ReferenceEquals(this.Revenue, other.Revenue) ||
((this.Revenue != null) && (other.Revenue != null) && Movie.MapEquals(this.Revenue, other.Revenue)));
}
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class Earnings
{
public decimal Domestic;
public decimal Worldwide;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Earnings earnings && this.Equals(earnings);
}
public override int GetHashCode()
{
unchecked
{
return (this.Domestic.GetHashCode() * 397) ^ this.Worldwide.GetHashCode();
}
}
private bool Equals(Earnings other)
{
return this.Domestic == other.Domestic && this.Worldwide == other.Worldwide;
}
}
}
}

View File

@@ -0,0 +1,877 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[DeploymentItem(TypedSetUnitTests.SchemaFile, "TestData")]
public sealed class TypedSetUnitTests
{
private const string SchemaFile = @"TestData\TodoSchema.json";
private const int InitialRowSize = 2 * 1024 * 1024;
private Namespace counterSchema;
private LayoutResolver resolver;
private Layout layout;
[TestInitialize]
public void ParseNamespaceExample()
{
string json = File.ReadAllText(TypedSetUnitTests.SchemaFile);
this.counterSchema = Namespace.Parse(json);
this.resolver = new LayoutResolverNamespace(this.counterSchema);
this.layout = this.resolver.Resolve(this.counterSchema.Schemas.Find(x => x.Name == "Todo").SchemaId);
}
[TestMethod]
[Owner("jthunter")]
public void CreateTodos()
{
RowBuffer row = new RowBuffer(TypedSetUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
Todo t1 = new Todo
{
Attendees = new List<string> { "jason", "janice", "joshua" },
Projects = new List<Guid>
{
Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"),
Guid.Parse("{7499C40E-7077-45C1-AE5F-3E384966B3B9}"),
Guid.Parse("{B7BC39C2-1A2D-4EAF-8F33-ED976872D876}"),
Guid.Parse("{DEA71ABE-3041-4CAF-BBD9-1A46D10832A0}"),
},
Checkboxes = new List<bool> { true, false },
Prices = new List<List<float>>
{
new List<float> { 1.2F, 3.0F },
new List<float> { 4.1F, 5.7F },
new List<float> { 7.3F, 8.12F, 9.14F },
},
Nested = new List<List<List<int>>>
{
new List<List<int>> { new List<int> { 1, 2 } },
new List<List<int>> { new List<int> { 3, 4 } },
new List<List<int>> { new List<int> { 5, 6 } },
},
Shopping = new List<ShoppingItem>
{
new ShoppingItem { Label = "milk", Count = 1 },
new ShoppingItem { Label = "broccoli", Count = 2 },
new ShoppingItem { Label = "steak", Count = 6 },
},
Work = new List<Tuple<bool, ulong>>
{
Tuple.Create(false, 10000UL),
Tuple.Create(true, 49053UL),
Tuple.Create(false, 53111UL),
},
};
this.WriteTodo(ref row, ref RowCursor.Create(ref row, out RowCursor _), t1);
Todo t2 = this.ReadTodo(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(t1, t2);
}
[TestMethod]
[Owner("jthunter")]
public void PreventUpdatesInNonUpdatableScope()
{
RowBuffer row = new RowBuffer(TypedSetUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
// Write a set and then try to write directly into it.
Assert.IsTrue(this.layout.TryFind("attendees", out LayoutColumn c));
RowCursor.Create(ref row, out RowCursor setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref setScope, c.TypeArgs, out setScope));
ResultAssert.InsufficientPermissions(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref setScope, "foo"));
RowCursor.Create(ref row, out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tempCursor, "foo"));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor));
ResultAssert.InsufficientPermissions(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref setScope, "foo"));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().DeleteSparse(ref row, ref setScope));
// Write a set of sets, successfully insert an empty set into it, and then try to write directly to the inner set.
Assert.IsTrue(this.layout.TryFind("prices", out c));
RowCursor.Create(ref row, out setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref setScope, c.TypeArgs, out setScope));
TypeArgument innerType = c.TypeArgs[0];
TypeArgument itemType = innerType.TypeArgs[0];
LayoutUniqueScope innerLayout = innerType.Type.TypeAs<LayoutUniqueScope>();
RowCursor.Create(ref row, out RowCursor tempCursor1).Find(ref row, "prices.0");
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref tempCursor1, innerType.TypeArgs, out RowCursor innerScope));
RowCursor.Create(ref row, out RowCursor tempCursor2).Find(ref row, "prices.0.0");
ResultAssert.IsSuccess(itemType.Type.TypeAs<LayoutFloat32>().WriteSparse(ref row, ref tempCursor2, 1.0F));
ResultAssert.IsSuccess(innerLayout.MoveField(ref row, ref innerScope, ref tempCursor2));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor1));
Assert.IsTrue(setScope.MoveNext(ref row));
ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref setScope, out innerScope));
ResultAssert.InsufficientPermissions(itemType.Type.TypeAs<LayoutFloat32>().WriteSparse(ref row, ref innerScope, 1.0F));
ResultAssert.InsufficientPermissions(itemType.Type.TypeAs<LayoutFloat32>().DeleteSparse(ref row, ref innerScope));
}
[TestMethod]
[Owner("jthunter")]
public void PreventUniquenessViolations()
{
RowBuffer row = new RowBuffer(TypedSetUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
Todo t1 = new Todo
{
Attendees = new List<string> { "jason" },
Projects = new List<Guid>
{
Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"),
},
Prices = new List<List<float>>
{
new List<float> { 1.2F, 3.0F },
},
Shopping = new List<ShoppingItem>
{
new ShoppingItem { Label = "milk", Count = 1 },
},
Work = new List<Tuple<bool, ulong>>
{
Tuple.Create(false, 10000UL),
},
};
this.WriteTodo(ref row, ref RowCursor.Create(ref row, out RowCursor _), t1);
// Attempt to insert duplicate items in existing sets.
RowCursor root = RowCursor.Create(ref row);
Assert.IsTrue(this.layout.TryFind("attendees", out LayoutColumn c));
root.Clone(out RowCursor setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tempCursor, t1.Attendees[0]));
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor, UpdateOptions.Insert));
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tempCursor, t1.Attendees[0]));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref setScope, ref tempCursor, out RowCursor _));
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.NotFound(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref tempCursor, out string _));
Assert.IsTrue(this.layout.TryFind("projects", out c));
root.Clone(out setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, t1.Projects[0]));
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor, UpdateOptions.Insert));
// Attempt to move a duplicate set into a set of sets.
Assert.IsTrue(this.layout.TryFind("prices", out c));
root.Clone(out setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
TypeArgument innerType = c.TypeArgs[0];
LayoutUniqueScope innerLayout = innerType.Type.TypeAs<LayoutUniqueScope>();
root.Clone(out RowCursor tempCursor1).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref tempCursor1, innerType.TypeArgs, out RowCursor innerScope));
foreach (float innerItem in t1.Prices[0])
{
LayoutFloat32 itemLayout = innerType.TypeArgs[0].Type.TypeAs<LayoutFloat32>();
root.Clone(out RowCursor tempCursor2).Find(ref row, "prices.0.0");
ResultAssert.IsSuccess(itemLayout.WriteSparse(ref row, ref tempCursor2, innerItem));
ResultAssert.IsSuccess(innerLayout.MoveField(ref row, ref innerScope, ref tempCursor2));
}
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor1, UpdateOptions.Insert));
// Attempt to move a duplicate UDT into a set of UDT.
Assert.IsTrue(this.layout.TryFind("shopping", out c));
root.Clone(out setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
LayoutUDT udtLayout = c.TypeArgs[0].Type.TypeAs<LayoutUDT>();
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(udtLayout.WriteScope(ref row, ref tempCursor, c.TypeArgs[0].TypeArgs, out RowCursor udtScope));
TypedSetUnitTests.WriteShoppingItem(ref row, ref udtScope, c.TypeArgs[0].TypeArgs, t1.Shopping[0]);
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor, UpdateOptions.Insert));
// Attempt to move a duplicate tuple into a set of tuple.
Assert.IsTrue(this.layout.TryFind("work", out c));
root.Clone(out setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
innerType = c.TypeArgs[0];
LayoutIndexedScope tupleLayout = innerType.Type.TypeAs<LayoutIndexedScope>();
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(tupleLayout.WriteScope(ref row, ref tempCursor, innerType.TypeArgs, out RowCursor tupleScope));
ResultAssert.IsSuccess(innerType.TypeArgs[0].Type.TypeAs<LayoutBoolean>().WriteSparse(ref row, ref tupleScope, t1.Work[0].Item1));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(innerType.TypeArgs[1].Type.TypeAs<LayoutVarUInt>().WriteSparse(ref row, ref tupleScope, t1.Work[0].Item2));
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor, UpdateOptions.Insert));
}
[TestMethod]
[Owner("jthunter")]
public void FindInSet()
{
RowBuffer row = new RowBuffer(TypedSetUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
Todo t1 = new Todo
{
Attendees = new List<string> { "jason", "janice", "joshua" },
Prices = new List<List<float>>
{
new List<float> { 1.2F, 3.0F },
new List<float> { 4.1F, 5.7F },
new List<float> { 7.3F, 8.12F, 9.14F },
},
};
this.WriteTodo(ref row, ref RowCursor.Create(ref row, out RowCursor _), t1);
// Attempt to find each item in turn.
RowCursor root = RowCursor.Create(ref row);
Assert.IsTrue(this.layout.TryFind("attendees", out LayoutColumn c));
root.Clone(out RowCursor setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
for (int i = 0; i < t1.Attendees.Count; i++)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tempCursor, t1.Attendees[i]));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref setScope, ref tempCursor, out RowCursor findScope));
Assert.AreEqual(i, findScope.Index, $"Failed to find t1.Attendees[{i}]");
}
Assert.IsTrue(this.layout.TryFind("prices", out c));
root.Clone(out setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
TypeArgument innerType = c.TypeArgs[0];
TypeArgument itemType = innerType.TypeArgs[0];
LayoutUniqueScope innerLayout = innerType.Type.TypeAs<LayoutUniqueScope>();
for (int i = 0; i < t1.Prices.Count; i++)
{
root.Clone(out RowCursor tempCursor1).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref tempCursor1, innerType.TypeArgs, out RowCursor innerScope));
for (int j = 0; j < t1.Prices[i].Count; j++)
{
root.Clone(out RowCursor tempCursor2).Find(ref row, "prices.0.0");
ResultAssert.IsSuccess(itemType.Type.TypeAs<LayoutFloat32>().WriteSparse(ref row, ref tempCursor2, t1.Prices[i][j]));
ResultAssert.IsSuccess(innerLayout.MoveField(ref row, ref innerScope, ref tempCursor2));
}
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref setScope, ref tempCursor1, out RowCursor findScope));
Assert.AreEqual(i, findScope.Index, $"Failed to find t1.Prices[{i}]");
}
}
[TestMethod]
[Owner("jthunter")]
public void UpdateInSet()
{
RowBuffer row = new RowBuffer(TypedSetUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
List<Guid> expected = new List<Guid>
{
Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"),
Guid.Parse("{7499C40E-7077-45C1-AE5F-3E384966B3B9}"),
Guid.Parse("{B7BC39C2-1A2D-4EAF-8F33-ED976872D876}"),
Guid.Parse("{DEA71ABE-3041-4CAF-BBD9-1A46D10832A0}"),
};
foreach (IEnumerable<Guid> permutation in expected.Permute())
{
Todo t1 = new Todo
{
Projects = new List<Guid>(permutation),
};
this.WriteTodo(ref row, ref RowCursor.Create(ref row, out RowCursor _), t1);
// Attempt to find each item in turn and then delete it.
RowCursor root = RowCursor.Create(ref row);
Assert.IsTrue(this.layout.TryFind("projects", out LayoutColumn c));
root.Clone(out RowCursor setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
foreach (Guid elm in t1.Projects)
{
// Verify it is already there.
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, elm));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref setScope, ref tempCursor, value: out RowCursor _));
// Insert it again with update.
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, elm));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor, UpdateOptions.Update));
// Insert it again with upsert.
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, elm));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor));
// Insert it again with insert (fail: exists).
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, elm));
ResultAssert.Exists(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor, UpdateOptions.Insert));
// Insert it again with insert at (fail: disallowed).
root.Clone(out tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, elm));
ResultAssert.TypeConstraint(
c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref setScope, ref tempCursor, UpdateOptions.InsertAt));
}
}
}
[TestMethod]
[Owner("jthunter")]
public void FindAndDelete()
{
RowBuffer row = new RowBuffer(TypedSetUnitTests.InitialRowSize);
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
List<Guid> expected = new List<Guid>
{
Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"),
Guid.Parse("{7499C40E-7077-45C1-AE5F-3E384966B3B9}"),
Guid.Parse("{B7BC39C2-1A2D-4EAF-8F33-ED976872D876}"),
Guid.Parse("{DEA71ABE-3041-4CAF-BBD9-1A46D10832A0}"),
};
foreach (IEnumerable<Guid> permutation in expected.Permute())
{
Todo t1 = new Todo
{
Projects = new List<Guid>(permutation),
};
this.WriteTodo(ref row, ref RowCursor.Create(ref row, out RowCursor _), t1);
// Attempt to update each item in turn and then update it with itself.
RowCursor root = RowCursor.Create(ref row);
Assert.IsTrue(this.layout.TryFind("projects", out LayoutColumn c));
root.Clone(out RowCursor setScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref setScope, out setScope));
foreach (Guid p in t1.Projects)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, p));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().Find(ref row, ref setScope, ref tempCursor, out RowCursor findScope));
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().DeleteSparse(ref row, ref findScope));
}
}
}
[TestMethod]
[Owner("jthunter")]
public void RowWriterTest()
{
RowBuffer row = new RowBuffer(TypedSetUnitTests.InitialRowSize);
List<Guid> expected = new List<Guid>
{
Guid.Parse("{4674962B-CE11-4916-81C5-0421EE36F168}"),
Guid.Parse("{7499C40E-7077-45C1-AE5F-3E384966B3B9}"),
Guid.Parse("{B7BC39C2-1A2D-4EAF-8F33-ED976872D876}"),
Guid.Parse("{DEA71ABE-3041-4CAF-BBD9-1A46D10832A0}"),
};
foreach (IEnumerable<Guid> permutation in expected.Permute())
{
Todo t1 = new Todo
{
Projects = new List<Guid>(permutation),
};
row.InitLayout(HybridRowVersion.V1, this.layout, this.resolver);
ResultAssert.IsSuccess(RowWriter.WriteBuffer(ref row, t1, TypedSetUnitTests.SerializeTodo));
// Update the existing Set by updating each item with itself. This ensures that the RowWriter has
// maintained the unique index correctly.
Assert.IsTrue(this.layout.TryFind("projects", out LayoutColumn c));
RowCursor.Create(ref row, out RowCursor root);
root.Clone(out RowCursor projScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref projScope, out projScope));
foreach (Guid item in t1.Projects)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, item));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref projScope, ref tempCursor));
}
Todo t2 = this.ReadTodo(ref row, ref RowCursor.Create(ref row, out RowCursor _));
Assert.AreEqual(t1, t2);
}
}
private static Result SerializeTodo(ref RowWriter writer, TypeArgument typeArg, Todo value)
{
if (value.Projects != null)
{
Assert.IsTrue(writer.Layout.TryFind("projects", out LayoutColumn c));
Result r = writer.WriteScope(
"projects",
c.TypeArg,
value.Projects,
(ref RowWriter writer2, TypeArgument typeArg2, List<Guid> value2) =>
{
foreach (Guid item in value2)
{
ResultAssert.IsSuccess(writer2.WriteGuid(null, item));
}
return Result.Success;
});
if (r != Result.Success)
{
return r;
}
}
return Result.Success;
}
private void WriteTodo(ref RowBuffer row, ref RowCursor root, Todo value)
{
LayoutColumn c;
if (value.Attendees != null)
{
Assert.IsTrue(this.layout.TryFind("attendees", out c));
root.Clone(out RowCursor attendScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref attendScope, c.TypeArgs, out attendScope));
foreach (string item in value.Attendees)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().WriteSparse(ref row, ref tempCursor, item));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref attendScope, ref tempCursor));
}
}
if (value.Projects != null)
{
Assert.IsTrue(this.layout.TryFind("projects", out c));
root.Clone(out RowCursor projScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref projScope, c.TypeArgs, out projScope));
foreach (Guid item in value.Projects)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().WriteSparse(ref row, ref tempCursor, item));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref projScope, ref tempCursor));
}
}
if (value.Checkboxes != null)
{
Assert.IsTrue(this.layout.TryFind("checkboxes", out c));
root.Clone(out RowCursor checkboxScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref checkboxScope, c.TypeArgs, out checkboxScope));
foreach (bool item in value.Checkboxes)
{
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutBoolean>().WriteSparse(ref row, ref tempCursor, item));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref checkboxScope, ref tempCursor));
}
}
if (value.Prices != null)
{
Assert.IsTrue(this.layout.TryFind("prices", out c));
root.Clone(out RowCursor pricesScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref pricesScope, c.TypeArgs, out pricesScope));
foreach (List<float> item in value.Prices)
{
Assert.IsTrue(item != null);
TypeArgument innerType = c.TypeArgs[0];
LayoutUniqueScope innerLayout = innerType.Type.TypeAs<LayoutUniqueScope>();
root.Clone(out RowCursor tempCursor1).Find(ref row, "prices.0");
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref tempCursor1, innerType.TypeArgs, out RowCursor innerScope));
foreach (float innerItem in item)
{
LayoutFloat32 itemLayout = innerType.TypeArgs[0].Type.TypeAs<LayoutFloat32>();
root.Clone(out RowCursor tempCursor2).Find(ref row, "prices.0.0");
ResultAssert.IsSuccess(itemLayout.WriteSparse(ref row, ref tempCursor2, innerItem));
ResultAssert.IsSuccess(innerLayout.MoveField(ref row, ref innerScope, ref tempCursor2));
}
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref pricesScope, ref tempCursor1));
}
}
if (value.Nested != null)
{
Assert.IsTrue(this.layout.TryFind("nested", out c));
root.Clone(out RowCursor nestedScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref nestedScope, c.TypeArgs, out nestedScope));
foreach (List<List<int>> item in value.Nested)
{
Assert.IsTrue(item != null);
TypeArgument in2Type = c.TypeArgs[0];
LayoutUniqueScope in2Layout = in2Type.Type.TypeAs<LayoutUniqueScope>();
root.Clone(out RowCursor tempCursor1).Find(ref row, "prices.0");
ResultAssert.IsSuccess(in2Layout.WriteScope(ref row, ref tempCursor1, in2Type.TypeArgs, out RowCursor in2Scope));
foreach (List<int> item2 in item)
{
Assert.IsTrue(item2 != null);
TypeArgument in3Type = in2Type.TypeArgs[0];
LayoutUniqueScope in3Layout = in3Type.Type.TypeAs<LayoutUniqueScope>();
root.Clone(out RowCursor tempCursor2).Find(ref row, "prices.0.0");
ResultAssert.IsSuccess(in3Layout.WriteScope(ref row, ref tempCursor2, in3Type.TypeArgs, out RowCursor in3Scope));
foreach (int innerItem in item2)
{
LayoutInt32 itemLayout = in3Type.TypeArgs[0].Type.TypeAs<LayoutInt32>();
root.Clone(out RowCursor tempCursor3).Find(ref row, "prices.0.0.0");
ResultAssert.IsSuccess(itemLayout.WriteSparse(ref row, ref tempCursor3, innerItem));
ResultAssert.IsSuccess(in3Layout.MoveField(ref row, ref in3Scope, ref tempCursor3));
}
ResultAssert.IsSuccess(in2Layout.MoveField(ref row, ref in2Scope, ref tempCursor2));
}
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref nestedScope, ref tempCursor1));
}
}
if (value.Shopping != null)
{
Assert.IsTrue(this.layout.TryFind("shopping", out c));
root.Clone(out RowCursor shoppingScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref shoppingScope, c.TypeArgs, out shoppingScope));
foreach (ShoppingItem item in value.Shopping)
{
TypeArgument innerType = c.TypeArgs[0];
LayoutUDT innerLayout = innerType.Type.TypeAs<LayoutUDT>();
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref tempCursor, innerType.TypeArgs, out RowCursor itemScope));
TypedSetUnitTests.WriteShoppingItem(ref row, ref itemScope, innerType.TypeArgs, item);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref shoppingScope, ref tempCursor));
}
}
if (value.Work != null)
{
Assert.IsTrue(this.layout.TryFind("work", out c));
root.Clone(out RowCursor workScope).Find(ref row, c.Path);
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().WriteScope(ref row, ref workScope, c.TypeArgs, out workScope));
foreach (Tuple<bool, ulong> item in value.Work)
{
TypeArgument innerType = c.TypeArgs[0];
LayoutIndexedScope innerLayout = innerType.Type.TypeAs<LayoutIndexedScope>();
root.Clone(out RowCursor tempCursor).Find(ref row, Utf8String.Empty);
ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref tempCursor, innerType.TypeArgs, out RowCursor tupleScope));
ResultAssert.IsSuccess(innerType.TypeArgs[0].Type.TypeAs<LayoutBoolean>().WriteSparse(ref row, ref tupleScope, item.Item1));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(innerType.TypeArgs[1].Type.TypeAs<LayoutVarUInt>().WriteSparse(ref row, ref tupleScope, item.Item2));
ResultAssert.IsSuccess(c.TypeAs<LayoutUniqueScope>().MoveField(ref row, ref workScope, ref tempCursor));
}
}
}
private Todo ReadTodo(ref RowBuffer row, ref RowCursor root)
{
Todo value = new Todo();
Assert.IsTrue(this.layout.TryFind("attendees", out LayoutColumn c));
root.Clone(out RowCursor tagsScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref tagsScope, out tagsScope) == Result.Success)
{
value.Attendees = new List<string>();
while (tagsScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutUtf8>().ReadSparse(ref row, ref tagsScope, out string item));
value.Attendees.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("projects", out c));
root.Clone(out RowCursor projScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref projScope, out projScope) == Result.Success)
{
value.Projects = new List<Guid>();
while (projScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutGuid>().ReadSparse(ref row, ref projScope, out Guid item));
value.Projects.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("checkboxes", out c));
root.Clone(out RowCursor checkboxScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref checkboxScope, out checkboxScope) == Result.Success)
{
value.Checkboxes = new List<bool>();
while (checkboxScope.MoveNext(ref row))
{
ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs<LayoutBoolean>().ReadSparse(ref row, ref checkboxScope, out bool item));
value.Checkboxes.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("prices", out c));
root.Clone(out RowCursor pricesScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref pricesScope, out pricesScope) == Result.Success)
{
value.Prices = new List<List<float>>();
TypeArgument innerType = c.TypeArgs[0];
LayoutUniqueScope innerLayout = innerType.Type.TypeAs<LayoutUniqueScope>();
while (pricesScope.MoveNext(ref row))
{
List<float> item = new List<float>();
ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref pricesScope, out RowCursor innerScope));
while (innerScope.MoveNext(ref row))
{
LayoutFloat32 itemLayout = innerType.TypeArgs[0].Type.TypeAs<LayoutFloat32>();
ResultAssert.IsSuccess(itemLayout.ReadSparse(ref row, ref innerScope, out float innerItem));
item.Add(innerItem);
}
value.Prices.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("nested", out c));
root.Clone(out RowCursor nestedScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref nestedScope, out nestedScope) == Result.Success)
{
value.Nested = new List<List<List<int>>>();
TypeArgument in2Type = c.TypeArgs[0];
LayoutUniqueScope in2Layout = in2Type.Type.TypeAs<LayoutUniqueScope>();
while (nestedScope.MoveNext(ref row))
{
List<List<int>> item = new List<List<int>>();
ResultAssert.IsSuccess(in2Layout.ReadScope(ref row, ref nestedScope, out RowCursor in2Scope));
while (in2Scope.MoveNext(ref row))
{
TypeArgument in3Type = in2Type.TypeArgs[0];
LayoutUniqueScope in3Layout = in3Type.Type.TypeAs<LayoutUniqueScope>();
List<int> item2 = new List<int>();
ResultAssert.IsSuccess(in3Layout.ReadScope(ref row, ref in2Scope, out RowCursor in3Scope));
while (in3Scope.MoveNext(ref row))
{
LayoutInt32 itemLayout = in3Type.TypeArgs[0].Type.TypeAs<LayoutInt32>();
ResultAssert.IsSuccess(itemLayout.ReadSparse(ref row, ref in3Scope, out int innerItem));
item2.Add(innerItem);
}
item.Add(item2);
}
value.Nested.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("shopping", out c));
root.Clone(out RowCursor shoppingScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref shoppingScope, out shoppingScope) == Result.Success)
{
value.Shopping = new List<ShoppingItem>();
while (shoppingScope.MoveNext(ref row))
{
TypeArgument innerType = c.TypeArgs[0];
LayoutUDT innerLayout = innerType.Type.TypeAs<LayoutUDT>();
ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref shoppingScope, out RowCursor matchScope));
ShoppingItem item = TypedSetUnitTests.ReadShoppingItem(ref row, ref matchScope);
value.Shopping.Add(item);
}
}
Assert.IsTrue(this.layout.TryFind("work", out c));
root.Clone(out RowCursor workScope).Find(ref row, c.Path);
if (c.TypeAs<LayoutUniqueScope>().ReadScope(ref row, ref workScope, out workScope) == Result.Success)
{
value.Work = new List<Tuple<bool, ulong>>();
while (workScope.MoveNext(ref row))
{
TypeArgument innerType = c.TypeArgs[0];
LayoutIndexedScope innerLayout = innerType.Type.TypeAs<LayoutIndexedScope>();
ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref workScope, out RowCursor tupleScope));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(innerType.TypeArgs[0].Type.TypeAs<LayoutBoolean>().ReadSparse(ref row, ref tupleScope, out bool item1));
Assert.IsTrue(tupleScope.MoveNext(ref row));
ResultAssert.IsSuccess(innerType.TypeArgs[1].Type.TypeAs<LayoutVarUInt>().ReadSparse(ref row, ref tupleScope, out ulong item2));
value.Work.Add(Tuple.Create(item1, item2));
}
}
return value;
}
private static void WriteShoppingItem(ref RowBuffer row, ref RowCursor matchScope, TypeArgumentList typeArgs, ShoppingItem m)
{
Layout matchLayout = row.Resolver.Resolve(typeArgs.SchemaId);
Assert.IsTrue(matchLayout.TryFind("label", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().WriteVariable(ref row, ref matchScope, c, m.Label));
Assert.IsTrue(matchLayout.TryFind("count", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUInt8>().WriteFixed(ref row, ref matchScope, c, m.Count));
}
private static ShoppingItem ReadShoppingItem(ref RowBuffer row, ref RowCursor matchScope)
{
Layout matchLayout = matchScope.Layout;
ShoppingItem m = new ShoppingItem();
Assert.IsTrue(matchLayout.TryFind("label", out LayoutColumn c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUtf8>().ReadVariable(ref row, ref matchScope, c, out m.Label));
Assert.IsTrue(matchLayout.TryFind("count", out c));
ResultAssert.IsSuccess(c.TypeAs<LayoutUInt8>().ReadFixed(ref row, ref matchScope, c, out m.Count));
return m;
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class Todo
{
public List<string> Attendees;
public List<Guid> Projects;
public List<bool> Checkboxes;
public List<List<float>> Prices;
public List<List<List<int>>> Nested;
public List<ShoppingItem> Shopping;
public List<Tuple<bool, ulong>> Work;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is Todo todo && this.Equals(todo);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = 0;
hashCode = (hashCode * 397) ^ (this.Attendees?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Projects?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Checkboxes?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Prices?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Nested?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Shopping?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (this.Work?.GetHashCode() ?? 0);
return hashCode;
}
}
private static bool NestedNestedSetEquals<T>(List<List<List<T>>> left, List<List<List<T>>> right)
{
if (left.Count != right.Count)
{
return false;
}
for (int i = 0; i < left.Count; i++)
{
if (!Todo.NestedSetEquals(left[i], right[i]))
{
return false;
}
}
return true;
}
private static bool NestedSetEquals<T>(List<List<T>> left, List<List<T>> right)
{
if (left.Count != right.Count)
{
return false;
}
for (int i = 0; i < left.Count; i++)
{
if (!Todo.SetEquals(left[i], right[i]))
{
return false;
}
}
return true;
}
private static bool SetEquals<T>(List<T> left, List<T> right)
{
if (left.Count != right.Count)
{
return false;
}
foreach (T item in left)
{
if (!right.Contains(item))
{
return false;
}
}
return true;
}
private bool Equals(Todo other)
{
return (object.ReferenceEquals(this.Attendees, other.Attendees) ||
((this.Attendees != null) && (other.Attendees != null) && Todo.SetEquals(this.Attendees, other.Attendees))) &&
(object.ReferenceEquals(this.Projects, other.Projects) ||
((this.Projects != null) && (other.Projects != null) && Todo.SetEquals(this.Projects, other.Projects))) &&
(object.ReferenceEquals(this.Checkboxes, other.Checkboxes) ||
((this.Checkboxes != null) && (other.Checkboxes != null) && Todo.SetEquals(this.Checkboxes, other.Checkboxes))) &&
(object.ReferenceEquals(this.Prices, other.Prices) ||
((this.Prices != null) && (other.Prices != null) && Todo.NestedSetEquals(this.Prices, other.Prices))) &&
(object.ReferenceEquals(this.Nested, other.Nested) ||
((this.Nested != null) && (other.Nested != null) && Todo.NestedNestedSetEquals(this.Nested, other.Nested))) &&
(object.ReferenceEquals(this.Shopping, other.Shopping) ||
((this.Shopping != null) && (other.Shopping != null) && Todo.SetEquals(this.Shopping, other.Shopping))) &&
(object.ReferenceEquals(this.Work, other.Work) ||
((this.Work != null) && (other.Work != null) && Todo.SetEquals(this.Work, other.Work)));
}
}
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")]
private sealed class ShoppingItem
{
public string Label;
public byte Count;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(null, obj))
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
return obj is ShoppingItem shoppingItem && this.Equals(shoppingItem);
}
public override int GetHashCode()
{
unchecked
{
return (this.Label.GetHashCode() * 397) ^ this.Count.GetHashCode();
}
}
private bool Equals(ShoppingItem other)
{
return this.Label == other.Label && this.Count == other.Count;
}
}
}
}

View File

@@ -0,0 +1,24 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit
{
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class UpdateOptionsUnitTests
{
[TestMethod]
[Owner("jthunter")]
public void UpdateOptionsTest()
{
Assert.AreEqual((int)RowOptions.None, (int)UpdateOptions.None);
Assert.AreEqual((int)RowOptions.Update, (int)UpdateOptions.Update);
Assert.AreEqual((int)RowOptions.Insert, (int)UpdateOptions.Insert);
Assert.AreEqual((int)RowOptions.Upsert, (int)UpdateOptions.Upsert);
Assert.AreEqual((int)RowOptions.InsertAt, (int)UpdateOptions.InsertAt);
}
}
}