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,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<ProjectGuid>{CE1C4987-FC19-4887-9EB6-13508F8DA644}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>Microsoft.Azure.Cosmos.Serialization.HybridRow.Json</RootNamespace>
<AssemblyName>Microsoft.Azure.Cosmos.Serialization.HybridRow.Json</AssemblyName>
<TargetFramework>netstandard2.0</TargetFramework>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Build.props))\build.props" />
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.Memory" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\Core\Microsoft.Azure.Cosmos.Core.csproj" />
<ProjectReference Include="..\HybridRow\Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
using System.Reflection;
using System.Runtime.CompilerServices;
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.Json")]
// 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("CE1C4987-FC19-4887-9EB6-13508F8DA644")]

View File

@@ -0,0 +1,453 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Json
{
using System;
using System.Text;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
public static class RowReaderJsonExtensions
{
/// <summary>
/// Project a JSON document from a HybridRow <see cref="RowReader"/>.
/// </summary>
/// <param name="reader">The reader to project to JSON.</param>
/// <param name="str">If successful, the JSON document that corresponds to the <paramref name="reader"/>.</param>
/// <returns>The result.</returns>
public static Result ToJson(this ref RowReader reader, out string str)
{
return reader.ToJson(new RowReaderJsonSettings(" "), out str);
}
/// <summary>
/// Project a JSON document from a HybridRow <see cref="RowReader"/>.
/// </summary>
/// <param name="reader">The reader to project to JSON.</param>
/// <param name="settings">Settings that control how the JSON document is formatted.</param>
/// <param name="str">If successful, the JSON document that corresponds to the <paramref name="reader"/>.</param>
/// <returns>The result.</returns>
public static Result ToJson(this ref RowReader reader, RowReaderJsonSettings settings, out string str)
{
ReaderStringContext ctx = new ReaderStringContext(
new StringBuilder(),
new RowReaderJsonSettings(settings.IndentChars, settings.QuoteChar == '\'' ? '\'' : '"'),
1);
ctx.Builder.Append("{");
Result result = RowReaderJsonExtensions.ToJson(ref reader, ctx);
if (result != Result.Success)
{
str = null;
return result;
}
ctx.Builder.Append(ctx.NewLine);
ctx.Builder.Append("}");
str = ctx.Builder.ToString();
return Result.Success;
}
private static Result ToJson(ref RowReader reader, ReaderStringContext ctx)
{
int index = 0;
while (reader.Read())
{
string path = !reader.Path.IsNull ? $"{ctx.Settings.QuoteChar}{reader.Path}{ctx.Settings.QuoteChar}:" : null;
if (index != 0)
{
ctx.Builder.Append(',');
}
index++;
ctx.Builder.Append(ctx.NewLine);
ctx.WriteIndent();
if (path != null)
{
ctx.Builder.Append(path);
ctx.Builder.Append(ctx.Separator);
}
Result r;
char scopeBracket = default;
char scopeCloseBracket = default;
switch (reader.Type.LayoutCode)
{
case LayoutCode.Null:
{
r = reader.ReadNull(out NullValue _);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append("null");
break;
}
case LayoutCode.Boolean:
{
r = reader.ReadBool(out bool value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.Int8:
{
r = reader.ReadInt8(out sbyte value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.Int16:
{
r = reader.ReadInt16(out short value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.Int32:
{
r = reader.ReadInt32(out int value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.Int64:
{
r = reader.ReadInt64(out long value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.UInt8:
{
r = reader.ReadUInt8(out byte value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.UInt16:
{
r = reader.ReadUInt16(out ushort value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.UInt32:
{
r = reader.ReadUInt32(out uint value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.UInt64:
{
r = reader.ReadUInt64(out ulong value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.VarInt:
{
r = reader.ReadVarInt(out long value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.VarUInt:
{
r = reader.ReadVarUInt(out ulong value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.Float32:
{
r = reader.ReadFloat32(out float value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.Float64:
{
r = reader.ReadFloat64(out double value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.Float128:
{
r = reader.ReadFloat128(out Float128 _);
if (r != Result.Success)
{
return r;
}
// ctx.Builder.AppendFormat("High: {0}, Low: {1}\n", value.High, value.Low);
Contract.Assert(false, "Float128 are not supported.");
break;
}
case LayoutCode.Decimal:
{
r = reader.ReadDecimal(out decimal value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value);
break;
}
case LayoutCode.DateTime:
{
r = reader.ReadDateTime(out DateTime value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(ctx.Settings.QuoteChar);
ctx.Builder.Append(value);
ctx.Builder.Append(ctx.Settings.QuoteChar);
break;
}
case LayoutCode.UnixDateTime:
{
r = reader.ReadUnixDateTime(out UnixDateTime value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(value.Milliseconds);
break;
}
case LayoutCode.Guid:
{
r = reader.ReadGuid(out Guid value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(ctx.Settings.QuoteChar);
ctx.Builder.Append(value.ToString());
ctx.Builder.Append(ctx.Settings.QuoteChar);
break;
}
case LayoutCode.MongoDbObjectId:
{
r = reader.ReadMongoDbObjectId(out MongoDbObjectId value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(ctx.Settings.QuoteChar);
ReadOnlyMemory<byte> bytes = value.ToByteArray();
ctx.Builder.Append(bytes.Span.ToHexString());
ctx.Builder.Append(ctx.Settings.QuoteChar);
break;
}
case LayoutCode.Utf8:
{
r = reader.ReadString(out Utf8Span value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(ctx.Settings.QuoteChar);
ctx.Builder.Append(value.ToString());
ctx.Builder.Append(ctx.Settings.QuoteChar);
break;
}
case LayoutCode.Binary:
{
r = reader.ReadBinary(out ReadOnlySpan<byte> value);
if (r != Result.Success)
{
return r;
}
ctx.Builder.Append(ctx.Settings.QuoteChar);
ctx.Builder.Append(value.ToHexString());
ctx.Builder.Append(ctx.Settings.QuoteChar);
break;
}
case LayoutCode.NullableScope:
case LayoutCode.ImmutableNullableScope:
{
if (!reader.HasValue)
{
ctx.Builder.Append("null");
break;
}
goto case LayoutCode.TypedTupleScope;
}
case LayoutCode.ArrayScope:
case LayoutCode.ImmutableArrayScope:
case LayoutCode.TypedArrayScope:
case LayoutCode.ImmutableTypedArrayScope:
case LayoutCode.TypedSetScope:
case LayoutCode.ImmutableTypedSetScope:
case LayoutCode.TypedMapScope:
case LayoutCode.ImmutableTypedMapScope:
case LayoutCode.TupleScope:
case LayoutCode.ImmutableTupleScope:
case LayoutCode.TypedTupleScope:
case LayoutCode.ImmutableTypedTupleScope:
case LayoutCode.TaggedScope:
case LayoutCode.ImmutableTaggedScope:
case LayoutCode.Tagged2Scope:
case LayoutCode.ImmutableTagged2Scope:
scopeBracket = '[';
scopeCloseBracket = ']';
goto case LayoutCode.EndScope;
case LayoutCode.ObjectScope:
case LayoutCode.ImmutableObjectScope:
case LayoutCode.Schema:
case LayoutCode.ImmutableSchema:
scopeBracket = '{';
scopeCloseBracket = '}';
goto case LayoutCode.EndScope;
case LayoutCode.EndScope:
{
ctx.Builder.Append(scopeBracket);
int snapshot = ctx.Builder.Length;
r = reader.ReadScope(new ReaderStringContext(ctx.Builder, ctx.Settings, ctx.Indent + 1), RowReaderJsonExtensions.ToJson);
if (r != Result.Success)
{
return r;
}
if (ctx.Builder.Length != snapshot)
{
ctx.Builder.Append(ctx.NewLine);
ctx.WriteIndent();
}
ctx.Builder.Append(scopeCloseBracket);
break;
}
default:
{
Contract.Assert(false, $"Unknown type will be ignored: {reader.Type.LayoutCode}");
break;
}
}
}
return Result.Success;
}
private readonly struct ReaderStringContext
{
public readonly int Indent;
public readonly StringBuilder Builder;
public readonly RowReaderJsonSettings Settings;
public readonly string Separator;
public readonly string NewLine;
public ReaderStringContext(StringBuilder builder, RowReaderJsonSettings settings, int indent)
{
this.Settings = settings;
this.Separator = settings.IndentChars == null ? "" : " ";
this.NewLine = settings.IndentChars == null ? "" : "\n";
this.Indent = indent;
this.Builder = builder;
}
public void WriteIndent()
{
string indentChars = this.Settings.IndentChars ?? "";
for (int i = 0; i < this.Indent; i++)
{
this.Builder.Append(indentChars);
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable CA1051 // Do not declare visible instance fields
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Json
{
public readonly struct RowReaderJsonSettings
{
/// <summary>If non-null then child objects are indented by one copy of this string per level.</summary>
public readonly string IndentChars;
/// <summary>The quote character to use.</summary>
/// <remarks>May be <see cref="lang:\""/> or <see cref="'" />.</remarks>
public readonly char QuoteChar;
public RowReaderJsonSettings(string indentChars = " ", char quoteChar = '"')
{
this.IndentChars = indentChars;
this.QuoteChar = quoteChar;
}
}
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{7047DD2A-14FA-445E-93B2-67EB98282C4D}</ProjectGuid>
<ShippingScope>External</ShippingScope>
<DisableCosmosDBRules>true</DisableCosmosDBRules>
</PropertyGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Build.props))\Build.props" />
<PropertyGroup>
<NugetPackBasePath>$(OutputRootDir)</NugetPackBasePath>
<NugetPackProperties>$(NugetPackProperties);ProductSemanticVersion=$(ProductSemanticVersion);VersionPrereleaseExtension=$(VersionPrereleaseExtension)</NugetPackProperties>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(DocDBRoot)\Cosmos\Core\Core\Microsoft.Azure.Cosmos.Core.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<Project>{B7621117-AEF3-4E10-928D-533AE893F379}</Project>
<Name>Microsoft.Azure.Cosmos.Core</Name>
</ProjectReference>
<ProjectReference Include="..\HybridRow\Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<Project>{490D42EE-1FEF-47CC-97E4-782A353B4D58}</Project>
<Name>Microsoft.Azure.Cosmos.Serialization.HybridRow</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(BuildExtensionsPath)\NoTarget.targets" />
</Project>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<package >
<metadata>
<id>Microsoft.Azure.Cosmos.Serialization.HybridRow</id>
<version>1.0.0-preview</version>
<title>Microsoft.Azure.Cosmos.Serialization.HybridRow</title>
<authors>Microsoft</authors>
<owners>Microsoft</owners>
<licenseUrl>https://aka.ms/netcoregaeula</licenseUrl>
<projectUrl>https://github.com/Azure/azure-cosmos-dotnet-v3</projectUrl>
<iconUrl>http://go.microsoft.com/fwlink/?LinkID=288890</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<summary>Microsoft.Azure.Cosmos.Serialization.HybridRow</summary>
<description>This package supports the Microsoft Azure Cosmos Client and is not intended to be used directly from your code.</description>
<copyright>Copyright © Microsoft Corporation</copyright>
<tags>microsoft azure cosmos dotnetcore netcore netstandard</tags>
<dependencies>
<dependency id="System.Memory" version="4.5.1" />
<dependency id="Newtonsoft.Json" version="10.0.2" />
<dependency id="System.Runtime.CompilerServices.Unsafe" version="4.5.1" />
<dependency id="System.Threading.Tasks.Extensions" version="4.5.1" />
<dependency id="System.ValueTuple" version="4.5.0" />
</dependencies>
</metadata>
<files>
<file src="Product\Microsoft.Azure.Cosmos.Serialization.HybridRow\Microsoft.Azure.Cosmos.Serialization.HybridRow.dll" target="lib/netstandard2.0" />
<file src="Product\Microsoft.Azure.Cosmos.Core\Microsoft.Azure.Cosmos.Core.dll" target="lib/netstandard2.0" />
</files>
</package>

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,119 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable SA1401 // Fields should be private
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
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.RecordIO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
using Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
public class BenchmarkSuiteBase
{
private protected const int InitialCapacity = 2 * 1024 * 1024;
private protected LayoutResolverNamespace DefaultResolver = (LayoutResolverNamespace)SystemSchema.LayoutResolver;
private protected async Task<(List<Dictionary<Utf8String, object>>, LayoutResolverNamespace)> LoadExpectedAsync(string expectedFile)
{
LayoutResolverNamespace resolver = this.DefaultResolver;
List<Dictionary<Utf8String, object>> expected = new List<Dictionary<Utf8String, object>>();
using (Stream stm = new FileStream(expectedFile, FileMode.Open))
{
// Read a RecordIO stream.
MemorySpanResizer<byte> resizer = new MemorySpanResizer<byte>(BenchmarkSuiteBase.InitialCapacity);
Result r = await stm.ReadRecordIOAsync(
record =>
{
r = BenchmarkSuiteBase.LoadOneRow(record, resolver, out Dictionary<Utf8String, object> rowValue);
ResultAssert.IsSuccess(r);
expected.Add(rowValue);
return Result.Success;
},
segment =>
{
r = SegmentSerializer.Read(segment.Span, SystemSchema.LayoutResolver, out Segment s);
ResultAssert.IsSuccess(r);
Assert.IsNotNull(s.SDL);
resolver = new LayoutResolverNamespace(Namespace.Parse(s.SDL), resolver);
return Result.Success;
},
resizer);
ResultAssert.IsSuccess(r);
}
return (expected, resolver);
}
private protected static async Task WriteAllRowsAsync(
string file,
string sdl,
LayoutResolver resolver,
Layout layout,
List<Dictionary<Utf8String, object>> rows)
{
using (Stream stm = new FileStream(file, FileMode.Truncate))
{
// Create a reusable, resizable buffer.
MemorySpanResizer<byte> resizer = new MemorySpanResizer<byte>(BenchmarkSuiteBase.InitialCapacity);
// Write a RecordIO stream.
Result r = await stm.WriteRecordIOAsync(
new Segment("HybridRow.Tests.Perf Expected Results", sdl),
(long index, out ReadOnlyMemory<byte> body) =>
{
body = default;
if (index >= rows.Count)
{
return Result.Success;
}
StreamingRowGenerator writer = new StreamingRowGenerator(
BenchmarkSuiteBase.InitialCapacity,
layout,
resolver,
resizer);
Result r2 = writer.WriteBuffer(rows[(int)index]);
if (r2 != Result.Success)
{
return r2;
}
body = resizer.Memory.Slice(0, writer.Length);
return Result.Success;
});
ResultAssert.IsSuccess(r);
}
}
private protected static Result LoadOneRow(Memory<byte> buffer, LayoutResolver resolver, out Dictionary<Utf8String, object> rowValue)
{
RowBuffer row = new RowBuffer(buffer.Span, HybridRowVersion.V1, resolver);
RowReader reader = new RowReader(ref row);
return DiagnosticConverter.ReaderToDynamic(ref reader, out rowValue);
}
private protected static class ResultAssert
{
public static void IsSuccess(Result actual)
{
if (actual != Result.Success)
{
Assert.AreEqual(Result.Success, actual);
}
}
}
}
}

View File

@@ -0,0 +1,110 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Core.Utf8;
using MongoDB.Bson.IO;
internal sealed class BsonJsonModelRowGenerator : IDisposable
{
private readonly MemoryStream stream;
private readonly BsonWriter writer;
public BsonJsonModelRowGenerator(int capacity)
{
this.stream = new MemoryStream(capacity);
this.writer = new BsonBinaryWriter(this.stream);
}
public int Length => (int)this.stream.Position;
public byte[] ToArray()
{
return this.stream.ToArray();
}
public void Reset()
{
this.stream.SetLength(0);
this.stream.Position = 0;
}
public void WriteBuffer(Dictionary<Utf8String, object> dict)
{
this.writer.WriteStartDocument();
foreach ((Utf8String propPath, object propValue) in dict)
{
this.JsonModelSwitch(propPath, propValue);
}
this.writer.WriteEndDocument();
}
public void Dispose()
{
this.writer.Dispose();
this.stream.Dispose();
}
private void JsonModelSwitch(Utf8String path, object value)
{
if (path != null)
{
this.writer.WriteName(path.ToString());
}
switch (value)
{
case null:
this.writer.WriteNull();
return;
case bool x:
this.writer.WriteBoolean(x);
return;
case long x:
this.writer.WriteInt64(x);
return;
case double x:
this.writer.WriteDouble(x);
return;
case string x:
this.writer.WriteString(x);
return;
case Utf8String x:
this.writer.WriteString(x.ToString());
return;
case byte[] x:
this.writer.WriteBytes(x);
return;
case Dictionary<Utf8String, object> x:
this.writer.WriteStartDocument();
foreach ((Utf8String propPath, object propValue) in x)
{
this.JsonModelSwitch(propPath, propValue);
}
this.writer.WriteEndDocument();
return;
case List<object> x:
this.writer.WriteStartArray();
foreach (object item in x)
{
this.JsonModelSwitch(null, item);
}
this.writer.WriteEndArray();
return;
default:
Contract.Assert(false, $"Unknown type will be ignored: {value.GetType().Name}");
return;
}
}
}
}

View File

@@ -0,0 +1,63 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using MongoDB.Bson;
using MongoDB.Bson.IO;
internal static class BsonReaderExtensions
{
public static void VisitBsonDocument(this BsonReader bsonReader)
{
bsonReader.ReadStartDocument();
BsonType type;
while ((type = bsonReader.ReadBsonType()) != BsonType.EndOfDocument)
{
string path = bsonReader.ReadName();
switch (type)
{
case BsonType.Array:
bsonReader.VisitBsonArray();
break;
case BsonType.Document:
bsonReader.VisitBsonDocument();
break;
default:
bsonReader.SkipValue();
break;
}
}
bsonReader.ReadEndDocument();
}
private static void VisitBsonArray(this BsonReader bsonReader)
{
bsonReader.ReadStartArray();
BsonType type;
while ((type = bsonReader.ReadBsonType()) != BsonType.EndOfDocument)
{
switch (type)
{
case BsonType.Array:
bsonReader.VisitBsonArray();
break;
case BsonType.Document:
bsonReader.VisitBsonDocument();
break;
default:
bsonReader.SkipValue();
break;
}
}
bsonReader.ReadEndArray();
}
}
}

View File

@@ -0,0 +1,303 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using MongoDB.Bson;
using MongoDB.Bson.IO;
internal sealed class BsonRowGenerator : IDisposable
{
private readonly MemoryStream stream;
private readonly BsonWriter writer;
private readonly Layout layout;
private readonly LayoutResolver resolver;
public BsonRowGenerator(int capacity, Layout layout, LayoutResolver resolver)
{
this.stream = new MemoryStream(capacity);
this.writer = new BsonBinaryWriter(this.stream);
this.layout = layout;
this.resolver = resolver;
}
public int Length => (int)this.stream.Position;
public byte[] ToArray()
{
return this.stream.ToArray();
}
public void Reset()
{
this.stream.SetLength(0);
this.stream.Position = 0;
}
public void WriteBuffer(Dictionary<Utf8String, object> dict)
{
this.writer.WriteStartDocument();
foreach (LayoutColumn c in this.layout.Columns)
{
this.LayoutCodeSwitch(c.Path, c.TypeArg, dict[c.Path]);
}
this.writer.WriteEndDocument();
}
public void Dispose()
{
this.writer.Dispose();
this.stream.Dispose();
}
private void LayoutCodeSwitch(UtfAnyString path, TypeArgument typeArg, object value)
{
if (!path.IsNull)
{
this.writer.WriteName(path);
}
switch (typeArg.Type.LayoutCode)
{
case LayoutCode.Null:
this.writer.WriteNull();
return;
case LayoutCode.Boolean:
this.writer.WriteBoolean(value == null ? default(bool) : (bool)value);
return;
case LayoutCode.Int8:
this.writer.WriteInt32(value == null ? default(sbyte) : (sbyte)value);
return;
case LayoutCode.Int16:
this.writer.WriteInt32(value == null ? default(short) : (short)value);
return;
case LayoutCode.Int32:
this.writer.WriteInt32(value == null ? default(int) : (int)value);
return;
case LayoutCode.Int64:
this.writer.WriteInt64(value == null ? default(long) : (long)value);
return;
case LayoutCode.UInt8:
this.writer.WriteInt32(value == null ? default(byte) : (byte)value);
return;
case LayoutCode.UInt16:
this.writer.WriteInt32(value == null ? default(ushort) : (ushort)value);
return;
case LayoutCode.UInt32:
this.writer.WriteInt32(value == null ? default(int) : unchecked((int)(uint)value));
return;
case LayoutCode.UInt64:
this.writer.WriteInt64(value == null ? default(long) : unchecked((long)(ulong)value));
return;
case LayoutCode.VarInt:
this.writer.WriteInt64(value == null ? default(long) : (long)value);
return;
case LayoutCode.VarUInt:
this.writer.WriteInt64(value == null ? default(long) : unchecked((long)(ulong)value));
return;
case LayoutCode.Float32:
this.writer.WriteDouble(value == null ? default(float) : (float)value);
return;
case LayoutCode.Float64:
this.writer.WriteDouble(value == null ? default(double) : (double)value);
return;
case LayoutCode.Float128:
Decimal128 d128 = default(Decimal128);
if (value != null)
{
Float128 f128 = (Float128)value;
d128 = unchecked(Decimal128.FromIEEEBits((ulong)f128.High, (ulong)f128.Low));
}
this.writer.WriteDecimal128(d128);
return;
case LayoutCode.Decimal:
this.writer.WriteDecimal128(value == null ? default(Decimal128) : new Decimal128((decimal)value));
return;
case LayoutCode.DateTime:
this.writer.WriteDateTime(value == null ? default(long) : ((DateTime)value).Ticks);
return;
case LayoutCode.UnixDateTime:
this.writer.WriteDateTime(value == null ? default(long) : ((UnixDateTime)value).Milliseconds);
return;
case LayoutCode.Guid:
this.writer.WriteString(value == null ? string.Empty : ((Guid)value).ToString());
return;
case LayoutCode.MongoDbObjectId:
this.writer.WriteObjectId(value == null ? default(ObjectId) : new ObjectId(((MongoDbObjectId)value).ToByteArray()));
return;
case LayoutCode.Utf8:
this.writer.WriteString(value == null ? string.Empty : ((Utf8String)value).ToString());
return;
case LayoutCode.Binary:
this.writer.WriteBytes(value == null ? default(byte[]) : (byte[])value);
return;
case LayoutCode.ObjectScope:
case LayoutCode.ImmutableObjectScope:
this.DispatchObject(typeArg, value);
return;
case LayoutCode.TypedArrayScope:
case LayoutCode.ImmutableTypedArrayScope:
this.DispatchArray(typeArg, value);
return;
case LayoutCode.TypedSetScope:
case LayoutCode.ImmutableTypedSetScope:
this.DispatchSet(typeArg, value);
return;
case LayoutCode.TypedMapScope:
case LayoutCode.ImmutableTypedMapScope:
this.DispatchMap(typeArg, value);
return;
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.DispatchTuple(typeArg, value);
return;
case LayoutCode.NullableScope:
case LayoutCode.ImmutableNullableScope:
this.DispatchNullable(typeArg, value);
return;
case LayoutCode.Schema:
case LayoutCode.ImmutableSchema:
this.DispatchUDT(typeArg, value);
return;
default:
Contract.Assert(false, $"Unknown type will be ignored: {typeArg}");
return;
}
}
private void DispatchObject(TypeArgument typeArg, object value)
{
this.writer.WriteStartDocument();
// TODO: support properties in an object scope.
this.writer.WriteEndDocument();
}
private void DispatchArray(TypeArgument typeArg, object value)
{
Contract.Requires(typeArg.TypeArgs.Count == 1);
this.writer.WriteStartArray();
foreach (object item in (List<object>)value)
{
this.LayoutCodeSwitch(null, typeArg.TypeArgs[0], item);
}
this.writer.WriteEndArray();
}
private void DispatchTuple(TypeArgument typeArg, object value)
{
Contract.Requires(typeArg.TypeArgs.Count >= 2);
List<object> items = (List<object>)value;
Contract.Assert(items.Count == typeArg.TypeArgs.Count);
this.writer.WriteStartArray();
for (int i = 0; i < items.Count; i++)
{
object item = items[i];
this.LayoutCodeSwitch(null, typeArg.TypeArgs[i], item);
}
this.writer.WriteEndArray();
}
private void DispatchNullable(TypeArgument typeArg, object value)
{
Contract.Requires(typeArg.TypeArgs.Count == 1);
if (value != null)
{
this.LayoutCodeSwitch(null, typeArg.TypeArgs[0], value);
}
else
{
this.writer.WriteNull();
}
}
private void DispatchSet(TypeArgument typeArg, object value)
{
Contract.Requires(typeArg.TypeArgs.Count == 1);
this.writer.WriteStartArray();
foreach (object item in (List<object>)value)
{
this.LayoutCodeSwitch(null, typeArg.TypeArgs[0], item);
}
this.writer.WriteEndArray();
}
private void DispatchMap(TypeArgument typeArg, object value)
{
Contract.Requires(typeArg.TypeArgs.Count == 2);
this.writer.WriteStartArray();
foreach (object item in (List<object>)value)
{
this.DispatchTuple(typeArg, item);
}
this.writer.WriteEndArray();
}
private void DispatchUDT(TypeArgument typeArg, object value)
{
this.writer.WriteStartDocument();
Dictionary<Utf8String, object> dict = (Dictionary<Utf8String, object>)value;
Layout udt = this.resolver.Resolve(typeArg.TypeArgs.SchemaId);
foreach (LayoutColumn c in udt.Columns)
{
this.LayoutCodeSwitch(c.Path, c.TypeArg, dict[c.Path]);
}
this.writer.WriteEndDocument();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,293 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
/// <summary>
/// Tests involving generated (early bound) code compiled from schema based on a partial implementation
/// of Cassandra Hotel Schema described here: https://www.oreilly.com/ideas/cassandra-data-modeling .
/// <para>
/// The tests here differ from <see cref="SchematizedMicroBenchmarkSuite" /> in that they rely on
/// the schema being known at compile time instead of runtime. This allows code to be generated that
/// directly addresses the schema structure instead of dynamically discovering schema structure at
/// runtime.
/// </para>
/// </summary>
[TestClass]
public sealed class CodeGenMicroBenchmarkSuite : MicroBenchmarkSuiteBase
{
private const int GuestCount = 1000;
private const int HotelCount = 10000;
private const int RoomsCount = 10000;
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task ProtobufGuestsWriteBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.ProtobufWriteBenchmark("Guests", "Guests", CodeGenMicroBenchmarkSuite.GuestCount, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task ProtobufHotelWriteBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.ProtobufWriteBenchmark("Hotels", "Hotels", CodeGenMicroBenchmarkSuite.HotelCount, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task ProtobufRoomsWriteBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.ProtobufWriteBenchmark(
"Available_Rooms_By_Hotel_Date",
"Rooms",
CodeGenMicroBenchmarkSuite.RoomsCount,
expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task ProtobufGuestsReadBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.ProtobufReadBenchmark("Guests", "Guests", CodeGenMicroBenchmarkSuite.GuestCount, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task ProtobufHotelReadBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.ProtobufReadBenchmark("Hotels", "Hotels", CodeGenMicroBenchmarkSuite.HotelCount, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task ProtobufRoomsReadBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.ProtobufReadBenchmark(
"Available_Rooms_By_Hotel_Date",
"Rooms",
CodeGenMicroBenchmarkSuite.RoomsCount,
expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task CodeGenGuestsWriteBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.CodeGenWriteBenchmark(resolver, "Guests", "Guests", CodeGenMicroBenchmarkSuite.GuestCount, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task CodeGenHotelWriteBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.CodeGenWriteBenchmark(resolver, "Hotels", "Hotels", CodeGenMicroBenchmarkSuite.HotelCount, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task CodeGenRoomsWriteBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.CodeGenWriteBenchmark(
resolver,
"Available_Rooms_By_Hotel_Date",
"Rooms",
CodeGenMicroBenchmarkSuite.RoomsCount,
expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task CodeGenGuestsReadBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.CodeGenReadBenchmark(resolver, "Guests", "Guests", CodeGenMicroBenchmarkSuite.GuestCount, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task CodeGenHotelReadBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.CodeGenReadBenchmark(resolver, "Hotels", "Hotels", CodeGenMicroBenchmarkSuite.HotelCount, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task CodeGenRoomsReadBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
CodeGenMicroBenchmarkSuite.CodeGenReadBenchmark(
resolver,
"Available_Rooms_By_Hotel_Date",
"Rooms",
CodeGenMicroBenchmarkSuite.RoomsCount,
expected);
}
private static void ProtobufWriteBenchmark(
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
BenchmarkContext context = new BenchmarkContext
{
ProtobufWriter = new ProtobufRowGenerator(schemaName, BenchmarkSuiteBase.InitialCapacity)
};
MicroBenchmarkSuiteBase.Benchmark(
"CodeGen",
"Write",
dataSetName,
"Protobuf",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> tableValue) => { ctx.ProtobufWriter.WriteBuffer(tableValue); },
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> tableValue) => ctx.ProtobufWriter.Length,
expected);
}
private static void ProtobufReadBenchmark(
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
BenchmarkContext context = new BenchmarkContext
{
ProtobufWriter = new ProtobufRowGenerator(schemaName, BenchmarkSuiteBase.InitialCapacity)
};
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
context.ProtobufWriter.WriteBuffer(tableValue);
expectedSerialized.Add(context.ProtobufWriter.ToArray());
}
MicroBenchmarkSuiteBase.Benchmark(
"CodeGen",
"Read",
dataSetName,
"Protobuf",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, byte[] tableValue) => ctx.ProtobufWriter.ReadBuffer(tableValue),
(ref BenchmarkContext ctx, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
private static void CodeGenWriteBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
BenchmarkContext context = new BenchmarkContext
{
CodeGenWriter = new CodeGenRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver)
};
MicroBenchmarkSuiteBase.Benchmark(
"CodeGen",
"Write",
dataSetName,
"HybridRowGen",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> tableValue) =>
{
ctx.CodeGenWriter.Reset();
Result r = ctx.CodeGenWriter.WriteBuffer(tableValue);
ResultAssert.IsSuccess(r);
},
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> tableValue) => ctx.CodeGenWriter.Length,
expected);
}
private static void CodeGenReadBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
BenchmarkContext context = new BenchmarkContext
{
CodeGenWriter = new CodeGenRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver)
};
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
context.CodeGenWriter.Reset();
Result r = context.CodeGenWriter.WriteBuffer(tableValue);
ResultAssert.IsSuccess(r);
expectedSerialized.Add(context.CodeGenWriter.ToArray());
}
MicroBenchmarkSuiteBase.Benchmark(
"CodeGen",
"Read",
dataSetName,
"HybridRowGen",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, byte[] tableValue) =>
{
Result r = ctx.CodeGenWriter.ReadBuffer(tableValue);
ResultAssert.IsSuccess(r);
},
(ref BenchmarkContext ctx, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
}
}

View File

@@ -0,0 +1,872 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
internal ref struct CodeGenRowGenerator
{
private RowBuffer row;
private readonly HybridRowSerializer dispatcher;
public CodeGenRowGenerator(int capacity, Layout layout, LayoutResolver resolver, ISpanResizer<byte> resizer = default)
{
this.row = new RowBuffer(capacity, resizer);
this.row.InitLayout(HybridRowVersion.V1, layout, resolver);
switch (layout.Name)
{
case "Hotels":
this.dispatcher = new HotelsHybridRowSerializer(layout, resolver);
break;
case "Guests":
this.dispatcher = new GuestsHybridRowSerializer(layout, resolver);
break;
case "Available_Rooms_By_Hotel_Date":
this.dispatcher = new RoomsHybridRowSerializer(layout, resolver);
break;
default:
Contract.Fail($"Unknown schema will be ignored: {layout.Name}");
this.dispatcher = null;
break;
}
}
public int Length => this.row.Length;
public byte[] ToArray()
{
return this.row.ToArray();
}
public void Reset()
{
Layout layout = this.row.Resolver.Resolve(this.row.Header.SchemaId);
this.row.InitLayout(HybridRowVersion.V1, layout, this.row.Resolver);
}
public Result WriteBuffer(Dictionary<Utf8String, object> tableValue)
{
RowCursor root = RowCursor.Create(ref this.row);
return this.dispatcher.WriteBuffer(ref this.row, ref root, tableValue);
}
public Result ReadBuffer(byte[] buffer)
{
this.row = new RowBuffer(buffer.AsSpan(), HybridRowVersion.V1, this.row.Resolver);
RowCursor root = RowCursor.Create(ref this.row);
return this.dispatcher.ReadBuffer(ref this.row, ref root);
}
private abstract class HybridRowSerializer
{
public abstract Result WriteBuffer(ref RowBuffer row, ref RowCursor root, Dictionary<Utf8String, object> tableValue);
public abstract Result ReadBuffer(ref RowBuffer row, ref RowCursor root);
}
private sealed class GuestsHybridRowSerializer : HybridRowSerializer
{
private static readonly Utf8String GuestIdName = Utf8String.TranscodeUtf16("guest_id");
private static readonly Utf8String FirstNameName = Utf8String.TranscodeUtf16("first_name");
private static readonly Utf8String LastNameName = Utf8String.TranscodeUtf16("last_name");
private static readonly Utf8String TitleName = Utf8String.TranscodeUtf16("title");
private static readonly Utf8String EmailsName = Utf8String.TranscodeUtf16("emails");
private static readonly Utf8String PhoneNumbersName = Utf8String.TranscodeUtf16("phone_numbers");
private static readonly Utf8String AddressesName = Utf8String.TranscodeUtf16("addresses");
private static readonly Utf8String ConfirmNumberName = Utf8String.TranscodeUtf16("confirm_number");
private readonly LayoutColumn guestId;
private readonly LayoutColumn firstName;
private readonly LayoutColumn lastName;
private readonly LayoutColumn title;
private readonly LayoutColumn emails;
private readonly LayoutColumn phoneNumbers;
private readonly LayoutColumn addresses;
private readonly LayoutColumn confirmNumber;
private readonly StringToken emailsToken;
private readonly StringToken phoneNumbersToken;
private readonly StringToken addressesToken;
private readonly TypeArgumentList addressesFieldType;
private readonly AddressHybridRowSerializer addressSerializer;
private readonly LayoutScope.WriterFunc<Dictionary<Utf8String, object>> addressSerializerWriter;
public GuestsHybridRowSerializer(Layout layout, LayoutResolver resolver)
{
layout.TryFind(GuestsHybridRowSerializer.GuestIdName, out this.guestId);
layout.TryFind(GuestsHybridRowSerializer.FirstNameName, out this.firstName);
layout.TryFind(GuestsHybridRowSerializer.LastNameName, out this.lastName);
layout.TryFind(GuestsHybridRowSerializer.TitleName, out this.title);
layout.TryFind(GuestsHybridRowSerializer.EmailsName, out this.emails);
layout.TryFind(GuestsHybridRowSerializer.PhoneNumbersName, out this.phoneNumbers);
layout.TryFind(GuestsHybridRowSerializer.AddressesName, out this.addresses);
layout.TryFind(GuestsHybridRowSerializer.ConfirmNumberName, out this.confirmNumber);
layout.Tokenizer.TryFindToken(this.emails.Path, out this.emailsToken);
layout.Tokenizer.TryFindToken(this.phoneNumbers.Path, out this.phoneNumbersToken);
layout.Tokenizer.TryFindToken(this.addresses.Path, out this.addressesToken);
this.addressesFieldType = new TypeArgumentList(
new[]
{
new TypeArgument(LayoutType.TypedTuple, this.addresses.TypeArgs)
});
this.addressSerializer = new AddressHybridRowSerializer(resolver.Resolve(this.addresses.TypeArgs[1].TypeArgs.SchemaId), resolver);
this.addressSerializerWriter = this.addressSerializer.WriteBuffer;
}
public override Result WriteBuffer(ref RowBuffer row, ref RowCursor root, Dictionary<Utf8String, object> tableValue)
{
foreach ((Utf8String key, object value) in tableValue)
{
Result r;
switch (0)
{
case 0 when key.Equals(GuestsHybridRowSerializer.GuestIdName):
if (value != null)
{
r = LayoutType.Guid.WriteFixed(ref row, ref root, this.guestId, (Guid)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(GuestsHybridRowSerializer.FirstNameName):
if (value != null)
{
r = LayoutType.Utf8.WriteVariable(ref row, ref root, this.firstName, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(GuestsHybridRowSerializer.LastNameName):
if (value != null)
{
r = LayoutType.Utf8.WriteVariable(ref row, ref root, this.lastName, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(GuestsHybridRowSerializer.TitleName):
if (value != null)
{
r = LayoutType.Utf8.WriteVariable(ref row, ref root, this.title, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(GuestsHybridRowSerializer.EmailsName):
if (value != null)
{
root.Find(ref row, this.emailsToken);
r = LayoutType.TypedArray.WriteScope(
ref row,
ref root,
this.emails.TypeArgs,
(List<object>)value,
(ref RowBuffer row2, ref RowCursor childScope, List<object> context) =>
{
foreach (object item in context)
{
Result r2 = LayoutType.Utf8.WriteSparse(ref row2, ref childScope, (Utf8String)item);
if (r2 != Result.Success)
{
return r2;
}
childScope.MoveNext(ref row2);
}
return Result.Success;
});
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(GuestsHybridRowSerializer.PhoneNumbersName):
if (value != null)
{
root.Find(ref row, this.phoneNumbersToken);
r = LayoutType.TypedArray.WriteScope(ref row, ref root, this.phoneNumbers.TypeArgs, out RowCursor childScope);
if (r != Result.Success)
{
return r;
}
foreach (object item in (List<object>)value)
{
r = LayoutType.Utf8.WriteSparse(ref row, ref childScope, (Utf8String)item);
if (r != Result.Success)
{
return r;
}
childScope.MoveNext(ref row);
}
root.Skip(ref row, ref childScope);
}
break;
case 0 when key.Equals(GuestsHybridRowSerializer.AddressesName):
if (value != null)
{
root.Find(ref row, this.addressesToken);
r = LayoutType.TypedMap.WriteScope(
ref row,
ref root,
this.addresses.TypeArgs,
(this, (List<object>)value),
(ref RowBuffer row2, ref RowCursor childScope, (GuestsHybridRowSerializer _this, List<object> value) ctx) =>
{
foreach (object item in ctx.value)
{
Result r2 = LayoutType.TypedTuple.WriteScope(
ref row2,
ref childScope,
ctx._this.addresses.TypeArgs,
(ctx._this, (List<object>)item),
(ref RowBuffer row3, ref RowCursor tupleScope, (GuestsHybridRowSerializer _this, List<object> value) ctx2) =>
{
Result r3 = LayoutType.Utf8.WriteSparse(ref row3, ref tupleScope, (Utf8String)ctx2.value[0]);
if (r3 != Result.Success)
{
return r3;
}
tupleScope.MoveNext(ref row3);
return LayoutType.ImmutableUDT.WriteScope(
ref row3,
ref tupleScope,
ctx2._this.addresses.TypeArgs[1].TypeArgs,
(Dictionary<Utf8String, object>)ctx2.value[1],
ctx2._this.addressSerializerWriter);
});
if (r2 != Result.Success)
{
return r2;
}
childScope.MoveNext(ref row2);
}
return Result.Success;
});
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(GuestsHybridRowSerializer.ConfirmNumberName):
if (value != null)
{
r = LayoutType.Utf8.WriteVariable(ref row, ref root, this.confirmNumber, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
default:
Contract.Fail($"Unknown property name: {key}");
break;
}
}
return Result.Success;
}
public override Result ReadBuffer(ref RowBuffer row, ref RowCursor root)
{
Result r = LayoutType.Guid.ReadFixed(ref row, ref root, this.guestId, out Guid _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.Utf8.ReadVariable(ref row, ref root, this.firstName, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.Utf8.ReadVariable(ref row, ref root, this.lastName, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.Utf8.ReadVariable(ref row, ref root, this.title, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
root.Find(ref row, this.emailsToken);
r = LayoutType.TypedArray.ReadScope(ref row, ref root, out RowCursor childScope);
if (r != Result.Success)
{
return r;
}
while (childScope.MoveNext(ref row))
{
r = LayoutType.Utf8.ReadSparse(ref row, ref childScope, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
}
root.Skip(ref row, ref childScope);
root.Find(ref row, this.phoneNumbersToken);
r = LayoutType.TypedArray.ReadScope(ref row, ref root, out childScope);
if (r != Result.Success)
{
return r;
}
while (childScope.MoveNext(ref row))
{
r = LayoutType.Utf8.ReadSparse(ref row, ref childScope, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
}
root.Skip(ref row, ref childScope);
root.Find(ref row, this.addressesToken);
r = LayoutType.TypedMap.ReadScope(ref row, ref root, out childScope);
if (r != Result.Success)
{
return r;
}
while (childScope.MoveNext(ref row))
{
r = LayoutType.TypedTuple.ReadScope(ref row, ref childScope, out RowCursor tupleScope);
if (r != Result.Success)
{
return r;
}
if (!tupleScope.MoveNext(ref row))
{
return Result.InvalidRow;
}
r = LayoutType.Utf8.ReadSparse(ref row, ref tupleScope, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
if (!tupleScope.MoveNext(ref row))
{
return Result.InvalidRow;
}
r = LayoutType.ImmutableUDT.ReadScope(ref row, ref tupleScope, out RowCursor valueScope);
if (r != Result.Success)
{
return r;
}
r = this.addressSerializer.ReadBuffer(ref row, ref valueScope);
if (r != Result.Success)
{
return r;
}
tupleScope.Skip(ref row, ref valueScope);
childScope.Skip(ref row, ref tupleScope);
}
root.Skip(ref row, ref childScope);
return LayoutType.Utf8.ReadVariable(ref row, ref root, this.confirmNumber, out Utf8Span _);
}
}
private sealed class HotelsHybridRowSerializer : HybridRowSerializer
{
private static readonly Utf8String HotelIdName = Utf8String.TranscodeUtf16("hotel_id");
private static readonly Utf8String NameName = Utf8String.TranscodeUtf16("name");
private static readonly Utf8String PhoneName = Utf8String.TranscodeUtf16("phone");
private static readonly Utf8String AddressName = Utf8String.TranscodeUtf16("address");
private readonly LayoutColumn hotelId;
private readonly LayoutColumn name;
private readonly LayoutColumn phone;
private readonly LayoutColumn address;
private readonly StringToken addressToken;
private readonly AddressHybridRowSerializer addressSerializer;
public HotelsHybridRowSerializer(Layout layout, LayoutResolver resolver)
{
layout.TryFind(HotelsHybridRowSerializer.HotelIdName, out this.hotelId);
layout.TryFind(HotelsHybridRowSerializer.NameName, out this.name);
layout.TryFind(HotelsHybridRowSerializer.PhoneName, out this.phone);
layout.TryFind(HotelsHybridRowSerializer.AddressName, out this.address);
layout.Tokenizer.TryFindToken(this.address.Path, out this.addressToken);
this.addressSerializer = new AddressHybridRowSerializer(resolver.Resolve(this.address.TypeArgs.SchemaId), resolver);
}
public override Result WriteBuffer(ref RowBuffer row, ref RowCursor root, Dictionary<Utf8String, object> tableValue)
{
foreach ((Utf8String key, object value) in tableValue)
{
Result r;
switch (0)
{
case 0 when key.Equals(HotelsHybridRowSerializer.HotelIdName):
if (value != null)
{
r = LayoutType.Utf8.WriteFixed(ref row, ref root, this.hotelId, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(HotelsHybridRowSerializer.NameName):
if (value != null)
{
r = LayoutType.Utf8.WriteVariable(ref row, ref root, this.name, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(HotelsHybridRowSerializer.PhoneName):
if (value != null)
{
r = LayoutType.Utf8.WriteVariable(ref row, ref root, this.phone, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(HotelsHybridRowSerializer.AddressName):
if (value != null)
{
root.Find(ref row, this.addressToken);
r = LayoutType.UDT.WriteScope(ref row, ref root, this.address.TypeArgs, out RowCursor childScope);
if (r != Result.Success)
{
return r;
}
r = this.addressSerializer.WriteBuffer(ref row, ref childScope, (Dictionary<Utf8String, object>)value);
if (r != Result.Success)
{
return r;
}
root.Skip(ref row, ref childScope);
}
break;
default:
Contract.Fail($"Unknown property name: {key}");
break;
}
}
return Result.Success;
}
public override Result ReadBuffer(ref RowBuffer row, ref RowCursor root)
{
Result r = LayoutType.Utf8.ReadFixed(ref row, ref root, this.hotelId, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.Utf8.ReadVariable(ref row, ref root, this.name, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.Utf8.ReadVariable(ref row, ref root, this.phone, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
root.Find(ref row, this.addressToken);
r = LayoutType.UDT.ReadScope(ref row, ref root, out RowCursor childScope);
if (r != Result.Success)
{
return r;
}
r = this.addressSerializer.ReadBuffer(ref row, ref childScope);
if (r != Result.Success)
{
return r;
}
root.Skip(ref row, ref childScope);
return Result.Success;
}
}
private sealed class RoomsHybridRowSerializer : HybridRowSerializer
{
private static readonly Utf8String HotelIdName = Utf8String.TranscodeUtf16("hotel_id");
private static readonly Utf8String DateName = Utf8String.TranscodeUtf16("date");
private static readonly Utf8String RoomNumberName = Utf8String.TranscodeUtf16("room_number");
private static readonly Utf8String IsAvailableName = Utf8String.TranscodeUtf16("is_available");
private readonly LayoutColumn hotelId;
private readonly LayoutColumn date;
private readonly LayoutColumn roomNumber;
private readonly LayoutColumn isAvailable;
// ReSharper disable once UnusedParameter.Local
public RoomsHybridRowSerializer(Layout layout, LayoutResolver resolver)
{
layout.TryFind(RoomsHybridRowSerializer.HotelIdName, out this.hotelId);
layout.TryFind(RoomsHybridRowSerializer.DateName, out this.date);
layout.TryFind(RoomsHybridRowSerializer.RoomNumberName, out this.roomNumber);
layout.TryFind(RoomsHybridRowSerializer.IsAvailableName, out this.isAvailable);
}
public override Result WriteBuffer(ref RowBuffer row, ref RowCursor root, Dictionary<Utf8String, object> tableValue)
{
foreach ((Utf8String key, object value) in tableValue)
{
Result r;
switch (0)
{
case 0 when key.Equals(RoomsHybridRowSerializer.HotelIdName):
if (value != null)
{
r = LayoutType.Utf8.WriteFixed(ref row, ref root, this.hotelId, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(RoomsHybridRowSerializer.DateName):
if (value != null)
{
r = LayoutType.DateTime.WriteFixed(ref row, ref root, this.date, (DateTime)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(RoomsHybridRowSerializer.RoomNumberName):
if (value != null)
{
r = LayoutType.UInt8.WriteFixed(ref row, ref root, this.roomNumber, (byte)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(RoomsHybridRowSerializer.IsAvailableName):
if (value != null)
{
r = LayoutType.Boolean.WriteFixed(ref row, ref root, this.isAvailable, (bool)value);
if (r != Result.Success)
{
return r;
}
}
break;
default:
Contract.Fail($"Unknown property name: {key}");
break;
}
}
return Result.Success;
}
public override Result ReadBuffer(ref RowBuffer row, ref RowCursor root)
{
Result r = LayoutType.Utf8.ReadFixed(ref row, ref root, this.hotelId, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.DateTime.ReadFixed(ref row, ref root, this.date, out DateTime _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.UInt8.ReadFixed(ref row, ref root, this.roomNumber, out byte _);
if (r != Result.Success)
{
return r;
}
return LayoutType.Boolean.ReadFixed(ref row, ref root, this.isAvailable, out bool _);
}
}
private sealed class PostalCodeHybridRowSerializer : HybridRowSerializer
{
private static readonly Utf8String ZipName = Utf8String.TranscodeUtf16("zip");
private static readonly Utf8String Plus4Name = Utf8String.TranscodeUtf16("plus4");
private readonly LayoutColumn zip;
private readonly LayoutColumn plus4;
private readonly StringToken plus4Token;
// ReSharper disable once UnusedParameter.Local
public PostalCodeHybridRowSerializer(Layout layout, LayoutResolver resolver)
{
layout.TryFind(PostalCodeHybridRowSerializer.ZipName, out this.zip);
layout.TryFind(PostalCodeHybridRowSerializer.Plus4Name, out this.plus4);
layout.Tokenizer.TryFindToken(this.plus4.Path, out this.plus4Token);
}
public override Result WriteBuffer(ref RowBuffer row, ref RowCursor root, Dictionary<Utf8String, object> tableValue)
{
foreach ((Utf8String key, object value) in tableValue)
{
Result r;
switch (0)
{
case 0 when key.Equals(PostalCodeHybridRowSerializer.ZipName):
if (value != null)
{
r = LayoutType.Int32.WriteFixed(ref row, ref root, this.zip, (int)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(PostalCodeHybridRowSerializer.Plus4Name):
if (value != null)
{
root.Find(ref row, this.plus4Token);
r = LayoutType.Int16.WriteSparse(ref row, ref root, (short)value);
if (r != Result.Success)
{
return r;
}
}
break;
default:
Contract.Fail($"Unknown property name: {key}");
break;
}
}
return Result.Success;
}
public override Result ReadBuffer(ref RowBuffer row, ref RowCursor root)
{
Result r = LayoutType.Int32.ReadFixed(ref row, ref root, this.zip, out int _);
if (r != Result.Success)
{
return r;
}
root.Find(ref row, this.plus4Token);
return LayoutType.Int16.ReadSparse(ref row, ref root, out short _);
}
}
private sealed class AddressHybridRowSerializer : HybridRowSerializer
{
private static readonly Utf8String StreetName = Utf8String.TranscodeUtf16("street");
private static readonly Utf8String CityName = Utf8String.TranscodeUtf16("city");
private static readonly Utf8String StateName = Utf8String.TranscodeUtf16("state");
private static readonly Utf8String PostalCodeName = Utf8String.TranscodeUtf16("postal_code");
private readonly LayoutColumn street;
private readonly LayoutColumn city;
private readonly LayoutColumn state;
private readonly LayoutColumn postalCode;
private readonly StringToken postalCodeToken;
private readonly PostalCodeHybridRowSerializer postalCodeSerializer;
public AddressHybridRowSerializer(Layout layout, LayoutResolver resolver)
{
layout.TryFind(AddressHybridRowSerializer.StreetName, out this.street);
layout.TryFind(AddressHybridRowSerializer.CityName, out this.city);
layout.TryFind(AddressHybridRowSerializer.StateName, out this.state);
layout.TryFind(AddressHybridRowSerializer.PostalCodeName, out this.postalCode);
layout.Tokenizer.TryFindToken(this.postalCode.Path, out this.postalCodeToken);
this.postalCodeSerializer = new PostalCodeHybridRowSerializer(resolver.Resolve(this.postalCode.TypeArgs.SchemaId), resolver);
}
public override Result WriteBuffer(ref RowBuffer row, ref RowCursor root, Dictionary<Utf8String, object> tableValue)
{
foreach ((Utf8String key, object value) in tableValue)
{
Result r;
switch (0)
{
case 0 when key.Equals(AddressHybridRowSerializer.StreetName):
if (value != null)
{
r = LayoutType.Utf8.WriteVariable(ref row, ref root, this.street, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(AddressHybridRowSerializer.CityName):
if (value != null)
{
r = LayoutType.Utf8.WriteVariable(ref row, ref root, this.city, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(AddressHybridRowSerializer.StateName):
if (value != null)
{
r = LayoutType.Utf8.WriteFixed(ref row, ref root, this.state, (Utf8String)value);
if (r != Result.Success)
{
return r;
}
}
break;
case 0 when key.Equals(AddressHybridRowSerializer.PostalCodeName):
if (value != null)
{
root.Find(ref row, this.postalCodeToken);
r = LayoutType.UDT.WriteScope(ref row, ref root, this.postalCode.TypeArgs, out RowCursor childScope);
if (r != Result.Success)
{
return r;
}
r = this.postalCodeSerializer.WriteBuffer(ref row, ref childScope, (Dictionary<Utf8String, object>)value);
if (r != Result.Success)
{
return r;
}
root.Skip(ref row, ref childScope);
}
break;
default:
Contract.Fail($"Unknown property name: {key}");
break;
}
}
return Result.Success;
}
public override Result ReadBuffer(ref RowBuffer row, ref RowCursor root)
{
Result r = LayoutType.Utf8.ReadVariable(ref row, ref root, this.street, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.Utf8.ReadVariable(ref row, ref root, this.city, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
r = LayoutType.Utf8.ReadFixed(ref row, ref root, this.state, out Utf8Span _);
if (r != Result.Success)
{
return r;
}
root.Find(ref row, this.postalCodeToken);
r = LayoutType.UDT.ReadScope(ref row, ref root, out RowCursor childScope);
if (r != Result.Success)
{
return r;
}
r = this.postalCodeSerializer.ReadBuffer(ref row, ref childScope);
if (r != Result.Success)
{
return r;
}
root.Skip(ref row, ref childScope);
return Result.Success;
}
}
}
}

View File

@@ -0,0 +1,100 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
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.HybridRowGenerator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[DeploymentItem(TestData.SchemaFile, TestData.Target)]
public sealed class GenerateBenchmarkSuite : BenchmarkSuiteBase
{
private string sdl;
[TestInitialize]
public void ParseNamespaceExample()
{
this.sdl = File.ReadAllText(TestData.SchemaFile);
Namespace schema = Namespace.Parse(this.sdl);
this.DefaultResolver = new LayoutResolverNamespace(schema, SystemSchema.LayoutResolver);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task GenerateHotelBenchmarkAsync()
{
await this.GenerateBenchmarkAsync("Hotels", 100, TestData.HotelExpected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task GenerateRoomsBenchmarkAsync()
{
await this.GenerateBenchmarkAsync("Available_Rooms_By_Hotel_Date", 100, TestData.RoomsExpected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task GenerateGuestsBenchmarkAsync()
{
await this.GenerateBenchmarkAsync("Guests", 50, TestData.GuestsExpected);
}
private static List<Dictionary<Utf8String, object>> GenerateBenchmarkInputs(
LayoutResolverNamespace resolver,
string schemaName,
int outerLoopIterations)
{
HybridRowGeneratorConfig generatorConfig = new HybridRowGeneratorConfig();
const int seed = 42;
RandomGenerator rand = new RandomGenerator(new Random(seed));
HybridRowValueGenerator valueGenerator = new HybridRowValueGenerator(rand, generatorConfig);
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
List<Dictionary<Utf8String, object>> rows = new List<Dictionary<Utf8String, object>>(outerLoopIterations);
for (int iteration = 0; iteration != outerLoopIterations; iteration += outerLoopIterations < 0 ? 0 : 1)
{
TypeArgument typeArg = new TypeArgument(LayoutType.UDT, new TypeArgumentList(layout.SchemaId));
Dictionary<Utf8String, object> rowValue = (Dictionary<Utf8String, object>)valueGenerator.GenerateLayoutType(resolver, typeArg);
rows.Add(rowValue);
}
return rows;
}
private async Task GenerateBenchmarkAsync(string schemaName, int outerLoopIterations, string expectedFile)
{
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
List<Dictionary<Utf8String, object>>
rows = GenerateBenchmarkSuite.GenerateBenchmarkInputs(resolver, schemaName, outerLoopIterations);
Schema tableSchema = resolver.Namespace.Schemas.Find(x => x.Name == schemaName);
TypeArgument typeArg = new TypeArgument(LayoutType.UDT, new TypeArgumentList(tableSchema.SchemaId));
bool allMatch = rows.Count == expected.Count;
for (int i = 0; allMatch && i < rows.Count; i++)
{
allMatch |= HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, expected[i], rows[i], typeArg);
}
if (!allMatch)
{
await BenchmarkSuiteBase.WriteAllRowsAsync(expectedFile, this.sdl, resolver, resolver.Resolve(tableSchema.SchemaId), rows);
Console.WriteLine("Updated expected file at: {0}", Path.GetFullPath(expectedFile));
Assert.IsTrue(allMatch, "Expected output does not match expected file.");
}
}
}
}

View File

@@ -0,0 +1,7 @@
%NugetMachineInstallRoot%\google.protobuf.tools\3.5.1\tools\windows_x64\protoc.exe --csharp_out=CassandraProto TestData\CassandraHotelSchema.proto --proto_path=. --proto_path=%NugetMachineInstallRoot%\google.protobuf.tools\3.5.1\tools
move CassandraProto\CassandraHotelSchema.cs CassandraProto\CassandraHotelSchema.tmp
echo #pragma warning disable DontUseNamespaceAliases // Namespace Aliases should be avoided > CassandraProto\CassandraHotelSchema.cs
echo #pragma warning disable NamespaceMatchesFolderStructure // Namespace Declarations must match folder structure >> CassandraProto\CassandraHotelSchema.cs
cat CassandraProto\CassandraHotelSchema.tmp >> CassandraProto\CassandraHotelSchema.cs
del CassandraProto\CassandraHotelSchema.tmp

View File

@@ -0,0 +1,43 @@
RunId,Model,Operation,Schema,API,Iterations,Size (bytes),Total (ms),Duration (ms),Allocated (bytes),ThreadId,Gen0,Gen1,Gen2,Total Allocated (bytes)
636970252347090152,CodeGen,Write,Guests,Protobuf,50,1328,795.36630000,0.01590733,5084.00000000,8,40,0,0,254200600
636970252347090152,CodeGen,Write,Hotels,Protobuf,100,156,2303.97720000,0.00230398,861.00000000,8,137,0,0,861760000
636970252347090152,CodeGen,Write,Rooms,Protobuf,100,31,629.30810000,0.00062931,343.00000000,12,54,0,0,343440072
636970252347090152,CodeGen,Read,Guests,Protobuf,50,1328,313.95220000,0.00627904,3556.00000000,12,28,0,0,177840000
636970252347090152,CodeGen,Read,Hotels,Protobuf,100,156,795.37780000,0.00079538,485.00000000,13,77,0,0,485760072
636970252347090152,CodeGen,Read,Rooms,Protobuf,100,31,252.23010000,0.00025223,183.00000000,8,29,0,0,183440000
636970252347090152,CodeGen,Write,Guests,HybridRowGen,50,1222,1118.28520000,0.02236570,0.00000000,12,0,0,0,0
636970252347090152,CodeGen,Write,Hotels,HybridRowGen,100,143,2044.81250000,0.00204481,0.00000000,8,0,0,0,0
636970252347090152,CodeGen,Write,Rooms,HybridRowGen,100,23,534.58980000,0.00053459,0.00000000,8,0,0,0,0
636970252347090152,CodeGen,Read,Guests,HybridRowGen,50,1222,207.97910000,0.00415958,168.00000000,12,1,0,0,8400000
636970252347090152,CodeGen,Read,Hotels,HybridRowGen,100,143,731.67050000,0.00073167,0.00000000,12,0,0,0,0
636970252347090152,CodeGen,Read,Rooms,HybridRowGen,100,23,184.29990000,0.00018430,0.00000000,12,0,0,0,0
636970252347090152,Schematized,Write,Hotels,JSON,100,342,926.68970000,0.00926690,1653.00000000,13,26,1,0,165382696
636970252347090152,Schematized,Write,Rooms,JSON,100,129,3728.27760000,0.00372828,871.00000000,12,138,0,0,871474472
636970252347090152,Schematized,Write,Guests,JSON,50,2467,2236.45660000,0.04472913,6252.00000000,13,49,0,0,312604400
636970252347090152,Schematized,Read,Hotels,JSON,100,342,283.91940000,0.00283919,8557.00000000,12,136,0,0,855776000
636970252347090152,Schematized,Read,Rooms,JSON,100,129,1629.21600000,0.00162922,7846.00000000,13,1246,0,0,7846400000
636970252347090152,Schematized,Read,Guests,JSON,50,2467,626.56730000,0.01253135,11421.00000000,12,90,0,0,571096000
636970252347090152,Schematized,Write,Hotels,BSON,100,240,373.35650000,0.00373357,2125.00000000,13,33,0,0,212576000
636970252347090152,Schematized,Write,Rooms,BSON,100,74,1215.03590000,0.00121504,815.00000000,13,129,0,0,815440000
636970252347090152,Schematized,Write,Guests,BSON,50,1782,1018.99680000,0.02037994,9041.00000000,12,71,0,0,452072000
636970252347090152,Schematized,Read,Hotels,BSON,100,240,199.81420000,0.00199814,2688.00000000,13,42,0,0,268800000
636970252347090152,Schematized,Read,Rooms,BSON,100,74,888.62140000,0.00088862,1408.00000000,6,223,0,0,1408000000
636970252347090152,Schematized,Read,Guests,BSON,50,1782,431.03020000,0.00862060,9146.00000000,13,72,0,0,457344000
636970252347090152,Schematized,Write,Hotels,Layout,100,143,264.21190000,0.00264212,0.00000000,6,0,0,0,0
636970252347090152,Schematized,Write,Rooms,Layout,100,23,64.51090000,0.00064511,0.00000000,6,0,0,0,0
636970252347090152,Schematized,Write,Guests,Layout,50,1222,1221.28360000,0.02442567,0.00000000,12,0,0,0,0
636970252347090152,Schematized,Read,Hotels,Layout,100,143,79.88940000,0.00079889,0.00000000,13,0,0,0,0
636970252347090152,Schematized,Read,Rooms,Layout,100,23,21.45470000,0.00021455,0.00000000,12,0,0,0,0
636970252347090152,Schematized,Read,Guests,Layout,50,1222,248.11960000,0.00496239,168.00000000,6,1,0,0,8400000
636970252347090152,Schematized,Write,Hotels,HybridRow,100,143,352.73410000,0.00352734,0.00000000,12,0,0,0,0
636970252347090152,Schematized,Write,Rooms,HybridRow,100,23,1129.17010000,0.00112917,0.00000000,6,0,0,0,0
636970252347090152,Schematized,Write,Guests,HybridRow,50,1222,1418.34740000,0.02836695,0.00000000,12,0,0,0,0
636970252347090152,Schematized,Read,Hotels,HybridRow,100,143,107.00560000,0.00107006,0.00000000,6,0,0,0,0
636970252347090152,Schematized,Read,Rooms,HybridRow,100,23,307.03740000,0.00030704,0.00000000,12,0,0,0,0
636970252347090152,Schematized,Read,Guests,HybridRow,50,1222,356.78610000,0.00713572,168.00000000,6,1,0,0,8400000
636970252347090152,Unschematized,Write,Messages1K,HybridRowSparse,1001,3405,312.43510000,0.03121230,0.00000000,13,0,0,0,0
636970252347090152,Unschematized,Read,Messages1K,HybridRowSparse,1001,3405,153.64990000,0.01534964,0.00000000,12,0,0,0,0
636970252347090152,Unschematized,Write,Messages1K,BSON,1001,3949,301.69900000,0.03013976,30476.00000000,13,48,0,0,305065120
636970252347090152,Unschematized,Read,Messages1K,BSON,1001,3949,177.64080000,0.01774633,23166.00000000,8,36,0,0,231892392
636970252347090152,Unschematized,Write,Messages1K,JSON,1001,5693,999.48440000,0.09984859,16900.00000000,8,26,0,0,169178696
636970252347090152,Unschematized,Read,Messages1K,JSON,1001,5693,291.96320000,0.02916715,32040.00000000,8,49,1,1,320720528
1 RunId Model Operation Schema API Iterations Size (bytes) Total (ms) Duration (ms) Allocated (bytes) ThreadId Gen0 Gen1 Gen2 Total Allocated (bytes)
2 636970252347090152 CodeGen Write Guests Protobuf 50 1328 795.36630000 0.01590733 5084.00000000 8 40 0 0 254200600
3 636970252347090152 CodeGen Write Hotels Protobuf 100 156 2303.97720000 0.00230398 861.00000000 8 137 0 0 861760000
4 636970252347090152 CodeGen Write Rooms Protobuf 100 31 629.30810000 0.00062931 343.00000000 12 54 0 0 343440072
5 636970252347090152 CodeGen Read Guests Protobuf 50 1328 313.95220000 0.00627904 3556.00000000 12 28 0 0 177840000
6 636970252347090152 CodeGen Read Hotels Protobuf 100 156 795.37780000 0.00079538 485.00000000 13 77 0 0 485760072
7 636970252347090152 CodeGen Read Rooms Protobuf 100 31 252.23010000 0.00025223 183.00000000 8 29 0 0 183440000
8 636970252347090152 CodeGen Write Guests HybridRowGen 50 1222 1118.28520000 0.02236570 0.00000000 12 0 0 0 0
9 636970252347090152 CodeGen Write Hotels HybridRowGen 100 143 2044.81250000 0.00204481 0.00000000 8 0 0 0 0
10 636970252347090152 CodeGen Write Rooms HybridRowGen 100 23 534.58980000 0.00053459 0.00000000 8 0 0 0 0
11 636970252347090152 CodeGen Read Guests HybridRowGen 50 1222 207.97910000 0.00415958 168.00000000 12 1 0 0 8400000
12 636970252347090152 CodeGen Read Hotels HybridRowGen 100 143 731.67050000 0.00073167 0.00000000 12 0 0 0 0
13 636970252347090152 CodeGen Read Rooms HybridRowGen 100 23 184.29990000 0.00018430 0.00000000 12 0 0 0 0
14 636970252347090152 Schematized Write Hotels JSON 100 342 926.68970000 0.00926690 1653.00000000 13 26 1 0 165382696
15 636970252347090152 Schematized Write Rooms JSON 100 129 3728.27760000 0.00372828 871.00000000 12 138 0 0 871474472
16 636970252347090152 Schematized Write Guests JSON 50 2467 2236.45660000 0.04472913 6252.00000000 13 49 0 0 312604400
17 636970252347090152 Schematized Read Hotels JSON 100 342 283.91940000 0.00283919 8557.00000000 12 136 0 0 855776000
18 636970252347090152 Schematized Read Rooms JSON 100 129 1629.21600000 0.00162922 7846.00000000 13 1246 0 0 7846400000
19 636970252347090152 Schematized Read Guests JSON 50 2467 626.56730000 0.01253135 11421.00000000 12 90 0 0 571096000
20 636970252347090152 Schematized Write Hotels BSON 100 240 373.35650000 0.00373357 2125.00000000 13 33 0 0 212576000
21 636970252347090152 Schematized Write Rooms BSON 100 74 1215.03590000 0.00121504 815.00000000 13 129 0 0 815440000
22 636970252347090152 Schematized Write Guests BSON 50 1782 1018.99680000 0.02037994 9041.00000000 12 71 0 0 452072000
23 636970252347090152 Schematized Read Hotels BSON 100 240 199.81420000 0.00199814 2688.00000000 13 42 0 0 268800000
24 636970252347090152 Schematized Read Rooms BSON 100 74 888.62140000 0.00088862 1408.00000000 6 223 0 0 1408000000
25 636970252347090152 Schematized Read Guests BSON 50 1782 431.03020000 0.00862060 9146.00000000 13 72 0 0 457344000
26 636970252347090152 Schematized Write Hotels Layout 100 143 264.21190000 0.00264212 0.00000000 6 0 0 0 0
27 636970252347090152 Schematized Write Rooms Layout 100 23 64.51090000 0.00064511 0.00000000 6 0 0 0 0
28 636970252347090152 Schematized Write Guests Layout 50 1222 1221.28360000 0.02442567 0.00000000 12 0 0 0 0
29 636970252347090152 Schematized Read Hotels Layout 100 143 79.88940000 0.00079889 0.00000000 13 0 0 0 0
30 636970252347090152 Schematized Read Rooms Layout 100 23 21.45470000 0.00021455 0.00000000 12 0 0 0 0
31 636970252347090152 Schematized Read Guests Layout 50 1222 248.11960000 0.00496239 168.00000000 6 1 0 0 8400000
32 636970252347090152 Schematized Write Hotels HybridRow 100 143 352.73410000 0.00352734 0.00000000 12 0 0 0 0
33 636970252347090152 Schematized Write Rooms HybridRow 100 23 1129.17010000 0.00112917 0.00000000 6 0 0 0 0
34 636970252347090152 Schematized Write Guests HybridRow 50 1222 1418.34740000 0.02836695 0.00000000 12 0 0 0 0
35 636970252347090152 Schematized Read Hotels HybridRow 100 143 107.00560000 0.00107006 0.00000000 6 0 0 0 0
36 636970252347090152 Schematized Read Rooms HybridRow 100 23 307.03740000 0.00030704 0.00000000 12 0 0 0 0
37 636970252347090152 Schematized Read Guests HybridRow 50 1222 356.78610000 0.00713572 168.00000000 6 1 0 0 8400000
38 636970252347090152 Unschematized Write Messages1K HybridRowSparse 1001 3405 312.43510000 0.03121230 0.00000000 13 0 0 0 0
39 636970252347090152 Unschematized Read Messages1K HybridRowSparse 1001 3405 153.64990000 0.01534964 0.00000000 12 0 0 0 0
40 636970252347090152 Unschematized Write Messages1K BSON 1001 3949 301.69900000 0.03013976 30476.00000000 13 48 0 0 305065120
41 636970252347090152 Unschematized Read Messages1K BSON 1001 3949 177.64080000 0.01774633 23166.00000000 8 36 0 0 231892392
42 636970252347090152 Unschematized Write Messages1K JSON 1001 5693 999.48440000 0.09984859 16900.00000000 8 26 0 0 169178696
43 636970252347090152 Unschematized Read Messages1K JSON 1001 5693 291.96320000 0.02916715 32040.00000000 8 49 1 1 320720528

View File

@@ -0,0 +1,132 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
public ref struct JsonModelRowGenerator
{
private RowBuffer row;
public JsonModelRowGenerator(int capacity, Layout layout, LayoutResolver resolver, ISpanResizer<byte> resizer = default)
{
this.row = new RowBuffer(capacity, resizer);
this.row.InitLayout(HybridRowVersion.V1, layout, resolver);
}
public int Length => this.row.Length;
public byte[] ToArray() => this.row.ToArray();
public void WriteTo(Stream stream)
{
this.row.WriteTo(stream);
}
public bool ReadFrom(Stream stream, int length)
{
return this.row.ReadFrom(stream, length, HybridRowVersion.V1, this.row.Resolver);
}
public void Reset()
{
Layout layout = this.row.Resolver.Resolve(this.row.Header.SchemaId);
this.row.InitLayout(HybridRowVersion.V1, layout, this.row.Resolver);
}
public RowReader GetReader()
{
return new RowReader(ref this.row);
}
public Result WriteBuffer(Dictionary<Utf8String, object> value)
{
return RowWriter.WriteBuffer(
ref this.row,
value,
(ref RowWriter writer, TypeArgument typeArg, Dictionary<Utf8String, object> dict) =>
{
foreach ((Utf8String propPath, object propValue) in dict)
{
Result result = JsonModelRowGenerator.JsonModelSwitch(ref writer, propPath, propValue);
if (result != Result.Success)
{
return result;
}
}
return Result.Success;
});
}
private static Result JsonModelSwitch(ref RowWriter writer, Utf8String path, object value)
{
switch (value)
{
case null:
return writer.WriteNull(path);
case bool x:
return writer.WriteBool(path, x);
case long x:
return writer.WriteInt64(path, x);
case double x:
return writer.WriteFloat64(path, x);
case string x:
return writer.WriteString(path, x);
case Utf8String x:
return writer.WriteString(path, x.Span);
case byte[] x:
return writer.WriteBinary(path, x);
case ReadOnlyMemory<byte> x:
return writer.WriteBinary(path, x.Span);
case Dictionary<Utf8String, object> x:
return writer.WriteScope(
path,
new TypeArgument(LayoutType.Object),
x,
(ref RowWriter writer2, TypeArgument typeArg, Dictionary<Utf8String, object> dict) =>
{
foreach ((Utf8String propPath, object propValue) in dict)
{
Result result = JsonModelRowGenerator.JsonModelSwitch(ref writer2, propPath, propValue);
if (result != Result.Success)
{
return result;
}
}
return Result.Success;
});
case List<object> x:
return writer.WriteScope(
path,
new TypeArgument(LayoutType.Array),
x,
(ref RowWriter writer2, TypeArgument typeArg, List<object> list) =>
{
foreach (object elm in list)
{
Result result = JsonModelRowGenerator.JsonModelSwitch(ref writer2, null, elm);
if (result != Result.Success)
{
return result;
}
}
return Result.Success;
});
default:
Contract.Assert(false, $"Unknown type will be ignored: {value.GetType().Name}");
return Result.Failure;
}
}
}
}

View File

@@ -0,0 +1,89 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.IO;
using System.Text;
internal class Measurements : IDisposable
{
private static readonly long RunId = DateTime.UtcNow.Ticks;
private readonly FileStream file;
private readonly TextWriter writer;
public Measurements(string path)
{
FileInfo info = new FileInfo(path);
if (info.Exists)
{
this.file = new FileStream(path, FileMode.Append);
this.writer = new StreamWriter(this.file, Encoding.ASCII);
}
else
{
this.file = new FileStream(path, FileMode.CreateNew);
this.writer = new StreamWriter(this.file, Encoding.ASCII);
this.writer.WriteLine(
"RunId,Model,Operation,Schema,API,Iterations,Size (bytes),Total (ms),Duration (ms),Allocated (bytes),ThreadId,Gen0,Gen1,Gen2,Total Allocated (bytes)");
}
}
public void Dispose()
{
this.writer.Flush();
this.writer.Dispose();
this.file.Dispose();
}
public void WriteMeasurement(string model, string operation, string schema, string api,
int outerLoopIterations, int innerLoopIterations, long totalSize, double totalDurationMs,
int threadId, int gen0, int gen1, int gen2, long totalAllocatedBytes)
{
Console.WriteLine(
"RunId: {0}, \nModel: {1} \nOperation: {2} \nSchema: {3} \nAPI: {4}",
Measurements.RunId,
model,
operation,
schema,
api);
Console.WriteLine(
"\n\nIterations: {0} \nSize (bytes): {1:F0} \nTotal (ms): {2:F4} \nDuration (ms): {3:F4} \nAllocated (bytes): {4:F4}",
outerLoopIterations,
totalSize / outerLoopIterations,
totalDurationMs,
totalDurationMs / (outerLoopIterations * innerLoopIterations),
totalAllocatedBytes / (outerLoopIterations * innerLoopIterations));
Console.WriteLine(
"\n\nThread: {0} \nCollections: {1}, {2}, {3} \nTotal Allocated: {4:n0} (bytes)",
threadId,
gen0,
gen1,
gen2,
totalAllocatedBytes);
this.writer.WriteLine(
"{0},{1},{2},{3},{4},{5},{6:F0},{7:F8},{8:F8},{9:F8},{10},{11},{12},{13},{14:0}",
Measurements.RunId,
model,
operation,
schema,
api,
outerLoopIterations,
totalSize / outerLoopIterations,
totalDurationMs,
totalDurationMs / (outerLoopIterations * innerLoopIterations),
totalAllocatedBytes / (outerLoopIterations * innerLoopIterations),
threadId,
gen0,
gen1,
gen2,
totalAllocatedBytes);
}
}
}

View File

@@ -0,0 +1,128 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using JetBrains.Profiler.Api;
using Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator;
[SuppressMessage("Microsoft.Reliability", "CA2001:Avoid calling problematic methods", Justification = "Perf Benchmark")]
public class MicroBenchmarkSuiteBase : BenchmarkSuiteBase
{
private const int WarmCount = 5;
private const string MetricsResultFile = "HybridRowPerf.csv";
[SuppressMessage("Microsoft.Reliability", "CA2001:Avoid calling problematic methods", Justification = "Perf Benchmark")]
private protected static void Benchmark<TValue>(
string model,
string operation,
string schema,
string api,
int innerLoopIterations,
ref BenchmarkContext context,
BenchmarkBody<TValue> loopBody,
BenchmarkMeasure<TValue> measure,
List<TValue> expected)
{
Stopwatch sw = new Stopwatch();
double durationMs = 0;
long rowSize = 0;
// Warm
int warm = Math.Min(MicroBenchmarkSuiteBase.WarmCount, expected.Count);
for (int i = 0; i < warm; i++)
{
for (int innerLoop = 0; innerLoop < innerLoopIterations; innerLoop++)
{
loopBody(ref context, expected[i]);
}
}
// Execute
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(1000);
int gen0 = GC.CollectionCount(0);
int gen1 = GC.CollectionCount(1);
int gen2 = GC.CollectionCount(2);
long allocated = GC.GetAllocatedBytesForCurrentThread();
int threadId = Thread.CurrentThread.ManagedThreadId;
ThreadPriority currentPriority = Thread.CurrentThread.Priority;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
MemoryProfiler.CollectAllocations(true);
MemoryProfiler.GetSnapshot();
try
{
foreach (TValue tableValue in expected)
{
sw.Restart();
MicroBenchmarkSuiteBase.BenchmarkInnerLoop(innerLoopIterations, tableValue, ref context, loopBody);
sw.Stop();
durationMs += sw.Elapsed.TotalMilliseconds;
rowSize += measure(ref context, tableValue);
}
}
finally
{
Thread.CurrentThread.Priority = currentPriority;
gen0 = GC.CollectionCount(0) - gen0;
gen1 = GC.CollectionCount(1) - gen1;
gen2 = GC.CollectionCount(2) - gen2;
allocated = GC.GetAllocatedBytesForCurrentThread() - allocated;
MemoryProfiler.GetSnapshot();
MemoryProfiler.CollectAllocations(false);
}
using (Measurements m = new Measurements(MicroBenchmarkSuiteBase.MetricsResultFile))
{
m.WriteMeasurement(
model: model,
operation: operation,
schema: schema,
api: api,
outerLoopIterations: expected.Count,
innerLoopIterations: innerLoopIterations,
totalSize: rowSize,
totalDurationMs: durationMs,
threadId: threadId,
gen0: gen0,
gen1: gen1,
gen2: gen2,
totalAllocatedBytes: allocated);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void BenchmarkInnerLoop<TValue>(
int innerLoopIterations,
TValue tableValue,
ref BenchmarkContext context,
BenchmarkBody<TValue> loopBody)
{
for (int innerLoop = 0; innerLoop < innerLoopIterations; innerLoop++)
{
loopBody(ref context, tableValue);
}
}
private protected ref struct BenchmarkContext
{
public CodeGenRowGenerator CodeGenWriter;
public ProtobufRowGenerator ProtobufWriter;
public WriteRowGenerator PatchWriter;
public StreamingRowGenerator StreamingWriter;
public JsonModelRowGenerator JsonModelWriter;
}
private protected delegate void BenchmarkBody<in TValue>(ref BenchmarkContext context, TValue value);
private protected delegate long BenchmarkMeasure<in TValue>(ref BenchmarkContext context, TValue value);
}
}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<ProjectGuid>{26A73F4A-AC9E-46AF-9445-286EE9EDA3EE}</ProjectGuid>
<OutputType>Library</OutputType>
<SigningType>Test</SigningType>
<RootNamespace>Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf</RootNamespace>
<AssemblyName>Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf</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>
<None Include="TestData\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(DocDBRoot)\Cosmos\Core\Core\Microsoft.Azure.Cosmos.Core.csproj" />
<ProjectReference Include="..\HybridRowGenerator\Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj" />
<ProjectReference Include="..\HybridRow\Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Profiler.Api" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Google.Protobuf.Tools" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.VisualStudio.TestPlatform" />
<PackageReference Include="MongoDB.Bson.Signed" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
<PackageReference Include="System.ValueTuple" />
<PackageReference Include="VisualStudio.UnitTest.Corext" />
</ItemGroup>
<ItemGroup>
<None Update="HybridRowPerf.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

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.Perf")]
// 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("26A73F4A-AC9E-46AF-9445-286EE9EDA3EE")]

View File

@@ -0,0 +1,315 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using Google.Protobuf;
using Google.Protobuf.Collections;
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.VisualStudio.TestTools.UnitTesting;
#pragma warning disable DontUseNamespaceAliases // Namespace Aliases should be avoided
using pb = Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.CassandraHotel.Protobuf;
#pragma warning restore DontUseNamespaceAliases // Namespace Aliases should be avoided
internal ref struct ProtobufRowGenerator
{
private readonly string schemaName;
private readonly byte[] buffer;
private ReadOnlySpan<byte> active;
public ProtobufRowGenerator(string schemaName, int capacity)
{
this.schemaName = schemaName;
this.buffer = new byte[capacity];
this.active = this.buffer;
}
public void WriteBuffer(Dictionary<Utf8String, object> tableValue)
{
switch (this.schemaName)
{
case "Hotels":
this.WriteBufferHotel(tableValue);
break;
case "Guests":
this.WriteBufferGuest(tableValue);
break;
case "Available_Rooms_By_Hotel_Date":
this.WriteBufferRoom(tableValue);
break;
default:
Contract.Fail($"Unknown schema will be ignored: {this.schemaName}");
break;
}
}
public void ReadBuffer(byte[] buffer)
{
switch (this.schemaName)
{
case "Hotels":
ProtobufRowGenerator.ReadBufferHotel(buffer);
break;
case "Guests":
ProtobufRowGenerator.ReadBufferGuest(buffer);
break;
case "Available_Rooms_By_Hotel_Date":
ProtobufRowGenerator.ReadBufferRoom(buffer);
break;
default:
Contract.Fail($"Unknown schema will be ignored: {this.schemaName}");
break;
}
}
public int Length => this.active.Length;
public byte[] ToArray()
{
return this.active.ToArray();
}
private void WriteBufferGuest(Dictionary<Utf8String, object> tableValue)
{
pb.Guests room = new pb.Guests();
using (CodedOutputStream stm = new CodedOutputStream(this.buffer))
{
foreach ((Utf8String key, object value) in tableValue)
{
switch (key.ToString())
{
case "guest_id":
room.GuestId = ((Guid?)value)?.ToString();
break;
case "first_name":
room.FirstName = ((Utf8String)value)?.ToString();
break;
case "last_name":
room.LastName = ((Utf8String)value)?.ToString();
break;
case "title":
room.Title = ((Utf8String)value)?.ToString();
break;
case "emails":
if (value != null)
{
ProtobufRowGenerator.PopulateStringList(room.Emails, (List<object>)value);
}
break;
case "phone_numbers":
if (value != null)
{
ProtobufRowGenerator.PopulateStringList(room.PhoneNumbers, (List<object>)value);
}
break;
case "addresses":
if (value != null)
{
ProtobufRowGenerator.PopulateStringAddressMap(room.Addresses, (List<object>)value);
}
break;
case "confirm_number":
room.ConfirmNumber = ((Utf8String)value)?.ToString();
break;
default:
Assert.Fail("should never happen");
break;
}
}
room.WriteTo(stm);
stm.Flush();
this.active = this.buffer.AsSpan(0, (int)stm.Position);
}
}
private void WriteBufferHotel(Dictionary<Utf8String, object> tableValue)
{
pb.Hotels room = new pb.Hotels();
using (CodedOutputStream stm = new CodedOutputStream(this.buffer))
{
foreach ((Utf8String key, object value) in tableValue)
{
switch (key.ToString())
{
case "hotel_id":
room.HotelId = ((Utf8String)value)?.ToString();
break;
case "name":
room.Name = ((Utf8String)value)?.ToString();
break;
case "phone":
room.Phone = ((Utf8String)value)?.ToString();
break;
case "address":
room.Address = value == null ? null : ProtobufRowGenerator.MakeAddress((Dictionary<Utf8String, object>)value);
break;
default:
Assert.Fail("should never happen");
break;
}
}
room.WriteTo(stm);
stm.Flush();
this.active = this.buffer.AsSpan(0, (int)stm.Position);
}
}
private void WriteBufferRoom(Dictionary<Utf8String, object> tableValue)
{
pb.Available_Rooms_By_Hotel_Date room = new pb.Available_Rooms_By_Hotel_Date();
using (CodedOutputStream stm = new CodedOutputStream(this.buffer))
{
foreach ((Utf8String key, object value) in tableValue)
{
switch (key.ToString())
{
case "hotel_id":
room.HotelId = ((Utf8String)value)?.ToString();
break;
case "date":
room.Date = ((DateTime?)value)?.Ticks;
break;
case "room_number":
room.RoomNumber = (byte?)value;
break;
case "is_available":
room.IsAvailable = (bool?)value;
break;
default:
Assert.Fail("should never happen");
break;
}
}
room.WriteTo(stm);
stm.Flush();
this.active = this.buffer.AsSpan(0, (int)stm.Position);
}
}
private static void ReadBufferGuest(byte[] buffer)
{
pb.Guests item = new pb.Guests();
item.MergeFrom(buffer);
}
private static void ReadBufferHotel(byte[] buffer)
{
pb.Hotels item = new pb.Hotels();
item.MergeFrom(buffer);
}
private static void ReadBufferRoom(byte[] buffer)
{
pb.Available_Rooms_By_Hotel_Date item = new pb.Available_Rooms_By_Hotel_Date();
item.MergeFrom(buffer);
}
private static void PopulateStringList(RepeatedField<string> field, List<object> list)
{
foreach (object item in list)
{
field.Add(((Utf8String)item).ToString());
}
}
private static void PopulateStringAddressMap(MapField<string, pb.Address> field, List<object> list)
{
foreach (object item in list)
{
List<object> tuple = (List<object>)item;
string key = ((Utf8String)tuple[0]).ToString();
pb.Address value = ProtobufRowGenerator.MakeAddress((Dictionary<Utf8String, object>)tuple[1]);
field.Add(key, value);
}
}
private static pb.PostalCode MakePostalCode(Dictionary<Utf8String, object> tableValue)
{
pb.PostalCode postalCode = new pb.PostalCode();
foreach ((Utf8String key, object value) in tableValue)
{
switch (key.ToString())
{
case "zip":
postalCode.Zip = (int?)value;
break;
case "plus4":
postalCode.Plus4 = (short?)value;
break;
default:
Assert.Fail("should never happen");
break;
}
}
return postalCode;
}
private static pb.Address MakeAddress(Dictionary<Utf8String, object> tableValue)
{
pb.Address address = new pb.Address();
foreach ((Utf8String key, object value) in tableValue)
{
switch (key.ToString())
{
case "street":
address.Street = ((Utf8String)value)?.ToString();
break;
case "city":
address.City = ((Utf8String)value)?.ToString();
break;
case "state":
address.State = ((Utf8String)value)?.ToString();
break;
case "postal_code":
address.PostalCode = value == null ? null : ProtobufRowGenerator.MakePostalCode((Dictionary<Utf8String, object>)value);
break;
default:
Assert.Fail("should never happen");
break;
}
}
return address;
}
}
}

View File

@@ -0,0 +1,282 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
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.RecordIO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
[SuppressMessage("Microsoft.Reliability", "CA2001:Avoid calling problematic methods", Justification = "Perf Benchmark")]
[DeploymentItem(@"TestData\*.hr", "TestData")]
public sealed class ReaderBenchmark
{
private const int InitialCapacity = 2 * 1024 * 1024;
private const int WarmCount = 5;
private const int MeasureCount = 10;
private const string CombinedScriptsData = @"TestData\CombinedScriptsData.hr";
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(ReaderBenchmark.CombinedScriptsData, "TestData")]
public async Task RowReaderAsync()
{
using (BenchmarkContext context = new BenchmarkContext(ReaderBenchmark.CombinedScriptsData, true, true))
{
await ReaderBenchmark.BenchmarkAsync(context, ReaderBenchmark.RowReaderBenchmarkAsync);
}
}
[TestMethod]
[Owner("jthunter")]
[Ignore]
public async Task SpecificFileAsync()
{
const string filename = @"E:\TestData\HybridRow\Lastfm.hr";
using (BenchmarkContext context = new BenchmarkContext(filename, true, true))
{
await ReaderBenchmark.BenchmarkAsync(context, ReaderBenchmark.RowReaderBenchmarkAsync);
}
}
[TestMethod]
[Owner("jthunter")]
[Ignore]
public async Task AllAsync()
{
const string dir = @"E:\TestData\HybridRow";
foreach (FileInfo childFile in new DirectoryInfo(dir).EnumerateFiles(@"*.hr"))
{
using (BenchmarkContext context = new BenchmarkContext(childFile.FullName, false, false))
{
await ReaderBenchmark.BenchmarkAsync(context, ReaderBenchmark.RowReaderBenchmarkAsync);
}
}
}
private static async Task RowReaderBenchmarkAsync(object ctx)
{
BenchmarkContext context = (BenchmarkContext)ctx;
MemorySpanResizer<byte> resizer = new MemorySpanResizer<byte>(ReaderBenchmark.InitialCapacity);
Result r = await context.Input.ReadRecordIOAsync(
record =>
{
context.IncrementRecordCount();
r = ReaderBenchmark.VisitOneRow(record, context.Resolver);
Assert.AreEqual(Result.Success, r);
return Result.Success;
},
segment =>
{
r = SegmentSerializer.Read(segment.Span, context.Resolver, out Segment _);
Assert.AreEqual(Result.Success, r);
// TODO: do something with embedded schema.
return Result.Success;
},
resizer);
Assert.AreEqual(Result.Success, r);
}
private static Result VisitOneRow(Memory<byte> buffer, LayoutResolver resolver)
{
RowBuffer row = new RowBuffer(buffer.Span, HybridRowVersion.V1, resolver);
RowReader reader = new RowReader(ref row);
return reader.VisitReader();
}
[SuppressMessage("Microsoft.Reliability", "CA2001:Avoid calling problematic methods", Justification = "Perf Benchmark")]
private static async Task BenchmarkAsync(BenchmarkContext context, Func<object, Task> body)
{
using (SingleThreadedTaskScheduler scheduler = new SingleThreadedTaskScheduler())
{
// Warm
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
for (int i = 0; i < ReaderBenchmark.WarmCount; i++)
{
context.Reset();
sw.Restart();
await Task.Factory.StartNew(body, context, CancellationToken.None, TaskCreationOptions.None, scheduler).Unwrap();
sw.Stop();
if (context.ShowWarmSummary)
{
context.Summarize(sw.Elapsed);
}
}
// Execute
double[] timing = new double[ReaderBenchmark.MeasureCount];
for (int i = 0; i < ReaderBenchmark.MeasureCount; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
context.Reset();
sw.Restart();
await Task.Factory.StartNew(body, context, CancellationToken.None, TaskCreationOptions.None, scheduler).Unwrap();
sw.Stop();
if (context.ShowSummary)
{
context.Summarize(sw.Elapsed);
}
timing[i] = sw.Elapsed.TotalMilliseconds;
}
Array.Sort(timing);
Console.WriteLine(
$"File: {Path.GetFileNameWithoutExtension(context.InputFile)}, Mean: {timing[ReaderBenchmark.MeasureCount / 2]:F4}");
}
}
private sealed class BenchmarkContext : IDisposable
{
private readonly string inputFile;
private readonly bool showSummary;
private readonly bool showWarmSummary;
private long recordCount;
private readonly Stream input;
private readonly LayoutResolver resolver;
public BenchmarkContext(string inputFile, bool showSummary = true, bool showWarmSummary = false)
{
this.inputFile = inputFile;
this.showSummary = showSummary;
this.showWarmSummary = showWarmSummary;
this.input = new FileStream(inputFile, FileMode.Open);
this.resolver = SystemSchema.LayoutResolver;
}
public bool ShowSummary => this.showSummary;
public bool ShowWarmSummary => this.showWarmSummary;
public string InputFile => this.inputFile;
public Stream Input => this.input;
public LayoutResolver Resolver => this.resolver;
public void IncrementRecordCount()
{
this.recordCount++;
}
public void Reset()
{
this.recordCount = 0;
this.input.Seek(0, SeekOrigin.Begin);
}
public void Summarize(TimeSpan duration)
{
Console.Write($"Total Time: {duration.TotalMilliseconds:F4}, ");
Console.WriteLine($"Record Count: {this.recordCount}");
}
public void Dispose()
{
this.input.Dispose();
}
}
private sealed class SingleThreadedTaskScheduler : TaskScheduler, IDisposable
{
private readonly Thread worker;
private readonly EventWaitHandle ready;
private readonly ConcurrentQueue<Task> tasks;
private readonly CancellationTokenSource cancel;
// Creates a new instance with the specified degree of parallelism.
public SingleThreadedTaskScheduler()
{
this.tasks = new ConcurrentQueue<Task>();
this.ready = new ManualResetEvent(false);
this.worker = new Thread(this.DoWork);
this.cancel = new CancellationTokenSource();
this.worker.Start();
}
// Gets the maximum concurrency level supported by this scheduler.
public override int MaximumConcurrencyLevel => 1;
public void Dispose()
{
if (!this.cancel.IsCancellationRequested)
{
this.cancel.Cancel();
this.worker.Join();
this.ready?.Dispose();
this.cancel?.Dispose();
}
}
// Queues a task to the scheduler.
protected override void QueueTask(Task task)
{
lock (this.tasks)
{
this.tasks.Enqueue(task);
if (Thread.CurrentThread != this.worker)
{
this.ready.Set();
}
}
}
// Attempts to execute the specified task on the current thread.
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// If this thread isn't already processing a task, we don't support inlining
if (Thread.CurrentThread != this.worker)
{
return false;
}
// If the task was previously queued, then skip it.
if (taskWasPreviouslyQueued)
{
return false;
}
return this.TryExecuteTask(task);
}
protected override bool TryDequeue(Task task)
{
return false;
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return null;
}
private void DoWork()
{
while (!this.cancel.IsCancellationRequested)
{
if (this.tasks.TryDequeue(out Task item))
{
this.TryExecuteTask(item);
}
else
{
this.ready.WaitOne(TimeSpan.FromSeconds(1));
}
}
}
}
}
}

View File

@@ -0,0 +1,97 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using Microsoft.Azure.Cosmos.Core;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
internal static class RowReaderExtensions
{
public static Result VisitReader(this ref RowReader reader)
{
while (reader.Read())
{
Utf8Span path = reader.PathSpan;
switch (reader.Type.LayoutCode)
{
case LayoutCode.Null:
case LayoutCode.Boolean:
case LayoutCode.Int8:
case LayoutCode.Int16:
case LayoutCode.Int32:
case LayoutCode.Int64:
case LayoutCode.UInt8:
case LayoutCode.UInt16:
case LayoutCode.UInt32:
case LayoutCode.UInt64:
case LayoutCode.VarInt:
case LayoutCode.VarUInt:
case LayoutCode.Float32:
case LayoutCode.Float64:
case LayoutCode.Float128:
case LayoutCode.Decimal:
case LayoutCode.DateTime:
case LayoutCode.UnixDateTime:
case LayoutCode.Guid:
case LayoutCode.MongoDbObjectId:
case LayoutCode.Utf8:
case LayoutCode.Binary:
break;
case LayoutCode.NullableScope:
case LayoutCode.ImmutableNullableScope:
{
if (!reader.HasValue)
{
break;
}
goto case LayoutCode.TypedTupleScope;
}
case LayoutCode.ObjectScope:
case LayoutCode.ImmutableObjectScope:
case LayoutCode.Schema:
case LayoutCode.ImmutableSchema:
case LayoutCode.ArrayScope:
case LayoutCode.ImmutableArrayScope:
case LayoutCode.TypedArrayScope:
case LayoutCode.ImmutableTypedArrayScope:
case LayoutCode.TypedSetScope:
case LayoutCode.ImmutableTypedSetScope:
case LayoutCode.TypedMapScope:
case LayoutCode.ImmutableTypedMapScope:
case LayoutCode.TupleScope:
case LayoutCode.ImmutableTupleScope:
case LayoutCode.TypedTupleScope:
case LayoutCode.ImmutableTypedTupleScope:
case LayoutCode.TaggedScope:
case LayoutCode.ImmutableTaggedScope:
case LayoutCode.Tagged2Scope:
case LayoutCode.ImmutableTagged2Scope:
{
Result r = reader.ReadScope(null, (ref RowReader child, object _) => child.VisitReader());
if (r != Result.Success)
{
return r;
}
break;
}
default:
{
Contract.Assert(false, $"Unknown type will be ignored: {reader.Type.LayoutCode}");
break;
}
}
}
return Result.Success;
}
}
}

View File

@@ -0,0 +1,583 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Internal;
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;
using MongoDB.Bson.IO;
using Newtonsoft.Json;
[TestClass]
[DeploymentItem(TestData.SchemaFile, TestData.Target)]
public sealed class SchematizedMicroBenchmarkSuite : MicroBenchmarkSuiteBase
{
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
private string sdl;
[TestInitialize]
public void ParseNamespaceExample()
{
this.sdl = File.ReadAllText(TestData.SchemaFile);
Namespace schema = Namespace.Parse(this.sdl);
this.DefaultResolver = new LayoutResolverNamespace(schema, SystemSchema.LayoutResolver);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task JsonHotelWriteBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.JsonWriteBenchmark("Hotels", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task JsonRoomsWriteBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.JsonWriteBenchmark("Rooms", 10000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task JsonGuestsWriteBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.JsonWriteBenchmark("Guests", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task JsonHotelReadBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.JsonReadBenchmark("Hotels", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task JsonRoomsReadBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.JsonReadBenchmark("Rooms", 10000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task JsonGuestsReadBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace _) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.JsonReadBenchmark("Guests", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task BsonHotelWriteBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.BsonWriteBenchmark(resolver, "Hotels", "Hotels", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task BsonRoomsWriteBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.BsonWriteBenchmark(resolver, "Available_Rooms_By_Hotel_Date", "Rooms", 10000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task BsonGuestsWriteBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.BsonWriteBenchmark(resolver, "Guests", "Guests", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task BsonHotelReadBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.BsonReadBenchmark(resolver, "Hotels", "Hotels", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task BsonRoomsReadBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.BsonReadBenchmark(resolver, "Available_Rooms_By_Hotel_Date", "Rooms", 10000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task BsonGuestsReadBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.BsonReadBenchmark(resolver, "Guests", "Guests", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task LayoutHotelWriteBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.LayoutWriteBenchmark(resolver, "Hotels", "Hotels", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task LayoutRoomsWriteBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.LayoutWriteBenchmark(resolver, "Available_Rooms_By_Hotel_Date", "Rooms", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task LayoutGuestsWriteBenchmarkAsync()
{
#if DEBUG
const int innerLoopIterations = 1;
#else
const int innerLoopIterations = 1000;
#endif
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.LayoutWriteBenchmark(resolver, "Guests", "Guests", innerLoopIterations, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task LayoutHotelReadBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.LayoutReadBenchmark(resolver, "Hotels", "Hotels", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task LayoutRoomsReadBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.LayoutReadBenchmark(resolver, "Available_Rooms_By_Hotel_Date", "Rooms", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task LayoutGuestsReadBenchmarkAsync()
{
#if DEBUG
const int innerLoopIterations = 1;
#else
const int innerLoopIterations = 1000;
#endif
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.LayoutReadBenchmark(resolver, "Guests", "Guests", innerLoopIterations, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task StreamingHotelWriteBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.StreamingWriteBenchmark(resolver, "Hotels", "Hotels", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task StreamingRoomsWriteBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.StreamingWriteBenchmark(resolver, "Available_Rooms_By_Hotel_Date", "Rooms", 10000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task StreamingGuestsWriteBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.StreamingWriteBenchmark(resolver, "Guests", "Guests", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.HotelExpected, TestData.Target)]
public async Task StreamingHotelReadBenchmarkAsync()
{
string expectedFile = TestData.HotelExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.StreamingReadBenchmark(resolver, "Hotels", "Hotels", 1000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.RoomsExpected, TestData.Target)]
public async Task StreamingRoomsReadBenchmarkAsync()
{
string expectedFile = TestData.RoomsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.StreamingReadBenchmark(resolver, "Available_Rooms_By_Hotel_Date", "Rooms", 10000, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.GuestsExpected, TestData.Target)]
public async Task StreamingGuestsReadBenchmarkAsync()
{
string expectedFile = TestData.GuestsExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
SchematizedMicroBenchmarkSuite.StreamingReadBenchmark(resolver, "Guests", "Guests", 1000, expected);
}
private static void JsonWriteBenchmark(
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
Encoding utf8Encoding = new UTF8Encoding();
JsonSerializer jsonSerializer = JsonSerializer.Create(SchematizedMicroBenchmarkSuite.JsonSettings);
using (MemoryStream jsonStream = new MemoryStream(BenchmarkSuiteBase.InitialCapacity))
using (StreamWriter textWriter = new StreamWriter(jsonStream, utf8Encoding))
using (JsonTextWriter jsonWriter = new JsonTextWriter(textWriter))
{
BenchmarkContext ignoredContext = default;
jsonSerializer.Converters.Add(new Utf8StringJsonConverter());
MicroBenchmarkSuiteBase.Benchmark(
"Schematized",
"Write",
dataSetName,
"JSON",
innerLoopIterations,
ref ignoredContext,
(ref BenchmarkContext _, Dictionary<Utf8String, object> tableValue) =>
{
jsonStream.SetLength(0);
jsonSerializer.Serialize(jsonWriter, tableValue);
jsonWriter.Flush();
},
(ref BenchmarkContext _, Dictionary<Utf8String, object> value) => jsonStream.Length,
expected);
}
}
private static void JsonReadBenchmark(string dataSetName, int innerLoopIterations, List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
Encoding utf8Encoding = new UTF8Encoding();
JsonSerializer jsonSerializer = JsonSerializer.Create(SchematizedMicroBenchmarkSuite.JsonSettings);
using (MemoryStream jsonStream = new MemoryStream(BenchmarkSuiteBase.InitialCapacity))
using (StreamWriter textWriter = new StreamWriter(jsonStream, utf8Encoding))
using (JsonTextWriter jsonWriter = new JsonTextWriter(textWriter))
{
jsonSerializer.Converters.Add(new Utf8StringJsonConverter());
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
jsonSerializer.Serialize(jsonWriter, tableValue);
jsonWriter.Flush();
expectedSerialized.Add(jsonStream.ToArray());
jsonStream.SetLength(0);
}
}
BenchmarkContext ignoredContext = default;
jsonSerializer.Converters.Add(new Utf8StringJsonConverter());
MicroBenchmarkSuiteBase.Benchmark(
"Schematized",
"Read",
dataSetName,
"JSON",
innerLoopIterations,
ref ignoredContext,
(ref BenchmarkContext _, byte[] tableValue) =>
{
using (MemoryStream jsonStream = new MemoryStream(tableValue))
using (StreamReader textReader = new StreamReader(jsonStream, utf8Encoding))
using (JsonTextReader jsonReader = new JsonTextReader(textReader))
{
while (jsonReader.Read())
{
// Just visit the entire structure without materializing any of the values.
}
}
},
(ref BenchmarkContext _, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
private static void BsonWriteBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
using (BsonRowGenerator writer = new BsonRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver))
{
BenchmarkContext ignoredContext = default;
MicroBenchmarkSuiteBase.Benchmark(
"Schematized",
"Write",
dataSetName,
"BSON",
innerLoopIterations,
ref ignoredContext,
(ref BenchmarkContext _, Dictionary<Utf8String, object> tableValue) =>
{
writer.Reset();
writer.WriteBuffer(tableValue);
},
(ref BenchmarkContext _, Dictionary<Utf8String, object> tableValue) => writer.Length,
expected);
}
}
private static void BsonReadBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
using (BsonRowGenerator writer = new BsonRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver))
{
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
writer.Reset();
writer.WriteBuffer(tableValue);
expectedSerialized.Add(writer.ToArray());
}
}
BenchmarkContext ignoredContext = default;
MicroBenchmarkSuiteBase.Benchmark(
"Schematized",
"Read",
dataSetName,
"BSON",
innerLoopIterations,
ref ignoredContext,
(ref BenchmarkContext _, byte[] tableValue) =>
{
using (MemoryStream stm = new MemoryStream(tableValue))
using (BsonBinaryReader bsonReader = new BsonBinaryReader(stm))
{
bsonReader.VisitBsonDocument();
}
},
(ref BenchmarkContext _, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
private static void LayoutWriteBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
BenchmarkContext context = new BenchmarkContext
{
PatchWriter = new WriteRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver)
};
MicroBenchmarkSuiteBase.Benchmark(
"Schematized",
"Write",
dataSetName,
"Layout",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> dict) =>
{
ctx.PatchWriter.Reset();
Result r = ctx.PatchWriter.DispatchLayout(layout, dict);
ResultAssert.IsSuccess(r);
},
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> _) => ctx.PatchWriter.Length,
expected);
}
private static void LayoutReadBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
BenchmarkContext context = new BenchmarkContext
{
StreamingWriter = new StreamingRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver)
};
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
context.StreamingWriter.Reset();
Result r = context.StreamingWriter.WriteBuffer(tableValue);
ResultAssert.IsSuccess(r);
expectedSerialized.Add(context.StreamingWriter.ToArray());
}
MicroBenchmarkSuiteBase.Benchmark(
"Schematized",
"Read",
dataSetName,
"Layout",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, byte[] tableValue) =>
{
VisitRowGenerator visitor = new VisitRowGenerator(tableValue.AsSpan(), resolver);
Result r = visitor.DispatchLayout(layout);
ResultAssert.IsSuccess(r);
},
(ref BenchmarkContext ctx, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
private static void StreamingWriteBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
BenchmarkContext context = new BenchmarkContext
{
StreamingWriter = new StreamingRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver)
};
MicroBenchmarkSuiteBase.Benchmark(
"Schematized",
"Write",
dataSetName,
"HybridRow",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> tableValue) =>
{
ctx.StreamingWriter.Reset();
Result r = ctx.StreamingWriter.WriteBuffer(tableValue);
ResultAssert.IsSuccess(r);
},
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> _) => ctx.StreamingWriter.Length,
expected);
}
private static void StreamingReadBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
BenchmarkContext context = new BenchmarkContext
{
StreamingWriter = new StreamingRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver)
};
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
context.StreamingWriter.Reset();
Result r = context.StreamingWriter.WriteBuffer(tableValue);
ResultAssert.IsSuccess(r);
expectedSerialized.Add(context.StreamingWriter.ToArray());
}
MicroBenchmarkSuiteBase.Benchmark(
"Schematized",
"Read",
dataSetName,
"HybridRow",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, byte[] tableValue) =>
{
RowBuffer row = new RowBuffer(tableValue.AsSpan(), HybridRowVersion.V1, resolver);
RowReader reader = new RowReader(ref row);
reader.VisitReader();
},
(ref BenchmarkContext ctx, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
}
}

View File

@@ -0,0 +1,22 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
/// <summary>
/// Names of assets in the TestData folder.
/// </summary>
internal class TestData
{
/// <summary>
/// The folder to which TestData assets should be copied during deployment.
/// </summary>
public const string Target = "TestData";
public const string SchemaFile = @"TestData\CassandraHotelSchema.json";
public const string HotelExpected = @"TestData\HotelSchemaExpected.hr";
public const string RoomsExpected = @"TestData\RoomsSchemaExpected.hr";
public const string GuestsExpected = @"TestData\GuestsSchemaExpected.hr";
public const string Messages1KExpected = @"TestData\Messages1KExpected.hr";
}
}

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.Perf.CassandraHotel",
"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": "fixed", "length": 8 } },
{ "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": "fixed", "length": 8 } },
{ "path": "date", "type": { "type": "datetime", "storage": "fixed" } },
{ "path": "room_number", "type": { "type": "uint8", "storage": "fixed" } },
{ "path": "is_available", "type": { "type": "bool", "storage": "fixed" } }
]
},
{
"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", "length": 20 } },
{ "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,43 @@
syntax = "proto3";
package Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.CassandraHotel;
import "google/protobuf/wrappers.proto";
option csharp_namespace = "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.CassandraHotel.Protobuf";
message PostalCode {
google.protobuf.Int32Value zip = 1;
google.protobuf.Int32Value plus4 = 2;
}
message Address {
google.protobuf.StringValue street = 1;
google.protobuf.StringValue city = 2;
google.protobuf.StringValue state = 3;
PostalCode postal_code = 4;
}
message Hotels {
google.protobuf.StringValue hotel_id = 1;
google.protobuf.StringValue name = 2;
google.protobuf.StringValue phone = 3;
Address address = 4;
}
message Available_Rooms_By_Hotel_Date {
google.protobuf.StringValue hotel_id = 1;
google.protobuf.Int64Value date = 2; // datetime
google.protobuf.Int32Value room_number = 3;
google.protobuf.BoolValue is_available = 4;
}
message Guests {
google.protobuf.StringValue guest_id = 1; // guid
google.protobuf.StringValue first_name = 2;
google.protobuf.StringValue last_name = 3;
google.protobuf.StringValue title = 4;
repeated string emails = 5;
repeated string phone_numbers = 6;
map<string, Address> addresses = 7;
google.protobuf.StringValue confirm_number = 8;
}

View File

@@ -0,0 +1,338 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Internal;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MongoDB.Bson.IO;
using Newtonsoft.Json;
/// <summary>Tests involving fully (or mostly) unschematized test data.</summary>
[TestClass]
public sealed class UnschematizedMicroBenchmarkSuite : MicroBenchmarkSuiteBase
{
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.Messages1KExpected, TestData.Target)]
public async Task Messages1KWriteBenchmarkAsync()
{
#if DEBUG
const int innerLoopIterations = 1;
#else
const int innerLoopIterations = 10;
#endif
string expectedFile = TestData.Messages1KExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
UnschematizedMicroBenchmarkSuite.JsonModelWriteBenchmark(
resolver,
"TypedJsonHybridRowSchema",
"Messages1K",
innerLoopIterations,
expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.Messages1KExpected, TestData.Target)]
public async Task Messages1KReadBenchmarkAsync()
{
#if DEBUG
const int innerLoopIterations = 1;
#else
const int innerLoopIterations = 10;
#endif
string expectedFile = TestData.Messages1KExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
UnschematizedMicroBenchmarkSuite.JsonModelReadBenchmark(
resolver,
"TypedJsonHybridRowSchema",
"Messages1K",
innerLoopIterations,
expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.Messages1KExpected, TestData.Target)]
public async Task BsonMessages1KWriteBenchmarkAsync()
{
#if DEBUG
const int innerLoopIterations = 1;
#else
const int innerLoopIterations = 10;
#endif
string expectedFile = TestData.Messages1KExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
UnschematizedMicroBenchmarkSuite.BsonWriteBenchmark("Messages1K", innerLoopIterations, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.Messages1KExpected, TestData.Target)]
public async Task BsonMessages1KReadBenchmarkAsync()
{
#if DEBUG
const int innerLoopIterations = 1;
#else
const int innerLoopIterations = 10;
#endif
string expectedFile = TestData.Messages1KExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
UnschematizedMicroBenchmarkSuite.BsonReadBenchmark("Messages1K", innerLoopIterations, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.Messages1KExpected, TestData.Target)]
public async Task JsonMessages1KWriteBenchmarkAsync()
{
#if DEBUG
const int innerLoopIterations = 1;
#else
const int innerLoopIterations = 10;
#endif
string expectedFile = TestData.Messages1KExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
UnschematizedMicroBenchmarkSuite.JsonWriteBenchmark("Messages1K", innerLoopIterations, expected);
}
[TestMethod]
[Owner("jthunter")]
[DeploymentItem(TestData.Messages1KExpected, TestData.Target)]
public async Task JsonMessages1KReadBenchmarkAsync()
{
#if DEBUG
const int innerLoopIterations = 1;
#else
const int innerLoopIterations = 10;
#endif
string expectedFile = TestData.Messages1KExpected;
(List<Dictionary<Utf8String, object>> expected, LayoutResolverNamespace resolver) = await this.LoadExpectedAsync(expectedFile);
UnschematizedMicroBenchmarkSuite.JsonReadBenchmark("Messages1K", innerLoopIterations, expected);
}
private static void JsonModelWriteBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
BenchmarkContext context = new BenchmarkContext
{
JsonModelWriter = new JsonModelRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver)
};
MicroBenchmarkSuiteBase.Benchmark(
"Unschematized",
"Write",
dataSetName,
"HybridRowSparse",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> tableValue) =>
{
ctx.JsonModelWriter.Reset();
Result r = ctx.JsonModelWriter.WriteBuffer(tableValue);
ResultAssert.IsSuccess(r);
},
(ref BenchmarkContext ctx, Dictionary<Utf8String, object> tableValue) => ctx.JsonModelWriter.Length,
expected);
}
private static void JsonModelReadBenchmark(
LayoutResolverNamespace resolver,
string schemaName,
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
Layout layout = resolver.Resolve(resolver.Namespace.Schemas.Find(x => x.Name == schemaName).SchemaId);
BenchmarkContext context = new BenchmarkContext
{
JsonModelWriter = new JsonModelRowGenerator(BenchmarkSuiteBase.InitialCapacity, layout, resolver)
};
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
context.JsonModelWriter.Reset();
Result r = context.JsonModelWriter.WriteBuffer(tableValue);
ResultAssert.IsSuccess(r);
expectedSerialized.Add(context.JsonModelWriter.ToArray());
}
MicroBenchmarkSuiteBase.Benchmark(
"Unschematized",
"Read",
dataSetName,
"HybridRowSparse",
innerLoopIterations,
ref context,
(ref BenchmarkContext ctx, byte[] tableValue) =>
{
RowBuffer row = new RowBuffer(tableValue.AsSpan(), HybridRowVersion.V1, resolver);
RowReader reader = new RowReader(ref row);
reader.VisitReader();
},
(ref BenchmarkContext ctx, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
private static void JsonWriteBenchmark(
string dataSetName,
int innerLoopIterations,
List<Dictionary<Utf8String, object>> expected)
{
Encoding utf8Encoding = new UTF8Encoding();
JsonSerializer jsonSerializer = JsonSerializer.Create(UnschematizedMicroBenchmarkSuite.JsonSettings);
using (MemoryStream jsonStream = new MemoryStream(BenchmarkSuiteBase.InitialCapacity))
using (StreamWriter textWriter = new StreamWriter(jsonStream, utf8Encoding))
using (JsonTextWriter jsonWriter = new JsonTextWriter(textWriter))
{
BenchmarkContext ignoredContext = default;
jsonSerializer.Converters.Add(new Utf8StringJsonConverter());
MicroBenchmarkSuiteBase.Benchmark(
"Unschematized",
"Write",
dataSetName,
"JSON",
innerLoopIterations,
ref ignoredContext,
(ref BenchmarkContext _, Dictionary<Utf8String, object> tableValue) =>
{
jsonStream.SetLength(0);
jsonSerializer.Serialize(jsonWriter, tableValue);
jsonWriter.Flush();
},
(ref BenchmarkContext _, Dictionary<Utf8String, object> value) => jsonStream.Length,
expected);
}
}
private static void JsonReadBenchmark(string dataSetName, int innerLoopIterations, List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
Encoding utf8Encoding = new UTF8Encoding();
JsonSerializer jsonSerializer = JsonSerializer.Create(UnschematizedMicroBenchmarkSuite.JsonSettings);
using (MemoryStream jsonStream = new MemoryStream(BenchmarkSuiteBase.InitialCapacity))
using (StreamWriter textWriter = new StreamWriter(jsonStream, utf8Encoding))
using (JsonTextWriter jsonWriter = new JsonTextWriter(textWriter))
{
jsonSerializer.Converters.Add(new Utf8StringJsonConverter());
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
jsonSerializer.Serialize(jsonWriter, tableValue);
jsonWriter.Flush();
expectedSerialized.Add(jsonStream.ToArray());
jsonStream.SetLength(0);
}
}
BenchmarkContext ignoredContext = default;
jsonSerializer.Converters.Add(new Utf8StringJsonConverter());
MicroBenchmarkSuiteBase.Benchmark(
"Unschematized",
"Read",
dataSetName,
"JSON",
innerLoopIterations,
ref ignoredContext,
(ref BenchmarkContext _, byte[] tableValue) =>
{
using (MemoryStream jsonStream = new MemoryStream(tableValue))
using (StreamReader textReader = new StreamReader(jsonStream, utf8Encoding))
using (JsonTextReader jsonReader = new JsonTextReader(textReader))
{
while (jsonReader.Read())
{
// Just visit the entire structure without materializing any of the values.
}
}
},
(ref BenchmarkContext _, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
private static void BsonWriteBenchmark(string dataSetName, int innerLoopIterations, List<Dictionary<Utf8String, object>> expected)
{
using (BsonJsonModelRowGenerator writer = new BsonJsonModelRowGenerator(BenchmarkSuiteBase.InitialCapacity))
{
BenchmarkContext ignoredContext = default;
MicroBenchmarkSuiteBase.Benchmark(
"Unschematized",
"Write",
dataSetName,
"BSON",
innerLoopIterations,
ref ignoredContext,
(ref BenchmarkContext _, Dictionary<Utf8String, object> tableValue) =>
{
writer.Reset();
writer.WriteBuffer(tableValue);
},
(ref BenchmarkContext _, Dictionary<Utf8String, object> tableValue) => writer.Length,
expected);
}
}
private static void BsonReadBenchmark(string dataSetName, int innerLoopIterations, List<Dictionary<Utf8String, object>> expected)
{
// Serialize input data to sequence of byte buffers.
List<byte[]> expectedSerialized = new List<byte[]>(expected.Count);
using (BsonJsonModelRowGenerator writer = new BsonJsonModelRowGenerator(BenchmarkSuiteBase.InitialCapacity))
{
foreach (Dictionary<Utf8String, object> tableValue in expected)
{
writer.Reset();
writer.WriteBuffer(tableValue);
expectedSerialized.Add(writer.ToArray());
}
}
BenchmarkContext ignoredContext = default;
MicroBenchmarkSuiteBase.Benchmark(
"Unschematized",
"Read",
dataSetName,
"BSON",
innerLoopIterations,
ref ignoredContext,
(ref BenchmarkContext _, byte[] tableValue) =>
{
using (MemoryStream stm = new MemoryStream(tableValue))
using (BsonBinaryReader bsonReader = new BsonBinaryReader(stm))
{
bsonReader.VisitBsonDocument();
}
},
(ref BenchmarkContext _, byte[] tableValue) => tableValue.Length,
expectedSerialized);
}
}
}

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);
}
}
}

View File

@@ -0,0 +1,31 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow
{
using System;
using System.Diagnostics.CodeAnalysis;
public class DefaultSpanResizer<T> : ISpanResizer<T>
{
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Type is immutable.")]
public static readonly DefaultSpanResizer<T> Default = new DefaultSpanResizer<T>();
private DefaultSpanResizer()
{
}
/// <inheritdoc />
public Span<T> Resize(int minimumLength, Span<T> buffer = default)
{
Span<T> next = new Memory<T>(new T[Math.Max(minimumLength, buffer.Length)]).Span;
if (!buffer.IsEmpty && next.Slice(0, buffer.Length) != buffer)
{
buffer.CopyTo(next);
}
return next;
}
}
}

View File

@@ -0,0 +1,71 @@
This *glossary* defines some terms used either in the HybirdRow Library code, its
documentation, or in its applications.
The definitions here are meant only to provide a common understanding when
implementing or consuming the HybridRow Library.
[[_TOC_]]
# Glossary
## General Terms
###### DDL
A Data Definition Language operation is one that defines a new [schema](#schema) or
redefines (ALTERs) an existing [schema](#schema).
Since HybridRow [schema](#schema) are themselves immutable, a
DDL operation that ALTERs a [schema](#schema) always defines a new [schema](#schema)
with a distinct [SchemaId](#schemaid). By convention the new [schema](#schema) has
the same name as previous schema being ALTERed and a new SchemaId whose absolutely value
is monotonically increasing relative to the old schema. See [SchemaId.md](./SchemaId.md)
for details on how [SchemaId](#schemaid) are allocated.
###### Namespace
A set of [schema](#schema) with non-overlapping [SchemaId](#schemaid).
###### Schema
Describes the logical structure of a row at a particular point in time (relative to the
DDL operation history).
###### SchemaHash
A 128-bit hash of a HybridRow [schema](#schema). The hash captures only the logical
elements of a schema. Whitespace elements such as formatting or comments have no impact
on the hash. See [SchemaHash.md](./SchemaHash.md) for more details.
###### SchemaId
An integer that uniquely defines a particular version of a schema within a
Namespace.
## Schema Versioning Terms
###### Latest (Schema) Version
The [SchemaId](#schemaid) of the latest known version of the schema (from the Backend's perspective).
###### Row (Schema) Version
The [SchemaId](#schemaid) at which the stored row was encoded. When a row is read it is
**upgraded** through a process called **row upgrade** when the
`Row Version < Latest Version`.
###### Target (Schema) Version
An operation performed by the Front End (FE) or Client (referred to collectively as FE
below) is done in the context of its understanding of the current schema.
The FE's view may trail the true Latest Version (`Target Version < Latest Version`)
if the FE's schema cache is stale. The FE's view may lead the true Latest Version
(`Target Version > Latest Version`) if a DDL operation has happened but has not
yet propagated to the specific BE in question (propagation is asynchronous and
concurrent).
The FE provides its view as the Target Version for each request. A Target Version
is always relative to a request and describes the version targeted by that request.
When the `Target Version < Latest Version`, the BE can still accept the request by
**upgrading** the request *before* applying it to the row. When the
`Target Version > Latest Version`, the BE must reject the request.
The FE is free to resubmit the request some time later in hopes that the BE has by then
seen the DDL operation and subsequently raised the Latest Version to match. The BE will,
of course, continue to reject requests whose Target Version is greater than its
Latest Version until the relavant DDL operation has successfully propagated.
This protection prevents the BE from writing rows that it would be unable to
immediately read.

View File

@@ -0,0 +1,109 @@
This document contains a brief EBNF-like grammar for describing the structure of a Hybrid Row.
[[_TOC_]]
# HybridRow Format
HybridRows are described by the following
[EBNF grammar](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form):
````ebnf
hybrid_row = hybrid_header, hybrid_body;
hybrid_header = version, schema_id;
hybrid_body = [schematized_fragment], { sparse_fragment };
schematized_fragment = presence_and_bool_bits, fixed_fields, var_fields;
version = uint8;
schema_id = int32;
````
The presence of a *Schematized Fragment* is defined by the schema referenced by the schema_id.
If the schema defines a Schematized Fragment, then one MUST be present. If it does NOT define
a Schematized Fragment, then one MUST NOT be present. The order of fields within a
Schematized Fragment, both fixed and variable length fields, is strictly defined by the schema
and all fields MUST be present, or its absence MUST be indicated by unsetting the corresponding
presence bit in the presence_bits field of the fragment. A presence bit is defined for all
nullable schematized fields. Non-nullable fields are **always** present, and if unwritten contain
their type's default value.
The formal specification of the Schematized Fragment is:
```ebnf
presence_and_bool_bits = ? 0+ bits, one for each nullable schematized field. Additionally one for each boolean field ?;
fixed_fields = { fixed_field };
fixed_field = finite_field | bounded_field;
finite_field = literal_null_field
| bool_field
| integer_field
| float_field
| guid_field
| datetime_field
| object_id_field;
bounded_field = string_field | binary_field;
var_fields = { var_field };
var_field = field_length, bounded_field | varint_field;
````
With the following primitive type definitions. Note, unless otherwise states, all values are stored little-endian.
````ebnf
literal_null_field = ? no encoded payload ?;
bool_field = ? 1 byte, 0 FALSE, 1 TRUE ?;
integer_field = int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64;
float_field =
float32 (? 4 byte IEEE 754 floating point value ?)
| float64 (? 8 byte IEEE 754 floating point value ?)
| float128 (? 16 byte IEEE 754 floating point value ?)
| decimal; (? 16 byte System.Decimal value ?)
guid_field = ? 16 byte, little-endian ?;
datetime_field = precise_datatime_field | unix_datetime_field;
precise_datatime_field = ? 8 byte, 100ns since 00:00:00, January 1, 1 CE UTC ?;
unix_datetime_field = ? 8 byte, milliseconds since Unix Epoch (midnight, January 1, 1970 UTC) ?;
object_id_field = ? 12 unsigned bytes (in big-endian order)?;
string_field = ? UTF-8, not null-terminated ?;
binary_field = ? unsigned bytes ?;
field_length = varuint;
varint_field = varint | varuint;
varint = ? varint variable length signed integer encoding, sign-bit rotated ?;
varuint = ? varuint variable length unsigned integer encoding ?;
````
Additionally, a row may contain zero or more *Sparse Fragments*. The structure of Sparse Fragment
may be described in full or in part within the schema. Describing Sparse Fragments within the schema
allows both for optional schema validation and path interning (See Paths).
And Sparse Fragments:
```ebnf
sparse_fragment = { sparse_field };
sparse_field = type, path, sparse_value;
type = type_code | generic_type;
generic_type =
typed_array_type_code, type
| nullable_type_code, type
| typed_tuple_type_code, type, {type}
| typed_set_type_code, type
| typed_map_type_code, type, type
| hybrid_row_type_code, schema_id;
sparse_value =
null_field
| finite_field
| var_field
| array_field (? typed arrays Generic N = 1 ?)
| obj_field
| tuple_field (? typed tuples are Generic N ?)
| set_field (? Generic N = 1 ?)
| map_field (? Generic N = 2 ?)
| hybrid_body; (? Generic N = 1 ?)
null_field = (? empty production ?);
array_field = typed_array_field | sparse_array_field;
typed_array_field = field_count, { sparse_value };
sparse_array_field = { type, sparse_value }, scope_end_symbol;
obj_field = { sparse_field }, scope_end_symbol;
nullable_field = bool_field, sparse_value;
tuple_field = sparse_value, {sparse_value};
set_field = field_count, { sparse_value };
map_field = field_count, { map_field_body };
map_field_body = sparse_value (? key ?), sparse_value (? value ?);
path = ? dictionary (@) encoded varint ? | field_length, string_lob_field;
````

View File

@@ -0,0 +1,38 @@
RecordIO refers to a class of streaming file formats that consist of
linear sequences of flat records. The metadata describing the records and the
encoding format of the records vary between different RecordIO incarnations.
This document describes a HybridRow RecordIO format.
[[_TOC_]]
# HybridRow RecordIO Stream Format
HybridRow RecordIO streams are described by the following
[EBNF grammar](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form):
```ebnf
record_io_stream = record_io_segment, {record_io_segment};
record_io_segment = record_io_header, [record];
record_io_header = "RecordIO Segment HybridRow";
record = record_header, record_body;
record_header = "RecordIO Record HybridRow";
record_body = "Any HybridRow enocded row";
```
A HybridRow RecordIO stream consists of one or more RecordIO segments, each
segment consisting of a RecordIO segment header followed by zero or more
records.
Each record consists of a record header which includes both the length
(in bytes) of the record body and an optional CRC. The record body can be any
encoded HybridRow.
Record bodies (serialized HybridRows) are limited to 2GB in length.
If a schema namespace is provided in the segment header, then all records
within the segment **MUST** conform to a schema type defined within that
schema namespace.
# RecordIO Schema
The segment and record headers are themselves HybridRow serialized values
described by [RecordIO System Schema](../SystemSchemas/RecordIOSchema.json).

View File

@@ -0,0 +1,53 @@
`SchemaHash` is a 128-bit hash of a HybridRow schema.
[[_TOC_]]
# Schema Evolution
During schema evolution scenarios (e.g. DDL) it is necessary to ensure that the
historical record of previous schema versions has not been improperly altered
and that the new schema introduced (e.g. as a result of the DDL operation) are
a proper and valid superset of that history.
If the historical record were altered or truncated then existing "old" rows
within the table might no longer be parsable, or their values may be
misinterpreted. Because of potential changes to the schema language version,
and non-determinism in the natural encoding of JSON-based schemas (e.g.
ordering, commas, comments) textual comparison is both insufficient and
incorrect. A logical comparison of the relevant schema structure using only
canonical formulizations that will be true across schema language versions
should be used.
`SchemaHash` defines exactly and only the necessary structural elements and
thus implements a method for calculating the logical schema version (hash) of a
schema given a namespace that contains that schema and its dependent
closure of types.
Notes:
* The provided Namespace may contain additional schemas not related to the
given schema (including other versions of that schema or other versions of
its dependent types).
* `SchemaHash` applies to a particular schema at a time, not an entire
namespace of schema.
* `SchemaHash` incorporates recursively the `SchemaHash` of each nested
schema (aka UDTs) that appear in the schema closure of the type. Thus a
`SchemaHash` provides a snapshot *version* that uniquely describes a row's
metadata at a specific point in time.
# Algorithm
`SchemaHash` is computed as an accumulated Murmur hash. The Little Endian,
x64, 128-bit [MurmurHash3](https://en.wikipedia.org/wiki/MurmurHash) algorithm
is to be used. The hash is computed over the relevant structural elements.
The hash is accumulated by passing the current accumulated
hash as the seed for the next round of hashing, thus chaining the hash results
until all structures are hashed.
All structural elements of the schema are hashed as individual blocks encoding
each block as the little-endian byte sequence with the following caveats:
* null values are skipped (contribute nothing to the accumulation).
* strings are hashed as their canonicalized UTF8 byte sequence without either
the length or null-termination.
* bools are hashed as a single byte: 1 for true and 0 for false.
* Lists are hashed by hashing their element in the ordered sequence in which they
appear in the SDL. Order matters. Empty lists are treated as nulls.

View File

@@ -0,0 +1,49 @@
`SchemaId` are 32-bit unique identifiers used in Hybrid Row to uniquely
reference a Schema type within a Schema Namespace. Schema have names, but
since multiple revisions of the same Schema may be defined within the same
Schema Namespace, the `SchemaId` distiguishes the revisions from each other.
[[_TOC_]]
# Hybrid Row Runtime
There is no `SchemaId` allocation policy imposed directly by the Hybrid Row
runtime, however, the runtime does require that all policies meet the
following requirements:
* All `SchemaId` **MUST** be unique **within** a Schema Namespace.
* The `SchemaId` of `0` is reserved as the `Invalid` `SchemaId` and
must never be assigned.
# Cosmos DB
This section describes the set of convention used for `SchemaId` allocation
by [Azure Cosmos DB](https://azure.microsoft.com/en-us/services/cosmos-db/).
Cosmos DB's `SchemaId` policy sub-divides the available 32-bit numeric address
space for `SchemaId` into distinct non-overlapping regions and assigns distinct
semantics for each region:
Name | Range | Description
--- | --- | ---
Invalid | **0** | Reserved (by runtime) [\*](#bugs-and-known-issues)
Table Schema | [$-1000000$ .. $-1$] | Each revision of the table schema has a distinct *monotonically decreasing* numeric value.
UDT Schema | [$1$ .. $1000000$] | Each revision of each UDT schema has a distinct *monotonically increasing* numeric value.
System Schema | [$2147473648$ .. $2147483647$] <br/><br/> [`Int32.Max` - $9,999$ .. `Int32.Max`] | Reserved for system defined schema types.
Dynamic Schema | [$2146473647$ .. $2147473647$] <br/><br/> [`Int32.Max` - $1,010,000$ .. `Int32.Max` - $10,000$] | Reserved for context-specific schema generated dynamically (e.g. result set schema scoped to a channel.)
App-Specific Schema | [$2145473647$ .. $2146473647$] <br/><br/> [`Int32.Max` - $2,010,000$ .. `Int32.Max` - $1,009,999$] | Reserved for app-specific schema. (e.g. Batch API schema used by the Cosmos DB application/sdk.)
## Monotonicity
Cosmos DB allows for the evolution of Schema over time. As Schema evolve new
revisions of their Schema are committed to the Schema Namespace. Because
existing encoded rows remain in the store referencing older revisions, all
revisions of a given Schema referenced by at least one existing row **MUST**
be retained in the Schema Namespace. As Schema are evolved, later revisions
of a Schema are assigned `SchemaId` whose (*absolute*) value is larger than
any previous revision of that Schema. The latest revision of any given Schema
is the Schema within the Schema Namespace with both a matching name and the
largest (*absolute*) `SchemaId` value.
## Bugs and Known Issues
A bug in the initial Cassandra GA code allocated some type schemas using the
`SchemaId` `0` accidentally.

View File

@@ -0,0 +1,27 @@
System Schema are globally available HybridRow Schema definitions. This
document summarizes the available schema namespaces and their reserved SchemaId
allocated from the [System Schema](./SchemaId.md) address space.
[[_TOC_]]
# System Schema Catalog
The following are System Schema namespaces defined by the HybridRow runtime:
* [**Microsoft.Azure.Cosmos.HybridRow.RecordIO**](./RecordIO.md):
Defines types used in streaming record-oriented files containing HybridRows.
# SchemaId Reserved by the HybridRow Runtime
* $2147473648$ - RecordIO Segment
* $2147473649$ - RecordIO Record
* $2147473650$ - Empty Schema
# SchemaId Reserved by the Cosmos DB application
*These must be within [2145473647..2146473647]*
* $2145473647$ - HybridRow Query Response
* $2145473648$ - Batch API Operation
* $2145473649$ - Batch API Result
* $2145473650$ - Patch Request
* $2145473651$ - Patch Operation
* $2145473652$ - Json Schema

View File

@@ -0,0 +1,57 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable CA1051 // Do not declare visible instance fields
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow
{
using System.Diagnostics;
using System.Runtime.InteropServices;
/// <summary>An IEEE 128-bit floating point value.</summary>
/// <remarks>
/// A binary integer decimal representation of a 128-bit decimal value, supporting 34 decimal digits of
/// significand and an exponent range of -6143 to +6144.
/// <list type="table">
/// <listheader>
/// <term>Source</term> <description>Link</description>
/// </listheader> <item>
/// <term>Wikipedia:</term>
/// <description>https://en.wikipedia.org/wiki/Decimal128_floating-point_format</description>
/// </item> <item>
/// <term>The spec:</term> <description>https://ieeexplore.ieee.org/document/4610935</description>
/// </item> <item>
/// <term>Decimal Encodings:</term> <description>http://speleotrove.com/decimal/decbits.html</description>
/// </item>
/// </list>
/// </remarks>
[DebuggerDisplay("{" + nameof(Float128.Low) + "," + nameof(Float128.High) + "}")]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct Float128
{
/// <summary>The size (in bytes) of a <see cref="Float128" />.</summary>
public const int Size = sizeof(long) + sizeof(long);
/// <summary>
/// The low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point, using the BID
/// encoding scheme.
/// </summary>
public readonly long Low;
/// <summary>
/// The high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point, using the BID
/// encoding scheme.
/// </summary>
public readonly long High;
/// <summary>Initializes a new instance of the <see cref="Float128" /> struct.</summary>
/// <param name="high">the high-order 64 bits.</param>
/// <param name="low">the low-order 64 bits.</param>
public Float128(long high, long low)
{
this.High = high;
this.Low = low;
}
}
}

View File

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

View File

@@ -0,0 +1,33 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow
{
using System.Runtime.InteropServices;
/// <summary>Describes the header the precedes all valid Hybrid Rows.</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct HybridRowHeader
{
/// <summary>Size (in bytes) of a serialized header.</summary>
public const int Size = sizeof(HybridRowVersion) + SchemaId.Size;
/// <summary>
/// Initializes a new instance of the <see cref="HybridRowHeader"/> struct.
/// </summary>
/// <param name="version">The version of the HybridRow library used to write this row.</param>
/// <param name="schemaId">The unique identifier of the schema whose layout was used to write this row.</param>
public HybridRowHeader(HybridRowVersion version, SchemaId schemaId)
{
this.Version = version;
this.SchemaId = schemaId;
}
/// <summary>The version of the HybridRow library used to write this row.</summary>
public HybridRowVersion Version { get; }
/// <summary>The unique identifier of the schema whose layout was used to write this row.</summary>
public SchemaId SchemaId { get; }
}
}

View File

@@ -0,0 +1,18 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
#pragma warning disable CA1028 // Enum Storage should be Int32
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow
{
/// <summary>Versions of HybridRow.</summary>
/// <remarks>A version from this list MUST be inserted in the version BOM at the beginning of all rows.</remarks>
public enum HybridRowVersion : byte
{
Invalid = 0,
/// <summary>Initial version of the HybridRow format.</summary>
V1 = 0x81,
}
}

View File

@@ -0,0 +1,22 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO
{
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
/// <summary>
/// A type may implement this interface to support serialization into a HybridRow.
/// </summary>
public interface IRowSerializable
{
/// <summary>
/// Writes the current instance into the row.
/// </summary>
/// <param name="writer">A writer for the current row scope.</param>
/// <param name="typeArg">The schematized layout type, if a schema is available.</param>
/// <returns>Success if the write is successful, the error code otherwise.</returns>
Result Write(ref RowWriter writer, TypeArgument typeArg);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO
{
using System.Collections.Generic;
public static class RowReaderExtensions
{
/// <summary>A function to read content from a <see cref="RowReader" />.</summary>
/// <typeparam name="TItem">The type of the item to read.</typeparam>
/// <param name="reader">A forward-only cursor for reading the item.</param>
/// <param name="item">On success, the item read.</param>
/// <returns>The result.</returns>
public delegate Result DeserializerFunc<TItem>(ref RowReader reader, out TItem item);
/// <summary>Read the current field as a nested, structured, sparse scope containing a linear collection of zero or more items.</summary>
/// <typeparam name="TItem">The type of the items within the collection.</typeparam>
/// <param name="reader">A forward-only cursor for reading the collection.</param>
/// <param name="deserializer">A function that reads one item from the collection.</param>
/// <param name="list">On success, the collection of materialized items.</param>
/// <returns>The result.</returns>
public static Result ReadList<TItem>(this ref RowReader reader, DeserializerFunc<TItem> deserializer, out List<TItem> list)
{
// Pass the context as a struct by value to avoid allocations.
ListContext<TItem> ctx = new ListContext<TItem>
{
List = new List<TItem>(),
Deserializer = deserializer,
};
// All lambda's here are static.
Result r = reader.ReadScope(
ctx,
(ref RowReader arrayReader, ListContext<TItem> ctx1) =>
{
while (arrayReader.Read())
{
Result r2 = arrayReader.ReadScope(
ctx1,
(ref RowReader itemReader, ListContext<TItem> ctx2) =>
{
Result r3 = ctx2.Deserializer(ref itemReader, out TItem op);
if (r3 != Result.Success)
{
return r3;
}
ctx2.List.Add(op);
return Result.Success;
});
if (r2 != Result.Success)
{
return r2;
}
}
return Result.Success;
});
if (r != Result.Success)
{
list = default;
return r;
}
list = ctx.List;
return Result.Success;
}
private struct ListContext<TItem>
{
public List<TItem> List;
public DeserializerFunc<TItem> Deserializer;
}
}
}

View File

@@ -0,0 +1,810 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO
{
using System;
using System.Buffers;
using Microsoft.Azure.Cosmos.Core.Utf8;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
public ref struct RowWriter
{
private RowBuffer row;
private RowCursor cursor;
/// <summary>Initializes a new instance of the <see cref="RowWriter" /> struct.</summary>
/// <param name="row">The row to be read.</param>
/// <param name="scope">The scope into which items should be written.</param>
/// <remarks>
/// A <see cref="RowWriter" /> instance writes the fields of a given scope from left to right
/// in a forward only manner. If the root scope is provided then all top-level fields in the row can be
/// written.
/// </remarks>
private RowWriter(ref RowBuffer row, ref RowCursor scope)
{
this.row = row;
this.cursor = scope;
}
/// <summary>A function to write content into a <see cref="RowBuffer" />.</summary>
/// <typeparam name="TContext">The type of the context value passed by the caller.</typeparam>
/// <param name="writer">A forward-only cursor for writing content.</param>
/// <param name="typeArg">The type of the current scope.</param>
/// <param name="context">A context value provided by the caller.</param>
/// <returns>The result.</returns>
public delegate Result WriterFunc<in TContext>(ref RowWriter writer, TypeArgument typeArg, TContext context);
private delegate void AccessMethod<in TValue>(ref RowWriter writer, TValue value);
private delegate void AccessReadOnlySpanMethod<T>(ref RowWriter writer, ReadOnlySpan<T> value);
private delegate void AccessUtf8SpanMethod(ref RowWriter writer, Utf8Span value);
/// <summary>The resolver for UDTs.</summary>
public LayoutResolver Resolver => this.row.Resolver;
/// <summary>The length of row in bytes.</summary>
public int Length => this.row.Length;
/// <summary>The active layout of the current writer scope.</summary>
public Layout Layout => this.cursor.layout;
/// <summary>Write an entire row in a streaming left-to-right way.</summary>
/// <typeparam name="TContext">The type of the context value to pass to <paramref name="func" />.</typeparam>
/// <param name="row">The row to write.</param>
/// <param name="context">A context value to pass to <paramref name="func" />.</param>
/// <param name="func">A function to write the entire row.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public static Result WriteBuffer<TContext>(ref RowBuffer row, TContext context, WriterFunc<TContext> func)
{
RowCursor scope = RowCursor.Create(ref row);
RowWriter writer = new RowWriter(ref row, ref scope);
TypeArgument typeArg = new TypeArgument(LayoutType.UDT, new TypeArgumentList(scope.layout.SchemaId));
Result result = func(ref writer, typeArg, context);
row = writer.row;
return result;
}
/// <summary>Write a field as a <see cref="bool" />.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteBool(UtfAnyString path, bool value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Boolean,
(ref RowWriter w, bool v) => w.row.WriteSparseBool(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a <see cref="t:null"/>.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteNull(UtfAnyString path)
{
return this.WritePrimitive(
path,
NullValue.Default,
LayoutType.Null,
(ref RowWriter w, NullValue v) => w.row.WriteSparseNull(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 8-bit, signed integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteInt8(UtfAnyString path, sbyte value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Int8,
(ref RowWriter w, sbyte v) => w.row.WriteSparseInt8(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 16-bit, signed integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteInt16(UtfAnyString path, short value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Int16,
(ref RowWriter w, short v) => w.row.WriteSparseInt16(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 32-bit, signed integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteInt32(UtfAnyString path, int value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Int32,
(ref RowWriter w, int v) => w.row.WriteSparseInt32(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 64-bit, signed integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteInt64(UtfAnyString path, long value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Int64,
(ref RowWriter w, long v) => w.row.WriteSparseInt64(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 8-bit, unsigned integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteUInt8(UtfAnyString path, byte value)
{
return this.WritePrimitive(
path,
value,
LayoutType.UInt8,
(ref RowWriter w, byte v) => w.row.WriteSparseUInt8(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 16-bit, unsigned integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteUInt16(UtfAnyString path, ushort value)
{
return this.WritePrimitive(
path,
value,
LayoutType.UInt16,
(ref RowWriter w, ushort v) => w.row.WriteSparseUInt16(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 32-bit, unsigned integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteUInt32(UtfAnyString path, uint value)
{
return this.WritePrimitive(
path,
value,
LayoutType.UInt32,
(ref RowWriter w, uint v) => w.row.WriteSparseUInt32(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 64-bit, unsigned integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteUInt64(UtfAnyString path, ulong value)
{
return this.WritePrimitive(
path,
value,
LayoutType.UInt64,
(ref RowWriter w, ulong v) => w.row.WriteSparseUInt64(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a variable length, 7-bit encoded, signed integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteVarInt(UtfAnyString path, long value)
{
return this.WritePrimitive(
path,
value,
LayoutType.VarInt,
(ref RowWriter w, long v) => w.row.WriteSparseVarInt(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a variable length, 7-bit encoded, unsigned integer.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteVarUInt(UtfAnyString path, ulong value)
{
return this.WritePrimitive(
path,
value,
LayoutType.VarUInt,
(ref RowWriter w, ulong v) => w.row.WriteSparseVarUInt(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 32-bit, IEEE-encoded floating point value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteFloat32(UtfAnyString path, float value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Float32,
(ref RowWriter w, float v) => w.row.WriteSparseFloat32(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 64-bit, IEEE-encoded floating point value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteFloat64(UtfAnyString path, double value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Float64,
(ref RowWriter w, double v) => w.row.WriteSparseFloat64(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length, 128-bit, IEEE-encoded floating point value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteFloat128(UtfAnyString path, Float128 value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Float128,
(ref RowWriter w, Float128 v) => w.row.WriteSparseFloat128(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length <see cref="decimal" /> value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteDecimal(UtfAnyString path, decimal value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Decimal,
(ref RowWriter w, decimal v) => w.row.WriteSparseDecimal(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length <see cref="DateTime" /> value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteDateTime(UtfAnyString path, DateTime value)
{
return this.WritePrimitive(
path,
value,
LayoutType.DateTime,
(ref RowWriter w, DateTime v) => w.row.WriteSparseDateTime(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length <see cref="UnixDateTime" /> value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteUnixDateTime(UtfAnyString path, UnixDateTime value)
{
return this.WritePrimitive(
path,
value,
LayoutType.UnixDateTime,
(ref RowWriter w, UnixDateTime v) => w.row.WriteSparseUnixDateTime(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length <see cref="Guid" /> value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteGuid(UtfAnyString path, Guid value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Guid,
(ref RowWriter w, Guid v) => w.row.WriteSparseGuid(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a fixed length <see cref="MongoDbObjectId" /> value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteMongoDbObjectId(UtfAnyString path, MongoDbObjectId value)
{
return this.WritePrimitive(
path,
value,
LayoutType.MongoDbObjectId,
(ref RowWriter w, MongoDbObjectId v) => w.row.WriteSparseMongoDbObjectId(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a variable length, UTF8 encoded, string value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteString(UtfAnyString path, string value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Utf8,
(ref RowWriter w, string v) => w.row.WriteSparseString(ref w.cursor, Utf8Span.TranscodeUtf16(v), UpdateOptions.Upsert));
}
/// <summary>Write a field as a variable length, UTF8 encoded, string value.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteString(UtfAnyString path, Utf8Span value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Utf8,
(ref RowWriter w, Utf8Span v) => w.row.WriteSparseString(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a variable length, sequence of bytes.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteBinary(UtfAnyString path, byte[] value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Binary,
(ref RowWriter w, byte[] v) => w.row.WriteSparseBinary(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a variable length, sequence of bytes.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteBinary(UtfAnyString path, ReadOnlySpan<byte> value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Binary,
(ref RowWriter w, ReadOnlySpan<byte> v) => w.row.WriteSparseBinary(ref w.cursor, v, UpdateOptions.Upsert));
}
/// <summary>Write a field as a variable length, sequence of bytes.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
public Result WriteBinary(UtfAnyString path, ReadOnlySequence<byte> value)
{
return this.WritePrimitive(
path,
value,
LayoutType.Binary,
(ref RowWriter w, ReadOnlySequence<byte> v) => w.row.WriteSparseBinary(ref w.cursor, v, UpdateOptions.Upsert));
}
public Result WriteScope<TContext>(UtfAnyString path, TypeArgument typeArg, TContext context, WriterFunc<TContext> func)
{
LayoutType type = typeArg.Type;
Result result = this.PrepareSparseWrite(path, typeArg);
if (result != Result.Success)
{
return result;
}
RowCursor nestedScope;
switch (type)
{
case LayoutObject scopeType:
this.row.WriteSparseObject(ref this.cursor, scopeType, UpdateOptions.Upsert, out nestedScope);
break;
case LayoutArray scopeType:
this.row.WriteSparseArray(ref this.cursor, scopeType, UpdateOptions.Upsert, out nestedScope);
break;
case LayoutTypedArray scopeType:
this.row.WriteTypedArray(
ref this.cursor,
scopeType,
typeArg.TypeArgs,
UpdateOptions.Upsert,
out nestedScope);
break;
case LayoutTuple scopeType:
this.row.WriteSparseTuple(
ref this.cursor,
scopeType,
typeArg.TypeArgs,
UpdateOptions.Upsert,
out nestedScope);
break;
case LayoutTypedTuple scopeType:
this.row.WriteTypedTuple(
ref this.cursor,
scopeType,
typeArg.TypeArgs,
UpdateOptions.Upsert,
out nestedScope);
break;
case LayoutTagged scopeType:
this.row.WriteTypedTuple(
ref this.cursor,
scopeType,
typeArg.TypeArgs,
UpdateOptions.Upsert,
out nestedScope);
break;
case LayoutTagged2 scopeType:
this.row.WriteTypedTuple(
ref this.cursor,
scopeType,
typeArg.TypeArgs,
UpdateOptions.Upsert,
out nestedScope);
break;
case LayoutNullable scopeType:
this.row.WriteNullable(
ref this.cursor,
scopeType,
typeArg.TypeArgs,
UpdateOptions.Upsert,
func != null,
out nestedScope);
break;
case LayoutUDT scopeType:
Layout udt = this.row.Resolver.Resolve(typeArg.TypeArgs.SchemaId);
this.row.WriteSparseUDT(ref this.cursor, scopeType, udt, UpdateOptions.Upsert, out nestedScope);
break;
case LayoutTypedSet scopeType:
this.row.WriteTypedSet(
ref this.cursor,
scopeType,
typeArg.TypeArgs,
UpdateOptions.Upsert,
out nestedScope);
break;
case LayoutTypedMap scopeType:
this.row.WriteTypedMap(
ref this.cursor,
scopeType,
typeArg.TypeArgs,
UpdateOptions.Upsert,
out nestedScope);
break;
default:
return Result.Failure;
}
RowWriter nestedWriter = new RowWriter(ref this.row, ref nestedScope);
result = func?.Invoke(ref nestedWriter, typeArg, context) ?? Result.Success;
this.row = nestedWriter.row;
nestedScope.count = nestedWriter.cursor.count;
if (result != Result.Success)
{
// TODO: what about unique violations here?
return result;
}
if (type is LayoutUniqueScope)
{
result = this.row.TypedCollectionUniqueIndexRebuild(ref nestedScope);
if (result != Result.Success)
{
// TODO: If the index rebuild fails then the row is corrupted. Should we automatically clean up here?
return result;
}
}
this.cursor.MoveNext(ref this.row, ref nestedWriter.cursor);
return Result.Success;
}
/// <summary>Helper for writing a primitive value.</summary>
/// <typeparam name="TValue">The type of the primitive value.</typeparam>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <param name="type">The layout type.</param>
/// <param name="sparse">The <see cref="RowBuffer" /> access method for <paramref name="type" />.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
private Result WritePrimitive<TValue>(UtfAnyString path, TValue value, LayoutType<TValue> type, AccessMethod<TValue> sparse)
{
Result result = Result.NotFound;
if (this.cursor.scopeType is LayoutUDT)
{
result = this.WriteSchematizedValue(path, value);
}
if (result == Result.NotFound)
{
// Write sparse value.
result = this.PrepareSparseWrite(path, type.TypeArg);
if (result != Result.Success)
{
return result;
}
sparse(ref this, value);
this.cursor.MoveNext(ref this.row);
}
return result;
}
/// <summary>Helper for writing a primitive value.</summary>
/// <typeparam name="TLayoutType">The type of layout type.</typeparam>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <param name="type">The layout type.</param>
/// <param name="sparse">The <see cref="RowBuffer" /> access method for <paramref name="type" />.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
private Result WritePrimitive<TLayoutType>(
UtfAnyString path,
Utf8Span value,
TLayoutType type,
AccessUtf8SpanMethod sparse)
where TLayoutType : LayoutType<string>, ILayoutUtf8SpanWritable
{
Result result = Result.NotFound;
if (this.cursor.scopeType is LayoutUDT)
{
result = this.WriteSchematizedValue(path, value);
}
if (result == Result.NotFound)
{
// Write sparse value.
result = this.PrepareSparseWrite(path, type.TypeArg);
if (result != Result.Success)
{
return result;
}
sparse(ref this, value);
this.cursor.MoveNext(ref this.row);
}
return result;
}
/// <summary>Helper for writing a primitive value.</summary>
/// <typeparam name="TLayoutType">The type of layout type.</typeparam>
/// <typeparam name="TElement">The sub-element type of the field.</typeparam>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <param name="type">The layout type.</param>
/// <param name="sparse">The <see cref="RowBuffer" /> access method for <paramref name="type" />.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
private Result WritePrimitive<TLayoutType, TElement>(
UtfAnyString path,
ReadOnlySpan<TElement> value,
TLayoutType type,
AccessReadOnlySpanMethod<TElement> sparse)
where TLayoutType : LayoutType<TElement[]>, ILayoutSpanWritable<TElement>
{
Result result = Result.NotFound;
if (this.cursor.scopeType is LayoutUDT)
{
result = this.WriteSchematizedValue(path, value);
}
if (result == Result.NotFound)
{
// Write sparse value.
result = this.PrepareSparseWrite(path, type.TypeArg);
if (result != Result.Success)
{
return result;
}
sparse(ref this, value);
this.cursor.MoveNext(ref this.row);
}
return result;
}
/// <summary>Helper for writing a primitive value.</summary>
/// <typeparam name="TLayoutType">The type of layout type.</typeparam>
/// <typeparam name="TElement">The sub-element type of the field.</typeparam>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <param name="type">The layout type.</param>
/// <param name="sparse">The <see cref="RowBuffer" /> access method for <paramref name="type" />.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
private Result WritePrimitive<TLayoutType, TElement>(
UtfAnyString path,
ReadOnlySequence<TElement> value,
TLayoutType type,
AccessMethod<ReadOnlySequence<TElement>> sparse)
where TLayoutType : LayoutType<TElement[]>, ILayoutSequenceWritable<TElement>
{
Result result = Result.NotFound;
if (this.cursor.scopeType is LayoutUDT)
{
result = this.WriteSchematizedValue(path, value);
}
if (result == Result.NotFound)
{
// Write sparse value.
result = this.PrepareSparseWrite(path, type.TypeArg);
if (result != Result.Success)
{
return result;
}
sparse(ref this, value);
this.cursor.MoveNext(ref this.row);
}
return result;
}
/// <summary>Helper for preparing the write of a sparse field.</summary>
/// <param name="path">The path identifying the field to write.</param>
/// <param name="typeArg">The (optional) type constraints.</param>
/// <returns>Success if the write is permitted, the error code otherwise.</returns>
private Result PrepareSparseWrite(UtfAnyString path, TypeArgument typeArg)
{
if (this.cursor.scopeType.IsFixedArity && !(this.cursor.scopeType is LayoutNullable))
{
if ((this.cursor.index < this.cursor.scopeTypeArgs.Count) && !typeArg.Equals(this.cursor.scopeTypeArgs[this.cursor.index]))
{
return Result.TypeConstraint;
}
}
else if (this.cursor.scopeType is LayoutTypedMap)
{
if (!typeArg.Equals(this.cursor.scopeType.TypeAs<LayoutUniqueScope>().FieldType(ref this.cursor)))
{
return Result.TypeConstraint;
}
}
else if (this.cursor.scopeType.IsTypedScope && !typeArg.Equals(this.cursor.scopeTypeArgs[0]))
{
return Result.TypeConstraint;
}
this.cursor.writePath = path;
return Result.Success;
}
/// <summary>Write a generic schematized field value via the scope's layout.</summary>
/// <typeparam name="TValue">The expected type of the field.</typeparam>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
private Result WriteSchematizedValue<TValue>(UtfAnyString path, TValue value)
{
if (!this.cursor.layout.TryFind(path, out LayoutColumn col))
{
return Result.NotFound;
}
if (!(col.Type is LayoutType<TValue> t))
{
return Result.NotFound;
}
switch (col.Storage)
{
case StorageKind.Fixed:
return t.WriteFixed(ref this.row, ref this.cursor, col, value);
case StorageKind.Variable:
return t.WriteVariable(ref this.row, ref this.cursor, col, value);
default:
return Result.NotFound;
}
return Result.NotFound;
}
/// <summary>Write a generic schematized field value via the scope's layout.</summary>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
private Result WriteSchematizedValue(UtfAnyString path, Utf8Span value)
{
if (!this.cursor.layout.TryFind(path, out LayoutColumn col))
{
return Result.NotFound;
}
LayoutType t = col.Type;
if (!(t is ILayoutUtf8SpanWritable))
{
return Result.NotFound;
}
switch (col.Storage)
{
case StorageKind.Fixed:
return t.TypeAs<ILayoutUtf8SpanWritable>().WriteFixed(ref this.row, ref this.cursor, col, value);
case StorageKind.Variable:
return t.TypeAs<ILayoutUtf8SpanWritable>().WriteVariable(ref this.row, ref this.cursor, col, value);
default:
return Result.NotFound;
}
}
/// <summary>Write a generic schematized field value via the scope's layout.</summary>
/// <typeparam name="TElement">The sub-element type of the field.</typeparam>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
private Result WriteSchematizedValue<TElement>(UtfAnyString path, ReadOnlySpan<TElement> value)
{
if (!this.cursor.layout.TryFind(path, out LayoutColumn col))
{
return Result.NotFound;
}
LayoutType t = col.Type;
if (!(t is ILayoutSpanWritable<TElement>))
{
return Result.NotFound;
}
switch (col.Storage)
{
case StorageKind.Fixed:
return t.TypeAs<ILayoutSpanWritable<TElement>>().WriteFixed(ref this.row, ref this.cursor, col, value);
case StorageKind.Variable:
return t.TypeAs<ILayoutSpanWritable<TElement>>().WriteVariable(ref this.row, ref this.cursor, col, value);
default:
return Result.NotFound;
}
}
/// <summary>Write a generic schematized field value via the scope's layout.</summary>
/// <typeparam name="TElement">The sub-element type of the field.</typeparam>
/// <param name="path">The scope-relative path of the field to write.</param>
/// <param name="value">The value to write.</param>
/// <returns>Success if the write is successful, an error code otherwise.</returns>
private Result WriteSchematizedValue<TElement>(UtfAnyString path, ReadOnlySequence<TElement> value)
{
if (!this.cursor.layout.TryFind(path, out LayoutColumn col))
{
return Result.NotFound;
}
LayoutType t = col.Type;
if (!(t is ILayoutSequenceWritable<TElement>))
{
return Result.NotFound;
}
switch (col.Storage)
{
case StorageKind.Fixed:
return t.TypeAs<ILayoutSequenceWritable<TElement>>().WriteFixed(ref this.row, ref this.cursor, col, value);
case StorageKind.Variable:
return t.TypeAs<ILayoutSequenceWritable<TElement>>().WriteVariable(ref this.row, ref this.cursor, col, value);
default:
return Result.NotFound;
}
}
}
}

View File

@@ -0,0 +1,24 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Serialization.HybridRow
{
using System;
public interface ISpanResizer<T>
{
/// <summary>Resizes an existing a buffer.</summary>
/// <typeparam name="T">The type of the elements of the memory.</typeparam>
/// <param name="minimumLength">The minimum required length (in elements) of the memory.</param>
/// <param name="buffer">
/// Optional existing memory to be copied to the new buffer. Ownership of <paramref name="buffer" /> is
/// transferred as part of this call and it should not be used by the caller after this call completes.
/// </param>
/// <returns>
/// A new memory whose size is <em>at least as big</em> as <paramref name="minimumLength" />
/// and containing the content of <paramref name="buffer" />.
/// </returns>
Span<T> Resize(int minimumLength, Span<T> buffer = default);
}
}

Some files were not shown because too many files have changed in this diff Show More