mirror of
https://github.com/microsoft/HybridRow.git
synced 2026-01-22 19:03:05 +00:00
Copied dotnet code from CosmosDB repository
This commit is contained in:
@@ -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>
|
||||
20
dotnet/src/HybridRow.Json/Properties/AssemblyInfo.cs
Normal file
20
dotnet/src/HybridRow.Json/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
453
dotnet/src/HybridRow.Json/RowReaderJsonExtensions.cs
Normal file
453
dotnet/src/HybridRow.Json/RowReaderJsonExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
dotnet/src/HybridRow.Json/RowReaderJsonSettings.cs
Normal file
24
dotnet/src/HybridRow.Json/RowReaderJsonSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
11
dotnet/src/HybridRow.Tests.Perf/App.config
Normal file
11
dotnet/src/HybridRow.Tests.Perf/App.config
Normal 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>
|
||||
119
dotnet/src/HybridRow.Tests.Perf/BenchmarkSuiteBase.cs
Normal file
119
dotnet/src/HybridRow.Tests.Perf/BenchmarkSuiteBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
dotnet/src/HybridRow.Tests.Perf/BsonJsonModelRowGenerator.cs
Normal file
110
dotnet/src/HybridRow.Tests.Perf/BsonJsonModelRowGenerator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
dotnet/src/HybridRow.Tests.Perf/BsonReaderExtensions.cs
Normal file
63
dotnet/src/HybridRow.Tests.Perf/BsonReaderExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
303
dotnet/src/HybridRow.Tests.Perf/BsonRowGenerator.cs
Normal file
303
dotnet/src/HybridRow.Tests.Perf/BsonRowGenerator.cs
Normal 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
293
dotnet/src/HybridRow.Tests.Perf/CodeGenMicroBenchmarkSuite.cs
Normal file
293
dotnet/src/HybridRow.Tests.Perf/CodeGenMicroBenchmarkSuite.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
872
dotnet/src/HybridRow.Tests.Perf/CodeGenRowGenerator.cs
Normal file
872
dotnet/src/HybridRow.Tests.Perf/CodeGenRowGenerator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
100
dotnet/src/HybridRow.Tests.Perf/GenerateBenchmarkSuite.cs
Normal file
100
dotnet/src/HybridRow.Tests.Perf/GenerateBenchmarkSuite.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
dotnet/src/HybridRow.Tests.Perf/GenerateProtoBuf.cmd
Normal file
7
dotnet/src/HybridRow.Tests.Perf/GenerateProtoBuf.cmd
Normal 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
|
||||
43
dotnet/src/HybridRow.Tests.Perf/HybridRowPerf.csv
Normal file
43
dotnet/src/HybridRow.Tests.Perf/HybridRowPerf.csv
Normal 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
|
||||
|
132
dotnet/src/HybridRow.Tests.Perf/JsonModelRowGenerator.cs
Normal file
132
dotnet/src/HybridRow.Tests.Perf/JsonModelRowGenerator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
dotnet/src/HybridRow.Tests.Perf/Measurements.cs
Normal file
89
dotnet/src/HybridRow.Tests.Perf/Measurements.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
dotnet/src/HybridRow.Tests.Perf/MicroBenchmarkSuiteBase.cs
Normal file
128
dotnet/src/HybridRow.Tests.Perf/MicroBenchmarkSuiteBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
19
dotnet/src/HybridRow.Tests.Perf/Properties/AssemblyInfo.cs
Normal file
19
dotnet/src/HybridRow.Tests.Perf/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
315
dotnet/src/HybridRow.Tests.Perf/ProtobufRowGenerator.cs
Normal file
315
dotnet/src/HybridRow.Tests.Perf/ProtobufRowGenerator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
282
dotnet/src/HybridRow.Tests.Perf/ReaderBenchmark.cs
Normal file
282
dotnet/src/HybridRow.Tests.Perf/ReaderBenchmark.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
dotnet/src/HybridRow.Tests.Perf/RowReaderExtensions.cs
Normal file
97
dotnet/src/HybridRow.Tests.Perf/RowReaderExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
dotnet/src/HybridRow.Tests.Perf/TestData.cs
Normal file
22
dotnet/src/HybridRow.Tests.Perf/TestData.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
@@ -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" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/CombinedScriptsData.hr
Normal file
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/CombinedScriptsData.hr
Normal file
Binary file not shown.
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/GuestsSchemaExpected.hr
Normal file
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/GuestsSchemaExpected.hr
Normal file
Binary file not shown.
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/HotelSchemaExpected.hr
Normal file
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/HotelSchemaExpected.hr
Normal file
Binary file not shown.
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/Messages1KExpected.hr
Normal file
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/Messages1KExpected.hr
Normal file
Binary file not shown.
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/RoomsSchemaExpected.hr
Normal file
BIN
dotnet/src/HybridRow.Tests.Perf/TestData/RoomsSchemaExpected.hr
Normal file
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
dotnet/src/HybridRow.Tests.Unit/App.config
Normal file
11
dotnet/src/HybridRow.Tests.Unit/App.config
Normal 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>
|
||||
59
dotnet/src/HybridRow.Tests.Unit/ArrayAssert.cs
Normal file
59
dotnet/src/HybridRow.Tests.Unit/ArrayAssert.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
137
dotnet/src/HybridRow.Tests.Unit/AssertThrowsException.cs
Normal file
137
dotnet/src/HybridRow.Tests.Unit/AssertThrowsException.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
494
dotnet/src/HybridRow.Tests.Unit/CrossVersioningUnitTests.cs
Normal file
494
dotnet/src/HybridRow.Tests.Unit/CrossVersioningUnitTests.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
481
dotnet/src/HybridRow.Tests.Unit/CustomerExampleUnitTests.cs
Normal file
481
dotnet/src/HybridRow.Tests.Unit/CustomerExampleUnitTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Address.cs
Normal file
51
dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Address.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Guest.cs
Normal file
103
dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Guest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Hotel.cs
Normal file
51
dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Hotel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
dotnet/src/HybridRow.Tests.Unit/CustomerSchema/PostalCode.cs
Normal file
42
dotnet/src/HybridRow.Tests.Unit/CustomerSchema/PostalCode.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
dotnet/src/HybridRow.Tests.Unit/GlobalSuppressions.cs
Normal file
9
dotnet/src/HybridRow.Tests.Unit/GlobalSuppressions.cs
Normal 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")]
|
||||
102
dotnet/src/HybridRow.Tests.Unit/Internal/MurmurHash3UnitTests.cs
Normal file
102
dotnet/src/HybridRow.Tests.Unit/Internal/MurmurHash3UnitTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1999
dotnet/src/HybridRow.Tests.Unit/LayoutCompilerUnitTests.cs
Normal file
1999
dotnet/src/HybridRow.Tests.Unit/LayoutCompilerUnitTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
53
dotnet/src/HybridRow.Tests.Unit/LayoutTypeUnitTests.cs
Normal file
53
dotnet/src/HybridRow.Tests.Unit/LayoutTypeUnitTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
437
dotnet/src/HybridRow.Tests.Unit/NullableUnitTests.cs
Normal file
437
dotnet/src/HybridRow.Tests.Unit/NullableUnitTests.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
dotnet/src/HybridRow.Tests.Unit/PermuteExtensions.cs
Normal file
38
dotnet/src/HybridRow.Tests.Unit/PermuteExtensions.cs
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
dotnet/src/HybridRow.Tests.Unit/Properties/AssemblyInfo.cs
Normal file
19
dotnet/src/HybridRow.Tests.Unit/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
66
dotnet/src/HybridRow.Tests.Unit/RandomGeneratorUnitTests.cs
Normal file
66
dotnet/src/HybridRow.Tests.Unit/RandomGeneratorUnitTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
198
dotnet/src/HybridRow.Tests.Unit/RecordIOUnitTests.cs
Normal file
198
dotnet/src/HybridRow.Tests.Unit/RecordIOUnitTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
101
dotnet/src/HybridRow.Tests.Unit/ResultAssert.cs
Normal file
101
dotnet/src/HybridRow.Tests.Unit/ResultAssert.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
dotnet/src/HybridRow.Tests.Unit/RowBufferUnitTests.cs
Normal file
60
dotnet/src/HybridRow.Tests.Unit/RowBufferUnitTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
890
dotnet/src/HybridRow.Tests.Unit/RowOperationDispatcher.cs
Normal file
890
dotnet/src/HybridRow.Tests.Unit/RowOperationDispatcher.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
353
dotnet/src/HybridRow.Tests.Unit/RowReaderUnitTests.cs
Normal file
353
dotnet/src/HybridRow.Tests.Unit/RowReaderUnitTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
553
dotnet/src/HybridRow.Tests.Unit/RowWriterUnitTests.cs
Normal file
553
dotnet/src/HybridRow.Tests.Unit/RowWriterUnitTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
249
dotnet/src/HybridRow.Tests.Unit/SchemaHashUnitTests.cs
Normal file
249
dotnet/src/HybridRow.Tests.Unit/SchemaHashUnitTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
dotnet/src/HybridRow.Tests.Unit/SchemaIdUnitTests.cs
Normal file
47
dotnet/src/HybridRow.Tests.Unit/SchemaIdUnitTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
603
dotnet/src/HybridRow.Tests.Unit/SchemaUnitTests.cs
Normal file
603
dotnet/src/HybridRow.Tests.Unit/SchemaUnitTests.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
317
dotnet/src/HybridRow.Tests.Unit/SerializerUnitTest.cs
Normal file
317
dotnet/src/HybridRow.Tests.Unit/SerializerUnitTest.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
dotnet/src/HybridRow.Tests.Unit/TaggedUnitTests.cs
Normal file
145
dotnet/src/HybridRow.Tests.Unit/TaggedUnitTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
dotnet/src/HybridRow.Tests.Unit/TestData/BatchApiSchema.json
Normal file
96
dotnet/src/HybridRow.Tests.Unit/TestData/BatchApiSchema.json
Normal 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" } }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
211
dotnet/src/HybridRow.Tests.Unit/TestData/CoverageSchema.json
Normal file
211
dotnet/src/HybridRow.Tests.Unit/TestData/CoverageSchema.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"CrossVersionFixed": "8101000000FFFF1FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA3E555555555555D53F2A00000000000000000000000000000000001C00CA44C50A55555505CB00B714006E39578A01D6082A00000000000000B9259C2A2E921146BB0A244A9496503C2A0000000000000000000000616263000102",
|
||||
"CrossVersionNullFixed": "810100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"CrossVersionVariable": "81020000000FADD5AAD5AAD5AAD5AA01AAD5AAD5AAD5AAD5AA010361626303000102",
|
||||
"CrossVersionNullVariable": "810200000000",
|
||||
"CrossVersionSparse": "8103000000010103020503AA0604AAAA0705AAAAAAAA0806AAAAAAAAAAAAAAAA0907AA0A08AAAA0B09AAAAAAAA0C0AAAAAAAAAAAAAAAAA0F0BABAAAA3E100C555555555555D53F160D2A000000000000000000000000000000110E00001C00CA44C50A55555505CB00B714120F006E39578A01D60817102A000000000000001311B9259C2A2E921146BB0A244A9496503C18122A000000000000000000000014130361626315140300010222051503000000AAAAAA22220F1602000000030000000000803F0000004000004040030000000000803F00000040000040402214170300000003616263036465660368696A26020D0818ADD5AAD5AAD5AAD5AA01AAAAAAAAAAAAAAAA260201260205051901AAAA26020344040000001A02030100000002000000462E141B03000000036162630365666703787A792E22051C030000000300000001020303000000040506030000000708092E2E071D030000000300000001000000020000000300000003000000040000000500000006000000030000000700000008000000090000002E44040000001E030000000301000000020000004603030000000400000046030500000006000000462A14141F02000000044D61726B044C756B65084861727269736F6E0348616E2A0522052002000000010300000001020302030000000405062A062A0707210200000001000200000001000000020000000300000004000000020002000000050000000600000007000000080000002A1044040000002203000000000000000000004003030000000400000046000000000000084003050000000600000046000000000000F03F03010000000200000046",
|
||||
"CrossVersionNullSparse": "8103000000"
|
||||
}
|
||||
@@ -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" } }
|
||||
]}
|
||||
]
|
||||
}
|
||||
76
dotnet/src/HybridRow.Tests.Unit/TestData/CustomerSchema.json
Normal file
76
dotnet/src/HybridRow.Tests.Unit/TestData/CustomerSchema.json
Normal 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" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
40
dotnet/src/HybridRow.Tests.Unit/TestData/MovieSchema.json
Normal file
40
dotnet/src/HybridRow.Tests.Unit/TestData/MovieSchema.json
Normal 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" } }
|
||||
]}
|
||||
]
|
||||
}
|
||||
26
dotnet/src/HybridRow.Tests.Unit/TestData/NullableSchema.json
Normal file
26
dotnet/src/HybridRow.Tests.Unit/TestData/NullableSchema.json
Normal 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" } }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
143
dotnet/src/HybridRow.Tests.Unit/TestData/ReaderSchema.json
Normal file
143
dotnet/src/HybridRow.Tests.Unit/TestData/ReaderSchema.json
Normal 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" } }
|
||||
]}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
28
dotnet/src/HybridRow.Tests.Unit/TestData/TagSchema.json
Normal file
28
dotnet/src/HybridRow.Tests.Unit/TestData/TagSchema.json
Normal 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" } }
|
||||
]}
|
||||
]
|
||||
}
|
||||
@@ -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 }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
75
dotnet/src/HybridRow.Tests.Unit/TestData/TodoSchema.json
Normal file
75
dotnet/src/HybridRow.Tests.Unit/TestData/TodoSchema.json
Normal 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" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
546
dotnet/src/HybridRow.Tests.Unit/TupleUnitTests.cs
Normal file
546
dotnet/src/HybridRow.Tests.Unit/TupleUnitTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
413
dotnet/src/HybridRow.Tests.Unit/TypedArrayUnitTests.cs
Normal file
413
dotnet/src/HybridRow.Tests.Unit/TypedArrayUnitTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
704
dotnet/src/HybridRow.Tests.Unit/TypedMapUnitTests.cs
Normal file
704
dotnet/src/HybridRow.Tests.Unit/TypedMapUnitTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
877
dotnet/src/HybridRow.Tests.Unit/TypedSetUnitTests.cs
Normal file
877
dotnet/src/HybridRow.Tests.Unit/TypedSetUnitTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
dotnet/src/HybridRow.Tests.Unit/UpdateOptionsUnitTests.cs
Normal file
24
dotnet/src/HybridRow.Tests.Unit/UpdateOptionsUnitTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
dotnet/src/HybridRow/DefaultSpanResizer.cs
Normal file
31
dotnet/src/HybridRow/DefaultSpanResizer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
dotnet/src/HybridRow/Docs/Glossary.md
Normal file
71
dotnet/src/HybridRow/Docs/Glossary.md
Normal 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.
|
||||
109
dotnet/src/HybridRow/Docs/Grammar.md
Normal file
109
dotnet/src/HybridRow/Docs/Grammar.md
Normal 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;
|
||||
|
||||
````
|
||||
38
dotnet/src/HybridRow/Docs/RecordIO.md
Normal file
38
dotnet/src/HybridRow/Docs/RecordIO.md
Normal 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).
|
||||
53
dotnet/src/HybridRow/Docs/SchemaHash.md
Normal file
53
dotnet/src/HybridRow/Docs/SchemaHash.md
Normal 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.
|
||||
49
dotnet/src/HybridRow/Docs/SchemaId.md
Normal file
49
dotnet/src/HybridRow/Docs/SchemaId.md
Normal 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.
|
||||
27
dotnet/src/HybridRow/Docs/SystemSchema.md
Normal file
27
dotnet/src/HybridRow/Docs/SystemSchema.md
Normal 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
|
||||
57
dotnet/src/HybridRow/Float128.cs
Normal file
57
dotnet/src/HybridRow/Float128.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
dotnet/src/HybridRow/GlobalSuppressions.cs
Normal file
9
dotnet/src/HybridRow/GlobalSuppressions.cs
Normal 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")]
|
||||
33
dotnet/src/HybridRow/HybridRowHeader.cs
Normal file
33
dotnet/src/HybridRow/HybridRowHeader.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
18
dotnet/src/HybridRow/HybridRowVersion.cs
Normal file
18
dotnet/src/HybridRow/HybridRowVersion.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
22
dotnet/src/HybridRow/IO/IRowSerializable.cs
Normal file
22
dotnet/src/HybridRow/IO/IRowSerializable.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
1027
dotnet/src/HybridRow/IO/RowReader.cs
Normal file
1027
dotnet/src/HybridRow/IO/RowReader.cs
Normal file
File diff suppressed because it is too large
Load Diff
79
dotnet/src/HybridRow/IO/RowReaderExtensions.cs
Normal file
79
dotnet/src/HybridRow/IO/RowReaderExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
810
dotnet/src/HybridRow/IO/RowWriter.cs
Normal file
810
dotnet/src/HybridRow/IO/RowWriter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
dotnet/src/HybridRow/ISpanResizer.cs
Normal file
24
dotnet/src/HybridRow/ISpanResizer.cs
Normal 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
Reference in New Issue
Block a user