diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..b2df1b7 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,11 @@ +# This is a comment. +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. +* @invino4 + +# Order is important; the last matching pattern takes the most precedence +CODEOWNERS @invino4 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ebf23ac..54cdd16 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,13 @@ # Contributing -This project welcomes contributions and suggestions. Most contributions require you to -agree to a Contributor License Agreement (CLA) declaring that you have the right to, -and actually do, grant us the rights to use your contribution. For details, visit -https://cla.microsoft.com. +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.microsoft.com. -When you submit a pull request, a CLA-bot will automatically determine whether you need -to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the -instructions provided by the bot. You will only need to do this once across all repositories using our CLA. +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/README.md b/README.md index fe1d34b..f2a2b29 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ -# PLACEHOLDER +# Microsoft HybridRow -TODO: DANOBLE: Write README.md +This client library is an internal component of the Azure Cosmos DB SDK. +For more information, refer to https://azure.microsoft.com/services/cosmos-db/. + +## Install via [Nuget.org](https://www.nuget.org/packages/Microsoft.HybridRow/) + +`Install-Package Microsoft.HybridRow` + +## Useful links + +- [Release notes](https://github.com/microsoft/HybridRow/blob/master/changelog.md) diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..b6eaf0a --- /dev/null +++ b/changelog.md @@ -0,0 +1,9 @@ +Preview features are treated as a separate branch and will not be included in the official release until the feature is +ready. Each preview release lists all the additional features that are enabled. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### [1.1.0-preview3](https://www.nuget.org/packages/Microsoft.HybridRow/1.1.0-preview3) - 2021-03-30 + +- Initial snapshot of code with tools. \ No newline at end of file diff --git a/dotnet/src/HybridRow.Json/Properties/AssemblyInfo.cs b/dotnet/src/HybridRow.Json/Properties/AssemblyInfo.cs deleted file mode 100644 index be43fcc..0000000 --- a/dotnet/src/HybridRow.Json/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -//------------------------------------------------------------ -// 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")] diff --git a/dotnet/src/HybridRow.Package/Microsoft.Azure.Cosmos.Serialization.HybridRow.Package.nuproj b/dotnet/src/HybridRow.Package/Microsoft.Azure.Cosmos.Serialization.HybridRow.Package.nuproj deleted file mode 100644 index 91fda2c..0000000 --- a/dotnet/src/HybridRow.Package/Microsoft.Azure.Cosmos.Serialization.HybridRow.Package.nuproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - {7047DD2A-14FA-445E-93B2-67EB98282C4D} - External - true - - - - $(OutputRootDir) - $(NugetPackProperties);ProductSemanticVersion=$(ProductSemanticVersion);VersionPrereleaseExtension=$(VersionPrereleaseExtension) - - - - false - {B7621117-AEF3-4E10-928D-533AE893F379} - Microsoft.Azure.Cosmos.Core - - - false - {490D42EE-1FEF-47CC-97E4-782A353B4D58} - Microsoft.Azure.Cosmos.Serialization.HybridRow - - - - diff --git a/dotnet/src/HybridRow.Package/Microsoft.Azure.Cosmos.Serialization.HybridRow.Package.nuspec b/dotnet/src/HybridRow.Package/Microsoft.Azure.Cosmos.Serialization.HybridRow.Package.nuspec deleted file mode 100644 index b557e87..0000000 --- a/dotnet/src/HybridRow.Package/Microsoft.Azure.Cosmos.Serialization.HybridRow.Package.nuspec +++ /dev/null @@ -1,29 +0,0 @@ - - - - Microsoft.Azure.Cosmos.Serialization.HybridRow - 1.0.0-preview - Microsoft.Azure.Cosmos.Serialization.HybridRow - Microsoft - Microsoft - https://aka.ms/netcoregaeula - https://github.com/Azure/azure-cosmos-dotnet-v3 - http://go.microsoft.com/fwlink/?LinkID=288890 - false - Microsoft.Azure.Cosmos.Serialization.HybridRow - This package supports the Microsoft Azure Cosmos Client and is not intended to be used directly from your code. - Copyright © Microsoft Corporation - microsoft azure cosmos dotnetcore netcore netstandard - - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet/src/HybridRow.Tests.Perf/App.config b/dotnet/src/HybridRow.Tests.Perf/App.config deleted file mode 100644 index 02249c3..0000000 --- a/dotnet/src/HybridRow.Tests.Perf/App.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/dotnet/src/HybridRow.Tests.Perf/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.csproj b/dotnet/src/HybridRow.Tests.Perf/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.csproj deleted file mode 100644 index 34e4c7d..0000000 --- a/dotnet/src/HybridRow.Tests.Perf/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - true - true - {26A73F4A-AC9E-46AF-9445-286EE9EDA3EE} - Library - Test - Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf - Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf - netcoreapp2.2 - True - AnyCPU - - - - MsTest_Latest - FrameworkCore20 - X64 - $(OutDir) - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - \ No newline at end of file diff --git a/dotnet/src/HybridRow.Tests.Perf/Properties/AssemblyInfo.cs b/dotnet/src/HybridRow.Tests.Perf/Properties/AssemblyInfo.cs deleted file mode 100644 index 9421396..0000000 --- a/dotnet/src/HybridRow.Tests.Perf/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// - -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")] diff --git a/dotnet/src/HybridRow.Tests.Perf/TestData/RoomsSchemaExpected.hr b/dotnet/src/HybridRow.Tests.Perf/TestData/RoomsSchemaExpected.hr deleted file mode 100644 index 2ecf0d3..0000000 Binary files a/dotnet/src/HybridRow.Tests.Perf/TestData/RoomsSchemaExpected.hr and /dev/null differ diff --git a/dotnet/src/HybridRow.Tests.Unit/App.config b/dotnet/src/HybridRow.Tests.Unit/App.config deleted file mode 100644 index 02249c3..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/App.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Address.cs b/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Address.cs deleted file mode 100644 index 908badd..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Address.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ------------------------------------------------------------ -// 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); - } - } -} diff --git a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/AddressSerializer.cs b/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/AddressSerializer.cs deleted file mode 100644 index a43ea11..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/AddressSerializer.cs +++ /dev/null @@ -1,104 +0,0 @@ -// ------------------------------------------------------------ -// 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; - } - } -} diff --git a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Guest.cs b/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Guest.cs deleted file mode 100644 index a76a5e1..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Guest.cs +++ /dev/null @@ -1,103 +0,0 @@ -// ------------------------------------------------------------ -// 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 Emails; - public IList PhoneNumbers; - public IDictionary 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(IDictionary left, IDictionary right) - { - if (left == right) - { - return true; - } - - if ((left == null) || (right == null)) - { - return false; - } - - if (left.Count != right.Count) - { - return false; - } - - foreach (KeyValuePair 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); - } - } -} diff --git a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Hotel.cs b/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Hotel.cs deleted file mode 100644 index 2377fc6..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/Hotel.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ------------------------------------------------------------ -// 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; - } - } - } -} diff --git a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/PostalCode.cs b/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/PostalCode.cs deleted file mode 100644 index 2edff67..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/PostalCode.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ------------------------------------------------------------ -// 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; - } - } -} diff --git a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/PostalCodeSerializer.cs b/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/PostalCodeSerializer.cs deleted file mode 100644 index 11b07ef..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/CustomerSchema/PostalCodeSerializer.cs +++ /dev/null @@ -1,68 +0,0 @@ -// ------------------------------------------------------------ -// 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; - } - } -} diff --git a/dotnet/src/HybridRow.Tests.Unit/GlobalSuppressions.cs b/dotnet/src/HybridRow.Tests.Unit/GlobalSuppressions.cs deleted file mode 100644 index ca4810f..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/GlobalSuppressions.cs +++ /dev/null @@ -1,9 +0,0 @@ -// ------------------------------------------------------------ -// 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")] diff --git a/dotnet/src/HybridRow.Tests.Unit/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.csproj b/dotnet/src/HybridRow.Tests.Unit/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.csproj deleted file mode 100644 index ce8bea0..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.csproj +++ /dev/null @@ -1,82 +0,0 @@ - - - - true - true - {DC93CAA3-9732-46D4-ACBF-D69EFC3F6511} - Library - Test - Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit - Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit - netcoreapp2.2 - True - AnyCPU - - - - MsTest_Latest - FrameworkCore20 - X64 - $(OutDir) - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - diff --git a/dotnet/src/HybridRow.Tests.Unit/Properties/AssemblyInfo.cs b/dotnet/src/HybridRow.Tests.Unit/Properties/AssemblyInfo.cs deleted file mode 100644 index 2a8e8ee..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// - -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")] diff --git a/dotnet/src/HybridRow.Tests.Unit/TypedArrayUnitTests.cs b/dotnet/src/HybridRow.Tests.Unit/TypedArrayUnitTests.cs deleted file mode 100644 index 5e0cd97..0000000 --- a/dotnet/src/HybridRow.Tests.Unit/TypedArrayUnitTests.cs +++ /dev/null @@ -1,413 +0,0 @@ -// ------------------------------------------------------------ -// 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 { "classic", "Post-disco", "funk" }, - Options = new List { 8, null, 9 }, - Ratings = new List> - { - new List { 1.2, 3.0 }, - new List { 4.1, 5.7 }, - new List { 7.3, 8.12, 9.14 }, - }, - Similars = new List - { - 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.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().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().WriteScope(ref row, ref tagsScope, c.TypeArgs, out tagsScope)); - foreach (string item in value.Tags) - { - ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs().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().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() - .WriteScope(ref row, ref optionsScope, itemType.TypeArgs, item.HasValue, out RowCursor nullableScope)); - - if (item.HasValue) - { - ResultAssert.IsSuccess( - itemType.TypeArgs[0].Type.TypeAs().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().WriteScope(ref row, ref ratingsScope, c.TypeArgs, out ratingsScope)); - foreach (List item in value.Ratings) - { - Assert.IsTrue(item != null); - TypeArgument innerType = c.TypeArgs[0]; - LayoutTypedArray innerLayout = innerType.Type.TypeAs(); - 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(); - 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().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(); - 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().WriteScope(ref row, ref priorityScope, c.TypeArgs, out priorityScope)); - foreach (Tuple item in value.Priority) - { - TypeArgument innerType = c.TypeArgs[0]; - LayoutIndexedScope innerLayout = innerType.Type.TypeAs(); - ResultAssert.IsSuccess(innerLayout.WriteScope(ref row, ref priorityScope, innerType.TypeArgs, out RowCursor tupleScope)); - ResultAssert.IsSuccess(innerType.TypeArgs[0].Type.TypeAs().WriteSparse(ref row, ref tupleScope, item.Item1)); - Assert.IsTrue(tupleScope.MoveNext(ref row)); - ResultAssert.IsSuccess(innerType.TypeArgs[1].Type.TypeAs().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().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().ReadScope(ref row, ref tagsScope, out tagsScope) == Result.Success) - { - value.Tags = new List(); - while (tagsScope.MoveNext(ref row)) - { - ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs().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().ReadScope(ref row, ref optionsScope, out optionsScope) == Result.Success) - { - value.Options = new List(); - while (optionsScope.MoveNext(ref row)) - { - TypeArgument itemType = c.TypeArgs[0]; - ResultAssert.IsSuccess( - itemType.Type.TypeAs() - .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().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().ReadScope(ref row, ref ratingsScope, out ratingsScope) == Result.Success) - { - value.Ratings = new List>(); - TypeArgument innerType = c.TypeArgs[0]; - LayoutTypedArray innerLayout = innerType.Type.TypeAs(); - RowCursor innerScope = default; - while (ratingsScope.MoveNext(ref row, ref innerScope)) - { - List item = new List(); - ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref ratingsScope, out innerScope)); - while (innerScope.MoveNext(ref row)) - { - LayoutFloat64 itemLayout = innerType.TypeArgs[0].Type.TypeAs(); - 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().ReadScope(ref row, ref similarsScope, out similarsScope) == Result.Success) - { - value.Similars = new List(); - while (similarsScope.MoveNext(ref row)) - { - TypeArgument innerType = c.TypeArgs[0]; - LayoutUDT innerLayout = innerType.Type.TypeAs(); - 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().ReadScope(ref row, ref priorityScope, out priorityScope) == Result.Success) - { - value.Priority = new List>(); - RowCursor tupleScope = default; - while (priorityScope.MoveNext(ref row, ref tupleScope)) - { - TypeArgument innerType = c.TypeArgs[0]; - LayoutIndexedScope innerLayout = innerType.Type.TypeAs(); - - ResultAssert.IsSuccess(innerLayout.ReadScope(ref row, ref priorityScope, out tupleScope)); - Assert.IsTrue(tupleScope.MoveNext(ref row)); - ResultAssert.IsSuccess( - innerType.TypeArgs[0].Type.TypeAs().ReadSparse(ref row, ref tupleScope, out string item1)); - - Assert.IsTrue(tupleScope.MoveNext(ref row)); - ResultAssert.IsSuccess( - innerType.TypeArgs[1].Type.TypeAs().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().WriteFixed(ref row, ref matchScope, c, m.Thumbprint)); - Assert.IsTrue(matchLayout.TryFind("score", out c)); - ResultAssert.IsSuccess(c.TypeAs().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().ReadFixed(ref row, ref matchScope, c, out m.Thumbprint)); - Assert.IsTrue(matchLayout.TryFind("score", out c)); - ResultAssert.IsSuccess(c.TypeAs().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 Tags; - public List Options; - public List> Ratings; - public List Similars; - public List> 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(List> left, List> 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; - } - } - } -} diff --git a/dotnet/src/HybridRow/Docs/SystemSchema.md b/dotnet/src/HybridRow/Docs/SystemSchema.md deleted file mode 100644 index 35c1a63..0000000 --- a/dotnet/src/HybridRow/Docs/SystemSchema.md +++ /dev/null @@ -1,27 +0,0 @@ -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 \ No newline at end of file diff --git a/dotnet/src/HybridRow/Layouts/SystemSchema.cs b/dotnet/src/HybridRow/Layouts/SystemSchema.cs deleted file mode 100644 index 61d73a5..0000000 --- a/dotnet/src/HybridRow/Layouts/SystemSchema.cs +++ /dev/null @@ -1,61 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts -{ - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Reflection; - using Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO; - using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; - - public static class SystemSchema - { - /// - /// SchemaId of the empty schema. This schema has no defined cells but can accomodate - /// unschematized sparse content. - /// - public static readonly SchemaId EmptySchemaId = new SchemaId(2147473650); - - /// SchemaId of HybridRow RecordIO Segments. - public static readonly SchemaId SegmentSchemaId = new SchemaId(2147473648); - - /// SchemaId of HybridRow RecordIO Record Headers. - public static readonly SchemaId RecordSchemaId = new SchemaId(2147473649); - - [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Type is immutable.")] - public static readonly LayoutResolver LayoutResolver = SystemSchema.LoadSchema(); - - private static LayoutResolver LoadSchema() - { - string json = SystemSchema.GetEmbeddedResource(@"SystemSchemas\SystemSchema.json"); - Namespace ns = Namespace.Parse(json); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(ns); - return resolver; - } - - private static string GetEmbeddedResource(string resourceName) - { - Assembly assembly = Assembly.GetAssembly(typeof(RecordIOFormatter)); - resourceName = SystemSchema.FormatResourceName(assembly, resourceName); - using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) - { - if (resourceStream == null) - { - return null; - } - - using (StreamReader reader = new StreamReader(resourceStream)) - { - return reader.ReadToEnd(); - } - } - } - - private static string FormatResourceName(Assembly assembly, string resourceName) - { - return assembly.GetName().Name + "." + resourceName.Replace(" ", "_").Replace("\\", ".").Replace("/", "."); - } - } -} diff --git a/dotnet/src/HybridRow/Properties/AssemblyInfo.cs b/dotnet/src/HybridRow/Properties/AssemblyInfo.cs deleted file mode 100644 index bcb6e23..0000000 --- a/dotnet/src/HybridRow/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------ -// 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")] - -// 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("490D42EE-1FEF-47CC-97E4-782A353B4D58")] - -// Allow tests to see internals. -[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit" + AssemblyRef.TestPublicKey)] -[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf" + AssemblyRef.TestPublicKey)] -[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator" + AssemblyRef.TestPublicKey)] -[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Serialization.HybridRowStress" + AssemblyRef.TestPublicKey)] diff --git a/dotnet/src/HybridRow/RecordIO/RecordSerializer.cs b/dotnet/src/HybridRow/RecordIO/RecordSerializer.cs deleted file mode 100644 index 037ceab..0000000 --- a/dotnet/src/HybridRow/RecordIO/RecordSerializer.cs +++ /dev/null @@ -1,62 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO -{ - using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; - using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; - - public static class RecordSerializer - { - public static Result Write(ref RowWriter writer, TypeArgument typeArg, Record obj) - { - Result r; - r = writer.WriteInt32("length", obj.Length); - if (r != Result.Success) - { - return r; - } - - r = writer.WriteUInt32("crc32", obj.Crc32); - if (r != Result.Success) - { - return r; - } - - return Result.Success; - } - - public static Result Read(ref RowReader reader, out Record obj) - { - obj = default; - while (reader.Read()) - { - Result r; - - // TODO: use Path tokens here. - switch (reader.Path.ToString()) - { - case "length": - r = reader.ReadInt32(out obj.Length); - if (r != Result.Success) - { - return r; - } - - break; - case "crc32": - r = reader.ReadUInt32(out obj.Crc32); - if (r != Result.Success) - { - return r; - } - - break; - } - } - - return Result.Success; - } - } -} diff --git a/dotnet/src/HybridRow/RecordIO/SegmentSerializer.cs b/dotnet/src/HybridRow/RecordIO/SegmentSerializer.cs deleted file mode 100644 index c03f646..0000000 --- a/dotnet/src/HybridRow/RecordIO/SegmentSerializer.cs +++ /dev/null @@ -1,103 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO -{ - using System; - using Microsoft.Azure.Cosmos.Core; - using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; - using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; - - public static class SegmentSerializer - { - public static Result Write(ref RowWriter writer, TypeArgument typeArg, Segment obj) - { - Result r; - if (obj.Comment != null) - { - r = writer.WriteString("comment", obj.Comment); - if (r != Result.Success) - { - return r; - } - } - - if (obj.SDL != null) - { - r = writer.WriteString("sdl", obj.SDL); - if (r != Result.Success) - { - return r; - } - } - - // Defer writing the length until all other fields of the segment header are written. - // The length is then computed based on the current size of the underlying RowBuffer. - // Because the length field is itself fixed, writing the length can never change the length. - int length = writer.Length; - r = writer.WriteInt32("length", length); - if (r != Result.Success) - { - return r; - } - - Contract.Assert(length == writer.Length); - return Result.Success; - } - - public static Result Read(Span span, LayoutResolver resolver, out Segment obj) - { - RowBuffer row = new RowBuffer(span, HybridRowVersion.V1, resolver); - RowReader reader = new RowReader(ref row); - return SegmentSerializer.Read(ref reader, out obj); - } - - public static Result Read(ref RowReader reader, out Segment obj) - { - obj = default; - while (reader.Read()) - { - Result r; - - // TODO: use Path tokens here. - switch (reader.Path.ToString()) - { - case "length": - r = reader.ReadInt32(out obj.Length); - if (r != Result.Success) - { - return r; - } - - // If the RowBuffer isn't big enough to contain the rest of the header, then just - // return the length. - if (reader.Length < obj.Length) - { - return Result.Success; - } - - break; - case "comment": - r = reader.ReadString(out obj.Comment); - if (r != Result.Success) - { - return r; - } - - break; - case "sdl": - r = reader.ReadString(out obj.SDL); - if (r != Result.Success) - { - return r; - } - - break; - } - } - - return Result.Success; - } - } -} diff --git a/dotnet/src/HybridRow/Schemas/Namespace.cs b/dotnet/src/HybridRow/Schemas/Namespace.cs deleted file mode 100644 index e46ab34..0000000 --- a/dotnet/src/HybridRow/Schemas/Namespace.cs +++ /dev/null @@ -1,65 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -#pragma warning disable CA1716 // Identifiers should not match keywords - -namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas -{ - using System.Collections.Generic; - using Newtonsoft.Json; - - [JsonObject] - public class Namespace - { - /// - /// The standard settings used by the JSON parser for interpreting - /// documents. - /// - private static readonly JsonSerializerSettings NamespaceParseSettings = new JsonSerializerSettings() - { - CheckAdditionalContent = true, - }; - - /// The set of schemas that make up the . - private List schemas; - - /// Initializes a new instance of the class. - public Namespace() - { - this.Schemas = new List(); - } - - /// The version of the HybridRow Schema Definition Language used to encode this namespace. - [JsonProperty(PropertyName = "version")] - public SchemaLanguageVersion Version { get; set; } - - /// The fully qualified identifier of the namespace. - [JsonProperty(PropertyName = "name")] - public string Name { get; set; } - - /// The set of schemas that make up the . - /// - /// Namespaces may consist of zero or more table schemas along with zero or more UDT schemas. - /// Table schemas can only reference UDT schemas defined in the same namespace. UDT schemas can - /// contain nested UDTs whose schemas are defined within the same namespace. - /// - [JsonProperty(PropertyName = "schemas")] - public List Schemas - { - get => this.schemas; - - set => this.schemas = value ?? new List(); - } - - /// Parse a JSON document and return a full namespace. - /// The JSON text to parse. - /// A namespace containing a set of logical schemas. - public static Namespace Parse(string json) - { - Namespace ns = JsonConvert.DeserializeObject(json, Namespace.NamespaceParseSettings); - SchemaValidator.Validate(ns); - return ns; - } - } -} diff --git a/dotnet/src/HybridRow/Schemas/PrimitivePropertyType.cs b/dotnet/src/HybridRow/Schemas/PrimitivePropertyType.cs deleted file mode 100644 index 9867bf7..0000000 --- a/dotnet/src/HybridRow/Schemas/PrimitivePropertyType.cs +++ /dev/null @@ -1,29 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas -{ - using Newtonsoft.Json; - - /// A primitive property. - /// - /// Primitive properties map to columns one-to-one. Primitive properties indicate how the - /// column should be represented within the row. - /// - public class PrimitivePropertyType : PropertyType - { - /// The maximum allowable length in bytes. - /// - /// This annotation is only valid for non-fixed length types. A value of 0 means the maximum - /// allowable length. - /// - [JsonProperty(PropertyName = "length")] - [JsonConverter(typeof(StrictIntegerConverter))] - public int Length { get; set; } - - /// Storage requirements of the property. - [JsonProperty(PropertyName = "storage")] - public StorageKind Storage { get; set; } - } -} diff --git a/dotnet/src/HybridRowCLI/App.config b/dotnet/src/HybridRowCLI/App.config deleted file mode 100644 index f6888fa..0000000 --- a/dotnet/src/HybridRowCLI/App.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/dotnet/src/HybridRowCLI/Properties/AssemblyInfo.cs b/dotnet/src/HybridRowCLI/Properties/AssemblyInfo.cs deleted file mode 100644 index 4cfce31..0000000 --- a/dotnet/src/HybridRowCLI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// - -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.HybridRowCLI")] - -// 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("F7D04E9B-4257-4D7E-9AAD-C743AEDBED04")] diff --git a/dotnet/src/HybridRowCLI/StringExtensions.cs b/dotnet/src/HybridRowCLI/StringExtensions.cs deleted file mode 100644 index 89ac764..0000000 --- a/dotnet/src/HybridRowCLI/StringExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI -{ - using System; - - // TODO: this class should go away once we move to .NET Core 2.1. - internal static class StringExtensions - { - public static unsafe string AsString(this ReadOnlySpan span) - { - fixed (char* p = span) - { - return new string(p, 0, span.Length); - } - } - } -} diff --git a/dotnet/src/HybridRowGenerator/Properties/AssemblyInfo.cs b/dotnet/src/HybridRowGenerator/Properties/AssemblyInfo.cs deleted file mode 100644 index b5a6fb8..0000000 --- a/dotnet/src/HybridRowGenerator/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -//------------------------------------------------------------ -// 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.HybridRowGenerator")] - -// 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("B3F04B26-800A-4097-95CE-EACECA9ACE23")] diff --git a/dotnet/src/HybridRowStress/Properties/AssemblyInfo.cs b/dotnet/src/HybridRowStress/Properties/AssemblyInfo.cs deleted file mode 100644 index b5779d0..0000000 --- a/dotnet/src/HybridRowStress/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// - -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.HybridRowStress")] - -// 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("2FED4096-A113-4946-85E2-36A1A94924C9")] diff --git a/dotnet/src/build.props b/dotnet/src/build.props deleted file mode 100644 index 1a9cac9..0000000 --- a/dotnet/src/build.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - $(MSBuildThisFileDirectory) - - - - - - 1 - 0 - 0 - $(VersionMajor).$(VersionMinor).$(VersionPatch)-preview - $(ProductSemanticVersion) - $(ProductSemanticVersion) - $(ProductSemanticVersion) - - diff --git a/dotnet/src/dirs.proj b/dotnet/src/dirs.proj deleted file mode 100644 index 64fd6bc..0000000 --- a/dotnet/src/dirs.proj +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - true - - - - - - - - - - - - - - - - diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/ISpanResizer.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/ISpanResizer.java similarity index 98% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/ISpanResizer.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/ISpanResizer.java index bb25c0d..5f6f99b 100644 --- a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/ISpanResizer.java +++ b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/ISpanResizer.java @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -public interface ISpanResizer { - /** - * Resizes an existing a buffer. - * The type of the elements of the memory. - * - * @param minimumLength The minimum required length (in elements) of the memory. - * @param buffer Optional existing memory to be copied to the new buffer. Ownership of is - * transferred as part of this call and it should not be used by the caller after this call - * completes. - * @return A new memory whose size is at least as big as - * and containing the content of . - */ - - Span Resize(int minimumLength); - - //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: - //ORIGINAL LINE: Span Resize(int minimumLength, Span buffer = default); - Span Resize(int minimumLength, Span buffer); +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +public interface ISpanResizer { + /** + * Resizes an existing a buffer. + * The type of the elements of the memory. + * + * @param minimumLength The minimum required length (in elements) of the memory. + * @param buffer Optional existing memory to be copied to the new buffer. Ownership of is + * transferred as part of this call and it should not be used by the caller after this call + * completes. + * @return A new memory whose size is at least as big as + * and containing the content of . + */ + + Span Resize(int minimumLength); + + //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: + //ORIGINAL LINE: Span Resize(int minimumLength, Span buffer = default); + Span Resize(int minimumLength, Span buffer); } \ No newline at end of file diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/MemorySpanResizer.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/MemorySpanResizer.java similarity index 97% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/MemorySpanResizer.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/MemorySpanResizer.java index 712863d..61fe09d 100644 --- a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/MemorySpanResizer.java +++ b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/MemorySpanResizer.java @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class MemorySpanResizer implements ISpanResizer { - private Memory memory; - - - public MemorySpanResizer() { - this(0); - } - - //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: - //ORIGINAL LINE: public MemorySpanResizer(int initialCapacity = 0) - public MemorySpanResizer(int initialCapacity) { - checkArgument(initialCapacity >= 0); - - //C# TO JAVA CONVERTER WARNING: Java does not allow direct instantiation of arrays of generic type parameters: - //ORIGINAL LINE: this.memory = initialCapacity == 0 ? default : new Memory(new T[initialCapacity]); - this.memory = initialCapacity == 0 ? null : new Memory((T[])new Object[initialCapacity]); - } - - public Memory getMemory() { - return this.memory; - } - - /** - * - */ - - public Span Resize(int minimumLength) { - return Resize(minimumLength, null); - } - - //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: - //ORIGINAL LINE: public Span Resize(int minimumLength, Span buffer = default) - public Span Resize(int minimumLength, Span buffer) { - if (this.memory.Length < minimumLength) { - //C# TO JAVA CONVERTER WARNING: Java does not allow direct instantiation of arrays of generic type - // parameters: - //ORIGINAL LINE: this.memory = new Memory(new T[Math.Max(minimumLength, buffer.Length)]); - this.memory = new Memory((T[])new Object[Math.max(minimumLength, buffer.Length)]); - } - - Span next = this.memory.Span; - if (!buffer.IsEmpty && next.Slice(0, buffer.Length) != buffer) { - buffer.CopyTo(next); - } - - return next; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class MemorySpanResizer implements ISpanResizer { + private Memory memory; + + + public MemorySpanResizer() { + this(0); + } + + //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: + //ORIGINAL LINE: public MemorySpanResizer(int initialCapacity = 0) + public MemorySpanResizer(int initialCapacity) { + checkArgument(initialCapacity >= 0); + + //C# TO JAVA CONVERTER WARNING: Java does not allow direct instantiation of arrays of generic type parameters: + //ORIGINAL LINE: this.memory = initialCapacity == 0 ? default : new Memory(new T[initialCapacity]); + this.memory = initialCapacity == 0 ? null : new Memory((T[])new Object[initialCapacity]); + } + + public Memory getMemory() { + return this.memory; + } + + /** + * + */ + + public Span Resize(int minimumLength) { + return Resize(minimumLength, null); + } + + //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: + //ORIGINAL LINE: public Span Resize(int minimumLength, Span buffer = default) + public Span Resize(int minimumLength, Span buffer) { + if (this.memory.Length < minimumLength) { + //C# TO JAVA CONVERTER WARNING: Java does not allow direct instantiation of arrays of generic type + // parameters: + //ORIGINAL LINE: this.memory = new Memory(new T[Math.Max(minimumLength, buffer.Length)]); + this.memory = new Memory((T[])new Object[Math.max(minimumLength, buffer.Length)]); + } + + Span next = this.memory.Span; + if (!buffer.IsEmpty && next.Slice(0, buffer.Length) != buffer) { + buffer.CopyTo(next); + } + + return next; + } } \ No newline at end of file diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingStringComparer.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingStringComparer.java similarity index 97% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingStringComparer.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingStringComparer.java index 2427305..2eeafb9 100644 --- a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingStringComparer.java +++ b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingStringComparer.java @@ -1,48 +1,48 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -public class SamplingStringComparer implements IEqualityComparer { - - public static final SamplingStringComparer Default = new SamplingStringComparer(); - - public final boolean equals(String x, String y) { - checkArgument(x != null); - checkArgument(y != null); - - return x.equals(y); - } - - public final int hashCode(String obj) { - checkArgument(obj != null); - - // TODO: C# TO JAVA CONVERTER: There is no equivalent to an 'unchecked' block in Java: - unchecked - { - //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - //ORIGINAL LINE: uint hash1 = 5381; - int hash1 = 5381; - //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - //ORIGINAL LINE: uint hash2 = hash1; - int hash2 = hash1; - final int numSamples = 4; - final int modulus = 13; - - ReadOnlySpan utf16 = obj.AsSpan(); - int max = Math.min(utf16.Length, numSamples); - for (int i = 0; i < max; i++) { - //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - //ORIGINAL LINE: uint c = utf16[(i * modulus) % utf16.Length]; - int c = utf16[(i * modulus) % utf16.Length]; - if (i % 2 == 0) { - hash1 = ((hash1 << 5) + hash1) ^ c; - } else { - hash2 = ((hash2 << 5) + hash2) ^ c; - } - } - - return hash1 + (hash2 * 1566083941); - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +public class SamplingStringComparer implements IEqualityComparer { + + public static final SamplingStringComparer Default = new SamplingStringComparer(); + + public final boolean equals(String x, String y) { + checkArgument(x != null); + checkArgument(y != null); + + return x.equals(y); + } + + public final int hashCode(String obj) { + checkArgument(obj != null); + + // TODO: C# TO JAVA CONVERTER: There is no equivalent to an 'unchecked' block in Java: + unchecked + { + //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + //ORIGINAL LINE: uint hash1 = 5381; + int hash1 = 5381; + //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + //ORIGINAL LINE: uint hash2 = hash1; + int hash2 = hash1; + final int numSamples = 4; + final int modulus = 13; + + ReadOnlySpan utf16 = obj.AsSpan(); + int max = Math.min(utf16.Length, numSamples); + for (int i = 0; i < max; i++) { + //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + //ORIGINAL LINE: uint c = utf16[(i * modulus) % utf16.Length]; + int c = utf16[(i * modulus) % utf16.Length]; + if (i % 2 == 0) { + hash1 = ((hash1 << 5) + hash1) ^ c; + } else { + hash2 = ((hash2 << 5) + hash2) ^ c; + } + } + + return hash1 + (hash2 * 1566083941); + } + } } \ No newline at end of file diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingUtf8StringComparer.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingUtf8StringComparer.java similarity index 97% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingUtf8StringComparer.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingUtf8StringComparer.java index c2f589e..d90b08b 100644 --- a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingUtf8StringComparer.java +++ b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SamplingUtf8StringComparer.java @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -public class SamplingUtf8StringComparer implements IEqualityComparer { - public static final SamplingUtf8StringComparer Default = new SamplingUtf8StringComparer(); - - public final boolean equals(Utf8String x, Utf8String y) { - checkArgument(x != null); - checkArgument(y != null); - - return x.Span.equals(y.Span); - } - - public final int hashCode(Utf8String obj) { - checkArgument(obj != null); - - // TODO: C# TO JAVA CONVERTER: There is no equivalent to an 'unchecked' block in Java: - unchecked - { - //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - //ORIGINAL LINE: uint hash1 = 5381; - int hash1 = 5381; - //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - //ORIGINAL LINE: uint hash2 = hash1; - int hash2 = hash1; - final int numSamples = 4; - final int modulus = 13; - - //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - //ORIGINAL LINE: ReadOnlySpan utf8 = obj.Span.Span; - ReadOnlySpan utf8 = obj.Span.Span; - int max = Math.min(utf8.Length, numSamples); - for (int i = 0; i < max; i++) { - //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - //ORIGINAL LINE: uint c = utf8[(i * modulus) % utf8.Length]; - int c = utf8[(i * modulus) % utf8.Length]; - if (i % 2 == 0) { - hash1 = ((hash1 << 5) + hash1) ^ c; - } else { - hash2 = ((hash2 << 5) + hash2) ^ c; - } - } - - return hash1 + (hash2 * 1566083941); - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +public class SamplingUtf8StringComparer implements IEqualityComparer { + public static final SamplingUtf8StringComparer Default = new SamplingUtf8StringComparer(); + + public final boolean equals(Utf8String x, Utf8String y) { + checkArgument(x != null); + checkArgument(y != null); + + return x.Span.equals(y.Span); + } + + public final int hashCode(Utf8String obj) { + checkArgument(obj != null); + + // TODO: C# TO JAVA CONVERTER: There is no equivalent to an 'unchecked' block in Java: + unchecked + { + //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + //ORIGINAL LINE: uint hash1 = 5381; + int hash1 = 5381; + //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + //ORIGINAL LINE: uint hash2 = hash1; + int hash2 = hash1; + final int numSamples = 4; + final int modulus = 13; + + //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + //ORIGINAL LINE: ReadOnlySpan utf8 = obj.Span.Span; + ReadOnlySpan utf8 = obj.Span.Span; + int max = Math.min(utf8.Length, numSamples); + for (int i = 0; i < max; i++) { + //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + //ORIGINAL LINE: uint c = utf8[(i * modulus) % utf8.Length]; + int c = utf8[(i * modulus) % utf8.Length]; + if (i % 2 == 0) { + hash1 = ((hash1 << 5) + hash1) ^ c; + } else { + hash2 = ((hash2 << 5) + hash2) ^ c; + } + } + + return hash1 + (hash2 * 1566083941); + } + } } \ No newline at end of file diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/Record.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/Record.java similarity index 100% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/Record.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/Record.java diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOFormatter.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOFormatter.java similarity index 100% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOFormatter.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOFormatter.java diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOParser.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOParser.java similarity index 100% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOParser.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOParser.java diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOStream.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOStream.java similarity index 100% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOStream.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordIOStream.java diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordSerializer.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordSerializer.java similarity index 100% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordSerializer.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/RecordSerializer.java diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/SegmentSerializer.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/SegmentSerializer.java similarity index 100% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/SegmentSerializer.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/recordio/SegmentSerializer.java diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertySchemaConverter.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertySchemaConverter.java similarity index 97% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertySchemaConverter.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertySchemaConverter.java index 4d92ef9..05d310d 100644 --- a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertySchemaConverter.java +++ b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertySchemaConverter.java @@ -1,84 +1,84 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import Newtonsoft.Json.*; -import Newtonsoft.Json.Converters.*; -import Newtonsoft.Json.Linq.*; - -/** - * Helper class for parsing the polymorphic {@link PropertyType} subclasses from JSON. - */ -public class PropertySchemaConverter extends JsonConverter { - @Override - public boolean getCanWrite() { - return false; - } - - @Override - public boolean CanConvert(java.lang.Class objectType) { - return objectType.isAssignableFrom(PropertyType.class); - } - - @Override - public Object ReadJson(JsonReader reader, java.lang.Class objectType, Object existingValue, - JsonSerializer serializer) { - PropertyType p; - if (reader.TokenType != JsonToken.StartObject) { - throw new JsonSerializationException(); - } - - JObject propSchema = JObject.Load(reader); - TypeKind propType; - - JToken value; - // TODO: C# TO JAVA CONVERTER: The following method call contained an unresolved 'out' keyword - these - // cannot be converted using the 'Out' helper class unless the method is within the code being modified: - if (!propSchema.TryGetValue("type", out value)) { - throw new JsonSerializationException("Required \"type\" property missing."); - } - - try (JsonReader typeReader = value.CreateReader()) { - typeReader.Read(); // Move to the start token - propType = TypeKind.forValue((new StringEnumConverter(true)).ReadJson(typeReader, TypeKind.class, null, - serializer)); - } - - switch (propType) { - case Array: - p = new ArrayPropertyType(); - break; - case SET: - p = new SetPropertyType(); - break; - case MAP: - p = new MapPropertyType(); - break; - case Object: - p = new ObjectPropertyType(); - break; - case Tuple: - p = new TuplePropertyType(); - break; - case TAGGED: - p = new TaggedPropertyType(); - break; - case Schema: - p = new UdtPropertyType(); - break; - default: - p = new PrimitivePropertyType(); - break; - } - - serializer.Populate(propSchema.CreateReader(), p); - return p; - } - - // TODO: C# TO JAVA CONVERTER: Java annotations will not correspond to .NET attributes: - //ORIGINAL LINE: [ExcludeFromCodeCoverage] public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - @Override - public void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) { - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import Newtonsoft.Json.*; +import Newtonsoft.Json.Converters.*; +import Newtonsoft.Json.Linq.*; + +/** + * Helper class for parsing the polymorphic {@link PropertyType} subclasses from JSON. + */ +public class PropertySchemaConverter extends JsonConverter { + @Override + public boolean getCanWrite() { + return false; + } + + @Override + public boolean CanConvert(java.lang.Class objectType) { + return objectType.isAssignableFrom(PropertyType.class); + } + + @Override + public Object ReadJson(JsonReader reader, java.lang.Class objectType, Object existingValue, + JsonSerializer serializer) { + PropertyType p; + if (reader.TokenType != JsonToken.StartObject) { + throw new JsonSerializationException(); + } + + JObject propSchema = JObject.Load(reader); + TypeKind propType; + + JToken value; + // TODO: C# TO JAVA CONVERTER: The following method call contained an unresolved 'out' keyword - these + // cannot be converted using the 'Out' helper class unless the method is within the code being modified: + if (!propSchema.TryGetValue("type", out value)) { + throw new JsonSerializationException("Required \"type\" property missing."); + } + + try (JsonReader typeReader = value.CreateReader()) { + typeReader.Read(); // Move to the start token + propType = TypeKind.forValue((new StringEnumConverter(true)).ReadJson(typeReader, TypeKind.class, null, + serializer)); + } + + switch (propType) { + case Array: + p = new ArrayPropertyType(); + break; + case SET: + p = new SetPropertyType(); + break; + case MAP: + p = new MapPropertyType(); + break; + case Object: + p = new ObjectPropertyType(); + break; + case Tuple: + p = new TuplePropertyType(); + break; + case TAGGED: + p = new TaggedPropertyType(); + break; + case Schema: + p = new UdtPropertyType(); + break; + default: + p = new PrimitivePropertyType(); + break; + } + + serializer.Populate(propSchema.CreateReader(), p); + return p; + } + + // TODO: C# TO JAVA CONVERTER: Java annotations will not correspond to .NET attributes: + //ORIGINAL LINE: [ExcludeFromCodeCoverage] public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + @Override + public void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) { + } } \ No newline at end of file diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictBooleanConverter.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictBooleanConverter.java similarity index 97% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictBooleanConverter.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictBooleanConverter.java index 596536b..927f61e 100644 --- a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictBooleanConverter.java +++ b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictBooleanConverter.java @@ -1,37 +1,37 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import Newtonsoft.Json.*; - -public class StrictBooleanConverter extends JsonConverter { - @Override - public boolean getCanWrite() { - return false; - } - - @Override - public boolean CanConvert(java.lang.Class objectType) { - return objectType == Boolean.class; - } - - @Override - public Object ReadJson(JsonReader reader, java.lang.Class objectType, Object existingValue, - JsonSerializer serializer) { - switch (reader.TokenType) { - case JsonToken.Boolean: - return serializer.Deserialize(reader, objectType); - default: - throw new JsonSerializationException(String.format("Token \"%1$s\" of type %2$s was not a JSON bool", - reader.Value, reader.TokenType)); - } - } - - // TODO: C# TO JAVA CONVERTER: Java annotations will not correspond to .NET attributes: - //ORIGINAL LINE: [ExcludeFromCodeCoverage] public override void WriteJson(JsonWriter writer, object value, - // JsonSerializer serializer) - @Override - public void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) { - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import Newtonsoft.Json.*; + +public class StrictBooleanConverter extends JsonConverter { + @Override + public boolean getCanWrite() { + return false; + } + + @Override + public boolean CanConvert(java.lang.Class objectType) { + return objectType == Boolean.class; + } + + @Override + public Object ReadJson(JsonReader reader, java.lang.Class objectType, Object existingValue, + JsonSerializer serializer) { + switch (reader.TokenType) { + case JsonToken.Boolean: + return serializer.Deserialize(reader, objectType); + default: + throw new JsonSerializationException(String.format("Token \"%1$s\" of type %2$s was not a JSON bool", + reader.Value, reader.TokenType)); + } + } + + // TODO: C# TO JAVA CONVERTER: Java annotations will not correspond to .NET attributes: + //ORIGINAL LINE: [ExcludeFromCodeCoverage] public override void WriteJson(JsonWriter writer, object value, + // JsonSerializer serializer) + @Override + public void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) { + } } \ No newline at end of file diff --git a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictIntegerConverter.java b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictIntegerConverter.java similarity index 97% rename from java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictIntegerConverter.java rename to experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictIntegerConverter.java index 7626519..b89c00c 100644 --- a/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictIntegerConverter.java +++ b/experimental/java/exclusions/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StrictIntegerConverter.java @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import Newtonsoft.Json.*; - -import java.math.BigInteger; - -public class StrictIntegerConverter extends JsonConverter { - @Override - public boolean getCanWrite() { - return false; - } - - @Override - public boolean CanConvert(java.lang.Class objectType) { - return StrictIntegerConverter.IsIntegerType(objectType); - } - - @Override - public Object ReadJson(JsonReader reader, java.lang.Class objectType, Object existingValue, - JsonSerializer serializer) { - switch (reader.TokenType) { - case JsonToken.Integer: - return serializer.Deserialize(reader, objectType); - default: - throw new JsonSerializationException(String.format("Token \"%1$s\" of type %2$s was not a JSON " + - "integer", reader.Value, reader.TokenType)); - } - } - - // TODO: C# TO JAVA CONVERTER: Java annotations will not correspond to .NET attributes: - //ORIGINAL LINE: [ExcludeFromCodeCoverage] public override void WriteJson(JsonWriter writer, object value, - // JsonSerializer serializer) - @Override - public void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) { - } - - private static boolean IsIntegerType(java.lang.Class type) { - //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - //ORIGINAL LINE: if (type == typeof(long) || type == typeof(ulong) || type == typeof(int) || type == typeof - // (uint) || type == typeof(short) || type == typeof(ushort) || type == typeof(byte) || type == typeof(sbyte) - // || type == typeof(BigInteger)) - return type == Long.class || type == Long.class || type == Integer.class || type == Integer.class || type == Short.class || type == Short.class || type == Byte.class || type == Byte.class || type == BigInteger.class; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import Newtonsoft.Json.*; + +import java.math.BigInteger; + +public class StrictIntegerConverter extends JsonConverter { + @Override + public boolean getCanWrite() { + return false; + } + + @Override + public boolean CanConvert(java.lang.Class objectType) { + return StrictIntegerConverter.IsIntegerType(objectType); + } + + @Override + public Object ReadJson(JsonReader reader, java.lang.Class objectType, Object existingValue, + JsonSerializer serializer) { + switch (reader.TokenType) { + case JsonToken.Integer: + return serializer.Deserialize(reader, objectType); + default: + throw new JsonSerializationException(String.format("Token \"%1$s\" of type %2$s was not a JSON " + + "integer", reader.Value, reader.TokenType)); + } + } + + // TODO: C# TO JAVA CONVERTER: Java annotations will not correspond to .NET attributes: + //ORIGINAL LINE: [ExcludeFromCodeCoverage] public override void WriteJson(JsonWriter writer, object value, + // JsonSerializer serializer) + @Override + public void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) { + } + + private static boolean IsIntegerType(java.lang.Class type) { + //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + //ORIGINAL LINE: if (type == typeof(long) || type == typeof(ulong) || type == typeof(int) || type == typeof + // (uint) || type == typeof(short) || type == typeof(ushort) || type == typeof(byte) || type == typeof(sbyte) + // || type == typeof(BigInteger)) + return type == Long.class || type == Long.class || type == Integer.class || type == Integer.class || type == Short.class || type == Short.class || type == Byte.class || type == Byte.class || type == BigInteger.class; + } } \ No newline at end of file diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BenchmarkSuiteBase.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BenchmarkSuiteBase.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BenchmarkSuiteBase.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BenchmarkSuiteBase.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonJsonModelRowGenerator.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonJsonModelRowGenerator.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonJsonModelRowGenerator.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonJsonModelRowGenerator.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonReaderExtensions.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonReaderExtensions.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonReaderExtensions.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonReaderExtensions.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonRowGenerator.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonRowGenerator.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonRowGenerator.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/BsonRowGenerator.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/CodeGenMicroBenchmarkSuite.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/CodeGenMicroBenchmarkSuite.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/CodeGenMicroBenchmarkSuite.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/CodeGenMicroBenchmarkSuite.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/CodeGenRowGenerator.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/CodeGenRowGenerator.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/CodeGenRowGenerator.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/CodeGenRowGenerator.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/GenerateBenchmarkSuite.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/GenerateBenchmarkSuite.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/GenerateBenchmarkSuite.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/GenerateBenchmarkSuite.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/JsonModelRowGenerator.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/JsonModelRowGenerator.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/JsonModelRowGenerator.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/JsonModelRowGenerator.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/Measurements.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/Measurements.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/Measurements.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/Measurements.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/MicroBenchmarkSuiteBase.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/MicroBenchmarkSuiteBase.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/MicroBenchmarkSuiteBase.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/MicroBenchmarkSuiteBase.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/ProtobufRowGenerator.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/ProtobufRowGenerator.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/ProtobufRowGenerator.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/ProtobufRowGenerator.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/ReaderBenchmark.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/ReaderBenchmark.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/ReaderBenchmark.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/ReaderBenchmark.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/RowReaderExtensions.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/RowReaderExtensions.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/RowReaderExtensions.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/RowReaderExtensions.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/SchematizedMicroBenchmarkSuite.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/SchematizedMicroBenchmarkSuite.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/SchematizedMicroBenchmarkSuite.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/SchematizedMicroBenchmarkSuite.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/TestData.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/TestData.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/TestData.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/TestData.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/UnschematizedMicroBenchmarkSuite.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/UnschematizedMicroBenchmarkSuite.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/UnschematizedMicroBenchmarkSuite.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/UnschematizedMicroBenchmarkSuite.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Address.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Address.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Address.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Address.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Available_Rooms_By_Hotel_Date.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Available_Rooms_By_Hotel_Date.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Available_Rooms_By_Hotel_Date.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Available_Rooms_By_Hotel_Date.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/CassandraHotelSchemaReflection.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/CassandraHotelSchemaReflection.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/CassandraHotelSchemaReflection.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/CassandraHotelSchemaReflection.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Guests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Guests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Guests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Guests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Hotels.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Hotels.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Hotels.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/Hotels.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/PostalCode.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/PostalCode.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/PostalCode.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/perf/cassandrahotel/protobuf/PostalCode.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ArrayAssert.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ArrayAssert.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ArrayAssert.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ArrayAssert.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/AssertThrowsException.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/AssertThrowsException.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/AssertThrowsException.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/AssertThrowsException.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/CrossVersioningUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/CrossVersioningUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/CrossVersioningUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/CrossVersioningUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/CustomerExampleUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/CustomerExampleUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/CustomerExampleUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/CustomerExampleUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/DeleteRowDispatcher.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/DeleteRowDispatcher.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/DeleteRowDispatcher.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/DeleteRowDispatcher.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/IDispatchable.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/IDispatchable.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/IDispatchable.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/IDispatchable.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/IDispatcher.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/IDispatcher.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/IDispatcher.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/IDispatcher.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/LayoutCompilerUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/LayoutCompilerUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/LayoutCompilerUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/LayoutCompilerUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/LayoutTypeUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/LayoutTypeUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/LayoutTypeUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/LayoutTypeUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/NullRowDispatcher.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/NullRowDispatcher.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/NullRowDispatcher.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/NullRowDispatcher.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/NullableUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/NullableUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/NullableUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/NullableUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/PermuteExtensions.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/PermuteExtensions.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/PermuteExtensions.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/PermuteExtensions.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RandomGeneratorUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RandomGeneratorUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RandomGeneratorUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RandomGeneratorUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ReadRowDispatcher.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ReadRowDispatcher.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ReadRowDispatcher.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ReadRowDispatcher.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RecordIOUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RecordIOUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RecordIOUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RecordIOUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ResultAssert.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ResultAssert.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ResultAssert.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/ResultAssert.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowBufferUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowBufferUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowBufferUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowBufferUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowOperationDispatcher.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowOperationDispatcher.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowOperationDispatcher.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowOperationDispatcher.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowReaderUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowReaderUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowReaderUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowReaderUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowWriterUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowWriterUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowWriterUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/RowWriterUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaHashUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaHashUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaHashUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaHashUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaIdUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaIdUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaIdUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaIdUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SchemaUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SerializerUnitTest.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SerializerUnitTest.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SerializerUnitTest.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/SerializerUnitTest.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TaggedUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TaggedUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TaggedUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TaggedUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TupleUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TupleUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TupleUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TupleUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedArrayUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedArrayUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedArrayUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedArrayUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedMapUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedMapUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedMapUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedMapUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedSetUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedSetUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedSetUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/TypedSetUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/UpdateOptionsUnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/UpdateOptionsUnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/UpdateOptionsUnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/UpdateOptionsUnitTests.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/WriteRowDispatcher.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/WriteRowDispatcher.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/WriteRowDispatcher.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/WriteRowDispatcher.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Address.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Address.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Address.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Address.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/AddressSerializer.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/AddressSerializer.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/AddressSerializer.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/AddressSerializer.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Guest.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Guest.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Guest.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Guest.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Hotel.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Hotel.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Hotel.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/Hotel.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/PostalCode.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/PostalCode.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/PostalCode.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/PostalCode.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/PostalCodeSerializer.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/PostalCodeSerializer.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/PostalCodeSerializer.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/customerschema/PostalCodeSerializer.java diff --git a/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/internal/MurmurHash3UnitTests.java b/experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/internal/MurmurHash3UnitTests.java similarity index 100% rename from java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/internal/MurmurHash3UnitTests.java rename to experimental/java/exclusions/test/java/com/azure/data/cosmos/serialization/hybridrow/unit/internal/MurmurHash3UnitTests.java diff --git a/layout.xlsx b/experimental/java/layout.xlsx similarity index 100% rename from layout.xlsx rename to experimental/java/layout.xlsx diff --git a/java/pom.xml b/experimental/java/pom.xml similarity index 100% rename from java/pom.xml rename to experimental/java/pom.xml diff --git a/dotnet/src/HybridRow/SystemSchemas/SystemSchema.json b/experimental/java/schemas/SystemSchema.json similarity index 100% rename from dotnet/src/HybridRow/SystemSchemas/SystemSchema.json rename to experimental/java/schemas/SystemSchema.json diff --git a/java/src/main/java/com/azure/data/cosmos/core/Json.java b/experimental/java/src/main/java/com/azure/data/cosmos/core/Json.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/core/Json.java rename to experimental/java/src/main/java/com/azure/data/cosmos/core/Json.java diff --git a/java/src/main/java/com/azure/data/cosmos/core/Out.java b/experimental/java/src/main/java/com/azure/data/cosmos/core/Out.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/core/Out.java rename to experimental/java/src/main/java/com/azure/data/cosmos/core/Out.java index 676786b..c7a06e3 100644 --- a/java/src/main/java/com/azure/data/cosmos/core/Out.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/core/Out.java @@ -1,86 +1,86 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.core; - -import java.util.Objects; - -/** - * A container object which may or may not contain a non-null value - * - * This is a value-based class and as such use of identity-sensitive operations--including reference equality - * ({@code ==}), identity hash code, or synchronization--on instances of {@code Out} may have unpredictable results and - * should be avoided. - * - * @param type of the referent. - */ -public final class Out { - - private volatile T value; - - public T get() { - return this.value; - } - - public void set(T value) { - this.value = value; - } - - public T setAndGet(T value) { - return this.value = value; - } - - /** - * {@code true} if there is a value present, otherwise {@code false} - *

- * This is equivalent to evaluating the expression {@code out.get() == null}. - * - * @return {@code true} if there is a value present, otherwise {@code false} - */ - public boolean isPresent() { - return this.value != null; - } - - /** - * Indicates whether some other object is equal to this {@link Out} value. - *

- * The other object is considered equal if: - *

    - *
  • it is also an {@link Out} and; - *
  • both instances have no value present or; - *
  • the present values are equal to each other as determined by {@code T.equals(Object)}}. - *
- * - * @param other an object to be tested for equality - * @return {code true} if the other object is equal to this object; otherwise {@code false} - */ - @Override - public boolean equals(Object other) { - - if (this == other) { - return true; - } - - if (other.getClass() != Out.class) { - return false; - } - - return Objects.equals(this.value, ((Out)other).value); - } - - /** - * Returns the hash code value of the present value, if any, or 0 (zero) if - * no value is present. - * - * @return hash code value of the present value or 0 if no value is present - */ - @Override - public int hashCode() { - return Objects.hashCode(this.value); - } - - @Override - public String toString() { - return this.value == null ? "null" : this.value.toString(); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.core; + +import java.util.Objects; + +/** + * A container object which may or may not contain a non-null value + * + * This is a value-based class and as such use of identity-sensitive operations--including reference equality + * ({@code ==}), identity hash code, or synchronization--on instances of {@code Out} may have unpredictable results and + * should be avoided. + * + * @param type of the referent. + */ +public final class Out { + + private volatile T value; + + public T get() { + return this.value; + } + + public void set(T value) { + this.value = value; + } + + public T setAndGet(T value) { + return this.value = value; + } + + /** + * {@code true} if there is a value present, otherwise {@code false} + *

+ * This is equivalent to evaluating the expression {@code out.get() == null}. + * + * @return {@code true} if there is a value present, otherwise {@code false} + */ + public boolean isPresent() { + return this.value != null; + } + + /** + * Indicates whether some other object is equal to this {@link Out} value. + *

+ * The other object is considered equal if: + *

    + *
  • it is also an {@link Out} and; + *
  • both instances have no value present or; + *
  • the present values are equal to each other as determined by {@code T.equals(Object)}}. + *
+ * + * @param other an object to be tested for equality + * @return {code true} if the other object is equal to this object; otherwise {@code false} + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other.getClass() != Out.class) { + return false; + } + + return Objects.equals(this.value, ((Out)other).value); + } + + /** + * Returns the hash code value of the present value, if any, or 0 (zero) if + * no value is present. + * + * @return hash code value of the present value or 0 if no value is present + */ + @Override + public int hashCode() { + return Objects.hashCode(this.value); + } + + @Override + public String toString() { + return this.value == null ? "null" : this.value.toString(); + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/core/Reference.java b/experimental/java/src/main/java/com/azure/data/cosmos/core/Reference.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/core/Reference.java rename to experimental/java/src/main/java/com/azure/data/cosmos/core/Reference.java index cede38a..472525c 100644 --- a/java/src/main/java/com/azure/data/cosmos/core/Reference.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/core/Reference.java @@ -1,79 +1,79 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.core; - -import java.util.Objects; - -/** - * A container object which may or may not contain a non-null value. - * - * This is a value-based class and as such use of identity-sensitive operations--including reference equality - * ({@code ==}), identity hash code, or synchronization--on instances of {@link Reference} may have unpredictable - * results and should be avoided. - * - * @param type of the referent. - */ -public final class Reference { - - private volatile T value; - - public Reference(T value) { - this.setAndGet(value); - } - - public T get() { - return this.value; - } - - public void set(T value) { - this.value = value; - } - public T setAndGet(T value) { - return this.value = value; - } - - /** - * {@code true} if there is a value present, otherwise {@code false}. - * - * This is equivalent to evaluating the expression {@code ref.get() == null}. - * - * @return {@code true} if there is a value present, otherwise {@code false} - */ - public boolean isPresent() { - return this.value != null; - } - - /** - * Indicates whether some other object is equal to this {@link Reference} value. - *

- * The other object is considered equal if: - *

    - *
  • it is also an {@link Reference} and; - *
  • both instances have no value present or; - *
  • the present values are equal to each other as determined by {@code T.equals(Object)}}. - *
- * - * @param other an object to be tested for equality - * @return {code true} if the other object is equal to this object; otherwise {@code false} - */ - @Override - public boolean equals(Object other) { - - if (this == other) { - return true; - } - - if (other.getClass() != Reference.class) { - return false; - } - - return Objects.equals(this.value, ((Reference)other).value); - } - - - @Override - public String toString() { - return this.value == null ? "null" : this.value.toString(); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.core; + +import java.util.Objects; + +/** + * A container object which may or may not contain a non-null value. + * + * This is a value-based class and as such use of identity-sensitive operations--including reference equality + * ({@code ==}), identity hash code, or synchronization--on instances of {@link Reference} may have unpredictable + * results and should be avoided. + * + * @param type of the referent. + */ +public final class Reference { + + private volatile T value; + + public Reference(T value) { + this.setAndGet(value); + } + + public T get() { + return this.value; + } + + public void set(T value) { + this.value = value; + } + public T setAndGet(T value) { + return this.value = value; + } + + /** + * {@code true} if there is a value present, otherwise {@code false}. + * + * This is equivalent to evaluating the expression {@code ref.get() == null}. + * + * @return {@code true} if there is a value present, otherwise {@code false} + */ + public boolean isPresent() { + return this.value != null; + } + + /** + * Indicates whether some other object is equal to this {@link Reference} value. + *

+ * The other object is considered equal if: + *

    + *
  • it is also an {@link Reference} and; + *
  • both instances have no value present or; + *
  • the present values are equal to each other as determined by {@code T.equals(Object)}}. + *
+ * + * @param other an object to be tested for equality + * @return {code true} if the other object is equal to this object; otherwise {@code false} + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other.getClass() != Reference.class) { + return false; + } + + return Objects.equals(this.value, ((Reference)other).value); + } + + + @Override + public String toString() { + return this.value == null ? "null" : this.value.toString(); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/core/Utf8String.java b/experimental/java/src/main/java/com/azure/data/cosmos/core/Utf8String.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/core/Utf8String.java rename to experimental/java/src/main/java/com/azure/data/cosmos/core/Utf8String.java diff --git a/java/src/main/java/com/azure/data/cosmos/core/UtfAnyString.java b/experimental/java/src/main/java/com/azure/data/cosmos/core/UtfAnyString.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/core/UtfAnyString.java rename to experimental/java/src/main/java/com/azure/data/cosmos/core/UtfAnyString.java index cfadd65..bfc4458 100644 --- a/java/src/main/java/com/azure/data/cosmos/core/UtfAnyString.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/core/UtfAnyString.java @@ -1,309 +1,309 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.core; - -import javax.annotation.Nonnull; - -import static com.azure.data.cosmos.core.Utf8String.transcodeUtf16; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A string whose memory representation may be either UTF-8 or UTF-16. - *

- * This type supports polymorphic use of {@link String} and {@link Utf8String} when equality, hashing, and comparison - * are needed against either encoding. An API leveraging {@link UtfAnyString} can avoid separate method overloads - * while still accepting either encoding without imposing additional allocations. - */ -public final class UtfAnyString implements CharSequence, Comparable { - - public static final UtfAnyString EMPTY = new UtfAnyString(""); - public static final UtfAnyString NULL = new UtfAnyString(); - - private static final int NULL_HASHCODE = reduceHashCode(5_381, 5_381); - - private CharSequence buffer; - - public UtfAnyString(final String value) { - this.buffer = value; - } - - public UtfAnyString(final Utf8String value) { - this.buffer = value; - } - - private UtfAnyString() { - } - - private UtfAnyString(final CharSequence sequence) { - this.buffer = sequence; - } - - /** - * {@code true} if the {@link UtfAnyString} is empty. - * - * @return {@code true} if the {@link UtfAnyString} is empty. - */ - public boolean isEmpty() { - return this.buffer != null && this.buffer.length() == 0; - } - - /** - * {@code true} if the {@link UtfAnyString} is {@code null}. - * - * @return {@code true} if the {@link UtfAnyString} is {@code null}. - */ - public boolean isNull() { - return null == this.buffer; - } - - /** - * {@code true} if the underlying representation of the {@link UtfAnyString} is a {@link String}. - * - * @return {@code true} if the underlying representation of the {@link UtfAnyString} is a {@link String}. - */ - public boolean isUtf16() { - return this.buffer instanceof String; - } - - /** - * {@code true} if the underlying representation of the {@link UtfAnyString} is a {@link Utf8String}. - * - * @return {@code true} if the underlying representation of the {@link UtfAnyString} is a {@link Utf8String}. - */ - public boolean isUtf8() { - return this.buffer instanceof Utf8String; - } - - /** - * Returns the {@code char} value at the specified {@code index}. - *

- * An index ranges from zero to {@link UtfAnyString#length()} minus one. The first {@code char} value of the - * sequence is at index zero, the next at index one, and so on, as for array indexing. If the {@code char} - * value specified by the {@code index} is a surrogate, the surrogate (not the surrogate pair) is returned. - * - * @param index the index of the {@code char} value to be returned. - * @return the specified {@code char} value - * @throws IndexOutOfBoundsException if the {@code index} argument is negative or not less than - * {@link UtfAnyString#length()} - * @throws UnsupportedOperationException if this {@link UtfAnyString} is {@code null}. - */ - @Override - public char charAt(final int index) { - if (this.buffer == null) { - throw new UnsupportedOperationException("String is null"); - } - return this.buffer.charAt(index); - } - - public int compareTo(@Nonnull final String other) { - - checkNotNull(other, "expected non-null other"); - - if (other == this.buffer) { - return 0; - } - - if (this.buffer == null) { - return -1; - } - - return this.buffer instanceof String - ? ((String) this.buffer).compareTo(other) - : ((Utf8String) this.buffer).compareTo(other); - } - - public int compareTo(@Nonnull final Utf8String other) { - - checkNotNull(other, "expected non-null other"); - - if (other == this.buffer) { - return 0; - } - - if (this.buffer == null) { - return -1; - } - - return this.buffer instanceof String - ? -other.compareTo((String) this.buffer) - : ((Utf8String) this.buffer).compareTo(other); - } - - @Override - public int compareTo(@Nonnull final UtfAnyString other) { - - checkNotNull(other, "expected non-null other"); - - if (other.buffer == this.buffer) { - return 0; - } - - if (other.buffer == null) { - return 1; - } - - if (this.buffer == null) { - return -1; - } - - if (this.buffer instanceof String) { - return other.buffer instanceof String - ? ((String) this.buffer).compareTo((String) other.buffer) - : -((Utf8String) other.buffer).compareTo((String) this.buffer); - } - - return ((Utf8String) this.buffer).compareTo((Utf8String) other.buffer); - } - - public static UtfAnyString empty() { - return EMPTY; - } - - @Override - public boolean equals(final Object other) { - - if (other == null) { - return false; - } - - if (other instanceof String) { - return this.equals((String) other); - } - - if (other instanceof Utf8String) { - return this.equals((Utf8String) other); - } - - if (other instanceof UtfAnyString) { - return this.equals((UtfAnyString) other); - } - - return false; - } - - public boolean equals(final String other) { - - if (null == this.buffer) { - return null == other; - } - - if (this.buffer instanceof String) { - return other.contentEquals(this.buffer); // skips the type check that String.equals performs - } - - return ((Utf8String) this.buffer).equals(other); - } - - public boolean equals(final Utf8String other) { - - if (null == other) { - return null == this.buffer; - } - - return other.equals(this.buffer); - } - - public boolean equals(final UtfAnyString other) { - - if (null == other) { - return false; - } - - if (null == this.buffer) { - return null == other.buffer; - } - - return this.buffer instanceof String ? other.buffer.equals(this.buffer) : this.buffer.equals(other.buffer); - } - - @Override - public int hashCode() { - - final long[] hash = { 5_381, 5_381 }; - - if (this.buffer == null) { - return NULL_HASHCODE; - } - - if (this.buffer instanceof String) { - - final int ignored = ((String) this.buffer).codePoints().reduce(0, (index, codePoint) -> { - if (index % 2 == 0) { - hash[0] = ((hash[0] << 5) + hash[0]) ^ codePoint; - } else { - hash[1] = ((hash[1] << 5) + hash[1]) ^ codePoint; - } - return index; - }); - - return reduceHashCode(hash[0], hash[1]); - } - - return this.buffer.hashCode(); - } - - /** - * Returns the length of this character sequence. - *

- * The length is the number of 16-bit {@code char}s in the sequence. - * - * @return the number of {@code char}s in this sequence. - * @throws UnsupportedOperationException if this {@link UtfAnyString} is {@code null}. - */ - @Override - public int length() { - if (this.buffer == null) { - throw new UnsupportedOperationException("String is null"); - } - return this.buffer.length(); - } - - /** - * Returns a {@code CharSequence} that is a subsequence of this sequence. - *

- * The subsequence starts with the {@code char} value at the specified index and ends with the{@code char} value at - * index {@code end - 1}. The length (in {@code char}s) of the returned sequence is {@code end - start}, so if - * {@code start == end}, an empty sequence is returned. - * - * @param start the start index, inclusive - * @param end the end index, exclusive - * @return the specified subsequence - * @throws IndexOutOfBoundsException if {@code start} or {@code end} are negative, {@code end} is greater than - * {@link UtfAnyString#length()}, or {@code start} is greater than {@code - * end}. - * @throws UnsupportedOperationException if string is {@code null} - */ - @Override - @Nonnull - public CharSequence subSequence(final int start, final int end) { - if (this.buffer == null) { - throw new UnsupportedOperationException("String is null"); - } - return this.buffer.subSequence(start, end); - } - - @Override - @Nonnull - public String toString() { - return String.valueOf(this.buffer); - } - - public String toUtf16() { - if (null == this.buffer) { - return null; - } - return this.buffer instanceof String ? (String) this.buffer : this.buffer.toString(); - } - - public Utf8String toUtf8() { - if (null == this.buffer) { - return null; - } - return this.buffer instanceof String ? transcodeUtf16((String) this.buffer) : (Utf8String) this.buffer; - } - - private static int reduceHashCode(final long h1, final long h2) { - return Long.valueOf(h1 + (h2 * 1_566_083_941L)).intValue(); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.core; + +import javax.annotation.Nonnull; + +import static com.azure.data.cosmos.core.Utf8String.transcodeUtf16; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A string whose memory representation may be either UTF-8 or UTF-16. + *

+ * This type supports polymorphic use of {@link String} and {@link Utf8String} when equality, hashing, and comparison + * are needed against either encoding. An API leveraging {@link UtfAnyString} can avoid separate method overloads + * while still accepting either encoding without imposing additional allocations. + */ +public final class UtfAnyString implements CharSequence, Comparable { + + public static final UtfAnyString EMPTY = new UtfAnyString(""); + public static final UtfAnyString NULL = new UtfAnyString(); + + private static final int NULL_HASHCODE = reduceHashCode(5_381, 5_381); + + private CharSequence buffer; + + public UtfAnyString(final String value) { + this.buffer = value; + } + + public UtfAnyString(final Utf8String value) { + this.buffer = value; + } + + private UtfAnyString() { + } + + private UtfAnyString(final CharSequence sequence) { + this.buffer = sequence; + } + + /** + * {@code true} if the {@link UtfAnyString} is empty. + * + * @return {@code true} if the {@link UtfAnyString} is empty. + */ + public boolean isEmpty() { + return this.buffer != null && this.buffer.length() == 0; + } + + /** + * {@code true} if the {@link UtfAnyString} is {@code null}. + * + * @return {@code true} if the {@link UtfAnyString} is {@code null}. + */ + public boolean isNull() { + return null == this.buffer; + } + + /** + * {@code true} if the underlying representation of the {@link UtfAnyString} is a {@link String}. + * + * @return {@code true} if the underlying representation of the {@link UtfAnyString} is a {@link String}. + */ + public boolean isUtf16() { + return this.buffer instanceof String; + } + + /** + * {@code true} if the underlying representation of the {@link UtfAnyString} is a {@link Utf8String}. + * + * @return {@code true} if the underlying representation of the {@link UtfAnyString} is a {@link Utf8String}. + */ + public boolean isUtf8() { + return this.buffer instanceof Utf8String; + } + + /** + * Returns the {@code char} value at the specified {@code index}. + *

+ * An index ranges from zero to {@link UtfAnyString#length()} minus one. The first {@code char} value of the + * sequence is at index zero, the next at index one, and so on, as for array indexing. If the {@code char} + * value specified by the {@code index} is a surrogate, the surrogate (not the surrogate pair) is returned. + * + * @param index the index of the {@code char} value to be returned. + * @return the specified {@code char} value + * @throws IndexOutOfBoundsException if the {@code index} argument is negative or not less than + * {@link UtfAnyString#length()} + * @throws UnsupportedOperationException if this {@link UtfAnyString} is {@code null}. + */ + @Override + public char charAt(final int index) { + if (this.buffer == null) { + throw new UnsupportedOperationException("String is null"); + } + return this.buffer.charAt(index); + } + + public int compareTo(@Nonnull final String other) { + + checkNotNull(other, "expected non-null other"); + + if (other == this.buffer) { + return 0; + } + + if (this.buffer == null) { + return -1; + } + + return this.buffer instanceof String + ? ((String) this.buffer).compareTo(other) + : ((Utf8String) this.buffer).compareTo(other); + } + + public int compareTo(@Nonnull final Utf8String other) { + + checkNotNull(other, "expected non-null other"); + + if (other == this.buffer) { + return 0; + } + + if (this.buffer == null) { + return -1; + } + + return this.buffer instanceof String + ? -other.compareTo((String) this.buffer) + : ((Utf8String) this.buffer).compareTo(other); + } + + @Override + public int compareTo(@Nonnull final UtfAnyString other) { + + checkNotNull(other, "expected non-null other"); + + if (other.buffer == this.buffer) { + return 0; + } + + if (other.buffer == null) { + return 1; + } + + if (this.buffer == null) { + return -1; + } + + if (this.buffer instanceof String) { + return other.buffer instanceof String + ? ((String) this.buffer).compareTo((String) other.buffer) + : -((Utf8String) other.buffer).compareTo((String) this.buffer); + } + + return ((Utf8String) this.buffer).compareTo((Utf8String) other.buffer); + } + + public static UtfAnyString empty() { + return EMPTY; + } + + @Override + public boolean equals(final Object other) { + + if (other == null) { + return false; + } + + if (other instanceof String) { + return this.equals((String) other); + } + + if (other instanceof Utf8String) { + return this.equals((Utf8String) other); + } + + if (other instanceof UtfAnyString) { + return this.equals((UtfAnyString) other); + } + + return false; + } + + public boolean equals(final String other) { + + if (null == this.buffer) { + return null == other; + } + + if (this.buffer instanceof String) { + return other.contentEquals(this.buffer); // skips the type check that String.equals performs + } + + return ((Utf8String) this.buffer).equals(other); + } + + public boolean equals(final Utf8String other) { + + if (null == other) { + return null == this.buffer; + } + + return other.equals(this.buffer); + } + + public boolean equals(final UtfAnyString other) { + + if (null == other) { + return false; + } + + if (null == this.buffer) { + return null == other.buffer; + } + + return this.buffer instanceof String ? other.buffer.equals(this.buffer) : this.buffer.equals(other.buffer); + } + + @Override + public int hashCode() { + + final long[] hash = { 5_381, 5_381 }; + + if (this.buffer == null) { + return NULL_HASHCODE; + } + + if (this.buffer instanceof String) { + + final int ignored = ((String) this.buffer).codePoints().reduce(0, (index, codePoint) -> { + if (index % 2 == 0) { + hash[0] = ((hash[0] << 5) + hash[0]) ^ codePoint; + } else { + hash[1] = ((hash[1] << 5) + hash[1]) ^ codePoint; + } + return index; + }); + + return reduceHashCode(hash[0], hash[1]); + } + + return this.buffer.hashCode(); + } + + /** + * Returns the length of this character sequence. + *

+ * The length is the number of 16-bit {@code char}s in the sequence. + * + * @return the number of {@code char}s in this sequence. + * @throws UnsupportedOperationException if this {@link UtfAnyString} is {@code null}. + */ + @Override + public int length() { + if (this.buffer == null) { + throw new UnsupportedOperationException("String is null"); + } + return this.buffer.length(); + } + + /** + * Returns a {@code CharSequence} that is a subsequence of this sequence. + *

+ * The subsequence starts with the {@code char} value at the specified index and ends with the{@code char} value at + * index {@code end - 1}. The length (in {@code char}s) of the returned sequence is {@code end - start}, so if + * {@code start == end}, an empty sequence is returned. + * + * @param start the start index, inclusive + * @param end the end index, exclusive + * @return the specified subsequence + * @throws IndexOutOfBoundsException if {@code start} or {@code end} are negative, {@code end} is greater than + * {@link UtfAnyString#length()}, or {@code start} is greater than {@code + * end}. + * @throws UnsupportedOperationException if string is {@code null} + */ + @Override + @Nonnull + public CharSequence subSequence(final int start, final int end) { + if (this.buffer == null) { + throw new UnsupportedOperationException("String is null"); + } + return this.buffer.subSequence(start, end); + } + + @Override + @Nonnull + public String toString() { + return String.valueOf(this.buffer); + } + + public String toUtf16() { + if (null == this.buffer) { + return null; + } + return this.buffer instanceof String ? (String) this.buffer : this.buffer.toString(); + } + + public Utf8String toUtf8() { + if (null == this.buffer) { + return null; + } + return this.buffer instanceof String ? transcodeUtf16((String) this.buffer) : (Utf8String) this.buffer; + } + + private static int reduceHashCode(final long h1, final long h2) { + return Long.valueOf(h1 + (h2 * 1_566_083_941L)).intValue(); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Float128.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Float128.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Float128.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Float128.java index aeb75d4..5e694cb 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Float128.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Float128.java @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -/** - * Represents an IEEE 754-2008 128-bit decimal floating point number. - *

- * The {@link Float128} represents an IEEE 754-2008 floating point number as a pair of {@code long} values: - * {@link #high()} and {@link #low()}. - * - * @see decimal128 floating-point format - * @see 754-2008: IEEE Standard for Floating-Point Arithmetic - * @see Decimal Arithmetic Encodings Version 1.01 – 7 Apr 2009 - */ -public final class Float128 { - - /** - * The size (in bytes) of a {@link Float128}. - */ - public static final int BYTES = 2 * Long.BYTES; - public static final Float128 ZERO = new Float128(0L, 0L); - - private final long high; - private final long low; - - /** - * Initializes a new instance of the {@link Float128} struct. - * - * @param high the high-order 64 bits. - * @param low the low-order 64 bits. - */ - public Float128(long high, long low) { - this.high = high; - this.low = low; - } - - /** - * The high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point, using the BID encoding scheme. - * - * @return the high-order 64 bits of the IEEE 754-2008 128-bit floating point number represented by this object. - */ - public long high() { - return this.high; - } - - /** - * The low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point, using the BID encoding scheme. - * - * @return the low-order 64 bits of the IEEE 754-2008 128-bit floating point number represented by this object. - */ - public long low() { - return this.low; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +/** + * Represents an IEEE 754-2008 128-bit decimal floating point number. + *

+ * The {@link Float128} represents an IEEE 754-2008 floating point number as a pair of {@code long} values: + * {@link #high()} and {@link #low()}. + * + * @see decimal128 floating-point format + * @see 754-2008: IEEE Standard for Floating-Point Arithmetic + * @see Decimal Arithmetic Encodings Version 1.01 – 7 Apr 2009 + */ +public final class Float128 { + + /** + * The size (in bytes) of a {@link Float128}. + */ + public static final int BYTES = 2 * Long.BYTES; + public static final Float128 ZERO = new Float128(0L, 0L); + + private final long high; + private final long low; + + /** + * Initializes a new instance of the {@link Float128} struct. + * + * @param high the high-order 64 bits. + * @param low the low-order 64 bits. + */ + public Float128(long high, long low) { + this.high = high; + this.low = low; + } + + /** + * The high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point, using the BID encoding scheme. + * + * @return the high-order 64 bits of the IEEE 754-2008 128-bit floating point number represented by this object. + */ + public long high() { + return this.high; + } + + /** + * The low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point, using the BID encoding scheme. + * + * @return the low-order 64 bits of the IEEE 754-2008 128-bit floating point number represented by this object. + */ + public long low() { + return this.low; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HashCode128.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HashCode128.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HashCode128.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HashCode128.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowHeader.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowHeader.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowHeader.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowHeader.java index df3b078..be10a88 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowHeader.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowHeader.java @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Describes the header that precedes all valid Hybrid Rows. - */ -public final class HybridRowHeader { - /** - * Size (in bytes) of a serialized header. - */ - public static final int BYTES = HybridRowVersion.BYTES + SchemaId.BYTES; - - private final SchemaId schemaId; - private final HybridRowVersion version; - - /** - * Initializes a new instance of a {@link HybridRowHeader}. - * - * @param version The version of the HybridRow library used to write this row. - * @param schemaId The unique identifier of the schema whose layout was used to write this row. - */ - public HybridRowHeader(@Nonnull final HybridRowVersion version, @Nonnull SchemaId schemaId) { - - checkNotNull(version, "expected non-null version"); - checkNotNull(schemaId, "expected non-null schemaId"); - - this.version = version; - this.schemaId = schemaId; - } - - /** - * The unique identifier of the schema whose layout was used to write this {@link HybridRowHeader}. - * - * @return unique identifier of the schema whose layout was used to write this {@link HybridRowHeader}. - */ - @Nonnull - public SchemaId schemaId() { - return this.schemaId; - } - - /** - * The version of the HybridRow serialization library used to write this {@link HybridRowHeader}. - * - * @return version of the HybridRow serialization library used to write this {@link HybridRowHeader}. - */ - @Nonnull - public HybridRowVersion version() { - return this.version; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Describes the header that precedes all valid Hybrid Rows. + */ +public final class HybridRowHeader { + /** + * Size (in bytes) of a serialized header. + */ + public static final int BYTES = HybridRowVersion.BYTES + SchemaId.BYTES; + + private final SchemaId schemaId; + private final HybridRowVersion version; + + /** + * Initializes a new instance of a {@link HybridRowHeader}. + * + * @param version The version of the HybridRow library used to write this row. + * @param schemaId The unique identifier of the schema whose layout was used to write this row. + */ + public HybridRowHeader(@Nonnull final HybridRowVersion version, @Nonnull SchemaId schemaId) { + + checkNotNull(version, "expected non-null version"); + checkNotNull(schemaId, "expected non-null schemaId"); + + this.version = version; + this.schemaId = schemaId; + } + + /** + * The unique identifier of the schema whose layout was used to write this {@link HybridRowHeader}. + * + * @return unique identifier of the schema whose layout was used to write this {@link HybridRowHeader}. + */ + @Nonnull + public SchemaId schemaId() { + return this.schemaId; + } + + /** + * The version of the HybridRow serialization library used to write this {@link HybridRowHeader}. + * + * @return version of the HybridRow serialization library used to write this {@link HybridRowHeader}. + */ + @Nonnull + public HybridRowVersion version() { + return this.version; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowVersion.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowVersion.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowVersion.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowVersion.java index f60b370..8c038e5 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowVersion.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/HybridRowVersion.java @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import com.google.common.base.Suppliers; -import it.unimi.dsi.fastutil.bytes.Byte2ReferenceMap; -import it.unimi.dsi.fastutil.bytes.Byte2ReferenceOpenHashMap; - -import java.util.function.Supplier; - -/** - * Versions of HybridRow. - *

- * A version from this list MUST be inserted in the version BOM at the beginning of all rows. - */ -public enum HybridRowVersion { - - INVALID((byte)0), - - /** - * Initial version of the HybridRow format. - */ - V1((byte)0x81); - - public static final int BYTES = Byte.BYTES; - - private static final Supplier> mappings = Suppliers.memoize(() -> { - final HybridRowVersion[] constants = HybridRowVersion.class.getEnumConstants(); - final byte[] values = new byte[constants.length]; - for (int i = 0; i < constants.length; i++) { - values[i] = constants[i].value(); - } - return new Byte2ReferenceOpenHashMap<>(values, constants); - }); - - private final byte value; - - HybridRowVersion(final byte value) { - this.value = value; - } - - public static HybridRowVersion from(final byte value) { - return mappings.get().get(value); - } - - public byte value() { - return this.value; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.bytes.Byte2ReferenceMap; +import it.unimi.dsi.fastutil.bytes.Byte2ReferenceOpenHashMap; + +import java.util.function.Supplier; + +/** + * Versions of HybridRow. + *

+ * A version from this list MUST be inserted in the version BOM at the beginning of all rows. + */ +public enum HybridRowVersion { + + INVALID((byte)0), + + /** + * Initial version of the HybridRow format. + */ + V1((byte)0x81); + + public static final int BYTES = Byte.BYTES; + + private static final Supplier> mappings = Suppliers.memoize(() -> { + final HybridRowVersion[] constants = HybridRowVersion.class.getEnumConstants(); + final byte[] values = new byte[constants.length]; + for (int i = 0; i < constants.length; i++) { + values[i] = constants[i].value(); + } + return new Byte2ReferenceOpenHashMap<>(values, constants); + }); + + private final byte value; + + HybridRowVersion(final byte value) { + this.value = value; + } + + public static HybridRowVersion from(final byte value) { + return mappings.get().get(value); + } + + public byte value() { + return this.value; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/NullValue.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/NullValue.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/NullValue.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/NullValue.java index f2b9294..4dd8f2e 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/NullValue.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/NullValue.java @@ -1,41 +1,41 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -/** - * The literal null value. - *

- * May be stored hybrid row to indicate the literal null value. Typically this value should not be used and the - * corresponding column should be absent from the row. - */ -public final class NullValue { - /** - * The default null literal. - * This is the same value as default({@link NullValue}). - */ - public static final NullValue DEFAULT = new NullValue(); - - /** - * Returns true if this is the same value as {@code other}. - * - * @param other The value to compare against. - * @return True if the two values are the same. - */ - public boolean equals(NullValue other) { - return true; - } - - @Override - public boolean equals(Object other) { - if (null == other) { - return false; - } - return other instanceof NullValue && this.equals((NullValue)other); - } - - @Override - public int hashCode() { - return 42; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +/** + * The literal null value. + *

+ * May be stored hybrid row to indicate the literal null value. Typically this value should not be used and the + * corresponding column should be absent from the row. + */ +public final class NullValue { + /** + * The default null literal. + * This is the same value as default({@link NullValue}). + */ + public static final NullValue DEFAULT = new NullValue(); + + /** + * Returns true if this is the same value as {@code other}. + * + * @param other The value to compare against. + * @return True if the two values are the same. + */ + public boolean equals(NullValue other) { + return true; + } + + @Override + public boolean equals(Object other) { + if (null == other) { + return false; + } + return other instanceof NullValue && this.equals((NullValue)other); + } + + @Override + public int hashCode() { + return 42; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Result.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Result.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Result.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Result.java index 50843a2..19323d0 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Result.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/Result.java @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import com.google.common.base.Suppliers; -import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; - -import java.util.Arrays; -import java.util.function.Supplier; - -public enum Result { - - SUCCESS(0), - FAILURE(1), - NOT_FOUND(2), - EXISTS(3), - TOO_BIG(4), - - /** - * The type of an existing field does not match the expected type for this operation. - */ - TYPE_MISMATCH(5), - - /** - * An attempt to write in a read-only scope. - */ - INSUFFICIENT_PERMISSIONS(6), - - /** - * An attempt to write a field that did not match its (optional) type constraints. - */ - TYPE_CONSTRAINT(7), - - /** - * The byte sequence could not be parsed as a valid row. - */ - INVALID_ROW(8), - - /** - * The byte sequence was too short for the requested action. - */ - INSUFFICIENT_BUFFER(9), - - /** - * The operation was cancelled. - */ - CANCELED(10); - - public static final int BYTES = Integer.BYTES; - - private static final Supplier> mappings = Suppliers.memoize(() -> { - Result[] constants = Result.class.getEnumConstants(); - int[] values = new int[constants.length]; - Arrays.setAll(values, index -> constants[index].value); - return new Int2ReferenceOpenHashMap<>(values, constants); - }); - - private final int value; - - Result(int value) { - this.value = value; - } - - public static Result from(int value) { - return mappings.get().get(value); - } - - public int value() { - return this.value; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; + +import java.util.Arrays; +import java.util.function.Supplier; + +public enum Result { + + SUCCESS(0), + FAILURE(1), + NOT_FOUND(2), + EXISTS(3), + TOO_BIG(4), + + /** + * The type of an existing field does not match the expected type for this operation. + */ + TYPE_MISMATCH(5), + + /** + * An attempt to write in a read-only scope. + */ + INSUFFICIENT_PERMISSIONS(6), + + /** + * An attempt to write a field that did not match its (optional) type constraints. + */ + TYPE_CONSTRAINT(7), + + /** + * The byte sequence could not be parsed as a valid row. + */ + INVALID_ROW(8), + + /** + * The byte sequence was too short for the requested action. + */ + INSUFFICIENT_BUFFER(9), + + /** + * The operation was cancelled. + */ + CANCELED(10); + + public static final int BYTES = Integer.BYTES; + + private static final Supplier> mappings = Suppliers.memoize(() -> { + Result[] constants = Result.class.getEnumConstants(); + int[] values = new int[constants.length]; + Arrays.setAll(values, index -> constants[index].value); + return new Int2ReferenceOpenHashMap<>(values, constants); + }); + + private final int value; + + Result(int value) { + this.value = value; + } + + public static Result from(int value) { + return mappings.get().get(value); + } + + public int value() { + return this.value; + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java index 03b21ac..f688069 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java @@ -1,3761 +1,3761 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.core.Utf8String; -import com.azure.data.cosmos.serialization.hybridrow.codecs.DateTimeCodec; -import com.azure.data.cosmos.serialization.hybridrow.codecs.DecimalCodec; -import com.azure.data.cosmos.serialization.hybridrow.codecs.Float128Codec; -import com.azure.data.cosmos.serialization.hybridrow.codecs.GuidCodec; -import com.azure.data.cosmos.serialization.hybridrow.io.RowWriter; -import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutArray; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBinary; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBit; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBoolean; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutColumn; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutDateTime; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutDecimal; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutEndScope; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat128; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat32; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat64; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutGuid; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt16; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt32; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt64; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt8; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutMongoDbObjectId; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNull; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNullable; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutObject; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutResolver; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTagged; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTagged2; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTuple; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutType; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypeScope; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedArray; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedMap; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedSet; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedTuple; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypes; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUDT; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt16; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt32; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt64; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt8; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUnixDateTime; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUtf8; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarInt; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarUInt; -import com.azure.data.cosmos.serialization.hybridrow.layouts.StringToken; -import com.azure.data.cosmos.serialization.hybridrow.layouts.StringTokenizer; -import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgument; -import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; -import com.azure.data.cosmos.serialization.hybridrow.layouts.UpdateOptions; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigDecimal; -import java.time.OffsetDateTime; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Strings.lenientFormat; - -// import com.azure.data.cosmos.serialization.hybridrow.RowBuffer.UniqueIndexItem; - -//import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypes.MongoDbObjectId; - -/** - * Manages a sequence of bytes representing a Hybrid Row. - *

- * A Hybrid Row begins in the 0-th byte of the {@link RowBuffer}. The sequence of bytes is defined by the Hybrid Row - * grammar. - */ -public final class RowBuffer { - - private final ByteBuf buffer; - private LayoutResolver resolver; - - /** - * Initializes a new instance of a {@link RowBuffer}. - * - * @param capacity Initial buffer capacity. - */ - public RowBuffer(int capacity) { - this(capacity, ByteBufAllocator.DEFAULT); - } - - /** - * Initializes a new instance of a {@link RowBuffer}. - * - * @param capacity Initial buffer capacity - * @param allocator A buffer allocator - */ - public RowBuffer(final int capacity, @Nonnull final ByteBufAllocator allocator) { - checkArgument(capacity > 0, "capacity: %s", capacity); - checkNotNull(allocator, "expected non-null allocator"); - this.buffer = allocator.buffer(capacity); - this.resolver = null; - } - - /** - * Initializes a new instance of a {@link RowBuffer} from an existing buffer. - * - * @param buffer An existing {@link ByteBuf} containing a Hybrid Row. This instance takes ownership of the buffer. - * Hence, the caller should not maintain a reference to the buffer or mutate the buffer after this - * call returns. - * @param version The version of the Hybrid Row format to use for encoding the buffer. - * @param resolver The resolver for UDTs. - */ - public RowBuffer( - @Nonnull final ByteBuf buffer, - @Nonnull final HybridRowVersion version, - @Nonnull final LayoutResolver resolver) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(version, "expected non-null version"); - checkNotNull(resolver, "expected non-null resolver"); - - final int length = buffer.writerIndex(); - - checkArgument(length >= HybridRowHeader.BYTES, - "expected buffer with at least %s, not %s bytes", HybridRowHeader.BYTES, length); - - this.buffer = buffer; - this.resolver = resolver; - - final Item item = this.read(this::readHeader, 0); - final HybridRowHeader header = item.value(); - checkState(header.version() == version, "expected header version %s, not %s", version, header.version()); - - final Layout layout = resolver.resolve(header.schemaId()); - checkState(header.schemaId().equals(layout.schemaId())); - checkState(HybridRowHeader.BYTES + layout.size() <= this.length()); - } - - /** - * Compute the byte offset from the beginning of the row for a given variable's value. - * - * @param layout The (optional) layout of the current scope. - * @param scopeOffset The zero-based offset to the beginning of the scope's value. - * @param varIndex The zero-based index of the variable within the variable segment. - * @return The byte offset from the beginning of the row where the variable's value should be located. - */ - public int computeVariableValueOffset(@Nullable final Layout layout, final int scopeOffset, final int varIndex) { - - checkArgument(scopeOffset >= 0, "expected non-negative scopeOffset, not %s", scopeOffset); - checkArgument(varIndex >= 0, "expected non-negative varIndex, not %s", varIndex); - - if (layout == null) { - return scopeOffset; - } - - final List columns = layout.columns(); - final int index = layout.numFixed() + varIndex; - checkState(index <= columns.size()); - - int offset = scopeOffset + layout.size(); - - for (int i = layout.numFixed(); i < index; i++) { - - LayoutColumn column = columns.get(i); - - if (this.readBit(scopeOffset, column.nullBit())) { - Item item = this.read(this::read7BitEncodedUInt, offset); - if (column.type().isVarint()) { - offset += item.length(); - } else { - offset += item.length() + item.value(); - } - } - } - - return offset; - } - - /** - * Compute the number of bytes necessary to store the unsigned 32-bit integer value using the varuint encoding. - * - * @param value the value to be encoded - * @return the number of bytes needed to store the varuint encoding of {@code value} - */ - public static int count7BitEncodedUInt(long value) { - checkArgument(0 <= value && value <= 0x00000000FFFFFFFFL, "value: %s", value); - int i = 0; - while (value >= 0x80L) { - i++; - value >>>= 7; - } - i++; - return i; - } - - /** - * Decrement the unsigned 32-bit integer value at the given {@code offset} in this {@link RowBuffer}. - * - * @param offset offset of a 32-bit unsigned integer value in this {@link RowBuffer}. - * @param decrement the decrement value. - */ - public void decrementUInt32(int offset, long decrement) { - long value = this.buffer.getUnsignedIntLE(offset); - this.buffer.setIntLE(offset, (int) (value - decrement)); - } - - /** - * Delete the sparse field at the specified cursor position. - * - * @param edit identifies the field to delete - */ - public void deleteSparse(@Nonnull final RowCursor edit) { - - checkNotNull(edit, "expected non-null edit"); - - if (!edit.exists()) { - return; // do nothing - } - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(0, edit, edit.cellType(), edit.cellTypeArgs(), RowOptions.DELETE, metaBytes, spaceNeeded, shift); - - checkState(this.length() == priorLength + shift.get()); - } - - /** - * Delete the variable-length field at a specified {@code offset}. - *

- * The field is interpreted as either a variable-length integer or a variable-length sequence of bytes as indicated - * by the value of {@code isVarint}. - * - * @param offset index of the field in this {@link RowBuffer} - * @param isVarint {@code true}, if the field should be interpreted as a variable-length integer value; - * {@code false}, if the field should be interpreted as a variable-length sequence of bytes. - */ - public void deleteVariable(final int offset, final boolean isVarint) { - - final Item item = this.read(this::read7BitEncodedUInt, offset); - - final int source = isVarint - ? offset + item.length() // because this is a varint value - : offset + item.length() + item.value().intValue(); // because this is a variable-length sequence of bytes - - final int length = this.buffer.writerIndex() - source; - - this.buffer.setBytes(offset, this.buffer, source, length); - this.buffer.writerIndex(this.buffer.writerIndex() - length); - } - - /** - * The root header of this {@link RowBuffer}. - * - * @return root header of this {@link RowBuffer}. - */ - public HybridRowHeader header() { - return this.readHeader(); - } - - // TODO: DANOBLE: ressurrect this method - // public void WriteSparseMongoDbObjectId(@Nonnull final RowCursor edit, MongoDbObjectId value, - // UpdateOptions options) { - // int numBytes = MongoDbObjectId.Size; - // int metaBytes; - // final Out metaBytes = new Out<>(); - // int spaceNeeded; - // final Out spaceNeeded = new Out<>(); - // int shift; - // final Out shift = new Out<>(); - // this.ensureSparse(numBytes, edit, MongoDbObjectId, TypeArgumentList.EMPTY, options, - // metaBytes, spaceNeeded, shift); - // this.writeSparseMetadata(edit, MongoDbObjectId, TypeArgumentList.EMPTY, metaBytes); - // this.WriteMongoDbObjectId(edit.valueOffset(), value.clone()); - // checkState(spaceNeeded == metaBytes + MongoDbObjectId.Size); - // edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - // this.buffer.writerIndex(this.length() + shift); - // } - - /** - * Decrement the unsigned 32-bit integer value at the given {@code offset} in this {@link RowBuffer}. - * - * @param offset offset of a 32-bit unsigned integer value in this {@link RowBuffer}. - * @param increment the increment value. - */ - public void incrementUInt32(final int offset, final long increment) { - final long value = this.buffer.getUnsignedIntLE(offset); - this.buffer.setIntLE(offset, (int) (value + increment)); - } - - /** - * Initializes a row to the minimal size for the given layout. - * - * @param version The version of the Hybrid Row format to use for encoding this row. - * @param layout The layout that describes the column layout of the row. - * @param resolver The resolver for UDTs. - *

- * The row is initialized to default row for the given layout. All fixed columns have their - * default values. All variable columns are null. No sparse columns are present. The row is - * valid. - */ - public void initLayout(HybridRowVersion version, Layout layout, LayoutResolver resolver) { - - checkNotNull(version, "expected non-null version"); - checkNotNull(layout, "expected non-null layout"); - checkNotNull(resolver, "expected non-null resolver"); - - this.writeHeader(new HybridRowHeader(version, layout.schemaId())); - this.buffer.writeZero(layout.size()); - this.resolver = resolver; - } - - /** - * The length of this {@link RowBuffer} in bytes. - * - * @return The length of this {@link RowBuffer} in bytes. - */ - public int length() { - return this.buffer.writerIndex(); - } - - /** - * Compute the byte offsets from the beginning of the row for a given sparse field insertion. - * into a set/map. - * - * @param scope The sparse scope to insert into. - * @param srcEdit The field to move into the set/map. - * @return The prepared edit context. - */ - @Nonnull - public RowCursor prepareSparseMove(@Nonnull final RowCursor scope, @Nonnull final RowCursor srcEdit) { - - checkNotNull(srcEdit, "expected non-null srcEdit"); - checkNotNull(scope, "expected non-null scope"); - checkArgument(scope.index() == 0); - checkArgument(scope.scopeType().isUniqueScope()); - - RowCursor dstEdit = scope.clone().metaOffset(scope.valueOffset()); - int srcSize = this.sparseComputeSize(srcEdit); - int srcBytes = srcSize - (srcEdit.valueOffset() - srcEdit.metaOffset()); - - while (dstEdit.index() < dstEdit.count()) { - - this.readSparseMetadata(dstEdit); - checkState(dstEdit.pathOffset() == 0); - - int elmSize = -1; // defer calculating the full size until needed - int cmp; - - if (scope.scopeType() instanceof LayoutTypedMap) { - cmp = this.compareKeyValueFieldValue(srcEdit, dstEdit); - } else { - elmSize = this.sparseComputeSize(dstEdit); - int elmBytes = elmSize - (dstEdit.valueOffset() - dstEdit.metaOffset()); - cmp = this.compareFieldValue(srcEdit, srcBytes, dstEdit, elmBytes); - } - - if (cmp <= 0) { - dstEdit.exists(cmp == 0); - return dstEdit; - } - - elmSize = elmSize == -1 ? this.sparseComputeSize(dstEdit) : elmSize; - dstEdit.index(dstEdit.index() + 1); - dstEdit.metaOffset(dstEdit.metaOffset() + elmSize); - } - - dstEdit.exists(false); - dstEdit.cellType(LayoutTypes.END_SCOPE); - dstEdit.valueOffset(dstEdit.metaOffset()); - - return dstEdit; - } - - /** - * Read the value of a bit within the bit field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a bit field within this {@link RowBuffer}. - * @param bit the bit to read. - * @return {@code true} if the {@code bit} is set, otherwise {@code false}. - */ - public boolean readBit(final int offset, @Nonnull final LayoutBit bit) { - - checkNotNull(bit, "expected non-null bit"); - - if (bit.isInvalid()) { - return true; - } - - Item item = this.read(() -> (this.buffer.readByte() & (byte) (1 << bit.bit())) != 0, bit.offset(offset)); - return item.value(); - } - - /** - * Read the value of the {@code DateTime} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code DateTime} field within this {@link RowBuffer}. - * @return the {@code DateTime} value read. - */ - public OffsetDateTime readDateTime(int offset) { - Item item = this.read(() -> DateTimeCodec.decode(this.buffer), offset); - return item.value(); - } - - // TODO: DANOBLE: resurrect this method - // public MongoDbObjectId ReadMongoDbObjectId(int offset) { - // return MemoryMarshal.Read(this.buffer.Slice(offset)); - // } - - /** - * Read the value of the {@code Decimal} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Decimal} field within this {@link RowBuffer}. - * @return the {@code Decimal} value read. - */ - public BigDecimal readDecimal(int offset) { - Item item = this.read(() -> DecimalCodec.decode(this.buffer), offset); - return item.value(); - } - - /** - * Read the value of a {@code FixedBinary} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code FixedBinary} field within this {@link RowBuffer}. - * @param length number of bytes to read. - * @return the {@code FixedBinary} value read. - */ - public ByteBuf readFixedBinary(int offset, int length) { - Item item = this.read(() -> this.buffer.readSlice(length), offset); - return item.value(); - } - - /** - * Read the value of a {@code FixedString} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code FixedString} field within this {@link RowBuffer}. - * @param length number of bytes in the {@code FixedString} field. - * @return the {@code FixedString} value read. - */ - public Utf8String readFixedString(int offset, int length) { - Item item = this.read(this::readFixedString, offset, length); - return item.value(); - } - - /** - * Read the value of a {@code Float128} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Float128} field within this {@link RowBuffer}. - * @return the {@code Float128} value read. - */ - public Float128 readFloat128(int offset) { - Item item = this.read(this::readFloat128, offset); - return item.value(); - } - - /** - * Read the value of a {@code Float32} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Float32} field within this {@link RowBuffer}. - * @return the {@code Float32} value read. - */ - public float readFloat32(int offset) { - Item item = this.read(this.buffer::readFloatLE, offset); - return item.value(); - } - - /** - * Read the value of a {@code Float64} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Float64} field within this {@link RowBuffer}. - * @return the {@code Float64} value read. - */ - public double readFloat64(int offset) { - Item item = this.read(this.buffer::readDoubleLE, offset); - return item.value(); - } - - // TODO: DANOBLE: resurrect this method - // public MongoDbObjectId ReadSparseMongoDbObjectId(Reference edit) { - // this.readSparsePrimitiveTypeCode(edit, MongoDbObjectId); - // edit.endOffset = edit.valueOffset() + MongoDbObjectId.Size; - // return this.ReadMongoDbObjectId(edit.valueOffset()).clone(); - // } - - /** - * Reads in the contents of the current {@link RowBuffer} from an {@link InputStream}. - *

- * The {@link RowBuffer} is initialized with the associated layout and row {@code version}. - * - * @param inputStream the stream from which the contents of the current {@link RowBuffer} should be read - * @param byteCount the number of bytes to be read from the {@code inputStream} - * @param version the {@link HybridRowVersion} to be assigned to the current {@link RowBuffer} - * @param resolver the layout resolver to be used in parsing the {@code inputStream} - * @return {@code true} if the read succeeded; otherwise, if the {@link InputStream} was corrupted, {@code false} - */ - public boolean readFrom( - @Nonnull final InputStream inputStream, final int byteCount, @Nonnull final HybridRowVersion version, - @Nonnull final LayoutResolver resolver) { - - checkNotNull(inputStream, "expected non-null inputStream"); - checkNotNull(resolver, "expected non-null resolver"); - checkNotNull(version, "expected non-null version"); - checkState(byteCount >= HybridRowHeader.BYTES, "expected byteCount >= %s, not %s", HybridRowHeader.BYTES, - byteCount); - - this.reset(); - this.ensure(byteCount); - this.resolver = resolver; - - final int bytesRead; - - try { - bytesRead = this.buffer.writeBytes(inputStream, byteCount); - } catch (IOException error) { - return false; - } - - if (bytesRead != byteCount) { - return false; - } - - return this.validateHeader(version); - } - - /** - * Reads the contents of the current {@link RowBuffer} from a {@link ByteBuf}. - *

- * The {@link RowBuffer} is initialized with a copy of the specified input {@link ByteBuf} and the associated layout - * and row {@code version}. - * - * @param input the buffer from which the contents of the current {@link RowBuffer} should be read - * @param version the {@link HybridRowVersion} to be assigned to the current {@link RowBuffer} - * @param resolver the layout resolver to be used in parsing the {@code inputStream} - * @return {@code true} if the read succeeded; otherwise, if the {@link InputStream} was corrupted, {@code false} - */ - public boolean readFrom( - @Nonnull final ByteBuf input, @Nonnull final HybridRowVersion version, @Nonnull final LayoutResolver resolver) { - - checkNotNull(input, "expected non-null input"); - checkNotNull(version, "expected non-null version"); - checkNotNull(resolver, "expected non-null resolver"); - checkState(input.readableBytes() >= HybridRowHeader.BYTES); - - this.reset(); - this.resolver = resolver; - this.buffer.writeBytes(this.buffer); - - return this.validateHeader(version); - } - - /** - * Read the value of a {@code Guid} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Guid} field within this {@link RowBuffer}. - * @return the {@code Guid} value read. - */ - public UUID readGuid(int offset) { - return this.read(() -> GuidCodec.decode(this.buffer), offset).value(); - } - - /** - * Read the value of a {@code Header} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Header} field within this {@link RowBuffer}. - * @return the {@code Header} value read. - */ - public HybridRowHeader readHeader(int offset) { - return this.read(this::readHeader, offset).value(); - } - - /** - * Read the value of a {@code Int16} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Int16} field within this {@link RowBuffer}. - * @return the {@code Int16} value read. - */ - public short readInt16(int offset) { - return this.read(this.buffer::readShortLE, offset).value(); - } - - /** - * Read the value of a {@code Int32} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Int32} field within this {@link RowBuffer}. - * @return the {@code Int32} value read. - */ - public int readInt32(int offset) { - Item item = this.read(this.buffer::readIntLE, offset); - return item.value(); - } - - /** - * Read the value of a {@code Int64} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Int64} field within this {@link RowBuffer}. - * @return the {@code Int64} value read. - */ - public long readInt64(int offset) { - Item item = this.read(this.buffer::readLongLE, offset); - return item.value(); - } - - /** - * Read the value of a {@code Int8} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code Int8} field within this {@link RowBuffer}. - * @return the {@code Int8} value read. - */ - public byte readInt8(int offset) { - Item item = this.read(this.buffer::readByte, offset); - return item.value(); - } - - /** - * Read the value of a {@code SchemaId} field at the given {@code offset} within this {@link RowBuffer}. - * - * @param offset offset of a {@code SchemaId} field within this {@link RowBuffer}. - * @return the {@code SchemaId} value read. - */ - public SchemaId readSchemaId(int offset) { - Item item = this.read(() -> SchemaId.from(this.buffer.readIntLE()), offset); - return item.value(); - } - - /** - * Read the value of a {@code SparseBinary} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseBinary} field within this {@link RowBuffer}. - * @return the {@code SparseBinary} value read. - */ - public ByteBuf readSparseBinary(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.BINARY); - Item item = this.read(this::readVariableBinary, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseBoolean} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseBoolean} field within this {@link RowBuffer}. - * @return the {@code SparseBoolean} value read. - */ - public boolean readSparseBoolean(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.BOOLEAN); - edit.endOffset(edit.valueOffset()); - return edit.cellType() == LayoutTypes.BOOLEAN; - } - - /** - * Read the value of a {@code SparseDateTime} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseDateTime} field within this {@link RowBuffer}. - * @return the {@code SparseDateTime} value read. - */ - public OffsetDateTime readSparseDateTime(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.DATE_TIME); - edit.endOffset(edit.valueOffset() + Long.SIZE); - return this.readDateTime(edit.valueOffset()); - } - - /** - * Read the value of a {@code SparseDecimal} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseDecimal} field within this {@link RowBuffer}. - * @return the {@code SparseDecimal} value read. - */ - public BigDecimal readSparseDecimal(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.DECIMAL); - Item item = this.read(this::readDecimal, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseFloat128} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseFloat128} field within this {@link RowBuffer}. - * @return the {@code SparseFloat128} value read. - */ - public Float128 readSparseFloat128(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.FLOAT_128); - Item item = this.read(this::readFloat128, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseFloat32} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseFloat32} field within this {@link RowBuffer}. - * @return the {@code SparseFloat32} value read. - */ - public float readSparseFloat32(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.FLOAT_32); - Item item = this.read(this.buffer::readFloatLE, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseFloat64} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseFloat64} field within this {@link RowBuffer}. - * @return the {@code SparseFloat64} value read. - */ - public double readSparseFloat64(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.FLOAT_64); - Item item = this.read(this.buffer::readDoubleLE, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseGuid} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseGuid} field within this {@link RowBuffer}. - * @return the {@code SparseGuid} value read. - */ - public UUID readSparseGuid(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.GUID); - Item item = this.read(this::readGuid, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseInt16} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseInt16} field within this {@link RowBuffer}. - * @return the {@code SparseInt16} value read. - */ - public short readSparseInt16(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.INT_16); - Item item = this.read(this.buffer::readShortLE, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseInt32} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseInt32} field within this {@link RowBuffer}. - * @return the {@code SparseInt32} value read. - */ - public int readSparseInt32(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.INT_32); - Item item = this.read(this.buffer::readIntLE, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseInt64} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseInt64} field within this {@link RowBuffer}. - * @return the {@code SparseInt64} value read. - */ - public long readSparseInt64(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.INT_64); - Item item = this.read(this.buffer::readLongLE, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseInt8} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseInt8} field within this {@link RowBuffer}. - * @return the {@code SparseInt8} value read. - */ - public byte readSparseInt8(RowCursor edit) { - // TODO: Remove calls to readSparsePrimitiveTypeCode once moved to V2 read. - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.INT_8); - Item item = this.read(this.buffer::readByte, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseNull} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseNull} field within this {@link RowBuffer}. - * @return the {@code SparseNull} value read. - */ - public NullValue readSparseNull(@Nonnull RowCursor edit) { - - checkNotNull(edit, "expected non-null edit"); - - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.NULL); - edit.endOffset(edit.valueOffset()); - - return NullValue.DEFAULT; - } - - /** - * Read the value of a {@code SparsePath} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparsePath} field within this {@link RowBuffer}. - * @return the {@code SparsePath} value read. - */ - public Utf8String readSparsePath(@Nonnull final RowCursor edit) { - - checkNotNull(edit, "expected non-null edit"); - - final StringTokenizer tokenizer = edit.layout().tokenizer(); - final Optional path = tokenizer.tryFindString(edit.pathToken()); - - if (path.isPresent()) { - return path.get(); - } - - final int length = edit.pathToken() - tokenizer.count(); - Item item = this.read(this::readFixedString, edit.pathOffset(), length); - - return item.value(); - } - - /** - * Read the value of a {@code SparsePathLen} field at the given {@code offset} position. - * - * @param layout layout of the {@code SparsePathLen} field. - * @param offset position of the {@code SparsePathLen} field.. - * @param pathOffset [output] position of the {@code SparsePathLen} field value. - * @param pathLengthInBytes [output] length of the {@code SparsePathLen} field value. - * @return the {@code SparsePathLen} value read. - */ - public int readSparsePathLen( - @Nonnull final Layout layout, - final int offset, - @Nonnull final Out pathOffset, - @Nonnull final Out pathLengthInBytes) { - - checkNotNull(layout, "expected non-null layout"); - checkNotNull(pathOffset, "expected non-null pathOffset"); - checkNotNull(pathLengthInBytes, "expected non-null pathLengthInBytes"); - - final Item item = this.read(this::read7BitEncodedUInt, offset); - final int token = item.value().intValue(); - - if (token < layout.tokenizer().count()) { - pathLengthInBytes.set(item.length()); - pathOffset.set(offset); - return token; - } - - final int numBytes = token - layout.tokenizer().count(); - pathLengthInBytes.set(numBytes + item.length()); - pathOffset.set(offset + item.length()); - - return token; - } - - /** - * Read the value of a {@code SparseString} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseString} field within this {@link RowBuffer}. - * @return the {@code SparseString} value read. - */ - public Utf8String readSparseString(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UTF_8); - Item item = this.read(this::readUtf8String, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseTypeCode} field at the given {@code offset} position. - * - * @param offset position of a {@code SparseTypeCode} field within this {@link RowBuffer}. - * @return the {@code SparseTypeCode} value read. - */ - public LayoutType readSparseTypeCode(int offset) { - byte value = this.readInt8(offset); - LayoutCode code = LayoutCode.from(value); - checkState(code != null, "expected layout code at offset %s, not %s", offset, code); - return LayoutType.fromLayoutCode(code); - } - - /** - * Read the value of a {@code SparseUInt16} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseUInt16} field within this {@link RowBuffer}. - * @return the {@code SparseUInt16} value read. - */ - public int readSparseUInt16(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UINT_16); - Item item = this.read(this.buffer::readUnsignedShortLE, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseUInt32} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseUInt32} field within this {@link RowBuffer}. - * @return the {@code SparseUInt32} value read. - */ - public long readSparseUInt32(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UINT_32); - Item item = this.read(this.buffer::readUnsignedIntLE, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseUInt64} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseUInt64} field within this {@link RowBuffer}. - * @return the {@code SparseUInt64} value read. - */ - public long readSparseUInt64(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UINT_64); - Item item = this.read(this.buffer::readLongLE, edit); - return item.value; - } - - /** - * Read the value of a {@code SparseUInt8} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseUInt8} field within this {@link RowBuffer}. - * @return the {@code SparseUInt8} value read. - */ - public short readSparseUInt8(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UINT_8); - Item item = this.read(this.buffer::readUnsignedByte, edit); - return item.value; - } - - /** - * Read the value of a {@code SparseUnixDateTime} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseUnixDateTime} field within this {@link RowBuffer}. - * @return the {@code SparseUnixDateTime} value read. - */ - public UnixDateTime readSparseUnixDateTime(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UNIX_DATE_TIME); - Item item = this.read(this::readUnixDateTime, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseVarInt} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseVarInt} field within this {@link RowBuffer}. - * @return the {@code SparseVarInt} value read. - */ - public long readSparseVarInt(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.VAR_INT); - Item item = this.read(this::read7BitEncodedInt, edit); - return item.value(); - } - - /** - * Read the value of a {@code SparseVarUInt} field at the given {@link RowCursor edit} position. - * - * @param edit {@link RowCursor edit} position of a {@code SparseVarUInt} field within this {@link RowBuffer}. - * @return the {@code SparseVarUInt} value read. - */ - public long readSparseVarUInt(RowCursor edit) { - this.readSparsePrimitiveTypeCode(edit, LayoutTypes.VAR_UINT); - Item item = this.read(this::read7BitEncodedUInt, edit); - return item.value(); - } - - /** - * Read the value of a {@code UInt16} field at the given {@code offset} position. - * - * @param offset position of a {@code UInt16} field within this {@link RowBuffer}. - * @return the {@code UInt16} value read. - */ - public int readUInt16(int offset) { - Item item = this.read(this.buffer::readUnsignedShortLE, offset); - return item.value(); - } - - /** - * Read the value of a {@code UInt32} field at the given {@code offset} position. - * - * @param offset position of a {@code UInt32} field within this {@link RowBuffer}. - * @return the {@code UInt32} value read. - */ - public long readUInt32(int offset) { - Item item = this.read(this.buffer::readUnsignedIntLE, offset); - return item.value(); - } - - /** - * Read the value of a {@code UInt64} field at the given {@code offset} position. - * - * @param offset position of a {@code UInt64} field within this {@link RowBuffer}. - * @return the {@code UInt64} value read. - */ - public long readUInt64(int offset) { - Item item = this.read(this.buffer::readLongLE, offset); - return item.value(); - } - - /** - * Read the value of a {@code UInt8} field at the given {@code offset} position. - * - * @param offset position of a {@code UInt8} field within this {@link RowBuffer}. - * @return the {@code UInt8} value read. - */ - public short readUInt8(int offset) { - Item item = this.read(this.buffer::readUnsignedByte, offset); - return item.value(); - } - - /** - * Read the value of a {@code UnixDateTime} field at the given {@code offset} position. - * - * @param offset position of a {@code UnixDateTime} field within this {@link RowBuffer}. - * @return the {@code UnixDateTime} value read. - */ - public UnixDateTime readUnixDateTime(int offset) { - Item item = this.read(this::readUnixDateTime, offset); - return item.value(); - } - - /** - * Read the value of a {@code VariableBinary} field at the given {@code offset} position. - * - * @param offset position of a {@code VariableBinary} field within this {@link RowBuffer}. - * @return the {@code VariableBinary} value read. - */ - public ByteBuf readVariableBinary(int offset) { - Item item = this.read(this::readVariableBinary, offset); - return item.value(); - } - - /** - * Read the value of a {@code VariableInt} field at the given {@code offset} position. - * - * @param offset position of a {@code VariableInt} field within this {@link RowBuffer}. - * @return the {@code VariableInt} value read. - */ - public long readVariableInt(int offset) { - Item item = this.read(this::read7BitEncodedInt, offset); - return item.value(); - } - - /** - * Read the value of a {@code VariableString} field at the given {@code offset} position. - * - * @param offset position of a {@code VariableString} field within this {@link RowBuffer}. - * @return the {@code VariableString} value read. - */ - public Utf8String readVariableString(final int offset) { - Item item = this.read(this::readUtf8String, offset); - return item.value(); - } - - /** - * Read the value of a {@code VariableUInt} field at the given {@code offset} position. - * - * @param offset position of a {@code VariableUInt} field within this {@link RowBuffer}. - * @return the {@code VariableUInt} value read. - */ - public long readVariableUInt(final int offset) { - Item item = this.read(this::read7BitEncodedUInt, offset); - return item.value(); - } - - /** - * Read the value of a {@code VariableUInt} field at the given {@code offset} position. - * - * @param offset position of a {@code VariableUInt} field within this {@link RowBuffer}. - * @param length on return, the number of bytes read. - * @return the {@code VariableUInt} value read. - */ - public long readVariableUInt(final int offset, @Nonnull final Out length) { - Item item = this.read(this::read7BitEncodedUInt, offset); - length.set(item.length()); - return item.value(); - } - - /** - * Clears all content from the row. The row is empty after this method. - */ - public void reset() { - this.buffer.clear(); - this.resolver = null; - } - - /** - * The resolver for UDTs. - * - * @return reference to the resolver for UDTs. - */ - public LayoutResolver resolver() { - return this.resolver; - } - - /** - * Rotates the sign bit of a two's complement value to the least significant bit. - * - * @param value A signed value. - * @return An unsigned value encoding the same value but with the sign bit in the LSB. - *

- * Moves the signed bit of a two's complement value to the least significant bit (LSB) by: - *

    - *
  1. If negative, take the two's complement. - *
  2. Left shift the value by 1 bit. - *
  3. If negative, set the LSB to 1. - *
- */ - public static long rotateSignToLsb(long value) { - boolean isNegative = value < 0; - long unsignedValue = value; - unsignedValue = isNegative ? ((~unsignedValue + 1) << 1) + 1 : unsignedValue << 1; - return unsignedValue; - } - - /** - * Undoes the rotation introduced by {@link #rotateSignToLsb}. - * - * @param unsignedValue An unsigned value with the sign bit in the LSB. - * @return A signed two's complement value encoding the same value. - */ - public static long rotateSignToMsb(long unsignedValue) { - boolean isNegative = unsignedValue % 2 != 0; - return isNegative ? (~(unsignedValue >>> 1) + 1) | 0x8000000000000000L : unsignedValue >>> 1; - } - - // TODO: DANOBLE: Support MongoDbObjectId values - // public void WriteMongoDbObjectId(int offset, MongoDbObjectId value) { - // Reference tempReference_value = - // new Reference(value); - // MemoryMarshal.Write(this.buffer.Slice(offset), tempReference_value); - // value = tempReference_value.get(); - // } - - public void setBit(final int offset, @Nonnull final LayoutBit bit) { - checkNotNull(bit, "expected non-null bit"); - if (bit.isInvalid()) { - return; - } - final int index = bit.offset(offset); - this.buffer.setByte(index, this.buffer.getByte(bit.offset(offset)) | (byte) (1 << bit.bit())); - } - - /** - * Move a sparse iterator to the next field within the same sparse scope. - * - * @param edit The iterator to advance. - * - * {@code edit.Path} - * On success, the path of the field at the given offset, otherwise - * undefined. - * - * {@code edit.MetaOffset} - * If found, the offset to the metadata of the field, otherwise a - * location to insert the field. - * - * {@code edit.cellType} - * If found, the layout code of the matching field found, otherwise - * undefined. - * - * {@code edit.ValueOffset} - * If found, the offset to the value of the field, otherwise - * undefined. - *. - * - * @return {@code true} if there is another field; {@code false} if there is not. - */ - public boolean sparseIteratorMoveNext(RowCursor edit) { - - if (edit.cellType() != null) { - // Move to the next element of an indexed scope. - if (edit.scopeType().isIndexedScope()) { - edit.index(edit.index() + 1); - } - - // Skip forward to the end of the current value. - if (edit.endOffset() != 0) { - edit.metaOffset(edit.endOffset()); - edit.endOffset(0); - } else { - edit.metaOffset(edit.metaOffset() + this.sparseComputeSize(edit)); - } - } - - // Check if reached end of buffer - - if (edit.metaOffset() < this.length()) { - - // Check if reached end of sized scope. - - if (!edit.scopeType().isSizedScope() || (edit.index() != edit.count())) { - - this.readSparseMetadata(edit); - - if (!(edit.cellType() instanceof LayoutEndScope)) { - // End of sparse scope - edit.exists(true); - return true; - } - } - } - - edit.cellType(LayoutTypes.END_SCOPE); - edit.exists(false); - edit.valueOffset(edit.metaOffset()); - return false; - } - - /** - * Produce a new scope from the current iterator position. - * - * @param edit An initialized iterator pointing at a scope. - * @param immutable {@code true} if the new scope should be marked immutable (read-only). - * @return A new scope beginning at the current iterator position. - */ - public RowCursor sparseIteratorReadScope(@Nonnull final RowCursor edit, boolean immutable) { - - LayoutTypeScope scopeType = edit.cellType() instanceof LayoutTypeScope ? (LayoutTypeScope) edit.cellType() : null; - - if (scopeType instanceof LayoutObject || scopeType instanceof LayoutArray) { - return new RowCursor() - .scopeType(scopeType) - .scopeTypeArgs(edit.cellTypeArgs()) - .start(edit.valueOffset()) - .valueOffset(edit.valueOffset()) - .metaOffset(edit.valueOffset()) - .layout(edit.layout()) - .immutable(immutable); - } - - if (scopeType instanceof LayoutTypedArray || scopeType instanceof LayoutTypedSet || scopeType instanceof LayoutTypedMap) { - - final int valueOffset = edit.valueOffset() + Integer.BYTES; // Point after the Size - - return new RowCursor() - .scopeType(scopeType) - .scopeTypeArgs(edit.cellTypeArgs()) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(edit.layout()) - .immutable(immutable) - .count(this.readInt32(edit.valueOffset())); - } - - if (scopeType instanceof LayoutTypedTuple || scopeType instanceof LayoutTuple || scopeType instanceof LayoutTagged || scopeType instanceof LayoutTagged2) { - - return new RowCursor() - .scopeType(scopeType) - .scopeTypeArgs(edit.cellTypeArgs()) - .start(edit.valueOffset()) - .valueOffset(edit.valueOffset()) - .metaOffset(edit.valueOffset()) - .layout(edit.layout()) - .immutable(immutable) - .count(edit.cellTypeArgs().count()); - } - - if (scopeType instanceof LayoutNullable) { - - boolean hasValue = this.readInt8(edit.valueOffset()) != 0; - - if (hasValue) { - - // Start at the T so it can be read. - final int valueOffset = edit.valueOffset() + 1; - - return new RowCursor() - .scopeType(scopeType) - .scopeTypeArgs(edit.cellTypeArgs()) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(edit.layout()) - .immutable(immutable) - .count(2) - .index(1); - } else { - - // Start at the end of the scope, instead of at the T, so the T will be skipped. - final TypeArgument typeArg = edit.cellTypeArgs().get(0); - final int valueOffset = edit.valueOffset() + 1 + this.countDefaultValue(typeArg.type(), - typeArg.typeArgs()); - - return new RowCursor() - .scopeType(scopeType) - .scopeTypeArgs(edit.cellTypeArgs()) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(edit.layout()) - .immutable(immutable) - .count(2) - .index(2); - } - } - - if (scopeType instanceof LayoutUDT) { - - final Layout udt = this.resolver.resolve(edit.cellTypeArgs().schemaId()); - final int valueOffset = this.computeVariableValueOffset(udt, edit.valueOffset(), udt.numVariable()); - - return new RowCursor() - .scopeType(scopeType) - .scopeTypeArgs(edit.cellTypeArgs()) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(udt) - .immutable(immutable); - } - - throw new IllegalStateException(lenientFormat("Not a scope type: %s", scopeType)); - } - - public byte[] toArray() { - byte[] content = new byte[this.length()]; - this.buffer.getBytes(0, content); - return content; - } - - public void typedCollectionMoveField( - @Nonnull final RowCursor dstEdit, - @Nonnull final RowCursor srcEdit, - @Nonnull final RowOptions options) { - - final int length = this.sparseComputeSize(srcEdit) - (srcEdit.valueOffset() - srcEdit.metaOffset()); - - // Insert the field metadata into its new location - - Out metaBytes = new Out<>(); - Out spaceNeeded = new Out<>(); - Out shiftInsert = new Out<>(); - Out shiftDelete = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse( - length, dstEdit, srcEdit.cellType(), srcEdit.cellTypeArgs(), options, metaBytes, spaceNeeded, shiftInsert); - this.writeSparseMetadata(dstEdit, srcEdit.cellType(), srcEdit.cellTypeArgs(), metaBytes.get()); - - if (srcEdit.metaOffset() >= dstEdit.metaOffset()) { - srcEdit.metaOffset(srcEdit.metaOffset() + shiftInsert.get()); - srcEdit.valueOffset(srcEdit.valueOffset() + shiftInsert.get()); - } - - // Copy the value bits from the old location - - this.writeFixedBinary(dstEdit.valueOffset(), this.buffer, srcEdit.valueOffset(), length); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shiftInsert.get()); - - // Delete the old location - - this.ensureSparse( - length, srcEdit, srcEdit.cellType(), srcEdit.cellTypeArgs(), RowOptions.DELETE, metaBytes, spaceNeeded, - shiftDelete); - - checkState(shiftDelete.get() < 0); - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shiftDelete.get()); - } - - /** - * Rebuild the unique index for a set/map scope. - * - * @param scope The sparse scope to rebuild an index for. - * @return Success if the index could be built, an error otherwise. - *

- * The {@code scope} MUST be a set or map scope. - *

- * The scope may have been built (e.g. via RowWriter) with relaxed uniqueness constraint checking. - * This operation rebuilds an index to support verification of uniqueness constraints during - * subsequent partial updates. If the appropriate uniqueness constraints cannot be established (i.e. - * a duplicate exists), this operation fails. Before continuing, the resulting scope should either: - *

    - *
  1. Be repaired (e.g. by deleting duplicates) and the index rebuild operation should be run again. - *
  2. Be deleted. The entire scope should be removed including its items. - *
- * Failure to perform one of these actions will leave the row is potentially in a corrupted state where partial - * updates may subsequent fail. - *

- * The target {@code scope} may or may not have already been indexed. This operation is idempotent. - */ - @Nonnull - public Result typedCollectionUniqueIndexRebuild(@Nonnull final RowCursor scope) { - - checkNotNull(scope, "expected non-null scope"); - checkArgument(scope.scopeType().isUniqueScope(), "expected unique scope type"); - checkArgument(scope.index() == 0, "expected scope index of zero"); - - RowCursor edit = scope.clone(); - - if (edit.count() <= 1) { - return Result.SUCCESS; - } - - // Compute index elements - - UniqueIndexItem[] uniqueIndex = new UniqueIndexItem[edit.count()]; - edit.metaOffset(scope.valueOffset()); - - for (; edit.index() < edit.count(); edit.index(edit.index() + 1)) { - - final RowCursor dstEdit = new RowCursor(); - this.readSparseMetadata(dstEdit); - - checkState(edit.pathOffset() == 0); - - final int elementSize = this.sparseComputeSize(edit); - - uniqueIndex[edit.index()] = new UniqueIndexItem() - .code(edit.cellType().layoutCode()) - .metaOffset(edit.metaOffset()) - .valueOffset(edit.valueOffset()) - .size(elementSize); - - edit.metaOffset(edit.metaOffset() + elementSize); - } - - // Create scratch space equal to the sum of the sizes of the scope's values. - // Implementation Note: theoretically this scratch space could be eliminated by - // performing the item move operations directly during the Insertion Sort, however, - // doing so might result in moving the same item multiple times. Under the assumption - // that items are relatively large, using scratch space requires each item to be moved - // AT MOST once. Given that row buffer memory is likely reused, scratch space is - // relatively memory efficient. - - int shift = edit.metaOffset() - scope.valueOffset(); - - // Sort and check for duplicates - - if (!this.insertionSort(scope, edit, Arrays.asList(uniqueIndex))) { - return Result.EXISTS; - } - - // Move elements - - int metaOffset = scope.valueOffset(); - this.ensure(this.length() + shift); - this.writeFixedBinary(metaOffset + shift, this.buffer, metaOffset, this.length() - metaOffset); - - for (UniqueIndexItem item : uniqueIndex) { - this.writeFixedBinary(metaOffset, this.buffer, item.metaOffset() + shift, item.size()); - metaOffset += item.size(); - } - - // Delete the scratch space (if necessary - if it doesn't just fall off the end of the row) - - if (metaOffset != this.length()) { - this.writeFixedBinary(metaOffset, this.buffer, metaOffset + shift, this.length() - metaOffset); - } - - return Result.SUCCESS; - } - - public void unsetBit(final int offset, @Nonnull final LayoutBit bit) { - checkNotNull(bit, "expected non-null bit"); - checkArgument(!bit.isInvalid()); - final int index = bit.offset(offset); - this.buffer.setByte(index, this.buffer.getByte(index) & (byte) ~(1 << bit.bit())); - } - - public int write7BitEncodedInt(final long value) { - return this.write7BitEncodedUInt(RowBuffer.rotateSignToLsb(value)); - } - - /** - * Sets the specified 64-bit integer at the current {@link RowBuffer position} as a 7-bit encoded 32-bit value. - *

- * The 64-bit integer value is written 7-bits at a time. The high bit of the byte, when set, indicates there are - * more bytes. An {@link IllegalArgumentException} is thrown, if the specified 64-bit integer value is outside - * the range of an unsigned 32-bit integer, [0, 0x00000000FFFFFFFFL]. - * - * @param value a 64-bit integer constrained to the range of an unsigned 32-bit integer, [0, 0x00000000FFFFFFFFL] - * @return The number of bytes written - */ - public int write7BitEncodedUInt(final long value) { - checkArgument(0 <= value && value <= 0x00000000FFFFFFFFL, "expected value in range [0, %s], not %s", 0x00000000FFFFFFFFL, value); - long n = value; - int i = 0; - while (n >= 0x80L) { - this.buffer.writeByte((byte) (n | 0x80L)); - n >>>= 7; - } - this.buffer.writeByte((byte) n); - return i; - } - - public void writeDateTime(int offset, OffsetDateTime value) { - Item item = this.write(this::writeDateTime, offset, value); - } - - public void writeDecimal(int offset, BigDecimal value) { - Item item = this.write(this::writeDecimal, offset, value); - } - - public void writeFixedBinary(final int offset, @Nonnull final ByteBuf value, final int length) { - - checkNotNull(value, "expected non-null value"); - checkArgument(offset >= 0, "expected offset >= 0, not %s", offset); - checkArgument(length >= 0, "expected length >= 0, not %s", length); - - Item item = this.write(buffer -> { - int writableBytes = Math.min(length, buffer.readableBytes()); - this.buffer.writeBytes(buffer, writableBytes); - if (writableBytes < length) { - this.buffer.writeZero(length - writableBytes); - } - }, offset, value); - } - - public void writeFixedBinary(final int offset, @Nonnull final ByteBuf value, final int index, final int length) { - checkArgument(index >= 0, "expected index >= 0, not %s", index); - value.markReaderIndex().readerIndex(index); - this.writeFixedBinary(offset, value, length); - value.resetReaderIndex(); - } - - public void writeFixedBinary(final int offset, @Nonnull final byte[] value, final int index, final int length) { - - checkNotNull(value, "expected non-null value"); - checkArgument(offset >= 0, "expected offset >= 0, not %s", offset); - checkArgument(length >= 0, "expected length >= 0, not %s", length); - checkArgument(0 <= index && index < value.length, "expected in range [0, %s), not index", index); - - Item item = this.write(buffer -> { - int writableBytes = Math.min(length, buffer.length - index); - this.buffer.writeBytes(buffer, index, writableBytes); - if (writableBytes < length) { - this.buffer.writeZero(length - writableBytes); - } - }, offset, value); - } - - public void writeFixedString(final int offset, @Nonnull final Utf8String value) { - checkNotNull(value, "expected non-null value"); - checkArgument(!value.isNull(), "expected non-null value content"); - Item item = this.write(this::writeFixedString, offset, value); - } - - public void writeFloat128(int offset, Float128 value) { - this.buffer.writeLongLE(value.low()); - this.buffer.writeLongLE(value.high()); - } - - public void writeFloat32(final int offset, final float value) { - Item item = this.write(this.buffer::writeFloatLE, offset, value); - } - - public void writeFloat64(final int offset, final double value) { - Item item = this.write(this.buffer::writeDoubleLE, offset, value); - } - - public void writeGuid(final int offset, @Nonnull final UUID value) { - checkNotNull(value, "expected non-null value"); - Item item = this.write(this::writeGuid, offset, value); - } - - public void writeHeader(HybridRowHeader value) { - this.buffer.writeByte(value.version().value()); - this.buffer.writeIntLE(value.schemaId().value()); - } - - public void writeInt16(final int ignored, final short value) { - this.buffer.writeShortLE(value); - } - - public void writeInt32(final int ignored, final int value) { - this.buffer.writeIntLE(value); - } - - public void writeInt64(final int ignored, final long value) { - this.buffer.writeLongLE(value); - } - - public void writeInt8(final int ignored, final byte value) { - this.buffer.writeByte(value); - } - - @Nonnull - public RowCursor writeNullable( - @Nonnull final RowCursor edit, - @Nonnull final LayoutTypeScope scope, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - boolean hasValue) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - - final int length = this.countDefaultValue(scope, typeArgs); - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - - final int numWritten = this.writeDefaultValue(edit.valueOffset(), scope, typeArgs); - checkState(length == numWritten); - - if (hasValue) { - this.writeInt8(edit.valueOffset(), (byte) 1); - } - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - final int valueOffset = edit.valueOffset() + 1; - - RowCursor newScope = new RowCursor() - .scopeType(scope) - .scopeTypeArgs(typeArgs) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(edit.layout()) - .count(2) - .index(1); - - RowCursors.moveNext(newScope, this); - return newScope; - } - - public void writeSchemaId(final int offset, @Nonnull final SchemaId value) { - checkNotNull(value, "expected non-null value"); - this.writeInt32(offset, value.value()); - } - - @Nonnull - public RowCursor writeSparseArray( - @Nonnull final RowCursor edit, @Nonnull final LayoutTypeScope scope, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(options, "expected non-null options"); - - int length = LayoutCode.BYTES; - TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - this.writeSparseTypeCode(edit.valueOffset(), LayoutCode.END_SCOPE); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - return new RowCursor() - .scopeType(scope) - .scopeTypeArgs(typeArgs) - .start(edit.valueOffset()) - .valueOffset(edit.valueOffset()) - .metaOffset(edit.valueOffset()) - .layout(edit.layout()); - } - - public void writeSparseBinary( - @Nonnull final RowCursor edit, @Nonnull final ByteBuf value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - checkNotNull(options, "expected non-null options"); - - final int length = RowBuffer.count7BitEncodedUInt(value.readableBytes()) + value.readableBytes(); - final LayoutType type = LayoutTypes.BINARY; - - final Out metaBytes = new Out<>(); - final Out shift = new Out<>(); - final Out spaceNeeded = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeVariableBinary(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseBoolean( - @Nonnull final RowCursor edit, final boolean value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final int length = 0; - final LayoutType type = value ? LayoutTypes.BOOLEAN : LayoutTypes.BOOLEAN_FALSE; - final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, typeArgs, metaBytes.get()); - - checkState(spaceNeeded.get() == (int) metaBytes.get()); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseDateTime( - @Nonnull final RowCursor edit, @Nonnull final OffsetDateTime value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - checkNotNull(options, "expected non-null options"); - - LayoutType type = LayoutTypes.DATE_TIME; - int length = DateTimeCodec.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeDateTime(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseDecimal( - @Nonnull final RowCursor edit, @Nonnull final BigDecimal value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.DECIMAL; - final int length = DecimalCodec.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeDecimal(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseFloat128( - @Nonnull final RowCursor edit, @Nonnull final Float128 value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.FLOAT_128; - final int length = Float128.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeFloat128(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseFloat32(@Nonnull RowCursor edit, float value, @Nonnull UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.FLOAT_32; - final int length = Float.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeFloat32(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseFloat64(@Nonnull final RowCursor edit, double value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.FLOAT_64; - final int length = Double.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeFloat64(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseGuid( - @Nonnull final RowCursor edit, @Nonnull final UUID value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.GUID; - final int length = GuidCodec.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeGuid(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseInt16(@Nonnull final RowCursor edit, short value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.INT_16; - final int length = Short.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeInt16(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseInt32(@Nonnull final RowCursor edit, int value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.INT_32; - final int length = Integer.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeInt32(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseInt64(@Nonnull final RowCursor edit, long value, UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.INT_64; - final int length = Long.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeInt64(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseInt8(@Nonnull final RowCursor edit, byte value, UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final int length = Long.BYTES; - final LayoutType type = LayoutTypes.INT_8; - final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, typeArgs, metaBytes.get()); - this.writeInt8(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseNull( - @Nonnull final RowCursor edit, @Nonnull final NullValue value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final int length = 0; - final LayoutType type = LayoutTypes.NULL; - final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, typeArgs, metaBytes.get()); - - checkState(spaceNeeded.get() == (int)metaBytes.get()); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public RowCursor writeSparseObject( - @Nonnull final RowCursor edit, - @Nonnull final LayoutTypeScope scope, - @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(options, "expected non-null options"); - - int length = LayoutCode.BYTES; // end scope type code. - TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - this.writeSparseTypeCode(edit.valueOffset(), LayoutCode.END_SCOPE); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - return new RowCursor() - .scopeType(scope) - .scopeTypeArgs(TypeArgumentList.EMPTY) - .start(edit.valueOffset()) - .valueOffset(edit.valueOffset()) - .metaOffset(edit.valueOffset()) - .layout(edit.layout()); - } - - public void writeSparseString( - @Nonnull final RowCursor edit, @Nonnull final Utf8String value, @Nonnull final UpdateOptions options) { - - final LayoutType type = LayoutTypes.UTF_8; - final TypeArgumentList args = TypeArgumentList.EMPTY; - final int length = RowBuffer.count7BitEncodedUInt(value.encodedLength()) + value.encodedLength(); - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, args, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, args, metaBytes.get()); - this.write(this::writeVariableString, edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - @Nonnull - public RowCursor writeSparseTuple( - @Nonnull final RowCursor edit, - @Nonnull final LayoutTypeScope scope, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options) { - - int length = LayoutCode.BYTES * (1 + typeArgs.count()); // nulls for each element - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - - int valueOffset = edit.valueOffset(); - - for (int i = 0; i < typeArgs.count(); i++) { - this.writeSparseTypeCode(valueOffset, LayoutCode.NULL); - valueOffset += LayoutCode.BYTES; - } - - this.writeSparseTypeCode(valueOffset, LayoutCode.END_SCOPE); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - return new RowCursor() - .scopeType(scope) - .scopeTypeArgs(typeArgs) - .start(edit.valueOffset()) - .valueOffset(edit.valueOffset()) - .metaOffset(edit.valueOffset()) - .layout(edit.layout()) - .count(typeArgs.count()); - } - - public void writeSparseTypeCode(int offset, LayoutCode code) { - this.writeUInt8(offset, code.value()); - } - - @Nonnull - public RowCursor writeSparseUDT( - @Nonnull final RowCursor edit, - @Nonnull final LayoutTypeScope scope, - @Nonnull final Layout udt, - @Nonnull final UpdateOptions options) { - - TypeArgumentList typeArgs = new TypeArgumentList(udt.schemaId()); - int length = udt.size() + LayoutCode.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - this.write(this.buffer::writeZero, edit.valueOffset(), udt.size()); // clear all presence bits - - // Write scope terminator - - int valueOffset = edit.valueOffset() + udt.size(); - this.writeSparseTypeCode(valueOffset, LayoutCode.END_SCOPE); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - return new RowCursor() - .scopeType(scope) - .scopeTypeArgs(typeArgs) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(udt); - } - - public void writeSparseUInt16( - @Nonnull final RowCursor edit, final short value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final int length = Short.BYTES; - final LayoutType type = LayoutTypes.UINT_16; - final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeUInt16(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseUInt32( - @Nonnull final RowCursor edit, final int value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final int length = Integer.BYTES; - final LayoutType type = LayoutTypes.UINT_32; - final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeUInt32(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseUInt64(@Nonnull final RowCursor edit, long value, @Nonnull UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final int length = Long.BYTES; - final LayoutType type = LayoutTypes.UINT_64; - final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeUInt64(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseUInt8( - @Nonnull final RowCursor edit, final byte value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.UINT_8; - final int length = Byte.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeUInt8(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseUnixDateTime( - @Nonnull final RowCursor edit, @Nonnull final UnixDateTime value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - checkNotNull(options, "expected non-null options"); - - LayoutType type = LayoutTypes.UNIX_DATE_TIME; - final int length = UnixDateTime.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.writeUnixDateTime(edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseVarInt(@Nonnull final RowCursor edit, final long value, @Nonnull final UpdateOptions options) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(options, "expected non-null options"); - - final LayoutType type = LayoutTypes.VAR_INT; - final int length = RowBuffer.count7BitEncodedInt(value); - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); - this.write(this::write7BitEncodedInt, edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - public void writeSparseVarUInt( - @Nonnull final RowCursor edit, final long value, @Nonnull final UpdateOptions options) { - - final LayoutType type = LayoutTypes.VAR_UINT; - final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; - final int length = RowBuffer.count7BitEncodedUInt(value); - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, type, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, type, typeArgs, metaBytes.get()); - this.write(this::write7BitEncodedUInt, edit.valueOffset(), value); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - edit.endOffset(edit.metaOffset() + spaceNeeded.get()); - } - - /** - * Writes the content of the buffer on to an {@link OutputStream}. - * - * @param stream the target @{link OutputStream} - * @throws IOException if the specified {@code stream} throws an {@link IOException} during output - */ - public void writeTo(@Nonnull final OutputStream stream) throws IOException { - checkNotNull(stream, "expected non-null stream"); - this.buffer.getBytes(0, stream, this.length()); - } - - @Nonnull - public RowCursor writeTypedArray( - @Nonnull final RowCursor edit, - @Nonnull final LayoutTypeScope scope, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options) { - - final int length = Integer.BYTES; - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - this.writeUInt32(edit.valueOffset(), 0); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - int valueOffset = edit.valueOffset() + Integer.BYTES; // point after the size - - return new RowCursor() - .scopeType(scope) - .scopeTypeArgs(typeArgs) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(edit.layout()); - } - - @Nonnull - public RowCursor writeTypedMap( - @Nonnull final RowCursor edit, - @Nonnull final LayoutTypeScope scope, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options) { - - final int length = Integer.BYTES; // sized scope - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - this.writeUInt32(edit.valueOffset(), 0); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - int valueOffset = edit.valueOffset() + Integer.BYTES; // point after the size - - return new RowCursor() - .scopeType(scope) - .scopeTypeArgs(typeArgs) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(edit.layout()); - } - - @Nonnull - public RowCursor writeTypedSet( - @Nonnull final RowCursor edit, - @Nonnull final LayoutTypeScope scope, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options) { - - final int length = Integer.BYTES; - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - this.writeUInt32(edit.valueOffset(), 0); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - final int valueOffset = edit.valueOffset() + Integer.BYTES; // point after the size - - return new RowCursor() - .scopeType(scope) - .scopeTypeArgs(typeArgs) - .start(edit.valueOffset()) - .valueOffset(valueOffset) - .metaOffset(valueOffset) - .layout(edit.layout()); - } - - public RowCursor writeTypedTuple( - @Nonnull final RowCursor edit, - @Nonnull final LayoutTypeScope scope, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options) { - - final int length = this.countDefaultValue(scope, typeArgs); - - final Out metaBytes = new Out<>(); - final Out spaceNeeded = new Out<>(); - final Out shift = new Out<>(); - - final int priorLength = this.length(); - - this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); - this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); - - final int numWritten = this.writeDefaultValue(edit.valueOffset(), scope, typeArgs); - checkState(length == numWritten); - - checkState(spaceNeeded.get() == metaBytes.get() + length); - checkState(this.length() == priorLength + shift.get()); - - final RowCursor newScope = new RowCursor() - .scopeType(scope) - .scopeTypeArgs(typeArgs) - .start(edit.valueOffset()) - .valueOffset(edit.valueOffset()) - .metaOffset(edit.valueOffset()) - .layout(edit.layout()) - .count(typeArgs.count()); - - RowCursors.moveNext(newScope, this); - return newScope; - } - - public void writeUInt16(final int offset, final short value) { - final Item item = this.write(this::writeUInt16, offset, value); - } - - public void writeUInt32(final int offset, final int value) { - final Item item = this.write(this::writeUInt32, offset, value); - } - - public void writeUInt64(final int offset, final long value) { - final Item item = this.write(this::writeUInt64, offset, value); - } - - public void writeUInt8(int offset, byte value) { - final Item item = this.write(this::writeUInt8, offset, value); - } - - public void writeUnixDateTime(int offset, UnixDateTime value) { - final Item item = this.write(this::writeUInt64, offset, value.milliseconds()); - } - - public void writeVariableBinary( - final int offset, @Nonnull final ByteBuf value, final boolean exists, @Nonnull final Out shift) { - - checkNotNull(value, "expected non-null value"); - checkNotNull(shift, "expected non-null shift"); - - final int length = value.readableBytes(); - final Out spaceNeeded = new Out<>(); - - final int priorLength = this.length(); - - this.ensureVariable(offset, false, length, exists, spaceNeeded, shift); - final Item item = this.write(this::writeVariableBinary, offset, value); - - checkState(spaceNeeded.get() == length + item.length()); - checkState(this.length() == priorLength + shift.get()); - } - - public int writeVariableInt(int offset, long value, boolean exists) { - - final int length = RowBuffer.count7BitEncodedInt(value); - final Out shift = new Out<>(); - final Out spaceNeeded = new Out<>(); - - final int priorLength = this.length(); - - this.ensureVariable(offset, true, length, exists, spaceNeeded, shift); - final Item item = this.write(this::write7BitEncodedInt, offset, value); - - checkState(item.length == length); - checkState(spaceNeeded.get() == length); - checkState(this.length() == priorLength + shift.get()); - - return shift.get(); - } - - public int writeVariableString( - final int offset, @Nonnull final Utf8String value, final boolean exists) { - - checkNotNull(value, "expected non-null value"); - checkArgument(!value.isNull(), "expected non-null value content"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - final int length = value.encodedLength(); - final Out shift = new Out<>(); - final Out spaceNeeded = new Out<>(); - - final int priorLength = this.length(); - - this.ensureVariable(offset, false, length, exists, spaceNeeded, shift); - Item item = this.write(this::writeVariableString, offset, value); - - checkState(spaceNeeded.get() == length + item.length()); - checkState(this.length() == priorLength + shift.get()); - - return shift.get(); - } - - public int writeVariableUInt(final int offset, final long value) { - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - Item item = this.write(this::write7BitEncodedUInt, offset, value); - return item.length(); - } - - public int writeVariableUInt(final int offset, final long value, final boolean exists) { - - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - final int length = RowBuffer.count7BitEncodedUInt(value); - final Out shift = new Out<>(); - final Out spaceNeeded = new Out<>(); - - final int priorLength = this.length(); - - this.ensureVariable(offset, true, length, exists, spaceNeeded, shift); - final Item item = this.write(this::write7BitEncodedUInt, offset, value); - - checkState(item.length == length); - checkState(spaceNeeded.get() == length); - checkState(this.length() == priorLength + shift.get()); - - return shift.get(); - } - - /** - * Compares the values of two encoded fields using the hybrid row binary collation. - * - * @param left An edit describing the left field. - * @param leftLength The size of the left field's value in bytes. - * @param right An edit describing the right field. - * @param rightLength The size of the right field's value in bytes. - * @return - * - * -1left less than right. - * - * 0left and right are equal. - * - * 1left is greater than right. - * - * - */ - private int compareFieldValue( - @Nonnull final RowCursor left, final int leftLength, @Nonnull final RowCursor right, final int rightLength) { - - checkNotNull(left, "expected non-null left"); - checkNotNull(right, "expected non-null right"); - checkArgument(leftLength >= 0, "expected non-negative leftLength"); - checkArgument(rightLength >= 0, "expected non-negative rightLength"); - - if (left.cellType().layoutCode().value() < right.cellType().layoutCode().value()) { - return -1; - } - - if (left.cellType() != right.cellType()) { - return 1; - } - - if (leftLength > rightLength) { - return 1; - } - - if (leftLength < rightLength) { - return -1; - } - - ByteBuf sliceLeft = this.buffer.slice(left.valueOffset(), leftLength); - ByteBuf sliceRight = this.buffer.slice(right.valueOffset(), rightLength); - - return sliceLeft.compareTo(sliceRight); - } - - /** - * Compares the values of two encoded key-value pair fields using the hybrid row binary. - * collation. - * - * @param left An edit describing the left field. - * @param right An edit describing the right field. - * @return - * - * -1left less than right. - * - * 0left and right are equal. - * - * 1left is greater than right. - * - * - */ - private int compareKeyValueFieldValue(RowCursor left, RowCursor right) { - - LayoutTypedTuple leftScopeType = left.cellType() instanceof LayoutTypedTuple - ? (LayoutTypedTuple) left.cellType() - : null; - LayoutTypedTuple rightScopeType = right.cellType() instanceof LayoutTypedTuple - ? (LayoutTypedTuple) right.cellType() - : null; - - checkArgument(leftScopeType != null); - checkArgument(rightScopeType != null); - checkArgument(left.cellTypeArgs().count() == 2); - checkArgument(left.cellTypeArgs().equals(right.cellTypeArgs())); - - RowCursor leftKey = new RowCursor(); - leftKey.layout(left.layout()); - leftKey.scopeType(leftScopeType); - leftKey.scopeTypeArgs(left.cellTypeArgs()); - leftKey.start(left.valueOffset()); - leftKey.metaOffset(left.valueOffset()); - leftKey.index(0); - - this.readSparseMetadata(leftKey); - checkState(leftKey.pathOffset() == 0); - int leftKeyLen = this.sparseComputeSize(leftKey) - (leftKey.valueOffset() - leftKey.metaOffset()); - - RowCursor rightKey = new RowCursor(); - rightKey.layout(right.layout()); - rightKey.scopeType(rightScopeType); - rightKey.scopeTypeArgs(right.cellTypeArgs()); - rightKey.start(right.valueOffset()); - rightKey.metaOffset(right.valueOffset()); - rightKey.index(0); - - this.readSparseMetadata(rightKey); - checkState(rightKey.pathOffset() == 0); - int rightKeyLen = this.sparseComputeSize(rightKey) - (rightKey.valueOffset() - rightKey.metaOffset()); - - return this.compareFieldValue(leftKey, leftKeyLen, rightKey, rightKeyLen); - } - - /** - * Compute the number of bytes necessary to store the signed integer using the varint encoding. - * - * @param value The value to be encoded - * @return The number of bytes needed to store the varint encoding of {@code value} - */ - private static int count7BitEncodedInt(long value) { - return RowBuffer.count7BitEncodedUInt(RowBuffer.rotateSignToLsb(value)); - } - - /** - * Return the size (in bytes) of the default sparse value for the type. - * - * @param code The type of the default value. - * @param typeArgs - */ - private int countDefaultValue(LayoutType code, TypeArgumentList typeArgs) { - - // TODO: JTH: convert to a virtual? - - if (code instanceof LayoutNull || code instanceof LayoutBoolean) { - return 1; - } - if (code instanceof LayoutInt8) { - return LayoutTypes.INT_8.size(); - } - if (code instanceof LayoutInt16) { - return LayoutTypes.INT_16.size(); - } - if (code instanceof LayoutInt32) { - return LayoutTypes.INT_32.size(); - } - if (code instanceof LayoutInt64) { - return LayoutTypes.INT_64.size(); - } - if (code instanceof LayoutUInt8) { - return LayoutTypes.UINT_8.size(); - } - if (code instanceof LayoutUInt16) { - return LayoutTypes.UINT_16.size(); - } - if (code instanceof LayoutUInt32) { - return LayoutTypes.UINT_32.size(); - } - if (code instanceof LayoutUInt64) { - return LayoutTypes.UINT_64.size(); - } - if (code instanceof LayoutFloat32) { - return LayoutTypes.FLOAT_32.size(); - } - if (code instanceof LayoutFloat64) { - return LayoutTypes.FLOAT_64.size(); - } - if (code instanceof LayoutFloat128) { - return LayoutTypes.FLOAT_128.size(); - } - if (code instanceof LayoutDecimal) { - return LayoutTypes.DECIMAL.size(); - } - if (code instanceof LayoutDateTime) { - return LayoutTypes.DATE_TIME.size(); - } - if (code instanceof LayoutUnixDateTime) { - return LayoutTypes.UNIX_DATE_TIME.size(); - } - if (code instanceof LayoutGuid) { - return LayoutTypes.GUID.size(); - } - if (code instanceof LayoutMongoDbObjectId) { - // return MongoDbObjectId.size(); - throw new UnsupportedOperationException(); - } - if (code instanceof LayoutUtf8 || code instanceof LayoutBinary || code instanceof LayoutVarInt || code instanceof LayoutVarUInt) { - // Variable length types preceded by their varuint size take 1 byte for a size of 0. - return 1; - } - if (code instanceof LayoutObject || code instanceof LayoutArray) { - // Variable length sparse collection scopes take 1 byte for the end-of-scope terminator. - return LayoutCode.BYTES; - } - if (code instanceof LayoutTypedArray || code instanceof LayoutTypedSet || code instanceof LayoutTypedMap) { - // Variable length typed collection scopes preceded by their scope size take sizeof(uint) for a size of 0. - return Integer.BYTES; - } - if (code instanceof LayoutTuple) { - // Fixed arity sparse collections take 1 byte for end-of-scope plus a null for each element. - return LayoutCode.BYTES + (LayoutCode.BYTES * typeArgs.count()); - } - if (code instanceof LayoutTypedTuple || code instanceof LayoutTagged || code instanceof LayoutTagged2) { - // Fixed arity typed collections take the sum of the default values of each element. The scope size is - // implied by the arity. - return typeArgs.stream() - .map(arg -> this.countDefaultValue(arg.type(), arg.typeArgs())) - .reduce(0, Integer::sum); - } - if (code instanceof LayoutNullable) { - // Nullables take the default values of the value plus null. The scope size is implied by the arity. - return 1 + this.countDefaultValue(typeArgs.get(0).type(), typeArgs.get(0).typeArgs()); - } - if (code instanceof LayoutUDT) { - Layout udt = this.resolver.resolve(typeArgs.schemaId()); - return udt.size() + LayoutCode.BYTES; - } - throw new IllegalStateException(lenientFormat("Not Implemented: %s", code)); - } - - private static int countSparsePath(@Nonnull final RowCursor edit) { - - if (!edit.writePathToken().isNull()) { - StringToken token = edit.writePathToken(); - ByteBuf varint = token.varint(); - return varint.readerIndex() + varint.readableBytes(); - } - - Optional optional = edit.layout().tokenizer().tryFindToken(edit.writePath()); - - if (optional.isPresent()) { - StringToken token = optional.get(); - edit.writePathToken(token); - ByteBuf varint = token.varint(); - return varint.readerIndex() + varint.readableBytes(); - } - - Utf8String path = edit.writePath().toUtf8(); - assert path != null; - - int numBytes = path.length(); - int sizeLenInBytes = RowBuffer.count7BitEncodedUInt(edit.layout().tokenizer().count() + numBytes); - - return sizeLenInBytes + numBytes; - } - - private void ensure(int size) { - this.buffer.ensureWritable(size); - } - - /** - * Ensure that sufficient space exists in the row buffer to write the specified value. - * - * @param length The number of bytes needed to encode the value of the field to be written. - * @param edit The prepared edit indicating where and in what context the current write will happen. - * @param type The type of the field to be written. - * @param typeArgs The type arguments of the field to be written. - * @param options The kind of edit to be performed. - * @param metaBytes On success, the number of bytes needed to encode the metadata of the new field. - * @param spaceNeeded On success, the number of bytes needed in total to encode the new field and its metadata. - * @param shift On success, the number of bytes the length of the row buffer was increased. - */ - private void ensureSparse( - final int length, - @Nonnull final RowCursor edit, - @Nonnull final LayoutType type, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final RowOptions options, - @Nonnull final Out metaBytes, - @Nonnull final Out spaceNeeded, - @Nonnull final Out shift - ) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(type, "expected non-null type"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - checkNotNull(metaBytes, "expected non-null metaBytes"); - checkNotNull(spaceNeeded, "expected non-null spaceNeeded"); - checkNotNull(shift, "expected non-null shift"); - - int metaOffset = edit.metaOffset(); - int spaceAvailable = 0; - - // Compute the metadata offsets - - if (edit.scopeType().hasImplicitTypeCode(edit)) { - metaBytes.set(0); - } else { - metaBytes.set(type.countTypeArgument(typeArgs)); - } - - if (!edit.scopeType().isIndexedScope()) { - checkState(edit.writePath() != null); - int pathLenInBytes = RowBuffer.countSparsePath(edit); - metaBytes.set(metaBytes.get() + pathLenInBytes); - } - - if (edit.exists()) { - // Compute value offset for existing value to be overwritten - spaceAvailable = this.sparseComputeSize(edit); - } - - spaceNeeded.set(options == RowOptions.DELETE ? 0 : metaBytes.get() + length); - shift.set(spaceNeeded.get() - spaceAvailable); - - // Shift the contents of the buffer tail left or right as required to snugly fit the specified value - - final int destination = metaOffset + spaceNeeded.get(); - final int source = metaOffset + spaceAvailable; - - this.shift(destination, source, this.length() - (metaOffset + spaceAvailable)); - - // Update the stored size (fixed arity scopes don't store the size because it is implied by the type args) - - if (edit.scopeType().isSizedScope() && !edit.scopeType().isFixedArity()) { - - if ((options == RowOptions.INSERT) || (options == RowOptions.INSERT_AT) || ((options == RowOptions.UPSERT) && !edit.exists())) { - // Add one to the current scope count - checkState(!edit.exists()); - this.incrementUInt32(edit.start(), 1); - edit.count(edit.count() + 1); - } else if ((options == RowOptions.DELETE) && edit.exists()) { - // Subtract one from the current scope count - checkState(this.readUInt32(edit.start()) > 0); - this.decrementUInt32(edit.start(), 1); - edit.count(edit.count() - 1); - } - } - - if (options == RowOptions.DELETE) { - edit.cellType(null); - edit.cellTypeArgs(null); - edit.exists(false); - } else { - edit.cellType(type); - edit.cellTypeArgs(typeArgs); - edit.exists(true); - } - } - - /** - * Ensure that sufficient space exists in the row buffer to write the specified value. - * - * @param length The number of bytes needed to encode the value of the field to be written. - * @param edit The prepared edit indicating where and in what context the current write will happen. - * @param type The type of the field to be written. - * @param typeArgs The type arguments of the field to be written. - * @param options The kind of edit to be performed. - * @param metaBytes On success, the number of bytes needed to encode the metadata of the new field. - * @param spaceNeeded On success, the number of bytes needed in total to encode the new field and its metadata. - * @param shift On success, the number of bytes the length of the row buffer was increased. - */ - private void ensureSparse( - final int length, - @Nonnull final RowCursor edit, - @Nonnull final LayoutType type, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - @Nonnull final Out metaBytes, - @Nonnull final Out spaceNeeded, - @Nonnull final Out shift - ) { - checkNotNull(options, "expected non-null options"); - this.ensureSparse(length, edit, type, typeArgs, RowOptions.from(options.value()), metaBytes, spaceNeeded, shift); - } - - private void ensureVariable( - final int offset, - final boolean isVarint, - final int length, - final boolean exists, - @Nonnull final Out spaceNeeded, - @Nonnull final Out shift) { - - int spaceAvailable = 0; - long existingValueBytes = exists ? 0 : this.read7BitEncodedUInt(offset); - - if (isVarint) { - spaceNeeded.set(length); - } else { - assert existingValueBytes <= Integer.MAX_VALUE; - spaceAvailable += (int) existingValueBytes; - spaceNeeded.set(length + RowBuffer.count7BitEncodedUInt(length)); - } - - shift.set(spaceNeeded.get() - spaceAvailable); - - if (shift.get() != 0) { - final int destination = offset + spaceNeeded.get(); - final int source = offset + spaceAvailable; - this.shift(destination, source, this.length() - (offset + spaceAvailable)); - } - } - - /** - * Sorts a {@code uniqueIndex} list using the hybrid row binary collation. - * - * @param scope The scope to be sorted. - * @param edit A edit that points at the scope. - * @param uniqueIndex A unique index array structure that identifies the row offsets of each - * element in the scope. - * @return true if the array was sorted, false if a duplicate was found during sorting. - *

- * Implementation Note: - *

This method MUST guarantee that if at least one duplicate exists it will be found.

- * Insertion Sort is used for this purpose as it guarantees that each value is eventually compared - * against its previous item in sorted order. If any two successive items are the same they must be - * duplicates. - *

- * Other search algorithms, such as Quick Sort or Merge Sort, may offer fewer comparisons in the - * limit but don't necessarily guarantee that duplicates will be discovered. If an alternative - * algorithm is used, then an independent duplicate pass MUST be employed. - *

- *

- * Under the current operational assumptions, the expected cardinality of sets and maps is - * expected to be relatively small. If this assumption changes, Insertion Sort may no longer be the - * best choice. - *

- */ - private boolean insertionSort( - @Nonnull final RowCursor scope, - @Nonnull final RowCursor edit, - @Nonnull final List uniqueIndex) { - - checkNotNull(scope, "expected non-null scope"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(uniqueIndex, "expected non-null uniqueIndex"); - - RowCursor leftEdit = edit.clone(); - RowCursor rightEdit = edit.clone(); - - for (int i = 1; i < uniqueIndex.size(); i++) { - - UniqueIndexItem x = uniqueIndex.get(i); - leftEdit.cellType(LayoutType.fromLayoutCode(x.code())); - leftEdit.metaOffset(x.metaOffset()); - leftEdit.valueOffset(x.valueOffset()); - - final int leftBytes = x.size() - (x.valueOffset() - x.metaOffset()); - - // Walk backwards searching for the insertion point for the item as position i. - int j; - for (j = i - 1; j >= 0; j--) { - UniqueIndexItem y = uniqueIndex.get(j); - rightEdit.cellType(LayoutType.fromLayoutCode(y.code())); - rightEdit.metaOffset(y.metaOffset()); - rightEdit.valueOffset(y.valueOffset()); - - int cmp; - if (scope.scopeType() instanceof LayoutTypedMap) { - cmp = this.compareKeyValueFieldValue(leftEdit.clone(), rightEdit.clone()); - } else { - int rightBytes = y.size() - (y.valueOffset() - y.metaOffset()); - cmp = this.compareFieldValue(leftEdit.clone(), leftBytes, rightEdit.clone(), rightBytes); - } - - // If there are duplicates then fail. - if (cmp == 0) { - return false; - } - - if (cmp > 0) { - break; - } - - // Swap the jth item to the right to make space for the ith item which is smaller. - uniqueIndex.set(j + 1, uniqueIndex.get(j)); - } - - // Insert the ith item into the sorted array. - uniqueIndex.set(j + 1, x); - } - - return true; - } - - private Item read(@Nonnull final Supplier reader, @Nonnull final RowCursor cursor) { - - checkNotNull(reader, "expected non-null reader"); - checkNotNull(cursor, "expected non-null cursor"); - - Item item = this.read(reader, cursor.valueOffset()); - cursor.endOffset(this.buffer.readerIndex()); - - return item; - } - - private Item read(@Nonnull final Supplier reader, final int offset) { - - checkNotNull(reader, "expected non-null reader"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - this.buffer.readerIndex(offset); - final T value = reader.get(); - - return Item.of(value, offset, this.buffer.readerIndex() - offset); - } - - private Item read(@Nonnull final Function reader, final int offset, final int length) { - - checkNotNull(reader, "expected non-null reader"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - checkArgument(length >= 0, "expected non-negative length, not %s", length); - - this.buffer.readerIndex(offset); - final T value = reader.apply(length); - final int actualLength = this.buffer.readerIndex() - offset; - - checkState(actualLength == length, "expected read of length %s, not %s", length, actualLength); - return Item.of(value, offset, actualLength); - } - - private long read7BitEncodedInt(int offset) { - Item item = this.read(this::read7BitEncodedInt, offset); - return item.value(); - } - - private long read7BitEncodedInt() { - return RowBuffer.rotateSignToMsb(this.read7BitEncodedUInt()); - } - - private long read7BitEncodedUInt(int offset) { - Item item = this.read(this::read7BitEncodedUInt, offset); - return item.value(); - } - - private long read7BitEncodedUInt() { - - long b = this.buffer.readByte() & 0xFFL; - - if (b < 0x80L) { - return b; - } - - long result = b & 0x7FL; - int shift = 7; - - do { - checkState(shift < 10 * 7); - b = this.buffer.readByte() & 0xFFL; - result |= (b & 0x7FL) << shift; - shift += 7; - } while (b >= 0x80L); - - return result; - } - - private BigDecimal readDecimal() { - return DecimalCodec.decode(this.buffer); - } - - private Utf8String readFixedString(int length) { - return Utf8String.fromUnsafe(this.buffer.readSlice(length)); - } - - private Float128 readFloat128() { - return Float128Codec.decode(this.buffer); - } - - private UUID readGuid() { - return GuidCodec.decode(this.buffer); - } - - private HybridRowHeader readHeader() { - HybridRowVersion version = HybridRowVersion.from(this.buffer.readByte()); - SchemaId schemaId = SchemaId.from(this.buffer.readIntLE()); - return new HybridRowHeader(version, schemaId); - } - - /** - * Read the metadata of an encoded sparse field. - * - * @param edit The edit structure to fill in. - * - * {@code edit.Path} - * On success, the path of the field at the given offset, otherwise - * undefined. - * - * {@code edit.MetaOffset} - * On success, the offset to the metadata of the field, otherwise a - * location to insert the field. - * - * {@code edit.cellType} - * On success, the layout code of the existing field, otherwise - * undefined. - * - * {@code edit.TypeArgs} - * On success, the type args of the existing field, otherwise - * undefined. - * - * {@code edit.ValueOffset} - * On success, the offset to the value of the field, otherwise - * undefined. - *. - */ - private void readSparseMetadata(@Nonnull final RowCursor edit) { - - checkNotNull(edit, "expected non-null edit"); - - if (edit.scopeType().hasImplicitTypeCode(edit)) { - - edit.scopeType().setImplicitTypeCode(edit); - edit.valueOffset(edit.metaOffset()); - - } else { - - int metaOffset = edit.metaOffset(); - LayoutType layoutType = this.readSparseTypeCode(metaOffset); - - edit.cellType(layoutType); - edit.valueOffset(metaOffset + LayoutCode.BYTES); - edit.cellTypeArgs(TypeArgumentList.EMPTY); - - if (edit.cellType() instanceof LayoutEndScope) { - // Reached end of current scope without finding another field. - edit.pathToken(0); - edit.pathOffset(0); - edit.valueOffset(edit.metaOffset()); - return; - } - - Out lengthInBytes = new Out<>(); - edit.cellTypeArgs(edit.cellType().readTypeArgumentList(this, edit.valueOffset(), lengthInBytes)); - edit.valueOffset(edit.valueOffset() + lengthInBytes.get()); - } - - edit.scopeType().readSparsePath(this, edit); - } - - private void readSparsePrimitiveTypeCode(@Nonnull final RowCursor edit, @Nonnull final LayoutType code) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(code, "expected non-null code"); - checkArgument(edit.exists(), "expected edit.exists value of true, not false"); - - if (edit.scopeType().hasImplicitTypeCode(edit)) { - if (edit.scopeType() instanceof LayoutNullable) { - checkState(edit.scopeTypeArgs().count() == 1); - checkState(edit.index() == 1); - checkState(edit.scopeTypeArgs().get(0).type() == code); - checkState(edit.scopeTypeArgs().get(0).typeArgs().count() == 0); - } else if (edit.scopeType().isFixedArity()) { - checkState(edit.scopeTypeArgs().count() > edit.index()); - checkState(edit.scopeTypeArgs().get(edit.index()).type() == code); - checkState(edit.scopeTypeArgs().get(edit.index()).typeArgs().count() == 0); - } else { - checkState(edit.scopeTypeArgs().count() == 1); - checkState(edit.scopeTypeArgs().get(0).type() == code); - checkState(edit.scopeTypeArgs().get(0).typeArgs().count() == 0); - } - } else { - if (code == LayoutTypes.BOOLEAN) { - final LayoutType layoutType = this.readSparseTypeCode(edit.metaOffset()); - checkState(layoutType == LayoutTypes.BOOLEAN || layoutType == LayoutTypes.BOOLEAN_FALSE); - } else { - checkState(this.readSparseTypeCode(edit.metaOffset()) == code); - } - } - - if (edit.scopeType().isIndexedScope()) { - checkState(edit.pathOffset() == 0); - checkState(edit.pathToken() == 0); - } else { - int offset = edit.metaOffset() + LayoutCode.BYTES; - Out pathLenInBytes = new Out<>(); - Out pathOffset = new Out<>(); - int token = this.readSparsePathLen(edit.layout(), offset, pathOffset, pathLenInBytes); - checkState(edit.pathOffset() == pathOffset.get()); - checkState(edit.pathToken() == token); - } - } - - private UnixDateTime readUnixDateTime() { - return new UnixDateTime(this.buffer.readLongLE()); - } - - private Utf8String readUtf8String() { - long length = this.read7BitEncodedUInt(); - checkState(length <= Integer.MAX_VALUE, "expected length <= %s, not %s", Integer.MAX_VALUE, length); - return Utf8String.fromUnsafe(this.buffer.readSlice((int)length)); - } - - private ByteBuf readVariableBinary() { - long length = this.read7BitEncodedUInt(); - checkState(length <= Integer.MAX_VALUE, "expected length <= %s, not %s", Integer.MAX_VALUE, length); - return this.buffer.readSlice((int)length).asReadOnly(); - } - - private void shift(int destination, int source, int length) { - if (source != destination) { - if (length > 0) { - this.buffer.setBytes(destination, this.buffer, source, length); - } - this.buffer.writerIndex(destination + length); - } - } - - /** - * Skip over a nested scope. - * - * @param edit The sparse scope to search - * @return The zero-based byte offset immediately following the scope end marker - */ - private int skipScope(RowCursor edit) { - - //noinspection StatementWithEmptyBody - while (this.sparseIteratorMoveNext(edit)) { - } - - if (!edit.scopeType().isSizedScope()) { - edit.metaOffset(edit.metaOffset() + LayoutCode.BYTES); // move past end of scope marker - } - - return edit.metaOffset(); - } - - /** - * Compute the size of a sparse (primitive) field. - * - * @param type The type of the current sparse field. - * @param metaOffset The zero-based offset from the beginning of the row where the field begins. - * @param valueOffset The zero-based offset from the beginning of the row where the field's value begins. - * @return The length (in bytes) of the encoded field including the metadata and the value. - */ - private int sparseComputePrimitiveSize(LayoutType type, int metaOffset, int valueOffset) { - - // TODO: JTH: convert to a virtual? - - int metaBytes = valueOffset - metaOffset; - LayoutCode code = type.layoutCode(); - - switch (code) { - case NULL: - checkState(LayoutTypes.NULL.size() == 0); - return metaBytes; - - case BOOLEAN: - case BOOLEAN_FALSE: - checkState(LayoutTypes.BOOLEAN.size() == 0); - return metaBytes; - - case INT_8: - return metaBytes + LayoutTypes.INT_8.size(); - - case INT_16: - return metaBytes + LayoutTypes.INT_16.size(); - - case INT_32: - return metaBytes + LayoutTypes.INT_32.size(); - - case INT_64: - return metaBytes + LayoutTypes.INT_64.size(); - - case UINT_8: - return metaBytes + LayoutTypes.UINT_8.size(); - - case UINT_16: - return metaBytes + LayoutTypes.UINT_16.size(); - - case UINT_32: - return metaBytes + LayoutTypes.UINT_32.size(); - - case UINT_64: - return metaBytes + LayoutTypes.UINT_64.size(); - - case FLOAT_32: - return metaBytes + LayoutTypes.FLOAT_32.size(); - - case FLOAT_64: - return metaBytes + LayoutTypes.FLOAT_64.size(); - - case FLOAT_128: - return metaBytes + LayoutTypes.FLOAT_128.size(); - - case DECIMAL: - return metaBytes + LayoutTypes.DECIMAL.size(); - - case DATE_TIME: - return metaBytes + LayoutTypes.DATE_TIME.size(); - - case UNIX_DATE_TIME: - return metaBytes + LayoutTypes.UNIX_DATE_TIME.size(); - - case GUID: - return metaBytes + LayoutTypes.GUID.size(); - - case MONGODB_OBJECT_ID: - // return metaBytes + MongoDbObjectId.size(); - throw new UnsupportedOperationException(); - - case UTF_8: - case BINARY: { - Item item = this.read(this::read7BitEncodedUInt, metaOffset + metaBytes); - return metaBytes + item.length() + item.value().intValue(); - } - case VAR_INT: - case VAR_UINT: { - Item item = this.read(this::read7BitEncodedUInt, metaOffset + metaBytes); - return metaBytes + item.length(); - } - default: - throw new IllegalStateException(lenientFormat("Not Implemented: %s", code)); - } - } - - /** - * Compute the size of a sparse field. - * - * @param edit The edit structure describing the field to measure. - * @return The length (in bytes) of the encoded field including the metadata and the value. - */ - private int sparseComputeSize(RowCursor edit) { - - if (!(edit.cellType() instanceof LayoutTypeScope)) { - return this.sparseComputePrimitiveSize(edit.cellType(), edit.metaOffset(), edit.valueOffset()); - } - - // Compute offset to end of value for current value - RowCursor newScope = this.sparseIteratorReadScope(edit, true); - return this.skipScope(newScope) - edit.metaOffset(); - } - - /** - * Reads and validates the header of the current {@link RowBuffer}. - * - * @return {@code true} if the header validation succeeded; otherwise, if the header is invalid, {@code false} - */ - private boolean validateHeader(@Nonnull final HybridRowVersion version) { - - checkNotNull(version, "expected non-null version"); - - final Item item = this.read(this::readHeader, 0); - final HybridRowHeader header = item.value(); - final Layout layout = this.resolver.resolve(header.schemaId()); - - checkState(header.schemaId().equals(layout.schemaId())); - return header.version().equals(version) && (HybridRowHeader.BYTES + layout.size()) <= this.length(); - } - - private Item write(@Nonnull final Consumer consumer, final int offset, @Nonnull final T value) { - - checkNotNull(consumer, "expected non-null consumer"); - checkNotNull(value, "expected non-null value"); - checkArgument(offset >= 0, "expected non-negative offset"); - - final int priorWriterIndex = this.buffer.writerIndex(); - this.buffer.writerIndex(offset); - final int length; - - try { - consumer.accept(value); - length = this.buffer.writerIndex() - offset; - } finally { - if (priorWriterIndex > this.buffer.writerIndex()) { - this.buffer.writerIndex(priorWriterIndex); - } - } - - return new Item<>(value, offset, length); - } - - private void writeDateTime(OffsetDateTime value) { - DateTimeCodec.encode(value, this.buffer); - } - - private void writeDecimal(BigDecimal value) { - DecimalCodec.encode(value, this.buffer); - } - - private int writeDefaultValue(int offset, LayoutType code, TypeArgumentList typeArgs) { - - // TODO: DANOBLE: Put default values in a central location (LayoutTypes?) and use them in this method - // ensure that there are no null default values (which this method currently uses) - // TODO: JTH: convert to a virtual? - - if (code == LayoutTypes.NULL) { - this.writeSparseTypeCode(offset, code.layoutCode()); - return 1; - } - - if (code == LayoutTypes.BOOLEAN) { - this.writeSparseTypeCode(offset, LayoutCode.BOOLEAN_FALSE); - return 1; - } - - if (code == LayoutTypes.INT_8) { - this.writeInt8(offset, (byte) 0); - return LayoutTypes.INT_8.size(); - } - - if (code == LayoutTypes.INT_16) { - this.writeInt16(offset, (short) 0); - return LayoutTypes.INT_16.size(); - } - - if (code == LayoutTypes.INT_32) { - this.writeInt32(offset, 0); - return LayoutTypes.INT_32.size(); - } - - if (code == LayoutTypes.INT_64) { - this.writeInt64(offset, 0); - return LayoutTypes.INT_64.size(); - } - - if (code == LayoutTypes.UINT_8) { - this.writeUInt8(offset, (byte) 0); - return LayoutTypes.UINT_8.size(); - } - - if (code == LayoutTypes.UINT_16) { - this.writeUInt16(offset, (short) 0); - return LayoutTypes.UINT_16.size(); - } - - if (code == LayoutTypes.UINT_32) { - this.writeUInt32(offset, 0); - return LayoutTypes.UINT_32.size(); - } - - if (code == LayoutTypes.UINT_64) { - this.writeUInt64(offset, 0); - return LayoutTypes.UINT_64.size(); - } - - if (code == LayoutTypes.FLOAT_32) { - this.writeFloat32(offset, 0); - return LayoutTypes.FLOAT_32.size(); - } - - if (code == LayoutTypes.FLOAT_64) { - this.writeFloat64(offset, 0); - return LayoutTypes.FLOAT_64.size(); - } - - if (code == LayoutTypes.FLOAT_128) { - this.writeFloat128(offset, Float128.ZERO); - return LayoutTypes.FLOAT_128.size(); - } - - if (code == LayoutTypes.DECIMAL) { - this.writeDecimal(offset, BigDecimal.ZERO); - return LayoutTypes.DECIMAL.size(); - } - - if (code == LayoutTypes.DATE_TIME) { - this.writeDateTime(offset, OffsetDateTime.MIN); - return LayoutTypes.DATE_TIME.size(); - } - - if (code == LayoutTypes.UNIX_DATE_TIME) { - this.writeUnixDateTime(offset, UnixDateTime.EPOCH); - return LayoutTypes.UNIX_DATE_TIME.size(); - } - - if (code == LayoutTypes.GUID) { - this.writeGuid(offset, GuidCodec.EMPTY); - return LayoutTypes.GUID.size(); - } - - if (code == LayoutTypes.MONGODB_OBJECT_ID) { - // TODO: DANOBLE: Add support for LayoutTypes.MONGODB_OBJECT_ID - // this.writeMongoDbObjectId(offset, null); - // return MongoDbObjectId.Size; - throw new UnsupportedOperationException(); - } - - if (code == LayoutTypes.UTF_8 || code == LayoutTypes.BINARY || code == LayoutTypes.VAR_INT || code == LayoutTypes.VAR_UINT) { - // Variable length types preceded by their varuint size take 1 byte for a size of 0 - return this.write(this::write7BitEncodedUInt, offset, 0L).length(); - } - - if (code == LayoutTypes.OBJECT || code == LayoutTypes.ARRAY) { - // Variable length sparse collection scopes take 1 byte for the end-of-scope terminator. - this.writeSparseTypeCode(offset, LayoutCode.END_SCOPE); - return LayoutCode.BYTES; - } - - if (code == LayoutTypes.TYPED_ARRAY || code == LayoutTypes.TYPED_SET || code == LayoutTypes.TYPED_MAP) { - // Variable length typed collection scopes preceded by their scope size take sizeof(uint) for a size of 0. - this.writeUInt32(offset, 0); - return Integer.BYTES; - } - - if (code == LayoutTypes.TUPLE) { - // Fixed arity sparse collections take 1 byte for end-of-scope plus a null for each element. - for (int i = 0; i < typeArgs.count(); i++) { - this.writeSparseTypeCode(offset, LayoutCode.NULL); - } - this.writeSparseTypeCode(offset, LayoutCode.END_SCOPE); - return LayoutCode.BYTES + (LayoutCode.BYTES * typeArgs.count()); - } - - if (code == LayoutTypes.TYPED_TUPLE || code == LayoutTypes.TAGGED || code == LayoutTypes.TAGGED_2) { - // Fixed arity typed collections take the sum of the default values of each element. The scope size is - // implied by the arity. - int sum = 0; - for (final Iterator iterator = typeArgs.stream().iterator(); iterator.hasNext(); ) { - final TypeArgument arg = iterator.next(); - sum += this.writeDefaultValue(offset + sum, arg.type(), arg.typeArgs()); - } - return sum; - } - - if (code == LayoutTypes.NULLABLE) { - // Nullables take the default values of the value plus null. The scope size is implied by the arity. - this.writeInt8(offset, (byte) 0); - return 1 + this.writeDefaultValue(offset + 1, typeArgs.get(0).type(), typeArgs.get(0).typeArgs()); - } - - if (code == LayoutTypes.UDT) { - - // Clear all presence bits - Layout udt = this.resolver.resolve(typeArgs.schemaId()); - this.write(this.buffer::writeZero, offset, udt.size()); - - // Write scope terminator - this.writeSparseTypeCode(offset + udt.size(), LayoutCode.END_SCOPE); - return udt.size() + LayoutCode.BYTES; - } - - throw new IllegalStateException(lenientFormat("Not Implemented: %s", code)); - } - - private void writeFixedString(Utf8String value) { - this.buffer.writeBytes(value.content(), value.encodedLength()); - } - - private void writeGuid(UUID value) { - GuidCodec.encode(value, this.buffer); - } - - private void writeSparseMetadata( - @Nonnull final RowCursor edit, @Nonnull final LayoutType cellType, @Nonnull final TypeArgumentList typeArgs, - final int metaBytes) { - - int metaOffset = edit.metaOffset(); - - if (!edit.scopeType().hasImplicitTypeCode(edit)) { - metaOffset += cellType.writeTypeArgument(this, metaOffset, typeArgs); - } - - this.writeSparsePath(edit, metaOffset); - edit.valueOffset(edit.metaOffset() + metaBytes); - - checkState(edit.valueOffset() == edit.metaOffset() + metaBytes); - } - - private void writeSparsePath(@Nonnull final RowCursor edit, final int offset) { - - checkNotNull(edit, "expected non-null edit"); - checkArgument(offset >= 0, "expected non-negative offset"); - - // Some scopes don't encode paths, therefore the cost is always zero - - if (edit.scopeType().isIndexedScope()) { - edit.pathToken(0); - edit.pathOffset(0); - return; - } - - final StringTokenizer tokenizer = edit.layout().tokenizer(); - final Optional writePathToken = tokenizer.tryFindToken(edit.writePath()); - - checkState(!(writePathToken.isPresent() && edit.writePathToken().isNull())); - - if (!edit.writePathToken().isNull()) { - this.write(this.buffer::writeBytes, offset, edit.writePathToken().varint()); - edit.pathToken((int) edit.writePathToken().id()); - edit.pathOffset(offset); - } else { - // TODO: It would be better if we could avoid allocating here when the path is UTF16 - Utf8String writePath = edit.writePath().toUtf8(); - checkState(writePath != null); - edit.pathToken(tokenizer.count() + writePath.encodedLength()); - Item item = this.write(this::write7BitEncodedUInt, offset, (long) edit.pathToken()); - edit.pathOffset(offset + item.length()); - this.write(this::writeFixedString, edit.pathOffset(), writePath); - } - } - - - private void writeUInt16(Short value) { - this.buffer.writeShortLE(value); - } - - private void writeUInt32(Integer value) { - this.buffer.writeIntLE(value); - } - - private void writeUInt64(Long value) { - this.buffer.writeLongLE(value); - } - - private void writeUInt8(Byte value) { - this.buffer.writeByte(value); - } - - private int writeVariableBinary(int offset, ByteBuf value) { - Item item = this.write(this::writeVariableBinary, offset, value); - return item.length(); - } - - private void writeVariableBinary(ByteBuf value) { - this.write7BitEncodedUInt(value.readableBytes()); - this.buffer.writeBytes(value); - } - - private void writeVariableString(@Nonnull final Utf8String value) { - final int length = this.write7BitEncodedUInt((long) value.encodedLength()); - assert length == value.encodedLength(); - assert value.content() != null; - this.buffer.writeBytes(value.content().readerIndex(0)); - } - - private static class Item { - - private int length; - private int offset; - private T value; - - private Item(T value, int offset, int length) { - this.value = value; - this.offset = offset; - this.length = length; - } - - public int length() { - return this.length; - } - - public static Item of(T value, int offset, int length) { - return new Item<>(value, offset, length); - } - - public int offset() { - return this.offset; - } - - public T value() { - return this.value; - } - } - - /** - * Represents a single item within a set/map scope that needs to be indexed. - *

- * This structure is used when rebuilding a set/map index during row streaming via {@link RowWriter}. Each item - * encodes its offsets and length within the row. - */ - static final class UniqueIndexItem { - - private LayoutCode code = LayoutCode.values()[0]; - private int metaOffset; - private int size; - private int valueOffset; - - /** - * The layout code of the value. - */ - public LayoutCode code() { - return this.code; - } - - public UniqueIndexItem code(LayoutCode code) { - this.code = code; - return this; - } - - /** - * If existing, the offset to the metadata of the existing field, otherwise the location to insert a new field. - */ - public int metaOffset() { - return this.metaOffset; - } - - public UniqueIndexItem metaOffset(int metaOffset) { - this.metaOffset = metaOffset; - return this; - } - - /** - * Size of the target element. - */ - public int size() { - return this.size; - } - - public UniqueIndexItem size(int size) { - this.size = size; - return this; - } - - /** - * If existing, the offset to the value of the existing field, otherwise undefined. - */ - public int valueOffset() { - return this.valueOffset; - } - - public UniqueIndexItem valueOffset(int valueOffset) { - this.valueOffset = valueOffset; - return this; - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.core.Utf8String; +import com.azure.data.cosmos.serialization.hybridrow.codecs.DateTimeCodec; +import com.azure.data.cosmos.serialization.hybridrow.codecs.DecimalCodec; +import com.azure.data.cosmos.serialization.hybridrow.codecs.Float128Codec; +import com.azure.data.cosmos.serialization.hybridrow.codecs.GuidCodec; +import com.azure.data.cosmos.serialization.hybridrow.io.RowWriter; +import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutArray; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBinary; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBit; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBoolean; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutColumn; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutDateTime; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutDecimal; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutEndScope; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat128; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat32; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat64; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutGuid; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt16; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt32; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt64; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt8; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutMongoDbObjectId; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNull; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNullable; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutObject; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutResolver; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTagged; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTagged2; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTuple; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutType; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypeScope; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedArray; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedMap; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedSet; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedTuple; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypes; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUDT; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt16; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt32; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt64; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt8; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUnixDateTime; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUtf8; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarInt; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarUInt; +import com.azure.data.cosmos.serialization.hybridrow.layouts.StringToken; +import com.azure.data.cosmos.serialization.hybridrow.layouts.StringTokenizer; +import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgument; +import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; +import com.azure.data.cosmos.serialization.hybridrow.layouts.UpdateOptions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.lenientFormat; + +// import com.azure.data.cosmos.serialization.hybridrow.RowBuffer.UniqueIndexItem; + +//import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypes.MongoDbObjectId; + +/** + * Manages a sequence of bytes representing a Hybrid Row. + *

+ * A Hybrid Row begins in the 0-th byte of the {@link RowBuffer}. The sequence of bytes is defined by the Hybrid Row + * grammar. + */ +public final class RowBuffer { + + private final ByteBuf buffer; + private LayoutResolver resolver; + + /** + * Initializes a new instance of a {@link RowBuffer}. + * + * @param capacity Initial buffer capacity. + */ + public RowBuffer(int capacity) { + this(capacity, ByteBufAllocator.DEFAULT); + } + + /** + * Initializes a new instance of a {@link RowBuffer}. + * + * @param capacity Initial buffer capacity + * @param allocator A buffer allocator + */ + public RowBuffer(final int capacity, @Nonnull final ByteBufAllocator allocator) { + checkArgument(capacity > 0, "capacity: %s", capacity); + checkNotNull(allocator, "expected non-null allocator"); + this.buffer = allocator.buffer(capacity); + this.resolver = null; + } + + /** + * Initializes a new instance of a {@link RowBuffer} from an existing buffer. + * + * @param buffer An existing {@link ByteBuf} containing a Hybrid Row. This instance takes ownership of the buffer. + * Hence, the caller should not maintain a reference to the buffer or mutate the buffer after this + * call returns. + * @param version The version of the Hybrid Row format to use for encoding the buffer. + * @param resolver The resolver for UDTs. + */ + public RowBuffer( + @Nonnull final ByteBuf buffer, + @Nonnull final HybridRowVersion version, + @Nonnull final LayoutResolver resolver) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(version, "expected non-null version"); + checkNotNull(resolver, "expected non-null resolver"); + + final int length = buffer.writerIndex(); + + checkArgument(length >= HybridRowHeader.BYTES, + "expected buffer with at least %s, not %s bytes", HybridRowHeader.BYTES, length); + + this.buffer = buffer; + this.resolver = resolver; + + final Item item = this.read(this::readHeader, 0); + final HybridRowHeader header = item.value(); + checkState(header.version() == version, "expected header version %s, not %s", version, header.version()); + + final Layout layout = resolver.resolve(header.schemaId()); + checkState(header.schemaId().equals(layout.schemaId())); + checkState(HybridRowHeader.BYTES + layout.size() <= this.length()); + } + + /** + * Compute the byte offset from the beginning of the row for a given variable's value. + * + * @param layout The (optional) layout of the current scope. + * @param scopeOffset The zero-based offset to the beginning of the scope's value. + * @param varIndex The zero-based index of the variable within the variable segment. + * @return The byte offset from the beginning of the row where the variable's value should be located. + */ + public int computeVariableValueOffset(@Nullable final Layout layout, final int scopeOffset, final int varIndex) { + + checkArgument(scopeOffset >= 0, "expected non-negative scopeOffset, not %s", scopeOffset); + checkArgument(varIndex >= 0, "expected non-negative varIndex, not %s", varIndex); + + if (layout == null) { + return scopeOffset; + } + + final List columns = layout.columns(); + final int index = layout.numFixed() + varIndex; + checkState(index <= columns.size()); + + int offset = scopeOffset + layout.size(); + + for (int i = layout.numFixed(); i < index; i++) { + + LayoutColumn column = columns.get(i); + + if (this.readBit(scopeOffset, column.nullBit())) { + Item item = this.read(this::read7BitEncodedUInt, offset); + if (column.type().isVarint()) { + offset += item.length(); + } else { + offset += item.length() + item.value(); + } + } + } + + return offset; + } + + /** + * Compute the number of bytes necessary to store the unsigned 32-bit integer value using the varuint encoding. + * + * @param value the value to be encoded + * @return the number of bytes needed to store the varuint encoding of {@code value} + */ + public static int count7BitEncodedUInt(long value) { + checkArgument(0 <= value && value <= 0x00000000FFFFFFFFL, "value: %s", value); + int i = 0; + while (value >= 0x80L) { + i++; + value >>>= 7; + } + i++; + return i; + } + + /** + * Decrement the unsigned 32-bit integer value at the given {@code offset} in this {@link RowBuffer}. + * + * @param offset offset of a 32-bit unsigned integer value in this {@link RowBuffer}. + * @param decrement the decrement value. + */ + public void decrementUInt32(int offset, long decrement) { + long value = this.buffer.getUnsignedIntLE(offset); + this.buffer.setIntLE(offset, (int) (value - decrement)); + } + + /** + * Delete the sparse field at the specified cursor position. + * + * @param edit identifies the field to delete + */ + public void deleteSparse(@Nonnull final RowCursor edit) { + + checkNotNull(edit, "expected non-null edit"); + + if (!edit.exists()) { + return; // do nothing + } + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(0, edit, edit.cellType(), edit.cellTypeArgs(), RowOptions.DELETE, metaBytes, spaceNeeded, shift); + + checkState(this.length() == priorLength + shift.get()); + } + + /** + * Delete the variable-length field at a specified {@code offset}. + *

+ * The field is interpreted as either a variable-length integer or a variable-length sequence of bytes as indicated + * by the value of {@code isVarint}. + * + * @param offset index of the field in this {@link RowBuffer} + * @param isVarint {@code true}, if the field should be interpreted as a variable-length integer value; + * {@code false}, if the field should be interpreted as a variable-length sequence of bytes. + */ + public void deleteVariable(final int offset, final boolean isVarint) { + + final Item item = this.read(this::read7BitEncodedUInt, offset); + + final int source = isVarint + ? offset + item.length() // because this is a varint value + : offset + item.length() + item.value().intValue(); // because this is a variable-length sequence of bytes + + final int length = this.buffer.writerIndex() - source; + + this.buffer.setBytes(offset, this.buffer, source, length); + this.buffer.writerIndex(this.buffer.writerIndex() - length); + } + + /** + * The root header of this {@link RowBuffer}. + * + * @return root header of this {@link RowBuffer}. + */ + public HybridRowHeader header() { + return this.readHeader(); + } + + // TODO: DANOBLE: ressurrect this method + // public void WriteSparseMongoDbObjectId(@Nonnull final RowCursor edit, MongoDbObjectId value, + // UpdateOptions options) { + // int numBytes = MongoDbObjectId.Size; + // int metaBytes; + // final Out metaBytes = new Out<>(); + // int spaceNeeded; + // final Out spaceNeeded = new Out<>(); + // int shift; + // final Out shift = new Out<>(); + // this.ensureSparse(numBytes, edit, MongoDbObjectId, TypeArgumentList.EMPTY, options, + // metaBytes, spaceNeeded, shift); + // this.writeSparseMetadata(edit, MongoDbObjectId, TypeArgumentList.EMPTY, metaBytes); + // this.WriteMongoDbObjectId(edit.valueOffset(), value.clone()); + // checkState(spaceNeeded == metaBytes + MongoDbObjectId.Size); + // edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + // this.buffer.writerIndex(this.length() + shift); + // } + + /** + * Decrement the unsigned 32-bit integer value at the given {@code offset} in this {@link RowBuffer}. + * + * @param offset offset of a 32-bit unsigned integer value in this {@link RowBuffer}. + * @param increment the increment value. + */ + public void incrementUInt32(final int offset, final long increment) { + final long value = this.buffer.getUnsignedIntLE(offset); + this.buffer.setIntLE(offset, (int) (value + increment)); + } + + /** + * Initializes a row to the minimal size for the given layout. + * + * @param version The version of the Hybrid Row format to use for encoding this row. + * @param layout The layout that describes the column layout of the row. + * @param resolver The resolver for UDTs. + *

+ * The row is initialized to default row for the given layout. All fixed columns have their + * default values. All variable columns are null. No sparse columns are present. The row is + * valid. + */ + public void initLayout(HybridRowVersion version, Layout layout, LayoutResolver resolver) { + + checkNotNull(version, "expected non-null version"); + checkNotNull(layout, "expected non-null layout"); + checkNotNull(resolver, "expected non-null resolver"); + + this.writeHeader(new HybridRowHeader(version, layout.schemaId())); + this.buffer.writeZero(layout.size()); + this.resolver = resolver; + } + + /** + * The length of this {@link RowBuffer} in bytes. + * + * @return The length of this {@link RowBuffer} in bytes. + */ + public int length() { + return this.buffer.writerIndex(); + } + + /** + * Compute the byte offsets from the beginning of the row for a given sparse field insertion. + * into a set/map. + * + * @param scope The sparse scope to insert into. + * @param srcEdit The field to move into the set/map. + * @return The prepared edit context. + */ + @Nonnull + public RowCursor prepareSparseMove(@Nonnull final RowCursor scope, @Nonnull final RowCursor srcEdit) { + + checkNotNull(srcEdit, "expected non-null srcEdit"); + checkNotNull(scope, "expected non-null scope"); + checkArgument(scope.index() == 0); + checkArgument(scope.scopeType().isUniqueScope()); + + RowCursor dstEdit = scope.clone().metaOffset(scope.valueOffset()); + int srcSize = this.sparseComputeSize(srcEdit); + int srcBytes = srcSize - (srcEdit.valueOffset() - srcEdit.metaOffset()); + + while (dstEdit.index() < dstEdit.count()) { + + this.readSparseMetadata(dstEdit); + checkState(dstEdit.pathOffset() == 0); + + int elmSize = -1; // defer calculating the full size until needed + int cmp; + + if (scope.scopeType() instanceof LayoutTypedMap) { + cmp = this.compareKeyValueFieldValue(srcEdit, dstEdit); + } else { + elmSize = this.sparseComputeSize(dstEdit); + int elmBytes = elmSize - (dstEdit.valueOffset() - dstEdit.metaOffset()); + cmp = this.compareFieldValue(srcEdit, srcBytes, dstEdit, elmBytes); + } + + if (cmp <= 0) { + dstEdit.exists(cmp == 0); + return dstEdit; + } + + elmSize = elmSize == -1 ? this.sparseComputeSize(dstEdit) : elmSize; + dstEdit.index(dstEdit.index() + 1); + dstEdit.metaOffset(dstEdit.metaOffset() + elmSize); + } + + dstEdit.exists(false); + dstEdit.cellType(LayoutTypes.END_SCOPE); + dstEdit.valueOffset(dstEdit.metaOffset()); + + return dstEdit; + } + + /** + * Read the value of a bit within the bit field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a bit field within this {@link RowBuffer}. + * @param bit the bit to read. + * @return {@code true} if the {@code bit} is set, otherwise {@code false}. + */ + public boolean readBit(final int offset, @Nonnull final LayoutBit bit) { + + checkNotNull(bit, "expected non-null bit"); + + if (bit.isInvalid()) { + return true; + } + + Item item = this.read(() -> (this.buffer.readByte() & (byte) (1 << bit.bit())) != 0, bit.offset(offset)); + return item.value(); + } + + /** + * Read the value of the {@code DateTime} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code DateTime} field within this {@link RowBuffer}. + * @return the {@code DateTime} value read. + */ + public OffsetDateTime readDateTime(int offset) { + Item item = this.read(() -> DateTimeCodec.decode(this.buffer), offset); + return item.value(); + } + + // TODO: DANOBLE: resurrect this method + // public MongoDbObjectId ReadMongoDbObjectId(int offset) { + // return MemoryMarshal.Read(this.buffer.Slice(offset)); + // } + + /** + * Read the value of the {@code Decimal} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Decimal} field within this {@link RowBuffer}. + * @return the {@code Decimal} value read. + */ + public BigDecimal readDecimal(int offset) { + Item item = this.read(() -> DecimalCodec.decode(this.buffer), offset); + return item.value(); + } + + /** + * Read the value of a {@code FixedBinary} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code FixedBinary} field within this {@link RowBuffer}. + * @param length number of bytes to read. + * @return the {@code FixedBinary} value read. + */ + public ByteBuf readFixedBinary(int offset, int length) { + Item item = this.read(() -> this.buffer.readSlice(length), offset); + return item.value(); + } + + /** + * Read the value of a {@code FixedString} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code FixedString} field within this {@link RowBuffer}. + * @param length number of bytes in the {@code FixedString} field. + * @return the {@code FixedString} value read. + */ + public Utf8String readFixedString(int offset, int length) { + Item item = this.read(this::readFixedString, offset, length); + return item.value(); + } + + /** + * Read the value of a {@code Float128} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Float128} field within this {@link RowBuffer}. + * @return the {@code Float128} value read. + */ + public Float128 readFloat128(int offset) { + Item item = this.read(this::readFloat128, offset); + return item.value(); + } + + /** + * Read the value of a {@code Float32} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Float32} field within this {@link RowBuffer}. + * @return the {@code Float32} value read. + */ + public float readFloat32(int offset) { + Item item = this.read(this.buffer::readFloatLE, offset); + return item.value(); + } + + /** + * Read the value of a {@code Float64} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Float64} field within this {@link RowBuffer}. + * @return the {@code Float64} value read. + */ + public double readFloat64(int offset) { + Item item = this.read(this.buffer::readDoubleLE, offset); + return item.value(); + } + + // TODO: DANOBLE: resurrect this method + // public MongoDbObjectId ReadSparseMongoDbObjectId(Reference edit) { + // this.readSparsePrimitiveTypeCode(edit, MongoDbObjectId); + // edit.endOffset = edit.valueOffset() + MongoDbObjectId.Size; + // return this.ReadMongoDbObjectId(edit.valueOffset()).clone(); + // } + + /** + * Reads in the contents of the current {@link RowBuffer} from an {@link InputStream}. + *

+ * The {@link RowBuffer} is initialized with the associated layout and row {@code version}. + * + * @param inputStream the stream from which the contents of the current {@link RowBuffer} should be read + * @param byteCount the number of bytes to be read from the {@code inputStream} + * @param version the {@link HybridRowVersion} to be assigned to the current {@link RowBuffer} + * @param resolver the layout resolver to be used in parsing the {@code inputStream} + * @return {@code true} if the read succeeded; otherwise, if the {@link InputStream} was corrupted, {@code false} + */ + public boolean readFrom( + @Nonnull final InputStream inputStream, final int byteCount, @Nonnull final HybridRowVersion version, + @Nonnull final LayoutResolver resolver) { + + checkNotNull(inputStream, "expected non-null inputStream"); + checkNotNull(resolver, "expected non-null resolver"); + checkNotNull(version, "expected non-null version"); + checkState(byteCount >= HybridRowHeader.BYTES, "expected byteCount >= %s, not %s", HybridRowHeader.BYTES, + byteCount); + + this.reset(); + this.ensure(byteCount); + this.resolver = resolver; + + final int bytesRead; + + try { + bytesRead = this.buffer.writeBytes(inputStream, byteCount); + } catch (IOException error) { + return false; + } + + if (bytesRead != byteCount) { + return false; + } + + return this.validateHeader(version); + } + + /** + * Reads the contents of the current {@link RowBuffer} from a {@link ByteBuf}. + *

+ * The {@link RowBuffer} is initialized with a copy of the specified input {@link ByteBuf} and the associated layout + * and row {@code version}. + * + * @param input the buffer from which the contents of the current {@link RowBuffer} should be read + * @param version the {@link HybridRowVersion} to be assigned to the current {@link RowBuffer} + * @param resolver the layout resolver to be used in parsing the {@code inputStream} + * @return {@code true} if the read succeeded; otherwise, if the {@link InputStream} was corrupted, {@code false} + */ + public boolean readFrom( + @Nonnull final ByteBuf input, @Nonnull final HybridRowVersion version, @Nonnull final LayoutResolver resolver) { + + checkNotNull(input, "expected non-null input"); + checkNotNull(version, "expected non-null version"); + checkNotNull(resolver, "expected non-null resolver"); + checkState(input.readableBytes() >= HybridRowHeader.BYTES); + + this.reset(); + this.resolver = resolver; + this.buffer.writeBytes(this.buffer); + + return this.validateHeader(version); + } + + /** + * Read the value of a {@code Guid} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Guid} field within this {@link RowBuffer}. + * @return the {@code Guid} value read. + */ + public UUID readGuid(int offset) { + return this.read(() -> GuidCodec.decode(this.buffer), offset).value(); + } + + /** + * Read the value of a {@code Header} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Header} field within this {@link RowBuffer}. + * @return the {@code Header} value read. + */ + public HybridRowHeader readHeader(int offset) { + return this.read(this::readHeader, offset).value(); + } + + /** + * Read the value of a {@code Int16} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Int16} field within this {@link RowBuffer}. + * @return the {@code Int16} value read. + */ + public short readInt16(int offset) { + return this.read(this.buffer::readShortLE, offset).value(); + } + + /** + * Read the value of a {@code Int32} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Int32} field within this {@link RowBuffer}. + * @return the {@code Int32} value read. + */ + public int readInt32(int offset) { + Item item = this.read(this.buffer::readIntLE, offset); + return item.value(); + } + + /** + * Read the value of a {@code Int64} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Int64} field within this {@link RowBuffer}. + * @return the {@code Int64} value read. + */ + public long readInt64(int offset) { + Item item = this.read(this.buffer::readLongLE, offset); + return item.value(); + } + + /** + * Read the value of a {@code Int8} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code Int8} field within this {@link RowBuffer}. + * @return the {@code Int8} value read. + */ + public byte readInt8(int offset) { + Item item = this.read(this.buffer::readByte, offset); + return item.value(); + } + + /** + * Read the value of a {@code SchemaId} field at the given {@code offset} within this {@link RowBuffer}. + * + * @param offset offset of a {@code SchemaId} field within this {@link RowBuffer}. + * @return the {@code SchemaId} value read. + */ + public SchemaId readSchemaId(int offset) { + Item item = this.read(() -> SchemaId.from(this.buffer.readIntLE()), offset); + return item.value(); + } + + /** + * Read the value of a {@code SparseBinary} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseBinary} field within this {@link RowBuffer}. + * @return the {@code SparseBinary} value read. + */ + public ByteBuf readSparseBinary(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.BINARY); + Item item = this.read(this::readVariableBinary, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseBoolean} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseBoolean} field within this {@link RowBuffer}. + * @return the {@code SparseBoolean} value read. + */ + public boolean readSparseBoolean(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.BOOLEAN); + edit.endOffset(edit.valueOffset()); + return edit.cellType() == LayoutTypes.BOOLEAN; + } + + /** + * Read the value of a {@code SparseDateTime} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseDateTime} field within this {@link RowBuffer}. + * @return the {@code SparseDateTime} value read. + */ + public OffsetDateTime readSparseDateTime(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.DATE_TIME); + edit.endOffset(edit.valueOffset() + Long.SIZE); + return this.readDateTime(edit.valueOffset()); + } + + /** + * Read the value of a {@code SparseDecimal} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseDecimal} field within this {@link RowBuffer}. + * @return the {@code SparseDecimal} value read. + */ + public BigDecimal readSparseDecimal(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.DECIMAL); + Item item = this.read(this::readDecimal, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseFloat128} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseFloat128} field within this {@link RowBuffer}. + * @return the {@code SparseFloat128} value read. + */ + public Float128 readSparseFloat128(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.FLOAT_128); + Item item = this.read(this::readFloat128, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseFloat32} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseFloat32} field within this {@link RowBuffer}. + * @return the {@code SparseFloat32} value read. + */ + public float readSparseFloat32(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.FLOAT_32); + Item item = this.read(this.buffer::readFloatLE, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseFloat64} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseFloat64} field within this {@link RowBuffer}. + * @return the {@code SparseFloat64} value read. + */ + public double readSparseFloat64(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.FLOAT_64); + Item item = this.read(this.buffer::readDoubleLE, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseGuid} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseGuid} field within this {@link RowBuffer}. + * @return the {@code SparseGuid} value read. + */ + public UUID readSparseGuid(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.GUID); + Item item = this.read(this::readGuid, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseInt16} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseInt16} field within this {@link RowBuffer}. + * @return the {@code SparseInt16} value read. + */ + public short readSparseInt16(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.INT_16); + Item item = this.read(this.buffer::readShortLE, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseInt32} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseInt32} field within this {@link RowBuffer}. + * @return the {@code SparseInt32} value read. + */ + public int readSparseInt32(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.INT_32); + Item item = this.read(this.buffer::readIntLE, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseInt64} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseInt64} field within this {@link RowBuffer}. + * @return the {@code SparseInt64} value read. + */ + public long readSparseInt64(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.INT_64); + Item item = this.read(this.buffer::readLongLE, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseInt8} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseInt8} field within this {@link RowBuffer}. + * @return the {@code SparseInt8} value read. + */ + public byte readSparseInt8(RowCursor edit) { + // TODO: Remove calls to readSparsePrimitiveTypeCode once moved to V2 read. + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.INT_8); + Item item = this.read(this.buffer::readByte, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseNull} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseNull} field within this {@link RowBuffer}. + * @return the {@code SparseNull} value read. + */ + public NullValue readSparseNull(@Nonnull RowCursor edit) { + + checkNotNull(edit, "expected non-null edit"); + + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.NULL); + edit.endOffset(edit.valueOffset()); + + return NullValue.DEFAULT; + } + + /** + * Read the value of a {@code SparsePath} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparsePath} field within this {@link RowBuffer}. + * @return the {@code SparsePath} value read. + */ + public Utf8String readSparsePath(@Nonnull final RowCursor edit) { + + checkNotNull(edit, "expected non-null edit"); + + final StringTokenizer tokenizer = edit.layout().tokenizer(); + final Optional path = tokenizer.tryFindString(edit.pathToken()); + + if (path.isPresent()) { + return path.get(); + } + + final int length = edit.pathToken() - tokenizer.count(); + Item item = this.read(this::readFixedString, edit.pathOffset(), length); + + return item.value(); + } + + /** + * Read the value of a {@code SparsePathLen} field at the given {@code offset} position. + * + * @param layout layout of the {@code SparsePathLen} field. + * @param offset position of the {@code SparsePathLen} field.. + * @param pathOffset [output] position of the {@code SparsePathLen} field value. + * @param pathLengthInBytes [output] length of the {@code SparsePathLen} field value. + * @return the {@code SparsePathLen} value read. + */ + public int readSparsePathLen( + @Nonnull final Layout layout, + final int offset, + @Nonnull final Out pathOffset, + @Nonnull final Out pathLengthInBytes) { + + checkNotNull(layout, "expected non-null layout"); + checkNotNull(pathOffset, "expected non-null pathOffset"); + checkNotNull(pathLengthInBytes, "expected non-null pathLengthInBytes"); + + final Item item = this.read(this::read7BitEncodedUInt, offset); + final int token = item.value().intValue(); + + if (token < layout.tokenizer().count()) { + pathLengthInBytes.set(item.length()); + pathOffset.set(offset); + return token; + } + + final int numBytes = token - layout.tokenizer().count(); + pathLengthInBytes.set(numBytes + item.length()); + pathOffset.set(offset + item.length()); + + return token; + } + + /** + * Read the value of a {@code SparseString} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseString} field within this {@link RowBuffer}. + * @return the {@code SparseString} value read. + */ + public Utf8String readSparseString(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UTF_8); + Item item = this.read(this::readUtf8String, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseTypeCode} field at the given {@code offset} position. + * + * @param offset position of a {@code SparseTypeCode} field within this {@link RowBuffer}. + * @return the {@code SparseTypeCode} value read. + */ + public LayoutType readSparseTypeCode(int offset) { + byte value = this.readInt8(offset); + LayoutCode code = LayoutCode.from(value); + checkState(code != null, "expected layout code at offset %s, not %s", offset, code); + return LayoutType.fromLayoutCode(code); + } + + /** + * Read the value of a {@code SparseUInt16} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseUInt16} field within this {@link RowBuffer}. + * @return the {@code SparseUInt16} value read. + */ + public int readSparseUInt16(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UINT_16); + Item item = this.read(this.buffer::readUnsignedShortLE, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseUInt32} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseUInt32} field within this {@link RowBuffer}. + * @return the {@code SparseUInt32} value read. + */ + public long readSparseUInt32(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UINT_32); + Item item = this.read(this.buffer::readUnsignedIntLE, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseUInt64} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseUInt64} field within this {@link RowBuffer}. + * @return the {@code SparseUInt64} value read. + */ + public long readSparseUInt64(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UINT_64); + Item item = this.read(this.buffer::readLongLE, edit); + return item.value; + } + + /** + * Read the value of a {@code SparseUInt8} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseUInt8} field within this {@link RowBuffer}. + * @return the {@code SparseUInt8} value read. + */ + public short readSparseUInt8(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UINT_8); + Item item = this.read(this.buffer::readUnsignedByte, edit); + return item.value; + } + + /** + * Read the value of a {@code SparseUnixDateTime} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseUnixDateTime} field within this {@link RowBuffer}. + * @return the {@code SparseUnixDateTime} value read. + */ + public UnixDateTime readSparseUnixDateTime(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.UNIX_DATE_TIME); + Item item = this.read(this::readUnixDateTime, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseVarInt} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseVarInt} field within this {@link RowBuffer}. + * @return the {@code SparseVarInt} value read. + */ + public long readSparseVarInt(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.VAR_INT); + Item item = this.read(this::read7BitEncodedInt, edit); + return item.value(); + } + + /** + * Read the value of a {@code SparseVarUInt} field at the given {@link RowCursor edit} position. + * + * @param edit {@link RowCursor edit} position of a {@code SparseVarUInt} field within this {@link RowBuffer}. + * @return the {@code SparseVarUInt} value read. + */ + public long readSparseVarUInt(RowCursor edit) { + this.readSparsePrimitiveTypeCode(edit, LayoutTypes.VAR_UINT); + Item item = this.read(this::read7BitEncodedUInt, edit); + return item.value(); + } + + /** + * Read the value of a {@code UInt16} field at the given {@code offset} position. + * + * @param offset position of a {@code UInt16} field within this {@link RowBuffer}. + * @return the {@code UInt16} value read. + */ + public int readUInt16(int offset) { + Item item = this.read(this.buffer::readUnsignedShortLE, offset); + return item.value(); + } + + /** + * Read the value of a {@code UInt32} field at the given {@code offset} position. + * + * @param offset position of a {@code UInt32} field within this {@link RowBuffer}. + * @return the {@code UInt32} value read. + */ + public long readUInt32(int offset) { + Item item = this.read(this.buffer::readUnsignedIntLE, offset); + return item.value(); + } + + /** + * Read the value of a {@code UInt64} field at the given {@code offset} position. + * + * @param offset position of a {@code UInt64} field within this {@link RowBuffer}. + * @return the {@code UInt64} value read. + */ + public long readUInt64(int offset) { + Item item = this.read(this.buffer::readLongLE, offset); + return item.value(); + } + + /** + * Read the value of a {@code UInt8} field at the given {@code offset} position. + * + * @param offset position of a {@code UInt8} field within this {@link RowBuffer}. + * @return the {@code UInt8} value read. + */ + public short readUInt8(int offset) { + Item item = this.read(this.buffer::readUnsignedByte, offset); + return item.value(); + } + + /** + * Read the value of a {@code UnixDateTime} field at the given {@code offset} position. + * + * @param offset position of a {@code UnixDateTime} field within this {@link RowBuffer}. + * @return the {@code UnixDateTime} value read. + */ + public UnixDateTime readUnixDateTime(int offset) { + Item item = this.read(this::readUnixDateTime, offset); + return item.value(); + } + + /** + * Read the value of a {@code VariableBinary} field at the given {@code offset} position. + * + * @param offset position of a {@code VariableBinary} field within this {@link RowBuffer}. + * @return the {@code VariableBinary} value read. + */ + public ByteBuf readVariableBinary(int offset) { + Item item = this.read(this::readVariableBinary, offset); + return item.value(); + } + + /** + * Read the value of a {@code VariableInt} field at the given {@code offset} position. + * + * @param offset position of a {@code VariableInt} field within this {@link RowBuffer}. + * @return the {@code VariableInt} value read. + */ + public long readVariableInt(int offset) { + Item item = this.read(this::read7BitEncodedInt, offset); + return item.value(); + } + + /** + * Read the value of a {@code VariableString} field at the given {@code offset} position. + * + * @param offset position of a {@code VariableString} field within this {@link RowBuffer}. + * @return the {@code VariableString} value read. + */ + public Utf8String readVariableString(final int offset) { + Item item = this.read(this::readUtf8String, offset); + return item.value(); + } + + /** + * Read the value of a {@code VariableUInt} field at the given {@code offset} position. + * + * @param offset position of a {@code VariableUInt} field within this {@link RowBuffer}. + * @return the {@code VariableUInt} value read. + */ + public long readVariableUInt(final int offset) { + Item item = this.read(this::read7BitEncodedUInt, offset); + return item.value(); + } + + /** + * Read the value of a {@code VariableUInt} field at the given {@code offset} position. + * + * @param offset position of a {@code VariableUInt} field within this {@link RowBuffer}. + * @param length on return, the number of bytes read. + * @return the {@code VariableUInt} value read. + */ + public long readVariableUInt(final int offset, @Nonnull final Out length) { + Item item = this.read(this::read7BitEncodedUInt, offset); + length.set(item.length()); + return item.value(); + } + + /** + * Clears all content from the row. The row is empty after this method. + */ + public void reset() { + this.buffer.clear(); + this.resolver = null; + } + + /** + * The resolver for UDTs. + * + * @return reference to the resolver for UDTs. + */ + public LayoutResolver resolver() { + return this.resolver; + } + + /** + * Rotates the sign bit of a two's complement value to the least significant bit. + * + * @param value A signed value. + * @return An unsigned value encoding the same value but with the sign bit in the LSB. + *

+ * Moves the signed bit of a two's complement value to the least significant bit (LSB) by: + *

    + *
  1. If negative, take the two's complement. + *
  2. Left shift the value by 1 bit. + *
  3. If negative, set the LSB to 1. + *
+ */ + public static long rotateSignToLsb(long value) { + boolean isNegative = value < 0; + long unsignedValue = value; + unsignedValue = isNegative ? ((~unsignedValue + 1) << 1) + 1 : unsignedValue << 1; + return unsignedValue; + } + + /** + * Undoes the rotation introduced by {@link #rotateSignToLsb}. + * + * @param unsignedValue An unsigned value with the sign bit in the LSB. + * @return A signed two's complement value encoding the same value. + */ + public static long rotateSignToMsb(long unsignedValue) { + boolean isNegative = unsignedValue % 2 != 0; + return isNegative ? (~(unsignedValue >>> 1) + 1) | 0x8000000000000000L : unsignedValue >>> 1; + } + + // TODO: DANOBLE: Support MongoDbObjectId values + // public void WriteMongoDbObjectId(int offset, MongoDbObjectId value) { + // Reference tempReference_value = + // new Reference(value); + // MemoryMarshal.Write(this.buffer.Slice(offset), tempReference_value); + // value = tempReference_value.get(); + // } + + public void setBit(final int offset, @Nonnull final LayoutBit bit) { + checkNotNull(bit, "expected non-null bit"); + if (bit.isInvalid()) { + return; + } + final int index = bit.offset(offset); + this.buffer.setByte(index, this.buffer.getByte(bit.offset(offset)) | (byte) (1 << bit.bit())); + } + + /** + * Move a sparse iterator to the next field within the same sparse scope. + * + * @param edit The iterator to advance. + * + * {@code edit.Path} + * On success, the path of the field at the given offset, otherwise + * undefined. + * + * {@code edit.MetaOffset} + * If found, the offset to the metadata of the field, otherwise a + * location to insert the field. + * + * {@code edit.cellType} + * If found, the layout code of the matching field found, otherwise + * undefined. + * + * {@code edit.ValueOffset} + * If found, the offset to the value of the field, otherwise + * undefined. + *. + * + * @return {@code true} if there is another field; {@code false} if there is not. + */ + public boolean sparseIteratorMoveNext(RowCursor edit) { + + if (edit.cellType() != null) { + // Move to the next element of an indexed scope. + if (edit.scopeType().isIndexedScope()) { + edit.index(edit.index() + 1); + } + + // Skip forward to the end of the current value. + if (edit.endOffset() != 0) { + edit.metaOffset(edit.endOffset()); + edit.endOffset(0); + } else { + edit.metaOffset(edit.metaOffset() + this.sparseComputeSize(edit)); + } + } + + // Check if reached end of buffer + + if (edit.metaOffset() < this.length()) { + + // Check if reached end of sized scope. + + if (!edit.scopeType().isSizedScope() || (edit.index() != edit.count())) { + + this.readSparseMetadata(edit); + + if (!(edit.cellType() instanceof LayoutEndScope)) { + // End of sparse scope + edit.exists(true); + return true; + } + } + } + + edit.cellType(LayoutTypes.END_SCOPE); + edit.exists(false); + edit.valueOffset(edit.metaOffset()); + return false; + } + + /** + * Produce a new scope from the current iterator position. + * + * @param edit An initialized iterator pointing at a scope. + * @param immutable {@code true} if the new scope should be marked immutable (read-only). + * @return A new scope beginning at the current iterator position. + */ + public RowCursor sparseIteratorReadScope(@Nonnull final RowCursor edit, boolean immutable) { + + LayoutTypeScope scopeType = edit.cellType() instanceof LayoutTypeScope ? (LayoutTypeScope) edit.cellType() : null; + + if (scopeType instanceof LayoutObject || scopeType instanceof LayoutArray) { + return new RowCursor() + .scopeType(scopeType) + .scopeTypeArgs(edit.cellTypeArgs()) + .start(edit.valueOffset()) + .valueOffset(edit.valueOffset()) + .metaOffset(edit.valueOffset()) + .layout(edit.layout()) + .immutable(immutable); + } + + if (scopeType instanceof LayoutTypedArray || scopeType instanceof LayoutTypedSet || scopeType instanceof LayoutTypedMap) { + + final int valueOffset = edit.valueOffset() + Integer.BYTES; // Point after the Size + + return new RowCursor() + .scopeType(scopeType) + .scopeTypeArgs(edit.cellTypeArgs()) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(edit.layout()) + .immutable(immutable) + .count(this.readInt32(edit.valueOffset())); + } + + if (scopeType instanceof LayoutTypedTuple || scopeType instanceof LayoutTuple || scopeType instanceof LayoutTagged || scopeType instanceof LayoutTagged2) { + + return new RowCursor() + .scopeType(scopeType) + .scopeTypeArgs(edit.cellTypeArgs()) + .start(edit.valueOffset()) + .valueOffset(edit.valueOffset()) + .metaOffset(edit.valueOffset()) + .layout(edit.layout()) + .immutable(immutable) + .count(edit.cellTypeArgs().count()); + } + + if (scopeType instanceof LayoutNullable) { + + boolean hasValue = this.readInt8(edit.valueOffset()) != 0; + + if (hasValue) { + + // Start at the T so it can be read. + final int valueOffset = edit.valueOffset() + 1; + + return new RowCursor() + .scopeType(scopeType) + .scopeTypeArgs(edit.cellTypeArgs()) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(edit.layout()) + .immutable(immutable) + .count(2) + .index(1); + } else { + + // Start at the end of the scope, instead of at the T, so the T will be skipped. + final TypeArgument typeArg = edit.cellTypeArgs().get(0); + final int valueOffset = edit.valueOffset() + 1 + this.countDefaultValue(typeArg.type(), + typeArg.typeArgs()); + + return new RowCursor() + .scopeType(scopeType) + .scopeTypeArgs(edit.cellTypeArgs()) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(edit.layout()) + .immutable(immutable) + .count(2) + .index(2); + } + } + + if (scopeType instanceof LayoutUDT) { + + final Layout udt = this.resolver.resolve(edit.cellTypeArgs().schemaId()); + final int valueOffset = this.computeVariableValueOffset(udt, edit.valueOffset(), udt.numVariable()); + + return new RowCursor() + .scopeType(scopeType) + .scopeTypeArgs(edit.cellTypeArgs()) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(udt) + .immutable(immutable); + } + + throw new IllegalStateException(lenientFormat("Not a scope type: %s", scopeType)); + } + + public byte[] toArray() { + byte[] content = new byte[this.length()]; + this.buffer.getBytes(0, content); + return content; + } + + public void typedCollectionMoveField( + @Nonnull final RowCursor dstEdit, + @Nonnull final RowCursor srcEdit, + @Nonnull final RowOptions options) { + + final int length = this.sparseComputeSize(srcEdit) - (srcEdit.valueOffset() - srcEdit.metaOffset()); + + // Insert the field metadata into its new location + + Out metaBytes = new Out<>(); + Out spaceNeeded = new Out<>(); + Out shiftInsert = new Out<>(); + Out shiftDelete = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse( + length, dstEdit, srcEdit.cellType(), srcEdit.cellTypeArgs(), options, metaBytes, spaceNeeded, shiftInsert); + this.writeSparseMetadata(dstEdit, srcEdit.cellType(), srcEdit.cellTypeArgs(), metaBytes.get()); + + if (srcEdit.metaOffset() >= dstEdit.metaOffset()) { + srcEdit.metaOffset(srcEdit.metaOffset() + shiftInsert.get()); + srcEdit.valueOffset(srcEdit.valueOffset() + shiftInsert.get()); + } + + // Copy the value bits from the old location + + this.writeFixedBinary(dstEdit.valueOffset(), this.buffer, srcEdit.valueOffset(), length); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shiftInsert.get()); + + // Delete the old location + + this.ensureSparse( + length, srcEdit, srcEdit.cellType(), srcEdit.cellTypeArgs(), RowOptions.DELETE, metaBytes, spaceNeeded, + shiftDelete); + + checkState(shiftDelete.get() < 0); + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shiftDelete.get()); + } + + /** + * Rebuild the unique index for a set/map scope. + * + * @param scope The sparse scope to rebuild an index for. + * @return Success if the index could be built, an error otherwise. + *

+ * The {@code scope} MUST be a set or map scope. + *

+ * The scope may have been built (e.g. via RowWriter) with relaxed uniqueness constraint checking. + * This operation rebuilds an index to support verification of uniqueness constraints during + * subsequent partial updates. If the appropriate uniqueness constraints cannot be established (i.e. + * a duplicate exists), this operation fails. Before continuing, the resulting scope should either: + *

    + *
  1. Be repaired (e.g. by deleting duplicates) and the index rebuild operation should be run again. + *
  2. Be deleted. The entire scope should be removed including its items. + *
+ * Failure to perform one of these actions will leave the row is potentially in a corrupted state where partial + * updates may subsequent fail. + *

+ * The target {@code scope} may or may not have already been indexed. This operation is idempotent. + */ + @Nonnull + public Result typedCollectionUniqueIndexRebuild(@Nonnull final RowCursor scope) { + + checkNotNull(scope, "expected non-null scope"); + checkArgument(scope.scopeType().isUniqueScope(), "expected unique scope type"); + checkArgument(scope.index() == 0, "expected scope index of zero"); + + RowCursor edit = scope.clone(); + + if (edit.count() <= 1) { + return Result.SUCCESS; + } + + // Compute index elements + + UniqueIndexItem[] uniqueIndex = new UniqueIndexItem[edit.count()]; + edit.metaOffset(scope.valueOffset()); + + for (; edit.index() < edit.count(); edit.index(edit.index() + 1)) { + + final RowCursor dstEdit = new RowCursor(); + this.readSparseMetadata(dstEdit); + + checkState(edit.pathOffset() == 0); + + final int elementSize = this.sparseComputeSize(edit); + + uniqueIndex[edit.index()] = new UniqueIndexItem() + .code(edit.cellType().layoutCode()) + .metaOffset(edit.metaOffset()) + .valueOffset(edit.valueOffset()) + .size(elementSize); + + edit.metaOffset(edit.metaOffset() + elementSize); + } + + // Create scratch space equal to the sum of the sizes of the scope's values. + // Implementation Note: theoretically this scratch space could be eliminated by + // performing the item move operations directly during the Insertion Sort, however, + // doing so might result in moving the same item multiple times. Under the assumption + // that items are relatively large, using scratch space requires each item to be moved + // AT MOST once. Given that row buffer memory is likely reused, scratch space is + // relatively memory efficient. + + int shift = edit.metaOffset() - scope.valueOffset(); + + // Sort and check for duplicates + + if (!this.insertionSort(scope, edit, Arrays.asList(uniqueIndex))) { + return Result.EXISTS; + } + + // Move elements + + int metaOffset = scope.valueOffset(); + this.ensure(this.length() + shift); + this.writeFixedBinary(metaOffset + shift, this.buffer, metaOffset, this.length() - metaOffset); + + for (UniqueIndexItem item : uniqueIndex) { + this.writeFixedBinary(metaOffset, this.buffer, item.metaOffset() + shift, item.size()); + metaOffset += item.size(); + } + + // Delete the scratch space (if necessary - if it doesn't just fall off the end of the row) + + if (metaOffset != this.length()) { + this.writeFixedBinary(metaOffset, this.buffer, metaOffset + shift, this.length() - metaOffset); + } + + return Result.SUCCESS; + } + + public void unsetBit(final int offset, @Nonnull final LayoutBit bit) { + checkNotNull(bit, "expected non-null bit"); + checkArgument(!bit.isInvalid()); + final int index = bit.offset(offset); + this.buffer.setByte(index, this.buffer.getByte(index) & (byte) ~(1 << bit.bit())); + } + + public int write7BitEncodedInt(final long value) { + return this.write7BitEncodedUInt(RowBuffer.rotateSignToLsb(value)); + } + + /** + * Sets the specified 64-bit integer at the current {@link RowBuffer position} as a 7-bit encoded 32-bit value. + *

+ * The 64-bit integer value is written 7-bits at a time. The high bit of the byte, when set, indicates there are + * more bytes. An {@link IllegalArgumentException} is thrown, if the specified 64-bit integer value is outside + * the range of an unsigned 32-bit integer, [0, 0x00000000FFFFFFFFL]. + * + * @param value a 64-bit integer constrained to the range of an unsigned 32-bit integer, [0, 0x00000000FFFFFFFFL] + * @return The number of bytes written + */ + public int write7BitEncodedUInt(final long value) { + checkArgument(0 <= value && value <= 0x00000000FFFFFFFFL, "expected value in range [0, %s], not %s", 0x00000000FFFFFFFFL, value); + long n = value; + int i = 0; + while (n >= 0x80L) { + this.buffer.writeByte((byte) (n | 0x80L)); + n >>>= 7; + } + this.buffer.writeByte((byte) n); + return i; + } + + public void writeDateTime(int offset, OffsetDateTime value) { + Item item = this.write(this::writeDateTime, offset, value); + } + + public void writeDecimal(int offset, BigDecimal value) { + Item item = this.write(this::writeDecimal, offset, value); + } + + public void writeFixedBinary(final int offset, @Nonnull final ByteBuf value, final int length) { + + checkNotNull(value, "expected non-null value"); + checkArgument(offset >= 0, "expected offset >= 0, not %s", offset); + checkArgument(length >= 0, "expected length >= 0, not %s", length); + + Item item = this.write(buffer -> { + int writableBytes = Math.min(length, buffer.readableBytes()); + this.buffer.writeBytes(buffer, writableBytes); + if (writableBytes < length) { + this.buffer.writeZero(length - writableBytes); + } + }, offset, value); + } + + public void writeFixedBinary(final int offset, @Nonnull final ByteBuf value, final int index, final int length) { + checkArgument(index >= 0, "expected index >= 0, not %s", index); + value.markReaderIndex().readerIndex(index); + this.writeFixedBinary(offset, value, length); + value.resetReaderIndex(); + } + + public void writeFixedBinary(final int offset, @Nonnull final byte[] value, final int index, final int length) { + + checkNotNull(value, "expected non-null value"); + checkArgument(offset >= 0, "expected offset >= 0, not %s", offset); + checkArgument(length >= 0, "expected length >= 0, not %s", length); + checkArgument(0 <= index && index < value.length, "expected in range [0, %s), not index", index); + + Item item = this.write(buffer -> { + int writableBytes = Math.min(length, buffer.length - index); + this.buffer.writeBytes(buffer, index, writableBytes); + if (writableBytes < length) { + this.buffer.writeZero(length - writableBytes); + } + }, offset, value); + } + + public void writeFixedString(final int offset, @Nonnull final Utf8String value) { + checkNotNull(value, "expected non-null value"); + checkArgument(!value.isNull(), "expected non-null value content"); + Item item = this.write(this::writeFixedString, offset, value); + } + + public void writeFloat128(int offset, Float128 value) { + this.buffer.writeLongLE(value.low()); + this.buffer.writeLongLE(value.high()); + } + + public void writeFloat32(final int offset, final float value) { + Item item = this.write(this.buffer::writeFloatLE, offset, value); + } + + public void writeFloat64(final int offset, final double value) { + Item item = this.write(this.buffer::writeDoubleLE, offset, value); + } + + public void writeGuid(final int offset, @Nonnull final UUID value) { + checkNotNull(value, "expected non-null value"); + Item item = this.write(this::writeGuid, offset, value); + } + + public void writeHeader(HybridRowHeader value) { + this.buffer.writeByte(value.version().value()); + this.buffer.writeIntLE(value.schemaId().value()); + } + + public void writeInt16(final int ignored, final short value) { + this.buffer.writeShortLE(value); + } + + public void writeInt32(final int ignored, final int value) { + this.buffer.writeIntLE(value); + } + + public void writeInt64(final int ignored, final long value) { + this.buffer.writeLongLE(value); + } + + public void writeInt8(final int ignored, final byte value) { + this.buffer.writeByte(value); + } + + @Nonnull + public RowCursor writeNullable( + @Nonnull final RowCursor edit, + @Nonnull final LayoutTypeScope scope, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + boolean hasValue) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + + final int length = this.countDefaultValue(scope, typeArgs); + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + + final int numWritten = this.writeDefaultValue(edit.valueOffset(), scope, typeArgs); + checkState(length == numWritten); + + if (hasValue) { + this.writeInt8(edit.valueOffset(), (byte) 1); + } + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + final int valueOffset = edit.valueOffset() + 1; + + RowCursor newScope = new RowCursor() + .scopeType(scope) + .scopeTypeArgs(typeArgs) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(edit.layout()) + .count(2) + .index(1); + + RowCursors.moveNext(newScope, this); + return newScope; + } + + public void writeSchemaId(final int offset, @Nonnull final SchemaId value) { + checkNotNull(value, "expected non-null value"); + this.writeInt32(offset, value.value()); + } + + @Nonnull + public RowCursor writeSparseArray( + @Nonnull final RowCursor edit, @Nonnull final LayoutTypeScope scope, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(options, "expected non-null options"); + + int length = LayoutCode.BYTES; + TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + this.writeSparseTypeCode(edit.valueOffset(), LayoutCode.END_SCOPE); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + return new RowCursor() + .scopeType(scope) + .scopeTypeArgs(typeArgs) + .start(edit.valueOffset()) + .valueOffset(edit.valueOffset()) + .metaOffset(edit.valueOffset()) + .layout(edit.layout()); + } + + public void writeSparseBinary( + @Nonnull final RowCursor edit, @Nonnull final ByteBuf value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + checkNotNull(options, "expected non-null options"); + + final int length = RowBuffer.count7BitEncodedUInt(value.readableBytes()) + value.readableBytes(); + final LayoutType type = LayoutTypes.BINARY; + + final Out metaBytes = new Out<>(); + final Out shift = new Out<>(); + final Out spaceNeeded = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeVariableBinary(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseBoolean( + @Nonnull final RowCursor edit, final boolean value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final int length = 0; + final LayoutType type = value ? LayoutTypes.BOOLEAN : LayoutTypes.BOOLEAN_FALSE; + final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, typeArgs, metaBytes.get()); + + checkState(spaceNeeded.get() == (int) metaBytes.get()); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseDateTime( + @Nonnull final RowCursor edit, @Nonnull final OffsetDateTime value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + checkNotNull(options, "expected non-null options"); + + LayoutType type = LayoutTypes.DATE_TIME; + int length = DateTimeCodec.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeDateTime(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseDecimal( + @Nonnull final RowCursor edit, @Nonnull final BigDecimal value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.DECIMAL; + final int length = DecimalCodec.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeDecimal(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseFloat128( + @Nonnull final RowCursor edit, @Nonnull final Float128 value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.FLOAT_128; + final int length = Float128.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeFloat128(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseFloat32(@Nonnull RowCursor edit, float value, @Nonnull UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.FLOAT_32; + final int length = Float.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeFloat32(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseFloat64(@Nonnull final RowCursor edit, double value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.FLOAT_64; + final int length = Double.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeFloat64(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseGuid( + @Nonnull final RowCursor edit, @Nonnull final UUID value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.GUID; + final int length = GuidCodec.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeGuid(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseInt16(@Nonnull final RowCursor edit, short value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.INT_16; + final int length = Short.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeInt16(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseInt32(@Nonnull final RowCursor edit, int value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.INT_32; + final int length = Integer.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeInt32(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseInt64(@Nonnull final RowCursor edit, long value, UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.INT_64; + final int length = Long.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeInt64(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseInt8(@Nonnull final RowCursor edit, byte value, UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final int length = Long.BYTES; + final LayoutType type = LayoutTypes.INT_8; + final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, typeArgs, metaBytes.get()); + this.writeInt8(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseNull( + @Nonnull final RowCursor edit, @Nonnull final NullValue value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final int length = 0; + final LayoutType type = LayoutTypes.NULL; + final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, typeArgs, metaBytes.get()); + + checkState(spaceNeeded.get() == (int)metaBytes.get()); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public RowCursor writeSparseObject( + @Nonnull final RowCursor edit, + @Nonnull final LayoutTypeScope scope, + @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(options, "expected non-null options"); + + int length = LayoutCode.BYTES; // end scope type code. + TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + this.writeSparseTypeCode(edit.valueOffset(), LayoutCode.END_SCOPE); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + return new RowCursor() + .scopeType(scope) + .scopeTypeArgs(TypeArgumentList.EMPTY) + .start(edit.valueOffset()) + .valueOffset(edit.valueOffset()) + .metaOffset(edit.valueOffset()) + .layout(edit.layout()); + } + + public void writeSparseString( + @Nonnull final RowCursor edit, @Nonnull final Utf8String value, @Nonnull final UpdateOptions options) { + + final LayoutType type = LayoutTypes.UTF_8; + final TypeArgumentList args = TypeArgumentList.EMPTY; + final int length = RowBuffer.count7BitEncodedUInt(value.encodedLength()) + value.encodedLength(); + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, args, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, args, metaBytes.get()); + this.write(this::writeVariableString, edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + @Nonnull + public RowCursor writeSparseTuple( + @Nonnull final RowCursor edit, + @Nonnull final LayoutTypeScope scope, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options) { + + int length = LayoutCode.BYTES * (1 + typeArgs.count()); // nulls for each element + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + + int valueOffset = edit.valueOffset(); + + for (int i = 0; i < typeArgs.count(); i++) { + this.writeSparseTypeCode(valueOffset, LayoutCode.NULL); + valueOffset += LayoutCode.BYTES; + } + + this.writeSparseTypeCode(valueOffset, LayoutCode.END_SCOPE); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + return new RowCursor() + .scopeType(scope) + .scopeTypeArgs(typeArgs) + .start(edit.valueOffset()) + .valueOffset(edit.valueOffset()) + .metaOffset(edit.valueOffset()) + .layout(edit.layout()) + .count(typeArgs.count()); + } + + public void writeSparseTypeCode(int offset, LayoutCode code) { + this.writeUInt8(offset, code.value()); + } + + @Nonnull + public RowCursor writeSparseUDT( + @Nonnull final RowCursor edit, + @Nonnull final LayoutTypeScope scope, + @Nonnull final Layout udt, + @Nonnull final UpdateOptions options) { + + TypeArgumentList typeArgs = new TypeArgumentList(udt.schemaId()); + int length = udt.size() + LayoutCode.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + this.write(this.buffer::writeZero, edit.valueOffset(), udt.size()); // clear all presence bits + + // Write scope terminator + + int valueOffset = edit.valueOffset() + udt.size(); + this.writeSparseTypeCode(valueOffset, LayoutCode.END_SCOPE); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + return new RowCursor() + .scopeType(scope) + .scopeTypeArgs(typeArgs) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(udt); + } + + public void writeSparseUInt16( + @Nonnull final RowCursor edit, final short value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final int length = Short.BYTES; + final LayoutType type = LayoutTypes.UINT_16; + final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeUInt16(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseUInt32( + @Nonnull final RowCursor edit, final int value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final int length = Integer.BYTES; + final LayoutType type = LayoutTypes.UINT_32; + final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeUInt32(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseUInt64(@Nonnull final RowCursor edit, long value, @Nonnull UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final int length = Long.BYTES; + final LayoutType type = LayoutTypes.UINT_64; + final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeUInt64(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseUInt8( + @Nonnull final RowCursor edit, final byte value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.UINT_8; + final int length = Byte.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeUInt8(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseUnixDateTime( + @Nonnull final RowCursor edit, @Nonnull final UnixDateTime value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + checkNotNull(options, "expected non-null options"); + + LayoutType type = LayoutTypes.UNIX_DATE_TIME; + final int length = UnixDateTime.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.writeUnixDateTime(edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseVarInt(@Nonnull final RowCursor edit, final long value, @Nonnull final UpdateOptions options) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(options, "expected non-null options"); + + final LayoutType type = LayoutTypes.VAR_INT; + final int length = RowBuffer.count7BitEncodedInt(value); + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, TypeArgumentList.EMPTY, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, TypeArgumentList.EMPTY, metaBytes.get()); + this.write(this::write7BitEncodedInt, edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + public void writeSparseVarUInt( + @Nonnull final RowCursor edit, final long value, @Nonnull final UpdateOptions options) { + + final LayoutType type = LayoutTypes.VAR_UINT; + final TypeArgumentList typeArgs = TypeArgumentList.EMPTY; + final int length = RowBuffer.count7BitEncodedUInt(value); + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, type, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, type, typeArgs, metaBytes.get()); + this.write(this::write7BitEncodedUInt, edit.valueOffset(), value); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + edit.endOffset(edit.metaOffset() + spaceNeeded.get()); + } + + /** + * Writes the content of the buffer on to an {@link OutputStream}. + * + * @param stream the target @{link OutputStream} + * @throws IOException if the specified {@code stream} throws an {@link IOException} during output + */ + public void writeTo(@Nonnull final OutputStream stream) throws IOException { + checkNotNull(stream, "expected non-null stream"); + this.buffer.getBytes(0, stream, this.length()); + } + + @Nonnull + public RowCursor writeTypedArray( + @Nonnull final RowCursor edit, + @Nonnull final LayoutTypeScope scope, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options) { + + final int length = Integer.BYTES; + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + this.writeUInt32(edit.valueOffset(), 0); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + int valueOffset = edit.valueOffset() + Integer.BYTES; // point after the size + + return new RowCursor() + .scopeType(scope) + .scopeTypeArgs(typeArgs) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(edit.layout()); + } + + @Nonnull + public RowCursor writeTypedMap( + @Nonnull final RowCursor edit, + @Nonnull final LayoutTypeScope scope, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options) { + + final int length = Integer.BYTES; // sized scope + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + this.writeUInt32(edit.valueOffset(), 0); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + int valueOffset = edit.valueOffset() + Integer.BYTES; // point after the size + + return new RowCursor() + .scopeType(scope) + .scopeTypeArgs(typeArgs) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(edit.layout()); + } + + @Nonnull + public RowCursor writeTypedSet( + @Nonnull final RowCursor edit, + @Nonnull final LayoutTypeScope scope, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options) { + + final int length = Integer.BYTES; + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + this.writeUInt32(edit.valueOffset(), 0); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + final int valueOffset = edit.valueOffset() + Integer.BYTES; // point after the size + + return new RowCursor() + .scopeType(scope) + .scopeTypeArgs(typeArgs) + .start(edit.valueOffset()) + .valueOffset(valueOffset) + .metaOffset(valueOffset) + .layout(edit.layout()); + } + + public RowCursor writeTypedTuple( + @Nonnull final RowCursor edit, + @Nonnull final LayoutTypeScope scope, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options) { + + final int length = this.countDefaultValue(scope, typeArgs); + + final Out metaBytes = new Out<>(); + final Out spaceNeeded = new Out<>(); + final Out shift = new Out<>(); + + final int priorLength = this.length(); + + this.ensureSparse(length, edit, scope, typeArgs, options, metaBytes, spaceNeeded, shift); + this.writeSparseMetadata(edit, scope, typeArgs, metaBytes.get()); + + final int numWritten = this.writeDefaultValue(edit.valueOffset(), scope, typeArgs); + checkState(length == numWritten); + + checkState(spaceNeeded.get() == metaBytes.get() + length); + checkState(this.length() == priorLength + shift.get()); + + final RowCursor newScope = new RowCursor() + .scopeType(scope) + .scopeTypeArgs(typeArgs) + .start(edit.valueOffset()) + .valueOffset(edit.valueOffset()) + .metaOffset(edit.valueOffset()) + .layout(edit.layout()) + .count(typeArgs.count()); + + RowCursors.moveNext(newScope, this); + return newScope; + } + + public void writeUInt16(final int offset, final short value) { + final Item item = this.write(this::writeUInt16, offset, value); + } + + public void writeUInt32(final int offset, final int value) { + final Item item = this.write(this::writeUInt32, offset, value); + } + + public void writeUInt64(final int offset, final long value) { + final Item item = this.write(this::writeUInt64, offset, value); + } + + public void writeUInt8(int offset, byte value) { + final Item item = this.write(this::writeUInt8, offset, value); + } + + public void writeUnixDateTime(int offset, UnixDateTime value) { + final Item item = this.write(this::writeUInt64, offset, value.milliseconds()); + } + + public void writeVariableBinary( + final int offset, @Nonnull final ByteBuf value, final boolean exists, @Nonnull final Out shift) { + + checkNotNull(value, "expected non-null value"); + checkNotNull(shift, "expected non-null shift"); + + final int length = value.readableBytes(); + final Out spaceNeeded = new Out<>(); + + final int priorLength = this.length(); + + this.ensureVariable(offset, false, length, exists, spaceNeeded, shift); + final Item item = this.write(this::writeVariableBinary, offset, value); + + checkState(spaceNeeded.get() == length + item.length()); + checkState(this.length() == priorLength + shift.get()); + } + + public int writeVariableInt(int offset, long value, boolean exists) { + + final int length = RowBuffer.count7BitEncodedInt(value); + final Out shift = new Out<>(); + final Out spaceNeeded = new Out<>(); + + final int priorLength = this.length(); + + this.ensureVariable(offset, true, length, exists, spaceNeeded, shift); + final Item item = this.write(this::write7BitEncodedInt, offset, value); + + checkState(item.length == length); + checkState(spaceNeeded.get() == length); + checkState(this.length() == priorLength + shift.get()); + + return shift.get(); + } + + public int writeVariableString( + final int offset, @Nonnull final Utf8String value, final boolean exists) { + + checkNotNull(value, "expected non-null value"); + checkArgument(!value.isNull(), "expected non-null value content"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + final int length = value.encodedLength(); + final Out shift = new Out<>(); + final Out spaceNeeded = new Out<>(); + + final int priorLength = this.length(); + + this.ensureVariable(offset, false, length, exists, spaceNeeded, shift); + Item item = this.write(this::writeVariableString, offset, value); + + checkState(spaceNeeded.get() == length + item.length()); + checkState(this.length() == priorLength + shift.get()); + + return shift.get(); + } + + public int writeVariableUInt(final int offset, final long value) { + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + Item item = this.write(this::write7BitEncodedUInt, offset, value); + return item.length(); + } + + public int writeVariableUInt(final int offset, final long value, final boolean exists) { + + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + final int length = RowBuffer.count7BitEncodedUInt(value); + final Out shift = new Out<>(); + final Out spaceNeeded = new Out<>(); + + final int priorLength = this.length(); + + this.ensureVariable(offset, true, length, exists, spaceNeeded, shift); + final Item item = this.write(this::write7BitEncodedUInt, offset, value); + + checkState(item.length == length); + checkState(spaceNeeded.get() == length); + checkState(this.length() == priorLength + shift.get()); + + return shift.get(); + } + + /** + * Compares the values of two encoded fields using the hybrid row binary collation. + * + * @param left An edit describing the left field. + * @param leftLength The size of the left field's value in bytes. + * @param right An edit describing the right field. + * @param rightLength The size of the right field's value in bytes. + * @return + * + * -1left less than right. + * + * 0left and right are equal. + * + * 1left is greater than right. + * + * + */ + private int compareFieldValue( + @Nonnull final RowCursor left, final int leftLength, @Nonnull final RowCursor right, final int rightLength) { + + checkNotNull(left, "expected non-null left"); + checkNotNull(right, "expected non-null right"); + checkArgument(leftLength >= 0, "expected non-negative leftLength"); + checkArgument(rightLength >= 0, "expected non-negative rightLength"); + + if (left.cellType().layoutCode().value() < right.cellType().layoutCode().value()) { + return -1; + } + + if (left.cellType() != right.cellType()) { + return 1; + } + + if (leftLength > rightLength) { + return 1; + } + + if (leftLength < rightLength) { + return -1; + } + + ByteBuf sliceLeft = this.buffer.slice(left.valueOffset(), leftLength); + ByteBuf sliceRight = this.buffer.slice(right.valueOffset(), rightLength); + + return sliceLeft.compareTo(sliceRight); + } + + /** + * Compares the values of two encoded key-value pair fields using the hybrid row binary. + * collation. + * + * @param left An edit describing the left field. + * @param right An edit describing the right field. + * @return + * + * -1left less than right. + * + * 0left and right are equal. + * + * 1left is greater than right. + * + * + */ + private int compareKeyValueFieldValue(RowCursor left, RowCursor right) { + + LayoutTypedTuple leftScopeType = left.cellType() instanceof LayoutTypedTuple + ? (LayoutTypedTuple) left.cellType() + : null; + LayoutTypedTuple rightScopeType = right.cellType() instanceof LayoutTypedTuple + ? (LayoutTypedTuple) right.cellType() + : null; + + checkArgument(leftScopeType != null); + checkArgument(rightScopeType != null); + checkArgument(left.cellTypeArgs().count() == 2); + checkArgument(left.cellTypeArgs().equals(right.cellTypeArgs())); + + RowCursor leftKey = new RowCursor(); + leftKey.layout(left.layout()); + leftKey.scopeType(leftScopeType); + leftKey.scopeTypeArgs(left.cellTypeArgs()); + leftKey.start(left.valueOffset()); + leftKey.metaOffset(left.valueOffset()); + leftKey.index(0); + + this.readSparseMetadata(leftKey); + checkState(leftKey.pathOffset() == 0); + int leftKeyLen = this.sparseComputeSize(leftKey) - (leftKey.valueOffset() - leftKey.metaOffset()); + + RowCursor rightKey = new RowCursor(); + rightKey.layout(right.layout()); + rightKey.scopeType(rightScopeType); + rightKey.scopeTypeArgs(right.cellTypeArgs()); + rightKey.start(right.valueOffset()); + rightKey.metaOffset(right.valueOffset()); + rightKey.index(0); + + this.readSparseMetadata(rightKey); + checkState(rightKey.pathOffset() == 0); + int rightKeyLen = this.sparseComputeSize(rightKey) - (rightKey.valueOffset() - rightKey.metaOffset()); + + return this.compareFieldValue(leftKey, leftKeyLen, rightKey, rightKeyLen); + } + + /** + * Compute the number of bytes necessary to store the signed integer using the varint encoding. + * + * @param value The value to be encoded + * @return The number of bytes needed to store the varint encoding of {@code value} + */ + private static int count7BitEncodedInt(long value) { + return RowBuffer.count7BitEncodedUInt(RowBuffer.rotateSignToLsb(value)); + } + + /** + * Return the size (in bytes) of the default sparse value for the type. + * + * @param code The type of the default value. + * @param typeArgs + */ + private int countDefaultValue(LayoutType code, TypeArgumentList typeArgs) { + + // TODO: JTH: convert to a virtual? + + if (code instanceof LayoutNull || code instanceof LayoutBoolean) { + return 1; + } + if (code instanceof LayoutInt8) { + return LayoutTypes.INT_8.size(); + } + if (code instanceof LayoutInt16) { + return LayoutTypes.INT_16.size(); + } + if (code instanceof LayoutInt32) { + return LayoutTypes.INT_32.size(); + } + if (code instanceof LayoutInt64) { + return LayoutTypes.INT_64.size(); + } + if (code instanceof LayoutUInt8) { + return LayoutTypes.UINT_8.size(); + } + if (code instanceof LayoutUInt16) { + return LayoutTypes.UINT_16.size(); + } + if (code instanceof LayoutUInt32) { + return LayoutTypes.UINT_32.size(); + } + if (code instanceof LayoutUInt64) { + return LayoutTypes.UINT_64.size(); + } + if (code instanceof LayoutFloat32) { + return LayoutTypes.FLOAT_32.size(); + } + if (code instanceof LayoutFloat64) { + return LayoutTypes.FLOAT_64.size(); + } + if (code instanceof LayoutFloat128) { + return LayoutTypes.FLOAT_128.size(); + } + if (code instanceof LayoutDecimal) { + return LayoutTypes.DECIMAL.size(); + } + if (code instanceof LayoutDateTime) { + return LayoutTypes.DATE_TIME.size(); + } + if (code instanceof LayoutUnixDateTime) { + return LayoutTypes.UNIX_DATE_TIME.size(); + } + if (code instanceof LayoutGuid) { + return LayoutTypes.GUID.size(); + } + if (code instanceof LayoutMongoDbObjectId) { + // return MongoDbObjectId.size(); + throw new UnsupportedOperationException(); + } + if (code instanceof LayoutUtf8 || code instanceof LayoutBinary || code instanceof LayoutVarInt || code instanceof LayoutVarUInt) { + // Variable length types preceded by their varuint size take 1 byte for a size of 0. + return 1; + } + if (code instanceof LayoutObject || code instanceof LayoutArray) { + // Variable length sparse collection scopes take 1 byte for the end-of-scope terminator. + return LayoutCode.BYTES; + } + if (code instanceof LayoutTypedArray || code instanceof LayoutTypedSet || code instanceof LayoutTypedMap) { + // Variable length typed collection scopes preceded by their scope size take sizeof(uint) for a size of 0. + return Integer.BYTES; + } + if (code instanceof LayoutTuple) { + // Fixed arity sparse collections take 1 byte for end-of-scope plus a null for each element. + return LayoutCode.BYTES + (LayoutCode.BYTES * typeArgs.count()); + } + if (code instanceof LayoutTypedTuple || code instanceof LayoutTagged || code instanceof LayoutTagged2) { + // Fixed arity typed collections take the sum of the default values of each element. The scope size is + // implied by the arity. + return typeArgs.stream() + .map(arg -> this.countDefaultValue(arg.type(), arg.typeArgs())) + .reduce(0, Integer::sum); + } + if (code instanceof LayoutNullable) { + // Nullables take the default values of the value plus null. The scope size is implied by the arity. + return 1 + this.countDefaultValue(typeArgs.get(0).type(), typeArgs.get(0).typeArgs()); + } + if (code instanceof LayoutUDT) { + Layout udt = this.resolver.resolve(typeArgs.schemaId()); + return udt.size() + LayoutCode.BYTES; + } + throw new IllegalStateException(lenientFormat("Not Implemented: %s", code)); + } + + private static int countSparsePath(@Nonnull final RowCursor edit) { + + if (!edit.writePathToken().isNull()) { + StringToken token = edit.writePathToken(); + ByteBuf varint = token.varint(); + return varint.readerIndex() + varint.readableBytes(); + } + + Optional optional = edit.layout().tokenizer().tryFindToken(edit.writePath()); + + if (optional.isPresent()) { + StringToken token = optional.get(); + edit.writePathToken(token); + ByteBuf varint = token.varint(); + return varint.readerIndex() + varint.readableBytes(); + } + + Utf8String path = edit.writePath().toUtf8(); + assert path != null; + + int numBytes = path.length(); + int sizeLenInBytes = RowBuffer.count7BitEncodedUInt(edit.layout().tokenizer().count() + numBytes); + + return sizeLenInBytes + numBytes; + } + + private void ensure(int size) { + this.buffer.ensureWritable(size); + } + + /** + * Ensure that sufficient space exists in the row buffer to write the specified value. + * + * @param length The number of bytes needed to encode the value of the field to be written. + * @param edit The prepared edit indicating where and in what context the current write will happen. + * @param type The type of the field to be written. + * @param typeArgs The type arguments of the field to be written. + * @param options The kind of edit to be performed. + * @param metaBytes On success, the number of bytes needed to encode the metadata of the new field. + * @param spaceNeeded On success, the number of bytes needed in total to encode the new field and its metadata. + * @param shift On success, the number of bytes the length of the row buffer was increased. + */ + private void ensureSparse( + final int length, + @Nonnull final RowCursor edit, + @Nonnull final LayoutType type, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final RowOptions options, + @Nonnull final Out metaBytes, + @Nonnull final Out spaceNeeded, + @Nonnull final Out shift + ) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(type, "expected non-null type"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + checkNotNull(metaBytes, "expected non-null metaBytes"); + checkNotNull(spaceNeeded, "expected non-null spaceNeeded"); + checkNotNull(shift, "expected non-null shift"); + + int metaOffset = edit.metaOffset(); + int spaceAvailable = 0; + + // Compute the metadata offsets + + if (edit.scopeType().hasImplicitTypeCode(edit)) { + metaBytes.set(0); + } else { + metaBytes.set(type.countTypeArgument(typeArgs)); + } + + if (!edit.scopeType().isIndexedScope()) { + checkState(edit.writePath() != null); + int pathLenInBytes = RowBuffer.countSparsePath(edit); + metaBytes.set(metaBytes.get() + pathLenInBytes); + } + + if (edit.exists()) { + // Compute value offset for existing value to be overwritten + spaceAvailable = this.sparseComputeSize(edit); + } + + spaceNeeded.set(options == RowOptions.DELETE ? 0 : metaBytes.get() + length); + shift.set(spaceNeeded.get() - spaceAvailable); + + // Shift the contents of the buffer tail left or right as required to snugly fit the specified value + + final int destination = metaOffset + spaceNeeded.get(); + final int source = metaOffset + spaceAvailable; + + this.shift(destination, source, this.length() - (metaOffset + spaceAvailable)); + + // Update the stored size (fixed arity scopes don't store the size because it is implied by the type args) + + if (edit.scopeType().isSizedScope() && !edit.scopeType().isFixedArity()) { + + if ((options == RowOptions.INSERT) || (options == RowOptions.INSERT_AT) || ((options == RowOptions.UPSERT) && !edit.exists())) { + // Add one to the current scope count + checkState(!edit.exists()); + this.incrementUInt32(edit.start(), 1); + edit.count(edit.count() + 1); + } else if ((options == RowOptions.DELETE) && edit.exists()) { + // Subtract one from the current scope count + checkState(this.readUInt32(edit.start()) > 0); + this.decrementUInt32(edit.start(), 1); + edit.count(edit.count() - 1); + } + } + + if (options == RowOptions.DELETE) { + edit.cellType(null); + edit.cellTypeArgs(null); + edit.exists(false); + } else { + edit.cellType(type); + edit.cellTypeArgs(typeArgs); + edit.exists(true); + } + } + + /** + * Ensure that sufficient space exists in the row buffer to write the specified value. + * + * @param length The number of bytes needed to encode the value of the field to be written. + * @param edit The prepared edit indicating where and in what context the current write will happen. + * @param type The type of the field to be written. + * @param typeArgs The type arguments of the field to be written. + * @param options The kind of edit to be performed. + * @param metaBytes On success, the number of bytes needed to encode the metadata of the new field. + * @param spaceNeeded On success, the number of bytes needed in total to encode the new field and its metadata. + * @param shift On success, the number of bytes the length of the row buffer was increased. + */ + private void ensureSparse( + final int length, + @Nonnull final RowCursor edit, + @Nonnull final LayoutType type, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + @Nonnull final Out metaBytes, + @Nonnull final Out spaceNeeded, + @Nonnull final Out shift + ) { + checkNotNull(options, "expected non-null options"); + this.ensureSparse(length, edit, type, typeArgs, RowOptions.from(options.value()), metaBytes, spaceNeeded, shift); + } + + private void ensureVariable( + final int offset, + final boolean isVarint, + final int length, + final boolean exists, + @Nonnull final Out spaceNeeded, + @Nonnull final Out shift) { + + int spaceAvailable = 0; + long existingValueBytes = exists ? 0 : this.read7BitEncodedUInt(offset); + + if (isVarint) { + spaceNeeded.set(length); + } else { + assert existingValueBytes <= Integer.MAX_VALUE; + spaceAvailable += (int) existingValueBytes; + spaceNeeded.set(length + RowBuffer.count7BitEncodedUInt(length)); + } + + shift.set(spaceNeeded.get() - spaceAvailable); + + if (shift.get() != 0) { + final int destination = offset + spaceNeeded.get(); + final int source = offset + spaceAvailable; + this.shift(destination, source, this.length() - (offset + spaceAvailable)); + } + } + + /** + * Sorts a {@code uniqueIndex} list using the hybrid row binary collation. + * + * @param scope The scope to be sorted. + * @param edit A edit that points at the scope. + * @param uniqueIndex A unique index array structure that identifies the row offsets of each + * element in the scope. + * @return true if the array was sorted, false if a duplicate was found during sorting. + *

+ * Implementation Note: + *

This method MUST guarantee that if at least one duplicate exists it will be found.

+ * Insertion Sort is used for this purpose as it guarantees that each value is eventually compared + * against its previous item in sorted order. If any two successive items are the same they must be + * duplicates. + *

+ * Other search algorithms, such as Quick Sort or Merge Sort, may offer fewer comparisons in the + * limit but don't necessarily guarantee that duplicates will be discovered. If an alternative + * algorithm is used, then an independent duplicate pass MUST be employed. + *

+ *

+ * Under the current operational assumptions, the expected cardinality of sets and maps is + * expected to be relatively small. If this assumption changes, Insertion Sort may no longer be the + * best choice. + *

+ */ + private boolean insertionSort( + @Nonnull final RowCursor scope, + @Nonnull final RowCursor edit, + @Nonnull final List uniqueIndex) { + + checkNotNull(scope, "expected non-null scope"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(uniqueIndex, "expected non-null uniqueIndex"); + + RowCursor leftEdit = edit.clone(); + RowCursor rightEdit = edit.clone(); + + for (int i = 1; i < uniqueIndex.size(); i++) { + + UniqueIndexItem x = uniqueIndex.get(i); + leftEdit.cellType(LayoutType.fromLayoutCode(x.code())); + leftEdit.metaOffset(x.metaOffset()); + leftEdit.valueOffset(x.valueOffset()); + + final int leftBytes = x.size() - (x.valueOffset() - x.metaOffset()); + + // Walk backwards searching for the insertion point for the item as position i. + int j; + for (j = i - 1; j >= 0; j--) { + UniqueIndexItem y = uniqueIndex.get(j); + rightEdit.cellType(LayoutType.fromLayoutCode(y.code())); + rightEdit.metaOffset(y.metaOffset()); + rightEdit.valueOffset(y.valueOffset()); + + int cmp; + if (scope.scopeType() instanceof LayoutTypedMap) { + cmp = this.compareKeyValueFieldValue(leftEdit.clone(), rightEdit.clone()); + } else { + int rightBytes = y.size() - (y.valueOffset() - y.metaOffset()); + cmp = this.compareFieldValue(leftEdit.clone(), leftBytes, rightEdit.clone(), rightBytes); + } + + // If there are duplicates then fail. + if (cmp == 0) { + return false; + } + + if (cmp > 0) { + break; + } + + // Swap the jth item to the right to make space for the ith item which is smaller. + uniqueIndex.set(j + 1, uniqueIndex.get(j)); + } + + // Insert the ith item into the sorted array. + uniqueIndex.set(j + 1, x); + } + + return true; + } + + private Item read(@Nonnull final Supplier reader, @Nonnull final RowCursor cursor) { + + checkNotNull(reader, "expected non-null reader"); + checkNotNull(cursor, "expected non-null cursor"); + + Item item = this.read(reader, cursor.valueOffset()); + cursor.endOffset(this.buffer.readerIndex()); + + return item; + } + + private Item read(@Nonnull final Supplier reader, final int offset) { + + checkNotNull(reader, "expected non-null reader"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + this.buffer.readerIndex(offset); + final T value = reader.get(); + + return Item.of(value, offset, this.buffer.readerIndex() - offset); + } + + private Item read(@Nonnull final Function reader, final int offset, final int length) { + + checkNotNull(reader, "expected non-null reader"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + checkArgument(length >= 0, "expected non-negative length, not %s", length); + + this.buffer.readerIndex(offset); + final T value = reader.apply(length); + final int actualLength = this.buffer.readerIndex() - offset; + + checkState(actualLength == length, "expected read of length %s, not %s", length, actualLength); + return Item.of(value, offset, actualLength); + } + + private long read7BitEncodedInt(int offset) { + Item item = this.read(this::read7BitEncodedInt, offset); + return item.value(); + } + + private long read7BitEncodedInt() { + return RowBuffer.rotateSignToMsb(this.read7BitEncodedUInt()); + } + + private long read7BitEncodedUInt(int offset) { + Item item = this.read(this::read7BitEncodedUInt, offset); + return item.value(); + } + + private long read7BitEncodedUInt() { + + long b = this.buffer.readByte() & 0xFFL; + + if (b < 0x80L) { + return b; + } + + long result = b & 0x7FL; + int shift = 7; + + do { + checkState(shift < 10 * 7); + b = this.buffer.readByte() & 0xFFL; + result |= (b & 0x7FL) << shift; + shift += 7; + } while (b >= 0x80L); + + return result; + } + + private BigDecimal readDecimal() { + return DecimalCodec.decode(this.buffer); + } + + private Utf8String readFixedString(int length) { + return Utf8String.fromUnsafe(this.buffer.readSlice(length)); + } + + private Float128 readFloat128() { + return Float128Codec.decode(this.buffer); + } + + private UUID readGuid() { + return GuidCodec.decode(this.buffer); + } + + private HybridRowHeader readHeader() { + HybridRowVersion version = HybridRowVersion.from(this.buffer.readByte()); + SchemaId schemaId = SchemaId.from(this.buffer.readIntLE()); + return new HybridRowHeader(version, schemaId); + } + + /** + * Read the metadata of an encoded sparse field. + * + * @param edit The edit structure to fill in. + * + * {@code edit.Path} + * On success, the path of the field at the given offset, otherwise + * undefined. + * + * {@code edit.MetaOffset} + * On success, the offset to the metadata of the field, otherwise a + * location to insert the field. + * + * {@code edit.cellType} + * On success, the layout code of the existing field, otherwise + * undefined. + * + * {@code edit.TypeArgs} + * On success, the type args of the existing field, otherwise + * undefined. + * + * {@code edit.ValueOffset} + * On success, the offset to the value of the field, otherwise + * undefined. + *. + */ + private void readSparseMetadata(@Nonnull final RowCursor edit) { + + checkNotNull(edit, "expected non-null edit"); + + if (edit.scopeType().hasImplicitTypeCode(edit)) { + + edit.scopeType().setImplicitTypeCode(edit); + edit.valueOffset(edit.metaOffset()); + + } else { + + int metaOffset = edit.metaOffset(); + LayoutType layoutType = this.readSparseTypeCode(metaOffset); + + edit.cellType(layoutType); + edit.valueOffset(metaOffset + LayoutCode.BYTES); + edit.cellTypeArgs(TypeArgumentList.EMPTY); + + if (edit.cellType() instanceof LayoutEndScope) { + // Reached end of current scope without finding another field. + edit.pathToken(0); + edit.pathOffset(0); + edit.valueOffset(edit.metaOffset()); + return; + } + + Out lengthInBytes = new Out<>(); + edit.cellTypeArgs(edit.cellType().readTypeArgumentList(this, edit.valueOffset(), lengthInBytes)); + edit.valueOffset(edit.valueOffset() + lengthInBytes.get()); + } + + edit.scopeType().readSparsePath(this, edit); + } + + private void readSparsePrimitiveTypeCode(@Nonnull final RowCursor edit, @Nonnull final LayoutType code) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(code, "expected non-null code"); + checkArgument(edit.exists(), "expected edit.exists value of true, not false"); + + if (edit.scopeType().hasImplicitTypeCode(edit)) { + if (edit.scopeType() instanceof LayoutNullable) { + checkState(edit.scopeTypeArgs().count() == 1); + checkState(edit.index() == 1); + checkState(edit.scopeTypeArgs().get(0).type() == code); + checkState(edit.scopeTypeArgs().get(0).typeArgs().count() == 0); + } else if (edit.scopeType().isFixedArity()) { + checkState(edit.scopeTypeArgs().count() > edit.index()); + checkState(edit.scopeTypeArgs().get(edit.index()).type() == code); + checkState(edit.scopeTypeArgs().get(edit.index()).typeArgs().count() == 0); + } else { + checkState(edit.scopeTypeArgs().count() == 1); + checkState(edit.scopeTypeArgs().get(0).type() == code); + checkState(edit.scopeTypeArgs().get(0).typeArgs().count() == 0); + } + } else { + if (code == LayoutTypes.BOOLEAN) { + final LayoutType layoutType = this.readSparseTypeCode(edit.metaOffset()); + checkState(layoutType == LayoutTypes.BOOLEAN || layoutType == LayoutTypes.BOOLEAN_FALSE); + } else { + checkState(this.readSparseTypeCode(edit.metaOffset()) == code); + } + } + + if (edit.scopeType().isIndexedScope()) { + checkState(edit.pathOffset() == 0); + checkState(edit.pathToken() == 0); + } else { + int offset = edit.metaOffset() + LayoutCode.BYTES; + Out pathLenInBytes = new Out<>(); + Out pathOffset = new Out<>(); + int token = this.readSparsePathLen(edit.layout(), offset, pathOffset, pathLenInBytes); + checkState(edit.pathOffset() == pathOffset.get()); + checkState(edit.pathToken() == token); + } + } + + private UnixDateTime readUnixDateTime() { + return new UnixDateTime(this.buffer.readLongLE()); + } + + private Utf8String readUtf8String() { + long length = this.read7BitEncodedUInt(); + checkState(length <= Integer.MAX_VALUE, "expected length <= %s, not %s", Integer.MAX_VALUE, length); + return Utf8String.fromUnsafe(this.buffer.readSlice((int)length)); + } + + private ByteBuf readVariableBinary() { + long length = this.read7BitEncodedUInt(); + checkState(length <= Integer.MAX_VALUE, "expected length <= %s, not %s", Integer.MAX_VALUE, length); + return this.buffer.readSlice((int)length).asReadOnly(); + } + + private void shift(int destination, int source, int length) { + if (source != destination) { + if (length > 0) { + this.buffer.setBytes(destination, this.buffer, source, length); + } + this.buffer.writerIndex(destination + length); + } + } + + /** + * Skip over a nested scope. + * + * @param edit The sparse scope to search + * @return The zero-based byte offset immediately following the scope end marker + */ + private int skipScope(RowCursor edit) { + + //noinspection StatementWithEmptyBody + while (this.sparseIteratorMoveNext(edit)) { + } + + if (!edit.scopeType().isSizedScope()) { + edit.metaOffset(edit.metaOffset() + LayoutCode.BYTES); // move past end of scope marker + } + + return edit.metaOffset(); + } + + /** + * Compute the size of a sparse (primitive) field. + * + * @param type The type of the current sparse field. + * @param metaOffset The zero-based offset from the beginning of the row where the field begins. + * @param valueOffset The zero-based offset from the beginning of the row where the field's value begins. + * @return The length (in bytes) of the encoded field including the metadata and the value. + */ + private int sparseComputePrimitiveSize(LayoutType type, int metaOffset, int valueOffset) { + + // TODO: JTH: convert to a virtual? + + int metaBytes = valueOffset - metaOffset; + LayoutCode code = type.layoutCode(); + + switch (code) { + case NULL: + checkState(LayoutTypes.NULL.size() == 0); + return metaBytes; + + case BOOLEAN: + case BOOLEAN_FALSE: + checkState(LayoutTypes.BOOLEAN.size() == 0); + return metaBytes; + + case INT_8: + return metaBytes + LayoutTypes.INT_8.size(); + + case INT_16: + return metaBytes + LayoutTypes.INT_16.size(); + + case INT_32: + return metaBytes + LayoutTypes.INT_32.size(); + + case INT_64: + return metaBytes + LayoutTypes.INT_64.size(); + + case UINT_8: + return metaBytes + LayoutTypes.UINT_8.size(); + + case UINT_16: + return metaBytes + LayoutTypes.UINT_16.size(); + + case UINT_32: + return metaBytes + LayoutTypes.UINT_32.size(); + + case UINT_64: + return metaBytes + LayoutTypes.UINT_64.size(); + + case FLOAT_32: + return metaBytes + LayoutTypes.FLOAT_32.size(); + + case FLOAT_64: + return metaBytes + LayoutTypes.FLOAT_64.size(); + + case FLOAT_128: + return metaBytes + LayoutTypes.FLOAT_128.size(); + + case DECIMAL: + return metaBytes + LayoutTypes.DECIMAL.size(); + + case DATE_TIME: + return metaBytes + LayoutTypes.DATE_TIME.size(); + + case UNIX_DATE_TIME: + return metaBytes + LayoutTypes.UNIX_DATE_TIME.size(); + + case GUID: + return metaBytes + LayoutTypes.GUID.size(); + + case MONGODB_OBJECT_ID: + // return metaBytes + MongoDbObjectId.size(); + throw new UnsupportedOperationException(); + + case UTF_8: + case BINARY: { + Item item = this.read(this::read7BitEncodedUInt, metaOffset + metaBytes); + return metaBytes + item.length() + item.value().intValue(); + } + case VAR_INT: + case VAR_UINT: { + Item item = this.read(this::read7BitEncodedUInt, metaOffset + metaBytes); + return metaBytes + item.length(); + } + default: + throw new IllegalStateException(lenientFormat("Not Implemented: %s", code)); + } + } + + /** + * Compute the size of a sparse field. + * + * @param edit The edit structure describing the field to measure. + * @return The length (in bytes) of the encoded field including the metadata and the value. + */ + private int sparseComputeSize(RowCursor edit) { + + if (!(edit.cellType() instanceof LayoutTypeScope)) { + return this.sparseComputePrimitiveSize(edit.cellType(), edit.metaOffset(), edit.valueOffset()); + } + + // Compute offset to end of value for current value + RowCursor newScope = this.sparseIteratorReadScope(edit, true); + return this.skipScope(newScope) - edit.metaOffset(); + } + + /** + * Reads and validates the header of the current {@link RowBuffer}. + * + * @return {@code true} if the header validation succeeded; otherwise, if the header is invalid, {@code false} + */ + private boolean validateHeader(@Nonnull final HybridRowVersion version) { + + checkNotNull(version, "expected non-null version"); + + final Item item = this.read(this::readHeader, 0); + final HybridRowHeader header = item.value(); + final Layout layout = this.resolver.resolve(header.schemaId()); + + checkState(header.schemaId().equals(layout.schemaId())); + return header.version().equals(version) && (HybridRowHeader.BYTES + layout.size()) <= this.length(); + } + + private Item write(@Nonnull final Consumer consumer, final int offset, @Nonnull final T value) { + + checkNotNull(consumer, "expected non-null consumer"); + checkNotNull(value, "expected non-null value"); + checkArgument(offset >= 0, "expected non-negative offset"); + + final int priorWriterIndex = this.buffer.writerIndex(); + this.buffer.writerIndex(offset); + final int length; + + try { + consumer.accept(value); + length = this.buffer.writerIndex() - offset; + } finally { + if (priorWriterIndex > this.buffer.writerIndex()) { + this.buffer.writerIndex(priorWriterIndex); + } + } + + return new Item<>(value, offset, length); + } + + private void writeDateTime(OffsetDateTime value) { + DateTimeCodec.encode(value, this.buffer); + } + + private void writeDecimal(BigDecimal value) { + DecimalCodec.encode(value, this.buffer); + } + + private int writeDefaultValue(int offset, LayoutType code, TypeArgumentList typeArgs) { + + // TODO: DANOBLE: Put default values in a central location (LayoutTypes?) and use them in this method + // ensure that there are no null default values (which this method currently uses) + // TODO: JTH: convert to a virtual? + + if (code == LayoutTypes.NULL) { + this.writeSparseTypeCode(offset, code.layoutCode()); + return 1; + } + + if (code == LayoutTypes.BOOLEAN) { + this.writeSparseTypeCode(offset, LayoutCode.BOOLEAN_FALSE); + return 1; + } + + if (code == LayoutTypes.INT_8) { + this.writeInt8(offset, (byte) 0); + return LayoutTypes.INT_8.size(); + } + + if (code == LayoutTypes.INT_16) { + this.writeInt16(offset, (short) 0); + return LayoutTypes.INT_16.size(); + } + + if (code == LayoutTypes.INT_32) { + this.writeInt32(offset, 0); + return LayoutTypes.INT_32.size(); + } + + if (code == LayoutTypes.INT_64) { + this.writeInt64(offset, 0); + return LayoutTypes.INT_64.size(); + } + + if (code == LayoutTypes.UINT_8) { + this.writeUInt8(offset, (byte) 0); + return LayoutTypes.UINT_8.size(); + } + + if (code == LayoutTypes.UINT_16) { + this.writeUInt16(offset, (short) 0); + return LayoutTypes.UINT_16.size(); + } + + if (code == LayoutTypes.UINT_32) { + this.writeUInt32(offset, 0); + return LayoutTypes.UINT_32.size(); + } + + if (code == LayoutTypes.UINT_64) { + this.writeUInt64(offset, 0); + return LayoutTypes.UINT_64.size(); + } + + if (code == LayoutTypes.FLOAT_32) { + this.writeFloat32(offset, 0); + return LayoutTypes.FLOAT_32.size(); + } + + if (code == LayoutTypes.FLOAT_64) { + this.writeFloat64(offset, 0); + return LayoutTypes.FLOAT_64.size(); + } + + if (code == LayoutTypes.FLOAT_128) { + this.writeFloat128(offset, Float128.ZERO); + return LayoutTypes.FLOAT_128.size(); + } + + if (code == LayoutTypes.DECIMAL) { + this.writeDecimal(offset, BigDecimal.ZERO); + return LayoutTypes.DECIMAL.size(); + } + + if (code == LayoutTypes.DATE_TIME) { + this.writeDateTime(offset, OffsetDateTime.MIN); + return LayoutTypes.DATE_TIME.size(); + } + + if (code == LayoutTypes.UNIX_DATE_TIME) { + this.writeUnixDateTime(offset, UnixDateTime.EPOCH); + return LayoutTypes.UNIX_DATE_TIME.size(); + } + + if (code == LayoutTypes.GUID) { + this.writeGuid(offset, GuidCodec.EMPTY); + return LayoutTypes.GUID.size(); + } + + if (code == LayoutTypes.MONGODB_OBJECT_ID) { + // TODO: DANOBLE: Add support for LayoutTypes.MONGODB_OBJECT_ID + // this.writeMongoDbObjectId(offset, null); + // return MongoDbObjectId.Size; + throw new UnsupportedOperationException(); + } + + if (code == LayoutTypes.UTF_8 || code == LayoutTypes.BINARY || code == LayoutTypes.VAR_INT || code == LayoutTypes.VAR_UINT) { + // Variable length types preceded by their varuint size take 1 byte for a size of 0 + return this.write(this::write7BitEncodedUInt, offset, 0L).length(); + } + + if (code == LayoutTypes.OBJECT || code == LayoutTypes.ARRAY) { + // Variable length sparse collection scopes take 1 byte for the end-of-scope terminator. + this.writeSparseTypeCode(offset, LayoutCode.END_SCOPE); + return LayoutCode.BYTES; + } + + if (code == LayoutTypes.TYPED_ARRAY || code == LayoutTypes.TYPED_SET || code == LayoutTypes.TYPED_MAP) { + // Variable length typed collection scopes preceded by their scope size take sizeof(uint) for a size of 0. + this.writeUInt32(offset, 0); + return Integer.BYTES; + } + + if (code == LayoutTypes.TUPLE) { + // Fixed arity sparse collections take 1 byte for end-of-scope plus a null for each element. + for (int i = 0; i < typeArgs.count(); i++) { + this.writeSparseTypeCode(offset, LayoutCode.NULL); + } + this.writeSparseTypeCode(offset, LayoutCode.END_SCOPE); + return LayoutCode.BYTES + (LayoutCode.BYTES * typeArgs.count()); + } + + if (code == LayoutTypes.TYPED_TUPLE || code == LayoutTypes.TAGGED || code == LayoutTypes.TAGGED_2) { + // Fixed arity typed collections take the sum of the default values of each element. The scope size is + // implied by the arity. + int sum = 0; + for (final Iterator iterator = typeArgs.stream().iterator(); iterator.hasNext(); ) { + final TypeArgument arg = iterator.next(); + sum += this.writeDefaultValue(offset + sum, arg.type(), arg.typeArgs()); + } + return sum; + } + + if (code == LayoutTypes.NULLABLE) { + // Nullables take the default values of the value plus null. The scope size is implied by the arity. + this.writeInt8(offset, (byte) 0); + return 1 + this.writeDefaultValue(offset + 1, typeArgs.get(0).type(), typeArgs.get(0).typeArgs()); + } + + if (code == LayoutTypes.UDT) { + + // Clear all presence bits + Layout udt = this.resolver.resolve(typeArgs.schemaId()); + this.write(this.buffer::writeZero, offset, udt.size()); + + // Write scope terminator + this.writeSparseTypeCode(offset + udt.size(), LayoutCode.END_SCOPE); + return udt.size() + LayoutCode.BYTES; + } + + throw new IllegalStateException(lenientFormat("Not Implemented: %s", code)); + } + + private void writeFixedString(Utf8String value) { + this.buffer.writeBytes(value.content(), value.encodedLength()); + } + + private void writeGuid(UUID value) { + GuidCodec.encode(value, this.buffer); + } + + private void writeSparseMetadata( + @Nonnull final RowCursor edit, @Nonnull final LayoutType cellType, @Nonnull final TypeArgumentList typeArgs, + final int metaBytes) { + + int metaOffset = edit.metaOffset(); + + if (!edit.scopeType().hasImplicitTypeCode(edit)) { + metaOffset += cellType.writeTypeArgument(this, metaOffset, typeArgs); + } + + this.writeSparsePath(edit, metaOffset); + edit.valueOffset(edit.metaOffset() + metaBytes); + + checkState(edit.valueOffset() == edit.metaOffset() + metaBytes); + } + + private void writeSparsePath(@Nonnull final RowCursor edit, final int offset) { + + checkNotNull(edit, "expected non-null edit"); + checkArgument(offset >= 0, "expected non-negative offset"); + + // Some scopes don't encode paths, therefore the cost is always zero + + if (edit.scopeType().isIndexedScope()) { + edit.pathToken(0); + edit.pathOffset(0); + return; + } + + final StringTokenizer tokenizer = edit.layout().tokenizer(); + final Optional writePathToken = tokenizer.tryFindToken(edit.writePath()); + + checkState(!(writePathToken.isPresent() && edit.writePathToken().isNull())); + + if (!edit.writePathToken().isNull()) { + this.write(this.buffer::writeBytes, offset, edit.writePathToken().varint()); + edit.pathToken((int) edit.writePathToken().id()); + edit.pathOffset(offset); + } else { + // TODO: It would be better if we could avoid allocating here when the path is UTF16 + Utf8String writePath = edit.writePath().toUtf8(); + checkState(writePath != null); + edit.pathToken(tokenizer.count() + writePath.encodedLength()); + Item item = this.write(this::write7BitEncodedUInt, offset, (long) edit.pathToken()); + edit.pathOffset(offset + item.length()); + this.write(this::writeFixedString, edit.pathOffset(), writePath); + } + } + + + private void writeUInt16(Short value) { + this.buffer.writeShortLE(value); + } + + private void writeUInt32(Integer value) { + this.buffer.writeIntLE(value); + } + + private void writeUInt64(Long value) { + this.buffer.writeLongLE(value); + } + + private void writeUInt8(Byte value) { + this.buffer.writeByte(value); + } + + private int writeVariableBinary(int offset, ByteBuf value) { + Item item = this.write(this::writeVariableBinary, offset, value); + return item.length(); + } + + private void writeVariableBinary(ByteBuf value) { + this.write7BitEncodedUInt(value.readableBytes()); + this.buffer.writeBytes(value); + } + + private void writeVariableString(@Nonnull final Utf8String value) { + final int length = this.write7BitEncodedUInt((long) value.encodedLength()); + assert length == value.encodedLength(); + assert value.content() != null; + this.buffer.writeBytes(value.content().readerIndex(0)); + } + + private static class Item { + + private int length; + private int offset; + private T value; + + private Item(T value, int offset, int length) { + this.value = value; + this.offset = offset; + this.length = length; + } + + public int length() { + return this.length; + } + + public static Item of(T value, int offset, int length) { + return new Item<>(value, offset, length); + } + + public int offset() { + return this.offset; + } + + public T value() { + return this.value; + } + } + + /** + * Represents a single item within a set/map scope that needs to be indexed. + *

+ * This structure is used when rebuilding a set/map index during row streaming via {@link RowWriter}. Each item + * encodes its offsets and length within the row. + */ + static final class UniqueIndexItem { + + private LayoutCode code = LayoutCode.values()[0]; + private int metaOffset; + private int size; + private int valueOffset; + + /** + * The layout code of the value. + */ + public LayoutCode code() { + return this.code; + } + + public UniqueIndexItem code(LayoutCode code) { + this.code = code; + return this; + } + + /** + * If existing, the offset to the metadata of the existing field, otherwise the location to insert a new field. + */ + public int metaOffset() { + return this.metaOffset; + } + + public UniqueIndexItem metaOffset(int metaOffset) { + this.metaOffset = metaOffset; + return this; + } + + /** + * Size of the target element. + */ + public int size() { + return this.size; + } + + public UniqueIndexItem size(int size) { + this.size = size; + return this; + } + + /** + * If existing, the offset to the value of the existing field, otherwise undefined. + */ + public int valueOffset() { + return this.valueOffset; + } + + public UniqueIndexItem valueOffset(int valueOffset) { + this.valueOffset = valueOffset; + return this; + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursor.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursor.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursor.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursor.java index f922c10..1d2ae49 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursor.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursor.java @@ -1,415 +1,415 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import com.azure.data.cosmos.core.UtfAnyString; -import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutEndScope; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTuple; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutType; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypeScope; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypes; -import com.azure.data.cosmos.serialization.hybridrow.layouts.StringToken; -import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgument; -import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; - -import static com.google.common.base.Strings.lenientFormat; - -public final class RowCursor implements Cloneable { - - private LayoutType cellType; - private TypeArgumentList cellTypeArgs; - private int count; - private boolean deferUniqueIndex; - private int endOffset; - private boolean exists; - private boolean immutable; - private int index; - private Layout layout; - private int metaOffset; - private int pathOffset; - private int pathToken; - private LayoutTypeScope scopeType; - private TypeArgumentList scopeTypeArgs; - private int start; - private int valueOffset; - private UtfAnyString writePath; - private StringToken writePathToken; - - RowCursor() { - } - - /** - * If existing, the layout code of the existing field, otherwise undefined. - * - * @return layout code. - */ - public LayoutType cellType() { - return this.cellType; - } - - /** - * Sets the layout type of an existing field. - * - * @param value a {@link LayoutType}. - * @return a reference to this {@link RowCursor}. - */ - public RowCursor cellType(LayoutType value) { - this.cellType = value; - return this; - } - - /** - * For types with generic parameters (e.g. {@link LayoutTuple}, the type parameters. - * - * @return a {@link TypeArgumentList} or {@code null}. - */ - public TypeArgumentList cellTypeArgs() { - return this.cellTypeArgs; - } - - /** - * Sets the layout type arguments of an existing field. - * - * @param value a {@link TypeArgumentList} or {@code null}. - * @return a reference to this {@link RowCursor}. - */ - public RowCursor cellTypeArgs(TypeArgumentList value) { - this.cellTypeArgs = value; - return this; - } - - public RowCursor clone() { - try { - return (RowCursor) super.clone(); - } catch (CloneNotSupportedException error) { - throw new IllegalStateException(error); - } - } - - /** - * For sized scopes (e.g. Typed Array), the number of elements. - * - * @return the number of elements or zero. - */ - public int count() { - return this.count; - } - - /** - * Sets the number of elements for a sized scope. - * - * @param count the number of elements for a sized scope. - * @return a reference to this {@link RowCursor}. - */ - public RowCursor count(int count) { - this.count = count; - return this; - } - - public static RowCursor create(RowBuffer row) { - - final SchemaId schemaId = row.readSchemaId(1); - final Layout layout = row.resolver().resolve(schemaId); - final int offset = row.computeVariableValueOffset(layout, HybridRowHeader.BYTES, layout.numVariable()); - - return new RowCursor() - .layout(layout) - .scopeType(LayoutTypes.UDT) - .scopeTypeArgs(new TypeArgumentList(schemaId)) - .start(HybridRowHeader.BYTES) - .metaOffset(offset) - .valueOffset(offset); - } - - public static RowCursor createForAppend(RowBuffer row) { - - final SchemaId schemaId = row.readSchemaId(1); - final Layout layout = row.resolver().resolve(schemaId); - - return new RowCursor() - .layout(layout) - .scopeType(LayoutTypes.UDT) - .scopeTypeArgs(new TypeArgumentList(schemaId)) - .start(HybridRowHeader.BYTES) - .metaOffset(row.length()) - .valueOffset(row.length()); - } - - /** - * If true, this scope is a unique index scope whose index will be built after its items are written. - * - * @return {@code true}, if this cursor identifies a unique index scope, otherwise {@code false}. - */ - public boolean deferUniqueIndex() { - return this.deferUniqueIndex; - } - - /** - * Sets a value that indicates whether this cursor identifies a unique index scope. - * - * @param value {@code true}, if this cursor identifies a unique index scope, otherwise {@code false}. - * @return a reference to this {@link RowCursor}. - */ - public RowCursor deferUniqueIndex(boolean value) { - this.deferUniqueIndex = value; - return this; - } - - /** - * If existing, the offset to the end of the existing field. - *

- * This value is used as a hint when skipping forward. - * - * @return offset of the end of an existing field. - */ - public int endOffset() { - return this.endOffset; - } - - /** - * Sets a value that indicates whether this cursor identifies a unique index scope. - * - * @param value {@code true}, if this cursor identifies a unique index scope, otherwise {@code false}. - * @return a reference to this {@link RowCursor}. - */ - public RowCursor endOffset(int value) { - this.endOffset = value; - return this; - } - - /** - * {@code true} if an existing field matching the search criteria was found. - * - * @return {@code true} if an existing field matching the search criteria was found, otherwise {@code false}. - */ - public boolean exists() { - return this.exists; - } - - /** - * Sets a value that indicates whether this cursor identifies a field matching search criteria. - * - * @param value {@code true}, if this cursor identifies a field matching search criteria, otherwise {@code false}. - * @return a reference to this {@link RowCursor}. - */ - public RowCursor exists(boolean value) { - this.exists = value; - return this; - } - - /** - * If {@code true}, this scope's nested fields cannot be updated individually. - *

- * The entire scope can still be replaced. - * - * @return {@code true} if this scope's nested fields cannot be updated individually, otherwise {@code false}. - */ - public boolean immutable() { - return this.immutable; - } - - /** - * Sets a flag indicated whether this scope's nested fields cannot be updated individually. - *

- * The entire scope can still be replaced. - * - * @param value {@code true} if this scope's nested fields cannot be updated individually, otherwise {@code false}. - * @return a reference to this {@link RowCursor}. - */ - public RowCursor immutable(boolean value) { - this.immutable = value; - return this; - } - - /** - * For indexed scopes (e.g. an Array scope), the zero-based index into the scope of the sparse field. - * - * @return the zero-based index into the scope of the sparse field. - */ - public int index() { - return this.index; - } - - /** - * Sets the zero-based index into the scope of a sparse field in an indexed scope (e.g. an Array scope). - * - * @param value the zero-based index into the scope of the sparse field. - * @return a reference to this {@link RowCursor}. - */ - public RowCursor index(int value) { - this.index = value; - return this; - } - - /** - * The layout describing the contents of the scope, or {@code null} if the scope is unschematized. - * - * @return layout describing the context of the scope, or {@code null} if the scope is unschematized. - */ - public Layout layout() { - return this.layout; - } - - public RowCursor layout(Layout value) { - this.layout = value; - return this; - } - - /** - * If existing, offset to the metadata of the existing field, otherwise the location to insert a new field. - * - * @return offset to the metadata of an existing field or the location to insert a new field. - */ - public int metaOffset() { - return this.metaOffset; - } - - public RowCursor metaOffset(final int value) { - this.metaOffset = value; - return this; - } - - /** - * If existing, the offset scope relative path for reading. - * - * @return If existing, the offset scope relative path for reading. - */ - public int pathOffset() { - return this.pathOffset; - } - - public RowCursor pathOffset(final int value) { - this.pathOffset = value; - return this; - } - - /** - * If existing, the layout string token of scope relative path for reading. - * - * @return If existing, the layout string token of scope relative path for reading. - */ - public int pathToken() { - return this.pathToken; - } - - public RowCursor pathToken(int value) { - this.pathToken = value; - return this; - } - - /** - * The kind of scope within which this edit was prepared. - * - * @return The kind of scope within which this edit was prepared. - */ - public LayoutTypeScope scopeType() { - return this.scopeType; - } - - public RowCursor scopeType(LayoutTypeScope scopeType) { - this.scopeType = scopeType; - return this; - } - - /** - * The type parameters of the scope within which this edit was prepared. - * - * @return The type parameters of the scope within which this edit was prepared. - */ - public TypeArgumentList scopeTypeArgs() { - return this.scopeTypeArgs; - } - - public RowCursor scopeTypeArgs(TypeArgumentList scopeTypeArgs) { - this.scopeTypeArgs = scopeTypeArgs; - return this; - } - - /** - * The 0-based offset from the beginning of the row where the first sparse field within the scope begins. - * - * @return 0-based offset from the beginning of the row where the first sparse field within the scope begins. - */ - public int start() { - return this.start; - } - - public RowCursor start(int start) { - this.start = start; - return this; - } - - @Override - public String toString() { - - try { - - if (this.scopeType() == null) { - return ""; - } - - TypeArgument scopeTypeArg = (this.scopeType() instanceof LayoutEndScope) - ? TypeArgument.NONE - : new TypeArgument(this.scopeType(), this.scopeTypeArgs()); - - TypeArgument typeArg = (this.cellType() == null) || (this.cellType() instanceof LayoutEndScope) - ? TypeArgument.NONE - : new TypeArgument(this.cellType(), this.cellTypeArgs()); - - String pathOrIndex = this.writePath().isNull() ? String.valueOf(this.index()) : this.writePath().toString(); - - return lenientFormat("%s[%s] : %s@%s/%s%s", - scopeTypeArg, - pathOrIndex, - typeArg, - this.metaOffset(), - this.valueOffset(), - this.immutable() ? " immutable" : ""); - - } catch (Exception ignored) { - return ""; - } - } - - /** - * If existing, the offset to the value of the existing field, otherwise undefined. - * - * @return If existing, the offset to the value of the existing field, otherwise undefined. - */ - public int valueOffset() { - return this.valueOffset; - } - - public RowCursor valueOffset(int valueOffset) { - this.valueOffset = valueOffset; - return this; - } - - /** - * If existing, the scope relative path for writing. - * - * @return If existing, the scope relative path for writing. - */ - public UtfAnyString writePath() { - return this.writePath; - } - - public void writePath(UtfAnyString writePath) { - this.writePath = writePath; - } - - /** - * If {@link #writePath} is tokenized, then its token. - * - * @return if {@link #writePath} is tokenized, then its token. - */ - public StringToken writePathToken() { - return this.writePathToken; - } - - public void writePathToken(StringToken writePathToken) { - this.writePathToken = writePathToken; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import com.azure.data.cosmos.core.UtfAnyString; +import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutEndScope; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTuple; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutType; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypeScope; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypes; +import com.azure.data.cosmos.serialization.hybridrow.layouts.StringToken; +import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgument; +import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; + +import static com.google.common.base.Strings.lenientFormat; + +public final class RowCursor implements Cloneable { + + private LayoutType cellType; + private TypeArgumentList cellTypeArgs; + private int count; + private boolean deferUniqueIndex; + private int endOffset; + private boolean exists; + private boolean immutable; + private int index; + private Layout layout; + private int metaOffset; + private int pathOffset; + private int pathToken; + private LayoutTypeScope scopeType; + private TypeArgumentList scopeTypeArgs; + private int start; + private int valueOffset; + private UtfAnyString writePath; + private StringToken writePathToken; + + RowCursor() { + } + + /** + * If existing, the layout code of the existing field, otherwise undefined. + * + * @return layout code. + */ + public LayoutType cellType() { + return this.cellType; + } + + /** + * Sets the layout type of an existing field. + * + * @param value a {@link LayoutType}. + * @return a reference to this {@link RowCursor}. + */ + public RowCursor cellType(LayoutType value) { + this.cellType = value; + return this; + } + + /** + * For types with generic parameters (e.g. {@link LayoutTuple}, the type parameters. + * + * @return a {@link TypeArgumentList} or {@code null}. + */ + public TypeArgumentList cellTypeArgs() { + return this.cellTypeArgs; + } + + /** + * Sets the layout type arguments of an existing field. + * + * @param value a {@link TypeArgumentList} or {@code null}. + * @return a reference to this {@link RowCursor}. + */ + public RowCursor cellTypeArgs(TypeArgumentList value) { + this.cellTypeArgs = value; + return this; + } + + public RowCursor clone() { + try { + return (RowCursor) super.clone(); + } catch (CloneNotSupportedException error) { + throw new IllegalStateException(error); + } + } + + /** + * For sized scopes (e.g. Typed Array), the number of elements. + * + * @return the number of elements or zero. + */ + public int count() { + return this.count; + } + + /** + * Sets the number of elements for a sized scope. + * + * @param count the number of elements for a sized scope. + * @return a reference to this {@link RowCursor}. + */ + public RowCursor count(int count) { + this.count = count; + return this; + } + + public static RowCursor create(RowBuffer row) { + + final SchemaId schemaId = row.readSchemaId(1); + final Layout layout = row.resolver().resolve(schemaId); + final int offset = row.computeVariableValueOffset(layout, HybridRowHeader.BYTES, layout.numVariable()); + + return new RowCursor() + .layout(layout) + .scopeType(LayoutTypes.UDT) + .scopeTypeArgs(new TypeArgumentList(schemaId)) + .start(HybridRowHeader.BYTES) + .metaOffset(offset) + .valueOffset(offset); + } + + public static RowCursor createForAppend(RowBuffer row) { + + final SchemaId schemaId = row.readSchemaId(1); + final Layout layout = row.resolver().resolve(schemaId); + + return new RowCursor() + .layout(layout) + .scopeType(LayoutTypes.UDT) + .scopeTypeArgs(new TypeArgumentList(schemaId)) + .start(HybridRowHeader.BYTES) + .metaOffset(row.length()) + .valueOffset(row.length()); + } + + /** + * If true, this scope is a unique index scope whose index will be built after its items are written. + * + * @return {@code true}, if this cursor identifies a unique index scope, otherwise {@code false}. + */ + public boolean deferUniqueIndex() { + return this.deferUniqueIndex; + } + + /** + * Sets a value that indicates whether this cursor identifies a unique index scope. + * + * @param value {@code true}, if this cursor identifies a unique index scope, otherwise {@code false}. + * @return a reference to this {@link RowCursor}. + */ + public RowCursor deferUniqueIndex(boolean value) { + this.deferUniqueIndex = value; + return this; + } + + /** + * If existing, the offset to the end of the existing field. + *

+ * This value is used as a hint when skipping forward. + * + * @return offset of the end of an existing field. + */ + public int endOffset() { + return this.endOffset; + } + + /** + * Sets a value that indicates whether this cursor identifies a unique index scope. + * + * @param value {@code true}, if this cursor identifies a unique index scope, otherwise {@code false}. + * @return a reference to this {@link RowCursor}. + */ + public RowCursor endOffset(int value) { + this.endOffset = value; + return this; + } + + /** + * {@code true} if an existing field matching the search criteria was found. + * + * @return {@code true} if an existing field matching the search criteria was found, otherwise {@code false}. + */ + public boolean exists() { + return this.exists; + } + + /** + * Sets a value that indicates whether this cursor identifies a field matching search criteria. + * + * @param value {@code true}, if this cursor identifies a field matching search criteria, otherwise {@code false}. + * @return a reference to this {@link RowCursor}. + */ + public RowCursor exists(boolean value) { + this.exists = value; + return this; + } + + /** + * If {@code true}, this scope's nested fields cannot be updated individually. + *

+ * The entire scope can still be replaced. + * + * @return {@code true} if this scope's nested fields cannot be updated individually, otherwise {@code false}. + */ + public boolean immutable() { + return this.immutable; + } + + /** + * Sets a flag indicated whether this scope's nested fields cannot be updated individually. + *

+ * The entire scope can still be replaced. + * + * @param value {@code true} if this scope's nested fields cannot be updated individually, otherwise {@code false}. + * @return a reference to this {@link RowCursor}. + */ + public RowCursor immutable(boolean value) { + this.immutable = value; + return this; + } + + /** + * For indexed scopes (e.g. an Array scope), the zero-based index into the scope of the sparse field. + * + * @return the zero-based index into the scope of the sparse field. + */ + public int index() { + return this.index; + } + + /** + * Sets the zero-based index into the scope of a sparse field in an indexed scope (e.g. an Array scope). + * + * @param value the zero-based index into the scope of the sparse field. + * @return a reference to this {@link RowCursor}. + */ + public RowCursor index(int value) { + this.index = value; + return this; + } + + /** + * The layout describing the contents of the scope, or {@code null} if the scope is unschematized. + * + * @return layout describing the context of the scope, or {@code null} if the scope is unschematized. + */ + public Layout layout() { + return this.layout; + } + + public RowCursor layout(Layout value) { + this.layout = value; + return this; + } + + /** + * If existing, offset to the metadata of the existing field, otherwise the location to insert a new field. + * + * @return offset to the metadata of an existing field or the location to insert a new field. + */ + public int metaOffset() { + return this.metaOffset; + } + + public RowCursor metaOffset(final int value) { + this.metaOffset = value; + return this; + } + + /** + * If existing, the offset scope relative path for reading. + * + * @return If existing, the offset scope relative path for reading. + */ + public int pathOffset() { + return this.pathOffset; + } + + public RowCursor pathOffset(final int value) { + this.pathOffset = value; + return this; + } + + /** + * If existing, the layout string token of scope relative path for reading. + * + * @return If existing, the layout string token of scope relative path for reading. + */ + public int pathToken() { + return this.pathToken; + } + + public RowCursor pathToken(int value) { + this.pathToken = value; + return this; + } + + /** + * The kind of scope within which this edit was prepared. + * + * @return The kind of scope within which this edit was prepared. + */ + public LayoutTypeScope scopeType() { + return this.scopeType; + } + + public RowCursor scopeType(LayoutTypeScope scopeType) { + this.scopeType = scopeType; + return this; + } + + /** + * The type parameters of the scope within which this edit was prepared. + * + * @return The type parameters of the scope within which this edit was prepared. + */ + public TypeArgumentList scopeTypeArgs() { + return this.scopeTypeArgs; + } + + public RowCursor scopeTypeArgs(TypeArgumentList scopeTypeArgs) { + this.scopeTypeArgs = scopeTypeArgs; + return this; + } + + /** + * The 0-based offset from the beginning of the row where the first sparse field within the scope begins. + * + * @return 0-based offset from the beginning of the row where the first sparse field within the scope begins. + */ + public int start() { + return this.start; + } + + public RowCursor start(int start) { + this.start = start; + return this; + } + + @Override + public String toString() { + + try { + + if (this.scopeType() == null) { + return ""; + } + + TypeArgument scopeTypeArg = (this.scopeType() instanceof LayoutEndScope) + ? TypeArgument.NONE + : new TypeArgument(this.scopeType(), this.scopeTypeArgs()); + + TypeArgument typeArg = (this.cellType() == null) || (this.cellType() instanceof LayoutEndScope) + ? TypeArgument.NONE + : new TypeArgument(this.cellType(), this.cellTypeArgs()); + + String pathOrIndex = this.writePath().isNull() ? String.valueOf(this.index()) : this.writePath().toString(); + + return lenientFormat("%s[%s] : %s@%s/%s%s", + scopeTypeArg, + pathOrIndex, + typeArg, + this.metaOffset(), + this.valueOffset(), + this.immutable() ? " immutable" : ""); + + } catch (Exception ignored) { + return ""; + } + } + + /** + * If existing, the offset to the value of the existing field, otherwise undefined. + * + * @return If existing, the offset to the value of the existing field, otherwise undefined. + */ + public int valueOffset() { + return this.valueOffset; + } + + public RowCursor valueOffset(int valueOffset) { + this.valueOffset = valueOffset; + return this; + } + + /** + * If existing, the scope relative path for writing. + * + * @return If existing, the scope relative path for writing. + */ + public UtfAnyString writePath() { + return this.writePath; + } + + public void writePath(UtfAnyString writePath) { + this.writePath = writePath; + } + + /** + * If {@link #writePath} is tokenized, then its token. + * + * @return if {@link #writePath} is tokenized, then its token. + */ + public StringToken writePathToken() { + return this.writePathToken; + } + + public void writePathToken(StringToken writePathToken) { + this.writePathToken = writePathToken; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java index f795404..91f89c9 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java @@ -1,137 +1,137 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import com.azure.data.cosmos.core.UtfAnyString; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutEndScope; -import com.azure.data.cosmos.serialization.hybridrow.layouts.StringToken; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class RowCursors { - - private RowCursors() { - } - - public static RowCursor Find(@Nonnull RowCursor edit, @Nonnull RowBuffer row, @Nonnull UtfAnyString path) { - checkArgument(!edit.scopeType().isIndexedScope()); - - if (!(edit.cellType() instanceof LayoutEndScope)) { - while (row.sparseIteratorMoveNext(edit)) { - if (path.equals(row.readSparsePath(edit))) { - edit.exists(true); - break; - } - } - } - - edit.writePath(path); - edit.writePathToken(null); - - return edit; - } - - public static RowCursor Find(@Nonnull RowCursor edit, @Nonnull RowBuffer row, @Nonnull StringToken pathToken) { - - checkNotNull(edit); - checkNotNull(row); - checkNotNull(pathToken); - - checkArgument(!edit.scopeType().isIndexedScope()); - - if (!(edit.cellType() instanceof LayoutEndScope)) { - while (row.sparseIteratorMoveNext(edit)) { - if (pathToken.id() == (long) edit.pathToken()) { - edit.exists(true); - break; - } - } - } - - edit.writePath(new UtfAnyString(pathToken.path())); - edit.writePathToken(pathToken); - - return edit; - } - - /** - * An equivalent scope that is read-only. - * - * @param source source scope. - * @return an equivalent scope that is read-only. - */ - public static RowCursor asReadOnly(RowCursor source) { - return source.clone().immutable(true); - } - - /** - * Makes a copy of the current cursor. - *

- * The two cursors will have independent and unconnected lifetimes after cloning. However, mutations to a - * {@link RowBuffer} can invalidate any active cursors over the same row. - * - * @param source source cursor. - * @return copy of the source cursor. - */ - public static RowCursor copy(RowCursor source) { - return source.clone(); - } - - public static boolean moveNext(RowCursor edit, RowBuffer row) { - edit.writePath(null); - edit.writePathToken(null); - return row.sparseIteratorMoveNext(edit); - } - - public static boolean moveNext(@Nonnull RowCursor edit, @Nonnull RowBuffer row, @Nonnull RowCursor childScope) { - if (childScope.scopeType() != null) { - RowCursors.skip(edit.clone(), row, childScope); - } - return RowCursors.moveNext(edit.clone(), row); - } - - public static boolean moveTo(@Nonnull final RowCursor edit, @Nonnull final RowBuffer row, final int index) { - - checkNotNull(row); - checkNotNull(edit); - checkArgument(edit.index() <= index); - - edit.writePath(null); - edit.writePathToken(null); - - while (edit.index() < index) { - if (!row.sparseIteratorMoveNext(edit)) { - return false; - } - } - - return true; - } - - public static void skip( - @Nonnull final RowCursor edit, @Nonnull final RowBuffer buffer, @Nonnull final RowCursor childScope) { - - checkNotNull(edit, "expected non-null edit"); - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(childScope, "expected non-null childScope"); - - checkArgument(childScope.start() == edit.valueOffset()); - - if (!(childScope.cellType() instanceof LayoutEndScope)) { - //noinspection StatementWithEmptyBody - while (buffer.sparseIteratorMoveNext(childScope)) { - } - } - - if (childScope.scopeType().isSizedScope()) { - edit.endOffset(childScope.metaOffset()); - } else { - edit.endOffset(childScope.metaOffset() + LayoutCode.BYTES); // move past end of scope marker - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import com.azure.data.cosmos.core.UtfAnyString; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutEndScope; +import com.azure.data.cosmos.serialization.hybridrow.layouts.StringToken; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class RowCursors { + + private RowCursors() { + } + + public static RowCursor Find(@Nonnull RowCursor edit, @Nonnull RowBuffer row, @Nonnull UtfAnyString path) { + checkArgument(!edit.scopeType().isIndexedScope()); + + if (!(edit.cellType() instanceof LayoutEndScope)) { + while (row.sparseIteratorMoveNext(edit)) { + if (path.equals(row.readSparsePath(edit))) { + edit.exists(true); + break; + } + } + } + + edit.writePath(path); + edit.writePathToken(null); + + return edit; + } + + public static RowCursor Find(@Nonnull RowCursor edit, @Nonnull RowBuffer row, @Nonnull StringToken pathToken) { + + checkNotNull(edit); + checkNotNull(row); + checkNotNull(pathToken); + + checkArgument(!edit.scopeType().isIndexedScope()); + + if (!(edit.cellType() instanceof LayoutEndScope)) { + while (row.sparseIteratorMoveNext(edit)) { + if (pathToken.id() == (long) edit.pathToken()) { + edit.exists(true); + break; + } + } + } + + edit.writePath(new UtfAnyString(pathToken.path())); + edit.writePathToken(pathToken); + + return edit; + } + + /** + * An equivalent scope that is read-only. + * + * @param source source scope. + * @return an equivalent scope that is read-only. + */ + public static RowCursor asReadOnly(RowCursor source) { + return source.clone().immutable(true); + } + + /** + * Makes a copy of the current cursor. + *

+ * The two cursors will have independent and unconnected lifetimes after cloning. However, mutations to a + * {@link RowBuffer} can invalidate any active cursors over the same row. + * + * @param source source cursor. + * @return copy of the source cursor. + */ + public static RowCursor copy(RowCursor source) { + return source.clone(); + } + + public static boolean moveNext(RowCursor edit, RowBuffer row) { + edit.writePath(null); + edit.writePathToken(null); + return row.sparseIteratorMoveNext(edit); + } + + public static boolean moveNext(@Nonnull RowCursor edit, @Nonnull RowBuffer row, @Nonnull RowCursor childScope) { + if (childScope.scopeType() != null) { + RowCursors.skip(edit.clone(), row, childScope); + } + return RowCursors.moveNext(edit.clone(), row); + } + + public static boolean moveTo(@Nonnull final RowCursor edit, @Nonnull final RowBuffer row, final int index) { + + checkNotNull(row); + checkNotNull(edit); + checkArgument(edit.index() <= index); + + edit.writePath(null); + edit.writePathToken(null); + + while (edit.index() < index) { + if (!row.sparseIteratorMoveNext(edit)) { + return false; + } + } + + return true; + } + + public static void skip( + @Nonnull final RowCursor edit, @Nonnull final RowBuffer buffer, @Nonnull final RowCursor childScope) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(childScope, "expected non-null childScope"); + + checkArgument(childScope.start() == edit.valueOffset()); + + if (!(childScope.cellType() instanceof LayoutEndScope)) { + //noinspection StatementWithEmptyBody + while (buffer.sparseIteratorMoveNext(childScope)) { + } + } + + if (childScope.scopeType().isSizedScope()) { + edit.endOffset(childScope.metaOffset()); + } else { + edit.endOffset(childScope.metaOffset() + LayoutCode.BYTES); // move past end of scope marker + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowOptions.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowOptions.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowOptions.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowOptions.java index f9d58fc..0659f10 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowOptions.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowOptions.java @@ -1,85 +1,85 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import com.azure.data.cosmos.serialization.hybridrow.schemas.SortDirection; -import com.google.common.base.Suppliers; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; - -import java.util.Arrays; -import java.util.function.Supplier; - -/** - * Describes the desired behavior when mutating a hybrid row. - */ -public enum RowOptions { - - NONE(0), - - /** - * Overwrite an existing value. - *

- * An existing value is assumed to exist at the offset provided. The existing value is - * replaced inline. The remainder of the row is resized to accomodate either an increase or decrease - * in required space. - */ - UPDATE(1), - - /** - * Insert a new value. - *

- * An existing value is assumed NOT to exist at the offset provided. The new value is - * inserted immediately at the offset. The remainder of the row is resized to accomodate either an - * increase or decrease in required space. - */ - INSERT(2), - - /** - * Update an existing value or insert a new value, if no value exists. - *

- * If a value exists, then this operation becomes {@link #UPDATE}, otherwise it - * becomes {@link #INSERT}. - */ - UPSERT(3), - - /** - * Insert a new value moving existing values to the right. - *

- * Within an array scope, inserts a new value immediately at the index moving all subsequent - * items to the right. In any other scope behaves the same as {@link #UPSERT}. - */ - INSERT_AT(4), - - /** - * Delete an existing value. - *

- * If a value exists, then it is removed. The remainder of the row is resized to accommodate - * a decrease in required space. If no value exists this operation is a no-op. - */ - DELETE(5); - - public static final int BYTES = Integer.BYTES; - - private static final Supplier> mappings = Suppliers.memoize(() -> { - RowOptions[] constants = RowOptions.class.getEnumConstants(); - int[] values = new int[constants.length]; - Arrays.setAll(values, index -> constants[index].value); - return new Int2ReferenceArrayMap<>(values, constants); - }); - - private final int value; - - RowOptions(int value) { - this.value = value; - } - - public static RowOptions from(int value) { - return mappings.get().get(value); - } - - public int value() { - return this.value; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import com.azure.data.cosmos.serialization.hybridrow.schemas.SortDirection; +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; + +import java.util.Arrays; +import java.util.function.Supplier; + +/** + * Describes the desired behavior when mutating a hybrid row. + */ +public enum RowOptions { + + NONE(0), + + /** + * Overwrite an existing value. + *

+ * An existing value is assumed to exist at the offset provided. The existing value is + * replaced inline. The remainder of the row is resized to accomodate either an increase or decrease + * in required space. + */ + UPDATE(1), + + /** + * Insert a new value. + *

+ * An existing value is assumed NOT to exist at the offset provided. The new value is + * inserted immediately at the offset. The remainder of the row is resized to accomodate either an + * increase or decrease in required space. + */ + INSERT(2), + + /** + * Update an existing value or insert a new value, if no value exists. + *

+ * If a value exists, then this operation becomes {@link #UPDATE}, otherwise it + * becomes {@link #INSERT}. + */ + UPSERT(3), + + /** + * Insert a new value moving existing values to the right. + *

+ * Within an array scope, inserts a new value immediately at the index moving all subsequent + * items to the right. In any other scope behaves the same as {@link #UPSERT}. + */ + INSERT_AT(4), + + /** + * Delete an existing value. + *

+ * If a value exists, then it is removed. The remainder of the row is resized to accommodate + * a decrease in required space. If no value exists this operation is a no-op. + */ + DELETE(5); + + public static final int BYTES = Integer.BYTES; + + private static final Supplier> mappings = Suppliers.memoize(() -> { + RowOptions[] constants = RowOptions.class.getEnumConstants(); + int[] values = new int[constants.length]; + Arrays.setAll(values, index -> constants[index].value); + return new Int2ReferenceArrayMap<>(values, constants); + }); + + private final int value; + + RowOptions(int value) { + this.value = value; + } + + public static RowOptions from(int value) { + return mappings.get().get(value); + } + + public int value() { + return this.value; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/SchemaId.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/SchemaId.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/SchemaId.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/SchemaId.java index 91b1459..c54ab1c 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/SchemaId.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/SchemaId.java @@ -1,160 +1,160 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import it.unimi.dsi.fastutil.HashCommon; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; - -import javax.annotation.Nonnull; -import java.io.IOException; - -import static com.google.common.base.Strings.lenientFormat; -import static it.unimi.dsi.fastutil.HashCommon.mix; - -/** - * The unique identifier for a schema. - *

- * Identifiers must be unique within the scope of the database in which they are used. - */ -@JsonDeserialize(using = SchemaId.JsonDeserializer.class) -@JsonSerialize(using = SchemaId.JsonSerializer.class) -public final class SchemaId implements Comparable { - - public static final int BYTES = Integer.BYTES; - public static final SchemaId INVALID; - public static final SchemaId NONE; - - private static final Int2ReferenceMap cache; - - static { - cache = new Int2ReferenceOpenHashMap<>(); - cache.put(0, INVALID = NONE = new SchemaId(0)); - } - - private final int value; - - /** - * Initializes a new instance of the {@link SchemaId} class. - * - * @param value The underlying globally unique identifier of the schema. - */ - private SchemaId(int value) { - this.value = value; - } - - @Override - public int compareTo(@Nonnull SchemaId other) { - return Integer.compare(this.value, other.value); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null || this.getClass() != other.getClass()) { - return false; - } - SchemaId schemaId = (SchemaId) other; - return this.value == schemaId.value; - } - - /** - * {@code true} if this is the same {@link SchemaId} as {@code other}. - * - * @param other The value to compare against. - * @return {@code true} if the two values are the same. - */ - public boolean equals(SchemaId other) { - if (null == other) { - return false; - } - return this.value() == other.value(); - } - - /** - * Returns a {@link SchemaId} with the given underlying integer value. - * - * @param value an integer. - * @return a {@link SchemaId} with the given underlying integer {@code value}. - */ - public static SchemaId from(int value) { - return cache.computeIfAbsent(value, SchemaId::new); - } - - /** - * Returns the hash code value for this {@link SchemaId}. - *

- * This method mixes the bits of the underlying {@code int} value of the {@link SchemaId} by multiplying by the - * golden ratio and xor-shifting the result. It has slightly worse behavior than MurmurHash3. In open-addressing - * In open-addressing tables the average number of probes is slightly larger, but the computation of the value - * is faster. - * - * @return the hash code value for this {@link SchemaId}. - * @see HashCommon#mix(int) - * @see Koloboke - * @see MurmurHash - */ - @Override - public int hashCode() { - return mix(this.value); - } - - @Override - public String toString() { - return Integer.toString(this.value); - } - - /** - * The underlying integer value of this {@link SchemaId}. - * - * @return The integer value of this {@link SchemaId} - */ - public int value() { - return this.value; - } - - static final class JsonDeserializer extends StdDeserializer { - - private JsonDeserializer() { - super(SchemaId.class); - } - - @Override - public SchemaId deserialize(final JsonParser parser, final DeserializationContext context) throws IOException { - - final int value = parser.getIntValue(); - - if (value == 0) { - String message = "expected non-zero int value for SchemaId"; - throw MismatchedInputException.from(parser, SchemaId.class, message); - } - - return SchemaId.from(value); - } - } - - static final class JsonSerializer extends StdSerializer { - - private JsonSerializer() { - super(SchemaId.class); - } - - @Override - public void serialize( - final SchemaId value, final JsonGenerator generator, final SerializerProvider provider) throws IOException { - generator.writeNumber(value.value()); - } - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; + +import javax.annotation.Nonnull; +import java.io.IOException; + +import static com.google.common.base.Strings.lenientFormat; +import static it.unimi.dsi.fastutil.HashCommon.mix; + +/** + * The unique identifier for a schema. + *

+ * Identifiers must be unique within the scope of the database in which they are used. + */ +@JsonDeserialize(using = SchemaId.JsonDeserializer.class) +@JsonSerialize(using = SchemaId.JsonSerializer.class) +public final class SchemaId implements Comparable { + + public static final int BYTES = Integer.BYTES; + public static final SchemaId INVALID; + public static final SchemaId NONE; + + private static final Int2ReferenceMap cache; + + static { + cache = new Int2ReferenceOpenHashMap<>(); + cache.put(0, INVALID = NONE = new SchemaId(0)); + } + + private final int value; + + /** + * Initializes a new instance of the {@link SchemaId} class. + * + * @param value The underlying globally unique identifier of the schema. + */ + private SchemaId(int value) { + this.value = value; + } + + @Override + public int compareTo(@Nonnull SchemaId other) { + return Integer.compare(this.value, other.value); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || this.getClass() != other.getClass()) { + return false; + } + SchemaId schemaId = (SchemaId) other; + return this.value == schemaId.value; + } + + /** + * {@code true} if this is the same {@link SchemaId} as {@code other}. + * + * @param other The value to compare against. + * @return {@code true} if the two values are the same. + */ + public boolean equals(SchemaId other) { + if (null == other) { + return false; + } + return this.value() == other.value(); + } + + /** + * Returns a {@link SchemaId} with the given underlying integer value. + * + * @param value an integer. + * @return a {@link SchemaId} with the given underlying integer {@code value}. + */ + public static SchemaId from(int value) { + return cache.computeIfAbsent(value, SchemaId::new); + } + + /** + * Returns the hash code value for this {@link SchemaId}. + *

+ * This method mixes the bits of the underlying {@code int} value of the {@link SchemaId} by multiplying by the + * golden ratio and xor-shifting the result. It has slightly worse behavior than MurmurHash3. In open-addressing + * In open-addressing tables the average number of probes is slightly larger, but the computation of the value + * is faster. + * + * @return the hash code value for this {@link SchemaId}. + * @see HashCommon#mix(int) + * @see Koloboke + * @see MurmurHash + */ + @Override + public int hashCode() { + return mix(this.value); + } + + @Override + public String toString() { + return Integer.toString(this.value); + } + + /** + * The underlying integer value of this {@link SchemaId}. + * + * @return The integer value of this {@link SchemaId} + */ + public int value() { + return this.value; + } + + static final class JsonDeserializer extends StdDeserializer { + + private JsonDeserializer() { + super(SchemaId.class); + } + + @Override + public SchemaId deserialize(final JsonParser parser, final DeserializationContext context) throws IOException { + + final int value = parser.getIntValue(); + + if (value == 0) { + String message = "expected non-zero int value for SchemaId"; + throw MismatchedInputException.from(parser, SchemaId.class, message); + } + + return SchemaId.from(value); + } + } + + static final class JsonSerializer extends StdSerializer { + + private JsonSerializer() { + super(SchemaId.class); + } + + @Override + public void serialize( + final SchemaId value, final JsonGenerator generator, final SerializerProvider provider) throws IOException { + generator.writeNumber(value.value()); + } + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/UnixDateTime.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/UnixDateTime.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/UnixDateTime.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/UnixDateTime.java index 49be205..63680dc 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/UnixDateTime.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/UnixDateTime.java @@ -1,78 +1,78 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow; - -/** - * A wall clock time expressed in milliseconds since the Unix Epoch. - *

- * A {@link UnixDateTime} is a fixed length value-type providing millisecond - * granularity as a signed offset from the Unix Epoch (midnight, January 1, 1970 UTC). - */ -public final class UnixDateTime { - /** - * Unix epoch. - *

- * {@link UnixDateTime} values are signed values centered on this value. - */ - public static final UnixDateTime EPOCH = new UnixDateTime(); - - /** - * Size in bytes of a {@link UnixDateTime}. - */ - public static final int BYTES = Long.SIZE; - - private long milliseconds; - - private UnixDateTime() { - } - - /** - * Initializes a new instance of the {@link UnixDateTime} class. - * - * @param milliseconds The number of milliseconds since {@link #EPOCH}. - */ - public UnixDateTime(long milliseconds) { - this.milliseconds = milliseconds; - } - - /** - * {@code> true} if this value is the same as another value. - * - * @param other value to compare. - * @return {code true} if this value is the same as the {code other}, {@code false} otherwise. - */ - public boolean equals(UnixDateTime other) { - if (other == null) { - return false; - } - return this.milliseconds() == other.milliseconds(); - } - - @Override - public boolean equals(Object other) { - if (null == other) { - return false; - } - if (this == other) { - return true; - } - return other instanceof UnixDateTime && this.equals((UnixDateTime)other); - } - - @Override - public int hashCode() { - return Long.valueOf(this.milliseconds).hashCode(); - } - - /** - * The number of milliseconds since {@link #EPOCH}. - *

- * This value may be negative. - * - * @return the number of milliseconds since {@link #EPOCH}. - */ - public long milliseconds() { - return this.milliseconds; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow; + +/** + * A wall clock time expressed in milliseconds since the Unix Epoch. + *

+ * A {@link UnixDateTime} is a fixed length value-type providing millisecond + * granularity as a signed offset from the Unix Epoch (midnight, January 1, 1970 UTC). + */ +public final class UnixDateTime { + /** + * Unix epoch. + *

+ * {@link UnixDateTime} values are signed values centered on this value. + */ + public static final UnixDateTime EPOCH = new UnixDateTime(); + + /** + * Size in bytes of a {@link UnixDateTime}. + */ + public static final int BYTES = Long.SIZE; + + private long milliseconds; + + private UnixDateTime() { + } + + /** + * Initializes a new instance of the {@link UnixDateTime} class. + * + * @param milliseconds The number of milliseconds since {@link #EPOCH}. + */ + public UnixDateTime(long milliseconds) { + this.milliseconds = milliseconds; + } + + /** + * {@code> true} if this value is the same as another value. + * + * @param other value to compare. + * @return {code true} if this value is the same as the {code other}, {@code false} otherwise. + */ + public boolean equals(UnixDateTime other) { + if (other == null) { + return false; + } + return this.milliseconds() == other.milliseconds(); + } + + @Override + public boolean equals(Object other) { + if (null == other) { + return false; + } + if (this == other) { + return true; + } + return other instanceof UnixDateTime && this.equals((UnixDateTime)other); + } + + @Override + public int hashCode() { + return Long.valueOf(this.milliseconds).hashCode(); + } + + /** + * The number of milliseconds since {@link #EPOCH}. + *

+ * This value may be negative. + * + * @return the number of milliseconds since {@link #EPOCH}. + */ + public long milliseconds() { + return this.milliseconds; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodec.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodec.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodec.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodec.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodec.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodec.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodec.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodec.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/Float128Codec.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/Float128Codec.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/Float128Codec.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/Float128Codec.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodec.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodec.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodec.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodec.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/internal/Murmur3Hash.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/internal/Murmur3Hash.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/internal/Murmur3Hash.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/internal/Murmur3Hash.java index 6805335..9c8224f 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/internal/Murmur3Hash.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/internal/Murmur3Hash.java @@ -1,129 +1,129 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.internal; - -import com.azure.data.cosmos.core.Utf8String; -import com.azure.data.cosmos.serialization.hybridrow.HashCode128; -import com.google.common.base.Utf8; -import com.google.common.hash.HashCode; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; - -import javax.annotation.Nonnull; -import javax.annotation.concurrent.Immutable; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.lenientFormat; -import static java.nio.charset.StandardCharsets.UTF_8; - -/** - * Murmur3Hash for x86_64 (little endian). - * - * @see MurmurHash - *

- */ -@SuppressWarnings("UnstableApiUsage") -@Immutable -public final class Murmur3Hash { - - private static final ByteBuf FALSE = Constant.add(false); - private static final ByteBuf TRUE = Constant.add(true); - private static final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; - private static final ByteBuf EMPTY_STRING = Constant.add(""); - - /** - * Computes a 128-bit Murmur3Hash 128-bit value for a data item. - * - * @param item The data to hash - * @param seed The seed with which to initialize - * @return The 128-bit hash represented as two 64-bit words encapsulated by a {@link HashCode128} instance - */ - @SuppressWarnings("ConstantConditions") - public static HashCode128 Hash128(@Nonnull final String item, @Nonnull final HashCode128 seed) { - - checkNotNull(item, "expected non-null item"); - checkNotNull(seed, "expected non-null seed"); - - if (item.isEmpty()) { - return Hash128(EMPTY_STRING, seed); - } - - Utf8String value = Utf8String.transcodeUtf16(item); - - try { - return Hash128(value.content(), seed); - } finally { - value.release(); - } - } - - /** - * Computes a 128-bit Murmur3Hash 128-bit value for a {@code boolean} data item. - * - * @param item The data to hash. - * @param seed The seed with which to initialize. - * @return The 128-bit hash represented as two 64-bit words encapsulated by a {@link HashCode128} instance. - */ - public static HashCode128 Hash128(final boolean item, final HashCode128 seed) { - return Murmur3Hash.Hash128(item ? TRUE : FALSE, seed); - } - - public static HashCode128 Hash128(short item, HashCode128 seed) { - ByteBuf buffer = Unpooled.wrappedBuffer(new byte[Integer.BYTES]).writeShortLE(item); - return Murmur3Hash.Hash128(buffer, seed); - } - - public static HashCode128 Hash128(byte item, HashCode128 seed) { - ByteBuf buffer = Unpooled.wrappedBuffer(new byte[Integer.BYTES]).writeByte(item); - return Murmur3Hash.Hash128(buffer, seed); - } - - public static HashCode128 Hash128(int item, HashCode128 seed) { - ByteBuf buffer = Unpooled.wrappedBuffer(new byte[Integer.BYTES]).writeIntLE(item); - return Murmur3Hash.Hash128(buffer, seed); - } - - /** - * Computes a 128-bit Murmur3Hash 128-bit value for a {@link ByteBuf} data item. - * - * @param item The data to hash - * @param seed The seed with which to initialize - * @return The 128-bit hash represented as two 64-bit words encapsulated by a {@link HashCode128} instance. - */ - public static HashCode128 Hash128(ByteBuf item, HashCode128 seed) { - // TODO: DANOBLE: Support 128-bit hash code seeds by bringing in the murmur3 hash code from the Cosmos Java SDK - HashFunction hashFunction = Hashing.murmur3_128(Long.valueOf(seed.high() | 0xFFFFFFFFL).intValue()); - HashCode hashCode = hashFunction.hashBytes(item.array()); - return HashCode128.from(hashCode.asBytes()); - } - - private static final class Constant { - - private static ByteBuf constants = allocator.heapBuffer(); - - private Constant() { - } - - static ByteBuf add(final boolean value) { - final int start = constants.writerIndex(); - constants.writeByte(value ? 1 : 0); - return constants.slice(start, Byte.BYTES).asReadOnly(); - } - - static ByteBuf add(final String value) { - - final int start = constants.writerIndex(); - final int encodedLength = Utf8.encodedLength(value); - final ByteBuf buffer = allocator.buffer(encodedLength, encodedLength); - - final int count = buffer.writeCharSequence(value, UTF_8); - assert count == encodedLength : lenientFormat("count: %s, encodedLength: %s"); - - return constants.slice(start, encodedLength); - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.internal; + +import com.azure.data.cosmos.core.Utf8String; +import com.azure.data.cosmos.serialization.hybridrow.HashCode128; +import com.google.common.base.Utf8; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.lenientFormat; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Murmur3Hash for x86_64 (little endian). + * + * @see MurmurHash + *

+ */ +@SuppressWarnings("UnstableApiUsage") +@Immutable +public final class Murmur3Hash { + + private static final ByteBuf FALSE = Constant.add(false); + private static final ByteBuf TRUE = Constant.add(true); + private static final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + private static final ByteBuf EMPTY_STRING = Constant.add(""); + + /** + * Computes a 128-bit Murmur3Hash 128-bit value for a data item. + * + * @param item The data to hash + * @param seed The seed with which to initialize + * @return The 128-bit hash represented as two 64-bit words encapsulated by a {@link HashCode128} instance + */ + @SuppressWarnings("ConstantConditions") + public static HashCode128 Hash128(@Nonnull final String item, @Nonnull final HashCode128 seed) { + + checkNotNull(item, "expected non-null item"); + checkNotNull(seed, "expected non-null seed"); + + if (item.isEmpty()) { + return Hash128(EMPTY_STRING, seed); + } + + Utf8String value = Utf8String.transcodeUtf16(item); + + try { + return Hash128(value.content(), seed); + } finally { + value.release(); + } + } + + /** + * Computes a 128-bit Murmur3Hash 128-bit value for a {@code boolean} data item. + * + * @param item The data to hash. + * @param seed The seed with which to initialize. + * @return The 128-bit hash represented as two 64-bit words encapsulated by a {@link HashCode128} instance. + */ + public static HashCode128 Hash128(final boolean item, final HashCode128 seed) { + return Murmur3Hash.Hash128(item ? TRUE : FALSE, seed); + } + + public static HashCode128 Hash128(short item, HashCode128 seed) { + ByteBuf buffer = Unpooled.wrappedBuffer(new byte[Integer.BYTES]).writeShortLE(item); + return Murmur3Hash.Hash128(buffer, seed); + } + + public static HashCode128 Hash128(byte item, HashCode128 seed) { + ByteBuf buffer = Unpooled.wrappedBuffer(new byte[Integer.BYTES]).writeByte(item); + return Murmur3Hash.Hash128(buffer, seed); + } + + public static HashCode128 Hash128(int item, HashCode128 seed) { + ByteBuf buffer = Unpooled.wrappedBuffer(new byte[Integer.BYTES]).writeIntLE(item); + return Murmur3Hash.Hash128(buffer, seed); + } + + /** + * Computes a 128-bit Murmur3Hash 128-bit value for a {@link ByteBuf} data item. + * + * @param item The data to hash + * @param seed The seed with which to initialize + * @return The 128-bit hash represented as two 64-bit words encapsulated by a {@link HashCode128} instance. + */ + public static HashCode128 Hash128(ByteBuf item, HashCode128 seed) { + // TODO: DANOBLE: Support 128-bit hash code seeds by bringing in the murmur3 hash code from the Cosmos Java SDK + HashFunction hashFunction = Hashing.murmur3_128(Long.valueOf(seed.high() | 0xFFFFFFFFL).intValue()); + HashCode hashCode = hashFunction.hashBytes(item.array()); + return HashCode128.from(hashCode.asBytes()); + } + + private static final class Constant { + + private static ByteBuf constants = allocator.heapBuffer(); + + private Constant() { + } + + static ByteBuf add(final boolean value) { + final int start = constants.writerIndex(); + constants.writeByte(value ? 1 : 0); + return constants.slice(start, Byte.BYTES).asReadOnly(); + } + + static ByteBuf add(final String value) { + + final int start = constants.writerIndex(); + final int encodedLength = Utf8.encodedLength(value); + final ByteBuf buffer = allocator.buffer(encodedLength, encodedLength); + + final int count = buffer.writeCharSequence(value, UTF_8); + assert count == encodedLength : lenientFormat("count: %s, encodedLength: %s"); + + return constants.slice(start, encodedLength); + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/DataItem.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/DataItem.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/DataItem.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/DataItem.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java index 75d85b3..a5d76c5 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java @@ -1,1274 +1,1274 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.io; - -import com.azure.data.cosmos.core.Json; -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.core.Utf8String; -import com.azure.data.cosmos.serialization.hybridrow.Float128; -import com.azure.data.cosmos.serialization.hybridrow.NullValue; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.azure.data.cosmos.serialization.hybridrow.RowCursors; -import com.azure.data.cosmos.serialization.hybridrow.UnixDateTime; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBinary; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBoolean; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutColumn; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutDateTime; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutDecimal; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat128; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat32; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat64; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutGuid; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt16; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt32; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt64; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt8; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutListReadable; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNull; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNullable; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutType; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypePrimitive; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUDT; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt16; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt32; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt64; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt8; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUnixDateTime; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUtf8; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarInt; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarUInt; -import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; -import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.netty.buffer.ByteBuf; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.math.BigDecimal; -import java.time.OffsetDateTime; -import java.util.List; -import java.util.UUID; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Strings.lenientFormat; - -/** - * A forward-only, streaming, field reader for {@link RowBuffer}. - *

- * A {@link RowReader} allows the traversal in a streaming, left to right fashion, of an entire HybridRow. The row's - * layout provides decoding for any schematized portion of the row. However, unschematized sparse fields are read - * directly from the sparse segment with or without schematization allowing all fields within the row, both known and - * unknown, to be read. - *

- * Modifying a {@link RowBuffer} invalidates any reader or child reader associated with it. In general{@link RowBuffer}s - * should not be mutated while being enumerated. - */ -@JsonSerialize(using = RowReader.JsonSerializer.class) -public final class RowReader { - - private int columnIndex; - private List columns; - private RowCursor cursor; - private RowBuffer buffer; - private int schematizedCount; - private States state; - - /** - * Initializes a new instance of the {@link RowReader} class. - * - * @param buffer The row to be read - */ - public RowReader(@Nonnull RowBuffer buffer) { - this(buffer, RowCursor.create(buffer)); - } - - /** - * Initializes a new instance of the {@link RowReader} class. - * - * @param buffer The buffer to be read - * @param checkpoint Initial state of the reader - */ - public RowReader(@Nonnull final RowBuffer buffer, @Nonnull final Checkpoint checkpoint) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(checkpoint, "expected non-null checkpoint"); - - this.buffer = buffer; - this.columns = checkpoint.cursor().layout().columns(); - this.schematizedCount = checkpoint.cursor().layout().numFixed() + checkpoint.cursor().layout().numVariable(); - - this.state = checkpoint.state(); - this.cursor = checkpoint.cursor(); - this.columnIndex = checkpoint.columnIndex(); - } - - /** - * Initializes a new instance of the {@link RowReader} class. - * - * @param buffer The buffer to be read. - * @param scope Cursor defining the scope of the fields to be read. - *

- * A {@link RowReader} instance traverses all of the top-level fields of a given scope. If the root - * scope is provided then all top-level fields in the buffer are enumerated. Nested child - * {@link RowReader} instances can be access through the {@link RowReader#readScope} method to process - * nested content. - */ - private RowReader(@Nonnull final RowBuffer buffer, @Nonnull final RowCursor scope) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - - this.cursor = scope; - this.buffer = buffer; - this.columns = this.cursor.layout().columns(); - this.schematizedCount = this.cursor.layout().numFixed() + this.cursor.layout().numVariable(); - - this.state = States.NONE; - this.columnIndex = -1; - } - - /** - * Read the current field as a fixed length {@code MongoDbObjectId} value. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result ReadMongoDbObjectId(Out value) { - // TODO: DANOBLE: Resurrect this method - // switch (this.state) { - // - // case Schematized: - // return this.readPrimitiveValue(value); - // - // case Sparse: - // if (!(this.cursor.cellType() instanceof LayoutMongoDbObjectId)) { - // value.set(null); - // return Result.TYPE_MISMATCH; - // } - // value.set(this.row.readSparseMongoDbObjectId(this.cursor)); - // return Result.SUCCESS; - // - // default: - // value.set(null); - // return Result.FAILURE; - // } - throw new UnsupportedOperationException(); - } - - public boolean isDone() { - return this.state == States.DONE; - } - - /** - * {@code true} if field has a value--if positioned on a field--undefined otherwise. - *

- * If the current field is a Nullable scope, this method return true if the value is not null. If the current field - * is a nullable Null primitive value, this method return true if the value is set (even though its values is set - * to null). - * - * @return {@code true} if field has a value, {@code false} otherwise. - */ - public boolean hasValue() { - - switch (this.state) { - - case SCHEMATIZED: - return true; - - case SPARSE: - if (this.cursor.cellType() instanceof LayoutNullable) { - RowCursor nullableScope = this.buffer.sparseIteratorReadScope(this.cursor, true); - return LayoutNullable.hasValue(this.buffer, nullableScope) == Result.SUCCESS; - } - return true; - - default: - return false; - } - } - - /** - * Zero-based index, relative to the scope, of the field--if positioned on a field--undefined otherwise. - *

- * When enumerating a non-indexed scope, this value is always zero. - * - * @return zero-based index of the field relative to the scope, if positioned on a field; otherwise undefined. - * @see #path() - */ - public int index() { - return this.state == States.SPARSE ? this.cursor.index() : 0; - } - - /** - * The length of row in bytes. - * - * @return length of the current row in bytes. - */ - public int length() { - return this.buffer.length(); - } - - /** - * The path, relative to the scope, of the field--if positioned on a field--undefined otherwise. - *

- * When enumerating an indexed scope, this value is always null. - * - * @return path of the field relative to the scope, if positioned on a field; otherwise undefined. - * @see #index - */ - @Nonnull - public Utf8String path() { - switch (this.state) { - case SCHEMATIZED: - return this.columns.get(this.columnIndex).path(); - case SPARSE: - return this.buffer.readSparsePath(this.cursor); - default: - return Utf8String.NULL; - } - } - - /** - * Advances the reader to the next field. - * - * @return {@code true}, if there is another field to be read; {@code false} otherwise. - */ - public boolean read() { - - while (this.state != States.DONE) { - - switch (this.state) { - - case NONE: { - this.state = this.cursor.scopeType() instanceof LayoutUDT ? States.SCHEMATIZED : States.SPARSE; - break; - } - case SCHEMATIZED: { - - this.columnIndex++; - - if (this.columnIndex >= this.schematizedCount) { - this.state = States.SPARSE; - break; - } - - checkState(this.cursor.scopeType() instanceof LayoutUDT); - LayoutColumn column = this.columns.get(this.columnIndex); - - if (!this.buffer.readBit(this.cursor.start(), column.nullBit())) { - break; // to skip schematized values if they aren't present - } - - return true; - } - case SPARSE: { - - if (!RowCursors.moveNext(this.cursor, this.buffer)) { - this.state = States.DONE; - break; - } - return true; - } - } - } - - return false; - } - - /** - * Read the current field as a variable length, sequence of bytes. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readBinary(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - - if (!(this.cursor.cellType() instanceof LayoutBinary)) { - value.set(null); - return Result.TYPE_MISMATCH; - } - - value.set(this.buffer.readSparseBinary(this.cursor)); - return Result.SUCCESS; - - default: - value.set(null); - return Result.FAILURE; - } - } - - /** - * Read the current field as a variable length, sequence of bytes. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readBinaryArray(Out value) { - - Out buffer = new Out<>(); - Result result = this.readBinary(buffer); - - if (result == Result.SUCCESS) { - byte[] array = new byte[buffer.get().writerIndex()]; - buffer.get().getBytes(0, array); - value.set(array); - } - - return result; - } - - /** - * Read the current field as a {@link Boolean}. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readBoolean(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutBoolean)) { - value.set(false); - return Result.TYPE_MISMATCH; - } - - value.set(this.buffer.readSparseBoolean(this.cursor)); - return Result.SUCCESS; - - default: - value.set(false); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length {@code DateTime} value. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readDateTime(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutDateTime)) { - value.set(OffsetDateTime.MIN); - return Result.TYPE_MISMATCH; - } - - value.set(this.buffer.readSparseDateTime(this.cursor)); - return Result.SUCCESS; - - default: - value.set(OffsetDateTime.MIN); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length decimal value. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readDecimal(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutDecimal)) { - value.set(new BigDecimal(0)); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseDecimal(this.cursor)); - return Result.SUCCESS; - - default: - value.set(new BigDecimal(0)); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 128-bit, IEEE-encoded floating point value. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readFloat128(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutFloat128)) { - value.setAndGet(null); - return Result.TYPE_MISMATCH; - } - value.setAndGet(this.buffer.readSparseFloat128(this.cursor)); - return Result.SUCCESS; - - default: - value.setAndGet(null); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 32-bit, IEEE-encoded floating point value - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readFloat32(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutFloat32)) { - value.set(0F); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseFloat32(this.cursor)); - return Result.SUCCESS; - - default: - value.set(0F); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 64-bit, IEEE-encoded floating point value - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readFloat64(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutFloat64)) { - value.set(0D); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseFloat64(this.cursor)); - return Result.SUCCESS; - - default: - value.set(0D); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length GUID value. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readGuid(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutGuid)) { - value.set(null); - return Result.TYPE_MISMATCH; - } - - value.set(this.buffer.readSparseGuid(this.cursor)); - return Result.SUCCESS; - - default: - value.set(null); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 16-bit, signed integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readInt16(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutInt16)) { - value.set((short)0); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseInt16(this.cursor)); - return Result.SUCCESS; - - default: - value.set((short)0); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 32-bit, signed integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readInt32(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutInt32)) { - value.set(0); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseInt32(this.cursor)); - return Result.SUCCESS; - - default: - value.set(0); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 64-bit, signed integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readInt64(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutInt64)) { - value.setAndGet(0L); - return Result.TYPE_MISMATCH; - } - value.setAndGet(this.buffer.readSparseInt64(this.cursor)); - return Result.SUCCESS; - - default: - value.setAndGet(0L); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 8-bit, signed integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readInt8(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutInt8)) { - value.set((byte)0); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseInt8(this.cursor)); - return Result.SUCCESS; - - default: - value.set((byte)0); - return Result.FAILURE; - } - } - - /** - * Read the current field as a null. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readNull(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutNull)) { - value.set(null); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseNull(this.cursor)); - return Result.SUCCESS; - - default: - value.set(null); - return Result.FAILURE; - } - } - - /** - * Read the current field as a nested, structured, sparse scope. - *

- * Child readers can be used to read all sparse scope types including typed and untyped objects, arrays, tuples, - * set, and maps. - * - * @param a reader context type. - * @param context a reader context. - * @param func a reader function. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - @Nonnull - public Result readScope(@Nullable final TContext context, @Nullable final ReaderFunc func) { - - final RowCursor childScope = this.buffer.sparseIteratorReadScope(this.cursor, true); - final RowReader nestedReader = new RowReader(this.buffer, childScope); - final Result result = func == null ? null : func.invoke(nestedReader, context); - - if (!(result == null || result == Result.SUCCESS)) { - return result; - } - - RowCursors.skip(this.cursor, this.buffer, nestedReader.cursor); - return Result.SUCCESS; - } - - /** - * Read the current field as a nested, structured, sparse scope. - *

- * Child readers can be used to read all sparse scope types including typed and untyped objects, arrays, tuples, - * set, and maps. Nested child readers are independent of their parent. - * - * @return a new {@link RowReader}. - */ - public @Nonnull RowReader readScope() { - RowCursor scope = this.buffer.sparseIteratorReadScope(this.cursor, true); - return new RowReader(this.buffer, scope); - } - - /** - * Read the current field as a variable length, UTF-8 encoded string value. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readString(Out value) { - - Out string = new Out<>(); - Result result = this.readUtf8String(string); - value.set((result == Result.SUCCESS) ? string.get().toUtf16() : null); - string.get().release(); - - return result; - } - - /** - * Read the current field as a variable length, UTF-8 encoded, string value. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readUtf8String(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutUtf8)) { - value.set(null); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseString(this.cursor)); - return Result.SUCCESS; - - default: - value.set(null); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 16-bit, unsigned integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readUInt16(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutUInt16)) { - value.set(0); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseUInt16(this.cursor)); - return Result.SUCCESS; - - default: - value.set(0); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 32-bit, unsigned integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readUInt32(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutUInt32)) { - value.set(0L); - return Result.TYPE_MISMATCH; - } - - value.set(this.buffer.readSparseUInt32(this.cursor)); - return Result.SUCCESS; - - default: - value.set(0L); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 64-bit, unsigned integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readUInt64(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutUInt64)) { - value.set(0L); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseUInt64(this.cursor)); - return Result.SUCCESS; - - default: - value.set(0L); - return Result.FAILURE; - } - } - - /** - * Read the current field as a fixed length, 8-bit, unsigned integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - @Nonnull - public Result readUInt8(@Nonnull Out value) { - - checkNotNull(value, "expected non-null value"); - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutUInt8)) { - value.set((short)0); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseUInt8(this.cursor)); - return Result.SUCCESS; - - default: - value.set((short)0); - return Result.FAILURE; - } - } - - /** - * Returns a string representation of the object. In general, the - * {@code toString} method returns a string that - * "textually represents" this object. The result should - * be a concise but informative representation that is easy for a - * person to read. - * It is recommended that all subclasses override this method. - *

- * The {@code toString} method for class {@code Object} - * returns a string consisting of the name of the class of which the - * object is an instance, the at-sign character `{@code @}', and - * the unsigned hexadecimal representation of the hash code of the - * object. In other words, this method returns a string equal to the - * value of: - *

- *
-     * getClass().getName() + '@' + Integer.toHexString(hashCode())
-     * 
- * - * @return a string representation of the object. - */ - @Override - public String toString() { - return Json.toString(this); - } - - /** - * Read the current field as a fixed length {@link UnixDateTime} value. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readUnixDateTime(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutUnixDateTime)) { - value.set(null); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseUnixDateTime(this.cursor)); - return Result.SUCCESS; - - default: - value.set(null); - return Result.FAILURE; - } - } - - /** - * Read the current field as a variable length, 7-bit encoded, signed integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readVarInt(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutVarInt)) { - value.set(0L); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseVarInt(this.cursor)); - return Result.SUCCESS; - - default: - value.set(0L); - return Result.FAILURE; - } - } - - /** - * Read the current field as a variable length, 7-bit encoded, unsigned integer. - * - * @param value On success, receives the value, undefined otherwise. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result readVarUInt(Out value) { - - switch (this.state) { - - case SCHEMATIZED: - return this.readPrimitiveValue(value); - - case SPARSE: - if (!(this.cursor.cellType() instanceof LayoutVarUInt)) { - value.set(0L); - return Result.TYPE_MISMATCH; - } - value.set(this.buffer.readSparseVarUInt(this.cursor)); - return Result.SUCCESS; - - default: - value.set(0L); - return Result.FAILURE; - } - } - - public Checkpoint saveCheckpoint() { - return new Checkpoint(this.state, this.columnIndex, this.cursor); - } - - /** - * Advance a reader to the end of a child reader. - *

- * The child reader is also advanced to the end of its scope. The reader must not have been advanced since the child - * reader was created with {@link #readScope}. This method can be used when the overload of {@link #readScope} that - * takes a {@link ReaderFunc} is not an option. - * - * @param nestedReader nested (child) reader to be advanced. - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - public Result skipScope(@Nonnull final RowReader nestedReader) { - if (nestedReader.cursor.start() != this.cursor.valueOffset()) { - return Result.FAILURE; - } - RowCursors.skip(this.cursor, this.buffer, nestedReader.cursor); - return Result.SUCCESS; - } - - /** - * The storage placement of the field--if positioned on a field--undefined otherwise. - * - * @return storage kind. - */ - public StorageKind storage() { - switch (this.state) { - case SCHEMATIZED: - return this.columns.get(this.columnIndex).storage(); - case SPARSE: - return StorageKind.SPARSE; - default: - return null; - } - } - - /** - * The type of the field--if positioned on a field--undefined otherwise. - * - * @return layout type or {@code null}. - */ - @Nullable - public LayoutType type() { - - switch (this.state) { - case SCHEMATIZED: - return this.columns.get(this.columnIndex).type(); - case SPARSE: - return this.cursor.cellType(); - default: - return null; - } - } - - /** - * The type arguments of the field, if positioned on a field, undefined otherwise. - * - * @return type argument list. - */ - public TypeArgumentList typeArgs() { - - switch (this.state) { - case SCHEMATIZED: - return this.columns.get(this.columnIndex).typeArgs(); - case SPARSE: - return this.cursor.cellTypeArgs(); - default: - return TypeArgumentList.EMPTY; - } - } - - /** - * Reads a generic schematized field value via the scope's layout - * - * @param value On success, receives the value, undefined otherwise - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - @Nonnull - private Result readPrimitiveValue(@Nonnull Out value) { - - final LayoutColumn column = this.columns.get(this.columnIndex); - final LayoutType type = this.columns.get(this.columnIndex).type(); - - if (!(type instanceof LayoutTypePrimitive)) { - value.set(null); - return Result.TYPE_MISMATCH; - } - - final StorageKind storage = column == null ? StorageKind.NONE : column.storage(); - - switch (storage) { - case FIXED: - return type.>typeAs().readFixed(this.buffer, this.cursor, column, value); - case VARIABLE: - return type.>typeAs().readVariable(this.buffer, this.cursor, column, value); - default: - String message = lenientFormat("expected FIXED or VARIABLE column storage, not %s", storage); - throw new IllegalStateException(message); - } - } - -// /** -// * Reads a generic schematized field value via the scope's layout -// * -// * @param value On success, receives the value, undefined otherwise -// * @return {@link Result#SUCCESS} if the read is successful; an error {@link Result} otherwise -// */ -// private Result readPrimitiveValue(Out value) { -// -// LayoutColumn column = this.columns.get(this.columnIndex); -// LayoutType type = this.columns.get(this.columnIndex).type(); -// -// if (!(type instanceof LayoutUtf8Readable)) { -// value.set(null); -// return Result.TYPE_MISMATCH; -// } -// -// StorageKind storage = column == null ? StorageKind.NONE : column.storage(); -// -// switch (storage) { -// -// case FIXED: -// return type.typeAs().readFixed(this.row, this.cursor, column, value); -// -// case VARIABLE: -// return type.typeAs().readVariable(this.row, this.cursor, column, value); -// -// default: -// assert false : lenientFormat("expected FIXED or VARIABLE column storage, not %s", storage); -// value.set(null); -// return Result.FAILURE; -// } -// } -// - /** - * Reads a generic schematized field value via the scope's layout - * - * @param The sub-element type of the field - * @param value On success, receives the value, undefined otherwise - * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. - */ - private Result readPrimitiveValueList(Out> value) { - - LayoutColumn column = this.columns.get(this.columnIndex); - LayoutType type = this.columns.get(this.columnIndex).type(); - - if (!(type instanceof LayoutListReadable)) { - value.set(null); - return Result.TYPE_MISMATCH; - } - - StorageKind storage = column == null ? StorageKind.NONE : column.storage(); - - switch (storage) { - - case FIXED: - return type.>typeAs().readFixedList(this.buffer, this.cursor, column, value); - - case VARIABLE: - return type.>typeAs().readVariableList(this.buffer, this.cursor, column, value); - - default: - assert false : lenientFormat("expected FIXED or VARIABLE column storage, not %s", storage); - value.set(null); - return Result.FAILURE; - } - } - - /** - * The current traversal state of the reader. - */ - public enum States { - /** - * The reader has not be started yet. - */ - NONE, - - /** - * Enumerating schematized fields (fixed and variable) from left to right. - */ - SCHEMATIZED, - - /** - * Enumerating top-level fields of the current scope. - */ - SPARSE, - - /** - * The reader has completed the scope. - */ - DONE; - - public static final int BYTES = Byte.BYTES; - private final String friendlyName; - - States() { - this.friendlyName = this.name().toLowerCase(); - } - - public String friendlyName() { - return this.friendlyName; - } - - public static States from(byte value) { - return values()[value]; - } - - /** - * Returns the friendly name of this enum constant, as returned by {@link #friendlyName()}. - * - * @return the friendly name of this enum constant - */ - @Override - public String toString() { - return this.friendlyName; - } - - public byte value() { - return (byte) this.ordinal(); - } - } - - /** - * A functional interface for reading content from a {@link RowBuffer} - * - * @param The type of the context value passed by the caller - */ - @FunctionalInterface - public interface ReaderFunc { - /** - * The read {@link Result} - * - * @param reader the current reader - * @param context the current reader context - * @return the read result - */ - Result invoke(RowReader reader, TContext context); - } - - /** - * An encapsulation of the current state of a {@link RowReader} - *

- * This value can be used to recreate the {@link RowReader} in the same logical position. - */ - public final static class Checkpoint { - - private int columnIndex; - private RowCursor cursor; - private States state; - - public Checkpoint(States state, int columnIndex, RowCursor cursor) { - this.state(state); - this.columnIndex(columnIndex); - this.cursor(cursor); - } - - public int columnIndex() { - return this.columnIndex; - } - - public Checkpoint columnIndex(int columnIndex) { - this.columnIndex = columnIndex; - return this; - } - - public RowCursor cursor() { - return this.cursor; - } - - public Checkpoint cursor(RowCursor cursor) { - this.cursor = cursor; - return this; - } - - public States state() { - return this.state; - } - - public Checkpoint state(States state) { - this.state = state; - return this; - } - } - - static final class JsonSerializer extends StdSerializer { - - JsonSerializer() { - super(RowReader.class); - } - - @Override - public void serialize(RowReader value, JsonGenerator generator, SerializerProvider provider) throws IOException { - generator.writeStartObject(); - generator.writeStringField("path", value.path().toUtf16()); - generator.writeStringField("state", value.state == null ? null : value.state.friendlyName); - generator.writeObjectFieldStart("span"); - generator.writeNumberField("start", value.cursor.start()); - generator.writeNumberField("end", value.cursor.endOffset()); - generator.writeEndObject(); - LayoutColumn column = value.columns.get(value.columnIndex); - generator.writeObjectFieldStart("column"); - generator.writeStringField("fullPath", column.fullPath().toUtf16()); - generator.writeStringField("type", column.type().name()); - generator.writeNumberField("index", column.index()); - generator.writeNumberField("offset", column.offset()); - generator.writeEndObject(); - generator.writeEndObject(); - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.io; + +import com.azure.data.cosmos.core.Json; +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.core.Utf8String; +import com.azure.data.cosmos.serialization.hybridrow.Float128; +import com.azure.data.cosmos.serialization.hybridrow.NullValue; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.azure.data.cosmos.serialization.hybridrow.RowCursors; +import com.azure.data.cosmos.serialization.hybridrow.UnixDateTime; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBinary; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutBoolean; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutColumn; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutDateTime; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutDecimal; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat128; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat32; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutFloat64; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutGuid; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt16; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt32; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt64; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutInt8; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutListReadable; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNull; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNullable; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutType; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypePrimitive; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUDT; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt16; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt32; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt64; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUInt8; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUnixDateTime; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUtf8; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarInt; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarUInt; +import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; +import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import io.netty.buffer.ByteBuf; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.lenientFormat; + +/** + * A forward-only, streaming, field reader for {@link RowBuffer}. + *

+ * A {@link RowReader} allows the traversal in a streaming, left to right fashion, of an entire HybridRow. The row's + * layout provides decoding for any schematized portion of the row. However, unschematized sparse fields are read + * directly from the sparse segment with or without schematization allowing all fields within the row, both known and + * unknown, to be read. + *

+ * Modifying a {@link RowBuffer} invalidates any reader or child reader associated with it. In general{@link RowBuffer}s + * should not be mutated while being enumerated. + */ +@JsonSerialize(using = RowReader.JsonSerializer.class) +public final class RowReader { + + private int columnIndex; + private List columns; + private RowCursor cursor; + private RowBuffer buffer; + private int schematizedCount; + private States state; + + /** + * Initializes a new instance of the {@link RowReader} class. + * + * @param buffer The row to be read + */ + public RowReader(@Nonnull RowBuffer buffer) { + this(buffer, RowCursor.create(buffer)); + } + + /** + * Initializes a new instance of the {@link RowReader} class. + * + * @param buffer The buffer to be read + * @param checkpoint Initial state of the reader + */ + public RowReader(@Nonnull final RowBuffer buffer, @Nonnull final Checkpoint checkpoint) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(checkpoint, "expected non-null checkpoint"); + + this.buffer = buffer; + this.columns = checkpoint.cursor().layout().columns(); + this.schematizedCount = checkpoint.cursor().layout().numFixed() + checkpoint.cursor().layout().numVariable(); + + this.state = checkpoint.state(); + this.cursor = checkpoint.cursor(); + this.columnIndex = checkpoint.columnIndex(); + } + + /** + * Initializes a new instance of the {@link RowReader} class. + * + * @param buffer The buffer to be read. + * @param scope Cursor defining the scope of the fields to be read. + *

+ * A {@link RowReader} instance traverses all of the top-level fields of a given scope. If the root + * scope is provided then all top-level fields in the buffer are enumerated. Nested child + * {@link RowReader} instances can be access through the {@link RowReader#readScope} method to process + * nested content. + */ + private RowReader(@Nonnull final RowBuffer buffer, @Nonnull final RowCursor scope) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + + this.cursor = scope; + this.buffer = buffer; + this.columns = this.cursor.layout().columns(); + this.schematizedCount = this.cursor.layout().numFixed() + this.cursor.layout().numVariable(); + + this.state = States.NONE; + this.columnIndex = -1; + } + + /** + * Read the current field as a fixed length {@code MongoDbObjectId} value. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result ReadMongoDbObjectId(Out value) { + // TODO: DANOBLE: Resurrect this method + // switch (this.state) { + // + // case Schematized: + // return this.readPrimitiveValue(value); + // + // case Sparse: + // if (!(this.cursor.cellType() instanceof LayoutMongoDbObjectId)) { + // value.set(null); + // return Result.TYPE_MISMATCH; + // } + // value.set(this.row.readSparseMongoDbObjectId(this.cursor)); + // return Result.SUCCESS; + // + // default: + // value.set(null); + // return Result.FAILURE; + // } + throw new UnsupportedOperationException(); + } + + public boolean isDone() { + return this.state == States.DONE; + } + + /** + * {@code true} if field has a value--if positioned on a field--undefined otherwise. + *

+ * If the current field is a Nullable scope, this method return true if the value is not null. If the current field + * is a nullable Null primitive value, this method return true if the value is set (even though its values is set + * to null). + * + * @return {@code true} if field has a value, {@code false} otherwise. + */ + public boolean hasValue() { + + switch (this.state) { + + case SCHEMATIZED: + return true; + + case SPARSE: + if (this.cursor.cellType() instanceof LayoutNullable) { + RowCursor nullableScope = this.buffer.sparseIteratorReadScope(this.cursor, true); + return LayoutNullable.hasValue(this.buffer, nullableScope) == Result.SUCCESS; + } + return true; + + default: + return false; + } + } + + /** + * Zero-based index, relative to the scope, of the field--if positioned on a field--undefined otherwise. + *

+ * When enumerating a non-indexed scope, this value is always zero. + * + * @return zero-based index of the field relative to the scope, if positioned on a field; otherwise undefined. + * @see #path() + */ + public int index() { + return this.state == States.SPARSE ? this.cursor.index() : 0; + } + + /** + * The length of row in bytes. + * + * @return length of the current row in bytes. + */ + public int length() { + return this.buffer.length(); + } + + /** + * The path, relative to the scope, of the field--if positioned on a field--undefined otherwise. + *

+ * When enumerating an indexed scope, this value is always null. + * + * @return path of the field relative to the scope, if positioned on a field; otherwise undefined. + * @see #index + */ + @Nonnull + public Utf8String path() { + switch (this.state) { + case SCHEMATIZED: + return this.columns.get(this.columnIndex).path(); + case SPARSE: + return this.buffer.readSparsePath(this.cursor); + default: + return Utf8String.NULL; + } + } + + /** + * Advances the reader to the next field. + * + * @return {@code true}, if there is another field to be read; {@code false} otherwise. + */ + public boolean read() { + + while (this.state != States.DONE) { + + switch (this.state) { + + case NONE: { + this.state = this.cursor.scopeType() instanceof LayoutUDT ? States.SCHEMATIZED : States.SPARSE; + break; + } + case SCHEMATIZED: { + + this.columnIndex++; + + if (this.columnIndex >= this.schematizedCount) { + this.state = States.SPARSE; + break; + } + + checkState(this.cursor.scopeType() instanceof LayoutUDT); + LayoutColumn column = this.columns.get(this.columnIndex); + + if (!this.buffer.readBit(this.cursor.start(), column.nullBit())) { + break; // to skip schematized values if they aren't present + } + + return true; + } + case SPARSE: { + + if (!RowCursors.moveNext(this.cursor, this.buffer)) { + this.state = States.DONE; + break; + } + return true; + } + } + } + + return false; + } + + /** + * Read the current field as a variable length, sequence of bytes. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readBinary(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + + if (!(this.cursor.cellType() instanceof LayoutBinary)) { + value.set(null); + return Result.TYPE_MISMATCH; + } + + value.set(this.buffer.readSparseBinary(this.cursor)); + return Result.SUCCESS; + + default: + value.set(null); + return Result.FAILURE; + } + } + + /** + * Read the current field as a variable length, sequence of bytes. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readBinaryArray(Out value) { + + Out buffer = new Out<>(); + Result result = this.readBinary(buffer); + + if (result == Result.SUCCESS) { + byte[] array = new byte[buffer.get().writerIndex()]; + buffer.get().getBytes(0, array); + value.set(array); + } + + return result; + } + + /** + * Read the current field as a {@link Boolean}. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readBoolean(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutBoolean)) { + value.set(false); + return Result.TYPE_MISMATCH; + } + + value.set(this.buffer.readSparseBoolean(this.cursor)); + return Result.SUCCESS; + + default: + value.set(false); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length {@code DateTime} value. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readDateTime(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutDateTime)) { + value.set(OffsetDateTime.MIN); + return Result.TYPE_MISMATCH; + } + + value.set(this.buffer.readSparseDateTime(this.cursor)); + return Result.SUCCESS; + + default: + value.set(OffsetDateTime.MIN); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length decimal value. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readDecimal(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutDecimal)) { + value.set(new BigDecimal(0)); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseDecimal(this.cursor)); + return Result.SUCCESS; + + default: + value.set(new BigDecimal(0)); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 128-bit, IEEE-encoded floating point value. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readFloat128(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutFloat128)) { + value.setAndGet(null); + return Result.TYPE_MISMATCH; + } + value.setAndGet(this.buffer.readSparseFloat128(this.cursor)); + return Result.SUCCESS; + + default: + value.setAndGet(null); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 32-bit, IEEE-encoded floating point value + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readFloat32(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutFloat32)) { + value.set(0F); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseFloat32(this.cursor)); + return Result.SUCCESS; + + default: + value.set(0F); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 64-bit, IEEE-encoded floating point value + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readFloat64(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutFloat64)) { + value.set(0D); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseFloat64(this.cursor)); + return Result.SUCCESS; + + default: + value.set(0D); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length GUID value. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readGuid(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutGuid)) { + value.set(null); + return Result.TYPE_MISMATCH; + } + + value.set(this.buffer.readSparseGuid(this.cursor)); + return Result.SUCCESS; + + default: + value.set(null); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 16-bit, signed integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readInt16(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutInt16)) { + value.set((short)0); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseInt16(this.cursor)); + return Result.SUCCESS; + + default: + value.set((short)0); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 32-bit, signed integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readInt32(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutInt32)) { + value.set(0); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseInt32(this.cursor)); + return Result.SUCCESS; + + default: + value.set(0); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 64-bit, signed integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readInt64(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutInt64)) { + value.setAndGet(0L); + return Result.TYPE_MISMATCH; + } + value.setAndGet(this.buffer.readSparseInt64(this.cursor)); + return Result.SUCCESS; + + default: + value.setAndGet(0L); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 8-bit, signed integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readInt8(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutInt8)) { + value.set((byte)0); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseInt8(this.cursor)); + return Result.SUCCESS; + + default: + value.set((byte)0); + return Result.FAILURE; + } + } + + /** + * Read the current field as a null. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readNull(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutNull)) { + value.set(null); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseNull(this.cursor)); + return Result.SUCCESS; + + default: + value.set(null); + return Result.FAILURE; + } + } + + /** + * Read the current field as a nested, structured, sparse scope. + *

+ * Child readers can be used to read all sparse scope types including typed and untyped objects, arrays, tuples, + * set, and maps. + * + * @param a reader context type. + * @param context a reader context. + * @param func a reader function. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + @Nonnull + public Result readScope(@Nullable final TContext context, @Nullable final ReaderFunc func) { + + final RowCursor childScope = this.buffer.sparseIteratorReadScope(this.cursor, true); + final RowReader nestedReader = new RowReader(this.buffer, childScope); + final Result result = func == null ? null : func.invoke(nestedReader, context); + + if (!(result == null || result == Result.SUCCESS)) { + return result; + } + + RowCursors.skip(this.cursor, this.buffer, nestedReader.cursor); + return Result.SUCCESS; + } + + /** + * Read the current field as a nested, structured, sparse scope. + *

+ * Child readers can be used to read all sparse scope types including typed and untyped objects, arrays, tuples, + * set, and maps. Nested child readers are independent of their parent. + * + * @return a new {@link RowReader}. + */ + public @Nonnull RowReader readScope() { + RowCursor scope = this.buffer.sparseIteratorReadScope(this.cursor, true); + return new RowReader(this.buffer, scope); + } + + /** + * Read the current field as a variable length, UTF-8 encoded string value. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readString(Out value) { + + Out string = new Out<>(); + Result result = this.readUtf8String(string); + value.set((result == Result.SUCCESS) ? string.get().toUtf16() : null); + string.get().release(); + + return result; + } + + /** + * Read the current field as a variable length, UTF-8 encoded, string value. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readUtf8String(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutUtf8)) { + value.set(null); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseString(this.cursor)); + return Result.SUCCESS; + + default: + value.set(null); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 16-bit, unsigned integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readUInt16(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutUInt16)) { + value.set(0); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseUInt16(this.cursor)); + return Result.SUCCESS; + + default: + value.set(0); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 32-bit, unsigned integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readUInt32(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutUInt32)) { + value.set(0L); + return Result.TYPE_MISMATCH; + } + + value.set(this.buffer.readSparseUInt32(this.cursor)); + return Result.SUCCESS; + + default: + value.set(0L); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 64-bit, unsigned integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readUInt64(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutUInt64)) { + value.set(0L); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseUInt64(this.cursor)); + return Result.SUCCESS; + + default: + value.set(0L); + return Result.FAILURE; + } + } + + /** + * Read the current field as a fixed length, 8-bit, unsigned integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + @Nonnull + public Result readUInt8(@Nonnull Out value) { + + checkNotNull(value, "expected non-null value"); + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutUInt8)) { + value.set((short)0); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseUInt8(this.cursor)); + return Result.SUCCESS; + + default: + value.set((short)0); + return Result.FAILURE; + } + } + + /** + * Returns a string representation of the object. In general, the + * {@code toString} method returns a string that + * "textually represents" this object. The result should + * be a concise but informative representation that is easy for a + * person to read. + * It is recommended that all subclasses override this method. + *

+ * The {@code toString} method for class {@code Object} + * returns a string consisting of the name of the class of which the + * object is an instance, the at-sign character `{@code @}', and + * the unsigned hexadecimal representation of the hash code of the + * object. In other words, this method returns a string equal to the + * value of: + *

+ *
+     * getClass().getName() + '@' + Integer.toHexString(hashCode())
+     * 
+ * + * @return a string representation of the object. + */ + @Override + public String toString() { + return Json.toString(this); + } + + /** + * Read the current field as a fixed length {@link UnixDateTime} value. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readUnixDateTime(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutUnixDateTime)) { + value.set(null); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseUnixDateTime(this.cursor)); + return Result.SUCCESS; + + default: + value.set(null); + return Result.FAILURE; + } + } + + /** + * Read the current field as a variable length, 7-bit encoded, signed integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readVarInt(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutVarInt)) { + value.set(0L); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseVarInt(this.cursor)); + return Result.SUCCESS; + + default: + value.set(0L); + return Result.FAILURE; + } + } + + /** + * Read the current field as a variable length, 7-bit encoded, unsigned integer. + * + * @param value On success, receives the value, undefined otherwise. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result readVarUInt(Out value) { + + switch (this.state) { + + case SCHEMATIZED: + return this.readPrimitiveValue(value); + + case SPARSE: + if (!(this.cursor.cellType() instanceof LayoutVarUInt)) { + value.set(0L); + return Result.TYPE_MISMATCH; + } + value.set(this.buffer.readSparseVarUInt(this.cursor)); + return Result.SUCCESS; + + default: + value.set(0L); + return Result.FAILURE; + } + } + + public Checkpoint saveCheckpoint() { + return new Checkpoint(this.state, this.columnIndex, this.cursor); + } + + /** + * Advance a reader to the end of a child reader. + *

+ * The child reader is also advanced to the end of its scope. The reader must not have been advanced since the child + * reader was created with {@link #readScope}. This method can be used when the overload of {@link #readScope} that + * takes a {@link ReaderFunc} is not an option. + * + * @param nestedReader nested (child) reader to be advanced. + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + public Result skipScope(@Nonnull final RowReader nestedReader) { + if (nestedReader.cursor.start() != this.cursor.valueOffset()) { + return Result.FAILURE; + } + RowCursors.skip(this.cursor, this.buffer, nestedReader.cursor); + return Result.SUCCESS; + } + + /** + * The storage placement of the field--if positioned on a field--undefined otherwise. + * + * @return storage kind. + */ + public StorageKind storage() { + switch (this.state) { + case SCHEMATIZED: + return this.columns.get(this.columnIndex).storage(); + case SPARSE: + return StorageKind.SPARSE; + default: + return null; + } + } + + /** + * The type of the field--if positioned on a field--undefined otherwise. + * + * @return layout type or {@code null}. + */ + @Nullable + public LayoutType type() { + + switch (this.state) { + case SCHEMATIZED: + return this.columns.get(this.columnIndex).type(); + case SPARSE: + return this.cursor.cellType(); + default: + return null; + } + } + + /** + * The type arguments of the field, if positioned on a field, undefined otherwise. + * + * @return type argument list. + */ + public TypeArgumentList typeArgs() { + + switch (this.state) { + case SCHEMATIZED: + return this.columns.get(this.columnIndex).typeArgs(); + case SPARSE: + return this.cursor.cellTypeArgs(); + default: + return TypeArgumentList.EMPTY; + } + } + + /** + * Reads a generic schematized field value via the scope's layout + * + * @param value On success, receives the value, undefined otherwise + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + @Nonnull + private Result readPrimitiveValue(@Nonnull Out value) { + + final LayoutColumn column = this.columns.get(this.columnIndex); + final LayoutType type = this.columns.get(this.columnIndex).type(); + + if (!(type instanceof LayoutTypePrimitive)) { + value.set(null); + return Result.TYPE_MISMATCH; + } + + final StorageKind storage = column == null ? StorageKind.NONE : column.storage(); + + switch (storage) { + case FIXED: + return type.>typeAs().readFixed(this.buffer, this.cursor, column, value); + case VARIABLE: + return type.>typeAs().readVariable(this.buffer, this.cursor, column, value); + default: + String message = lenientFormat("expected FIXED or VARIABLE column storage, not %s", storage); + throw new IllegalStateException(message); + } + } + +// /** +// * Reads a generic schematized field value via the scope's layout +// * +// * @param value On success, receives the value, undefined otherwise +// * @return {@link Result#SUCCESS} if the read is successful; an error {@link Result} otherwise +// */ +// private Result readPrimitiveValue(Out value) { +// +// LayoutColumn column = this.columns.get(this.columnIndex); +// LayoutType type = this.columns.get(this.columnIndex).type(); +// +// if (!(type instanceof LayoutUtf8Readable)) { +// value.set(null); +// return Result.TYPE_MISMATCH; +// } +// +// StorageKind storage = column == null ? StorageKind.NONE : column.storage(); +// +// switch (storage) { +// +// case FIXED: +// return type.typeAs().readFixed(this.row, this.cursor, column, value); +// +// case VARIABLE: +// return type.typeAs().readVariable(this.row, this.cursor, column, value); +// +// default: +// assert false : lenientFormat("expected FIXED or VARIABLE column storage, not %s", storage); +// value.set(null); +// return Result.FAILURE; +// } +// } +// + /** + * Reads a generic schematized field value via the scope's layout + * + * @param The sub-element type of the field + * @param value On success, receives the value, undefined otherwise + * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. + */ + private Result readPrimitiveValueList(Out> value) { + + LayoutColumn column = this.columns.get(this.columnIndex); + LayoutType type = this.columns.get(this.columnIndex).type(); + + if (!(type instanceof LayoutListReadable)) { + value.set(null); + return Result.TYPE_MISMATCH; + } + + StorageKind storage = column == null ? StorageKind.NONE : column.storage(); + + switch (storage) { + + case FIXED: + return type.>typeAs().readFixedList(this.buffer, this.cursor, column, value); + + case VARIABLE: + return type.>typeAs().readVariableList(this.buffer, this.cursor, column, value); + + default: + assert false : lenientFormat("expected FIXED or VARIABLE column storage, not %s", storage); + value.set(null); + return Result.FAILURE; + } + } + + /** + * The current traversal state of the reader. + */ + public enum States { + /** + * The reader has not be started yet. + */ + NONE, + + /** + * Enumerating schematized fields (fixed and variable) from left to right. + */ + SCHEMATIZED, + + /** + * Enumerating top-level fields of the current scope. + */ + SPARSE, + + /** + * The reader has completed the scope. + */ + DONE; + + public static final int BYTES = Byte.BYTES; + private final String friendlyName; + + States() { + this.friendlyName = this.name().toLowerCase(); + } + + public String friendlyName() { + return this.friendlyName; + } + + public static States from(byte value) { + return values()[value]; + } + + /** + * Returns the friendly name of this enum constant, as returned by {@link #friendlyName()}. + * + * @return the friendly name of this enum constant + */ + @Override + public String toString() { + return this.friendlyName; + } + + public byte value() { + return (byte) this.ordinal(); + } + } + + /** + * A functional interface for reading content from a {@link RowBuffer} + * + * @param The type of the context value passed by the caller + */ + @FunctionalInterface + public interface ReaderFunc { + /** + * The read {@link Result} + * + * @param reader the current reader + * @param context the current reader context + * @return the read result + */ + Result invoke(RowReader reader, TContext context); + } + + /** + * An encapsulation of the current state of a {@link RowReader} + *

+ * This value can be used to recreate the {@link RowReader} in the same logical position. + */ + public final static class Checkpoint { + + private int columnIndex; + private RowCursor cursor; + private States state; + + public Checkpoint(States state, int columnIndex, RowCursor cursor) { + this.state(state); + this.columnIndex(columnIndex); + this.cursor(cursor); + } + + public int columnIndex() { + return this.columnIndex; + } + + public Checkpoint columnIndex(int columnIndex) { + this.columnIndex = columnIndex; + return this; + } + + public RowCursor cursor() { + return this.cursor; + } + + public Checkpoint cursor(RowCursor cursor) { + this.cursor = cursor; + return this; + } + + public States state() { + return this.state; + } + + public Checkpoint state(States state) { + this.state = state; + return this; + } + } + + static final class JsonSerializer extends StdSerializer { + + JsonSerializer() { + super(RowReader.class); + } + + @Override + public void serialize(RowReader value, JsonGenerator generator, SerializerProvider provider) throws IOException { + generator.writeStartObject(); + generator.writeStringField("path", value.path().toUtf16()); + generator.writeStringField("state", value.state == null ? null : value.state.friendlyName); + generator.writeObjectFieldStart("span"); + generator.writeNumberField("start", value.cursor.start()); + generator.writeNumberField("end", value.cursor.endOffset()); + generator.writeEndObject(); + LayoutColumn column = value.columns.get(value.columnIndex); + generator.writeObjectFieldStart("column"); + generator.writeStringField("fullPath", column.fullPath().toUtf16()); + generator.writeStringField("type", column.type().name()); + generator.writeNumberField("index", column.index()); + generator.writeNumberField("offset", column.offset()); + generator.writeEndObject(); + generator.writeEndObject(); + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderExtensions.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderExtensions.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderExtensions.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderExtensions.java index 395fe76..20fd977 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderExtensions.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderExtensions.java @@ -1,95 +1,95 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.io; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.List; - -public final class RowReaderExtensions { - /** - * Read the current field as a nested, structured, sparse scope containing a linear collection of zero or more - * items. - * @param The type of the items within the collection. - * - * @param reader A forward-only cursor for reading the collection. - * @param deserializer A function that reads one item from the collection. - * @param list On success, the collection of materialized items. - * @return The result. - */ - @Nonnull - public static Result readList(RowReader reader, DeserializerFunc deserializer, Out> list) { - - // Pass the context as a struct by value to avoid allocations - - final ListContext context = new ListContext(deserializer, new ArrayList()); - final Out item = new Out<>(); - - Result result = reader.readScope(context, (arrayReader, arrayContext) -> { - while (arrayReader.read()) { - Result arrayResult = arrayReader.readScope(arrayContext, (itemReader, itemContext) -> { - Result itemResult = itemContext.deserializer().invoke(itemReader, item); - if (itemResult != Result.SUCCESS) { - return itemResult; - } - itemContext.items().add(item.get()); - return Result.SUCCESS; - }); - if (arrayResult != Result.SUCCESS) { - return arrayResult; - } - } - return Result.SUCCESS; - }); - - if (result != Result.SUCCESS) { - list.set(null); - return result; - } - - list.set(context.items()); - return Result.SUCCESS; - } - - /** - * A functional interface to read content from a {@link RowReader} - * - * @param The type of item to read - * - */ - @FunctionalInterface - public interface DeserializerFunc { - /** - * Read a row from a {@link RowReader} - * - * @param reader A forward-only cursor for reading the item - * @param item On success, the item read - * @return The result - */ - @Nonnull - Result invoke(@Nonnull RowReader reader, @Nonnull Out item); - } - - private final static class ListContext { - - private final DeserializerFunc deserializer; - private final List items; - - ListContext(DeserializerFunc deserializer, List items) { - this.deserializer = deserializer; - this.items = items; - } - - public DeserializerFunc deserializer() { - return this.deserializer; - } - - public List items() { - return this.items; - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.io; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; + +public final class RowReaderExtensions { + /** + * Read the current field as a nested, structured, sparse scope containing a linear collection of zero or more + * items. + * @param The type of the items within the collection. + * + * @param reader A forward-only cursor for reading the collection. + * @param deserializer A function that reads one item from the collection. + * @param list On success, the collection of materialized items. + * @return The result. + */ + @Nonnull + public static Result readList(RowReader reader, DeserializerFunc deserializer, Out> list) { + + // Pass the context as a struct by value to avoid allocations + + final ListContext context = new ListContext(deserializer, new ArrayList()); + final Out item = new Out<>(); + + Result result = reader.readScope(context, (arrayReader, arrayContext) -> { + while (arrayReader.read()) { + Result arrayResult = arrayReader.readScope(arrayContext, (itemReader, itemContext) -> { + Result itemResult = itemContext.deserializer().invoke(itemReader, item); + if (itemResult != Result.SUCCESS) { + return itemResult; + } + itemContext.items().add(item.get()); + return Result.SUCCESS; + }); + if (arrayResult != Result.SUCCESS) { + return arrayResult; + } + } + return Result.SUCCESS; + }); + + if (result != Result.SUCCESS) { + list.set(null); + return result; + } + + list.set(context.items()); + return Result.SUCCESS; + } + + /** + * A functional interface to read content from a {@link RowReader} + * + * @param The type of item to read + * + */ + @FunctionalInterface + public interface DeserializerFunc { + /** + * Read a row from a {@link RowReader} + * + * @param reader A forward-only cursor for reading the item + * @param item On success, the item read + * @return The result + */ + @Nonnull + Result invoke(@Nonnull RowReader reader, @Nonnull Out item); + } + + private final static class ListContext { + + private final DeserializerFunc deserializer; + private final List items; + + ListContext(DeserializerFunc deserializer, List items) { + this.deserializer = deserializer; + this.items = items; + } + + public DeserializerFunc deserializer() { + return this.deserializer; + } + + public List items() { + return this.items; + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowScanner.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowScanner.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowScanner.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowScanner.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowSerializable.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowSerializable.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowSerializable.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowSerializable.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowWriter.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowWriter.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowWriter.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowWriter.java index e31ea7f..7bf707a 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowWriter.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowWriter.java @@ -1,794 +1,794 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.io; - -import com.azure.data.cosmos.core.Utf8String; -import com.azure.data.cosmos.core.UtfAnyString; -import com.azure.data.cosmos.serialization.hybridrow.Float128; -import com.azure.data.cosmos.serialization.hybridrow.NullValue; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.azure.data.cosmos.serialization.hybridrow.RowCursors; -import com.azure.data.cosmos.serialization.hybridrow.UnixDateTime; -import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutArray; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutColumn; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutListWritable; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNullable; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutObject; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutResolver; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTagged; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTagged2; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTuple; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutType; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypePrimitive; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypeScope; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedArray; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedMap; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedSet; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedTuple; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypes; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUDT; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUniqueScope; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUtf8Writable; -import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgument; -import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; -import com.azure.data.cosmos.serialization.hybridrow.layouts.UpdateOptions; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.math.BigDecimal; -import java.time.OffsetDateTime; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Consumer; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.lenientFormat; - -public final class RowWriter { - - private RowCursor cursor; - private RowBuffer row; - - /** - * Initializes a new instance of the {@link RowWriter} class. - * - * @param row The row to be read. - * @param scope The scope into which items should be written. - *

- * A {@link 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 - */ - private RowWriter(RowBuffer row, RowCursor scope) { - this.row = row; - this.cursor = scope; - } - - /** - * The active layout of the current writer scope. - * - * @return layout of the current writer scope. - */ - public Layout layout() { - return this.cursor.layout(); - } - - /** - * The length of row in bytes. - * - * @return length of the row in bytes. - */ - public int length() { - return this.row.length(); - } - - /** - * The resolver for UDTs. - * - * @return the resolver of UDTs. - */ - public LayoutResolver resolver() { - return this.row.resolver(); - } - - /** - * Write a field as a variable length, sequence of bytes. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeBinary(UtfAnyString path, byte[] value) { - return this.writeBinary(path, Unpooled.wrappedBuffer(value)); - } - - /** - * Write a field as a variable length, sequence of bytes. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeBinary(UtfAnyString path, ByteBuf value) { - return this.writePrimitive(path, value, LayoutTypes.BINARY, - field -> this.row.writeSparseBinary(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a {@link Boolean}. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeBoolean(UtfAnyString path, boolean value) { - return this.writePrimitive(path, value, LayoutTypes.BOOLEAN, - field -> this.row.writeSparseBoolean(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write an entire buffer in a streaming left-to-right way. - * - * @param The type of the context value to pass to {@code func}. - * @param buffer The buffer to write. - * @param context A context value to pass to {@code func}. - * @param func A function to write the entire buffer. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public static Result writeBuffer( - @Nonnull final RowBuffer buffer, final @Nonnull TContext context, @Nonnull final WriterFunc func) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(context, "expected non-null context"); - checkNotNull(func, "expected non-null func"); - - RowCursor scope = RowCursor.create(buffer); - RowWriter writer = new RowWriter(buffer, scope); - TypeArgument typeArg = new TypeArgument(LayoutTypes.UDT, new TypeArgumentList(scope.layout().schemaId())); - - return func.invoke(writer, typeArg, context); - } - - /** - * Write a field as a fixed length {@code DateTime} value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeDateTime(UtfAnyString path, OffsetDateTime value) { - return this.writePrimitive(path, value, LayoutTypes.DATE_TIME, - field -> this.row.writeSparseDateTime(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length {@code Decimal} value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeDecimal(UtfAnyString path, BigDecimal value) { - return this.writePrimitive(path, value, LayoutTypes.DECIMAL, - field -> this.row.writeSparseDecimal(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 128-bit, IEEE-encoded floating point value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeFloat128(UtfAnyString path, Float128 value) { - return this.writePrimitive(path, value, LayoutTypes.FLOAT_128, - field -> this.row.writeSparseFloat128(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 32-bit, IEEE-encoded floating point value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeFloat32(UtfAnyString path, float value) { - return this.writePrimitive(path, value, LayoutTypes.FLOAT_32, - field -> this.row.writeSparseFloat32(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 64-bit, IEEE-encoded floating point value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeFloat64(UtfAnyString path, double value) { - return this.writePrimitive(path, value, LayoutTypes.FLOAT_64, - field -> this.row.writeSparseFloat64(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length {@code Guid} value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeGuid(UtfAnyString path, UUID value) { - return this.writePrimitive(path, value, LayoutTypes.GUID, - field -> this.row.writeSparseGuid(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 16-bit, signed integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeInt16(UtfAnyString path, short value) { - return this.writePrimitive(path, value, LayoutTypes.INT_16, - field -> this.row.writeSparseInt16(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 32-bit, signed integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeInt32(UtfAnyString path, int value) { - return this.writePrimitive(path, value, LayoutTypes.INT_32, - field -> this.row.writeSparseInt32(this.cursor, field, UpdateOptions.UPSERT)); - } - - /** - * Write a field as a fixed length, 64-bit, signed integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeInt64(UtfAnyString path, long value) { - return this.writePrimitive(path, value, LayoutTypes.INT_64, - field -> this.row.writeSparseInt64(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 8-bit, signed integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeInt8(UtfAnyString path, byte value) { - return this.writePrimitive(path, value, LayoutTypes.INT_8, - field -> this.row.writeSparseInt8(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - // TODO: DANOBLE: Resurrect this method - // /** - // * Write a field as a fixed length {@link MongoDbObjectId} value. - // * - // * @param path The scope-relative path of the field to write. - // * @param value The value to write. - // * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - // */ - // public Result WriteMongoDbObjectId(UtfAnyString path, MongoDbObjectId value) { - // throw new UnsupportedOperationException(); - // // return this.writePrimitive(path, value, LayoutTypes.MongoDbObjectId, (ref RowWriter w, MongoDbObjectId v) -> w.row.writeSparseMongoDbObjectId(ref w.cursor, v, UpdateOptions.UPSERT)); - // } - - /** - * Write a field as a {@code null}. - * - * @param path The scope-relative path of the field to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeNull(UtfAnyString path) { - return this.writePrimitive(path, NullValue.DEFAULT, LayoutTypes.NULL, - field -> this.row.writeSparseNull(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - public Result writeScope( - @Nonnull final UtfAnyString path, - @Nonnull final TypeArgument typeArg, - @Nullable final TContext context, - @Nullable final WriterFunc func) { - - checkNotNull(path, "expected non-null path"); - checkNotNull(typeArg, "expected non-null typeArg"); - - Result result = this.prepareSparseWrite(path, typeArg); - - if (result != Result.SUCCESS) { - return result; - } - - final UpdateOptions options = UpdateOptions.UPSERT; - final LayoutType type = typeArg.type(); - final RowCursor nestedScope; - - if (type instanceof LayoutObject) { - - nestedScope = this.row.writeSparseObject(this.cursor, (LayoutObject) type, options); - - } else if (type instanceof LayoutArray) { - - nestedScope = this.row.writeSparseArray(this.cursor, (LayoutArray) type, options); - - } else if (type instanceof LayoutTypedArray) { - - nestedScope = this.row.writeTypedArray(this.cursor, (LayoutTypedArray) type, typeArg.typeArgs(), options); - - } else if (type instanceof LayoutTuple) { - - nestedScope = this.row.writeSparseTuple(this.cursor, (LayoutTuple) type, typeArg.typeArgs(), options); - - } else if (type instanceof LayoutTypedTuple) { - - nestedScope = this.row.writeTypedTuple(this.cursor, (LayoutTypedTuple) type, typeArg.typeArgs(), options); - - } else if (type instanceof LayoutTagged) { - - nestedScope = this.row.writeTypedTuple(this.cursor, (LayoutTagged) type, typeArg.typeArgs(), options); - - } else if (type instanceof LayoutTagged2) { - - nestedScope = this.row.writeTypedTuple(this.cursor, (LayoutTagged2) type, typeArg.typeArgs(), options); - - } else if (type instanceof LayoutNullable) { - - nestedScope = this.row.writeNullable(this.cursor, (LayoutNullable) type, typeArg.typeArgs(), options, - func != null); - - } else if (type instanceof LayoutUDT) { - - LayoutUDT scopeType = (LayoutUDT) type; - Layout udt = this.row.resolver().resolve(typeArg.typeArgs().schemaId()); - nestedScope = this.row.writeSparseUDT(this.cursor, scopeType, udt, options); - - } else if (type instanceof LayoutTypedSet) { - - LayoutTypedSet scopeType = (LayoutTypedSet) type; - nestedScope = this.row.writeTypedSet(this.cursor, scopeType, typeArg.typeArgs(), options); - - } else if (type instanceof LayoutTypedMap) { - - LayoutTypedMap scopeType = (LayoutTypedMap) type; - nestedScope = this.row.writeTypedMap(this.cursor, scopeType, typeArg.typeArgs(), options); - - } else { - - throw new IllegalStateException(lenientFormat("expected type argument of %s, not %s", - LayoutTypeScope.class, - type.getClass())); - } - - RowWriter nestedWriter = new RowWriter(this.row, nestedScope); - result = func == null ? null : func.invoke(nestedWriter, typeArg, context); - - if (result == null) { - result = 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 instanceof LayoutUniqueScope) { - result = this.row.typedCollectionUniqueIndexRebuild(nestedScope); - if (result != Result.SUCCESS) { - // TODO: If the index rebuild fails then the row is corrupted. Should we automatically clean up here? - return result; - } - } - - RowCursors.moveNext(this.cursor, this.row, nestedWriter.cursor); - return Result.SUCCESS; - } - - /** - * Write a field as a variable length, UTF8 encoded, string value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeString(UtfAnyString path, String value) { - - // TODO: DANOBLE: RowBuffer should support writing String values directly (without conversion to Utf8String) - - Utf8String string = Utf8String.transcodeUtf16(value); - assert string != null; - - try { - return this.writePrimitive(path, value, LayoutTypes.UTF_8, - field -> this.row.writeSparseString(this.cursor, string, UpdateOptions.UPSERT) - ); - } finally { - string.release(); - } - } - - /** - * Write a field as a variable length, UTF8 encoded, string value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeString(UtfAnyString path, Utf8String value) { - // TODO: DANOBLE: BUG FIX: this.writePrimitive should write Utf8String as well as String - // note incorrect use of string "value" as the value argument - return this.writePrimitive(path, "value", LayoutTypes.UTF_8, - field -> this.row.writeSparseString(this.cursor, value, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 16-bit, unsigned integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeUInt16(UtfAnyString path, short value) { - return this.writePrimitive(path, (int) value, LayoutTypes.UINT_16, - field -> this.row.writeSparseUInt16(this.cursor, field.shortValue(), UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 32-bit, unsigned integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeUInt32(UtfAnyString path, long value) { - return this.writePrimitive(path, value, LayoutTypes.UINT_32, field -> - this.row.writeSparseUInt32(this.cursor, field.intValue(), UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 64-bit, unsigned integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeUInt64(UtfAnyString path, long value) { - return this.writePrimitive(path, value, LayoutTypes.UINT_64, field -> - this.row.writeSparseUInt64(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length, 8-bit, unsigned integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeUInt8(UtfAnyString path, byte value) { - return this.writePrimitive(path, (short) value, LayoutTypes.UINT_8, - field -> this.row.writeSparseUInt8(this.cursor, field.byteValue(), UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a fixed length {@link UnixDateTime} value. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeUnixDateTime(UtfAnyString path, UnixDateTime value) { - return this.writePrimitive(path, value, LayoutTypes.UNIX_DATE_TIME, - field -> this.row.writeSparseUnixDateTime(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a variable length, 7-bit encoded, signed integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeVarInt(UtfAnyString path, long value) { - return this.writePrimitive(path, value, LayoutTypes.VAR_INT, - field -> this.row.writeSparseVarInt(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Write a field as a variable length, 7-bit encoded, unsigned integer. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - public Result writeVarUInt(UtfAnyString path, long value) { - return this.writePrimitive(path, value, LayoutTypes.VAR_UINT, - field -> this.row.writeSparseVarUInt(this.cursor, field, UpdateOptions.UPSERT) - ); - } - - /** - * Helper for preparing the write of a sparse field. - * - * @param path The path identifying the field to write. - * @param typeArg The (optional) type constraints. - * @return Success if the write is permitted, the error code otherwise. - */ - private Result prepareSparseWrite(UtfAnyString path, TypeArgument typeArg) { - - if (this.cursor.scopeType().isFixedArity() && !(this.cursor.scopeType() instanceof LayoutNullable)) { - if ((this.cursor.index() < this.cursor.scopeTypeArgs().count()) && !typeArg.equals(this.cursor.scopeTypeArgs().get(this.cursor.index()))) { - return Result.TYPE_CONSTRAINT; - } - } else if (this.cursor.scopeType() instanceof LayoutTypedMap) { - if (!typeArg.equals(this.cursor.scopeType().typeAs().fieldType(this.cursor))) { - return Result.TYPE_CONSTRAINT; - } - } else if (this.cursor.scopeType().isTypedScope() && !typeArg.equals(this.cursor.scopeTypeArgs().get(0))) { - return Result.TYPE_CONSTRAINT; - } - - this.cursor.writePath(path); - return Result.SUCCESS; - } - - // TODO: DANOBLE: Does Java implementation need this method? - /** - * Helper for writing a primitive value. - * - * @param The type of layout type. - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @param type The layout type. - * @param sparse The {@link RowBuffer} access method for {@code type}. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - private - Result writePrimitive(UtfAnyString path, Utf8String value, TLayoutType type, Consumer sparse) { - - Result result = Result.NOT_FOUND; - - if (this.cursor.scopeType() instanceof LayoutUDT) { - result = this.writeSchematizedValue(path, value); - } - - if (result == Result.NOT_FOUND) { - - result = this.prepareSparseWrite(path, type.typeArg()); - - if (result != Result.SUCCESS) { - return result; - } - - sparse.accept(value); - RowCursors.moveNext(this.cursor, this.row); - } - - return result; - } - - // TODO: DANOBLE: Does Java implementation need this method? - /** - * Helper for writing a primitive value. - * - * @param The type of layout type. - * @param The sub-element type of the field. - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @param type The layout type. - * @param sparse The {@link RowBuffer} access method for {@code type}. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - private , TValue> - Result writePrimitiveList(UtfAnyString path, List value, TLayoutType type, Consumer> sparse) { - - Result result = Result.NOT_FOUND; - - if (this.cursor.scopeType() instanceof LayoutUDT) { - result = this.writeSchematizedValue(path, value); - } - - if (result == Result.NOT_FOUND) { - - result = this.prepareSparseWrite(path, type.typeArg()); - - if (result != Result.SUCCESS) { - return result; - } - - sparse.accept(value); - RowCursors.moveNext(this.cursor, this.row); - } - - return result; - } - - /** - * Helper for writing a primitive value. - * - * @param The type of the primitive value. - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @param type The layout type. - * @param sparse The {@link RowBuffer} access method for {@code type}. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - private Result writePrimitive( - UtfAnyString path, TValue value, LayoutTypePrimitive type, Consumer sparse) { - - Result result = Result.NOT_FOUND; - - if (this.cursor.scopeType() instanceof LayoutUDT) { - result = this.writeSchematizedValue(path, value); - } - - if (result == Result.NOT_FOUND) { - - result = this.prepareSparseWrite(path, type.typeArg()); - - if (result != Result.SUCCESS) { - return result; - } - - sparse.accept(value); - RowCursors.moveNext(this.cursor, this.row); - } - - return result; - } - - /** - * Write a generic schematized field value via the scope's layout. - * - * @param The expected type of the field. - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - private Result writeSchematizedValue(UtfAnyString path, TValue value) { - - final Optional column = this.cursor.layout().tryFind(path); - - if (!column.isPresent()) { - return Result.NOT_FOUND; - } - - // TODO: DANOBLE: Add a mechanism for performing the equivalent of this type check - // if (!(column.Type is LayoutTypePrimitive t)) { - // return Result.NotFound; - // } - // Type erasure prevents this test: - // column.type instanceof LayoutTypePrimitive - // Reason: Runtime does not instantiate or otherwise represent or identify instances of a generic type. - - if (!(column.get().type() instanceof LayoutTypePrimitive)) { - return Result.NOT_FOUND; - } - - @SuppressWarnings("unchecked") - LayoutTypePrimitive type = (LayoutTypePrimitive)column.get().type(); - - switch (column.get().storage()) { - case FIXED: - return type.writeFixed(this.row, this.cursor, column.get(), value); - case VARIABLE: - return type.writeVariable(this.row, this.cursor, column.get(), value); - } - - return Result.NOT_FOUND; - } - - /** - * Write a generic schematized field value via the scope's layout. - * - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - private Result writeSchematizedValue(UtfAnyString path, Utf8String value) { - - final Optional column = this.cursor.layout().tryFind(path); - - if (!column.isPresent()) { - return Result.NOT_FOUND; - } - - final LayoutType type = column.get().type(); - - if (!(type instanceof LayoutUtf8Writable)) { - return Result.NOT_FOUND; - } - - switch (column.get().storage()) { - case FIXED: - return type.typeAs().writeFixed(this.row, this.cursor, column.get(), value); - case VARIABLE: - return type.typeAs().writeVariable(this.row, this.cursor, column.get(), value); - } - - return Result.NOT_FOUND; - } - - /** - * Write a generic schematized field value via the scope's layout. - * - * @param The sub-element type of the field. - * @param path The scope-relative path of the field to write. - * @param value The value to write. - * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. - */ - private Result writeSchematizedValue(UtfAnyString path, List value) { - - final Optional column = this.cursor.layout().tryFind(path); - - if (!column.isPresent()) { - return Result.NOT_FOUND; - } - - final LayoutType type = column.get().type(); - - if (!(type instanceof LayoutListWritable)) { - return Result.NOT_FOUND; - } - - switch (column.get().storage()) { - case FIXED: - return type.>typeAs().writeFixedList(this.row, this.cursor, column.get(), value); - case VARIABLE: - return type.>typeAs().writeVariableList(this.row, this.cursor, column.get(), value); - } - - return Result.NOT_FOUND; - } - - /** - * Functional interface for writing content to a {@link RowBuffer}. - */ - @FunctionalInterface - public interface WriterFunc { - /** - * Write content using the specified writer, type argument, and context. - * - * @param writer writes content. - * @param typeArg specifies a type argument. - * @param context provides context for the write operation. - * @return a result code - */ - Result invoke(RowWriter writer, TypeArgument typeArg, TContext context); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.io; + +import com.azure.data.cosmos.core.Utf8String; +import com.azure.data.cosmos.core.UtfAnyString; +import com.azure.data.cosmos.serialization.hybridrow.Float128; +import com.azure.data.cosmos.serialization.hybridrow.NullValue; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.azure.data.cosmos.serialization.hybridrow.RowCursors; +import com.azure.data.cosmos.serialization.hybridrow.UnixDateTime; +import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutArray; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutColumn; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutListWritable; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutNullable; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutObject; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutResolver; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTagged; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTagged2; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTuple; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutType; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypePrimitive; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypeScope; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedArray; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedMap; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedSet; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypedTuple; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutTypes; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUDT; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUniqueScope; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutUtf8Writable; +import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgument; +import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; +import com.azure.data.cosmos.serialization.hybridrow.layouts.UpdateOptions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Consumer; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.lenientFormat; + +public final class RowWriter { + + private RowCursor cursor; + private RowBuffer row; + + /** + * Initializes a new instance of the {@link RowWriter} class. + * + * @param row The row to be read. + * @param scope The scope into which items should be written. + *

+ * A {@link 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 + */ + private RowWriter(RowBuffer row, RowCursor scope) { + this.row = row; + this.cursor = scope; + } + + /** + * The active layout of the current writer scope. + * + * @return layout of the current writer scope. + */ + public Layout layout() { + return this.cursor.layout(); + } + + /** + * The length of row in bytes. + * + * @return length of the row in bytes. + */ + public int length() { + return this.row.length(); + } + + /** + * The resolver for UDTs. + * + * @return the resolver of UDTs. + */ + public LayoutResolver resolver() { + return this.row.resolver(); + } + + /** + * Write a field as a variable length, sequence of bytes. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeBinary(UtfAnyString path, byte[] value) { + return this.writeBinary(path, Unpooled.wrappedBuffer(value)); + } + + /** + * Write a field as a variable length, sequence of bytes. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeBinary(UtfAnyString path, ByteBuf value) { + return this.writePrimitive(path, value, LayoutTypes.BINARY, + field -> this.row.writeSparseBinary(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a {@link Boolean}. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeBoolean(UtfAnyString path, boolean value) { + return this.writePrimitive(path, value, LayoutTypes.BOOLEAN, + field -> this.row.writeSparseBoolean(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write an entire buffer in a streaming left-to-right way. + * + * @param The type of the context value to pass to {@code func}. + * @param buffer The buffer to write. + * @param context A context value to pass to {@code func}. + * @param func A function to write the entire buffer. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public static Result writeBuffer( + @Nonnull final RowBuffer buffer, final @Nonnull TContext context, @Nonnull final WriterFunc func) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(context, "expected non-null context"); + checkNotNull(func, "expected non-null func"); + + RowCursor scope = RowCursor.create(buffer); + RowWriter writer = new RowWriter(buffer, scope); + TypeArgument typeArg = new TypeArgument(LayoutTypes.UDT, new TypeArgumentList(scope.layout().schemaId())); + + return func.invoke(writer, typeArg, context); + } + + /** + * Write a field as a fixed length {@code DateTime} value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeDateTime(UtfAnyString path, OffsetDateTime value) { + return this.writePrimitive(path, value, LayoutTypes.DATE_TIME, + field -> this.row.writeSparseDateTime(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length {@code Decimal} value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeDecimal(UtfAnyString path, BigDecimal value) { + return this.writePrimitive(path, value, LayoutTypes.DECIMAL, + field -> this.row.writeSparseDecimal(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 128-bit, IEEE-encoded floating point value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeFloat128(UtfAnyString path, Float128 value) { + return this.writePrimitive(path, value, LayoutTypes.FLOAT_128, + field -> this.row.writeSparseFloat128(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 32-bit, IEEE-encoded floating point value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeFloat32(UtfAnyString path, float value) { + return this.writePrimitive(path, value, LayoutTypes.FLOAT_32, + field -> this.row.writeSparseFloat32(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 64-bit, IEEE-encoded floating point value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeFloat64(UtfAnyString path, double value) { + return this.writePrimitive(path, value, LayoutTypes.FLOAT_64, + field -> this.row.writeSparseFloat64(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length {@code Guid} value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeGuid(UtfAnyString path, UUID value) { + return this.writePrimitive(path, value, LayoutTypes.GUID, + field -> this.row.writeSparseGuid(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 16-bit, signed integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeInt16(UtfAnyString path, short value) { + return this.writePrimitive(path, value, LayoutTypes.INT_16, + field -> this.row.writeSparseInt16(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 32-bit, signed integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeInt32(UtfAnyString path, int value) { + return this.writePrimitive(path, value, LayoutTypes.INT_32, + field -> this.row.writeSparseInt32(this.cursor, field, UpdateOptions.UPSERT)); + } + + /** + * Write a field as a fixed length, 64-bit, signed integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeInt64(UtfAnyString path, long value) { + return this.writePrimitive(path, value, LayoutTypes.INT_64, + field -> this.row.writeSparseInt64(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 8-bit, signed integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeInt8(UtfAnyString path, byte value) { + return this.writePrimitive(path, value, LayoutTypes.INT_8, + field -> this.row.writeSparseInt8(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + // TODO: DANOBLE: Resurrect this method + // /** + // * Write a field as a fixed length {@link MongoDbObjectId} value. + // * + // * @param path The scope-relative path of the field to write. + // * @param value The value to write. + // * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + // */ + // public Result WriteMongoDbObjectId(UtfAnyString path, MongoDbObjectId value) { + // throw new UnsupportedOperationException(); + // // return this.writePrimitive(path, value, LayoutTypes.MongoDbObjectId, (ref RowWriter w, MongoDbObjectId v) -> w.row.writeSparseMongoDbObjectId(ref w.cursor, v, UpdateOptions.UPSERT)); + // } + + /** + * Write a field as a {@code null}. + * + * @param path The scope-relative path of the field to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeNull(UtfAnyString path) { + return this.writePrimitive(path, NullValue.DEFAULT, LayoutTypes.NULL, + field -> this.row.writeSparseNull(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + public Result writeScope( + @Nonnull final UtfAnyString path, + @Nonnull final TypeArgument typeArg, + @Nullable final TContext context, + @Nullable final WriterFunc func) { + + checkNotNull(path, "expected non-null path"); + checkNotNull(typeArg, "expected non-null typeArg"); + + Result result = this.prepareSparseWrite(path, typeArg); + + if (result != Result.SUCCESS) { + return result; + } + + final UpdateOptions options = UpdateOptions.UPSERT; + final LayoutType type = typeArg.type(); + final RowCursor nestedScope; + + if (type instanceof LayoutObject) { + + nestedScope = this.row.writeSparseObject(this.cursor, (LayoutObject) type, options); + + } else if (type instanceof LayoutArray) { + + nestedScope = this.row.writeSparseArray(this.cursor, (LayoutArray) type, options); + + } else if (type instanceof LayoutTypedArray) { + + nestedScope = this.row.writeTypedArray(this.cursor, (LayoutTypedArray) type, typeArg.typeArgs(), options); + + } else if (type instanceof LayoutTuple) { + + nestedScope = this.row.writeSparseTuple(this.cursor, (LayoutTuple) type, typeArg.typeArgs(), options); + + } else if (type instanceof LayoutTypedTuple) { + + nestedScope = this.row.writeTypedTuple(this.cursor, (LayoutTypedTuple) type, typeArg.typeArgs(), options); + + } else if (type instanceof LayoutTagged) { + + nestedScope = this.row.writeTypedTuple(this.cursor, (LayoutTagged) type, typeArg.typeArgs(), options); + + } else if (type instanceof LayoutTagged2) { + + nestedScope = this.row.writeTypedTuple(this.cursor, (LayoutTagged2) type, typeArg.typeArgs(), options); + + } else if (type instanceof LayoutNullable) { + + nestedScope = this.row.writeNullable(this.cursor, (LayoutNullable) type, typeArg.typeArgs(), options, + func != null); + + } else if (type instanceof LayoutUDT) { + + LayoutUDT scopeType = (LayoutUDT) type; + Layout udt = this.row.resolver().resolve(typeArg.typeArgs().schemaId()); + nestedScope = this.row.writeSparseUDT(this.cursor, scopeType, udt, options); + + } else if (type instanceof LayoutTypedSet) { + + LayoutTypedSet scopeType = (LayoutTypedSet) type; + nestedScope = this.row.writeTypedSet(this.cursor, scopeType, typeArg.typeArgs(), options); + + } else if (type instanceof LayoutTypedMap) { + + LayoutTypedMap scopeType = (LayoutTypedMap) type; + nestedScope = this.row.writeTypedMap(this.cursor, scopeType, typeArg.typeArgs(), options); + + } else { + + throw new IllegalStateException(lenientFormat("expected type argument of %s, not %s", + LayoutTypeScope.class, + type.getClass())); + } + + RowWriter nestedWriter = new RowWriter(this.row, nestedScope); + result = func == null ? null : func.invoke(nestedWriter, typeArg, context); + + if (result == null) { + result = 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 instanceof LayoutUniqueScope) { + result = this.row.typedCollectionUniqueIndexRebuild(nestedScope); + if (result != Result.SUCCESS) { + // TODO: If the index rebuild fails then the row is corrupted. Should we automatically clean up here? + return result; + } + } + + RowCursors.moveNext(this.cursor, this.row, nestedWriter.cursor); + return Result.SUCCESS; + } + + /** + * Write a field as a variable length, UTF8 encoded, string value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeString(UtfAnyString path, String value) { + + // TODO: DANOBLE: RowBuffer should support writing String values directly (without conversion to Utf8String) + + Utf8String string = Utf8String.transcodeUtf16(value); + assert string != null; + + try { + return this.writePrimitive(path, value, LayoutTypes.UTF_8, + field -> this.row.writeSparseString(this.cursor, string, UpdateOptions.UPSERT) + ); + } finally { + string.release(); + } + } + + /** + * Write a field as a variable length, UTF8 encoded, string value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeString(UtfAnyString path, Utf8String value) { + // TODO: DANOBLE: BUG FIX: this.writePrimitive should write Utf8String as well as String + // note incorrect use of string "value" as the value argument + return this.writePrimitive(path, "value", LayoutTypes.UTF_8, + field -> this.row.writeSparseString(this.cursor, value, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 16-bit, unsigned integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeUInt16(UtfAnyString path, short value) { + return this.writePrimitive(path, (int) value, LayoutTypes.UINT_16, + field -> this.row.writeSparseUInt16(this.cursor, field.shortValue(), UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 32-bit, unsigned integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeUInt32(UtfAnyString path, long value) { + return this.writePrimitive(path, value, LayoutTypes.UINT_32, field -> + this.row.writeSparseUInt32(this.cursor, field.intValue(), UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 64-bit, unsigned integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeUInt64(UtfAnyString path, long value) { + return this.writePrimitive(path, value, LayoutTypes.UINT_64, field -> + this.row.writeSparseUInt64(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length, 8-bit, unsigned integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeUInt8(UtfAnyString path, byte value) { + return this.writePrimitive(path, (short) value, LayoutTypes.UINT_8, + field -> this.row.writeSparseUInt8(this.cursor, field.byteValue(), UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a fixed length {@link UnixDateTime} value. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeUnixDateTime(UtfAnyString path, UnixDateTime value) { + return this.writePrimitive(path, value, LayoutTypes.UNIX_DATE_TIME, + field -> this.row.writeSparseUnixDateTime(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a variable length, 7-bit encoded, signed integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeVarInt(UtfAnyString path, long value) { + return this.writePrimitive(path, value, LayoutTypes.VAR_INT, + field -> this.row.writeSparseVarInt(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Write a field as a variable length, 7-bit encoded, unsigned integer. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + public Result writeVarUInt(UtfAnyString path, long value) { + return this.writePrimitive(path, value, LayoutTypes.VAR_UINT, + field -> this.row.writeSparseVarUInt(this.cursor, field, UpdateOptions.UPSERT) + ); + } + + /** + * Helper for preparing the write of a sparse field. + * + * @param path The path identifying the field to write. + * @param typeArg The (optional) type constraints. + * @return Success if the write is permitted, the error code otherwise. + */ + private Result prepareSparseWrite(UtfAnyString path, TypeArgument typeArg) { + + if (this.cursor.scopeType().isFixedArity() && !(this.cursor.scopeType() instanceof LayoutNullable)) { + if ((this.cursor.index() < this.cursor.scopeTypeArgs().count()) && !typeArg.equals(this.cursor.scopeTypeArgs().get(this.cursor.index()))) { + return Result.TYPE_CONSTRAINT; + } + } else if (this.cursor.scopeType() instanceof LayoutTypedMap) { + if (!typeArg.equals(this.cursor.scopeType().typeAs().fieldType(this.cursor))) { + return Result.TYPE_CONSTRAINT; + } + } else if (this.cursor.scopeType().isTypedScope() && !typeArg.equals(this.cursor.scopeTypeArgs().get(0))) { + return Result.TYPE_CONSTRAINT; + } + + this.cursor.writePath(path); + return Result.SUCCESS; + } + + // TODO: DANOBLE: Does Java implementation need this method? + /** + * Helper for writing a primitive value. + * + * @param The type of layout type. + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @param type The layout type. + * @param sparse The {@link RowBuffer} access method for {@code type}. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + private + Result writePrimitive(UtfAnyString path, Utf8String value, TLayoutType type, Consumer sparse) { + + Result result = Result.NOT_FOUND; + + if (this.cursor.scopeType() instanceof LayoutUDT) { + result = this.writeSchematizedValue(path, value); + } + + if (result == Result.NOT_FOUND) { + + result = this.prepareSparseWrite(path, type.typeArg()); + + if (result != Result.SUCCESS) { + return result; + } + + sparse.accept(value); + RowCursors.moveNext(this.cursor, this.row); + } + + return result; + } + + // TODO: DANOBLE: Does Java implementation need this method? + /** + * Helper for writing a primitive value. + * + * @param The type of layout type. + * @param The sub-element type of the field. + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @param type The layout type. + * @param sparse The {@link RowBuffer} access method for {@code type}. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + private , TValue> + Result writePrimitiveList(UtfAnyString path, List value, TLayoutType type, Consumer> sparse) { + + Result result = Result.NOT_FOUND; + + if (this.cursor.scopeType() instanceof LayoutUDT) { + result = this.writeSchematizedValue(path, value); + } + + if (result == Result.NOT_FOUND) { + + result = this.prepareSparseWrite(path, type.typeArg()); + + if (result != Result.SUCCESS) { + return result; + } + + sparse.accept(value); + RowCursors.moveNext(this.cursor, this.row); + } + + return result; + } + + /** + * Helper for writing a primitive value. + * + * @param The type of the primitive value. + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @param type The layout type. + * @param sparse The {@link RowBuffer} access method for {@code type}. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + private Result writePrimitive( + UtfAnyString path, TValue value, LayoutTypePrimitive type, Consumer sparse) { + + Result result = Result.NOT_FOUND; + + if (this.cursor.scopeType() instanceof LayoutUDT) { + result = this.writeSchematizedValue(path, value); + } + + if (result == Result.NOT_FOUND) { + + result = this.prepareSparseWrite(path, type.typeArg()); + + if (result != Result.SUCCESS) { + return result; + } + + sparse.accept(value); + RowCursors.moveNext(this.cursor, this.row); + } + + return result; + } + + /** + * Write a generic schematized field value via the scope's layout. + * + * @param The expected type of the field. + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + private Result writeSchematizedValue(UtfAnyString path, TValue value) { + + final Optional column = this.cursor.layout().tryFind(path); + + if (!column.isPresent()) { + return Result.NOT_FOUND; + } + + // TODO: DANOBLE: Add a mechanism for performing the equivalent of this type check + // if (!(column.Type is LayoutTypePrimitive t)) { + // return Result.NotFound; + // } + // Type erasure prevents this test: + // column.type instanceof LayoutTypePrimitive + // Reason: Runtime does not instantiate or otherwise represent or identify instances of a generic type. + + if (!(column.get().type() instanceof LayoutTypePrimitive)) { + return Result.NOT_FOUND; + } + + @SuppressWarnings("unchecked") + LayoutTypePrimitive type = (LayoutTypePrimitive)column.get().type(); + + switch (column.get().storage()) { + case FIXED: + return type.writeFixed(this.row, this.cursor, column.get(), value); + case VARIABLE: + return type.writeVariable(this.row, this.cursor, column.get(), value); + } + + return Result.NOT_FOUND; + } + + /** + * Write a generic schematized field value via the scope's layout. + * + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + private Result writeSchematizedValue(UtfAnyString path, Utf8String value) { + + final Optional column = this.cursor.layout().tryFind(path); + + if (!column.isPresent()) { + return Result.NOT_FOUND; + } + + final LayoutType type = column.get().type(); + + if (!(type instanceof LayoutUtf8Writable)) { + return Result.NOT_FOUND; + } + + switch (column.get().storage()) { + case FIXED: + return type.typeAs().writeFixed(this.row, this.cursor, column.get(), value); + case VARIABLE: + return type.typeAs().writeVariable(this.row, this.cursor, column.get(), value); + } + + return Result.NOT_FOUND; + } + + /** + * Write a generic schematized field value via the scope's layout. + * + * @param The sub-element type of the field. + * @param path The scope-relative path of the field to write. + * @param value The value to write. + * @return {@link Result#SUCCESS} if the write is successful, an error {@link Result} otherwise. + */ + private Result writeSchematizedValue(UtfAnyString path, List value) { + + final Optional column = this.cursor.layout().tryFind(path); + + if (!column.isPresent()) { + return Result.NOT_FOUND; + } + + final LayoutType type = column.get().type(); + + if (!(type instanceof LayoutListWritable)) { + return Result.NOT_FOUND; + } + + switch (column.get().storage()) { + case FIXED: + return type.>typeAs().writeFixedList(this.row, this.cursor, column.get(), value); + case VARIABLE: + return type.>typeAs().writeVariableList(this.row, this.cursor, column.get(), value); + } + + return Result.NOT_FOUND; + } + + /** + * Functional interface for writing content to a {@link RowBuffer}. + */ + @FunctionalInterface + public interface WriterFunc { + /** + * Write content using the specified writer, type argument, and context. + * + * @param writer writes content. + * @param typeArg specifies a type argument. + * @param context provides context for the write operation. + * @return a result code + */ + Result invoke(RowWriter writer, TypeArgument typeArg, TContext context); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/Segment.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/Segment.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/Segment.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/Segment.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonExtensions.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonExtensions.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonExtensions.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonExtensions.java index f153e2d..30e6739 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonExtensions.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonExtensions.java @@ -1,441 +1,441 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.json; - -import com.azure.data.cosmos.core.Json; -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.UnixDateTime; -import com.azure.data.cosmos.serialization.hybridrow.io.RowReader; -import org.checkerframework.checker.nullness.qual.NonNull; - -import javax.annotation.Nonnull; -import java.util.Objects; - -import static com.google.common.base.Strings.lenientFormat; - -public final class RowReaderJsonExtensions { - /** - * Project a JSON document from a HybridRow {@link RowReader}. - * - * @param reader The reader to project to JSON. - * @param string If {@link Result#SUCCESS}, the JSON document that corresponds to the {@code reader}. - * @return The result. - */ - @Nonnull - public static Result toJson(@Nonnull final RowReader reader, @Nonnull final Out string) { - return RowReaderJsonExtensions.toJson(reader, new RowReaderJsonSettings(" "), string); - } - - /** - * Project a JSON document from a HybridRow {@link RowReader}. - * - * @param reader The reader to project to JSON. - * @param settings Settings that control how the JSON document is formatted. - * @param string If {@link Result#SUCCESS}, the JSON document that corresponds to the {@code reader}. - * @return The result. - */ - @Nonnull - public static Result toJson( - @Nonnull final RowReader reader, - @Nonnull final RowReaderJsonSettings settings, - @Nonnull final Out string) { - - final ReaderStringContext context = new ReaderStringContext( - new StringBuilder(), - new RowReaderJsonSettings( - settings.indentChars(), - settings.quoteChar() == '\'' ? '\'' : '"'), - 1); - - context.builder().append("{"); - Result result = RowReaderJsonExtensions.toJson(reader, context); - - if (result != Result.SUCCESS) { - string.set(null); - return result; - } - - context.builder().append(context.newline()); - context.builder().append("}"); - - string.set(context.builder().toString()); - return Result.SUCCESS; - } - - @Nonnull - private static Result toJson(@Nonnull final RowReader reader, @Nonnull final ReaderStringContext context) { - - int index = 0; - - while (reader.read()) { - String path = !reader.path().isNull() - ? lenientFormat("%s%s%s:", context.settings().quoteChar(), reader.path(), context.settings().quoteChar()) - : null; - if (index != 0) { - context.builder().append(','); - } - - index++; - context.builder().append(context.newline()); - context.writeIndent(); - - if (path != null) { - context.builder().append(path); - context.builder().append(context.separator()); - } - - final Out out = new Out<>(); - Result result; - char scopeBracket = '\0'; - char scopeCloseBracket = '\0'; - - switch (Objects.requireNonNull(reader.type()).layoutCode()) { - - case NULL: { - result = reader.readNull(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append("null"); - break; - } - - case BOOLEAN: { - result = reader.readBoolean(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case INT_8: { - result = reader.readInt8(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case INT_16: { - result = reader.readInt16(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case INT_32: { - result = reader.readInt32(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case INT_64: { - result = reader.readInt64(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case UINT_8: { - result = reader.readUInt8(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case UINT_16: { - result = reader.readUInt16(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case UINT_32: { - result = reader.readUInt32(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case UINT_64: { - result = reader.readUInt64(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case VAR_INT: { - result = reader.readVarInt(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case VAR_UINT: { - result = reader.readVarUInt(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case FLOAT_32: { - result = reader.readFloat32(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case FLOAT_64: { - result = reader.readFloat64(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case FLOAT_128: { - result = reader.readFloat128(out); - if (result != Result.SUCCESS) { - return result; - } - // context.Builder.AppendFormat("High: {0}, Low: {1}\n", value.High, value.Low); - throw new UnsupportedOperationException("Float128 values are not supported."); - } - - case DECIMAL: { - result = reader.readDecimal(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(out.get()); - break; - } - - case DATE_TIME: { - result = reader.readDateTime(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(context.settings().quoteChar()); - context.builder().append(out.get()); - context.builder().append(context.settings().quoteChar()); - break; - } - - case UNIX_DATE_TIME: { - result = reader.readUnixDateTime(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(((UnixDateTime)out.get()).milliseconds()); - break; - } - - case GUID: { - result = reader.readGuid(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(context.settings().quoteChar()); - context.builder().append(out.get()); - context.builder().append(context.settings().quoteChar()); - break; - } - - case MONGODB_OBJECT_ID: { - // TODO: DANOBLE: Resurrect this code block - // MongoDbObjectId value; - // Out tempOut_value18 = - // new Out(); - // result = reader.ReadMongoDbObjectId(tempOut_value18); - // value = tempOut_value18.get(); - // if (result != Result.SUCCESS) { - // return result; - // } - // - // context.builder().append(context.settings().quoteChar()); - // //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: - // //ORIGINAL LINE: ReadOnlyMemory bytes = value.ToByteArray(); - // ReadOnlyMemory bytes = value.ToByteArray(); - // context.builder().append(bytes.Span.ToHexString()); - // context.builder().append(context.settings().quoteChar()); - break; - } - - case UTF_8: { - result = reader.readString(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(context.settings().quoteChar()); - context.builder().append(Json.toString(out)); - context.builder().append(context.settings().quoteChar()); - break; - } - - case BINARY: { - result = reader.readBinary(out); - if (result != Result.SUCCESS) { - return result; - } - context.builder().append(context.settings().quoteChar()); - context.builder().append(out.get()); - context.builder().append(context.settings().quoteChar()); - break; - } - - case NULLABLE_SCOPE: - case IMMUTABLE_NULLABLE_SCOPE: - if (!reader.hasValue()) { - context.builder().append("null"); - break; - } - - case ARRAY_SCOPE: - case IMMUTABLE_ARRAY_SCOPE: - case TYPED_ARRAY_SCOPE: - case IMMUTABLE_TYPED_ARRAY_SCOPE: - case TYPED_SET_SCOPE: - case IMMUTABLE_TYPED_SET_SCOPE: - case TYPED_MAP_SCOPE: - case IMMUTABLE_TYPED_MAP_SCOPE: - case TUPLE_SCOPE: - case IMMUTABLE_TUPLE_SCOPE: - case TYPED_TUPLE_SCOPE: - case IMMUTABLE_TYPED_TUPLE_SCOPE: - case TAGGED_SCOPE: - case IMMUTABLE_TAGGED_SCOPE: - case TAGGED2_SCOPE: - case IMMUTABLE_TAGGED2_SCOPE: - result = endScope(reader, context, scopeBracket = '[', scopeCloseBracket = ']'); - if (result != Result.SUCCESS) { - return result; - } - break; - case OBJECT_SCOPE: - case IMMUTABLE_OBJECT_SCOPE: - case SCHEMA: - case IMMUTABLE_SCHEMA: - result = endScope(reader, context, scopeBracket = '{', scopeCloseBracket = '}'); - if (result != Result.SUCCESS) { - return result; - } - break; - - case END_SCOPE: { - result = endScope(reader, context, scopeBracket, scopeCloseBracket); - if (result != Result.SUCCESS) { - return result; - } - break; - } - - default: { - throw new IllegalStateException(lenientFormat("Unknown type will be ignored: %s", - Objects.requireNonNull(reader.type()).layoutCode()) - ); - } - } - } - - return Result.SUCCESS; - } - - @NonNull - private static Result endScope( - @Nonnull RowReader reader, @Nonnull ReaderStringContext context, char scopeBracket, char scopeCloseBracket) { - - Result result; - context.builder().append(scopeBracket); - int snapshot = context.builder().length(); - - result = reader.readScope( - new ReaderStringContext( - context.builder(), - context.settings(), - context.indent() + 1), - RowReaderJsonExtensions::toJson); - - if (result != Result.SUCCESS) { - return result; - } - - if (context.builder().length() != snapshot) { - context.builder().append(context.newline()); - context.writeIndent(); - } - - context.builder().append(scopeCloseBracket); - return result; - } - - private final static class ReaderStringContext { - - private StringBuilder builder; - private int indent; - private String newline; - private String separator; - private RowReaderJsonSettings settings = new RowReaderJsonSettings(); - - 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 StringBuilder builder() { - return this.builder; - } - - public int indent() { - return this.indent; - } - - public String newline() { - return this.newline; - } - - public String separator() { - return this.separator; - } - - public RowReaderJsonSettings settings() { - return this.settings; - } - - public void writeIndent() { - String indentChars = this.settings().indentChars() != null ? this.settings().indentChars() : ""; - for (int i = 0; i < this.indent(); i++) { - this.builder().append(indentChars); - } - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.json; + +import com.azure.data.cosmos.core.Json; +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.UnixDateTime; +import com.azure.data.cosmos.serialization.hybridrow.io.RowReader; +import org.checkerframework.checker.nullness.qual.NonNull; + +import javax.annotation.Nonnull; +import java.util.Objects; + +import static com.google.common.base.Strings.lenientFormat; + +public final class RowReaderJsonExtensions { + /** + * Project a JSON document from a HybridRow {@link RowReader}. + * + * @param reader The reader to project to JSON. + * @param string If {@link Result#SUCCESS}, the JSON document that corresponds to the {@code reader}. + * @return The result. + */ + @Nonnull + public static Result toJson(@Nonnull final RowReader reader, @Nonnull final Out string) { + return RowReaderJsonExtensions.toJson(reader, new RowReaderJsonSettings(" "), string); + } + + /** + * Project a JSON document from a HybridRow {@link RowReader}. + * + * @param reader The reader to project to JSON. + * @param settings Settings that control how the JSON document is formatted. + * @param string If {@link Result#SUCCESS}, the JSON document that corresponds to the {@code reader}. + * @return The result. + */ + @Nonnull + public static Result toJson( + @Nonnull final RowReader reader, + @Nonnull final RowReaderJsonSettings settings, + @Nonnull final Out string) { + + final ReaderStringContext context = new ReaderStringContext( + new StringBuilder(), + new RowReaderJsonSettings( + settings.indentChars(), + settings.quoteChar() == '\'' ? '\'' : '"'), + 1); + + context.builder().append("{"); + Result result = RowReaderJsonExtensions.toJson(reader, context); + + if (result != Result.SUCCESS) { + string.set(null); + return result; + } + + context.builder().append(context.newline()); + context.builder().append("}"); + + string.set(context.builder().toString()); + return Result.SUCCESS; + } + + @Nonnull + private static Result toJson(@Nonnull final RowReader reader, @Nonnull final ReaderStringContext context) { + + int index = 0; + + while (reader.read()) { + String path = !reader.path().isNull() + ? lenientFormat("%s%s%s:", context.settings().quoteChar(), reader.path(), context.settings().quoteChar()) + : null; + if (index != 0) { + context.builder().append(','); + } + + index++; + context.builder().append(context.newline()); + context.writeIndent(); + + if (path != null) { + context.builder().append(path); + context.builder().append(context.separator()); + } + + final Out out = new Out<>(); + Result result; + char scopeBracket = '\0'; + char scopeCloseBracket = '\0'; + + switch (Objects.requireNonNull(reader.type()).layoutCode()) { + + case NULL: { + result = reader.readNull(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append("null"); + break; + } + + case BOOLEAN: { + result = reader.readBoolean(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case INT_8: { + result = reader.readInt8(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case INT_16: { + result = reader.readInt16(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case INT_32: { + result = reader.readInt32(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case INT_64: { + result = reader.readInt64(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case UINT_8: { + result = reader.readUInt8(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case UINT_16: { + result = reader.readUInt16(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case UINT_32: { + result = reader.readUInt32(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case UINT_64: { + result = reader.readUInt64(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case VAR_INT: { + result = reader.readVarInt(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case VAR_UINT: { + result = reader.readVarUInt(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case FLOAT_32: { + result = reader.readFloat32(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case FLOAT_64: { + result = reader.readFloat64(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case FLOAT_128: { + result = reader.readFloat128(out); + if (result != Result.SUCCESS) { + return result; + } + // context.Builder.AppendFormat("High: {0}, Low: {1}\n", value.High, value.Low); + throw new UnsupportedOperationException("Float128 values are not supported."); + } + + case DECIMAL: { + result = reader.readDecimal(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(out.get()); + break; + } + + case DATE_TIME: { + result = reader.readDateTime(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(context.settings().quoteChar()); + context.builder().append(out.get()); + context.builder().append(context.settings().quoteChar()); + break; + } + + case UNIX_DATE_TIME: { + result = reader.readUnixDateTime(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(((UnixDateTime)out.get()).milliseconds()); + break; + } + + case GUID: { + result = reader.readGuid(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(context.settings().quoteChar()); + context.builder().append(out.get()); + context.builder().append(context.settings().quoteChar()); + break; + } + + case MONGODB_OBJECT_ID: { + // TODO: DANOBLE: Resurrect this code block + // MongoDbObjectId value; + // Out tempOut_value18 = + // new Out(); + // result = reader.ReadMongoDbObjectId(tempOut_value18); + // value = tempOut_value18.get(); + // if (result != Result.SUCCESS) { + // return result; + // } + // + // context.builder().append(context.settings().quoteChar()); + // //C# TO JAVA CONVERTER WARNING: Unsigned integer types have no direct equivalent in Java: + // //ORIGINAL LINE: ReadOnlyMemory bytes = value.ToByteArray(); + // ReadOnlyMemory bytes = value.ToByteArray(); + // context.builder().append(bytes.Span.ToHexString()); + // context.builder().append(context.settings().quoteChar()); + break; + } + + case UTF_8: { + result = reader.readString(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(context.settings().quoteChar()); + context.builder().append(Json.toString(out)); + context.builder().append(context.settings().quoteChar()); + break; + } + + case BINARY: { + result = reader.readBinary(out); + if (result != Result.SUCCESS) { + return result; + } + context.builder().append(context.settings().quoteChar()); + context.builder().append(out.get()); + context.builder().append(context.settings().quoteChar()); + break; + } + + case NULLABLE_SCOPE: + case IMMUTABLE_NULLABLE_SCOPE: + if (!reader.hasValue()) { + context.builder().append("null"); + break; + } + + case ARRAY_SCOPE: + case IMMUTABLE_ARRAY_SCOPE: + case TYPED_ARRAY_SCOPE: + case IMMUTABLE_TYPED_ARRAY_SCOPE: + case TYPED_SET_SCOPE: + case IMMUTABLE_TYPED_SET_SCOPE: + case TYPED_MAP_SCOPE: + case IMMUTABLE_TYPED_MAP_SCOPE: + case TUPLE_SCOPE: + case IMMUTABLE_TUPLE_SCOPE: + case TYPED_TUPLE_SCOPE: + case IMMUTABLE_TYPED_TUPLE_SCOPE: + case TAGGED_SCOPE: + case IMMUTABLE_TAGGED_SCOPE: + case TAGGED2_SCOPE: + case IMMUTABLE_TAGGED2_SCOPE: + result = endScope(reader, context, scopeBracket = '[', scopeCloseBracket = ']'); + if (result != Result.SUCCESS) { + return result; + } + break; + case OBJECT_SCOPE: + case IMMUTABLE_OBJECT_SCOPE: + case SCHEMA: + case IMMUTABLE_SCHEMA: + result = endScope(reader, context, scopeBracket = '{', scopeCloseBracket = '}'); + if (result != Result.SUCCESS) { + return result; + } + break; + + case END_SCOPE: { + result = endScope(reader, context, scopeBracket, scopeCloseBracket); + if (result != Result.SUCCESS) { + return result; + } + break; + } + + default: { + throw new IllegalStateException(lenientFormat("Unknown type will be ignored: %s", + Objects.requireNonNull(reader.type()).layoutCode()) + ); + } + } + } + + return Result.SUCCESS; + } + + @NonNull + private static Result endScope( + @Nonnull RowReader reader, @Nonnull ReaderStringContext context, char scopeBracket, char scopeCloseBracket) { + + Result result; + context.builder().append(scopeBracket); + int snapshot = context.builder().length(); + + result = reader.readScope( + new ReaderStringContext( + context.builder(), + context.settings(), + context.indent() + 1), + RowReaderJsonExtensions::toJson); + + if (result != Result.SUCCESS) { + return result; + } + + if (context.builder().length() != snapshot) { + context.builder().append(context.newline()); + context.writeIndent(); + } + + context.builder().append(scopeCloseBracket); + return result; + } + + private final static class ReaderStringContext { + + private StringBuilder builder; + private int indent; + private String newline; + private String separator; + private RowReaderJsonSettings settings = new RowReaderJsonSettings(); + + 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 StringBuilder builder() { + return this.builder; + } + + public int indent() { + return this.indent; + } + + public String newline() { + return this.newline; + } + + public String separator() { + return this.separator; + } + + public RowReaderJsonSettings settings() { + return this.settings; + } + + public void writeIndent() { + String indentChars = this.settings().indentChars() != null ? this.settings().indentChars() : ""; + for (int i = 0; i < this.indent(); i++) { + this.builder().append(indentChars); + } + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonSettings.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonSettings.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonSettings.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonSettings.java index fb70769..1472145 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonSettings.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/json/RowReaderJsonSettings.java @@ -1,43 +1,43 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.json; - -public final class RowReaderJsonSettings { - - private String indentChars; - private char quoteChar; - - public RowReaderJsonSettings(String indentChars) { - this(indentChars, '"'); - } - - public RowReaderJsonSettings() { - this(" ", '"'); - } - - public RowReaderJsonSettings(String indentChars, char quoteChar) { - this.indentChars = indentChars; - this.quoteChar = quoteChar; - } - - /** - * If non-null then child objects are indented by one copy of this string per level. - * - * @return indentation characters. - */ - public String indentChars() { - return this.indentChars; - } - - /** - * The current quote character. - *

- * May be double or single quote. - * - * @return quote character. - */ - public char quoteChar() { - return this.quoteChar; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.json; + +public final class RowReaderJsonSettings { + + private String indentChars; + private char quoteChar; + + public RowReaderJsonSettings(String indentChars) { + this(indentChars, '"'); + } + + public RowReaderJsonSettings() { + this(" ", '"'); + } + + public RowReaderJsonSettings(String indentChars, char quoteChar) { + this.indentChars = indentChars; + this.quoteChar = quoteChar; + } + + /** + * If non-null then child objects are indented by one copy of this string per level. + * + * @return indentation characters. + */ + public String indentChars() { + return this.indentChars; + } + + /** + * The current quote character. + *

+ * May be double or single quote. + * + * @return quote character. + */ + public char quoteChar() { + return this.quoteChar; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/ILayoutType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/ILayoutType.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/ILayoutType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/ILayoutType.java index 1c48809..3b49369 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/ILayoutType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/ILayoutType.java @@ -1,10 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -/** - * Marker interface for layout types. - */ -public interface ILayoutType { +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +/** + * Marker interface for layout types. + */ +public interface ILayoutType { } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/Layout.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/Layout.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/Layout.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/Layout.java index cdbf88c..1db30b2 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/Layout.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/Layout.java @@ -1,242 +1,242 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Utf8String; -import com.azure.data.cosmos.core.UtfAnyString; -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.azure.data.cosmos.serialization.hybridrow.schemas.Namespace; -import com.azure.data.cosmos.serialization.hybridrow.schemas.Schema; -import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; -import com.google.common.collect.ImmutableList; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A Layout describes the structure of a Hybrid Row. - *

- * A layout indicates the number, order, and type of all schematized columns to be stored within a hybrid row. The - * order and type of columns defines the physical ordering of bytes used to encode the row and impacts the cost of - * updating the row. - *

- * A layout is created by compiling a {@link Schema} through {@link Schema#compile(Namespace)} or by constructor through - * a {@link LayoutBuilder}. - * - * {@link Layout} is immutable. - */ -public final class Layout { - - public static final Layout EMPTY = SystemSchema.layoutResolver().resolve(SystemSchema.EMPTY_SCHEMA_ID); - - private final String name; - private final int numBitmaskBytes; - private final int numFixed; - private final int numVariable; - private final HashMap pathMap; - private final HashMap pathStringMap; - private final SchemaId schemaId; - private final int size; - private final StringTokenizer tokenizer; - private final ImmutableList topColumns; - - @SuppressWarnings("UnstableApiUsage") - public Layout( - @Nonnull final String name, - @Nonnull final SchemaId schemaId, - final int numBitmaskBytes, - final int minRequiredSize, - @Nonnull final ArrayList columns) { - - checkNotNull(name, "expected non-null name"); - checkNotNull(schemaId, "expected non-null schemaId"); - checkNotNull(columns, "expected non-null columns"); - - checkArgument(numBitmaskBytes >= 0, "expected non-negative numBitmaskBytes, not %s", numBitmaskBytes); - checkArgument(minRequiredSize >= 0, "expected non-negative minRequiredSize", minRequiredSize); - - this.name = name; - this.schemaId = schemaId; - this.numBitmaskBytes = numBitmaskBytes; - this.size = minRequiredSize; - this.tokenizer = new StringTokenizer(); - this.pathMap = new HashMap<>(columns.size()); - this.pathStringMap = new HashMap<>(columns.size()); - - ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(columns.size()); - int numFixed = 0; - int numVariable = 0; - - for (LayoutColumn column : columns) { - - this.tokenizer().add(column.path()); - this.pathMap.put(column.fullPath(), column); - this.pathStringMap.put(column.fullPath().toString(), column); - - if (column.storage() == StorageKind.FIXED) { - numFixed++; - } else if (column.storage() == StorageKind.VARIABLE) { - numVariable++; - } - - if (column.parent() == null) { - builder.add(column); - } - } - - this.numFixed = numFixed; - this.numVariable = numVariable; - this.topColumns = builder.build(); - } - - /** - * Finds a column specification for a column with a matching path. - * - * @param path path of the column to find - * @return {@link LayoutColumn}, if a column with the {@code path} is found, {@link Optional#empty()} - */ - public Optional tryFind(@Nonnull UtfAnyString path) { - - checkNotNull(path); - - if (path.isNull()) { - return Optional.empty(); - } - - if (path.isUtf8()) { - return Optional.ofNullable(this.pathMap.get(path.toUtf8())); - } - - return Optional.ofNullable(this.pathStringMap.get(path.toUtf16())); - } - - /** - * Finds a column specification for a column with a matching path. - * - * @param path The path of the column to find. - * @return True if a column with the path is found, otherwise false. - */ - public Optional tryFind(@Nonnull String path) { - checkNotNull(path); - return Optional.ofNullable(this.pathStringMap.get(path)); - } - - /** - * Top level columns defined by the current {@link Layout} in left-to-right order. - * - * @return Top level columns defined by the current {@link Layout} in left-to-right order - */ - public List columns() { - return this.topColumns; - } - - /** - * Name of the layout. - *

- * Usually this is the name of the {@link Schema} from which this {@link Layout} was generated. - * - * @return name of the layout. - */ - public String name() { - return this.name; - } - - /** - * The number of bit mask bytes allocated within the layout. - *

- * A presence bit is allocated for each fixed and variable-length field. Sparse columns never have presence bits. - * Fixed boolean allocate an additional bit from the bitmask to store their value. - * - * @return the number of bit mask bytes allocated with the layout. - */ - public int numBitmaskBytes() { - return this.numBitmaskBytes; - } - - /** - * The number of fixed columns. - * - * @return the number of fixed columns. - */ - public int numFixed() { - return this.numFixed; - } - - /** - * The number of variable-length columns. - * - * @return the number of variable-length columns. - */ - public int numVariable() { - return this.numVariable; - } - - /** - * Unique identifier of the schema from which this {@link Layout} was generated. - * - * @return the unique identifier of the schema from which this {@link Layout} was generated. - */ - public SchemaId schemaId() { - return this.schemaId; - } - - /** - * The minimum required size of a row with this layout. - *

- * This size excludes all sparse columns, and assumes all columns (including variable) are - * null. - * - * @return the minimum required size of a row with this layout. - */ - public int size() { - return this.size; - } - - /** - * A human readable diagnostic string representing this {@link Layout}. - *

- * This representation should only be used for debugging and diagnostic purposes. - * - * @return a human readable diagnostic string representing this {@link Layout}. - */ - @Override - @Nonnull - public String toString() { - - StringBuilder sb = new StringBuilder(); - - sb.append("Layout:\n"); - sb.append(String.format("\tCount: %1$s\n", this.topColumns.size())); - sb.append(String.format("\tFixedSize: %1$s\n", this.size())); - - for (LayoutColumn column : this.topColumns) { - if (column.type().isFixed()) { - if (column.type().isBoolean()) { - sb.append(String.format("\t%1$s: %2$s @ %3$s:%4$s:%5$s\n", column.fullPath(), column.type().name(), column.offset(), column.nullBit(), column.booleanBit())); - } else { - sb.append(String.format("\t%1$s: %2$s @ %3$s\n", column.fullPath(), column.type().name(), column.offset())); - } - } else { - sb.append(String.format("\t%1$s: %2$s[%4$s] @ %3$s\n", column.fullPath(), column.type().name(), column.offset(), column.size())); - } - } - - return sb.toString(); - } - - /** - * A {@linkplain StringTokenizer tokenizer} for path strings. - * - * @return a {@linkplain StringTokenizer tokenizer} for path strings. - */ - public StringTokenizer tokenizer() { - return this.tokenizer; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Utf8String; +import com.azure.data.cosmos.core.UtfAnyString; +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.azure.data.cosmos.serialization.hybridrow.schemas.Namespace; +import com.azure.data.cosmos.serialization.hybridrow.schemas.Schema; +import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; +import com.google.common.collect.ImmutableList; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A Layout describes the structure of a Hybrid Row. + *

+ * A layout indicates the number, order, and type of all schematized columns to be stored within a hybrid row. The + * order and type of columns defines the physical ordering of bytes used to encode the row and impacts the cost of + * updating the row. + *

+ * A layout is created by compiling a {@link Schema} through {@link Schema#compile(Namespace)} or by constructor through + * a {@link LayoutBuilder}. + * + * {@link Layout} is immutable. + */ +public final class Layout { + + public static final Layout EMPTY = SystemSchema.layoutResolver().resolve(SystemSchema.EMPTY_SCHEMA_ID); + + private final String name; + private final int numBitmaskBytes; + private final int numFixed; + private final int numVariable; + private final HashMap pathMap; + private final HashMap pathStringMap; + private final SchemaId schemaId; + private final int size; + private final StringTokenizer tokenizer; + private final ImmutableList topColumns; + + @SuppressWarnings("UnstableApiUsage") + public Layout( + @Nonnull final String name, + @Nonnull final SchemaId schemaId, + final int numBitmaskBytes, + final int minRequiredSize, + @Nonnull final ArrayList columns) { + + checkNotNull(name, "expected non-null name"); + checkNotNull(schemaId, "expected non-null schemaId"); + checkNotNull(columns, "expected non-null columns"); + + checkArgument(numBitmaskBytes >= 0, "expected non-negative numBitmaskBytes, not %s", numBitmaskBytes); + checkArgument(minRequiredSize >= 0, "expected non-negative minRequiredSize", minRequiredSize); + + this.name = name; + this.schemaId = schemaId; + this.numBitmaskBytes = numBitmaskBytes; + this.size = minRequiredSize; + this.tokenizer = new StringTokenizer(); + this.pathMap = new HashMap<>(columns.size()); + this.pathStringMap = new HashMap<>(columns.size()); + + ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(columns.size()); + int numFixed = 0; + int numVariable = 0; + + for (LayoutColumn column : columns) { + + this.tokenizer().add(column.path()); + this.pathMap.put(column.fullPath(), column); + this.pathStringMap.put(column.fullPath().toString(), column); + + if (column.storage() == StorageKind.FIXED) { + numFixed++; + } else if (column.storage() == StorageKind.VARIABLE) { + numVariable++; + } + + if (column.parent() == null) { + builder.add(column); + } + } + + this.numFixed = numFixed; + this.numVariable = numVariable; + this.topColumns = builder.build(); + } + + /** + * Finds a column specification for a column with a matching path. + * + * @param path path of the column to find + * @return {@link LayoutColumn}, if a column with the {@code path} is found, {@link Optional#empty()} + */ + public Optional tryFind(@Nonnull UtfAnyString path) { + + checkNotNull(path); + + if (path.isNull()) { + return Optional.empty(); + } + + if (path.isUtf8()) { + return Optional.ofNullable(this.pathMap.get(path.toUtf8())); + } + + return Optional.ofNullable(this.pathStringMap.get(path.toUtf16())); + } + + /** + * Finds a column specification for a column with a matching path. + * + * @param path The path of the column to find. + * @return True if a column with the path is found, otherwise false. + */ + public Optional tryFind(@Nonnull String path) { + checkNotNull(path); + return Optional.ofNullable(this.pathStringMap.get(path)); + } + + /** + * Top level columns defined by the current {@link Layout} in left-to-right order. + * + * @return Top level columns defined by the current {@link Layout} in left-to-right order + */ + public List columns() { + return this.topColumns; + } + + /** + * Name of the layout. + *

+ * Usually this is the name of the {@link Schema} from which this {@link Layout} was generated. + * + * @return name of the layout. + */ + public String name() { + return this.name; + } + + /** + * The number of bit mask bytes allocated within the layout. + *

+ * A presence bit is allocated for each fixed and variable-length field. Sparse columns never have presence bits. + * Fixed boolean allocate an additional bit from the bitmask to store their value. + * + * @return the number of bit mask bytes allocated with the layout. + */ + public int numBitmaskBytes() { + return this.numBitmaskBytes; + } + + /** + * The number of fixed columns. + * + * @return the number of fixed columns. + */ + public int numFixed() { + return this.numFixed; + } + + /** + * The number of variable-length columns. + * + * @return the number of variable-length columns. + */ + public int numVariable() { + return this.numVariable; + } + + /** + * Unique identifier of the schema from which this {@link Layout} was generated. + * + * @return the unique identifier of the schema from which this {@link Layout} was generated. + */ + public SchemaId schemaId() { + return this.schemaId; + } + + /** + * The minimum required size of a row with this layout. + *

+ * This size excludes all sparse columns, and assumes all columns (including variable) are + * null. + * + * @return the minimum required size of a row with this layout. + */ + public int size() { + return this.size; + } + + /** + * A human readable diagnostic string representing this {@link Layout}. + *

+ * This representation should only be used for debugging and diagnostic purposes. + * + * @return a human readable diagnostic string representing this {@link Layout}. + */ + @Override + @Nonnull + public String toString() { + + StringBuilder sb = new StringBuilder(); + + sb.append("Layout:\n"); + sb.append(String.format("\tCount: %1$s\n", this.topColumns.size())); + sb.append(String.format("\tFixedSize: %1$s\n", this.size())); + + for (LayoutColumn column : this.topColumns) { + if (column.type().isFixed()) { + if (column.type().isBoolean()) { + sb.append(String.format("\t%1$s: %2$s @ %3$s:%4$s:%5$s\n", column.fullPath(), column.type().name(), column.offset(), column.nullBit(), column.booleanBit())); + } else { + sb.append(String.format("\t%1$s: %2$s @ %3$s\n", column.fullPath(), column.type().name(), column.offset())); + } + } else { + sb.append(String.format("\t%1$s: %2$s[%4$s] @ %3$s\n", column.fullPath(), column.type().name(), column.offset(), column.size())); + } + } + + return sb.toString(); + } + + /** + * A {@linkplain StringTokenizer tokenizer} for path strings. + * + * @return a {@linkplain StringTokenizer tokenizer} for path strings. + */ + public StringTokenizer tokenizer() { + return this.tokenizer; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutArray.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutArray.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutArray.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutArray.java index 7aa2166..d2e1510 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutArray.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutArray.java @@ -1,52 +1,52 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.ARRAY_SCOPE; -import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.IMMUTABLE_ARRAY_SCOPE; - -public final class LayoutArray extends LayoutIndexedScope { - - public LayoutArray(final boolean immutable) { - super(immutable ? IMMUTABLE_ARRAY_SCOPE : ARRAY_SCOPE, immutable, false, false, false, false); - } - - @Nonnull - public String name() { - return this.isImmutable() ? "im_array" : "array"; - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull TypeArgumentList typeArgs, @Nonnull UpdateOptions options, @Nonnull Out value) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - buffer.writeSparseArray(edit, this, options); - return Result.SUCCESS; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.ARRAY_SCOPE; +import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.IMMUTABLE_ARRAY_SCOPE; + +public final class LayoutArray extends LayoutIndexedScope { + + public LayoutArray(final boolean immutable) { + super(immutable ? IMMUTABLE_ARRAY_SCOPE : ARRAY_SCOPE, immutable, false, false, false, false); + } + + @Nonnull + public String name() { + return this.isImmutable() ? "im_array" : "array"; + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull TypeArgumentList typeArgs, @Nonnull UpdateOptions options, @Nonnull Out value) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + buffer.writeSparseArray(edit, this, options); + return Result.SUCCESS; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBinary.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBinary.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBinary.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBinary.java index 4ea5aea..853f0bb 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBinary.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBinary.java @@ -1,197 +1,197 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import io.netty.buffer.ByteBuf; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutBinary extends LayoutTypePrimitive { - // implements - // LayoutListWritable, - // LayoutListReadable, - // ILayoutSequenceWritable { - - public LayoutBinary() { - super(LayoutCode.BINARY, 0); - } - - public boolean isFixed() { - return false; - } - - @Nonnull - public String name() { - return "binary"; - } - - @Override - @Nonnull - public Result readFixed( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT); - checkArgument(column.size() >= 0); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(null); - return Result.NOT_FOUND; - } - - value.set(buffer.readFixedBinary(scope.start() + column.offset(), column.size())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse( - @Nonnull final RowBuffer buffer, @Nonnull final RowCursor edit, @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - - final Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.readSparseBinary(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readVariable( - @Nonnull RowBuffer buffer, - @Nonnull RowCursor scope, - @Nonnull LayoutColumn column, - @Nonnull Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(null); - return Result.NOT_FOUND; - } - - final int valueOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); - value.set(buffer.readVariableBinary(valueOffset)); - - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed( - @Nonnull RowBuffer buffer, - @Nonnull RowCursor scope, - @Nonnull LayoutColumn column, - @Nonnull ByteBuf value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT); - checkArgument(column.size() >= 0); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - final int valueOffset = scope.start() + column.offset(); - buffer.setBit(scope.start(), column.nullBit()); - - buffer.writeFixedBinary(valueOffset, value, column.size()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull ByteBuf value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } - - @Override - @Nonnull - public Result writeSparse( - @Nonnull RowBuffer buffer, - @Nonnull RowCursor edit, - @Nonnull ByteBuf value, - @Nonnull UpdateOptions options) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - checkNotNull(options, "expected non-null options"); - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseBinary(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeVariable( - @Nonnull RowBuffer buffer, - @Nonnull RowCursor scope, - @Nonnull LayoutColumn column, - @Nonnull ByteBuf value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - if ((column.size() > 0) && (value.readableBytes() > column.size())) { - return Result.TOO_BIG; - } - - final boolean exists = buffer.readBit(scope.start(), column.nullBit()); - final int valueOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); - final Out shift = new Out<>(); - - buffer.writeVariableBinary(valueOffset, value, exists, shift); - buffer.setBit(scope.start(), column.nullBit()); - scope.metaOffset(scope.metaOffset() + shift.get()); - scope.valueOffset(scope.valueOffset() + shift.get()); - - return Result.SUCCESS; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import io.netty.buffer.ByteBuf; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutBinary extends LayoutTypePrimitive { + // implements + // LayoutListWritable, + // LayoutListReadable, + // ILayoutSequenceWritable { + + public LayoutBinary() { + super(LayoutCode.BINARY, 0); + } + + public boolean isFixed() { + return false; + } + + @Nonnull + public String name() { + return "binary"; + } + + @Override + @Nonnull + public Result readFixed( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT); + checkArgument(column.size() >= 0); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(null); + return Result.NOT_FOUND; + } + + value.set(buffer.readFixedBinary(scope.start() + column.offset(), column.size())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse( + @Nonnull final RowBuffer buffer, @Nonnull final RowCursor edit, @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + + final Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.readSparseBinary(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readVariable( + @Nonnull RowBuffer buffer, + @Nonnull RowCursor scope, + @Nonnull LayoutColumn column, + @Nonnull Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(null); + return Result.NOT_FOUND; + } + + final int valueOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); + value.set(buffer.readVariableBinary(valueOffset)); + + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed( + @Nonnull RowBuffer buffer, + @Nonnull RowCursor scope, + @Nonnull LayoutColumn column, + @Nonnull ByteBuf value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT); + checkArgument(column.size() >= 0); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + final int valueOffset = scope.start() + column.offset(); + buffer.setBit(scope.start(), column.nullBit()); + + buffer.writeFixedBinary(valueOffset, value, column.size()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull ByteBuf value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } + + @Override + @Nonnull + public Result writeSparse( + @Nonnull RowBuffer buffer, + @Nonnull RowCursor edit, + @Nonnull ByteBuf value, + @Nonnull UpdateOptions options) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + checkNotNull(options, "expected non-null options"); + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseBinary(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeVariable( + @Nonnull RowBuffer buffer, + @Nonnull RowCursor scope, + @Nonnull LayoutColumn column, + @Nonnull ByteBuf value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + if ((column.size() > 0) && (value.readableBytes() > column.size())) { + return Result.TOO_BIG; + } + + final boolean exists = buffer.readBit(scope.start(), column.nullBit()); + final int valueOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); + final Out shift = new Out<>(); + + buffer.writeVariableBinary(valueOffset, value, exists, shift); + buffer.setBit(scope.start(), column.nullBit()); + scope.metaOffset(scope.metaOffset() + shift.get()); + scope.valueOffset(scope.valueOffset() + shift.get()); + + return Result.SUCCESS; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBit.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBit.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBit.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBit.java index d4e635d..73f4552 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBit.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBit.java @@ -1,119 +1,119 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutBit { - /** - * The empty bit. - */ - public static final LayoutBit INVALID = new LayoutBit(-1); - - private final int index; - - /** - * Initializes a new instance of the {@link LayoutBit} class. - * - * @param index The zero-based offset into the layout bitmask. - */ - public LayoutBit(int index) { - checkArgument(index >= -1); - this.index = index; - } - - /** - * Compute the division rounding up to the next whole number - * - * @param numerator The numerator to divide. - * @param divisor The divisor to divide by. - * @return The ceiling(numerator/divisor). - */ - public static int divCeiling(int numerator, int divisor) { - return (numerator + (divisor - 1)) / divisor; - } - - /** - * Zero-based bit from the beginning of the byte that contains this bit. - *

- * Also see {@link #offset(int)} to identify relevant byte. - * - * @return The bit of the byte within the bitmask. - */ - public int bit() { - return this.index() % Byte.SIZE; - } - - /** - * Zero-based offset into the layout bitmask. - * - * @return zero-based offset into the layout bitmask. - */ - public int index() { - return this.index; - } - - public boolean isInvalid() { - return this.index == INVALID.index; - } - - /** - * Returns the zero-based byte offset from the beginning of the row or scope that contains the bit from the bitmask. - *

- * Also see {@link #bit()} to identify. - * - * @param offset The byte offset from the beginning of the row where the scope begins. - * @return The byte offset containing this bit. - */ - public int offset(int offset) { - return offset + (this.index() / Byte.SIZE); - } - - @Override - public boolean equals(Object other) { - return other instanceof LayoutBit && this.equals((LayoutBit)other); - } - - public boolean equals(LayoutBit other) { - return other != null && this.index() == other.index(); - } - - @Override - public int hashCode() { - return Integer.valueOf(this.index()).hashCode(); - } - - /** - * Allocates layout bits from a bitmask. - */ - static class Allocator { - /** - * The next bit to allocate. - */ - private int next; - - /** - * Initializes a new instance of the {@link Allocator} class. - */ - public Allocator() { - this.next = 0; - } - - /** - * The number of bytes needed to hold all bits so far allocated. - */ - public final int numBytes() { - return LayoutBit.divCeiling(this.next, Byte.SIZE); - } - - /** - * Allocates a new bit from the bitmask. - * - * @return The allocated bit. - */ - public final LayoutBit allocate() { - return new LayoutBit(this.next++); - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutBit { + /** + * The empty bit. + */ + public static final LayoutBit INVALID = new LayoutBit(-1); + + private final int index; + + /** + * Initializes a new instance of the {@link LayoutBit} class. + * + * @param index The zero-based offset into the layout bitmask. + */ + public LayoutBit(int index) { + checkArgument(index >= -1); + this.index = index; + } + + /** + * Compute the division rounding up to the next whole number + * + * @param numerator The numerator to divide. + * @param divisor The divisor to divide by. + * @return The ceiling(numerator/divisor). + */ + public static int divCeiling(int numerator, int divisor) { + return (numerator + (divisor - 1)) / divisor; + } + + /** + * Zero-based bit from the beginning of the byte that contains this bit. + *

+ * Also see {@link #offset(int)} to identify relevant byte. + * + * @return The bit of the byte within the bitmask. + */ + public int bit() { + return this.index() % Byte.SIZE; + } + + /** + * Zero-based offset into the layout bitmask. + * + * @return zero-based offset into the layout bitmask. + */ + public int index() { + return this.index; + } + + public boolean isInvalid() { + return this.index == INVALID.index; + } + + /** + * Returns the zero-based byte offset from the beginning of the row or scope that contains the bit from the bitmask. + *

+ * Also see {@link #bit()} to identify. + * + * @param offset The byte offset from the beginning of the row where the scope begins. + * @return The byte offset containing this bit. + */ + public int offset(int offset) { + return offset + (this.index() / Byte.SIZE); + } + + @Override + public boolean equals(Object other) { + return other instanceof LayoutBit && this.equals((LayoutBit)other); + } + + public boolean equals(LayoutBit other) { + return other != null && this.index() == other.index(); + } + + @Override + public int hashCode() { + return Integer.valueOf(this.index()).hashCode(); + } + + /** + * Allocates layout bits from a bitmask. + */ + static class Allocator { + /** + * The next bit to allocate. + */ + private int next; + + /** + * Initializes a new instance of the {@link Allocator} class. + */ + public Allocator() { + this.next = 0; + } + + /** + * The number of bytes needed to hold all bits so far allocated. + */ + public final int numBytes() { + return LayoutBit.divCeiling(this.next, Byte.SIZE); + } + + /** + * Allocates a new bit from the bitmask. + * + * @return The allocated bit. + */ + public final LayoutBit allocate() { + return new LayoutBit(this.next++); + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBoolean.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBoolean.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBoolean.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBoolean.java index 23abd09..1224902 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBoolean.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBoolean.java @@ -1,141 +1,141 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutBoolean extends LayoutTypePrimitive implements ILayoutType { - - public LayoutBoolean(boolean value) { - super(value ? LayoutCode.BOOLEAN : LayoutCode.BOOLEAN_FALSE, 0); - } - - @Override - public boolean isBoolean() { - return true; - } - - @Override - public boolean isFixed() { - return true; - } - - @Override - @Nonnull - public String name() { - return "bool"; - } - - @Override - @Nonnull - public Result readFixed( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(false); - return Result.NOT_FOUND; - } - - value.set(buffer.readBit(scope.start(), column.booleanBit())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - if (result != Result.SUCCESS) { - value.set(false); - return result; - } - - value.set(buffer.readSparseBoolean(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final Boolean value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT, - "expected scope of %s, not %s", LayoutUDT.class, scope.scopeType().getClass()); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - if (value) { - buffer.setBit(scope.start(), column.booleanBit()); - } else { - buffer.unsetBit(scope.start(), column.booleanBit()); - } - - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final Boolean value, - @Nonnull final UpdateOptions options) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(value, "expected non-null value"); - checkNotNull(options, "expected non-null options"); - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseBoolean(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Boolean value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutBoolean extends LayoutTypePrimitive implements ILayoutType { + + public LayoutBoolean(boolean value) { + super(value ? LayoutCode.BOOLEAN : LayoutCode.BOOLEAN_FALSE, 0); + } + + @Override + public boolean isBoolean() { + return true; + } + + @Override + public boolean isFixed() { + return true; + } + + @Override + @Nonnull + public String name() { + return "bool"; + } + + @Override + @Nonnull + public Result readFixed( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(false); + return Result.NOT_FOUND; + } + + value.set(buffer.readBit(scope.start(), column.booleanBit())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + if (result != Result.SUCCESS) { + value.set(false); + return result; + } + + value.set(buffer.readSparseBoolean(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final Boolean value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT, + "expected scope of %s, not %s", LayoutUDT.class, scope.scopeType().getClass()); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + if (value) { + buffer.setBit(scope.start(), column.booleanBit()); + } else { + buffer.unsetBit(scope.start(), column.booleanBit()); + } + + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final Boolean value, + @Nonnull final UpdateOptions options) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(value, "expected non-null value"); + checkNotNull(options, "expected non-null options"); + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseBoolean(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Boolean value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java index 99a0f1e..8b320ec 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java @@ -1,180 +1,180 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Stack; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutBuilder { - private LayoutBit.Allocator bitAllocator; - private ArrayList fixedColumns; - private int fixedCount; - private int fixedSize; - private String name; - private SchemaId schemaId; - private Stack scope; - private ArrayList sparseColumns; - private int sparseCount; - private ArrayList varColumns; - private int varCount; - - // [ - // - // ... - // ... - // ... - // ] - public LayoutBuilder(String name, SchemaId schemaId) { - this.name = name; - this.schemaId = schemaId; - this.reset(); - } - - public void addFixedColumn(@Nonnull String path, @Nonnull LayoutType type, boolean nullable, int length) { - - checkNotNull(path, "expected non-null path"); - checkNotNull(type, "expected non-null type"); - checkArgument(length >= 0); - checkArgument(!type.isVarint()); - - final LayoutColumn column; - - if (type.isNull()) { - checkArgument(nullable); - LayoutBit boolBit = this.bitAllocator.allocate(); - LayoutBit nullBit = LayoutBit.INVALID; - int offset = 0; - column = new LayoutColumn( - path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, - boolBit, nullBit, 0); - } else if (type.isBoolean()) { - LayoutBit nullBit = nullable ? this.bitAllocator.allocate() : LayoutBit.INVALID; - LayoutBit boolBit = this.bitAllocator.allocate(); - int offset = 0; - column = new LayoutColumn( - path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, - nullBit, boolBit, 0); - } else { - LayoutBit boolBit = LayoutBit.INVALID; - LayoutBit nullBit = nullable ? this.bitAllocator.allocate() : LayoutBit.INVALID; - int offset = this.fixedSize; - column = new LayoutColumn( - path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, - nullBit, boolBit, length); - this.fixedSize += type.isFixed() ? type.size() : length; - } - - this.fixedCount++; - this.fixedColumns.add(column); - } - - public void addObjectScope(String path, LayoutType type) { - - LayoutColumn column = new LayoutColumn(path, type, TypeArgumentList.EMPTY, StorageKind.SPARSE, this.parent(), - this.sparseCount, -1, LayoutBit.INVALID, LayoutBit.INVALID, 0); - - this.sparseCount++; - this.sparseColumns.add(column); - this.scope.push(column); - } - - public void addSparseColumn(@Nonnull final String path, @Nonnull final LayoutType type) { - - checkNotNull(path, "expected non-null path"); - checkNotNull(type, "expected non-null type"); - - final LayoutColumn column = new LayoutColumn( - path, type, TypeArgumentList.EMPTY, StorageKind.SPARSE, this.parent(), this.sparseCount, -1, - LayoutBit.INVALID, LayoutBit.INVALID, 0); - - this.sparseCount++; - this.sparseColumns.add(column); - } - - public void addTypedScope( - @Nonnull final String path, @Nonnull final LayoutType type, @Nonnull final TypeArgumentList typeArgs) { - - final LayoutColumn column = new LayoutColumn( - path, type, typeArgs, StorageKind.SPARSE, this.parent(), this.sparseCount, -1, LayoutBit.INVALID, - LayoutBit.INVALID, 0); - - this.sparseCount++; - this.sparseColumns.add(column); - } - - public void addVariableColumn(String path, LayoutType type, int length) { - - checkNotNull(path, "expected non-null path"); - checkNotNull(type, "expected non-null type"); - checkArgument(length >= 0); - checkArgument(type.allowVariable()); - - final LayoutColumn column = new LayoutColumn( - path, type, TypeArgumentList.EMPTY, StorageKind.VARIABLE, this.parent(), this.varCount, this.varCount, - this.bitAllocator.allocate(), LayoutBit.INVALID, length); - - this.varCount++; - this.varColumns.add(column); - } - - public Layout build() { - // Compute offset deltas. Offset bools by the present byte count, and fixed fields by the sum of the present - // and bool count. - int fixedDelta = this.bitAllocator.numBytes(); - int varIndexDelta = this.fixedCount; - - // Update the fixedColumns with the delta before freezing them. - ArrayList updatedColumns = - new ArrayList(this.fixedColumns.size() + this.varColumns.size()); - - for (LayoutColumn column : this.fixedColumns) { - column.offset(column.offset() + fixedDelta); - updatedColumns.add(column); - } - - for (LayoutColumn column : this.varColumns) { - // Adjust variable column indexes such that they begin immediately following the last fixed column. - column.index(column.index() + varIndexDelta); - updatedColumns.add(column); - } - - updatedColumns.addAll(this.sparseColumns); - - Layout layout = new Layout(this.name, this.schemaId, this.bitAllocator.numBytes(), this.fixedSize + fixedDelta, updatedColumns); - this.reset(); - return layout; - } - - public void EndObjectScope() { - checkArgument(this.scope.size() > 0); - this.scope.pop(); - } - - private LayoutColumn parent() { - if (this.scope.empty()) { - return null; - } - - return this.scope.peek(); - } - - private void reset() { - this.bitAllocator = new LayoutBit.Allocator(); - this.fixedSize = 0; - this.fixedCount = 0; - this.fixedColumns = new ArrayList(); - this.varCount = 0; - this.varColumns = new ArrayList(); - this.sparseCount = 0; - this.sparseColumns = new ArrayList(); - this.scope = new Stack(); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Stack; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutBuilder { + private LayoutBit.Allocator bitAllocator; + private ArrayList fixedColumns; + private int fixedCount; + private int fixedSize; + private String name; + private SchemaId schemaId; + private Stack scope; + private ArrayList sparseColumns; + private int sparseCount; + private ArrayList varColumns; + private int varCount; + + // [ + // + // ... + // ... + // ... + // ] + public LayoutBuilder(String name, SchemaId schemaId) { + this.name = name; + this.schemaId = schemaId; + this.reset(); + } + + public void addFixedColumn(@Nonnull String path, @Nonnull LayoutType type, boolean nullable, int length) { + + checkNotNull(path, "expected non-null path"); + checkNotNull(type, "expected non-null type"); + checkArgument(length >= 0); + checkArgument(!type.isVarint()); + + final LayoutColumn column; + + if (type.isNull()) { + checkArgument(nullable); + LayoutBit boolBit = this.bitAllocator.allocate(); + LayoutBit nullBit = LayoutBit.INVALID; + int offset = 0; + column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, + boolBit, nullBit, 0); + } else if (type.isBoolean()) { + LayoutBit nullBit = nullable ? this.bitAllocator.allocate() : LayoutBit.INVALID; + LayoutBit boolBit = this.bitAllocator.allocate(); + int offset = 0; + column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, + nullBit, boolBit, 0); + } else { + LayoutBit boolBit = LayoutBit.INVALID; + LayoutBit nullBit = nullable ? this.bitAllocator.allocate() : LayoutBit.INVALID; + int offset = this.fixedSize; + column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, + nullBit, boolBit, length); + this.fixedSize += type.isFixed() ? type.size() : length; + } + + this.fixedCount++; + this.fixedColumns.add(column); + } + + public void addObjectScope(String path, LayoutType type) { + + LayoutColumn column = new LayoutColumn(path, type, TypeArgumentList.EMPTY, StorageKind.SPARSE, this.parent(), + this.sparseCount, -1, LayoutBit.INVALID, LayoutBit.INVALID, 0); + + this.sparseCount++; + this.sparseColumns.add(column); + this.scope.push(column); + } + + public void addSparseColumn(@Nonnull final String path, @Nonnull final LayoutType type) { + + checkNotNull(path, "expected non-null path"); + checkNotNull(type, "expected non-null type"); + + final LayoutColumn column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.SPARSE, this.parent(), this.sparseCount, -1, + LayoutBit.INVALID, LayoutBit.INVALID, 0); + + this.sparseCount++; + this.sparseColumns.add(column); + } + + public void addTypedScope( + @Nonnull final String path, @Nonnull final LayoutType type, @Nonnull final TypeArgumentList typeArgs) { + + final LayoutColumn column = new LayoutColumn( + path, type, typeArgs, StorageKind.SPARSE, this.parent(), this.sparseCount, -1, LayoutBit.INVALID, + LayoutBit.INVALID, 0); + + this.sparseCount++; + this.sparseColumns.add(column); + } + + public void addVariableColumn(String path, LayoutType type, int length) { + + checkNotNull(path, "expected non-null path"); + checkNotNull(type, "expected non-null type"); + checkArgument(length >= 0); + checkArgument(type.allowVariable()); + + final LayoutColumn column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.VARIABLE, this.parent(), this.varCount, this.varCount, + this.bitAllocator.allocate(), LayoutBit.INVALID, length); + + this.varCount++; + this.varColumns.add(column); + } + + public Layout build() { + // Compute offset deltas. Offset bools by the present byte count, and fixed fields by the sum of the present + // and bool count. + int fixedDelta = this.bitAllocator.numBytes(); + int varIndexDelta = this.fixedCount; + + // Update the fixedColumns with the delta before freezing them. + ArrayList updatedColumns = + new ArrayList(this.fixedColumns.size() + this.varColumns.size()); + + for (LayoutColumn column : this.fixedColumns) { + column.offset(column.offset() + fixedDelta); + updatedColumns.add(column); + } + + for (LayoutColumn column : this.varColumns) { + // Adjust variable column indexes such that they begin immediately following the last fixed column. + column.index(column.index() + varIndexDelta); + updatedColumns.add(column); + } + + updatedColumns.addAll(this.sparseColumns); + + Layout layout = new Layout(this.name, this.schemaId, this.bitAllocator.numBytes(), this.fixedSize + fixedDelta, updatedColumns); + this.reset(); + return layout; + } + + public void EndObjectScope() { + checkArgument(this.scope.size() > 0); + this.scope.pop(); + } + + private LayoutColumn parent() { + if (this.scope.empty()) { + return null; + } + + return this.scope.peek(); + } + + private void reset() { + this.bitAllocator = new LayoutBit.Allocator(); + this.fixedSize = 0; + this.fixedCount = 0; + this.fixedColumns = new ArrayList(); + this.varCount = 0; + this.varColumns = new ArrayList(); + this.sparseCount = 0; + this.sparseColumns = new ArrayList(); + this.scope = new Stack(); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCode.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCode.java similarity index 95% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCode.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCode.java index 5afc92f..31991df 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCode.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCode.java @@ -1,119 +1,119 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.google.common.base.Suppliers; -import it.unimi.dsi.fastutil.bytes.Byte2ReferenceMap; -import it.unimi.dsi.fastutil.bytes.Byte2ReferenceOpenHashMap; - -import javax.annotation.Nullable; -import java.util.function.Supplier; - -/** - * Type coded used in the binary encoding to indicate the formatting of succeeding bytes. - */ -public enum LayoutCode { - - INVALID((byte)0), - - NULL((byte)1), - - BOOLEAN_FALSE((byte)2), - BOOLEAN((byte)3), - - INT_8((byte)5), - INT_16((byte)6), - INT_32((byte)7), - INT_64((byte)8), - UINT_8((byte)9), - UINT_16((byte)10), - UINT_32((byte)11), - UINT_64((byte)12), - VAR_INT((byte)13), - VAR_UINT((byte)14), - - FLOAT_32((byte)15), - FLOAT_64((byte)16), - DECIMAL((byte)17), - - DATE_TIME((byte)18), - GUID((byte)19), - - UTF_8((byte)20), - BINARY((byte)21), - - FLOAT_128((byte)22), - UNIX_DATE_TIME((byte)23), - MONGODB_OBJECT_ID((byte)24), - - OBJECT_SCOPE((byte)30), - IMMUTABLE_OBJECT_SCOPE((byte)31), - - ARRAY_SCOPE((byte)32), - IMMUTABLE_ARRAY_SCOPE((byte)33), - - TYPED_ARRAY_SCOPE((byte)34), - IMMUTABLE_TYPED_ARRAY_SCOPE((byte)35), - - TUPLE_SCOPE((byte)36), - IMMUTABLE_TUPLE_SCOPE((byte)37), - - TYPED_TUPLE_SCOPE((byte)38), - IMMUTABLE_TYPED_TUPLE_SCOPE((byte)39), - - MAP_SCOPE((byte)40), - IMMUTABLE_MAP_SCOPE((byte)41), - - TYPED_MAP_SCOPE((byte)42), - IMMUTABLE_TYPED_MAP_SCOPE((byte)43), - - SET_SCOPE((byte)44), - IMMUTABLE_SET_SCOPE((byte)45), - - TYPED_SET_SCOPE((byte)46), - IMMUTABLE_TYPED_SET_SCOPE((byte)47), - - NULLABLE_SCOPE((byte)48), - IMMUTABLE_NULLABLE_SCOPE((byte)49), - - TAGGED_SCOPE((byte)50), - IMMUTABLE_TAGGED_SCOPE((byte)51), - - TAGGED2_SCOPE((byte)52), - IMMUTABLE_TAGGED2_SCOPE((byte)53), - - /** - * Nested row. - */ - SCHEMA((byte)68), - IMMUTABLE_SCHEMA((byte)69), - - END_SCOPE((byte)70); - - public static final int BYTES = Byte.BYTES; - - private static final Supplier> mappings = Suppliers.memoize(() -> { - final LayoutCode[] constants = LayoutCode.class.getEnumConstants(); - final byte[] values = new byte[constants.length]; - for (int i = 0; i < constants.length; i++) { - values[i] = constants[i].value(); - } - return new Byte2ReferenceOpenHashMap<>(values, constants); - }); - - private final byte value; - - LayoutCode(final byte value) { - this.value = value; - } - - public byte value() { - return this.value; - } - - @Nullable - public static LayoutCode from(final byte value) { - return mappings.get().get(value); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.bytes.Byte2ReferenceMap; +import it.unimi.dsi.fastutil.bytes.Byte2ReferenceOpenHashMap; + +import javax.annotation.Nullable; +import java.util.function.Supplier; + +/** + * Type coded used in the binary encoding to indicate the formatting of succeeding bytes. + */ +public enum LayoutCode { + + INVALID((byte)0), + + NULL((byte)1), + + BOOLEAN_FALSE((byte)2), + BOOLEAN((byte)3), + + INT_8((byte)5), + INT_16((byte)6), + INT_32((byte)7), + INT_64((byte)8), + UINT_8((byte)9), + UINT_16((byte)10), + UINT_32((byte)11), + UINT_64((byte)12), + VAR_INT((byte)13), + VAR_UINT((byte)14), + + FLOAT_32((byte)15), + FLOAT_64((byte)16), + DECIMAL((byte)17), + + DATE_TIME((byte)18), + GUID((byte)19), + + UTF_8((byte)20), + BINARY((byte)21), + + FLOAT_128((byte)22), + UNIX_DATE_TIME((byte)23), + MONGODB_OBJECT_ID((byte)24), + + OBJECT_SCOPE((byte)30), + IMMUTABLE_OBJECT_SCOPE((byte)31), + + ARRAY_SCOPE((byte)32), + IMMUTABLE_ARRAY_SCOPE((byte)33), + + TYPED_ARRAY_SCOPE((byte)34), + IMMUTABLE_TYPED_ARRAY_SCOPE((byte)35), + + TUPLE_SCOPE((byte)36), + IMMUTABLE_TUPLE_SCOPE((byte)37), + + TYPED_TUPLE_SCOPE((byte)38), + IMMUTABLE_TYPED_TUPLE_SCOPE((byte)39), + + MAP_SCOPE((byte)40), + IMMUTABLE_MAP_SCOPE((byte)41), + + TYPED_MAP_SCOPE((byte)42), + IMMUTABLE_TYPED_MAP_SCOPE((byte)43), + + SET_SCOPE((byte)44), + IMMUTABLE_SET_SCOPE((byte)45), + + TYPED_SET_SCOPE((byte)46), + IMMUTABLE_TYPED_SET_SCOPE((byte)47), + + NULLABLE_SCOPE((byte)48), + IMMUTABLE_NULLABLE_SCOPE((byte)49), + + TAGGED_SCOPE((byte)50), + IMMUTABLE_TAGGED_SCOPE((byte)51), + + TAGGED2_SCOPE((byte)52), + IMMUTABLE_TAGGED2_SCOPE((byte)53), + + /** + * Nested row. + */ + SCHEMA((byte)68), + IMMUTABLE_SCHEMA((byte)69), + + END_SCOPE((byte)70); + + public static final int BYTES = Byte.BYTES; + + private static final Supplier> mappings = Suppliers.memoize(() -> { + final LayoutCode[] constants = LayoutCode.class.getEnumConstants(); + final byte[] values = new byte[constants.length]; + for (int i = 0; i < constants.length; i++) { + values[i] = constants[i].value(); + } + return new Byte2ReferenceOpenHashMap<>(values, constants); + }); + + private final byte value; + + LayoutCode(final byte value) { + this.value = value; + } + + public byte value() { + return this.value; + } + + @Nullable + public static LayoutCode from(final byte value) { + return mappings.get().get(value); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCodeTraits.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCodeTraits.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCodeTraits.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCodeTraits.java index 055efc2..355e819 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCodeTraits.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCodeTraits.java @@ -1,41 +1,41 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -public final class LayoutCodeTraits { - /** - * {@code true} if the specified layout code indicates that an element type always requires a type code. - *

- * When this method returns {@code true} it indicates that the element value is in the type code. - * - * @param code The element type code. - * @return {@code true} if the specified layout code indicates that an element type always requires a type code. - */ - public static boolean alwaysRequiresTypeCode(LayoutCode code) { - return (code == LayoutCode.BOOLEAN) || (code == LayoutCode.BOOLEAN_FALSE) || (code == LayoutCode.NULL); - } - - /** - * A canonicalized version of the specified layout code. - *

- * Some codes (e.g. {@link LayoutCode#BOOLEAN} use multiple type codes to also encode values. This function converts - * actual value based code into the canonicalized type code for schema comparisons. - * - * @param code the code to canonicalize. - * @return a canonicalized version of the specified layout code. - */ - public static LayoutCode canonicalize(LayoutCode code) { - return (code == LayoutCode.BOOLEAN_FALSE) ? LayoutCode.BOOLEAN : code; - } - - /** - * Returns the same scope code without the immutable bit set. - * - * @param code The scope type code. - * @return the same scope code without the immutable bit set. - */ - public static LayoutCode clearImmutableBit(LayoutCode code) { - return LayoutCode.from((byte) (code.value() & 0xFE)); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +public final class LayoutCodeTraits { + /** + * {@code true} if the specified layout code indicates that an element type always requires a type code. + *

+ * When this method returns {@code true} it indicates that the element value is in the type code. + * + * @param code The element type code. + * @return {@code true} if the specified layout code indicates that an element type always requires a type code. + */ + public static boolean alwaysRequiresTypeCode(LayoutCode code) { + return (code == LayoutCode.BOOLEAN) || (code == LayoutCode.BOOLEAN_FALSE) || (code == LayoutCode.NULL); + } + + /** + * A canonicalized version of the specified layout code. + *

+ * Some codes (e.g. {@link LayoutCode#BOOLEAN} use multiple type codes to also encode values. This function converts + * actual value based code into the canonicalized type code for schema comparisons. + * + * @param code the code to canonicalize. + * @return a canonicalized version of the specified layout code. + */ + public static LayoutCode canonicalize(LayoutCode code) { + return (code == LayoutCode.BOOLEAN_FALSE) ? LayoutCode.BOOLEAN : code; + } + + /** + * Returns the same scope code without the immutable bit set. + * + * @param code The scope type code. + * @return the same scope code without the immutable bit set. + */ + public static LayoutCode clearImmutableBit(LayoutCode code) { + return LayoutCode.from((byte) (code.value() & 0xFE)); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutColumn.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutColumn.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutColumn.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutColumn.java index 60ff3c7..d61633c 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutColumn.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutColumn.java @@ -1,242 +1,242 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Utf8String; -import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.lenientFormat; - -public final class LayoutColumn { - - private final LayoutBit booleanBit; - private final Utf8String fullPath; - private final LayoutBit nullBit; - private final LayoutColumn parent; - private final Utf8String path; - private final int size; - private final StorageKind storage; - private final LayoutType type; - private final TypeArgument typeArg; - private final TypeArgumentList typeArgs; - - private int index; - private int offset; - - /** - * Initializes a new instance of the {@link LayoutColumn} class - * - * @param path The path to the field relative to parent scope. - * @param type Type of the field. - * @param typeArgs For types with generic parameters (e.g. {@link LayoutTuple}, the type parameters. - * @param storage Storage encoding of the field. - * @param parent The layout of the parent scope, if a nested column. - * @param index zero-based column index. - * @param offset zero-based Offset from beginning of serialization. - * @param nullBit zero-based index into the bit mask for the null bit. - * @param booleanBit For bool fields, zero-based index into the bit mask for the boolean value. - * @param length For variable length types the length, otherwise {@code 0}. - */ - public LayoutColumn( - @Nonnull final String path, @Nonnull final LayoutType type, @Nonnull final TypeArgumentList typeArgs, - @Nonnull final StorageKind storage, final LayoutColumn parent, int index, int offset, - @Nonnull final LayoutBit nullBit, @Nonnull final LayoutBit booleanBit, int length) { - - checkNotNull(path); - checkNotNull(type); - checkNotNull(typeArgs); - checkNotNull(storage); - checkNotNull(nullBit); - checkNotNull(booleanBit); - - this.path = Utf8String.transcodeUtf16(path); - this.fullPath = Utf8String.transcodeUtf16(fullPath(parent, path)); - this.type = type; - this.typeArgs = typeArgs; - this.typeArg = new TypeArgument(type, typeArgs); - this.storage = storage; - this.parent = parent; - this.index = index; - this.offset = offset; - this.nullBit = nullBit; - this.booleanBit = booleanBit; - this.size = this.typeArg().type().isFixed() ? type.size() : length; - } - - /** - * For boolean fields, the zero-based index into the bit mask for the boolean value. - * - * @return for boolean fields, the zero-based index into the bit mask for the boolean value. - */ - public @Nonnull LayoutBit booleanBit() { - return this.booleanBit; - } - - /** - * Full logical path of the field within the row. - *

- * Paths are expressed in dotted notation: e.g. a relative {@link #path()} of 'b.c' within the scope 'a' yields a - * full path of 'a.b.c'. - * - * @return Full logical path of the field within the row. - */ - public @Nonnull Utf8String fullPath() { - return this.fullPath; - } - - /** - * Zero-based index of the column within the structure. - *

- * This value also indicates which presence bit controls this column. - * - * @return Zero-based index of the column within the structure. - */ - public int index() { - return this.index; - } - - /** - * For nullable fields, the zero-based index into the bit mask for the null bit. - * - * @return For nullable fields, the zero-based index into the bit mask for the null bit. - */ - public @Nonnull LayoutBit nullBit() { - return this.nullBit; - } - - /** - * If {@link #storage()} equals {@link StorageKind#FIXED} then the byte offset to the field location. - *

- * If {@link #storage()} equals {@link StorageKind#VARIABLE} then the zero-based index of the field from the - * beginning of the variable length segment. - *

- * For all other values of {@link #storage()}, {@code offset} is ignored. - * - * @return If {@link #storage()} equals {@link StorageKind#FIXED} then the byte offset to the field location. - */ - public int offset() { - return this.offset; - } - - /** - * Layout of the parent scope, if a nested column, otherwise {@code null}. - * - * @return Layout of the parent scope, if a nested column, otherwise {@code null}. - */ - public LayoutColumn parent() { - return this.parent; - } - - /** - * The relative path of the field within its parent scope. - *

- * Paths are expressed in dotted notation: e.g. a relative {@link #path} of 'b.c' within the scope 'a' yields a - * {@link #fullPath} of 'a.b.c'. - * - * @return the relative path of the field within its parent scope. - */ - public @Nonnull Utf8String path() { - return this.path; - } - - /** - * If {@link LayoutType#isBoolean()} then the zero-based extra index within the boolean byte holding the value of - * this type, otherwise must be 0. - * - * @return If {@link LayoutType#isBoolean()} then the zero-based extra index within the boolean byte holding the - * value of this type, otherwise must be 0. - */ - public int size() { - return this.size; - } - - /** - * The storage kind of the field. - * - * @return the storage kind of the field. - */ - public @Nonnull StorageKind storage() { - return this.storage; - } - - /** - * The physical layout type of the field. - * - * @return the physical layout type of the field. - */ - public @Nonnull LayoutType type() { - return this.type; - } - - /** - * The full logical type. - * - * @return the full logical type. - */ - public @Nonnull TypeArgument typeArg() { - return this.typeArg; - } - - /** - * For types with generic parameters (e.g. {@link LayoutTuple}, the type parameters. - * - * @return for types with generic parameters (e.g. {@link LayoutTuple}, the type parameters. - */ - public @Nonnull TypeArgumentList typeArgs() { - return this.typeArgs; - } - - /** - * The physical layout type of the field cast to the specified type. - * - * @param a type that implements {@code ILayoutType}. - * - * @return The physical layout type of the field cast to the specified type. - */ - @SuppressWarnings("unchecked") - public @Nonnull T typeAs() { - return (T) this.type().typeAs(); - } - - LayoutColumn index(int value) { - this.index = value; - return this; - } - - LayoutColumn offset(int value) { - this.offset = value; - return this; - } - - /** - * Computes the full logical path to the column. - * - * @param parent The layout of the parent scope, if a nested column, otherwise null. - * @param path The path to the field relative to parent scope. - * @return The full logical path. - */ - private static @Nonnull String fullPath(final LayoutColumn parent, @Nonnull final String path) { - - if (parent != null) { - switch (LayoutCodeTraits.clearImmutableBit(parent.type().layoutCode())) { - case OBJECT_SCOPE: - case SCHEMA: - return parent.fullPath().toString() + "." + path; - case ARRAY_SCOPE: - case TYPED_ARRAY_SCOPE: - case TYPED_SET_SCOPE: - case TYPED_MAP_SCOPE: - return parent.fullPath().toString() + "[]" + path; - default: - final String message = lenientFormat("Parent scope type not supported: %s", parent.type().layoutCode()); - throw new IllegalStateException(message); - } - } - - return path; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Utf8String; +import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.lenientFormat; + +public final class LayoutColumn { + + private final LayoutBit booleanBit; + private final Utf8String fullPath; + private final LayoutBit nullBit; + private final LayoutColumn parent; + private final Utf8String path; + private final int size; + private final StorageKind storage; + private final LayoutType type; + private final TypeArgument typeArg; + private final TypeArgumentList typeArgs; + + private int index; + private int offset; + + /** + * Initializes a new instance of the {@link LayoutColumn} class + * + * @param path The path to the field relative to parent scope. + * @param type Type of the field. + * @param typeArgs For types with generic parameters (e.g. {@link LayoutTuple}, the type parameters. + * @param storage Storage encoding of the field. + * @param parent The layout of the parent scope, if a nested column. + * @param index zero-based column index. + * @param offset zero-based Offset from beginning of serialization. + * @param nullBit zero-based index into the bit mask for the null bit. + * @param booleanBit For bool fields, zero-based index into the bit mask for the boolean value. + * @param length For variable length types the length, otherwise {@code 0}. + */ + public LayoutColumn( + @Nonnull final String path, @Nonnull final LayoutType type, @Nonnull final TypeArgumentList typeArgs, + @Nonnull final StorageKind storage, final LayoutColumn parent, int index, int offset, + @Nonnull final LayoutBit nullBit, @Nonnull final LayoutBit booleanBit, int length) { + + checkNotNull(path); + checkNotNull(type); + checkNotNull(typeArgs); + checkNotNull(storage); + checkNotNull(nullBit); + checkNotNull(booleanBit); + + this.path = Utf8String.transcodeUtf16(path); + this.fullPath = Utf8String.transcodeUtf16(fullPath(parent, path)); + this.type = type; + this.typeArgs = typeArgs; + this.typeArg = new TypeArgument(type, typeArgs); + this.storage = storage; + this.parent = parent; + this.index = index; + this.offset = offset; + this.nullBit = nullBit; + this.booleanBit = booleanBit; + this.size = this.typeArg().type().isFixed() ? type.size() : length; + } + + /** + * For boolean fields, the zero-based index into the bit mask for the boolean value. + * + * @return for boolean fields, the zero-based index into the bit mask for the boolean value. + */ + public @Nonnull LayoutBit booleanBit() { + return this.booleanBit; + } + + /** + * Full logical path of the field within the row. + *

+ * Paths are expressed in dotted notation: e.g. a relative {@link #path()} of 'b.c' within the scope 'a' yields a + * full path of 'a.b.c'. + * + * @return Full logical path of the field within the row. + */ + public @Nonnull Utf8String fullPath() { + return this.fullPath; + } + + /** + * Zero-based index of the column within the structure. + *

+ * This value also indicates which presence bit controls this column. + * + * @return Zero-based index of the column within the structure. + */ + public int index() { + return this.index; + } + + /** + * For nullable fields, the zero-based index into the bit mask for the null bit. + * + * @return For nullable fields, the zero-based index into the bit mask for the null bit. + */ + public @Nonnull LayoutBit nullBit() { + return this.nullBit; + } + + /** + * If {@link #storage()} equals {@link StorageKind#FIXED} then the byte offset to the field location. + *

+ * If {@link #storage()} equals {@link StorageKind#VARIABLE} then the zero-based index of the field from the + * beginning of the variable length segment. + *

+ * For all other values of {@link #storage()}, {@code offset} is ignored. + * + * @return If {@link #storage()} equals {@link StorageKind#FIXED} then the byte offset to the field location. + */ + public int offset() { + return this.offset; + } + + /** + * Layout of the parent scope, if a nested column, otherwise {@code null}. + * + * @return Layout of the parent scope, if a nested column, otherwise {@code null}. + */ + public LayoutColumn parent() { + return this.parent; + } + + /** + * The relative path of the field within its parent scope. + *

+ * Paths are expressed in dotted notation: e.g. a relative {@link #path} of 'b.c' within the scope 'a' yields a + * {@link #fullPath} of 'a.b.c'. + * + * @return the relative path of the field within its parent scope. + */ + public @Nonnull Utf8String path() { + return this.path; + } + + /** + * If {@link LayoutType#isBoolean()} then the zero-based extra index within the boolean byte holding the value of + * this type, otherwise must be 0. + * + * @return If {@link LayoutType#isBoolean()} then the zero-based extra index within the boolean byte holding the + * value of this type, otherwise must be 0. + */ + public int size() { + return this.size; + } + + /** + * The storage kind of the field. + * + * @return the storage kind of the field. + */ + public @Nonnull StorageKind storage() { + return this.storage; + } + + /** + * The physical layout type of the field. + * + * @return the physical layout type of the field. + */ + public @Nonnull LayoutType type() { + return this.type; + } + + /** + * The full logical type. + * + * @return the full logical type. + */ + public @Nonnull TypeArgument typeArg() { + return this.typeArg; + } + + /** + * For types with generic parameters (e.g. {@link LayoutTuple}, the type parameters. + * + * @return for types with generic parameters (e.g. {@link LayoutTuple}, the type parameters. + */ + public @Nonnull TypeArgumentList typeArgs() { + return this.typeArgs; + } + + /** + * The physical layout type of the field cast to the specified type. + * + * @param a type that implements {@code ILayoutType}. + * + * @return The physical layout type of the field cast to the specified type. + */ + @SuppressWarnings("unchecked") + public @Nonnull T typeAs() { + return (T) this.type().typeAs(); + } + + LayoutColumn index(int value) { + this.index = value; + return this; + } + + LayoutColumn offset(int value) { + this.offset = value; + return this; + } + + /** + * Computes the full logical path to the column. + * + * @param parent The layout of the parent scope, if a nested column, otherwise null. + * @param path The path to the field relative to parent scope. + * @return The full logical path. + */ + private static @Nonnull String fullPath(final LayoutColumn parent, @Nonnull final String path) { + + if (parent != null) { + switch (LayoutCodeTraits.clearImmutableBit(parent.type().layoutCode())) { + case OBJECT_SCOPE: + case SCHEMA: + return parent.fullPath().toString() + "." + path; + case ARRAY_SCOPE: + case TYPED_ARRAY_SCOPE: + case TYPED_SET_SCOPE: + case TYPED_MAP_SCOPE: + return parent.fullPath().toString() + "[]" + path; + default: + final String message = lenientFormat("Parent scope type not supported: %s", parent.type().layoutCode()); + throw new IllegalStateException(message); + } + } + + return path; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompilationException.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompilationException.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompilationException.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompilationException.java index af0c639..cae5bb5 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompilationException.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompilationException.java @@ -1,20 +1,20 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import java.io.Serializable; - -public final class LayoutCompilationException extends RuntimeException implements Serializable { - - public LayoutCompilationException() { - } - - public LayoutCompilationException(String message) { - super(message); - } - - public LayoutCompilationException(String message, RuntimeException cause) { - super(message, cause); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import java.io.Serializable; + +public final class LayoutCompilationException extends RuntimeException implements Serializable { + + public LayoutCompilationException() { + } + + public LayoutCompilationException(String message) { + super(message); + } + + public LayoutCompilationException(String message, RuntimeException cause) { + super(message, cause); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompiler.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompiler.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompiler.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompiler.java index 97034f5..1d87207 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompiler.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutCompiler.java @@ -1,433 +1,433 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.azure.data.cosmos.serialization.hybridrow.schemas.ArrayPropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.MapPropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.Namespace; -import com.azure.data.cosmos.serialization.hybridrow.schemas.ObjectPropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.PrimitivePropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.Property; -import com.azure.data.cosmos.serialization.hybridrow.schemas.PropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.Schema; -import com.azure.data.cosmos.serialization.hybridrow.schemas.ScopePropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.SetPropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.TaggedPropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.TuplePropertyType; -import com.azure.data.cosmos.serialization.hybridrow.schemas.TypeKind; -import com.azure.data.cosmos.serialization.hybridrow.schemas.UdtPropertyType; -import com.google.common.base.Strings; -import org.checkerframework.checker.index.qual.NonNegative; - -import javax.annotation.Nonnull; -import java.util.List; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.lenientFormat; - -/** - * Converts a logical schema into a physical layout. - */ -public final class LayoutCompiler { - /** - * Compiles a logical schema into a physical layout that can be used to read and write rows - * - * @param namespace The namespace within which {@code schema} is defined - * @param schema The logical schema to produce a layout for - * @return The layout for the schema - */ - @Nonnull - public static Layout compile(@Nonnull final Namespace namespace, @Nonnull final Schema schema) { - - checkNotNull(namespace, "expected non-null namespace"); - checkNotNull(schema, "expected non-null schema"); - checkArgument(schema.type() == TypeKind.SCHEMA); - checkArgument(!Strings.isNullOrEmpty(schema.name())); - checkArgument(namespace.schemas().contains(schema)); - - LayoutBuilder builder = new LayoutBuilder(schema.name(), schema.schemaId()); - LayoutCompiler.addProperties(builder, namespace, LayoutCode.SCHEMA, schema.properties()); - - return builder.build(); - } - - private static void addProperties( - @Nonnull final LayoutBuilder builder, - @Nonnull final Namespace namespace, - @Nonnull LayoutCode layoutCode, - @Nonnull List properties) { - - final Out typeArgs = new Out<>(); - - for (Property p : properties) { - - LayoutType type = LayoutCompiler.logicalToPhysicalType(namespace, p.type(), typeArgs); - - switch (LayoutCodeTraits.clearImmutableBit(type.layoutCode())) { - - case OBJECT_SCOPE: { - if (!p.type().nullable()) { - throw new LayoutCompilationException("Non-nullable sparse column are not supported."); - } - ObjectPropertyType op = (ObjectPropertyType)p.type(); - builder.addObjectScope(p.path(), type); - LayoutCompiler.addProperties(builder, namespace, type.layoutCode(), op.properties()); - builder.EndObjectScope(); - break; - } - - case ARRAY_SCOPE: - case TYPED_ARRAY_SCOPE: - case SET_SCOPE: - case TYPED_SET_SCOPE: - case MAP_SCOPE: - case TYPED_MAP_SCOPE: - case TUPLE_SCOPE: - case TYPED_TUPLE_SCOPE: - case TAGGED_SCOPE: - case TAGGED2_SCOPE: - case SCHEMA: { - if (!p.type().nullable()) { - throw new LayoutCompilationException("Non-nullable sparse column are not supported."); - } - builder.addTypedScope(p.path(), type, typeArgs.get()); - break; - } - - case NULLABLE_SCOPE: { - throw new LayoutCompilationException("Nullables cannot be explicitly declared as columns."); - } - - default: { - - if (p.type() instanceof PrimitivePropertyType) { - - PrimitivePropertyType pp = (PrimitivePropertyType) p.type(); - - switch (pp.storage()) { - - case FIXED: - if (LayoutCodeTraits.clearImmutableBit(layoutCode) != LayoutCode.SCHEMA) { - throw new LayoutCompilationException( - "Cannot have fixed storage within a sparse layoutCode."); - } - if (type.isNull() && !pp.nullable()) { - throw new LayoutCompilationException( - "Non-nullable null columns are not supported."); - } - builder.addFixedColumn(p.path(), type, pp.nullable(), pp.length()); - break; - - case VARIABLE: - if (LayoutCodeTraits.clearImmutableBit(layoutCode) != LayoutCode.SCHEMA) { - throw new LayoutCompilationException( - "Cannot have variable storage within a sparse layoutCode."); - } - if (!pp.nullable()) { - throw new LayoutCompilationException( - "Non-nullable variable columns are not supported."); - } - builder.addVariableColumn(p.path(), type, pp.length()); - break; - - case SPARSE: - if (!pp.nullable()) { - throw new LayoutCompilationException( - "Non-nullable sparse columns are not supported."); - } - builder.addSparseColumn(p.path(), type); - break; - - default: - throw new LayoutCompilationException( - lenientFormat("Unknown storage specification: %s", pp.storage())); - } - } else { - throw new LayoutCompilationException( - lenientFormat("Unknown property type: %s", type.name())); - } - - break; - } - } - } - } - - private static LayoutType logicalToPhysicalType( - Namespace namespace, PropertyType logicalType, Out typeArgs) { - - typeArgs.set(TypeArgumentList.EMPTY); - boolean immutable = logicalType instanceof ScopePropertyType && ((ScopePropertyType) logicalType).immutable(); - - switch (logicalType.type()) { - - case NULL: - return LayoutTypes.NULL; - - case BOOLEAN: - return LayoutTypes.BOOLEAN; - - case INT_8: - return LayoutTypes.INT_8; - - case INT_16: - return LayoutTypes.INT_16; - - case INT_32: - return LayoutTypes.INT_32; - - case INT_64: - return LayoutTypes.INT_64; - - case UINT_8: - return LayoutTypes.UINT_8; - - case UINT_16: - return LayoutTypes.UINT_16; - - case UINT_32: - return LayoutTypes.UINT_32; - - case UINT_64: - return LayoutTypes.UINT_64; - - case FLOAT_32: - return LayoutTypes.FLOAT_32; - - case FLOAT_64: - return LayoutTypes.FLOAT_64; - - case FLOAT_128: - return LayoutTypes.FLOAT_128; - - case DECIMAL: - return LayoutTypes.DECIMAL; - - case DATE_TIME: - return LayoutTypes.DATE_TIME; - - case UNIX_DATE_TIME: - return LayoutTypes.UNIX_DATE_TIME; - - case GUID: - return LayoutTypes.GUID; - - case MONGODB_OBJECT_ID: - throw new UnsupportedOperationException(); - // return LayoutTypes.MONGO_DB_OBJECT_ID; - - case UTF_8: - return LayoutTypes.UTF_8; - - case BINARY: - return LayoutTypes.BINARY; - - case VAR_INT: - return LayoutTypes.VAR_INT; - - case VAR_UINT: - return LayoutTypes.VAR_UINT; - - case OBJECT: - return immutable ? LayoutTypes.IMMUTABLE_OBJECT : LayoutTypes.OBJECT; - - case ARRAY: { - - assert logicalType instanceof ArrayPropertyType; - ArrayPropertyType ap = (ArrayPropertyType) logicalType; - - if (ap.items() != null && (ap.items().type() != TypeKind.ANY)) { - - final Out out = new Out<>(); - - LayoutType itemType = LayoutCompiler.logicalToPhysicalType(namespace, ap.items(), out); - TypeArgumentList itemTypeArgs = out.get(); - - if (ap.items().nullable()) { - itemTypeArgs = new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs)); - itemType = itemType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; - } - - typeArgs.set(new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs))); - return immutable ? LayoutTypes.IMMUTABLE_TYPED_ARRAY : LayoutTypes.TYPED_ARRAY; - } - - return immutable ? LayoutTypes.IMMUTABLE_ARRAY : LayoutTypes.ARRAY; - } - case SET: { - - assert logicalType instanceof SetPropertyType; - SetPropertyType sp = (SetPropertyType) logicalType; - - if ((sp.items() != null) && (sp.items().type() != TypeKind.ANY)) { - - final Out out = new Out<>(); - - LayoutType itemType = LayoutCompiler.logicalToPhysicalType(namespace, sp.items(), out); - TypeArgumentList itemTypeArgs = out.get(); - - if (sp.items().nullable()) { - itemTypeArgs = new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs)); - itemType = itemType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; - } - - typeArgs.set(new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs))); - return immutable ? LayoutTypes.IMMUTABLE_TYPED_SET : LayoutTypes.TYPED_SET; - } - - // TODO(283638): implement sparse set - - throw new LayoutCompilationException(lenientFormat( - "Unknown property type: %s", - logicalType.type() - )); - } - case MAP: { - - assert logicalType instanceof MapPropertyType; - MapPropertyType mp = (MapPropertyType) logicalType; - - if (mp.keys() != null && (mp.keys().type() != TypeKind.ANY) && (mp.values() != null) && (mp.values().type() != TypeKind.ANY)) { - - final Out out = new Out<>(); - - LayoutType keyType = LayoutCompiler.logicalToPhysicalType(namespace, mp.keys(), out); - TypeArgumentList keyTypeArgs = out.get(); - - if (mp.keys().nullable()) { - keyTypeArgs = new TypeArgumentList(new TypeArgument(keyType, keyTypeArgs)); - keyType = keyType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; - } - - LayoutType valueType = LayoutCompiler.logicalToPhysicalType(namespace, mp.values(), out); - TypeArgumentList valueTypeArgs = out.get(); - - if (mp.values().nullable()) { - valueTypeArgs = new TypeArgumentList(new TypeArgument(valueType, valueTypeArgs)); - valueType = valueType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; - } - - typeArgs.set(new TypeArgumentList( - new TypeArgument(keyType, keyTypeArgs), - new TypeArgument(valueType, valueTypeArgs) - )); - - return immutable ? LayoutTypes.IMMUTABLE_TYPED_MAP : LayoutTypes.TYPED_MAP; - } - - // TODO(283638): implement sparse map - - throw new LayoutCompilationException(lenientFormat( - "Unknown property type: %s", logicalType.type()) - ); - } - case TUPLE: { - - assert logicalType instanceof TuplePropertyType; - final TuplePropertyType tp = (TuplePropertyType) logicalType; - - final TypeArgument[] args = new TypeArgument[tp.items().size()]; - final Out out = new Out<>(); - - for (int i = 0; i < tp.items().size(); i++) { - - LayoutType itemType = LayoutCompiler.logicalToPhysicalType(namespace, tp.items().get(i), out); - TypeArgumentList itemTypeArgs = out.get(); - - if (tp.items().get(i).nullable()) { - itemTypeArgs = new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs)); - itemType = itemType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; - } - - args[i] = new TypeArgument(itemType, itemTypeArgs); - } - - typeArgs.setAndGet(new TypeArgumentList(args)); - return immutable ? LayoutTypes.IMMUTABLE_TYPED_TUPLE : LayoutTypes.TYPED_TUPLE; - } - case TAGGED: { - - assert logicalType instanceof TaggedPropertyType; - TaggedPropertyType tg = (TaggedPropertyType) logicalType; - - if (tg.items().size() < TaggedPropertyType.MIN_TAGGED_ARGUMENTS || (tg.items().size() > TaggedPropertyType.MAX_TAGGED_ARGUMENTS)) { - throw new LayoutCompilationException(lenientFormat( - "Invalid number of arguments in Tagged: %s <= %s <= %s", - TaggedPropertyType.MIN_TAGGED_ARGUMENTS, - tg.items().size(), - TaggedPropertyType.MAX_TAGGED_ARGUMENTS - )); - } - - final Out out = new Out<>(); - final TypeArgument[] tgArgs = new TypeArgument[tg.items().size() + 1]; - - tgArgs[0] = new TypeArgument(LayoutTypes.UINT_8, TypeArgumentList.EMPTY); - - for (int i = 0; i < tg.items().size(); i++) { - - LayoutType itemType = LayoutCompiler.logicalToPhysicalType(namespace, tg.items().get(i), out); - TypeArgumentList itemTypeArgs = out.get(); - - if (tg.items().get(i).nullable()) { - itemTypeArgs = new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs)); - itemType = itemType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; - } - - tgArgs[i + 1] = new TypeArgument(itemType, itemTypeArgs); - } - - typeArgs.set(new TypeArgumentList(tgArgs)); - - switch (tg.items().size()) { - case 1: - return immutable ? LayoutTypes.IMMUTABLE_TAGGED : LayoutTypes.TAGGED; - case 2: - return immutable ? LayoutTypes.IMMUTABLE_TAGGED_2 : LayoutTypes.TAGGED_2; - default: - throw new LayoutCompilationException("Unexpected tagged arity"); - } - } - case SCHEMA: { - - assert logicalType instanceof UdtPropertyType; - UdtPropertyType up = (UdtPropertyType) logicalType; - - final Optional udtSchema; - - if (up.schemaId() == SchemaId.NONE) { - udtSchema = namespace.schemas().stream() - .filter(schema -> up.name().equals(schema.name())) - .findFirst(); - } else { - udtSchema = namespace.schemas().stream() - .filter(schema -> up.schemaId().equals(schema.schemaId())) - .findFirst(); - if (udtSchema.isPresent() && !up.name().equals(udtSchema.get().name())) { - throw new LayoutCompilationException(lenientFormat( - "ambiguous schema reference: '%s:%s'", up.name(), up.schemaId() - )); - } - } - - if (!udtSchema.isPresent()) { - throw new LayoutCompilationException(lenientFormat( - "cannot resolve schema reference '%s:%s'", up.name(), up.schemaId() - )); - } - - typeArgs.set(new TypeArgumentList(udtSchema.get().schemaId())); - return immutable ? LayoutTypes.IMMUTABLE_UDT : LayoutTypes.UDT; - } - default: - throw new LayoutCompilationException(Strings.lenientFormat( - "Unknown property type: %s", logicalType.type() - )); - } - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.azure.data.cosmos.serialization.hybridrow.schemas.ArrayPropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.MapPropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.Namespace; +import com.azure.data.cosmos.serialization.hybridrow.schemas.ObjectPropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.PrimitivePropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.Property; +import com.azure.data.cosmos.serialization.hybridrow.schemas.PropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.Schema; +import com.azure.data.cosmos.serialization.hybridrow.schemas.ScopePropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.SetPropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.TaggedPropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.TuplePropertyType; +import com.azure.data.cosmos.serialization.hybridrow.schemas.TypeKind; +import com.azure.data.cosmos.serialization.hybridrow.schemas.UdtPropertyType; +import com.google.common.base.Strings; +import org.checkerframework.checker.index.qual.NonNegative; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.lenientFormat; + +/** + * Converts a logical schema into a physical layout. + */ +public final class LayoutCompiler { + /** + * Compiles a logical schema into a physical layout that can be used to read and write rows + * + * @param namespace The namespace within which {@code schema} is defined + * @param schema The logical schema to produce a layout for + * @return The layout for the schema + */ + @Nonnull + public static Layout compile(@Nonnull final Namespace namespace, @Nonnull final Schema schema) { + + checkNotNull(namespace, "expected non-null namespace"); + checkNotNull(schema, "expected non-null schema"); + checkArgument(schema.type() == TypeKind.SCHEMA); + checkArgument(!Strings.isNullOrEmpty(schema.name())); + checkArgument(namespace.schemas().contains(schema)); + + LayoutBuilder builder = new LayoutBuilder(schema.name(), schema.schemaId()); + LayoutCompiler.addProperties(builder, namespace, LayoutCode.SCHEMA, schema.properties()); + + return builder.build(); + } + + private static void addProperties( + @Nonnull final LayoutBuilder builder, + @Nonnull final Namespace namespace, + @Nonnull LayoutCode layoutCode, + @Nonnull List properties) { + + final Out typeArgs = new Out<>(); + + for (Property p : properties) { + + LayoutType type = LayoutCompiler.logicalToPhysicalType(namespace, p.type(), typeArgs); + + switch (LayoutCodeTraits.clearImmutableBit(type.layoutCode())) { + + case OBJECT_SCOPE: { + if (!p.type().nullable()) { + throw new LayoutCompilationException("Non-nullable sparse column are not supported."); + } + ObjectPropertyType op = (ObjectPropertyType)p.type(); + builder.addObjectScope(p.path(), type); + LayoutCompiler.addProperties(builder, namespace, type.layoutCode(), op.properties()); + builder.EndObjectScope(); + break; + } + + case ARRAY_SCOPE: + case TYPED_ARRAY_SCOPE: + case SET_SCOPE: + case TYPED_SET_SCOPE: + case MAP_SCOPE: + case TYPED_MAP_SCOPE: + case TUPLE_SCOPE: + case TYPED_TUPLE_SCOPE: + case TAGGED_SCOPE: + case TAGGED2_SCOPE: + case SCHEMA: { + if (!p.type().nullable()) { + throw new LayoutCompilationException("Non-nullable sparse column are not supported."); + } + builder.addTypedScope(p.path(), type, typeArgs.get()); + break; + } + + case NULLABLE_SCOPE: { + throw new LayoutCompilationException("Nullables cannot be explicitly declared as columns."); + } + + default: { + + if (p.type() instanceof PrimitivePropertyType) { + + PrimitivePropertyType pp = (PrimitivePropertyType) p.type(); + + switch (pp.storage()) { + + case FIXED: + if (LayoutCodeTraits.clearImmutableBit(layoutCode) != LayoutCode.SCHEMA) { + throw new LayoutCompilationException( + "Cannot have fixed storage within a sparse layoutCode."); + } + if (type.isNull() && !pp.nullable()) { + throw new LayoutCompilationException( + "Non-nullable null columns are not supported."); + } + builder.addFixedColumn(p.path(), type, pp.nullable(), pp.length()); + break; + + case VARIABLE: + if (LayoutCodeTraits.clearImmutableBit(layoutCode) != LayoutCode.SCHEMA) { + throw new LayoutCompilationException( + "Cannot have variable storage within a sparse layoutCode."); + } + if (!pp.nullable()) { + throw new LayoutCompilationException( + "Non-nullable variable columns are not supported."); + } + builder.addVariableColumn(p.path(), type, pp.length()); + break; + + case SPARSE: + if (!pp.nullable()) { + throw new LayoutCompilationException( + "Non-nullable sparse columns are not supported."); + } + builder.addSparseColumn(p.path(), type); + break; + + default: + throw new LayoutCompilationException( + lenientFormat("Unknown storage specification: %s", pp.storage())); + } + } else { + throw new LayoutCompilationException( + lenientFormat("Unknown property type: %s", type.name())); + } + + break; + } + } + } + } + + private static LayoutType logicalToPhysicalType( + Namespace namespace, PropertyType logicalType, Out typeArgs) { + + typeArgs.set(TypeArgumentList.EMPTY); + boolean immutable = logicalType instanceof ScopePropertyType && ((ScopePropertyType) logicalType).immutable(); + + switch (logicalType.type()) { + + case NULL: + return LayoutTypes.NULL; + + case BOOLEAN: + return LayoutTypes.BOOLEAN; + + case INT_8: + return LayoutTypes.INT_8; + + case INT_16: + return LayoutTypes.INT_16; + + case INT_32: + return LayoutTypes.INT_32; + + case INT_64: + return LayoutTypes.INT_64; + + case UINT_8: + return LayoutTypes.UINT_8; + + case UINT_16: + return LayoutTypes.UINT_16; + + case UINT_32: + return LayoutTypes.UINT_32; + + case UINT_64: + return LayoutTypes.UINT_64; + + case FLOAT_32: + return LayoutTypes.FLOAT_32; + + case FLOAT_64: + return LayoutTypes.FLOAT_64; + + case FLOAT_128: + return LayoutTypes.FLOAT_128; + + case DECIMAL: + return LayoutTypes.DECIMAL; + + case DATE_TIME: + return LayoutTypes.DATE_TIME; + + case UNIX_DATE_TIME: + return LayoutTypes.UNIX_DATE_TIME; + + case GUID: + return LayoutTypes.GUID; + + case MONGODB_OBJECT_ID: + throw new UnsupportedOperationException(); + // return LayoutTypes.MONGO_DB_OBJECT_ID; + + case UTF_8: + return LayoutTypes.UTF_8; + + case BINARY: + return LayoutTypes.BINARY; + + case VAR_INT: + return LayoutTypes.VAR_INT; + + case VAR_UINT: + return LayoutTypes.VAR_UINT; + + case OBJECT: + return immutable ? LayoutTypes.IMMUTABLE_OBJECT : LayoutTypes.OBJECT; + + case ARRAY: { + + assert logicalType instanceof ArrayPropertyType; + ArrayPropertyType ap = (ArrayPropertyType) logicalType; + + if (ap.items() != null && (ap.items().type() != TypeKind.ANY)) { + + final Out out = new Out<>(); + + LayoutType itemType = LayoutCompiler.logicalToPhysicalType(namespace, ap.items(), out); + TypeArgumentList itemTypeArgs = out.get(); + + if (ap.items().nullable()) { + itemTypeArgs = new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs)); + itemType = itemType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; + } + + typeArgs.set(new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs))); + return immutable ? LayoutTypes.IMMUTABLE_TYPED_ARRAY : LayoutTypes.TYPED_ARRAY; + } + + return immutable ? LayoutTypes.IMMUTABLE_ARRAY : LayoutTypes.ARRAY; + } + case SET: { + + assert logicalType instanceof SetPropertyType; + SetPropertyType sp = (SetPropertyType) logicalType; + + if ((sp.items() != null) && (sp.items().type() != TypeKind.ANY)) { + + final Out out = new Out<>(); + + LayoutType itemType = LayoutCompiler.logicalToPhysicalType(namespace, sp.items(), out); + TypeArgumentList itemTypeArgs = out.get(); + + if (sp.items().nullable()) { + itemTypeArgs = new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs)); + itemType = itemType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; + } + + typeArgs.set(new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs))); + return immutable ? LayoutTypes.IMMUTABLE_TYPED_SET : LayoutTypes.TYPED_SET; + } + + // TODO(283638): implement sparse set + + throw new LayoutCompilationException(lenientFormat( + "Unknown property type: %s", + logicalType.type() + )); + } + case MAP: { + + assert logicalType instanceof MapPropertyType; + MapPropertyType mp = (MapPropertyType) logicalType; + + if (mp.keys() != null && (mp.keys().type() != TypeKind.ANY) && (mp.values() != null) && (mp.values().type() != TypeKind.ANY)) { + + final Out out = new Out<>(); + + LayoutType keyType = LayoutCompiler.logicalToPhysicalType(namespace, mp.keys(), out); + TypeArgumentList keyTypeArgs = out.get(); + + if (mp.keys().nullable()) { + keyTypeArgs = new TypeArgumentList(new TypeArgument(keyType, keyTypeArgs)); + keyType = keyType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; + } + + LayoutType valueType = LayoutCompiler.logicalToPhysicalType(namespace, mp.values(), out); + TypeArgumentList valueTypeArgs = out.get(); + + if (mp.values().nullable()) { + valueTypeArgs = new TypeArgumentList(new TypeArgument(valueType, valueTypeArgs)); + valueType = valueType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; + } + + typeArgs.set(new TypeArgumentList( + new TypeArgument(keyType, keyTypeArgs), + new TypeArgument(valueType, valueTypeArgs) + )); + + return immutable ? LayoutTypes.IMMUTABLE_TYPED_MAP : LayoutTypes.TYPED_MAP; + } + + // TODO(283638): implement sparse map + + throw new LayoutCompilationException(lenientFormat( + "Unknown property type: %s", logicalType.type()) + ); + } + case TUPLE: { + + assert logicalType instanceof TuplePropertyType; + final TuplePropertyType tp = (TuplePropertyType) logicalType; + + final TypeArgument[] args = new TypeArgument[tp.items().size()]; + final Out out = new Out<>(); + + for (int i = 0; i < tp.items().size(); i++) { + + LayoutType itemType = LayoutCompiler.logicalToPhysicalType(namespace, tp.items().get(i), out); + TypeArgumentList itemTypeArgs = out.get(); + + if (tp.items().get(i).nullable()) { + itemTypeArgs = new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs)); + itemType = itemType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; + } + + args[i] = new TypeArgument(itemType, itemTypeArgs); + } + + typeArgs.setAndGet(new TypeArgumentList(args)); + return immutable ? LayoutTypes.IMMUTABLE_TYPED_TUPLE : LayoutTypes.TYPED_TUPLE; + } + case TAGGED: { + + assert logicalType instanceof TaggedPropertyType; + TaggedPropertyType tg = (TaggedPropertyType) logicalType; + + if (tg.items().size() < TaggedPropertyType.MIN_TAGGED_ARGUMENTS || (tg.items().size() > TaggedPropertyType.MAX_TAGGED_ARGUMENTS)) { + throw new LayoutCompilationException(lenientFormat( + "Invalid number of arguments in Tagged: %s <= %s <= %s", + TaggedPropertyType.MIN_TAGGED_ARGUMENTS, + tg.items().size(), + TaggedPropertyType.MAX_TAGGED_ARGUMENTS + )); + } + + final Out out = new Out<>(); + final TypeArgument[] tgArgs = new TypeArgument[tg.items().size() + 1]; + + tgArgs[0] = new TypeArgument(LayoutTypes.UINT_8, TypeArgumentList.EMPTY); + + for (int i = 0; i < tg.items().size(); i++) { + + LayoutType itemType = LayoutCompiler.logicalToPhysicalType(namespace, tg.items().get(i), out); + TypeArgumentList itemTypeArgs = out.get(); + + if (tg.items().get(i).nullable()) { + itemTypeArgs = new TypeArgumentList(new TypeArgument(itemType, itemTypeArgs)); + itemType = itemType.isImmutable() ? LayoutTypes.IMMUTABLE_NULLABLE : LayoutTypes.NULLABLE; + } + + tgArgs[i + 1] = new TypeArgument(itemType, itemTypeArgs); + } + + typeArgs.set(new TypeArgumentList(tgArgs)); + + switch (tg.items().size()) { + case 1: + return immutable ? LayoutTypes.IMMUTABLE_TAGGED : LayoutTypes.TAGGED; + case 2: + return immutable ? LayoutTypes.IMMUTABLE_TAGGED_2 : LayoutTypes.TAGGED_2; + default: + throw new LayoutCompilationException("Unexpected tagged arity"); + } + } + case SCHEMA: { + + assert logicalType instanceof UdtPropertyType; + UdtPropertyType up = (UdtPropertyType) logicalType; + + final Optional udtSchema; + + if (up.schemaId() == SchemaId.NONE) { + udtSchema = namespace.schemas().stream() + .filter(schema -> up.name().equals(schema.name())) + .findFirst(); + } else { + udtSchema = namespace.schemas().stream() + .filter(schema -> up.schemaId().equals(schema.schemaId())) + .findFirst(); + if (udtSchema.isPresent() && !up.name().equals(udtSchema.get().name())) { + throw new LayoutCompilationException(lenientFormat( + "ambiguous schema reference: '%s:%s'", up.name(), up.schemaId() + )); + } + } + + if (!udtSchema.isPresent()) { + throw new LayoutCompilationException(lenientFormat( + "cannot resolve schema reference '%s:%s'", up.name(), up.schemaId() + )); + } + + typeArgs.set(new TypeArgumentList(udtSchema.get().schemaId())); + return immutable ? LayoutTypes.IMMUTABLE_UDT : LayoutTypes.UDT; + } + default: + throw new LayoutCompilationException(Strings.lenientFormat( + "Unknown property type: %s", logicalType.type() + )); + } + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDateTime.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDateTime.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDateTime.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDateTime.java index 533e1b9..27c333a 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDateTime.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDateTime.java @@ -1,96 +1,96 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.azure.data.cosmos.serialization.hybridrow.codecs.DateTimeCodec; - -import javax.annotation.Nonnull; -import java.time.OffsetDateTime; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutDateTime extends LayoutTypePrimitive { - - public LayoutDateTime() { - super(LayoutCode.DATE_TIME, DateTimeCodec.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "datetime"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(OffsetDateTime.MIN); - return Result.NOT_FOUND; - } - - value.set(buffer.readDateTime(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(OffsetDateTime.MIN); - return result; - } - - value.set(buffer.readSparseDateTime(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull OffsetDateTime value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeDateTime(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull OffsetDateTime value, @Nonnull UpdateOptions options) { - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseDateTime(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull OffsetDateTime value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.azure.data.cosmos.serialization.hybridrow.codecs.DateTimeCodec; + +import javax.annotation.Nonnull; +import java.time.OffsetDateTime; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutDateTime extends LayoutTypePrimitive { + + public LayoutDateTime() { + super(LayoutCode.DATE_TIME, DateTimeCodec.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "datetime"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(OffsetDateTime.MIN); + return Result.NOT_FOUND; + } + + value.set(buffer.readDateTime(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(OffsetDateTime.MIN); + return result; + } + + value.set(buffer.readSparseDateTime(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull OffsetDateTime value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeDateTime(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull OffsetDateTime value, @Nonnull UpdateOptions options) { + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseDateTime(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull OffsetDateTime value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDecimal.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDecimal.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDecimal.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDecimal.java index bb64aff..92b7017 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDecimal.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutDecimal.java @@ -1,92 +1,92 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.azure.data.cosmos.serialization.hybridrow.codecs.DecimalCodec; - -import javax.annotation.Nonnull; -import java.math.BigDecimal; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutDecimal extends LayoutTypePrimitive { - - public LayoutDecimal() { - super(LayoutCode.DECIMAL, DecimalCodec.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "decimal"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.setAndGet(new BigDecimal(0)); - return Result.NOT_FOUND; - } - - value.setAndGet(buffer.readDecimal(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, - @Nonnull Out value) { - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - if (result != Result.SUCCESS) { - value.setAndGet(new BigDecimal(0)); - return result; - } - - value.setAndGet(buffer.readSparseDecimal(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, - @Nonnull BigDecimal value) { - checkArgument(scope.scopeType() instanceof LayoutUDT); - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeDecimal(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull BigDecimal value, @Nonnull UpdateOptions options) { - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseDecimal(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull BigDecimal value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.azure.data.cosmos.serialization.hybridrow.codecs.DecimalCodec; + +import javax.annotation.Nonnull; +import java.math.BigDecimal; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutDecimal extends LayoutTypePrimitive { + + public LayoutDecimal() { + super(LayoutCode.DECIMAL, DecimalCodec.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "decimal"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.setAndGet(new BigDecimal(0)); + return Result.NOT_FOUND; + } + + value.setAndGet(buffer.readDecimal(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, + @Nonnull Out value) { + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + if (result != Result.SUCCESS) { + value.setAndGet(new BigDecimal(0)); + return result; + } + + value.setAndGet(buffer.readSparseDecimal(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, + @Nonnull BigDecimal value) { + checkArgument(scope.scopeType() instanceof LayoutUDT); + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeDecimal(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull BigDecimal value, @Nonnull UpdateOptions options) { + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseDecimal(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull BigDecimal value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutEndScope.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutEndScope.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutEndScope.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutEndScope.java index 3e89f8f..2127e27 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutEndScope.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutEndScope.java @@ -1,48 +1,48 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -public final class LayoutEndScope extends LayoutTypeScope { - - public LayoutEndScope() { - super(LayoutCode.END_SCOPE, false, false, false, false, false, false); - } - - @Nonnull - public String name() { - return "end"; - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final Out value) { - return this.writeScope(buffer, scope, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - @Nonnull final Out value) { - - assert false : "cannot write an EndScope directly"; - value.set(null); - - return Result.FAILURE; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +public final class LayoutEndScope extends LayoutTypeScope { + + public LayoutEndScope() { + super(LayoutCode.END_SCOPE, false, false, false, false, false, false); + } + + @Nonnull + public String name() { + return "end"; + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final Out value) { + return this.writeScope(buffer, scope, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + @Nonnull final Out value) { + + assert false : "cannot write an EndScope directly"; + value.set(null); + + return Result.FAILURE; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat128.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat128.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat128.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat128.java index 9f5e88d..675220c 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat128.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat128.java @@ -1,95 +1,95 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Float128; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutFloat128 extends LayoutTypePrimitive { - - public LayoutFloat128() { - super(LayoutCode.FLOAT_128, Float128.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "float128"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.setAndGet(null); - return Result.NOT_FOUND; - } - - value.setAndGet(buffer.readFloat128(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.setAndGet(null); - return result; - } - - value.setAndGet(buffer.readSparseFloat128(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Float128 value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeFloat128(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Float128 value, @Nonnull UpdateOptions options) { - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseFloat128(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Float128 value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Float128; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutFloat128 extends LayoutTypePrimitive { + + public LayoutFloat128() { + super(LayoutCode.FLOAT_128, Float128.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "float128"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.setAndGet(null); + return Result.NOT_FOUND; + } + + value.setAndGet(buffer.readFloat128(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.setAndGet(null); + return result; + } + + value.setAndGet(buffer.readSparseFloat128(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Float128 value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeFloat128(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Float128 value, @Nonnull UpdateOptions options) { + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseFloat128(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Float128 value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat32.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat32.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat32.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat32.java index 7da5f10..c27fcd7 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat32.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat32.java @@ -1,94 +1,94 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutFloat32 extends LayoutTypePrimitive { - - public LayoutFloat32() { - super(LayoutCode.FLOAT_32, Float.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "float32"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0F); - return Result.NOT_FOUND; - } - - value.set(buffer.readFloat32(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0F); - return result; - } - - value.set(buffer.readSparseFloat32(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Float value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeFloat32(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Float value, @Nonnull UpdateOptions options) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseFloat32(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Float value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutFloat32 extends LayoutTypePrimitive { + + public LayoutFloat32() { + super(LayoutCode.FLOAT_32, Float.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "float32"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0F); + return Result.NOT_FOUND; + } + + value.set(buffer.readFloat32(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0F); + return result; + } + + value.set(buffer.readSparseFloat32(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Float value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeFloat32(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Float value, @Nonnull UpdateOptions options) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseFloat32(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Float value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java index 6cfb1ca..37793c7 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java @@ -1,97 +1,97 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutFloat64 extends LayoutTypePrimitive { - - public LayoutFloat64() { - super(LayoutCode.FLOAT_64, Double.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "float64"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0D); - return Result.NOT_FOUND; - } - - value.set(buffer.readFloat64(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0D); - return result; - } - - value.set(buffer.readSparseFloat64(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn col, @Nonnull Double value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeFloat64(scope.start() + col.offset(), value); - buffer.setBit(scope.start(), col.nullBit()); - return Result.SUCCESS; - } - - //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: - //ORIGINAL LINE: public override Result WriteSparse(ref RowBuffer b, ref RowCursor edit, double value, - // UpdateOptions options = UpdateOptions.Upsert) - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Double value, @Nonnull UpdateOptions options) { - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseFloat64(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Double value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutFloat64 extends LayoutTypePrimitive { + + public LayoutFloat64() { + super(LayoutCode.FLOAT_64, Double.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "float64"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0D); + return Result.NOT_FOUND; + } + + value.set(buffer.readFloat64(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0D); + return result; + } + + value.set(buffer.readSparseFloat64(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn col, @Nonnull Double value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeFloat64(scope.start() + col.offset(), value); + buffer.setBit(scope.start(), col.nullBit()); + return Result.SUCCESS; + } + + //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: + //ORIGINAL LINE: public override Result WriteSparse(ref RowBuffer b, ref RowCursor edit, double value, + // UpdateOptions options = UpdateOptions.Upsert) + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Double value, @Nonnull UpdateOptions options) { + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseFloat64(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Double value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutGuid.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutGuid.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutGuid.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutGuid.java index dbbd165..d7327fd 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutGuid.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutGuid.java @@ -1,96 +1,96 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.azure.data.cosmos.serialization.hybridrow.codecs.GuidCodec; - -import javax.annotation.Nonnull; -import java.util.UUID; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutGuid extends LayoutTypePrimitive { - - public LayoutGuid() { - super(LayoutCode.GUID, GuidCodec.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "guid"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(null); - return Result.NOT_FOUND; - } - - value.set(buffer.readGuid(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.readSparseGuid(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull UUID value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeGuid(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull UUID value, @Nonnull UpdateOptions options) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseGuid(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull UUID value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.azure.data.cosmos.serialization.hybridrow.codecs.GuidCodec; + +import javax.annotation.Nonnull; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutGuid extends LayoutTypePrimitive { + + public LayoutGuid() { + super(LayoutCode.GUID, GuidCodec.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "guid"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(null); + return Result.NOT_FOUND; + } + + value.set(buffer.readGuid(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.readSparseGuid(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull UUID value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeGuid(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull UUID value, @Nonnull UpdateOptions options) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseGuid(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull UUID value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutIndexedScope.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutIndexedScope.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutIndexedScope.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutIndexedScope.java index ff14a51..65e4662 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutIndexedScope.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutIndexedScope.java @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkNotNull; - -public abstract class LayoutIndexedScope extends LayoutTypeScope { - - protected LayoutIndexedScope( - @Nonnull final LayoutCode code, - final boolean immutable, - final boolean isSizedScope, - final boolean isFixedArity, - final boolean isUniqueScope, - final boolean isTypedScope) { - super(code, immutable, isSizedScope, true, isFixedArity, isUniqueScope, isTypedScope); - } - - @Override - public void readSparsePath(@Nonnull final RowBuffer buffer, @Nonnull final RowCursor edit) { - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - edit.pathToken(0); - edit.pathOffset(0); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; + +public abstract class LayoutIndexedScope extends LayoutTypeScope { + + protected LayoutIndexedScope( + @Nonnull final LayoutCode code, + final boolean immutable, + final boolean isSizedScope, + final boolean isFixedArity, + final boolean isUniqueScope, + final boolean isTypedScope) { + super(code, immutable, isSizedScope, true, isFixedArity, isUniqueScope, isTypedScope); + } + + @Override + public void readSparsePath(@Nonnull final RowBuffer buffer, @Nonnull final RowCursor edit) { + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + edit.pathToken(0); + edit.pathOffset(0); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt16.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt16.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt16.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt16.java index 9f806e4..196b264 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt16.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt16.java @@ -1,94 +1,94 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutInt16 extends LayoutTypePrimitive { - - public LayoutInt16() { - super(LayoutCode.INT_16, Short.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "int16"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set((short) 0); - return Result.NOT_FOUND; - } - - value.set(buffer.readInt16(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set((short) 0); - return result; - } - - value.set(buffer.readSparseInt16(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Short value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeInt16(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Short value, @Nonnull UpdateOptions options) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseInt16(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Short value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutInt16 extends LayoutTypePrimitive { + + public LayoutInt16() { + super(LayoutCode.INT_16, Short.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "int16"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set((short) 0); + return Result.NOT_FOUND; + } + + value.set(buffer.readInt16(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set((short) 0); + return result; + } + + value.set(buffer.readSparseInt16(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Short value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeInt16(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Short value, @Nonnull UpdateOptions options) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseInt16(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Short value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt32.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt32.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt32.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt32.java index 83c5578..4a3e489 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt32.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt32.java @@ -1,94 +1,94 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutInt32 extends LayoutTypePrimitive { - - public LayoutInt32() { - super(LayoutCode.INT_32, Integer.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "int32"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0); - return Result.NOT_FOUND; - } - - value.set(buffer.readInt32(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0); - return result; - } - - value.set(buffer.readSparseInt32(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Integer value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeInt32(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Integer value, @Nonnull UpdateOptions options) { - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseInt32(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Integer value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutInt32 extends LayoutTypePrimitive { + + public LayoutInt32() { + super(LayoutCode.INT_32, Integer.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "int32"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0); + return Result.NOT_FOUND; + } + + value.set(buffer.readInt32(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0); + return result; + } + + value.set(buffer.readSparseInt32(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Integer value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeInt32(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Integer value, @Nonnull UpdateOptions options) { + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseInt32(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Integer value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java index 815a97d..2a7d3d8 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java @@ -1,93 +1,93 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutInt64 extends LayoutTypePrimitive { - - public LayoutInt64() { - super(LayoutCode.INT_64, Long.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "int64"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0L); - return Result.NOT_FOUND; - } - - value.set(buffer.readInt64(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0L); - return result; - } - - value.set(buffer.readSparseInt64(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeInt64(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseInt64(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutInt64 extends LayoutTypePrimitive { + + public LayoutInt64() { + super(LayoutCode.INT_64, Long.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "int64"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0L); + return Result.NOT_FOUND; + } + + value.set(buffer.readInt64(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0L); + return result; + } + + value.set(buffer.readSparseInt64(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeInt64(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseInt64(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt8.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt8.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt8.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt8.java index cf49e1f..4c4d31f 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt8.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt8.java @@ -1,94 +1,94 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutInt8 extends LayoutTypePrimitive { - - public LayoutInt8() { - super(LayoutCode.INT_8, Byte.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "int8"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set((byte) 0); - return Result.NOT_FOUND; - } - - value.set(buffer.readInt8(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set((byte) 0); - return result; - } - - value.set(buffer.readSparseInt8(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Byte value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeInt8(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Byte value, @Nonnull UpdateOptions options) { - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseInt8(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Byte value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutInt8 extends LayoutTypePrimitive { + + public LayoutInt8() { + super(LayoutCode.INT_8, Byte.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "int8"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set((byte) 0); + return Result.NOT_FOUND; + } + + value.set(buffer.readInt8(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set((byte) 0); + return result; + } + + value.set(buffer.readSparseInt8(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Byte value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeInt8(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Byte value, @Nonnull UpdateOptions options) { + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseInt8(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Byte value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutListReadable.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutListReadable.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutListReadable.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutListReadable.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutListWritable.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutListWritable.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutListWritable.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutListWritable.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutMongoDbObjectId.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutMongoDbObjectId.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutMongoDbObjectId.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutMongoDbObjectId.java index 8652838..b1ba46a 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutMongoDbObjectId.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutMongoDbObjectId.java @@ -1,88 +1,88 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import javax.annotation.Nonnull; - -public final class LayoutMongoDbObjectId extends LayoutType/**/ { - - public LayoutMongoDbObjectId() { - super(LayoutCode.MONGODB_OBJECT_ID, 0/*MongoDbObjectId.Size*/); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "mongodbobjectid"; - } - - // TODO: DANOBLE: Ressurect this class implementation -// @Override -// @Nonnull -// public Result readFixed( -// RowBuffer buffer, RowCursor scope, LayoutColumn column, Out value) { -// checkArgument(scope.get().scopeType() instanceof LayoutUDT); -// if (!buffer.get().readBit(scope.get().start(), column.getNullBit().clone())) { -// value.setAndGet(null); -// return Result.NOT_FOUND; -// } -// -// value.setAndGet(buffer.get().ReadMongoDbObjectId(scope.get().start() + column.getOffset()).clone()); -// return Result.SUCCESS; -// } -// -// @Override -// @Nonnull -// public Result readSparse(RowBuffer buffer, RowCursor edit, -// Out value) { -// Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); -// if (result != Result.SUCCESS) { -// value.setAndGet(null); -// return result; -// } -// -// value.setAndGet(buffer.get().ReadSparseMongoDbObjectId(edit).clone()); -// return Result.SUCCESS; -// } -// -// @Override -// @Nonnull -// public Result writeFixed(RowBuffer buffer, RowCursor scope, LayoutColumn column, -// MongoDbObjectId value) { -// checkArgument(scope.get().scopeType() instanceof LayoutUDT); -// if (scope.get().immutable()) { -// return Result.INSUFFICIENT_PERMISSIONS; -// } -// -// buffer.get().WriteMongoDbObjectId(scope.get().start() + column.getOffset(), value.clone()); -// buffer.get().SetBit(scope.get().start(), column.getNullBit().clone()); -// return Result.SUCCESS; -// } -// -// //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: -// //ORIGINAL LINE: public override Result WriteSparse(ref RowBuffer b, ref RowCursor edit, MongoDbObjectId value, -// // UpdateOptions options = UpdateOptions.Upsert) -// @Override -// @Nonnull -// public Result writeSparse(RowBuffer buffer, RowCursor edit, -// MongoDbObjectId value, UpdateOptions options) { -// Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg().clone(), options); -// if (result != Result.SUCCESS) { -// return result; -// } -// -// buffer.get().WriteSparseMongoDbObjectId(edit, value.clone(), options); -// return Result.SUCCESS; -// } -// -// @Override -// @Nonnull -// public Result writeSparse(RowBuffer buffer, RowCursor edit, -// MongoDbObjectId value) { -// return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); -// } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import javax.annotation.Nonnull; + +public final class LayoutMongoDbObjectId extends LayoutType/**/ { + + public LayoutMongoDbObjectId() { + super(LayoutCode.MONGODB_OBJECT_ID, 0/*MongoDbObjectId.Size*/); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "mongodbobjectid"; + } + + // TODO: DANOBLE: Ressurect this class implementation +// @Override +// @Nonnull +// public Result readFixed( +// RowBuffer buffer, RowCursor scope, LayoutColumn column, Out value) { +// checkArgument(scope.get().scopeType() instanceof LayoutUDT); +// if (!buffer.get().readBit(scope.get().start(), column.getNullBit().clone())) { +// value.setAndGet(null); +// return Result.NOT_FOUND; +// } +// +// value.setAndGet(buffer.get().ReadMongoDbObjectId(scope.get().start() + column.getOffset()).clone()); +// return Result.SUCCESS; +// } +// +// @Override +// @Nonnull +// public Result readSparse(RowBuffer buffer, RowCursor edit, +// Out value) { +// Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); +// if (result != Result.SUCCESS) { +// value.setAndGet(null); +// return result; +// } +// +// value.setAndGet(buffer.get().ReadSparseMongoDbObjectId(edit).clone()); +// return Result.SUCCESS; +// } +// +// @Override +// @Nonnull +// public Result writeFixed(RowBuffer buffer, RowCursor scope, LayoutColumn column, +// MongoDbObjectId value) { +// checkArgument(scope.get().scopeType() instanceof LayoutUDT); +// if (scope.get().immutable()) { +// return Result.INSUFFICIENT_PERMISSIONS; +// } +// +// buffer.get().WriteMongoDbObjectId(scope.get().start() + column.getOffset(), value.clone()); +// buffer.get().SetBit(scope.get().start(), column.getNullBit().clone()); +// return Result.SUCCESS; +// } +// +// //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: +// //ORIGINAL LINE: public override Result WriteSparse(ref RowBuffer b, ref RowCursor edit, MongoDbObjectId value, +// // UpdateOptions options = UpdateOptions.Upsert) +// @Override +// @Nonnull +// public Result writeSparse(RowBuffer buffer, RowCursor edit, +// MongoDbObjectId value, UpdateOptions options) { +// Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg().clone(), options); +// if (result != Result.SUCCESS) { +// return result; +// } +// +// buffer.get().WriteSparseMongoDbObjectId(edit, value.clone(), options); +// return Result.SUCCESS; +// } +// +// @Override +// @Nonnull +// public Result writeSparse(RowBuffer buffer, RowCursor edit, +// MongoDbObjectId value) { +// return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); +// } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNull.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNull.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNull.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNull.java index 3a22ff2..d047de4 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNull.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNull.java @@ -1,85 +1,85 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.NullValue; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutNull extends LayoutTypePrimitive implements ILayoutType { - - public LayoutNull() { - super(LayoutCode.NULL, 0); - } - - public boolean isFixed() { - return true; - } - - public boolean isNull() { - return true; - } - - @Nonnull - public String name() { - return "null"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - checkArgument(scope.scopeType() instanceof LayoutUDT); - value.set(NullValue.DEFAULT); - if (!buffer.readBit(scope.start(), column.nullBit())) { - return Result.NOT_FOUND; - } - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - value.set(buffer.readSparseNull(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull NullValue value) { - checkArgument(scope.scopeType() instanceof LayoutUDT); - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull NullValue value, @Nonnull UpdateOptions options) { - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - if (result != Result.SUCCESS) { - return result; - } - buffer.writeSparseNull(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull NullValue value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.NullValue; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutNull extends LayoutTypePrimitive implements ILayoutType { + + public LayoutNull() { + super(LayoutCode.NULL, 0); + } + + public boolean isFixed() { + return true; + } + + public boolean isNull() { + return true; + } + + @Nonnull + public String name() { + return "null"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + checkArgument(scope.scopeType() instanceof LayoutUDT); + value.set(NullValue.DEFAULT); + if (!buffer.readBit(scope.start(), column.nullBit())) { + return Result.NOT_FOUND; + } + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + value.set(buffer.readSparseNull(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull NullValue value) { + checkArgument(scope.scopeType() instanceof LayoutUDT); + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull NullValue value, @Nonnull UpdateOptions options) { + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + if (result != Result.SUCCESS) { + return result; + } + buffer.writeSparseNull(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull NullValue value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNullable.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNullable.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNullable.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNullable.java index 73f6771..47eaf97 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNullable.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutNullable.java @@ -1,144 +1,144 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -public final class LayoutNullable extends LayoutIndexedScope { - - public LayoutNullable(boolean immutable) { - super( - immutable ? LayoutCode.IMMUTABLE_NULLABLE_SCOPE : LayoutCode.NULLABLE_SCOPE, immutable, - true, true, false, true - ); - } - - @Override - public int countTypeArgument(@Nonnull final TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - checkArgument(value.count() == 1); - return LayoutCode.BYTES + value.get(0).type().countTypeArgument(value.get(0).typeArgs()); - } - - @Override - @Nonnull - public String name() { - return this.isImmutable() ? "im_nullable" : "nullable"; - } - - @Override - public boolean hasImplicitTypeCode(@Nonnull final RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - checkArgument(edit.index() >= 0); - checkArgument(edit.scopeTypeArgs().count() == 1); - checkArgument(edit.index() == 1); - return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(0).type().layoutCode()); - } - - public static Result hasValue(@Nonnull final RowBuffer buffer, @Nonnull final RowCursor scope) { - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkArgument(scope.scopeType() instanceof LayoutNullable); - checkArgument(scope.index() == 1 || scope.index() == 2); - checkArgument(scope.scopeTypeArgs().count() == 1); - boolean hasValue = buffer.readInt8(scope.start()) != 0; - return hasValue ? Result.SUCCESS : Result.NOT_FOUND; - } - - @Nonnull - @Override - public TypeArgumentList readTypeArgumentList( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); - return new TypeArgumentList(LayoutType.readTypeArgument(buffer, offset, lengthInBytes)); - } - - @Override - public void setImplicitTypeCode(@Nonnull RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - checkState(edit.index() == 1); - edit.cellType(edit.scopeTypeArgs().get(0).type()); - edit.cellTypeArgs(edit.scopeTypeArgs().get(0).typeArgs()); - } - - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - boolean hasValue, - @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, hasValue, UpdateOptions.UPSERT, value); - } - - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - boolean hasValue, - @Nonnull final UpdateOptions options, - @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - checkNotNull(value, "expected non-null value"); - - Result result = LayoutType.prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - buffer.writeNullable(edit, this, typeArgs, options, hasValue); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, true, options, value); - } - - @Override - public int writeTypeArgument(@Nonnull final RowBuffer buffer, int offset, @Nonnull final TypeArgumentList value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(value, "expected non-null value"); - checkArgument(offset >= 0); - checkArgument(value.count() == 1); - - final TypeArgument typeArg = value.get(0); - buffer.writeSparseTypeCode(offset, this.layoutCode()); - return LayoutCode.BYTES + typeArg.type().writeTypeArgument(buffer, offset + LayoutCode.BYTES, typeArg.typeArgs()); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class LayoutNullable extends LayoutIndexedScope { + + public LayoutNullable(boolean immutable) { + super( + immutable ? LayoutCode.IMMUTABLE_NULLABLE_SCOPE : LayoutCode.NULLABLE_SCOPE, immutable, + true, true, false, true + ); + } + + @Override + public int countTypeArgument(@Nonnull final TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + checkArgument(value.count() == 1); + return LayoutCode.BYTES + value.get(0).type().countTypeArgument(value.get(0).typeArgs()); + } + + @Override + @Nonnull + public String name() { + return this.isImmutable() ? "im_nullable" : "nullable"; + } + + @Override + public boolean hasImplicitTypeCode(@Nonnull final RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + checkArgument(edit.index() >= 0); + checkArgument(edit.scopeTypeArgs().count() == 1); + checkArgument(edit.index() == 1); + return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(0).type().layoutCode()); + } + + public static Result hasValue(@Nonnull final RowBuffer buffer, @Nonnull final RowCursor scope) { + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkArgument(scope.scopeType() instanceof LayoutNullable); + checkArgument(scope.index() == 1 || scope.index() == 2); + checkArgument(scope.scopeTypeArgs().count() == 1); + boolean hasValue = buffer.readInt8(scope.start()) != 0; + return hasValue ? Result.SUCCESS : Result.NOT_FOUND; + } + + @Nonnull + @Override + public TypeArgumentList readTypeArgumentList( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); + return new TypeArgumentList(LayoutType.readTypeArgument(buffer, offset, lengthInBytes)); + } + + @Override + public void setImplicitTypeCode(@Nonnull RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + checkState(edit.index() == 1); + edit.cellType(edit.scopeTypeArgs().get(0).type()); + edit.cellTypeArgs(edit.scopeTypeArgs().get(0).typeArgs()); + } + + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + boolean hasValue, + @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, hasValue, UpdateOptions.UPSERT, value); + } + + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + boolean hasValue, + @Nonnull final UpdateOptions options, + @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + checkNotNull(value, "expected non-null value"); + + Result result = LayoutType.prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + buffer.writeNullable(edit, this, typeArgs, options, hasValue); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, true, options, value); + } + + @Override + public int writeTypeArgument(@Nonnull final RowBuffer buffer, int offset, @Nonnull final TypeArgumentList value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(value, "expected non-null value"); + checkArgument(offset >= 0); + checkArgument(value.count() == 1); + + final TypeArgument typeArg = value.get(0); + buffer.writeSparseTypeCode(offset, this.layoutCode()); + return LayoutCode.BYTES + typeArg.type().writeTypeArgument(buffer, offset + LayoutCode.BYTES, typeArg.typeArgs()); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutObject.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutObject.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutObject.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutObject.java index 7b265a5..7679cdd 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutObject.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutObject.java @@ -1,68 +1,68 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutObject extends LayoutPropertyScope { - - private TypeArgument typeArg; - - public LayoutObject(boolean immutable) { - super(immutable ? LayoutCode.IMMUTABLE_OBJECT_SCOPE : LayoutCode.OBJECT_SCOPE, immutable); - this.typeArg = new TypeArgument(this); - } - - @Override - @Nonnull - public String name() { - return this.isImmutable() ? "im_object" : "object"; - } - - public TypeArgument typeArg() { - return this.typeArg; - } - - - @Override - @Nonnull - public Result writeScope( - @Nonnull RowBuffer buffer, - @Nonnull RowCursor edit, - @Nonnull TypeArgumentList typeArgs, @Nonnull Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull RowBuffer buffer, - @Nonnull RowCursor edit, - @Nonnull TypeArgumentList typeArgs, - @Nonnull UpdateOptions options, @Nonnull Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - checkNotNull(value, "expected non-null value"); - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - value.setAndGet(null); - return result; - } - - value.set(buffer.writeSparseObject(edit, this, options)); - return Result.SUCCESS; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutObject extends LayoutPropertyScope { + + private TypeArgument typeArg; + + public LayoutObject(boolean immutable) { + super(immutable ? LayoutCode.IMMUTABLE_OBJECT_SCOPE : LayoutCode.OBJECT_SCOPE, immutable); + this.typeArg = new TypeArgument(this); + } + + @Override + @Nonnull + public String name() { + return this.isImmutable() ? "im_object" : "object"; + } + + public TypeArgument typeArg() { + return this.typeArg; + } + + + @Override + @Nonnull + public Result writeScope( + @Nonnull RowBuffer buffer, + @Nonnull RowCursor edit, + @Nonnull TypeArgumentList typeArgs, @Nonnull Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull RowBuffer buffer, + @Nonnull RowCursor edit, + @Nonnull TypeArgumentList typeArgs, + @Nonnull UpdateOptions options, @Nonnull Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + checkNotNull(value, "expected non-null value"); + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + value.setAndGet(null); + return result; + } + + value.set(buffer.writeSparseObject(edit, this, options)); + return Result.SUCCESS; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutPropertyScope.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutPropertyScope.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutPropertyScope.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutPropertyScope.java index c285886..66c6060 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutPropertyScope.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutPropertyScope.java @@ -1,10 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -public abstract class LayoutPropertyScope extends LayoutTypeScope { - protected LayoutPropertyScope(LayoutCode code, boolean immutable) { - super(code, immutable, false, false, false, false, false); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +public abstract class LayoutPropertyScope extends LayoutTypeScope { + protected LayoutPropertyScope(LayoutCode code, boolean immutable) { + super(code, immutable, false, false, false, false, false); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolver.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolver.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolver.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolver.java index 04b846d..42460b0 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolver.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolver.java @@ -1,13 +1,13 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; - -import javax.annotation.Nonnull; - -public abstract class LayoutResolver { - @Nonnull - public abstract Layout resolve(@Nonnull SchemaId schemaId); +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; + +import javax.annotation.Nonnull; + +public abstract class LayoutResolver { + @Nonnull + public abstract Layout resolve(@Nonnull SchemaId schemaId); } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverNamespace.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverNamespace.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverNamespace.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverNamespace.java index 4e8e96b..365c3f7 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverNamespace.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverNamespace.java @@ -1,64 +1,64 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.azure.data.cosmos.serialization.hybridrow.schemas.Namespace; -import com.azure.data.cosmos.serialization.hybridrow.schemas.Schema; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.concurrent.ConcurrentHashMap; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -/** - * An implementation of {@link LayoutResolver} which dynamically compiles schema from a {@link Namespace}. - *

- * This resolver assumes that {@link Schema} within the {@link Namespace} have their {@link Schema#schemaId()} properly - * populated. The resolver caches compiled schema. - *

- * All members of this class are multi-thread safe. - */ -public final class LayoutResolverNamespace extends LayoutResolver { - - private final ConcurrentHashMap layoutCache; - private final LayoutResolver parent; - private final Namespace schemaNamespace; - - public LayoutResolverNamespace(@Nonnull final Namespace namespace) { - this(namespace, null); - } - - public LayoutResolverNamespace(@Nonnull final Namespace schemaNamespace, @Nullable final LayoutResolver parent) { - checkNotNull(schemaNamespace, "expected non-null schemaNamespace"); - this.schemaNamespace = schemaNamespace; - this.parent = parent; - this.layoutCache = new ConcurrentHashMap<>(); - } - - public Namespace namespace() { - return this.schemaNamespace; - } - - @Nonnull - @Override - public Layout resolve(@Nonnull SchemaId schemaId) { - - checkNotNull(schemaId, "expected non-null schemaId"); - - Layout layout = this.layoutCache.computeIfAbsent(schemaId, id -> { - for (Schema schema : this.namespace().schemas()) { - if (schema.schemaId().equals(id)) { - return schema.compile(this.schemaNamespace); - } - } - return this.parent == null ? null : this.parent.resolve(schemaId); - }); - - checkState(layout != null, "failed to resolve schema %s", schemaId); - return layout; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.azure.data.cosmos.serialization.hybridrow.schemas.Namespace; +import com.azure.data.cosmos.serialization.hybridrow.schemas.Schema; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.concurrent.ConcurrentHashMap; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * An implementation of {@link LayoutResolver} which dynamically compiles schema from a {@link Namespace}. + *

+ * This resolver assumes that {@link Schema} within the {@link Namespace} have their {@link Schema#schemaId()} properly + * populated. The resolver caches compiled schema. + *

+ * All members of this class are multi-thread safe. + */ +public final class LayoutResolverNamespace extends LayoutResolver { + + private final ConcurrentHashMap layoutCache; + private final LayoutResolver parent; + private final Namespace schemaNamespace; + + public LayoutResolverNamespace(@Nonnull final Namespace namespace) { + this(namespace, null); + } + + public LayoutResolverNamespace(@Nonnull final Namespace schemaNamespace, @Nullable final LayoutResolver parent) { + checkNotNull(schemaNamespace, "expected non-null schemaNamespace"); + this.schemaNamespace = schemaNamespace; + this.parent = parent; + this.layoutCache = new ConcurrentHashMap<>(); + } + + public Namespace namespace() { + return this.schemaNamespace; + } + + @Nonnull + @Override + public Layout resolve(@Nonnull SchemaId schemaId) { + + checkNotNull(schemaId, "expected non-null schemaId"); + + Layout layout = this.layoutCache.computeIfAbsent(schemaId, id -> { + for (Schema schema : this.namespace().schemas()) { + if (schema.schemaId().equals(id)) { + return schema.compile(this.schemaNamespace); + } + } + return this.parent == null ? null : this.parent.resolve(schemaId); + }); + + checkState(layout != null, "failed to resolve schema %s", schemaId); + return layout; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverSimple.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverSimple.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverSimple.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverSimple.java index 41917e4..1312528 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverSimple.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutResolverSimple.java @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; - -import javax.annotation.Nonnull; -import java.util.function.Function; - -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutResolverSimple extends LayoutResolver { - - private Function resolver; - - public LayoutResolverSimple(Function resolver) { - this.resolver = resolver; - } - - @Nonnull - @Override - public Layout resolve(@Nonnull SchemaId schemaId) { - checkNotNull(schemaId, "expected non-null schemaId"); - return this.resolver.apply(schemaId); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; + +import javax.annotation.Nonnull; +import java.util.function.Function; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutResolverSimple extends LayoutResolver { + + private Function resolver; + + public LayoutResolverSimple(Function resolver) { + this.resolver = resolver; + } + + @Nonnull + @Override + public Layout resolve(@Nonnull SchemaId schemaId) { + checkNotNull(schemaId, "expected non-null schemaId"); + return this.resolver.apply(schemaId); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged.java index d48c12e..3fef76b 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged.java @@ -1,118 +1,118 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.IMMUTABLE_TAGGED_SCOPE; -import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.TAGGED_SCOPE; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutTagged extends LayoutIndexedScope { - - public LayoutTagged(boolean immutable) { - super(immutable ? IMMUTABLE_TAGGED_SCOPE : TAGGED_SCOPE, immutable, - true, true, false, true - ); - } - - @Override - public int countTypeArgument(@Nonnull final TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - checkArgument(value.count() == 2); - return LayoutCode.BYTES + value.get(1).type().countTypeArgument(value.get(1).typeArgs()); - } - - @Override - @Nonnull - public String name() { - return this.isImmutable() ? "im_tagged_t" : "tagged_t"; - } - - @Override - public boolean hasImplicitTypeCode(@Nonnull RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - checkArgument(edit.index() >= 0); - checkArgument(edit.scopeTypeArgs().count() > edit.index()); - return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(edit.index()).type().layoutCode()); - } - - @Override - @Nonnull - public TypeArgumentList readTypeArgumentList( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - return new TypeArgumentList( - new TypeArgument(LayoutTypes.UINT_8, TypeArgumentList.EMPTY), - readTypeArgument(buffer, offset, lengthInBytes) - ); - } - - @Override - public void setImplicitTypeCode(@Nonnull final RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - edit.cellType(edit.scopeTypeArgs().get(edit.index()).type()); - edit.cellTypeArgs(edit.scopeTypeArgs().get(edit.index()).typeArgs()); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, final @Nonnull Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - checkNotNull(value, "expected non-null value"); - - Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.writeTypedTuple(edit, this, typeArgs, options)); - return Result.SUCCESS; - } - - @Override - public int writeTypeArgument( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final TypeArgumentList value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(value, "expected non-null value"); - checkArgument(value.count() == 2); - - buffer.writeSparseTypeCode(offset, this.layoutCode()); - final TypeArgument typeArg = value.get(1); - int lengthInBytes = LayoutCode.BYTES; - lengthInBytes += typeArg.type().writeTypeArgument(buffer, offset + lengthInBytes, typeArg.typeArgs()); - - return lengthInBytes; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.IMMUTABLE_TAGGED_SCOPE; +import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.TAGGED_SCOPE; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutTagged extends LayoutIndexedScope { + + public LayoutTagged(boolean immutable) { + super(immutable ? IMMUTABLE_TAGGED_SCOPE : TAGGED_SCOPE, immutable, + true, true, false, true + ); + } + + @Override + public int countTypeArgument(@Nonnull final TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + checkArgument(value.count() == 2); + return LayoutCode.BYTES + value.get(1).type().countTypeArgument(value.get(1).typeArgs()); + } + + @Override + @Nonnull + public String name() { + return this.isImmutable() ? "im_tagged_t" : "tagged_t"; + } + + @Override + public boolean hasImplicitTypeCode(@Nonnull RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + checkArgument(edit.index() >= 0); + checkArgument(edit.scopeTypeArgs().count() > edit.index()); + return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(edit.index()).type().layoutCode()); + } + + @Override + @Nonnull + public TypeArgumentList readTypeArgumentList( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + return new TypeArgumentList( + new TypeArgument(LayoutTypes.UINT_8, TypeArgumentList.EMPTY), + readTypeArgument(buffer, offset, lengthInBytes) + ); + } + + @Override + public void setImplicitTypeCode(@Nonnull final RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + edit.cellType(edit.scopeTypeArgs().get(edit.index()).type()); + edit.cellTypeArgs(edit.scopeTypeArgs().get(edit.index()).typeArgs()); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, final @Nonnull Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + checkNotNull(value, "expected non-null value"); + + Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.writeTypedTuple(edit, this, typeArgs, options)); + return Result.SUCCESS; + } + + @Override + public int writeTypeArgument( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final TypeArgumentList value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(value, "expected non-null value"); + checkArgument(value.count() == 2); + + buffer.writeSparseTypeCode(offset, this.layoutCode()); + final TypeArgument typeArg = value.get(1); + int lengthInBytes = LayoutCode.BYTES; + lengthInBytes += typeArg.type().writeTypeArgument(buffer, offset + lengthInBytes, typeArg.typeArgs()); + + return lengthInBytes; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged2.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged2.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged2.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged2.java index fc321bc..087adad 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged2.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTagged2.java @@ -1,131 +1,131 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -public final class LayoutTagged2 extends LayoutIndexedScope { - - public LayoutTagged2(boolean immutable) { - super( - immutable ? LayoutCode.IMMUTABLE_TAGGED2_SCOPE : LayoutCode.TAGGED2_SCOPE, immutable, - true, true, false, true - ); - } - - @Override - public int countTypeArgument(@Nonnull TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - checkState(value.count() == 3); - return value.stream() - .map(arg -> arg.type().countTypeArgument(arg.typeArgs())) - .reduce(LayoutCode.BYTES, Integer::sum); - } - - @Override - public boolean hasImplicitTypeCode(@Nonnull RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - checkState(edit.index() >= 0); - checkState(edit.scopeTypeArgs().count() > edit.index()); - return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(edit.index()).type().layoutCode()); - } - - @Override - @Nonnull - public String name() { - return this.isImmutable() ? "im_tagged2_t" : "tagged2_t"; - } - - @Override - @Nonnull - public TypeArgumentList readTypeArgumentList( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - final TypeArgument[] typeArgs = new TypeArgument[] { - new TypeArgument(LayoutTypes.UINT_8, TypeArgumentList.EMPTY), - null, - null }; - - final Out len = new Out<>(); - int sum = 0; - - for (int i = 1; i < 3; i++) { - typeArgs[i] = readTypeArgument(buffer, offset + sum, len); - sum += len.get(); - } - - lengthInBytes.set(sum); - return new TypeArgumentList(typeArgs); - } - - @Override - public void setImplicitTypeCode(@Nonnull RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - edit.cellType(edit.scopeTypeArgs().get(edit.index()).type()); - edit.cellTypeArgs(edit.scopeTypeArgs().get(edit.index()).typeArgs()); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull RowBuffer buffer, - @Nonnull RowCursor edit, - @Nonnull TypeArgumentList typeArgs, @Nonnull Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull RowBuffer buffer, - @Nonnull RowCursor edit, - @Nonnull TypeArgumentList typeArgs, - @Nonnull UpdateOptions options, @Nonnull Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - checkNotNull(value, "expected non-null value"); - - Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.writeTypedTuple(edit, this, typeArgs, options)); - return Result.SUCCESS; - } - - @Override - public int writeTypeArgument(@Nonnull RowBuffer buffer, int offset, @Nonnull TypeArgumentList value) { - - checkState(value.count() == 3); - - buffer.writeSparseTypeCode(offset, this.layoutCode()); - int lengthInBytes = LayoutCode.BYTES; - - for (int i = 1; i < value.count(); i++) { - TypeArgument arg = value.get(i); - lengthInBytes += arg.type().writeTypeArgument(buffer, offset + lengthInBytes, arg.typeArgs()); - } - - return lengthInBytes; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class LayoutTagged2 extends LayoutIndexedScope { + + public LayoutTagged2(boolean immutable) { + super( + immutable ? LayoutCode.IMMUTABLE_TAGGED2_SCOPE : LayoutCode.TAGGED2_SCOPE, immutable, + true, true, false, true + ); + } + + @Override + public int countTypeArgument(@Nonnull TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + checkState(value.count() == 3); + return value.stream() + .map(arg -> arg.type().countTypeArgument(arg.typeArgs())) + .reduce(LayoutCode.BYTES, Integer::sum); + } + + @Override + public boolean hasImplicitTypeCode(@Nonnull RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + checkState(edit.index() >= 0); + checkState(edit.scopeTypeArgs().count() > edit.index()); + return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(edit.index()).type().layoutCode()); + } + + @Override + @Nonnull + public String name() { + return this.isImmutable() ? "im_tagged2_t" : "tagged2_t"; + } + + @Override + @Nonnull + public TypeArgumentList readTypeArgumentList( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + final TypeArgument[] typeArgs = new TypeArgument[] { + new TypeArgument(LayoutTypes.UINT_8, TypeArgumentList.EMPTY), + null, + null }; + + final Out len = new Out<>(); + int sum = 0; + + for (int i = 1; i < 3; i++) { + typeArgs[i] = readTypeArgument(buffer, offset + sum, len); + sum += len.get(); + } + + lengthInBytes.set(sum); + return new TypeArgumentList(typeArgs); + } + + @Override + public void setImplicitTypeCode(@Nonnull RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + edit.cellType(edit.scopeTypeArgs().get(edit.index()).type()); + edit.cellTypeArgs(edit.scopeTypeArgs().get(edit.index()).typeArgs()); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull RowBuffer buffer, + @Nonnull RowCursor edit, + @Nonnull TypeArgumentList typeArgs, @Nonnull Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull RowBuffer buffer, + @Nonnull RowCursor edit, + @Nonnull TypeArgumentList typeArgs, + @Nonnull UpdateOptions options, @Nonnull Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + checkNotNull(value, "expected non-null value"); + + Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.writeTypedTuple(edit, this, typeArgs, options)); + return Result.SUCCESS; + } + + @Override + public int writeTypeArgument(@Nonnull RowBuffer buffer, int offset, @Nonnull TypeArgumentList value) { + + checkState(value.count() == 3); + + buffer.writeSparseTypeCode(offset, this.layoutCode()); + int lengthInBytes = LayoutCode.BYTES; + + for (int i = 1; i < value.count(); i++) { + TypeArgument arg = value.get(i); + lengthInBytes += arg.type().writeTypeArgument(buffer, offset + lengthInBytes, arg.typeArgs()); + } + + return lengthInBytes; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTuple.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTuple.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTuple.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTuple.java index a9b71ea..076c9e8 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTuple.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTuple.java @@ -1,106 +1,106 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.IMMUTABLE_TUPLE_SCOPE; -import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.TUPLE_SCOPE; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutTuple extends LayoutIndexedScope { - - public LayoutTuple(boolean immutable) { - super( - immutable ? IMMUTABLE_TUPLE_SCOPE : TUPLE_SCOPE, - immutable, false, true, false, false - ); - } - - @Override - public int countTypeArgument(@Nonnull final TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - return value.stream() - .map(arg -> arg.type().countTypeArgument(arg.typeArgs())) - .reduce(LayoutCode.BYTES + RowBuffer.count7BitEncodedUInt(value.count()), Integer::sum); - } - - @Override - @Nonnull - public String name() { - return this.isImmutable() ? "im_tuple" : "tuple"; - } - - @Override - @Nonnull - public TypeArgumentList readTypeArgumentList( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { - - final int numTypeArgs = (int) buffer.readVariableUInt(offset, lengthInBytes); - final TypeArgument[] typeArgs = new TypeArgument[numTypeArgs]; - final Out len = new Out<>(); - - int sum = lengthInBytes.get(); - - for (int i = 0; i < numTypeArgs; i++) { - typeArgs[i] = readTypeArgument(buffer, offset + sum, len); - sum += len.get(); - } - - return new TypeArgumentList(typeArgs); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - checkNotNull(value, "expected non-null value"); - - Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.writeSparseTuple(edit, this, typeArgs, options)); - return Result.SUCCESS; - } - - @Override - public int writeTypeArgument(@Nonnull RowBuffer buffer, int offset, @Nonnull TypeArgumentList value) { - buffer.writeSparseTypeCode(offset, this.layoutCode()); - int lengthInBytes = LayoutCode.BYTES; - lengthInBytes += buffer.writeVariableUInt(offset + lengthInBytes, value.count()); - for (TypeArgument arg : value.list()) { - lengthInBytes += arg.type().writeTypeArgument(buffer, offset + lengthInBytes, arg.typeArgs()); - } - - return lengthInBytes; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.IMMUTABLE_TUPLE_SCOPE; +import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.TUPLE_SCOPE; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutTuple extends LayoutIndexedScope { + + public LayoutTuple(boolean immutable) { + super( + immutable ? IMMUTABLE_TUPLE_SCOPE : TUPLE_SCOPE, + immutable, false, true, false, false + ); + } + + @Override + public int countTypeArgument(@Nonnull final TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + return value.stream() + .map(arg -> arg.type().countTypeArgument(arg.typeArgs())) + .reduce(LayoutCode.BYTES + RowBuffer.count7BitEncodedUInt(value.count()), Integer::sum); + } + + @Override + @Nonnull + public String name() { + return this.isImmutable() ? "im_tuple" : "tuple"; + } + + @Override + @Nonnull + public TypeArgumentList readTypeArgumentList( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { + + final int numTypeArgs = (int) buffer.readVariableUInt(offset, lengthInBytes); + final TypeArgument[] typeArgs = new TypeArgument[numTypeArgs]; + final Out len = new Out<>(); + + int sum = lengthInBytes.get(); + + for (int i = 0; i < numTypeArgs; i++) { + typeArgs[i] = readTypeArgument(buffer, offset + sum, len); + sum += len.get(); + } + + return new TypeArgumentList(typeArgs); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + checkNotNull(value, "expected non-null value"); + + Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.writeSparseTuple(edit, this, typeArgs, options)); + return Result.SUCCESS; + } + + @Override + public int writeTypeArgument(@Nonnull RowBuffer buffer, int offset, @Nonnull TypeArgumentList value) { + buffer.writeSparseTypeCode(offset, this.layoutCode()); + int lengthInBytes = LayoutCode.BYTES; + lengthInBytes += buffer.writeVariableUInt(offset + lengthInBytes, value.count()); + for (TypeArgument arg : value.list()) { + lengthInBytes += arg.type().writeTypeArgument(buffer, offset + lengthInBytes, arg.typeArgs()); + } + + return lengthInBytes; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutType.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutType.java index c567f6e..baba5a2 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutType.java @@ -1,401 +1,401 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Json; -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.fasterxml.jackson.annotation.JsonProperty; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Describes the physical byte layout of a hybrid row field of a specific physical type {@code T}. - *

- * {@link LayoutType} provides methods for manipulating hybrid row fields of a particular type, and properties that - * describe the layout of fields of that type. - */ -public abstract class LayoutType /*implements ILayoutType*/ { - - private static final LayoutType[] codeIndex = new LayoutType[LayoutCode.END_SCOPE.value() + 1]; - - @JsonProperty - private final boolean immutable; - - @JsonProperty - private final LayoutCode layoutCode; - - @JsonProperty - private final int size; - - @JsonProperty - private final TypeArgument typeArg; - - /** - * Initializes a new instance of the {@link LayoutType} class. - * - * @param code the {@linkplain LayoutCode} layout code of the instance. - * @param immutable {@code true} if edits to fields with this layout type are prohibited. - * @param size size of fields with this layout type in bytes. - */ - protected LayoutType(@Nonnull final LayoutCode code, final boolean immutable, final int size) { - - checkNotNull(code, "expected non-null code"); - - this.immutable = immutable; - this.layoutCode = code; - this.size = size; - this.typeArg = new TypeArgument(this); - - codeIndex[code.value()] = this; - } - - /** - * Initializes a new instance of the {@link LayoutType} class. - * - * @param code the {@linkplain LayoutCode} layout code of the instance. - * @param size size of fields with this layout type in bytes. - */ - protected LayoutType(LayoutCode code, int size) { - this(code, false, size); - } - - /** - * {@code true} if this type is a boolean. - * - * @return {@code true} if this type is a boolean. - */ - public boolean isBoolean() { - return false; - } - - /** - * {@code true} if this type is always fixed length. - * - * @return {@code true} if this type is always fixed length. - */ - public abstract boolean isFixed(); - - /** - * {@code true} if this {@link LayoutType}'s nested fields cannot be updated individually. - *

- * Instances of this {@link LayoutType} can still be replaced in their entirety. - * - * @return {@code true} if this {@link LayoutType}'s nested fields cannot be updated individually. - */ - public boolean isImmutable() { - return this.immutable; - } - - /** - * {@code true} if this type is a literal null. - * - * @return {@code true} if this type is a literal null. - */ - public boolean isNull() { - return false; - } - - /** - * {@code true} if this type is a variable-length encoded integer type (either signed or unsigned). - * - * @return {@code true} if this type is a variable-length encoded integer type (either signed or unsigned). - */ - public boolean isVarint() { - return false; - } - - /** - * {@code true} if this type can be used in the variable-length segment. - * - * @return {@code true} if this type can be used in the variable-length segment. - */ - public final boolean allowVariable() { - return !this.isFixed(); - } - - public int countTypeArgument(@Nonnull TypeArgumentList value) { - return LayoutCode.BYTES; - } - - @Nonnull - public static LayoutType fromLayoutCode(LayoutCode code) { - LayoutType type = LayoutType.codeIndex[code.value()]; - checkArgument(type != null, "unimplemented code: %s", code); - return type; - } - - /** - * The physical layout code used to represent the type within the serialization. - * - * @return the physical layout code used to represent the type within the serialization. - */ - @Nonnull - public LayoutCode layoutCode() { - return this.layoutCode; - } - - /** - * Human readable name of the type. - * - * @return human readable name of the type. - */ - @Nonnull - public abstract String name(); - - /** - * Helper for preparing the delete of a sparse field. - * - * @param buffer The row to delete from. - * @param edit The parent edit containing the field to delete. - * @param code The expected type of the field. - * @return Success if the delete is permitted, the error code otherwise. - */ - @Nonnull - public static Result prepareSparseDelete(RowBuffer buffer, RowCursor edit, LayoutCode code) { - - if (edit.scopeType().isFixedArity()) { - return Result.TYPE_CONSTRAINT; - } - - if (edit.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - if (edit.exists() && LayoutCodeTraits.canonicalize(edit.cellType().layoutCode()) != code) { - return Result.TYPE_MISMATCH; - } - - return Result.SUCCESS; - } - - /** - * Helper for preparing the move of a sparse field into an existing restricted edit. - * - * @param buffer The row to read from. - * @param destinationScope The parent set edit into which the field should be moved. - * @param destinationCode The expected type of the edit moving within. - * @param elementType The expected type of the elements within the edit. - * @param srcEdit The field to be moved. - * @param options The move options. - * @param dstEdit If successful, a prepared insertion cursor for the destination. - * @return Success if the move is permitted, the error code otherwise. - * The source field is delete if the move prepare fails with a destination error. - */ - @Nonnull - public static Result prepareSparseMove( - RowBuffer buffer, - RowCursor destinationScope, - LayoutTypeScope destinationCode, - TypeArgument elementType, - RowCursor srcEdit, - UpdateOptions options, - Out dstEdit) { - - checkArgument(destinationScope.scopeType() == destinationCode); - checkArgument(destinationScope.index() == 0, "Can only insert into a edit at the root"); - - // Prepare the delete of the source - Result result = LayoutType.prepareSparseDelete(buffer, srcEdit, elementType.type().layoutCode()); - - if (result != Result.SUCCESS) { - dstEdit.set(null); - return result; - } - - if (!srcEdit.exists()) { - dstEdit.set(null); - return Result.NOT_FOUND; - } - - if (destinationScope.immutable()) { - buffer.deleteSparse(srcEdit); - dstEdit.set(null); - return Result.INSUFFICIENT_PERMISSIONS; - } - - if (!srcEdit.cellTypeArgs().equals(elementType.typeArgs())) { - buffer.deleteSparse(srcEdit); - dstEdit.set(null); - return Result.TYPE_CONSTRAINT; - } - - if (options == UpdateOptions.INSERT_AT) { - buffer.deleteSparse(srcEdit); - dstEdit.set(null); - return Result.TYPE_CONSTRAINT; - } - - // Prepare the insertion at the destination. - dstEdit.set(buffer.prepareSparseMove(destinationScope, srcEdit)); - - if ((options == UpdateOptions.UPDATE) && (!dstEdit.get().exists())) { - buffer.deleteSparse(srcEdit); - dstEdit.set(null); - return Result.NOT_FOUND; - } - - if ((options == UpdateOptions.INSERT) && dstEdit.get().exists()) { - buffer.deleteSparse(srcEdit); - dstEdit.set(null); - return Result.EXISTS; - } - - return Result.SUCCESS; - } - - /** - * Helper for preparing the read of a sparse field. - * - * @param buffer The row to read from. - * @param edit The parent edit containing the field to read. - * @param code The expected type of the field. - * @return Success if the read is permitted, the error code otherwise. - */ - @Nonnull - public static Result prepareSparseRead( - @Nonnull final RowBuffer buffer, @Nonnull final RowCursor edit, @Nonnull LayoutCode code) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(code, "expected non-null code"); - - if (!edit.exists()) { - return Result.NOT_FOUND; - } - - if (LayoutCodeTraits.canonicalize(edit.cellType().layoutCode()) != code) { - return Result.TYPE_MISMATCH; - } - - return Result.SUCCESS; - } - - /** - * Helper for preparing the write of a sparse field. - * - * @param buffer The row to write to. - * @param edit The cursor for the field to write. - * @param typeArg The (optional) type constraints. - * @param options The write options. - * @return Success if the write is permitted, the error code otherwise. - */ - @Nonnull - public static Result prepareSparseWrite( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgument typeArg, - @Nonnull final UpdateOptions options) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(typeArg, "expected non-null typeArg"); - checkNotNull(options, "expected non-null options"); - - if (edit.immutable() || (edit.scopeType().isUniqueScope() && !edit.deferUniqueIndex())) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - if (edit.scopeType().isFixedArity() && !(edit.scopeType() instanceof LayoutNullable)) { - if ((edit.index() < edit.scopeTypeArgs().count()) && !typeArg.equals(edit.scopeTypeArgs().get(edit.index()))) { - return Result.TYPE_CONSTRAINT; - } - } else if (edit.scopeType() instanceof LayoutTypedMap) { - if (!((typeArg.type() instanceof LayoutTypedTuple) && typeArg.typeArgs().equals(edit.scopeTypeArgs()))) { - return Result.TYPE_CONSTRAINT; - } - } else if (edit.scopeType().isTypedScope() && !typeArg.equals(edit.scopeTypeArgs().get(0))) { - return Result.TYPE_CONSTRAINT; - } - - if ((options == UpdateOptions.INSERT_AT) && edit.scopeType().isFixedArity()) { - return Result.TYPE_CONSTRAINT; - } - - if ((options == UpdateOptions.INSERT_AT) && !edit.scopeType().isFixedArity()) { - edit.exists(false); // InsertAt never overwrites an existing item. - } - - if ((options == UpdateOptions.UPDATE) && (!edit.exists())) { - return Result.NOT_FOUND; - } - - if ((options == UpdateOptions.INSERT) && edit.exists()) { - return Result.EXISTS; - } - - return Result.SUCCESS; - } - - @Nonnull - public static TypeArgument readTypeArgument( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - LayoutType type = buffer.readSparseTypeCode(offset); - TypeArgumentList typeArgs = type.readTypeArgumentList(buffer, offset + LayoutCode.BYTES, lengthInBytes); - lengthInBytes.set(LayoutCode.BYTES + lengthInBytes.get()); - - return new TypeArgument(type, typeArgs); - } - - @Nonnull - public TypeArgumentList readTypeArgumentList( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - lengthInBytes.set(0); - return TypeArgumentList.EMPTY; - } - - /** - * If fixed, the fixed size of the type's serialization in bytes, otherwise undefined. - * - * @return If fixed, the fixed size of the type's serialization in bytes, otherwise undefined. - */ - public int size() { - return this.size; - } - - /** - * Returns a string representation of the {@linkplain LayoutType layout type}. - * - * @return a string representation of the {@linkplain LayoutType layout type}. - */ - @Override - public String toString() { - return Json.toString(this); - } - - public TypeArgument typeArg() { - return this.typeArg; - } - - /** - * The physical layout type of the field cast to the specified type. - * - * @param a type that implements {@link ILayoutType}. - * @return the physical layout type of the field cast to the specified type. - */ - @SuppressWarnings("unchecked") - public final T typeAs() { - return (T) this; - } - - public int writeTypeArgument(@Nonnull final RowBuffer buffer, int offset, @Nonnull final TypeArgumentList value) { - buffer.writeSparseTypeCode(offset, this.layoutCode()); - return LayoutCode.BYTES; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Json; +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Describes the physical byte layout of a hybrid row field of a specific physical type {@code T}. + *

+ * {@link LayoutType} provides methods for manipulating hybrid row fields of a particular type, and properties that + * describe the layout of fields of that type. + */ +public abstract class LayoutType /*implements ILayoutType*/ { + + private static final LayoutType[] codeIndex = new LayoutType[LayoutCode.END_SCOPE.value() + 1]; + + @JsonProperty + private final boolean immutable; + + @JsonProperty + private final LayoutCode layoutCode; + + @JsonProperty + private final int size; + + @JsonProperty + private final TypeArgument typeArg; + + /** + * Initializes a new instance of the {@link LayoutType} class. + * + * @param code the {@linkplain LayoutCode} layout code of the instance. + * @param immutable {@code true} if edits to fields with this layout type are prohibited. + * @param size size of fields with this layout type in bytes. + */ + protected LayoutType(@Nonnull final LayoutCode code, final boolean immutable, final int size) { + + checkNotNull(code, "expected non-null code"); + + this.immutable = immutable; + this.layoutCode = code; + this.size = size; + this.typeArg = new TypeArgument(this); + + codeIndex[code.value()] = this; + } + + /** + * Initializes a new instance of the {@link LayoutType} class. + * + * @param code the {@linkplain LayoutCode} layout code of the instance. + * @param size size of fields with this layout type in bytes. + */ + protected LayoutType(LayoutCode code, int size) { + this(code, false, size); + } + + /** + * {@code true} if this type is a boolean. + * + * @return {@code true} if this type is a boolean. + */ + public boolean isBoolean() { + return false; + } + + /** + * {@code true} if this type is always fixed length. + * + * @return {@code true} if this type is always fixed length. + */ + public abstract boolean isFixed(); + + /** + * {@code true} if this {@link LayoutType}'s nested fields cannot be updated individually. + *

+ * Instances of this {@link LayoutType} can still be replaced in their entirety. + * + * @return {@code true} if this {@link LayoutType}'s nested fields cannot be updated individually. + */ + public boolean isImmutable() { + return this.immutable; + } + + /** + * {@code true} if this type is a literal null. + * + * @return {@code true} if this type is a literal null. + */ + public boolean isNull() { + return false; + } + + /** + * {@code true} if this type is a variable-length encoded integer type (either signed or unsigned). + * + * @return {@code true} if this type is a variable-length encoded integer type (either signed or unsigned). + */ + public boolean isVarint() { + return false; + } + + /** + * {@code true} if this type can be used in the variable-length segment. + * + * @return {@code true} if this type can be used in the variable-length segment. + */ + public final boolean allowVariable() { + return !this.isFixed(); + } + + public int countTypeArgument(@Nonnull TypeArgumentList value) { + return LayoutCode.BYTES; + } + + @Nonnull + public static LayoutType fromLayoutCode(LayoutCode code) { + LayoutType type = LayoutType.codeIndex[code.value()]; + checkArgument(type != null, "unimplemented code: %s", code); + return type; + } + + /** + * The physical layout code used to represent the type within the serialization. + * + * @return the physical layout code used to represent the type within the serialization. + */ + @Nonnull + public LayoutCode layoutCode() { + return this.layoutCode; + } + + /** + * Human readable name of the type. + * + * @return human readable name of the type. + */ + @Nonnull + public abstract String name(); + + /** + * Helper for preparing the delete of a sparse field. + * + * @param buffer The row to delete from. + * @param edit The parent edit containing the field to delete. + * @param code The expected type of the field. + * @return Success if the delete is permitted, the error code otherwise. + */ + @Nonnull + public static Result prepareSparseDelete(RowBuffer buffer, RowCursor edit, LayoutCode code) { + + if (edit.scopeType().isFixedArity()) { + return Result.TYPE_CONSTRAINT; + } + + if (edit.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + if (edit.exists() && LayoutCodeTraits.canonicalize(edit.cellType().layoutCode()) != code) { + return Result.TYPE_MISMATCH; + } + + return Result.SUCCESS; + } + + /** + * Helper for preparing the move of a sparse field into an existing restricted edit. + * + * @param buffer The row to read from. + * @param destinationScope The parent set edit into which the field should be moved. + * @param destinationCode The expected type of the edit moving within. + * @param elementType The expected type of the elements within the edit. + * @param srcEdit The field to be moved. + * @param options The move options. + * @param dstEdit If successful, a prepared insertion cursor for the destination. + * @return Success if the move is permitted, the error code otherwise. + * The source field is delete if the move prepare fails with a destination error. + */ + @Nonnull + public static Result prepareSparseMove( + RowBuffer buffer, + RowCursor destinationScope, + LayoutTypeScope destinationCode, + TypeArgument elementType, + RowCursor srcEdit, + UpdateOptions options, + Out dstEdit) { + + checkArgument(destinationScope.scopeType() == destinationCode); + checkArgument(destinationScope.index() == 0, "Can only insert into a edit at the root"); + + // Prepare the delete of the source + Result result = LayoutType.prepareSparseDelete(buffer, srcEdit, elementType.type().layoutCode()); + + if (result != Result.SUCCESS) { + dstEdit.set(null); + return result; + } + + if (!srcEdit.exists()) { + dstEdit.set(null); + return Result.NOT_FOUND; + } + + if (destinationScope.immutable()) { + buffer.deleteSparse(srcEdit); + dstEdit.set(null); + return Result.INSUFFICIENT_PERMISSIONS; + } + + if (!srcEdit.cellTypeArgs().equals(elementType.typeArgs())) { + buffer.deleteSparse(srcEdit); + dstEdit.set(null); + return Result.TYPE_CONSTRAINT; + } + + if (options == UpdateOptions.INSERT_AT) { + buffer.deleteSparse(srcEdit); + dstEdit.set(null); + return Result.TYPE_CONSTRAINT; + } + + // Prepare the insertion at the destination. + dstEdit.set(buffer.prepareSparseMove(destinationScope, srcEdit)); + + if ((options == UpdateOptions.UPDATE) && (!dstEdit.get().exists())) { + buffer.deleteSparse(srcEdit); + dstEdit.set(null); + return Result.NOT_FOUND; + } + + if ((options == UpdateOptions.INSERT) && dstEdit.get().exists()) { + buffer.deleteSparse(srcEdit); + dstEdit.set(null); + return Result.EXISTS; + } + + return Result.SUCCESS; + } + + /** + * Helper for preparing the read of a sparse field. + * + * @param buffer The row to read from. + * @param edit The parent edit containing the field to read. + * @param code The expected type of the field. + * @return Success if the read is permitted, the error code otherwise. + */ + @Nonnull + public static Result prepareSparseRead( + @Nonnull final RowBuffer buffer, @Nonnull final RowCursor edit, @Nonnull LayoutCode code) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(code, "expected non-null code"); + + if (!edit.exists()) { + return Result.NOT_FOUND; + } + + if (LayoutCodeTraits.canonicalize(edit.cellType().layoutCode()) != code) { + return Result.TYPE_MISMATCH; + } + + return Result.SUCCESS; + } + + /** + * Helper for preparing the write of a sparse field. + * + * @param buffer The row to write to. + * @param edit The cursor for the field to write. + * @param typeArg The (optional) type constraints. + * @param options The write options. + * @return Success if the write is permitted, the error code otherwise. + */ + @Nonnull + public static Result prepareSparseWrite( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgument typeArg, + @Nonnull final UpdateOptions options) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(typeArg, "expected non-null typeArg"); + checkNotNull(options, "expected non-null options"); + + if (edit.immutable() || (edit.scopeType().isUniqueScope() && !edit.deferUniqueIndex())) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + if (edit.scopeType().isFixedArity() && !(edit.scopeType() instanceof LayoutNullable)) { + if ((edit.index() < edit.scopeTypeArgs().count()) && !typeArg.equals(edit.scopeTypeArgs().get(edit.index()))) { + return Result.TYPE_CONSTRAINT; + } + } else if (edit.scopeType() instanceof LayoutTypedMap) { + if (!((typeArg.type() instanceof LayoutTypedTuple) && typeArg.typeArgs().equals(edit.scopeTypeArgs()))) { + return Result.TYPE_CONSTRAINT; + } + } else if (edit.scopeType().isTypedScope() && !typeArg.equals(edit.scopeTypeArgs().get(0))) { + return Result.TYPE_CONSTRAINT; + } + + if ((options == UpdateOptions.INSERT_AT) && edit.scopeType().isFixedArity()) { + return Result.TYPE_CONSTRAINT; + } + + if ((options == UpdateOptions.INSERT_AT) && !edit.scopeType().isFixedArity()) { + edit.exists(false); // InsertAt never overwrites an existing item. + } + + if ((options == UpdateOptions.UPDATE) && (!edit.exists())) { + return Result.NOT_FOUND; + } + + if ((options == UpdateOptions.INSERT) && edit.exists()) { + return Result.EXISTS; + } + + return Result.SUCCESS; + } + + @Nonnull + public static TypeArgument readTypeArgument( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + LayoutType type = buffer.readSparseTypeCode(offset); + TypeArgumentList typeArgs = type.readTypeArgumentList(buffer, offset + LayoutCode.BYTES, lengthInBytes); + lengthInBytes.set(LayoutCode.BYTES + lengthInBytes.get()); + + return new TypeArgument(type, typeArgs); + } + + @Nonnull + public TypeArgumentList readTypeArgumentList( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + lengthInBytes.set(0); + return TypeArgumentList.EMPTY; + } + + /** + * If fixed, the fixed size of the type's serialization in bytes, otherwise undefined. + * + * @return If fixed, the fixed size of the type's serialization in bytes, otherwise undefined. + */ + public int size() { + return this.size; + } + + /** + * Returns a string representation of the {@linkplain LayoutType layout type}. + * + * @return a string representation of the {@linkplain LayoutType layout type}. + */ + @Override + public String toString() { + return Json.toString(this); + } + + public TypeArgument typeArg() { + return this.typeArg; + } + + /** + * The physical layout type of the field cast to the specified type. + * + * @param a type that implements {@link ILayoutType}. + * @return the physical layout type of the field cast to the specified type. + */ + @SuppressWarnings("unchecked") + public final T typeAs() { + return (T) this; + } + + public int writeTypeArgument(@Nonnull final RowBuffer buffer, int offset, @Nonnull final TypeArgumentList value) { + buffer.writeSparseTypeCode(offset, this.layoutCode()); + return LayoutCode.BYTES; + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypePrimitive.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypePrimitive.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypePrimitive.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypePrimitive.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypeScope.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypeScope.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypeScope.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypeScope.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedArray.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedArray.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedArray.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedArray.java index fb9a398..9c280f3 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedArray.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedArray.java @@ -1,101 +1,101 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -public final class LayoutTypedArray extends LayoutIndexedScope { - - public LayoutTypedArray(boolean immutable) { - super( - immutable ? LayoutCode.IMMUTABLE_TYPED_ARRAY_SCOPE : LayoutCode.TYPED_ARRAY_SCOPE, immutable, - true, false, false, true - ); - } - - @Override - public int countTypeArgument(@Nonnull TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - checkState(value.count() == 1); - return LayoutCode.BYTES + value.get(0).type().countTypeArgument(value.get(0).typeArgs()); - } - - @Override - public boolean hasImplicitTypeCode(@Nonnull RowCursor edit) { - checkState(edit.index() >= 0); - checkState(edit.scopeTypeArgs().count() == 1); - return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(0).type().layoutCode()); - } - - @Override - @Nonnull - public String name() { - return this.isImmutable() ? "im_array_t" : "array_t"; - } - - @Override - @Nonnull - public TypeArgumentList readTypeArgumentList(@Nonnull RowBuffer buffer, int offset, @Nonnull Out lenInBytes) { - return new TypeArgumentList(LayoutType.readTypeArgument(buffer, offset, lenInBytes)); - } - - @Override - public void setImplicitTypeCode(@Nonnull final RowCursor edit) { - edit.cellType(edit.scopeTypeArgs().get(0).type()); - edit.cellTypeArgs(edit.scopeTypeArgs().get(0).typeArgs()); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - @Nonnull final Out value) { - - final TypeArgument typeArg = new TypeArgument(this, typeArgs); - final Result result = LayoutType.prepareSparseWrite(buffer, edit, typeArg, options); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.writeTypedArray(edit, this, typeArgs, options)); - return Result.SUCCESS; - } - - @Override - public int writeTypeArgument( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final TypeArgumentList value) { - - checkState(value.count() == 1); - - TypeArgument typeArg = value.get(0); - buffer.writeSparseTypeCode(offset, this.layoutCode()); - - return LayoutCode.BYTES + typeArg.type().writeTypeArgument( - buffer, offset + LayoutCode.BYTES, typeArg.typeArgs() - ); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class LayoutTypedArray extends LayoutIndexedScope { + + public LayoutTypedArray(boolean immutable) { + super( + immutable ? LayoutCode.IMMUTABLE_TYPED_ARRAY_SCOPE : LayoutCode.TYPED_ARRAY_SCOPE, immutable, + true, false, false, true + ); + } + + @Override + public int countTypeArgument(@Nonnull TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + checkState(value.count() == 1); + return LayoutCode.BYTES + value.get(0).type().countTypeArgument(value.get(0).typeArgs()); + } + + @Override + public boolean hasImplicitTypeCode(@Nonnull RowCursor edit) { + checkState(edit.index() >= 0); + checkState(edit.scopeTypeArgs().count() == 1); + return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(0).type().layoutCode()); + } + + @Override + @Nonnull + public String name() { + return this.isImmutable() ? "im_array_t" : "array_t"; + } + + @Override + @Nonnull + public TypeArgumentList readTypeArgumentList(@Nonnull RowBuffer buffer, int offset, @Nonnull Out lenInBytes) { + return new TypeArgumentList(LayoutType.readTypeArgument(buffer, offset, lenInBytes)); + } + + @Override + public void setImplicitTypeCode(@Nonnull final RowCursor edit) { + edit.cellType(edit.scopeTypeArgs().get(0).type()); + edit.cellTypeArgs(edit.scopeTypeArgs().get(0).typeArgs()); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + @Nonnull final Out value) { + + final TypeArgument typeArg = new TypeArgument(this, typeArgs); + final Result result = LayoutType.prepareSparseWrite(buffer, edit, typeArg, options); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.writeTypedArray(edit, this, typeArgs, options)); + return Result.SUCCESS; + } + + @Override + public int writeTypeArgument( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final TypeArgumentList value) { + + checkState(value.count() == 1); + + TypeArgument typeArg = value.get(0); + buffer.writeSparseTypeCode(offset, this.layoutCode()); + + return LayoutCode.BYTES + typeArg.type().writeTypeArgument( + buffer, offset + LayoutCode.BYTES, typeArg.typeArgs() + ); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedMap.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedMap.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedMap.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedMap.java index dadc860..023b435 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedMap.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedMap.java @@ -1,135 +1,135 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -public final class LayoutTypedMap extends LayoutUniqueScope { - - public LayoutTypedMap(boolean immutable) { - super( - immutable ? LayoutCode.IMMUTABLE_TYPED_MAP_SCOPE : LayoutCode.TYPED_MAP_SCOPE, immutable, - true, true); - } - - @Override - public int countTypeArgument(@Nonnull final TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - checkState(value.count() == 2); - return value.stream() - .map(arg -> arg.type().countTypeArgument(arg.typeArgs())) - .reduce(LayoutCode.BYTES, Integer::sum); - } - - @Nonnull - @Override - public TypeArgument fieldType(@Nonnull final RowCursor scope) { - checkNotNull(scope, "expected non-null scope"); - return new TypeArgument( - scope.scopeType().isImmutable() ? LayoutTypes.IMMUTABLE_TYPED_TUPLE : LayoutTypes.TYPED_TUPLE, - scope.scopeTypeArgs()); - } - - @Override - public boolean hasImplicitTypeCode(@Nonnull final RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - return true; - } - - @Nonnull - public String name() { - return this.isImmutable() ? "im_map_t" : "map_t"; - } - - @Override - @Nonnull - public TypeArgumentList readTypeArgumentList( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); - - TypeArgument[] typeArguments = new TypeArgument[2]; - Out length = new Out<>(); - int index = 0; - - for (int i = 0; i < 2; i++) { - typeArguments[i] = readTypeArgument(buffer, offset + index, length); - index += length.get(); - } - - lengthInBytes.set(index); - return new TypeArgumentList(typeArguments); - } - - @Override - public void setImplicitTypeCode(@Nonnull final RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - edit.cellType(edit.scopeType().isImmutable() ? LayoutTypes.IMMUTABLE_TYPED_TUPLE : LayoutTypes.TYPED_TUPLE); - edit.cellTypeArgs(edit.scopeTypeArgs()); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - checkNotNull(value, "expected non-null value"); - - final Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); - - if (result != Result.SUCCESS) { - value.setAndGet(null); - return result; - } - - value.set(buffer.writeTypedMap(edit, this, typeArgs, options)); - return Result.SUCCESS; - } - - @Override - public int writeTypeArgument( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final TypeArgumentList value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(value, "expected non-null value"); - checkArgument(value.count() == 2, "expected value count of 2, not %s", value.count()); - - buffer.writeSparseTypeCode(offset, this.layoutCode()); - int lengthInBytes = LayoutCode.BYTES; - - for (TypeArgument arg : value.list()) { - lengthInBytes += arg.type().writeTypeArgument(buffer, offset + lengthInBytes, arg.typeArgs()); - } - - return lengthInBytes; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class LayoutTypedMap extends LayoutUniqueScope { + + public LayoutTypedMap(boolean immutable) { + super( + immutable ? LayoutCode.IMMUTABLE_TYPED_MAP_SCOPE : LayoutCode.TYPED_MAP_SCOPE, immutable, + true, true); + } + + @Override + public int countTypeArgument(@Nonnull final TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + checkState(value.count() == 2); + return value.stream() + .map(arg -> arg.type().countTypeArgument(arg.typeArgs())) + .reduce(LayoutCode.BYTES, Integer::sum); + } + + @Nonnull + @Override + public TypeArgument fieldType(@Nonnull final RowCursor scope) { + checkNotNull(scope, "expected non-null scope"); + return new TypeArgument( + scope.scopeType().isImmutable() ? LayoutTypes.IMMUTABLE_TYPED_TUPLE : LayoutTypes.TYPED_TUPLE, + scope.scopeTypeArgs()); + } + + @Override + public boolean hasImplicitTypeCode(@Nonnull final RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + return true; + } + + @Nonnull + public String name() { + return this.isImmutable() ? "im_map_t" : "map_t"; + } + + @Override + @Nonnull + public TypeArgumentList readTypeArgumentList( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); + + TypeArgument[] typeArguments = new TypeArgument[2]; + Out length = new Out<>(); + int index = 0; + + for (int i = 0; i < 2; i++) { + typeArguments[i] = readTypeArgument(buffer, offset + index, length); + index += length.get(); + } + + lengthInBytes.set(index); + return new TypeArgumentList(typeArguments); + } + + @Override + public void setImplicitTypeCode(@Nonnull final RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + edit.cellType(edit.scopeType().isImmutable() ? LayoutTypes.IMMUTABLE_TYPED_TUPLE : LayoutTypes.TYPED_TUPLE); + edit.cellTypeArgs(edit.scopeTypeArgs()); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + checkNotNull(value, "expected non-null value"); + + final Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); + + if (result != Result.SUCCESS) { + value.setAndGet(null); + return result; + } + + value.set(buffer.writeTypedMap(edit, this, typeArgs, options)); + return Result.SUCCESS; + } + + @Override + public int writeTypeArgument( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final TypeArgumentList value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(value, "expected non-null value"); + checkArgument(value.count() == 2, "expected value count of 2, not %s", value.count()); + + buffer.writeSparseTypeCode(offset, this.layoutCode()); + int lengthInBytes = LayoutCode.BYTES; + + for (TypeArgument arg : value.list()) { + lengthInBytes += arg.type().writeTypeArgument(buffer, offset + lengthInBytes, arg.typeArgs()); + } + + return lengthInBytes; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedSet.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedSet.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedSet.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedSet.java index 3379894..feadc50 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedSet.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedSet.java @@ -1,121 +1,121 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.IMMUTABLE_TYPED_SET_SCOPE; -import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.TYPED_SET_SCOPE; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -public final class LayoutTypedSet extends LayoutUniqueScope { - - public LayoutTypedSet(boolean immutable) { - super(immutable ? IMMUTABLE_TYPED_SET_SCOPE : TYPED_SET_SCOPE, immutable, true, true); - } - - @Override - public int countTypeArgument(@Nonnull final TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - checkState(value.count() == 1); - return LayoutCode.BYTES + value.get(0).type().countTypeArgument(value.get(0).typeArgs()); - } - - @Nonnull - @Override - public TypeArgument fieldType(@Nonnull final RowCursor scope) { - checkNotNull(scope, "expected non-null scope"); - return scope.scopeTypeArgs().get(0); - } - - @Override - public boolean hasImplicitTypeCode(@Nonnull final RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - checkState(edit.index() >= 0); - checkState(edit.scopeTypeArgs().count() == 1); - return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(0).type().layoutCode()); - } - - @Nonnull - public String name() { - return this.isImmutable() ? "im_set_t" : "set_t"; - } - - @Override - @Nonnull - public TypeArgumentList readTypeArgumentList( - @Nonnull final RowBuffer buffer, - final int offset, - @Nonnull final Out lengthInBytes) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - return new TypeArgumentList(readTypeArgument(buffer, offset, lengthInBytes)); - } - - @Override - public void setImplicitTypeCode(@Nonnull final RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - edit.cellType(edit.scopeTypeArgs().get(0).type()); - edit.cellTypeArgs(edit.scopeTypeArgs().get(0).typeArgs()); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - @Nonnull final Out value) { - - Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); - - if (result != Result.SUCCESS) { - value.setAndGet(null); - return result; - } - - value.set(buffer.writeTypedSet(edit, this, typeArgs, options)); - return Result.SUCCESS; - } - - @Override - public int writeTypeArgument( - @Nonnull final RowBuffer buffer, - final int offset, - @Nonnull final TypeArgumentList value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(value, "expected non-null value"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - checkArgument(value.count() == 1, "expected a single value count, not %s", value.count()); - - buffer.writeSparseTypeCode(offset, this.layoutCode()); - final TypeArgument typeArg = value.get(0); - int lengthInBytes = LayoutCode.BYTES; - lengthInBytes += typeArg.type().writeTypeArgument(buffer, offset + lengthInBytes, typeArg.typeArgs()); - - return lengthInBytes; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.IMMUTABLE_TYPED_SET_SCOPE; +import static com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCode.TYPED_SET_SCOPE; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class LayoutTypedSet extends LayoutUniqueScope { + + public LayoutTypedSet(boolean immutable) { + super(immutable ? IMMUTABLE_TYPED_SET_SCOPE : TYPED_SET_SCOPE, immutable, true, true); + } + + @Override + public int countTypeArgument(@Nonnull final TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + checkState(value.count() == 1); + return LayoutCode.BYTES + value.get(0).type().countTypeArgument(value.get(0).typeArgs()); + } + + @Nonnull + @Override + public TypeArgument fieldType(@Nonnull final RowCursor scope) { + checkNotNull(scope, "expected non-null scope"); + return scope.scopeTypeArgs().get(0); + } + + @Override + public boolean hasImplicitTypeCode(@Nonnull final RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + checkState(edit.index() >= 0); + checkState(edit.scopeTypeArgs().count() == 1); + return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(0).type().layoutCode()); + } + + @Nonnull + public String name() { + return this.isImmutable() ? "im_set_t" : "set_t"; + } + + @Override + @Nonnull + public TypeArgumentList readTypeArgumentList( + @Nonnull final RowBuffer buffer, + final int offset, + @Nonnull final Out lengthInBytes) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + return new TypeArgumentList(readTypeArgument(buffer, offset, lengthInBytes)); + } + + @Override + public void setImplicitTypeCode(@Nonnull final RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + edit.cellType(edit.scopeTypeArgs().get(0).type()); + edit.cellTypeArgs(edit.scopeTypeArgs().get(0).typeArgs()); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + @Nonnull final Out value) { + + Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); + + if (result != Result.SUCCESS) { + value.setAndGet(null); + return result; + } + + value.set(buffer.writeTypedSet(edit, this, typeArgs, options)); + return Result.SUCCESS; + } + + @Override + public int writeTypeArgument( + @Nonnull final RowBuffer buffer, + final int offset, + @Nonnull final TypeArgumentList value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(value, "expected non-null value"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + checkArgument(value.count() == 1, "expected a single value count, not %s", value.count()); + + buffer.writeSparseTypeCode(offset, this.layoutCode()); + final TypeArgument typeArg = value.get(0); + int lengthInBytes = LayoutCode.BYTES; + lengthInBytes += typeArg.type().writeTypeArgument(buffer, offset + lengthInBytes, typeArg.typeArgs()); + + return lengthInBytes; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedTuple.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedTuple.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedTuple.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedTuple.java index ea61ba0..1bb7cae 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedTuple.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypedTuple.java @@ -1,132 +1,132 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutTypedTuple extends LayoutIndexedScope { - - public LayoutTypedTuple(boolean immutable) { - super( - immutable ? LayoutCode.IMMUTABLE_TYPED_TUPLE_SCOPE : LayoutCode.TYPED_TUPLE_SCOPE, immutable, - true, true, false, true - ); - } - - @Override - public int countTypeArgument(@Nonnull TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - return value.stream() - .map(arg -> arg.type().countTypeArgument(arg.typeArgs())) - .reduce(LayoutCode.BYTES + RowBuffer.count7BitEncodedUInt(value.count()), Integer::sum); - } - - @Override - public boolean hasImplicitTypeCode(@Nonnull final RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - checkArgument(edit.index() >= 0); - checkArgument(edit.scopeTypeArgs().count() > edit.index()); - return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(edit.index()).type().layoutCode()); - } - - @Override - @Nonnull - public String name() { - return this.isImmutable() ? "im_tuple_t" : "tuple_t"; - } - - @Nonnull - @Override - public TypeArgumentList readTypeArgumentList( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - final int numTypeArgs = (int) buffer.readVariableUInt(offset, lengthInBytes); - final TypeArgument[] typeArgs = new TypeArgument[numTypeArgs]; - final Out len = new Out<>(); - - int sum = lengthInBytes.get(); - - for (int i = 0; i < numTypeArgs; i++) { - typeArgs[i] = LayoutType.readTypeArgument(buffer, offset + sum, len); - sum += len.get(); - } - - lengthInBytes.set(sum); - return new TypeArgumentList(typeArgs); - } - - @Override - public void setImplicitTypeCode(@Nonnull final RowCursor edit) { - checkNotNull(edit, "expected non-null edit"); - edit.cellType(edit.scopeTypeArgs().get(edit.index()).type()); - edit.cellTypeArgs(edit.scopeTypeArgs().get(edit.index()).typeArgs()); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final TypeArgumentList typeArgs, - @Nonnull final UpdateOptions options, - @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(edit, "expected non-null edit"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - checkNotNull(value, "expected non-null value"); - - Result result = LayoutType.prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); - - if (result != Result.SUCCESS) { - value.setAndGet(null); - return result; - } - - value.set(buffer.writeTypedTuple(edit, this, typeArgs, options)); - return Result.SUCCESS; - } - - @Override - public int writeTypeArgument( - @Nonnull final RowBuffer buffer, final int offset, @Nonnull final TypeArgumentList value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(value, "expected non-null value"); - checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); - - buffer.writeSparseTypeCode(offset, this.layoutCode()); - int lengthInBytes = LayoutCode.BYTES; - lengthInBytes += buffer.writeVariableUInt(offset + lengthInBytes, value.count()); - - for (TypeArgument arg : value.list()) { - lengthInBytes += arg.type().writeTypeArgument(buffer, offset + lengthInBytes, arg.typeArgs()); - } - - return lengthInBytes; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutTypedTuple extends LayoutIndexedScope { + + public LayoutTypedTuple(boolean immutable) { + super( + immutable ? LayoutCode.IMMUTABLE_TYPED_TUPLE_SCOPE : LayoutCode.TYPED_TUPLE_SCOPE, immutable, + true, true, false, true + ); + } + + @Override + public int countTypeArgument(@Nonnull TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + return value.stream() + .map(arg -> arg.type().countTypeArgument(arg.typeArgs())) + .reduce(LayoutCode.BYTES + RowBuffer.count7BitEncodedUInt(value.count()), Integer::sum); + } + + @Override + public boolean hasImplicitTypeCode(@Nonnull final RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + checkArgument(edit.index() >= 0); + checkArgument(edit.scopeTypeArgs().count() > edit.index()); + return !LayoutCodeTraits.alwaysRequiresTypeCode(edit.scopeTypeArgs().get(edit.index()).type().layoutCode()); + } + + @Override + @Nonnull + public String name() { + return this.isImmutable() ? "im_tuple_t" : "tuple_t"; + } + + @Nonnull + @Override + public TypeArgumentList readTypeArgumentList( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final Out lengthInBytes) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(lengthInBytes, "expected non-null lengthInBytes"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + final int numTypeArgs = (int) buffer.readVariableUInt(offset, lengthInBytes); + final TypeArgument[] typeArgs = new TypeArgument[numTypeArgs]; + final Out len = new Out<>(); + + int sum = lengthInBytes.get(); + + for (int i = 0; i < numTypeArgs; i++) { + typeArgs[i] = LayoutType.readTypeArgument(buffer, offset + sum, len); + sum += len.get(); + } + + lengthInBytes.set(sum); + return new TypeArgumentList(typeArgs); + } + + @Override + public void setImplicitTypeCode(@Nonnull final RowCursor edit) { + checkNotNull(edit, "expected non-null edit"); + edit.cellType(edit.scopeTypeArgs().get(edit.index()).type()); + edit.cellTypeArgs(edit.scopeTypeArgs().get(edit.index()).typeArgs()); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final TypeArgumentList typeArgs, + @Nonnull final UpdateOptions options, + @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(edit, "expected non-null edit"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + checkNotNull(value, "expected non-null value"); + + Result result = LayoutType.prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); + + if (result != Result.SUCCESS) { + value.setAndGet(null); + return result; + } + + value.set(buffer.writeTypedTuple(edit, this, typeArgs, options)); + return Result.SUCCESS; + } + + @Override + public int writeTypeArgument( + @Nonnull final RowBuffer buffer, final int offset, @Nonnull final TypeArgumentList value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(value, "expected non-null value"); + checkArgument(offset >= 0, "expected non-negative offset, not %s", offset); + + buffer.writeSparseTypeCode(offset, this.layoutCode()); + int lengthInBytes = LayoutCode.BYTES; + lengthInBytes += buffer.writeVariableUInt(offset + lengthInBytes, value.count()); + + for (TypeArgument arg : value.list()) { + lengthInBytes += arg.type().writeTypeArgument(buffer, offset + lengthInBytes, arg.typeArgs()); + } + + return lengthInBytes; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypes.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypes.java similarity index 98% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypes.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypes.java index 2a9dd32..0960cb0 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypes.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutTypes.java @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -/** - * Layout type definitions - */ -public abstract class LayoutTypes { - public static final LayoutArray ARRAY = new LayoutArray(false); - public static final LayoutBinary BINARY = new LayoutBinary(); - public static final LayoutBoolean BOOLEAN = new LayoutBoolean(true); - public static final LayoutBoolean BOOLEAN_FALSE = new LayoutBoolean(false); - public static final LayoutDateTime DATE_TIME = new LayoutDateTime(); - public static final LayoutDecimal DECIMAL = new LayoutDecimal(); - public static final LayoutEndScope END_SCOPE = new LayoutEndScope(); - public static final LayoutFloat128 FLOAT_128 = new LayoutFloat128(); - public static final LayoutFloat32 FLOAT_32 = new LayoutFloat32(); - public static final LayoutFloat64 FLOAT_64 = new LayoutFloat64(); - public static final LayoutGuid GUID = new LayoutGuid(); - public static final LayoutArray IMMUTABLE_ARRAY = new LayoutArray(true); - public static final LayoutNullable IMMUTABLE_NULLABLE = new LayoutNullable(true); - public static final LayoutObject IMMUTABLE_OBJECT = new LayoutObject(true); - public static final LayoutTagged IMMUTABLE_TAGGED = new LayoutTagged(true); - public static final LayoutTagged2 IMMUTABLE_TAGGED_2 = new LayoutTagged2(true); - public static final LayoutTuple ImmutableTuple = new LayoutTuple(true); - public static final LayoutTypedArray IMMUTABLE_TYPED_ARRAY = new LayoutTypedArray(true); - public static final LayoutTypedMap IMMUTABLE_TYPED_MAP = new LayoutTypedMap(true); - public static final LayoutTypedSet IMMUTABLE_TYPED_SET = new LayoutTypedSet(true); - public static final LayoutTypedTuple IMMUTABLE_TYPED_TUPLE = new LayoutTypedTuple(true); - public static final LayoutUDT IMMUTABLE_UDT = new LayoutUDT(true); - public static final LayoutInt16 INT_16 = new LayoutInt16(); - public static final LayoutInt32 INT_32 = new LayoutInt32(); - public static final LayoutInt64 INT_64 = new LayoutInt64(); - public static final LayoutInt8 INT_8 = new LayoutInt8(); - public static final LayoutMongoDbObjectId MONGODB_OBJECT_ID = new LayoutMongoDbObjectId(); - public static final LayoutNull NULL = new LayoutNull(); - public static final LayoutNullable NULLABLE = new LayoutNullable(false); - public static final LayoutObject OBJECT = new LayoutObject(false); - public static final LayoutTagged TAGGED = new LayoutTagged(false); - public static final LayoutTagged2 TAGGED_2 = new LayoutTagged2(false); - public static final LayoutTuple TUPLE = new LayoutTuple(false); - public static final LayoutTypedArray TYPED_ARRAY = new LayoutTypedArray(false); - public static final LayoutTypedMap TYPED_MAP = new LayoutTypedMap(false); - public static final LayoutTypedSet TYPED_SET = new LayoutTypedSet(false); - public static final LayoutTypedTuple TYPED_TUPLE = new LayoutTypedTuple(false); - public static final LayoutUDT UDT = new LayoutUDT(false); - public static final LayoutUInt16 UINT_16 = new LayoutUInt16(); - public static final LayoutUInt32 UINT_32 = new LayoutUInt32(); - public static final LayoutUInt64 UINT_64 = new LayoutUInt64(); - public static final LayoutUInt8 UINT_8 = new LayoutUInt8(); - public static final LayoutUnixDateTime UNIX_DATE_TIME = new LayoutUnixDateTime(); - public static final LayoutUtf8 UTF_8 = new LayoutUtf8(); - public static final LayoutVarInt VAR_INT = new LayoutVarInt(); - public static final LayoutVarUInt VAR_UINT = new LayoutVarUInt(); -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +/** + * Layout type definitions + */ +public abstract class LayoutTypes { + public static final LayoutArray ARRAY = new LayoutArray(false); + public static final LayoutBinary BINARY = new LayoutBinary(); + public static final LayoutBoolean BOOLEAN = new LayoutBoolean(true); + public static final LayoutBoolean BOOLEAN_FALSE = new LayoutBoolean(false); + public static final LayoutDateTime DATE_TIME = new LayoutDateTime(); + public static final LayoutDecimal DECIMAL = new LayoutDecimal(); + public static final LayoutEndScope END_SCOPE = new LayoutEndScope(); + public static final LayoutFloat128 FLOAT_128 = new LayoutFloat128(); + public static final LayoutFloat32 FLOAT_32 = new LayoutFloat32(); + public static final LayoutFloat64 FLOAT_64 = new LayoutFloat64(); + public static final LayoutGuid GUID = new LayoutGuid(); + public static final LayoutArray IMMUTABLE_ARRAY = new LayoutArray(true); + public static final LayoutNullable IMMUTABLE_NULLABLE = new LayoutNullable(true); + public static final LayoutObject IMMUTABLE_OBJECT = new LayoutObject(true); + public static final LayoutTagged IMMUTABLE_TAGGED = new LayoutTagged(true); + public static final LayoutTagged2 IMMUTABLE_TAGGED_2 = new LayoutTagged2(true); + public static final LayoutTuple ImmutableTuple = new LayoutTuple(true); + public static final LayoutTypedArray IMMUTABLE_TYPED_ARRAY = new LayoutTypedArray(true); + public static final LayoutTypedMap IMMUTABLE_TYPED_MAP = new LayoutTypedMap(true); + public static final LayoutTypedSet IMMUTABLE_TYPED_SET = new LayoutTypedSet(true); + public static final LayoutTypedTuple IMMUTABLE_TYPED_TUPLE = new LayoutTypedTuple(true); + public static final LayoutUDT IMMUTABLE_UDT = new LayoutUDT(true); + public static final LayoutInt16 INT_16 = new LayoutInt16(); + public static final LayoutInt32 INT_32 = new LayoutInt32(); + public static final LayoutInt64 INT_64 = new LayoutInt64(); + public static final LayoutInt8 INT_8 = new LayoutInt8(); + public static final LayoutMongoDbObjectId MONGODB_OBJECT_ID = new LayoutMongoDbObjectId(); + public static final LayoutNull NULL = new LayoutNull(); + public static final LayoutNullable NULLABLE = new LayoutNullable(false); + public static final LayoutObject OBJECT = new LayoutObject(false); + public static final LayoutTagged TAGGED = new LayoutTagged(false); + public static final LayoutTagged2 TAGGED_2 = new LayoutTagged2(false); + public static final LayoutTuple TUPLE = new LayoutTuple(false); + public static final LayoutTypedArray TYPED_ARRAY = new LayoutTypedArray(false); + public static final LayoutTypedMap TYPED_MAP = new LayoutTypedMap(false); + public static final LayoutTypedSet TYPED_SET = new LayoutTypedSet(false); + public static final LayoutTypedTuple TYPED_TUPLE = new LayoutTypedTuple(false); + public static final LayoutUDT UDT = new LayoutUDT(false); + public static final LayoutUInt16 UINT_16 = new LayoutUInt16(); + public static final LayoutUInt32 UINT_32 = new LayoutUInt32(); + public static final LayoutUInt64 UINT_64 = new LayoutUInt64(); + public static final LayoutUInt8 UINT_8 = new LayoutUInt8(); + public static final LayoutUnixDateTime UNIX_DATE_TIME = new LayoutUnixDateTime(); + public static final LayoutUtf8 UTF_8 = new LayoutUtf8(); + public static final LayoutVarInt VAR_INT = new LayoutVarInt(); + public static final LayoutVarUInt VAR_UINT = new LayoutVarUInt(); +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java index bb5da7d..99b5744 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java @@ -1,71 +1,71 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutUDT extends LayoutPropertyScope { - - public LayoutUDT(boolean immutable) { - super(immutable ? LayoutCode.IMMUTABLE_SCHEMA : LayoutCode.SCHEMA, immutable); - } - - @Override - public int countTypeArgument(@Nonnull TypeArgumentList value) { - checkNotNull(value, "expected non-null value"); - return LayoutCode.BYTES + SchemaId.BYTES; - } - - @Override - @Nonnull - public String name() { - return this.isImmutable() ? "im_udt" : "udt"; - } - - @Override - @Nonnull - public TypeArgumentList readTypeArgumentList(@Nonnull RowBuffer row, int offset, @Nonnull Out lengthInBytes) { - SchemaId schemaId = row.readSchemaId(offset); - lengthInBytes.set(SchemaId.BYTES); - return new TypeArgumentList(schemaId); - } - - @Override - @Nonnull - public Result writeScope(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull TypeArgumentList typeArgs, @Nonnull Out value) { - return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); - } - - @Override - @Nonnull - public Result writeScope(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull TypeArgumentList typeArgs, @Nonnull UpdateOptions options, - @Nonnull Out value) { - - Layout udt = buffer.resolver().resolve(typeArgs.schemaId()); - Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.writeSparseUDT(edit, this, udt, options)); - return Result.SUCCESS; - } - - @Override - public int writeTypeArgument(@Nonnull RowBuffer buffer, int offset, @Nonnull TypeArgumentList value) { - buffer.writeSparseTypeCode(offset, this.layoutCode()); - buffer.writeSchemaId(offset + LayoutCode.BYTES, value.schemaId()); - return LayoutCode.BYTES + SchemaId.BYTES; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutUDT extends LayoutPropertyScope { + + public LayoutUDT(boolean immutable) { + super(immutable ? LayoutCode.IMMUTABLE_SCHEMA : LayoutCode.SCHEMA, immutable); + } + + @Override + public int countTypeArgument(@Nonnull TypeArgumentList value) { + checkNotNull(value, "expected non-null value"); + return LayoutCode.BYTES + SchemaId.BYTES; + } + + @Override + @Nonnull + public String name() { + return this.isImmutable() ? "im_udt" : "udt"; + } + + @Override + @Nonnull + public TypeArgumentList readTypeArgumentList(@Nonnull RowBuffer row, int offset, @Nonnull Out lengthInBytes) { + SchemaId schemaId = row.readSchemaId(offset); + lengthInBytes.set(SchemaId.BYTES); + return new TypeArgumentList(schemaId); + } + + @Override + @Nonnull + public Result writeScope(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull TypeArgumentList typeArgs, @Nonnull Out value) { + return this.writeScope(buffer, edit, typeArgs, UpdateOptions.UPSERT, value); + } + + @Override + @Nonnull + public Result writeScope(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull TypeArgumentList typeArgs, @Nonnull UpdateOptions options, + @Nonnull Out value) { + + Layout udt = buffer.resolver().resolve(typeArgs.schemaId()); + Result result = prepareSparseWrite(buffer, edit, new TypeArgument(this, typeArgs), options); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.writeSparseUDT(edit, this, udt, options)); + return Result.SUCCESS; + } + + @Override + public int writeTypeArgument(@Nonnull RowBuffer buffer, int offset, @Nonnull TypeArgumentList value) { + buffer.writeSparseTypeCode(offset, this.layoutCode()); + buffer.writeSchemaId(offset + LayoutCode.BYTES, value.schemaId()); + return LayoutCode.BYTES + SchemaId.BYTES; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt16.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt16.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt16.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt16.java index b38cb19..424a8b7 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt16.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt16.java @@ -1,94 +1,94 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutUInt16 extends LayoutTypePrimitive { - - public LayoutUInt16() { - super(LayoutCode.UINT_16, Short.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "uint16"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0); - return Result.NOT_FOUND; - } - - value.set(buffer.readUInt16(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0); - return result; - } - - value.set(buffer.readSparseUInt16(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Integer value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeUInt16(scope.start() + column.offset(), value.shortValue()); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Integer value, @Nonnull UpdateOptions options) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseUInt16(edit, value.shortValue(), options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Integer value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutUInt16 extends LayoutTypePrimitive { + + public LayoutUInt16() { + super(LayoutCode.UINT_16, Short.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "uint16"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0); + return Result.NOT_FOUND; + } + + value.set(buffer.readUInt16(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0); + return result; + } + + value.set(buffer.readSparseUInt16(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Integer value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeUInt16(scope.start() + column.offset(), value.shortValue()); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Integer value, @Nonnull UpdateOptions options) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseUInt16(edit, value.shortValue(), options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Integer value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java index af9787a..be0b997 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java @@ -1,97 +1,97 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutUInt32 extends LayoutTypePrimitive { - - public LayoutUInt32() { - super(LayoutCode.UINT_32, Integer.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "uint32"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0L); - return Result.NOT_FOUND; - } - - value.set(buffer.readUInt32(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0L); - return result; - } - - value.set(buffer.readSparseUInt32(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeUInt32(scope.start() + column.offset(), value.intValue()); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - if (result != Result.SUCCESS) { - return result; - } - buffer.writeSparseUInt32(edit, value.intValue(), options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutUInt32 extends LayoutTypePrimitive { + + public LayoutUInt32() { + super(LayoutCode.UINT_32, Integer.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "uint32"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0L); + return Result.NOT_FOUND; + } + + value.set(buffer.readUInt32(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0L); + return result; + } + + value.set(buffer.readSparseUInt32(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeUInt32(scope.start() + column.offset(), value.intValue()); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + if (result != Result.SUCCESS) { + return result; + } + buffer.writeSparseUInt32(edit, value.intValue(), options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt64.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt64.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt64.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt64.java index 1de6ce0..a4bf6e9 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt64.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt64.java @@ -1,95 +1,95 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutUInt64 extends LayoutTypePrimitive { - - public LayoutUInt64() { - super(LayoutCode.UINT_64, Long.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "uint64"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0L); - return Result.NOT_FOUND; - } - - value.set(buffer.readUInt64(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0L); - return result; - } - - value.set(buffer.readSparseUInt64(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeUInt64(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseUInt64(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutUInt64 extends LayoutTypePrimitive { + + public LayoutUInt64() { + super(LayoutCode.UINT_64, Long.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "uint64"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0L); + return Result.NOT_FOUND; + } + + value.set(buffer.readUInt64(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0L); + return result; + } + + value.set(buffer.readSparseUInt64(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeUInt64(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseUInt64(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java index 1d678a5..2d34981 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java @@ -1,101 +1,101 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutUInt8 extends LayoutTypePrimitive { - - public LayoutUInt8() { - super(LayoutCode.UINT_8, 1); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "uint8"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set((short) 0); - return Result.NOT_FOUND; - } - - value.set(buffer.readUInt8(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set((short) 0); - return result; - } - - value.set(buffer.readSparseUInt8(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Short value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeUInt8(scope.start() + column.offset(), value.byteValue()); - buffer.setBit(scope.start(), column.nullBit()); - - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Short value, @Nonnull UpdateOptions options) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseUInt8(edit, value.byteValue(), options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Short value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutUInt8 extends LayoutTypePrimitive { + + public LayoutUInt8() { + super(LayoutCode.UINT_8, 1); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "uint8"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set((short) 0); + return Result.NOT_FOUND; + } + + value.set(buffer.readUInt8(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set((short) 0); + return result; + } + + value.set(buffer.readSparseUInt8(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Short value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeUInt8(scope.start() + column.offset(), value.byteValue()); + buffer.setBit(scope.start(), column.nullBit()); + + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Short value, @Nonnull UpdateOptions options) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseUInt8(edit, value.byteValue(), options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Short value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUniqueScope.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUniqueScope.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUniqueScope.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUniqueScope.java index fa34ff4..46d15f4 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUniqueScope.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUniqueScope.java @@ -1,151 +1,151 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.azure.data.cosmos.serialization.hybridrow.RowCursors; -import com.azure.data.cosmos.serialization.hybridrow.RowOptions; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkNotNull; - -public abstract class LayoutUniqueScope extends LayoutIndexedScope implements ILayoutType { - - protected LayoutUniqueScope(LayoutCode code, boolean immutable, boolean isSizedScope, boolean isTypedScope) { - super(code, immutable, isSizedScope, false, true, isTypedScope); - } - - @Nonnull - public abstract TypeArgument fieldType(@Nonnull RowCursor scope); - - /** - * Search for a matching field within a unique index. - *

- * The pattern field is deleted whether the find succeeds or fails. - * - * @param buffer The row to search. - * @param scope The parent unique index edit to search. - * @param patternScope The parent edit from which the match pattern is read. - * @param value If successful, the updated edit. - * @return Success a matching field exists in the unique index, NotFound if no match is found, the error code - * otherwise. - */ - @Nonnull - public final Result find(RowBuffer buffer, RowCursor scope, RowCursor patternScope, Out value) { - - Result result = LayoutType.prepareSparseMove(buffer, scope, this, this.fieldType(scope), patternScope, - UpdateOptions.UPDATE, value); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.deleteSparse(patternScope); - return Result.SUCCESS; - } - - /** - * Moves an existing sparse field into the unique index. - *

- * The source field MUST be a field whose type arguments match the element type of the destination unique index. - * The source field is deleted whether the move succeeds or fails. - * - * @param buffer The row to move within. - * @param destinationScope The parent unique indexed edit into which the field should be moved. - * @param sourceEdit The field to be moved. - * @param options The move options. - * @return Success if the field is permitted within the unique index, the error code otherwise. - */ - @Nonnull - public final Result moveField( - RowBuffer buffer, RowCursor destinationScope, RowCursor sourceEdit, UpdateOptions options) { - - Out dstEdit = new Out<>(); - - Result result = LayoutType.prepareSparseMove( - buffer, destinationScope, this, this.fieldType(destinationScope), sourceEdit, options, dstEdit); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.typedCollectionMoveField(dstEdit.get(), sourceEdit, RowOptions.from(options.value())); - - // TODO: it would be "better" if the destinationScope were updated to point to the highest item seen. Then we - // would avoid the maximum reparse - - destinationScope.count(dstEdit.get().count()); - return Result.SUCCESS; - } - - /** - * Moves an existing sparse field into the unique index. - * - * @param buffer The row to move within. - * @param destinationScope The parent unique indexed edit into which the field should be moved. - * @param sourceEdit The field to be moved. - * @return {@link Result#SUCCESS} if the field is moved; an error {@link Result} otherwise. - *

- * The source field MUST be a field whose type arguments match the element type of the - * destination unique index. - *

- * The source field is deleted whether the move succeeds or fails. - */ - @Nonnull - public final Result moveField( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor destinationScope, - @Nonnull final RowCursor sourceEdit) { - return this.moveField(buffer, destinationScope, sourceEdit, UpdateOptions.UPSERT); - } - - @Override - @Nonnull - public Result writeScope( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final TypeArgumentList typeArgs, - @Nullable final TContext context, - @Nullable final WriterFunc func, - @Nonnull final UpdateOptions options) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(typeArgs, "expected non-null typeArgs"); - checkNotNull(options, "expected non-null options"); - - final Out uniqueScope = new Out<>(); - Result result; - - result = this.writeScope(buffer, scope, typeArgs, options, uniqueScope); - - if (result != Result.SUCCESS) { - return result; - } - - RowCursor childScope = uniqueScope.get().deferUniqueIndex(true); - result = func == null ? null : func.invoke(buffer, childScope, context); - - if (result != null && result != Result.SUCCESS) { - this.deleteScope(buffer, scope); - return result; - } - - uniqueScope.get().count(childScope.count()); - result = buffer.typedCollectionUniqueIndexRebuild(uniqueScope.get()); - - if (result != Result.SUCCESS) { - this.deleteScope(buffer, scope); - return result; - } - - RowCursors.skip(scope, buffer, childScope); - return Result.SUCCESS; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.azure.data.cosmos.serialization.hybridrow.RowCursors; +import com.azure.data.cosmos.serialization.hybridrow.RowOptions; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +public abstract class LayoutUniqueScope extends LayoutIndexedScope implements ILayoutType { + + protected LayoutUniqueScope(LayoutCode code, boolean immutable, boolean isSizedScope, boolean isTypedScope) { + super(code, immutable, isSizedScope, false, true, isTypedScope); + } + + @Nonnull + public abstract TypeArgument fieldType(@Nonnull RowCursor scope); + + /** + * Search for a matching field within a unique index. + *

+ * The pattern field is deleted whether the find succeeds or fails. + * + * @param buffer The row to search. + * @param scope The parent unique index edit to search. + * @param patternScope The parent edit from which the match pattern is read. + * @param value If successful, the updated edit. + * @return Success a matching field exists in the unique index, NotFound if no match is found, the error code + * otherwise. + */ + @Nonnull + public final Result find(RowBuffer buffer, RowCursor scope, RowCursor patternScope, Out value) { + + Result result = LayoutType.prepareSparseMove(buffer, scope, this, this.fieldType(scope), patternScope, + UpdateOptions.UPDATE, value); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.deleteSparse(patternScope); + return Result.SUCCESS; + } + + /** + * Moves an existing sparse field into the unique index. + *

+ * The source field MUST be a field whose type arguments match the element type of the destination unique index. + * The source field is deleted whether the move succeeds or fails. + * + * @param buffer The row to move within. + * @param destinationScope The parent unique indexed edit into which the field should be moved. + * @param sourceEdit The field to be moved. + * @param options The move options. + * @return Success if the field is permitted within the unique index, the error code otherwise. + */ + @Nonnull + public final Result moveField( + RowBuffer buffer, RowCursor destinationScope, RowCursor sourceEdit, UpdateOptions options) { + + Out dstEdit = new Out<>(); + + Result result = LayoutType.prepareSparseMove( + buffer, destinationScope, this, this.fieldType(destinationScope), sourceEdit, options, dstEdit); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.typedCollectionMoveField(dstEdit.get(), sourceEdit, RowOptions.from(options.value())); + + // TODO: it would be "better" if the destinationScope were updated to point to the highest item seen. Then we + // would avoid the maximum reparse + + destinationScope.count(dstEdit.get().count()); + return Result.SUCCESS; + } + + /** + * Moves an existing sparse field into the unique index. + * + * @param buffer The row to move within. + * @param destinationScope The parent unique indexed edit into which the field should be moved. + * @param sourceEdit The field to be moved. + * @return {@link Result#SUCCESS} if the field is moved; an error {@link Result} otherwise. + *

+ * The source field MUST be a field whose type arguments match the element type of the + * destination unique index. + *

+ * The source field is deleted whether the move succeeds or fails. + */ + @Nonnull + public final Result moveField( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor destinationScope, + @Nonnull final RowCursor sourceEdit) { + return this.moveField(buffer, destinationScope, sourceEdit, UpdateOptions.UPSERT); + } + + @Override + @Nonnull + public Result writeScope( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final TypeArgumentList typeArgs, + @Nullable final TContext context, + @Nullable final WriterFunc func, + @Nonnull final UpdateOptions options) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(typeArgs, "expected non-null typeArgs"); + checkNotNull(options, "expected non-null options"); + + final Out uniqueScope = new Out<>(); + Result result; + + result = this.writeScope(buffer, scope, typeArgs, options, uniqueScope); + + if (result != Result.SUCCESS) { + return result; + } + + RowCursor childScope = uniqueScope.get().deferUniqueIndex(true); + result = func == null ? null : func.invoke(buffer, childScope, context); + + if (result != null && result != Result.SUCCESS) { + this.deleteScope(buffer, scope); + return result; + } + + uniqueScope.get().count(childScope.count()); + result = buffer.typedCollectionUniqueIndexRebuild(uniqueScope.get()); + + if (result != Result.SUCCESS) { + this.deleteScope(buffer, scope); + return result; + } + + RowCursors.skip(scope, buffer, childScope); + return Result.SUCCESS; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUnixDateTime.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUnixDateTime.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUnixDateTime.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUnixDateTime.java index 7e827eb..c112619 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUnixDateTime.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUnixDateTime.java @@ -1,94 +1,94 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; -import com.azure.data.cosmos.serialization.hybridrow.UnixDateTime; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutUnixDateTime extends LayoutTypePrimitive { - - public LayoutUnixDateTime() { - super(LayoutCode.UNIX_DATE_TIME, UnixDateTime.BYTES); - } - - public boolean isFixed() { - return true; - } - - @Nonnull - public String name() { - return "unixdatetime"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(null); - return Result.NOT_FOUND; - } - - value.set(buffer.readUnixDateTime(scope.start() + column.offset())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.readSparseUnixDateTime(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull UnixDateTime value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeUnixDateTime(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull UnixDateTime value, @Nonnull UpdateOptions options) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseUnixDateTime(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull UnixDateTime value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; +import com.azure.data.cosmos.serialization.hybridrow.UnixDateTime; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutUnixDateTime extends LayoutTypePrimitive { + + public LayoutUnixDateTime() { + super(LayoutCode.UNIX_DATE_TIME, UnixDateTime.BYTES); + } + + public boolean isFixed() { + return true; + } + + @Nonnull + public String name() { + return "unixdatetime"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(null); + return Result.NOT_FOUND; + } + + value.set(buffer.readUnixDateTime(scope.start() + column.offset())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.readSparseUnixDateTime(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull UnixDateTime value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeUnixDateTime(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull UnixDateTime value, @Nonnull UpdateOptions options) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseUnixDateTime(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull UnixDateTime value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8.java index aaca359..5c8efd7 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8.java @@ -1,246 +1,246 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.core.Utf8String; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -public final class LayoutUtf8 extends LayoutTypePrimitive - implements LayoutUtf8Readable, LayoutUtf8Writable { - - public LayoutUtf8() { - super(LayoutCode.UTF_8, 0); - } - - public boolean isFixed() { - return false; - } - - @Nonnull - public String name() { - return "utf8"; - } - - @Override - @Nonnull - public Result readFixed( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final Out value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(column, "expected non-null column"); - checkNotNull(value, "expected non-null value"); - - Out span = new Out<>(); - Result result = this.readFixedSpan(buffer, scope, column, span); - value.set(result == Result.SUCCESS ? span.get().toUtf16() : null); - - return result; - } - - @Override - @Nonnull - public Result readFixedSpan( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - checkArgument(column.size() >= 0); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(null); - return Result.NOT_FOUND; - } - - value.set(buffer.readFixedString(scope.start() + column.offset(), column.size())); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readSparse( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final Out value) { - - Out span = new Out<>(); - Result result = this.readSparseSpan(buffer, edit, span); - value.set((result == Result.SUCCESS) ? span.get().toUtf16() : null); - return result; - } - - @Override - @Nonnull - public Result readSparseSpan( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor edit, - @Nonnull final Out value) { - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(null); - return result; - } - - value.set(buffer.readSparseString(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readVariable( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final Out value) { - - Out span = new Out<>(); - Result result = this.readVariableSpan(buffer, scope, column, span); - value.set(result == Result.SUCCESS ? span.get().toUtf16() : null); - return result; - } - - @Override - @Nonnull - public Result readVariableSpan( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(null); - return Result.NOT_FOUND; - } - - int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); - value.set(buffer.readVariableString(varOffset)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final String value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(column, "expected non-null column"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(value, "expected non-null value"); - - return this.writeFixed(buffer, scope, column, Utf8String.transcodeUtf16(value)); - } - - @Override - @Nonnull - public Result writeFixed( - @Nonnull final RowBuffer buffer, - @Nonnull final RowCursor scope, - @Nonnull final LayoutColumn column, - @Nonnull final Utf8String value) { - - checkNotNull(buffer, "expected non-null buffer"); - checkNotNull(column, "expected non-null column"); - checkNotNull(scope, "expected non-null scope"); - checkNotNull(value, "expected non-null value"); - - checkArgument(scope.scopeType() instanceof LayoutUDT); - checkArgument(column.size() >= 0); - checkArgument(value.encodedLength() == column.size()); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - buffer.writeFixedString(scope.start() + column.offset(), value); - buffer.setBit(scope.start(), column.nullBit()); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull String value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull String value, @Nonnull UpdateOptions options) { - checkArgument(value != null); - return this.writeSparse(buffer, edit, Utf8String.transcodeUtf16(value), options); - } - - @Override - @Nonnull - public Result writeSparse(RowBuffer buffer, RowCursor edit, Utf8String value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } - - @Override - @Nonnull - public Result writeSparse(RowBuffer buffer, RowCursor edit, Utf8String value, UpdateOptions options) { - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseString(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull String value) { - checkArgument(value != null); - return this.writeVariable(buffer, scope, column, Utf8String.transcodeUtf16(value)); - } - - @Override - @Nonnull - public Result writeVariable(RowBuffer buffer, RowCursor scope, LayoutColumn column, Utf8String value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - int length = value.encodedLength(); - - if ((column.size() > 0) && (length > column.size())) { - return Result.TOO_BIG; - } - - int offset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); - boolean exists = buffer.readBit(scope.start(), column.nullBit()); - int shift = buffer.writeVariableString(offset, value, exists); - - buffer.setBit(scope.start(), column.nullBit()); - scope.metaOffset(scope.metaOffset() + shift); - scope.valueOffset(scope.valueOffset() + shift); - - return Result.SUCCESS; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.core.Utf8String; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class LayoutUtf8 extends LayoutTypePrimitive + implements LayoutUtf8Readable, LayoutUtf8Writable { + + public LayoutUtf8() { + super(LayoutCode.UTF_8, 0); + } + + public boolean isFixed() { + return false; + } + + @Nonnull + public String name() { + return "utf8"; + } + + @Override + @Nonnull + public Result readFixed( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final Out value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + + Out span = new Out<>(); + Result result = this.readFixedSpan(buffer, scope, column, span); + value.set(result == Result.SUCCESS ? span.get().toUtf16() : null); + + return result; + } + + @Override + @Nonnull + public Result readFixedSpan( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + checkArgument(column.size() >= 0); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(null); + return Result.NOT_FOUND; + } + + value.set(buffer.readFixedString(scope.start() + column.offset(), column.size())); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readSparse( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final Out value) { + + Out span = new Out<>(); + Result result = this.readSparseSpan(buffer, edit, span); + value.set((result == Result.SUCCESS) ? span.get().toUtf16() : null); + return result; + } + + @Override + @Nonnull + public Result readSparseSpan( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor edit, + @Nonnull final Out value) { + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(null); + return result; + } + + value.set(buffer.readSparseString(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readVariable( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final Out value) { + + Out span = new Out<>(); + Result result = this.readVariableSpan(buffer, scope, column, span); + value.set(result == Result.SUCCESS ? span.get().toUtf16() : null); + return result; + } + + @Override + @Nonnull + public Result readVariableSpan( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(null); + return Result.NOT_FOUND; + } + + int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); + value.set(buffer.readVariableString(varOffset)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final String value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(column, "expected non-null column"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(value, "expected non-null value"); + + return this.writeFixed(buffer, scope, column, Utf8String.transcodeUtf16(value)); + } + + @Override + @Nonnull + public Result writeFixed( + @Nonnull final RowBuffer buffer, + @Nonnull final RowCursor scope, + @Nonnull final LayoutColumn column, + @Nonnull final Utf8String value) { + + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(column, "expected non-null column"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(value, "expected non-null value"); + + checkArgument(scope.scopeType() instanceof LayoutUDT); + checkArgument(column.size() >= 0); + checkArgument(value.encodedLength() == column.size()); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + buffer.writeFixedString(scope.start() + column.offset(), value); + buffer.setBit(scope.start(), column.nullBit()); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull String value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull String value, @Nonnull UpdateOptions options) { + checkArgument(value != null); + return this.writeSparse(buffer, edit, Utf8String.transcodeUtf16(value), options); + } + + @Override + @Nonnull + public Result writeSparse(RowBuffer buffer, RowCursor edit, Utf8String value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } + + @Override + @Nonnull + public Result writeSparse(RowBuffer buffer, RowCursor edit, Utf8String value, UpdateOptions options) { + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseString(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull String value) { + checkArgument(value != null); + return this.writeVariable(buffer, scope, column, Utf8String.transcodeUtf16(value)); + } + + @Override + @Nonnull + public Result writeVariable(RowBuffer buffer, RowCursor scope, LayoutColumn column, Utf8String value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + int length = value.encodedLength(); + + if ((column.size() > 0) && (length > column.size())) { + return Result.TOO_BIG; + } + + int offset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); + boolean exists = buffer.readBit(scope.start(), column.nullBit()); + int shift = buffer.writeVariableString(offset, value, exists); + + buffer.setBit(scope.start(), column.nullBit()); + scope.metaOffset(scope.metaOffset() + shift); + scope.valueOffset(scope.valueOffset() + shift); + + return Result.SUCCESS; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8Readable.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8Readable.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8Readable.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8Readable.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8Writable.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8Writable.java similarity index 100% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8Writable.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUtf8Writable.java diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarInt.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarInt.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarInt.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarInt.java index 2552dea..a4dc4d6 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarInt.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarInt.java @@ -1,120 +1,120 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutVarInt extends LayoutTypePrimitive { - - public LayoutVarInt() { - super(LayoutCode.VAR_INT, 0); - } - - public boolean isFixed() { - return false; - } - - public boolean isVarint() { - return true; - } - - @Nonnull - public String name() { - return "varint"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - assert false : "not implemented"; - value.set(0L); - return Result.FAILURE; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0L); - return result; - } - - value.set(buffer.readSparseVarInt(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0L); - return Result.NOT_FOUND; - } - - int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); - value.set(buffer.readVariableInt(varOffset)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { - assert false : "not implemented"; - return Result.FAILURE; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { - - Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseVarInt(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } - - @Override - @Nonnull - public Result writeVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - boolean exists = buffer.readBit(scope.start(), column.nullBit()); - int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); - int shift = buffer.writeVariableInt(varOffset, value, exists); - - buffer.setBit(scope.start(), column.nullBit()); - scope.metaOffset(scope.metaOffset() + shift); - scope.valueOffset(scope.valueOffset() + shift); - - return Result.SUCCESS; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutVarInt extends LayoutTypePrimitive { + + public LayoutVarInt() { + super(LayoutCode.VAR_INT, 0); + } + + public boolean isFixed() { + return false; + } + + public boolean isVarint() { + return true; + } + + @Nonnull + public String name() { + return "varint"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + assert false : "not implemented"; + value.set(0L); + return Result.FAILURE; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = LayoutType.prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0L); + return result; + } + + value.set(buffer.readSparseVarInt(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0L); + return Result.NOT_FOUND; + } + + int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); + value.set(buffer.readVariableInt(varOffset)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { + assert false : "not implemented"; + return Result.FAILURE; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { + + Result result = LayoutType.prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseVarInt(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } + + @Override + @Nonnull + public Result writeVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + boolean exists = buffer.readBit(scope.start(), column.nullBit()); + int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); + int shift = buffer.writeVariableInt(varOffset, value, exists); + + buffer.setBit(scope.start(), column.nullBit()); + scope.metaOffset(scope.metaOffset() + shift); + scope.valueOffset(scope.valueOffset() + shift); + + return Result.SUCCESS; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarUInt.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarUInt.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarUInt.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarUInt.java index 9ee81df..6372442 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarUInt.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutVarUInt.java @@ -1,120 +1,120 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Out; -import com.azure.data.cosmos.serialization.hybridrow.Result; -import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; -import com.azure.data.cosmos.serialization.hybridrow.RowCursor; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkArgument; - -public final class LayoutVarUInt extends LayoutTypePrimitive { - - public LayoutVarUInt() { - super(LayoutCode.VAR_UINT, 0); - } - - public boolean isFixed() { - return false; - } - - public boolean isVarint() { - return true; - } - - @Nonnull - public String name() { - return "varuint"; - } - - @Override - @Nonnull - public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - assert false : "not implemented"; - value.set(0L); - return Result.FAILURE; - } - - @Override - @Nonnull - public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { - - Result result = prepareSparseRead(buffer, edit, this.layoutCode()); - - if (result != Result.SUCCESS) { - value.set(0L); - return result; - } - - value.set(buffer.readSparseVarUInt(edit)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result readVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (!buffer.readBit(scope.start(), column.nullBit())) { - value.set(0L); - return Result.NOT_FOUND; - } - - int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); - value.set(buffer.readVariableUInt(varOffset)); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { - assert false : "not implemented"; - return Result.FAILURE; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { - - Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); - - if (result != Result.SUCCESS) { - return result; - } - - buffer.writeSparseVarUInt(edit, value, options); - return Result.SUCCESS; - } - - @Override - @Nonnull - public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { - return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); - } - - @Override - @Nonnull - public Result writeVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn col, @Nonnull Long value) { - - checkArgument(scope.scopeType() instanceof LayoutUDT); - - if (scope.immutable()) { - return Result.INSUFFICIENT_PERMISSIONS; - } - - final boolean exists = buffer.readBit(scope.start(), col.nullBit()); - final int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), col.offset()); - final int shift = buffer.writeVariableUInt(varOffset, value, exists); - - buffer.setBit(scope.start(), col.nullBit()); - scope.metaOffset(scope.metaOffset() + shift); - scope.valueOffset(scope.valueOffset() + shift); - - return Result.SUCCESS; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Out; +import com.azure.data.cosmos.serialization.hybridrow.Result; +import com.azure.data.cosmos.serialization.hybridrow.RowBuffer; +import com.azure.data.cosmos.serialization.hybridrow.RowCursor; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class LayoutVarUInt extends LayoutTypePrimitive { + + public LayoutVarUInt() { + super(LayoutCode.VAR_UINT, 0); + } + + public boolean isFixed() { + return false; + } + + public boolean isVarint() { + return true; + } + + @Nonnull + public String name() { + return "varuint"; + } + + @Override + @Nonnull + public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + assert false : "not implemented"; + value.set(0L); + return Result.FAILURE; + } + + @Override + @Nonnull + public Result readSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Out value) { + + Result result = prepareSparseRead(buffer, edit, this.layoutCode()); + + if (result != Result.SUCCESS) { + value.set(0L); + return result; + } + + value.set(buffer.readSparseVarUInt(edit)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result readVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (!buffer.readBit(scope.start(), column.nullBit())) { + value.set(0L); + return Result.NOT_FOUND; + } + + int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), column.offset()); + value.set(buffer.readVariableUInt(varOffset)); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Long value) { + assert false : "not implemented"; + return Result.FAILURE; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value, @Nonnull UpdateOptions options) { + + Result result = prepareSparseWrite(buffer, edit, this.typeArg(), options); + + if (result != Result.SUCCESS) { + return result; + } + + buffer.writeSparseVarUInt(edit, value, options); + return Result.SUCCESS; + } + + @Override + @Nonnull + public Result writeSparse(@Nonnull RowBuffer buffer, @Nonnull RowCursor edit, @Nonnull Long value) { + return this.writeSparse(buffer, edit, value, UpdateOptions.UPSERT); + } + + @Override + @Nonnull + public Result writeVariable(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn col, @Nonnull Long value) { + + checkArgument(scope.scopeType() instanceof LayoutUDT); + + if (scope.immutable()) { + return Result.INSUFFICIENT_PERMISSIONS; + } + + final boolean exists = buffer.readBit(scope.start(), col.nullBit()); + final int varOffset = buffer.computeVariableValueOffset(scope.layout(), scope.start(), col.offset()); + final int shift = buffer.writeVariableUInt(varOffset, value, exists); + + buffer.setBit(scope.start(), col.nullBit()); + scope.metaOffset(scope.metaOffset() + shift); + scope.valueOffset(scope.valueOffset() + shift); + + return Result.SUCCESS; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringToken.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringToken.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringToken.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringToken.java index 0aaf8d0..ac7ec39 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringToken.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringToken.java @@ -1,93 +1,93 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Utf8String; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkNotNull; - -public final class StringToken implements Cloneable { - - public static final StringToken NONE = new StringToken(); - - private final long id; - private final Utf8String path; - private final ByteBuf varint; - - public StringToken(long id, @Nonnull Utf8String path) { - - checkNotNull(path); - - byte[] buffer = new byte[count7BitEncodedUInt(id)]; - StringToken.write7BitEncodedUInt(buffer, id); - - this.varint = Unpooled.wrappedBuffer(buffer).asReadOnly(); - this.path = path; - this.id = id; - } - - private StringToken() { - this.id = 0L; - this.path = Utf8String.EMPTY; - this.varint = Unpooled.wrappedBuffer(new byte[1]).asReadOnly(); - } - - public boolean isNull() { - return this.varint() == null; - } - - public long id() { - return this.id; - } - - public Utf8String path() { - return this.path; - } - - public ByteBuf varint() { - return this.varint; - } - - @Override - protected StringToken clone() { - - try { - return (StringToken) super.clone(); - } catch (CloneNotSupportedException error) { - assert false : error; - throw new IllegalStateException(error); - } - } - - private static int count7BitEncodedUInt(long value) { - - // Count the number of bytes needed to write out an int 7 bits at a time. - int i = 0; - - while (value >= 0x80L) { - i++; - value >>>= 7; - } - - return ++i; - } - - private static int write7BitEncodedUInt(byte[] buffer, long value) { - - // Write an unsigned long 7 bits at a time. The high bit of the byte, when set, indicates there are more bytes. - int i = 0; - - while (value >= 0x80L) { - buffer[i++] = (byte) (value | 0x80); - value >>>= 7; - } - - buffer[i++] = (byte) value; - return i; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Utf8String; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class StringToken implements Cloneable { + + public static final StringToken NONE = new StringToken(); + + private final long id; + private final Utf8String path; + private final ByteBuf varint; + + public StringToken(long id, @Nonnull Utf8String path) { + + checkNotNull(path); + + byte[] buffer = new byte[count7BitEncodedUInt(id)]; + StringToken.write7BitEncodedUInt(buffer, id); + + this.varint = Unpooled.wrappedBuffer(buffer).asReadOnly(); + this.path = path; + this.id = id; + } + + private StringToken() { + this.id = 0L; + this.path = Utf8String.EMPTY; + this.varint = Unpooled.wrappedBuffer(new byte[1]).asReadOnly(); + } + + public boolean isNull() { + return this.varint() == null; + } + + public long id() { + return this.id; + } + + public Utf8String path() { + return this.path; + } + + public ByteBuf varint() { + return this.varint; + } + + @Override + protected StringToken clone() { + + try { + return (StringToken) super.clone(); + } catch (CloneNotSupportedException error) { + assert false : error; + throw new IllegalStateException(error); + } + } + + private static int count7BitEncodedUInt(long value) { + + // Count the number of bytes needed to write out an int 7 bits at a time. + int i = 0; + + while (value >= 0x80L) { + i++; + value >>>= 7; + } + + return ++i; + } + + private static int write7BitEncodedUInt(byte[] buffer, long value) { + + // Write an unsigned long 7 bits at a time. The high bit of the byte, when set, indicates there are more bytes. + int i = 0; + + while (value >= 0x80L) { + buffer[i++] = (byte) (value | 0x80); + value >>>= 7; + } + + buffer[i++] = (byte) value; + return i; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringTokenizer.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringTokenizer.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringTokenizer.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringTokenizer.java index 61c10c9..809b0ef 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringTokenizer.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/StringTokenizer.java @@ -1,110 +1,110 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Utf8String; -import com.azure.data.cosmos.core.UtfAnyString; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; - -public final class StringTokenizer { - - private final HashMap stringTokens; - private final ArrayList strings; - private final HashMap tokens; - - private int count; - - /** - * Initializes a new instance of the {@link StringTokenizer} class. - */ - public StringTokenizer() { - - this.tokens = new HashMap<>(); - this.tokens.put(Utf8String.EMPTY, StringToken.NONE); - - this.stringTokens = new HashMap<>(); - this.stringTokens.put("", StringToken.NONE); - - this.strings = new ArrayList<>(); - this.strings.add(Utf8String.EMPTY); - - this.count = 1; - } - - /** - * Looks up a token's corresponding string. - * - * @param token The token to look up. - * @return True if successful, false otherwise. - */ - public Optional tryFindString(long token) { - return token >= (long)this.strings.size() ? Optional.empty() : Optional.of(this.strings.get((int) token)); - } - - /** - * Looks up a string's corresponding token. - * - * @param path The string to look up. - * @return {@code true} if successful, {@code false} otherwise. - */ - public Optional tryFindToken(UtfAnyString path) { - - if (path.isNull()) { - return Optional.empty(); - } - - if (path.isUtf8()) { - return Optional.ofNullable(this.tokens.get(path.toUtf8())); - } - - return Optional.ofNullable(this.stringTokens.get(path.toUtf16())); - } - - /** - * Assign a token to a string - *

- * If the string already has a token, that token is returned instead. - * - * @param path The string to assign a new token. - * @return The token assigned to the string. - */ - public StringToken add(Utf8String path) { - checkArgument(path != null); - final StringToken token = this.tokens.get(path); - return token == null ? this.allocateToken(path) : token; - } - - /** - * The number of unique tokens described by the encoding. - * - * @return the number of unique tokens described by the encoding. - */ - public int count() { - return this.count; - } - - /** - * Allocates a new token and assigns the string to it. - * - * @param path The string that needs a new token. - * @return The new allocated token. - */ - private StringToken allocateToken(Utf8String path) { - - final StringToken token = new StringToken(this.count++, path); - - this.stringTokens.put(path.toUtf16(), token); - this.tokens.put(path, token); - this.strings.add(path); - - checkState((long)this.strings.size() - 1 == token.id()); - return token; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Utf8String; +import com.azure.data.cosmos.core.UtfAnyString; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +public final class StringTokenizer { + + private final HashMap stringTokens; + private final ArrayList strings; + private final HashMap tokens; + + private int count; + + /** + * Initializes a new instance of the {@link StringTokenizer} class. + */ + public StringTokenizer() { + + this.tokens = new HashMap<>(); + this.tokens.put(Utf8String.EMPTY, StringToken.NONE); + + this.stringTokens = new HashMap<>(); + this.stringTokens.put("", StringToken.NONE); + + this.strings = new ArrayList<>(); + this.strings.add(Utf8String.EMPTY); + + this.count = 1; + } + + /** + * Looks up a token's corresponding string. + * + * @param token The token to look up. + * @return True if successful, false otherwise. + */ + public Optional tryFindString(long token) { + return token >= (long)this.strings.size() ? Optional.empty() : Optional.of(this.strings.get((int) token)); + } + + /** + * Looks up a string's corresponding token. + * + * @param path The string to look up. + * @return {@code true} if successful, {@code false} otherwise. + */ + public Optional tryFindToken(UtfAnyString path) { + + if (path.isNull()) { + return Optional.empty(); + } + + if (path.isUtf8()) { + return Optional.ofNullable(this.tokens.get(path.toUtf8())); + } + + return Optional.ofNullable(this.stringTokens.get(path.toUtf16())); + } + + /** + * Assign a token to a string + *

+ * If the string already has a token, that token is returned instead. + * + * @param path The string to assign a new token. + * @return The token assigned to the string. + */ + public StringToken add(Utf8String path) { + checkArgument(path != null); + final StringToken token = this.tokens.get(path); + return token == null ? this.allocateToken(path) : token; + } + + /** + * The number of unique tokens described by the encoding. + * + * @return the number of unique tokens described by the encoding. + */ + public int count() { + return this.count; + } + + /** + * Allocates a new token and assigns the string to it. + * + * @param path The string that needs a new token. + * @return The new allocated token. + */ + private StringToken allocateToken(Utf8String path) { + + final StringToken token = new StringToken(this.count++, path); + + this.stringTokens.put(path.toUtf16(), token); + this.tokens.put(path, token); + this.strings.add(path); + + checkState((long)this.strings.size() - 1 == token.id()); + return token; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchema.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchema.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchema.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchema.java index 5cb455f..d37e4a7 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchema.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchema.java @@ -1,85 +1,85 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.azure.data.cosmos.serialization.hybridrow.schemas.Namespace; -import com.google.common.base.Suppliers; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.security.CodeSource; -import java.util.Enumeration; -import java.util.Optional; -import java.util.function.Supplier; - -import static com.google.common.base.Strings.lenientFormat; - -public final class SystemSchema { - - public static final String specificationTitle = "HybridRow serialization library"; - - /** - * SchemaId of the empty schema. This schema has no defined cells but can accomodate - * unschematized sparse content. - */ - public static final SchemaId EMPTY_SCHEMA_ID = SchemaId.from(2147473650); - - /** - * SchemaId of HybridRow RecordIO Record Headers. - */ - public static final SchemaId RECORD_SCHEMA_ID = SchemaId.from(2147473649); - - /** - * SchemaId of HybridRow RecordIO Segments. - */ - public static final SchemaId SEGMENT_SCHEMA_ID = SchemaId.from(2147473648); - - private static final Supplier layoutResolver = Suppliers.memoize(() -> { - - final Optional namespace; - - try (final InputStream stream = getResourceAsStream("SystemSchema.json")) { - namespace = Namespace.parse(stream); - } catch (IOException cause) { - String message = lenientFormat("failed to initialize %s due to %s", SystemSchema.class, cause); - throw new IllegalStateException(message, cause); - } - - return new LayoutResolverNamespace(namespace.orElseThrow(() -> { - String message = lenientFormat( - "failed to initialize %s due to system schema parse error", - SystemSchema.class); - return new IllegalStateException(message); - })); - }); - - private SystemSchema() { - } - - public static LayoutResolver layoutResolver() { - return layoutResolver.get(); - } - - private static InputStream getResourceAsStream(final String name) throws IOException { - - final CodeSource codeSource = SystemSchema.class.getProtectionDomain().getCodeSource(); - final ClassLoader classLoader = SystemSchema.class.getClassLoader(); - final String location = codeSource.getLocation().toString(); - final Enumeration urls; - - urls = classLoader.getResources(name); - - while (urls.hasMoreElements()) { - final URL url = urls.nextElement(); - if (url.getFile().endsWith(name)) { - return url.openStream(); - } - } - - throw new FileNotFoundException(lenientFormat("cannot find %s at %s", name, location)); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.azure.data.cosmos.serialization.hybridrow.schemas.Namespace; +import com.google.common.base.Suppliers; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.CodeSource; +import java.util.Enumeration; +import java.util.Optional; +import java.util.function.Supplier; + +import static com.google.common.base.Strings.lenientFormat; + +public final class SystemSchema { + + public static final String specificationTitle = "HybridRow serialization library"; + + /** + * SchemaId of the empty schema. This schema has no defined cells but can accomodate + * unschematized sparse content. + */ + public static final SchemaId EMPTY_SCHEMA_ID = SchemaId.from(2147473650); + + /** + * SchemaId of HybridRow RecordIO Record Headers. + */ + public static final SchemaId RECORD_SCHEMA_ID = SchemaId.from(2147473649); + + /** + * SchemaId of HybridRow RecordIO Segments. + */ + public static final SchemaId SEGMENT_SCHEMA_ID = SchemaId.from(2147473648); + + private static final Supplier layoutResolver = Suppliers.memoize(() -> { + + final Optional namespace; + + try (final InputStream stream = getResourceAsStream("SystemSchema.json")) { + namespace = Namespace.parse(stream); + } catch (IOException cause) { + String message = lenientFormat("failed to initialize %s due to %s", SystemSchema.class, cause); + throw new IllegalStateException(message, cause); + } + + return new LayoutResolverNamespace(namespace.orElseThrow(() -> { + String message = lenientFormat( + "failed to initialize %s due to system schema parse error", + SystemSchema.class); + return new IllegalStateException(message); + })); + }); + + private SystemSchema() { + } + + public static LayoutResolver layoutResolver() { + return layoutResolver.get(); + } + + private static InputStream getResourceAsStream(final String name) throws IOException { + + final CodeSource codeSource = SystemSchema.class.getProtectionDomain().getCodeSource(); + final ClassLoader classLoader = SystemSchema.class.getClassLoader(); + final String location = codeSource.getLocation().toString(); + final Enumeration urls; + + urls = classLoader.getResources(name); + + while (urls.hasMoreElements()) { + final URL url = urls.nextElement(); + if (url.getFile().endsWith(name)) { + return url.openStream(); + } + } + + throw new FileNotFoundException(lenientFormat("cannot find %s at %s", name, location)); + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgument.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgument.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgument.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgument.java index da4c560..d775c8d 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgument.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgument.java @@ -1,90 +1,90 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Json; -import com.fasterxml.jackson.annotation.JsonProperty; - -import javax.annotation.Nonnull; - -import static com.google.common.base.Preconditions.checkNotNull; - -public final class TypeArgument { - - public static final TypeArgument NONE = new TypeArgument(); - - private final LayoutType type; - - @JsonProperty - private final TypeArgumentList typeArgs; - - /** - * Initializes a new instance of the {@link TypeArgument} struct. - * - * @param type the type of the constraint. - */ - public TypeArgument(@Nonnull LayoutType type) { - checkNotNull(type, "expected non-null type"); - this.type = type; - this.typeArgs = TypeArgumentList.EMPTY; - } - - /** - * Initializes a new instance of the {@link TypeArgument} struct. - * - * @param type The type of the constraint. - * @param typeArgs For generic types the type parameters. - */ - public TypeArgument(@Nonnull LayoutType type, @Nonnull TypeArgumentList typeArgs) { - checkNotNull(type, "expected non-null type"); - checkNotNull(type, "expected non-null typeArgs"); - this.type = type; - this.typeArgs = typeArgs; - } - - private TypeArgument() { - this.type = null; - this.typeArgs = null; - } - - @Override - public boolean equals(Object other) { - if (null == other) { - return false; - } - return other.getClass() == TypeArgument.class && this.equals((TypeArgument) other); - } - - public boolean equals(TypeArgument other) { - return this.type.equals(other.type) && this.typeArgs.equals(other.typeArgs); - } - - @Override - public int hashCode() { - return (this.type.hashCode() * 397) ^ this.typeArgs.hashCode(); - } - - @Override - public String toString() { - return Json.toString(this); - } - - /** - * The physical layout type. - * - * @return the physical layout type. - */ - public LayoutType type() { - return this.type; - } - - /** - * If the type argument is itself generic, then its type arguments. - * - * @return it the type argument is itself generic, then its type arguments. - */ - public TypeArgumentList typeArgs() { - return this.typeArgs; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Json; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class TypeArgument { + + public static final TypeArgument NONE = new TypeArgument(); + + private final LayoutType type; + + @JsonProperty + private final TypeArgumentList typeArgs; + + /** + * Initializes a new instance of the {@link TypeArgument} struct. + * + * @param type the type of the constraint. + */ + public TypeArgument(@Nonnull LayoutType type) { + checkNotNull(type, "expected non-null type"); + this.type = type; + this.typeArgs = TypeArgumentList.EMPTY; + } + + /** + * Initializes a new instance of the {@link TypeArgument} struct. + * + * @param type The type of the constraint. + * @param typeArgs For generic types the type parameters. + */ + public TypeArgument(@Nonnull LayoutType type, @Nonnull TypeArgumentList typeArgs) { + checkNotNull(type, "expected non-null type"); + checkNotNull(type, "expected non-null typeArgs"); + this.type = type; + this.typeArgs = typeArgs; + } + + private TypeArgument() { + this.type = null; + this.typeArgs = null; + } + + @Override + public boolean equals(Object other) { + if (null == other) { + return false; + } + return other.getClass() == TypeArgument.class && this.equals((TypeArgument) other); + } + + public boolean equals(TypeArgument other) { + return this.type.equals(other.type) && this.typeArgs.equals(other.typeArgs); + } + + @Override + public int hashCode() { + return (this.type.hashCode() * 397) ^ this.typeArgs.hashCode(); + } + + @Override + public String toString() { + return Json.toString(this); + } + + /** + * The physical layout type. + * + * @return the physical layout type. + */ + public LayoutType type() { + return this.type; + } + + /** + * If the type argument is itself generic, then its type arguments. + * + * @return it the type argument is itself generic, then its type arguments. + */ + public TypeArgumentList typeArgs() { + return this.typeArgs; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgumentList.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgumentList.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgumentList.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgumentList.java index be164fe..39aeefb 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgumentList.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/TypeArgumentList.java @@ -1,156 +1,156 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.azure.data.cosmos.core.Json; -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; - -import javax.annotation.Nonnull; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import static com.google.common.base.Preconditions.checkNotNull; - -@JsonSerialize(using = TypeArgumentList.JsonSerializer.class) -public final class TypeArgumentList { - - public static final TypeArgumentList EMPTY = new TypeArgumentList(); - - private final TypeArgument[] args; - private final SchemaId schemaId; - - /** - * Initializes a new instance of the {@link TypeArgumentList} class. - * - * @param args arguments in the list. - */ - public TypeArgumentList(@Nonnull final TypeArgument... args) { - checkNotNull(args); - this.args = args; - this.schemaId = SchemaId.INVALID; - } - - /** - * Initializes a new instance of the {@link TypeArgumentList} class - * - * @param schemaId for UDT fields, the schema id of the nested layout - */ - public TypeArgumentList(@Nonnull final SchemaId schemaId) { - checkNotNull(schemaId); - this.args = EMPTY.args; - this.schemaId = schemaId; - } - - private TypeArgumentList() { - this.args = new TypeArgument[] {}; - this.schemaId = SchemaId.INVALID; - } - - /** - * Number of elements in this {@link TypeArgumentList} - *

- * @return number of arguments in the list - */ - public int count() { - return this.args.length; - } - - public boolean equals(TypeArgumentList other) { - if (null == other) { - return false; - } - if (this == other) { - return true; - } - return this.schemaId().equals(other.schemaId()) && Arrays.equals(this.args, other.args); - } - - @Override - public boolean equals(Object other) { - return other instanceof TypeArgumentList && this.equals((TypeArgumentList) other); - } - - /** - * Element at the specified position in this {@link TypeArgumentList} - *

- * @param index index of the element to return - * @return element at the specified position in this {@link TypeArgumentList} - */ - public TypeArgument get(int index) { - return this.args[index]; - } - - @Override - public int hashCode() { - - int hash = 19; - hash = (hash * 397) ^ this.schemaId().hashCode(); - - for (TypeArgument a : this.args) { - hash = (hash * 397) ^ a.hashCode(); - } - - return hash; - } - - public List list() { - return Collections.unmodifiableList(Arrays.asList(this.args)); - } - - /** - * For UDT fields, the schema id of the nested layout. - * - * @return for UDT fields, the Schema ID of the nested layout. - */ - public SchemaId schemaId() { - return this.schemaId; - } - - /** - * Stream for iterating over elements in this {@link TypeArgumentList} - *

- * @return a stream for iterating over elements in this {@link TypeArgumentList} - */ - public Stream stream() { - if (this.args.length == 0) { - return Stream.empty(); - } - return StreamSupport.stream(Arrays.spliterator(this.args), false); - } - - @Override - public String toString() { - return Json.toString(this); - } - - static class JsonSerializer extends StdSerializer { - - private JsonSerializer() { - super(TypeArgumentList.class); - } - - @Override - public void serialize(TypeArgumentList value, JsonGenerator generator, SerializerProvider provider) throws IOException { - - generator.writeStartObject(); - generator.writeObjectField("schemaId", value.schemaId); - generator.writeArrayFieldStart("args"); - - for (TypeArgument element : value.args) { - generator.writeString(element.toString()); - } - - generator.writeEndArray(); - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.azure.data.cosmos.core.Json; +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static com.google.common.base.Preconditions.checkNotNull; + +@JsonSerialize(using = TypeArgumentList.JsonSerializer.class) +public final class TypeArgumentList { + + public static final TypeArgumentList EMPTY = new TypeArgumentList(); + + private final TypeArgument[] args; + private final SchemaId schemaId; + + /** + * Initializes a new instance of the {@link TypeArgumentList} class. + * + * @param args arguments in the list. + */ + public TypeArgumentList(@Nonnull final TypeArgument... args) { + checkNotNull(args); + this.args = args; + this.schemaId = SchemaId.INVALID; + } + + /** + * Initializes a new instance of the {@link TypeArgumentList} class + * + * @param schemaId for UDT fields, the schema id of the nested layout + */ + public TypeArgumentList(@Nonnull final SchemaId schemaId) { + checkNotNull(schemaId); + this.args = EMPTY.args; + this.schemaId = schemaId; + } + + private TypeArgumentList() { + this.args = new TypeArgument[] {}; + this.schemaId = SchemaId.INVALID; + } + + /** + * Number of elements in this {@link TypeArgumentList} + *

+ * @return number of arguments in the list + */ + public int count() { + return this.args.length; + } + + public boolean equals(TypeArgumentList other) { + if (null == other) { + return false; + } + if (this == other) { + return true; + } + return this.schemaId().equals(other.schemaId()) && Arrays.equals(this.args, other.args); + } + + @Override + public boolean equals(Object other) { + return other instanceof TypeArgumentList && this.equals((TypeArgumentList) other); + } + + /** + * Element at the specified position in this {@link TypeArgumentList} + *

+ * @param index index of the element to return + * @return element at the specified position in this {@link TypeArgumentList} + */ + public TypeArgument get(int index) { + return this.args[index]; + } + + @Override + public int hashCode() { + + int hash = 19; + hash = (hash * 397) ^ this.schemaId().hashCode(); + + for (TypeArgument a : this.args) { + hash = (hash * 397) ^ a.hashCode(); + } + + return hash; + } + + public List list() { + return Collections.unmodifiableList(Arrays.asList(this.args)); + } + + /** + * For UDT fields, the schema id of the nested layout. + * + * @return for UDT fields, the Schema ID of the nested layout. + */ + public SchemaId schemaId() { + return this.schemaId; + } + + /** + * Stream for iterating over elements in this {@link TypeArgumentList} + *

+ * @return a stream for iterating over elements in this {@link TypeArgumentList} + */ + public Stream stream() { + if (this.args.length == 0) { + return Stream.empty(); + } + return StreamSupport.stream(Arrays.spliterator(this.args), false); + } + + @Override + public String toString() { + return Json.toString(this); + } + + static class JsonSerializer extends StdSerializer { + + private JsonSerializer() { + super(TypeArgumentList.class); + } + + @Override + public void serialize(TypeArgumentList value, JsonGenerator generator, SerializerProvider provider) throws IOException { + + generator.writeStartObject(); + generator.writeObjectField("schemaId", value.schemaId); + generator.writeArrayFieldStart("args"); + + for (TypeArgument element : value.args) { + generator.writeString(element.toString()); + } + + generator.writeEndArray(); + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/UpdateOptions.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/UpdateOptions.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/UpdateOptions.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/UpdateOptions.java index 7d66759..60c6dbc 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/UpdateOptions.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/UpdateOptions.java @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.layouts; - -import com.google.common.base.Suppliers; -import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; - -import java.util.Arrays; -import java.util.function.Supplier; - -/** - * Describes the desired behavior when writing a {@link LayoutType}. - */ -public enum UpdateOptions { - - NONE(0), - - /** - * Overwrite an existing value. - *

- * An existing value is assumed to exist at the offset provided. The existing value is - * replaced inline. The remainder of the row is resized to accomodate either an increase or decrease - * in required space. - */ - UPDATE(1), - - /** - * Insert a new value - *

- * An existing value is assumed NOT to exist at the offset provided. The new value is inserted immediately at the - * offset. The remainder of the row is resized to accommodate either an increase or decrease in required space. - */ - INSERT(2), - - /** - * Update an existing value or insert a new value, if no value exists - *

- * If a value exists, then this operation becomes {@link #UPDATE}, otherwise it becomes {@link #INSERT}. - */ - UPSERT(3), - - /** - * Insert a new value moving existing values to the right. - *

- * Within an array scope, inserts a new value immediately at the index moving all subsequent - * items to the right. In any other scope behaves the same as {@link #UPSERT}. - */ - INSERT_AT(4); - - public static final int BYTES = Integer.BYTES; - - private static final Supplier> mappings = Suppliers.memoize(() -> { - UpdateOptions[] constants = UpdateOptions.class.getEnumConstants(); - int[] values = new int[constants.length]; - Arrays.setAll(values, index -> constants[index].value); - return new Int2ReferenceArrayMap<>(values, constants); - }); - - private final int value; - - UpdateOptions(int value) { - this.value = value; - } - - public static UpdateOptions from(int value) { - return mappings.get().get(value); - } - - public int value() { - return this.value; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.layouts; + +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; + +import java.util.Arrays; +import java.util.function.Supplier; + +/** + * Describes the desired behavior when writing a {@link LayoutType}. + */ +public enum UpdateOptions { + + NONE(0), + + /** + * Overwrite an existing value. + *

+ * An existing value is assumed to exist at the offset provided. The existing value is + * replaced inline. The remainder of the row is resized to accomodate either an increase or decrease + * in required space. + */ + UPDATE(1), + + /** + * Insert a new value + *

+ * An existing value is assumed NOT to exist at the offset provided. The new value is inserted immediately at the + * offset. The remainder of the row is resized to accommodate either an increase or decrease in required space. + */ + INSERT(2), + + /** + * Update an existing value or insert a new value, if no value exists + *

+ * If a value exists, then this operation becomes {@link #UPDATE}, otherwise it becomes {@link #INSERT}. + */ + UPSERT(3), + + /** + * Insert a new value moving existing values to the right. + *

+ * Within an array scope, inserts a new value immediately at the index moving all subsequent + * items to the right. In any other scope behaves the same as {@link #UPSERT}. + */ + INSERT_AT(4); + + public static final int BYTES = Integer.BYTES; + + private static final Supplier> mappings = Suppliers.memoize(() -> { + UpdateOptions[] constants = UpdateOptions.class.getEnumConstants(); + int[] values = new int[constants.length]; + Arrays.setAll(values, index -> constants[index].value); + return new Int2ReferenceArrayMap<>(values, constants); + }); + + private final int value; + + UpdateOptions(int value) { + this.value = value; + } + + public static UpdateOptions from(int value) { + return mappings.get().get(value); + } + + public int value() { + return this.value; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ArrayPropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ArrayPropertyType.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ArrayPropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ArrayPropertyType.java index ae1cff7..774e571 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ArrayPropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ArrayPropertyType.java @@ -1,39 +1,39 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Array properties represent an unbounded set of zero or more items. - *

- * Arrays may be typed or untyped. Within typed arrays, all items MUST be the same type. The type of items is specified - * via {@link #items()}. Typed arrays may be stored more efficiently than untyped arrays. When {@link #items()} is - * unspecified, the array is untyped and its items may be heterogeneous. - */ -public class ArrayPropertyType extends ScopePropertyType { - - @JsonProperty - private PropertyType items; - - /** - * (Optional) type of the elements of the array, if a typed array, otherwise null. - * - * @return type of the elements of the array or {@code null}, if the array is untyped. - */ - public final PropertyType items() { - return this.items; - } - - /** - * Set the type of the elements of the array - * - * @param value type of the elements of the array or {@code null}, if the array is untyped. - * @return a reference to this {@link ArrayPropertyType}. - */ - public final ArrayPropertyType items(PropertyType value) { - this.items = value; - return this; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Array properties represent an unbounded set of zero or more items. + *

+ * Arrays may be typed or untyped. Within typed arrays, all items MUST be the same type. The type of items is specified + * via {@link #items()}. Typed arrays may be stored more efficiently than untyped arrays. When {@link #items()} is + * unspecified, the array is untyped and its items may be heterogeneous. + */ +public class ArrayPropertyType extends ScopePropertyType { + + @JsonProperty + private PropertyType items; + + /** + * (Optional) type of the elements of the array, if a typed array, otherwise null. + * + * @return type of the elements of the array or {@code null}, if the array is untyped. + */ + public final PropertyType items() { + return this.items; + } + + /** + * Set the type of the elements of the array + * + * @param value type of the elements of the array or {@code null}, if the array is untyped. + * @return a reference to this {@link ArrayPropertyType}. + */ + public final ArrayPropertyType items(PropertyType value) { + this.items = value; + return this; + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/MapPropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/MapPropertyType.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/MapPropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/MapPropertyType.java index 02e3c33..2434b12 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/MapPropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/MapPropertyType.java @@ -1,46 +1,46 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -/** - * Map properties represent an unbounded set of zero or more key-value pairs with unique keys. - *

- * Maps are typed or untyped. Within typed maps, all key MUST be the same type, and all values MUST be the same type. - * The type of both key and values is specified via {@link #keys()} and {@link #values()} respectively. Typed maps may - * be stored more efficiently than untyped maps. When {@link #keys()} or {@link #values()} is unspecified or marked - * {@link TypeKind#ANY}, the map is untyped and its key and/or values may be heterogeneous. - */ -public class MapPropertyType extends ScopePropertyType { - - private PropertyType keys; - private PropertyType values; - - /** - * (Optional) type of the keys of the map, if a typed map, otherwise {@code null}. - * - * @return type of the keys of the map, if a type map, otherwise {@code null}. - */ - public final PropertyType keys() { - return this.keys; - } - - public final MapPropertyType keys(PropertyType value) { - this.keys = value; - return this; - } - - /** - * (Optional) type of the values of the map, if a typed map, otherwise {@code null}. - * - * @return type of the values of the map, if a typed map, otherwise {@code null}. - */ - public final PropertyType values() { - return this.values; - } - - public final MapPropertyType values(PropertyType value) { - this.values = value; - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +/** + * Map properties represent an unbounded set of zero or more key-value pairs with unique keys. + *

+ * Maps are typed or untyped. Within typed maps, all key MUST be the same type, and all values MUST be the same type. + * The type of both key and values is specified via {@link #keys()} and {@link #values()} respectively. Typed maps may + * be stored more efficiently than untyped maps. When {@link #keys()} or {@link #values()} is unspecified or marked + * {@link TypeKind#ANY}, the map is untyped and its key and/or values may be heterogeneous. + */ +public class MapPropertyType extends ScopePropertyType { + + private PropertyType keys; + private PropertyType values; + + /** + * (Optional) type of the keys of the map, if a typed map, otherwise {@code null}. + * + * @return type of the keys of the map, if a type map, otherwise {@code null}. + */ + public final PropertyType keys() { + return this.keys; + } + + public final MapPropertyType keys(PropertyType value) { + this.keys = value; + return this; + } + + /** + * (Optional) type of the values of the map, if a typed map, otherwise {@code null}. + * + * @return type of the values of the map, if a typed map, otherwise {@code null}. + */ + public final PropertyType values() { + return this.values; + } + + public final MapPropertyType values(PropertyType value) { + this.values = value; + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Namespace.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Namespace.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Namespace.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Namespace.java index f2c27d1..dfc26f4 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Namespace.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Namespace.java @@ -1,132 +1,132 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.azure.data.cosmos.core.Json; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Preconditions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import java.io.File; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -public class Namespace { - - private static final Logger logger = LoggerFactory.getLogger(Json.class); - - @JsonProperty(required = true) - private String name; - - @JsonProperty(required = true) - private ArrayList schemas; - - @JsonProperty(required = true) - private SchemaLanguageVersion version; - - /** - * The fully qualified name of the namespace. - * - * @return fully qualified name of the {@linkplain Namespace namespace}. - */ - public final String name() { - return this.name; - } - - /** - * Sets the fully qualified name of the namespace. - * - * @param value fully qualified name of the {@linkplain Namespace namespace}. - * @return a reference to this {@linkplain Namespace namespace}. - */ - public final Namespace name(String value) { - this.name = value; - return this; - } - - /** - * Parse a JSON document and return a full namespace. - * - * @param file The JSON file to parse. - * @return A namespace containing a set of logical schemas. - */ - public static Optional parse(File file) { - Optional namespace = Json.parse(file, Namespace.class); - namespace.ifPresent(SchemaValidator::validate); - return namespace; - } - - /** - * Parse a JSON document and return a full namespace. - * - * @param stream The JSON input stream to parse. - * @return A namespace containing a set of logical schemas. - */ - public static Optional parse(InputStream stream) { - Optional namespace = Json.parse(stream, Namespace.class); - try { - namespace.ifPresent(SchemaValidator::validate); - } catch (SchemaException error) { - logger.error("failed to parse {} due to ", Namespace.class, error); - } - return namespace; - } - - /** - * Parse a JSON document and return a full namespace. - * - * @param value The JSON text to parse. - * @return A namespace containing a set of logical schemas. - */ - public static Optional parse(String value) { - Optional namespace = Json.parse(value, Namespace.class); - namespace.ifPresent(SchemaValidator::validate); - return namespace; - } - - /** - * The set of schemas that make up the {@link Namespace}. - *

- * Namespaces may consist of zero or more table schemas along with zero or more UDT schemas. Table schemas can only - * reference UDT schemas defined in the same namespace. UDT schemas can contain nested UDTs whose schemas are - * defined within the same namespace. - * - * @return list of schemas in the current {@link Namespace}. - */ - public final List schemas() { - return this.schemas; - } - - public final Namespace schemas(ArrayList value) { - this.schemas = value != null ? value : new ArrayList(); - return this; - } - - /** - * The version of the HybridRow Schema Definition Language used to encode this namespace. - * - * @return {linkplain SchemaLanguageVersion version} of the HybridRow Schema Definition Language used to encode this - * {@linkplain Namespace namespace}. - */ - public final SchemaLanguageVersion version() { - return this.version; - } - - /** - * Sets the version of the HybridRow Schema Definition Language used to encode this namespace. - * - * @param value {linkplain SchemaLanguageVersion version} of the HybridRow Schema Definition Language that will be - * used to encode this {@linkplain Namespace namespace}. - * @return a reference to this {@linkplain Namespace namespace}. - */ - public final Namespace version(@Nonnull SchemaLanguageVersion value) { - Preconditions.checkNotNull(value, "expected non-null value"); - this.version = value; - return this; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.azure.data.cosmos.core.Json; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class Namespace { + + private static final Logger logger = LoggerFactory.getLogger(Json.class); + + @JsonProperty(required = true) + private String name; + + @JsonProperty(required = true) + private ArrayList schemas; + + @JsonProperty(required = true) + private SchemaLanguageVersion version; + + /** + * The fully qualified name of the namespace. + * + * @return fully qualified name of the {@linkplain Namespace namespace}. + */ + public final String name() { + return this.name; + } + + /** + * Sets the fully qualified name of the namespace. + * + * @param value fully qualified name of the {@linkplain Namespace namespace}. + * @return a reference to this {@linkplain Namespace namespace}. + */ + public final Namespace name(String value) { + this.name = value; + return this; + } + + /** + * Parse a JSON document and return a full namespace. + * + * @param file The JSON file to parse. + * @return A namespace containing a set of logical schemas. + */ + public static Optional parse(File file) { + Optional namespace = Json.parse(file, Namespace.class); + namespace.ifPresent(SchemaValidator::validate); + return namespace; + } + + /** + * Parse a JSON document and return a full namespace. + * + * @param stream The JSON input stream to parse. + * @return A namespace containing a set of logical schemas. + */ + public static Optional parse(InputStream stream) { + Optional namespace = Json.parse(stream, Namespace.class); + try { + namespace.ifPresent(SchemaValidator::validate); + } catch (SchemaException error) { + logger.error("failed to parse {} due to ", Namespace.class, error); + } + return namespace; + } + + /** + * Parse a JSON document and return a full namespace. + * + * @param value The JSON text to parse. + * @return A namespace containing a set of logical schemas. + */ + public static Optional parse(String value) { + Optional namespace = Json.parse(value, Namespace.class); + namespace.ifPresent(SchemaValidator::validate); + return namespace; + } + + /** + * The set of schemas that make up the {@link Namespace}. + *

+ * Namespaces may consist of zero or more table schemas along with zero or more UDT schemas. Table schemas can only + * reference UDT schemas defined in the same namespace. UDT schemas can contain nested UDTs whose schemas are + * defined within the same namespace. + * + * @return list of schemas in the current {@link Namespace}. + */ + public final List schemas() { + return this.schemas; + } + + public final Namespace schemas(ArrayList value) { + this.schemas = value != null ? value : new ArrayList(); + return this; + } + + /** + * The version of the HybridRow Schema Definition Language used to encode this namespace. + * + * @return {linkplain SchemaLanguageVersion version} of the HybridRow Schema Definition Language used to encode this + * {@linkplain Namespace namespace}. + */ + public final SchemaLanguageVersion version() { + return this.version; + } + + /** + * Sets the version of the HybridRow Schema Definition Language used to encode this namespace. + * + * @param value {linkplain SchemaLanguageVersion version} of the HybridRow Schema Definition Language that will be + * used to encode this {@linkplain Namespace namespace}. + * @return a reference to this {@linkplain Namespace namespace}. + */ + public final Namespace version(@Nonnull SchemaLanguageVersion value) { + Preconditions.checkNotNull(value, "expected non-null value"); + this.version = value; + return this; + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ObjectPropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ObjectPropertyType.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ObjectPropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ObjectPropertyType.java index fed6462..a98269d 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ObjectPropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ObjectPropertyType.java @@ -1,40 +1,40 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import java.util.Collections; -import java.util.List; - -/** - * Object properties represent nested structures. - *

- * Object properties map to multiple columns depending on the number of internal properties within the defined object - * structure. Object properties are provided as a convince in schema design. They are effectively equivalent to - * defining the same properties explicitly via {@link PrimitivePropertyType} with nested property paths. - */ -public class ObjectPropertyType extends ScopePropertyType { - - private List properties; - - /** - * Initializes a new instance of the {@link ObjectPropertyType} class. - */ - public ObjectPropertyType() { - this.properties = Collections.emptyList(); - } - - /** - * A list of zero or more property definitions that define the columns within the schema. - * - * @return a list of zero or more property definitions that define the columns within the schema. - */ - public final List properties() { - return this.properties; - } - - public final ObjectPropertyType properties(List value) { - this.properties = value != null ? value : Collections.emptyList(); - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import java.util.Collections; +import java.util.List; + +/** + * Object properties represent nested structures. + *

+ * Object properties map to multiple columns depending on the number of internal properties within the defined object + * structure. Object properties are provided as a convince in schema design. They are effectively equivalent to + * defining the same properties explicitly via {@link PrimitivePropertyType} with nested property paths. + */ +public class ObjectPropertyType extends ScopePropertyType { + + private List properties; + + /** + * Initializes a new instance of the {@link ObjectPropertyType} class. + */ + public ObjectPropertyType() { + this.properties = Collections.emptyList(); + } + + /** + * A list of zero or more property definitions that define the columns within the schema. + * + * @return a list of zero or more property definitions that define the columns within the schema. + */ + public final List properties() { + return this.properties; + } + + public final ObjectPropertyType properties(List value) { + this.properties = value != null ? value : Collections.emptyList(); + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PartitionKey.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PartitionKey.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PartitionKey.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PartitionKey.java index d500479..fdece46 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PartitionKey.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PartitionKey.java @@ -1,24 +1,24 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -/** - * Describes a property or set of properties used to partition the data set across machines. - */ -public class PartitionKey { - /** - * The logical path of the referenced property. - * Partition keys MUST refer to properties defined within the same {@link Schema}. - */ - private String path; - - public final String path() { - return this.path; - } - - public final PartitionKey path(String value) { - this.path = value; - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +/** + * Describes a property or set of properties used to partition the data set across machines. + */ +public class PartitionKey { + /** + * The logical path of the referenced property. + * Partition keys MUST refer to properties defined within the same {@link Schema}. + */ + private String path; + + public final String path() { + return this.path; + } + + public final PartitionKey path(String value) { + this.path = value; + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimarySortKey.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimarySortKey.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimarySortKey.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimarySortKey.java index aa18a48..f1bfa5b 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimarySortKey.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimarySortKey.java @@ -1,46 +1,46 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -/** - * Describes a property or set of properties used to order the data set within a single. - * partition. - */ -public class PrimarySortKey { - - private SortDirection direction = SortDirection.values()[0]; - private String path; - - /** - * The logical path of the referenced property. - *

- * Primary keys MUST refer to properties defined within the same {@link Schema}. - * - * @return the logical path of the referenced property. - */ - public final SortDirection direction() { - return this.direction; - } - - public final PrimarySortKey direction(SortDirection value) { - this.direction = value; - return this; - } - - /** - * The logical path of the referenced property. - *

- * Primary keys MUST refer to properties defined within the same {@link Schema}. - * - * @return the logical path of the referenced property. - */ - public final String path() { - return this.path; - } - - public final PrimarySortKey path(String value) { - this.path = value; - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +/** + * Describes a property or set of properties used to order the data set within a single. + * partition. + */ +public class PrimarySortKey { + + private SortDirection direction = SortDirection.values()[0]; + private String path; + + /** + * The logical path of the referenced property. + *

+ * Primary keys MUST refer to properties defined within the same {@link Schema}. + * + * @return the logical path of the referenced property. + */ + public final SortDirection direction() { + return this.direction; + } + + public final PrimarySortKey direction(SortDirection value) { + this.direction = value; + return this; + } + + /** + * The logical path of the referenced property. + *

+ * Primary keys MUST refer to properties defined within the same {@link Schema}. + * + * @return the logical path of the referenced property. + */ + public final String path() { + return this.path; + } + + public final PrimarySortKey path(String value) { + this.path = value; + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimitivePropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimitivePropertyType.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimitivePropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimitivePropertyType.java index d9aed1c..f10079a 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimitivePropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PrimitivePropertyType.java @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * A primitive property. - *

- * Primitive properties map to columns one-to-one. Primitive properties indicate how the column should be represented - * within the row. - */ -public class PrimitivePropertyType extends PropertyType { - - @JsonProperty - private int length; - - @JsonProperty - private StorageKind storage; - - /** - * The maximum allowable length in bytes. - *

- * This annotation is only valid for non-fixed length types. A value of 0 means the maximum allowable length. - * - * @return the maximum allowable length in bytes. - */ - public final int length() { - return this.length; - } - - public final PrimitivePropertyType length(int value) { - this.length = value; - return this; - } - - /** - * Storage requirements of the property. - * - * @return storage requirements of the property. - */ - public final StorageKind storage() { - return this.storage; - } - - public final PrimitivePropertyType storage(StorageKind value) { - this.storage = value; - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A primitive property. + *

+ * Primitive properties map to columns one-to-one. Primitive properties indicate how the column should be represented + * within the row. + */ +public class PrimitivePropertyType extends PropertyType { + + @JsonProperty + private int length; + + @JsonProperty + private StorageKind storage; + + /** + * The maximum allowable length in bytes. + *

+ * This annotation is only valid for non-fixed length types. A value of 0 means the maximum allowable length. + * + * @return the maximum allowable length in bytes. + */ + public final int length() { + return this.length; + } + + public final PrimitivePropertyType length(int value) { + this.length = value; + return this; + } + + /** + * Storage requirements of the property. + * + * @return storage requirements of the property. + */ + public final StorageKind storage() { + return this.storage; + } + + public final PrimitivePropertyType storage(StorageKind value) { + this.storage = value; + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Property.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Property.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Property.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Property.java index 1c83c3f..4a6efae 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Property.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Property.java @@ -1,104 +1,104 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import javax.annotation.Nonnull; - -/** - * Describes a single property definition. - */ -public class Property { - - @JsonProperty(required = false) - private String comment; - - @JsonProperty(required = true) - private String path; - - @JsonProperty(required = true) - private PropertyType type; - - /** - * An (optional) comment describing the purpose of this {@linkplain Property property}. - *

- * Comments are for documentary purpose only and do not affect the property at runtime. - * - * @return the comment on this {@linkplain Schema property} or {@code null}, if there is no comment. - */ - public final String comment() { - return this.comment; - } - - /** - * Sets the (optional) comment describing the purpose of this {@linkplain Property property}. - *

- * Comments are for documentary purpose only and do not affect the property at runtime. - * - * @param value a comment on this {@linkplain Property property} or {@code null} to remove the comment, if any, on - * this {@linkplain Property property}. - * @return a reference to this {@linkplain Property property}. - */ - public final Property comment(String value) { - this.comment = value; - return this; - } - - /** - * The logical path of this {@linkplain Property property}. - *

. - * For complex properties (e.g. objects) the logical path forms a prefix to relative paths of properties defined - * within nested structures. - *

- * See the logical path specification for full details on both relative and absolute paths. - * - * @return the logical path of this {@linkplain Property property}. - */ - public final String path() { - return this.path; - } - - /** - * Sets the logical path of this {@linkplain Property property}. - *

. - * For complex properties (e.g. objects) the logical path forms a prefix to relative paths of properties defined - * within nested structures. - *

- * See the logical path specification for full details on both relative and absolute paths. - * - * @param value the logical path of this {@linkplain Property property}. - * @return a reference to this {@linkplain Property property}. - */ - public final Property path(@Nonnull String value) { - this.path = value; - return this; - } - - /** - * The type of this {@linkplain Property property}. - *

- * Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a single column. Complex - * types may define one or more columns depending on their structure. - * - * @return the type of this {@linkplain Property property}. - */ - public final PropertyType type() { - return this.type; - } - - /** - * Sets the type of this {@linkplain Property property}. - *

- * Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a single column. Complex - * types may define one or more columns depending on their structure. - * - * @param value the type of this {@linkplain Property property}. - * @return a reference to this {@linkplain Property property}. - */ - public final Property type(PropertyType value) { - this.type = value; - return this; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.annotation.Nonnull; + +/** + * Describes a single property definition. + */ +public class Property { + + @JsonProperty(required = false) + private String comment; + + @JsonProperty(required = true) + private String path; + + @JsonProperty(required = true) + private PropertyType type; + + /** + * An (optional) comment describing the purpose of this {@linkplain Property property}. + *

+ * Comments are for documentary purpose only and do not affect the property at runtime. + * + * @return the comment on this {@linkplain Schema property} or {@code null}, if there is no comment. + */ + public final String comment() { + return this.comment; + } + + /** + * Sets the (optional) comment describing the purpose of this {@linkplain Property property}. + *

+ * Comments are for documentary purpose only and do not affect the property at runtime. + * + * @param value a comment on this {@linkplain Property property} or {@code null} to remove the comment, if any, on + * this {@linkplain Property property}. + * @return a reference to this {@linkplain Property property}. + */ + public final Property comment(String value) { + this.comment = value; + return this; + } + + /** + * The logical path of this {@linkplain Property property}. + *

. + * For complex properties (e.g. objects) the logical path forms a prefix to relative paths of properties defined + * within nested structures. + *

+ * See the logical path specification for full details on both relative and absolute paths. + * + * @return the logical path of this {@linkplain Property property}. + */ + public final String path() { + return this.path; + } + + /** + * Sets the logical path of this {@linkplain Property property}. + *

. + * For complex properties (e.g. objects) the logical path forms a prefix to relative paths of properties defined + * within nested structures. + *

+ * See the logical path specification for full details on both relative and absolute paths. + * + * @param value the logical path of this {@linkplain Property property}. + * @return a reference to this {@linkplain Property property}. + */ + public final Property path(@Nonnull String value) { + this.path = value; + return this; + } + + /** + * The type of this {@linkplain Property property}. + *

+ * Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a single column. Complex + * types may define one or more columns depending on their structure. + * + * @return the type of this {@linkplain Property property}. + */ + public final PropertyType type() { + return this.type; + } + + /** + * Sets the type of this {@linkplain Property property}. + *

+ * Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a single column. Complex + * types may define one or more columns depending on their structure. + * + * @param value the type of this {@linkplain Property property}. + * @return a reference to this {@linkplain Property property}. + */ + public final Property type(PropertyType value) { + this.type = value; + return this; + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertyType.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertyType.java index 088363c..1db1591 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/PropertyType.java @@ -1,125 +1,125 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonSubTypes.Type; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; - -/** - * The base class for property types both primitive and complex. - */ -@JsonTypeInfo(use = Id.NAME, property = "type", visible = true) -@JsonSubTypes({ - // Composite types - @Type(value = ArrayPropertyType.class, name = "array"), - @Type(value = MapPropertyType.class, name = "map"), - @Type(value = ObjectPropertyType.class, name = "object"), - @Type(value = SetPropertyType.class, name = "set"), - @Type(value = TaggedPropertyType.class, name = "tagged"), - @Type(value = TuplePropertyType.class, name = "tuple"), - @Type(value = UdtPropertyType.class, name = "schema"), - // Primitive types - @Type(value = PrimitivePropertyType.class, name = "null"), - @Type(value = PrimitivePropertyType.class, name = "bool"), - @Type(value = PrimitivePropertyType.class, name = "int8"), - @Type(value = PrimitivePropertyType.class, name = "int16"), - @Type(value = PrimitivePropertyType.class, name = "int32"), - @Type(value = PrimitivePropertyType.class, name = "int64"), - @Type(value = PrimitivePropertyType.class, name = "varint"), - @Type(value = PrimitivePropertyType.class, name = "uint8"), - @Type(value = PrimitivePropertyType.class, name = "uint16"), - @Type(value = PrimitivePropertyType.class, name = "uint32"), - @Type(value = PrimitivePropertyType.class, name = "uint64"), - @Type(value = PrimitivePropertyType.class, name = "varuint"), - @Type(value = PrimitivePropertyType.class, name = "float32"), - @Type(value = PrimitivePropertyType.class, name = "float64"), - @Type(value = PrimitivePropertyType.class, name = "float128"), - @Type(value = PrimitivePropertyType.class, name = "decimal"), - @Type(value = PrimitivePropertyType.class, name = "datetime"), - @Type(value = PrimitivePropertyType.class, name = "unixdatetime"), - @Type(value = PrimitivePropertyType.class, name = "binary"), - @Type(value = PrimitivePropertyType.class, name = "guid"), - @Type(value = PrimitivePropertyType.class, name = "utf8"), - @Type(value = PrimitivePropertyType.class, name = "any") -}) -public abstract class PropertyType { - - @JsonProperty - private String apiType; - - @JsonProperty(defaultValue = "true") - private boolean nullable; - - @JsonProperty(required = true) - private TypeKind type; - - protected PropertyType() { - this.nullable(true); - } - - /** - * API-specific type annotations for this {@linkplain Property property}. - * - * @return API-specific type annotations for this {@linkplain Property property}. - */ - public final String apiType() { - return this.apiType; - } - - /** - * Sets API-specific type annotations for this {@linkplain Property property}. - * - * @param value API-specific type annotations for this {@linkplain Property property}. - * @return a reference to this {@linkplain Property property}. - */ - public final PropertyType apiType(String value) { - this.apiType = value; - return this; - } - - /** - * {@code true} if the {@linkplain Property property} can be {@code null}. - *

- * Default: {@code true} - * - * @return {@code true} if the {@linkplain Property property} can be {@code null, otherwise {@code false}}. - */ - public final boolean nullable() { - return this.nullable; - } - - /** - * Sets a flag indicating whether the {@linkplain Property property} can be {@code null}. - * - * @param value {@code true} indicates that this {@linkplain Property property} can be {@code null}. - * @return a reference to this {@linkplain Property property}. - */ - public final PropertyType nullable(boolean value) { - this.nullable = value; - return this; - } - - /** - * The logical type of this {@linkplain Property property}. - * - * @return the logical type of this {@linkplain Property property}. - */ - public final TypeKind type() { - return this.type; - } - - /** - * Sets the logical type of this {@linkplain Property property}. - * - * @param value the logical type of this {@linkplain Property property}. - * @return a reference to this {@linkplain Property property}. - */ - public final PropertyType type(TypeKind value) { - this.type = value; - return this; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; + +/** + * The base class for property types both primitive and complex. + */ +@JsonTypeInfo(use = Id.NAME, property = "type", visible = true) +@JsonSubTypes({ + // Composite types + @Type(value = ArrayPropertyType.class, name = "array"), + @Type(value = MapPropertyType.class, name = "map"), + @Type(value = ObjectPropertyType.class, name = "object"), + @Type(value = SetPropertyType.class, name = "set"), + @Type(value = TaggedPropertyType.class, name = "tagged"), + @Type(value = TuplePropertyType.class, name = "tuple"), + @Type(value = UdtPropertyType.class, name = "schema"), + // Primitive types + @Type(value = PrimitivePropertyType.class, name = "null"), + @Type(value = PrimitivePropertyType.class, name = "bool"), + @Type(value = PrimitivePropertyType.class, name = "int8"), + @Type(value = PrimitivePropertyType.class, name = "int16"), + @Type(value = PrimitivePropertyType.class, name = "int32"), + @Type(value = PrimitivePropertyType.class, name = "int64"), + @Type(value = PrimitivePropertyType.class, name = "varint"), + @Type(value = PrimitivePropertyType.class, name = "uint8"), + @Type(value = PrimitivePropertyType.class, name = "uint16"), + @Type(value = PrimitivePropertyType.class, name = "uint32"), + @Type(value = PrimitivePropertyType.class, name = "uint64"), + @Type(value = PrimitivePropertyType.class, name = "varuint"), + @Type(value = PrimitivePropertyType.class, name = "float32"), + @Type(value = PrimitivePropertyType.class, name = "float64"), + @Type(value = PrimitivePropertyType.class, name = "float128"), + @Type(value = PrimitivePropertyType.class, name = "decimal"), + @Type(value = PrimitivePropertyType.class, name = "datetime"), + @Type(value = PrimitivePropertyType.class, name = "unixdatetime"), + @Type(value = PrimitivePropertyType.class, name = "binary"), + @Type(value = PrimitivePropertyType.class, name = "guid"), + @Type(value = PrimitivePropertyType.class, name = "utf8"), + @Type(value = PrimitivePropertyType.class, name = "any") +}) +public abstract class PropertyType { + + @JsonProperty + private String apiType; + + @JsonProperty(defaultValue = "true") + private boolean nullable; + + @JsonProperty(required = true) + private TypeKind type; + + protected PropertyType() { + this.nullable(true); + } + + /** + * API-specific type annotations for this {@linkplain Property property}. + * + * @return API-specific type annotations for this {@linkplain Property property}. + */ + public final String apiType() { + return this.apiType; + } + + /** + * Sets API-specific type annotations for this {@linkplain Property property}. + * + * @param value API-specific type annotations for this {@linkplain Property property}. + * @return a reference to this {@linkplain Property property}. + */ + public final PropertyType apiType(String value) { + this.apiType = value; + return this; + } + + /** + * {@code true} if the {@linkplain Property property} can be {@code null}. + *

+ * Default: {@code true} + * + * @return {@code true} if the {@linkplain Property property} can be {@code null, otherwise {@code false}}. + */ + public final boolean nullable() { + return this.nullable; + } + + /** + * Sets a flag indicating whether the {@linkplain Property property} can be {@code null}. + * + * @param value {@code true} indicates that this {@linkplain Property property} can be {@code null}. + * @return a reference to this {@linkplain Property property}. + */ + public final PropertyType nullable(boolean value) { + this.nullable = value; + return this; + } + + /** + * The logical type of this {@linkplain Property property}. + * + * @return the logical type of this {@linkplain Property property}. + */ + public final TypeKind type() { + return this.type; + } + + /** + * Sets the logical type of this {@linkplain Property property}. + * + * @param value the logical type of this {@linkplain Property property}. + * @return a reference to this {@linkplain Property property}. + */ + public final PropertyType type(TypeKind value) { + this.type = value; + return this; + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Schema.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Schema.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Schema.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Schema.java index eb855b3..b02d8bb 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Schema.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/Schema.java @@ -1,290 +1,290 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.azure.data.cosmos.core.Json; -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; -import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCompiler; -import com.fasterxml.jackson.annotation.JsonProperty; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A schema describes either table or UDT metadata. - *

- * The schema of a table or UDT describes the structure of row (i.e. which columns and the types of those columns). A - * table schema represents the description of the contents of a collection level row directly. UDTs described nested - * structured objects that may appear either within a table column or within another UDT (i.e. nested UDTs). - */ -public class Schema { - - // Required fields - - @JsonProperty(required = true) - private String name; - - @JsonProperty(defaultValue = "schema", required = true) - private TypeKind type; - - // Optional fields - - @JsonProperty - private String comment; - - @JsonProperty() - private SchemaId id; - - @JsonProperty - private SchemaOptions options; - - @JsonProperty - private List properties; - - @JsonProperty - private SchemaLanguageVersion version; - - // TODO: DANOBLE: how do these properties serialize? - - private List partitionKeys; - private List primaryKeys; - private List staticKeys; - - /** - * Initializes a new instance of the {@link Schema} class. - */ - private Schema() { - this.id = SchemaId.NONE; - this.type = TypeKind.SCHEMA; - this.partitionKeys = Collections.emptyList(); - this.primaryKeys = Collections.emptyList(); - this.staticKeys = Collections.emptyList(); - } - - /** - * An (optional) comment describing the purpose of this schema. - *

- * Comments are for documentary purpose only and do not affect the schema at runtime. - * - * @return the comment on this {@linkplain Schema schema} or {@code null}, if there is no comment. - */ - public final String comment() { - return this.comment; - } - - /** - * Sets the (optional) comment describing the purpose of this schema. - *

- * Comments are for documentary purpose only and do not affect the schema at runtime. - * - * @param value a comment on this {@linkplain Schema schema} or {@code null} to remove the comment, if any, on this - * {@linkplain Schema schema}. - * @return a reference to this {@linkplain Schema schema}. - */ - public final Schema comment(String value) { - this.comment = value; - return this; - } - - /** - * Compiles this logical schema into a physical layout that can be used to read and write rows. - * - * @param namespace The namespace within which this schema is defined. - * @return The layout for the schema. - */ - public final Layout compile(Namespace namespace) { - - checkNotNull(namespace, "expected non-null ns"); - checkArgument(namespace.schemas().contains(this)); - - return LayoutCompiler.compile(namespace, this); - } - - /** - * The name of this {@linkplain Schema schema}. - *

- * The name of a schema MUST be unique within its namespace. Names must begin with an alpha-numeric character and - * can only contain alpha-numeric characters and underscores. - * - * @return the name of this {@linkplain Schema schema} or {@code null}, if the name has not yet been set. - */ - public final String name() { - return this.name; - } - - /** - * Sets the name of this {@linkplain Schema schema}. - *

- * The name of a schema MUST be unique within its namespace. Names must begin with an alpha-numeric character and - * can only contain alpha-numeric characters and underscores. - * - * @param value a name for this {@linkplain Schema schema}. - * @return a reference to this {@linkplain Schema schema}. - */ - @Nonnull - public final Schema name(@Nonnull String value) { - checkNotNull(value); - this.name = value; - return this; - } - - /** - * Schema-wide options. - * - * @return schema-wide options. - */ - public final SchemaOptions options() { - return this.options; - } - - public final Schema options(SchemaOptions value) { - this.options = value; - return this; - } - - /** - * Parse a JSON fragment and return a schema. - * - * @param value The JSON string value to parse - * @return A logical schema, if the value parses. - */ - public static Optional parse(String value) { - return Json.parse(value, Schema.class); - // TODO: DANOBLE: perform structural validation on the Schema after JSON parsing - } - - /** - * An (optional) list of zero or more logical paths that form the partition key. - *

- * All paths referenced MUST map to a property within the schema. This field is never null. - * - * @return list of zero or more logical paths that form the partition key - */ - @Nullable - public final List partitionKeys() { - return this.partitionKeys; - } - - public final Schema partitionKeys(@Nullable List value) { - this.partitionKeys = value != null ? value : Collections.emptyList(); - return this; - } - - /** - * An (optional) list of zero or more logical paths that form the primary sort key. - *

- * All paths referenced MUST map to a property within the schema. This field is never null. - * - * @return list of zero or more logical paths that form the partition key - */ - @Nullable - public final List primarySortKeys() { - return this.primaryKeys; - } - - public final Schema primarySortKeys(ArrayList value) { - this.primaryKeys = value != null ? value : Collections.emptyList(); - return this; - } - - /** - * A list of zero or more property definitions that define the columns within the schema. - *

- * This field is never null. - * - * @return list of zero or more property definitions that define the columns within the schema - */ - @Nonnull - public final List properties() { - return this.properties; - } - - public final Schema properties(List value) { - this.properties = value != null ? value : Collections.emptyList(); - return this; - } - - /** - * The unique identifier for a schema. - *

- * Identifiers must be unique within the scope of the database in which they are used. - * - * @return the unique identifier for a schema. - */ - public final SchemaId schemaId() { - return this.id; - } - - public final Schema schemaId(SchemaId value) { - this.id = value; - return this; - } - - /** - * A list of zero or more logical paths that hold data shared by all documents with same partition key. - *

- * All paths referenced MUST map to a property within the schema. - *

- * This field is never null. - * - * @return A list of zero or more logical paths that hold data shared by all documents with same partition key. - */ - @Nonnull - public final List staticKeys() { - return this.staticKeys; - } - - public final Schema staticKeys(List value) { - this.staticKeys = value != null ? value : Collections.emptyList(); - return this; - } - - /** - * Returns a JSON string representation of the current {@link Schema}. - * - * @return a JSON string representation of the current {@link Schema} - */ - @Override - public String toString() { - return Json.toString(this); - } - - /** - * The type of this schema. - *

- * This value MUST be {@link TypeKind#SCHEMA}. - * - * @return the type of this schema. - */ - public final TypeKind type() { - return this.type; - } - - public final Schema type(TypeKind value) { - this.type = value; - return this; - } - - /** - * The version of the HybridRow Schema Definition Language used to encode this schema. - * - * @return the version of the HybridRow Schema Definition Language used to encode this schema. - */ - public final SchemaLanguageVersion version() { - return this.version; - } - - public final Schema version(SchemaLanguageVersion value) { - this.version = value; - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.azure.data.cosmos.core.Json; +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; +import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCompiler; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A schema describes either table or UDT metadata. + *

+ * The schema of a table or UDT describes the structure of row (i.e. which columns and the types of those columns). A + * table schema represents the description of the contents of a collection level row directly. UDTs described nested + * structured objects that may appear either within a table column or within another UDT (i.e. nested UDTs). + */ +public class Schema { + + // Required fields + + @JsonProperty(required = true) + private String name; + + @JsonProperty(defaultValue = "schema", required = true) + private TypeKind type; + + // Optional fields + + @JsonProperty + private String comment; + + @JsonProperty() + private SchemaId id; + + @JsonProperty + private SchemaOptions options; + + @JsonProperty + private List properties; + + @JsonProperty + private SchemaLanguageVersion version; + + // TODO: DANOBLE: how do these properties serialize? + + private List partitionKeys; + private List primaryKeys; + private List staticKeys; + + /** + * Initializes a new instance of the {@link Schema} class. + */ + private Schema() { + this.id = SchemaId.NONE; + this.type = TypeKind.SCHEMA; + this.partitionKeys = Collections.emptyList(); + this.primaryKeys = Collections.emptyList(); + this.staticKeys = Collections.emptyList(); + } + + /** + * An (optional) comment describing the purpose of this schema. + *

+ * Comments are for documentary purpose only and do not affect the schema at runtime. + * + * @return the comment on this {@linkplain Schema schema} or {@code null}, if there is no comment. + */ + public final String comment() { + return this.comment; + } + + /** + * Sets the (optional) comment describing the purpose of this schema. + *

+ * Comments are for documentary purpose only and do not affect the schema at runtime. + * + * @param value a comment on this {@linkplain Schema schema} or {@code null} to remove the comment, if any, on this + * {@linkplain Schema schema}. + * @return a reference to this {@linkplain Schema schema}. + */ + public final Schema comment(String value) { + this.comment = value; + return this; + } + + /** + * Compiles this logical schema into a physical layout that can be used to read and write rows. + * + * @param namespace The namespace within which this schema is defined. + * @return The layout for the schema. + */ + public final Layout compile(Namespace namespace) { + + checkNotNull(namespace, "expected non-null ns"); + checkArgument(namespace.schemas().contains(this)); + + return LayoutCompiler.compile(namespace, this); + } + + /** + * The name of this {@linkplain Schema schema}. + *

+ * The name of a schema MUST be unique within its namespace. Names must begin with an alpha-numeric character and + * can only contain alpha-numeric characters and underscores. + * + * @return the name of this {@linkplain Schema schema} or {@code null}, if the name has not yet been set. + */ + public final String name() { + return this.name; + } + + /** + * Sets the name of this {@linkplain Schema schema}. + *

+ * The name of a schema MUST be unique within its namespace. Names must begin with an alpha-numeric character and + * can only contain alpha-numeric characters and underscores. + * + * @param value a name for this {@linkplain Schema schema}. + * @return a reference to this {@linkplain Schema schema}. + */ + @Nonnull + public final Schema name(@Nonnull String value) { + checkNotNull(value); + this.name = value; + return this; + } + + /** + * Schema-wide options. + * + * @return schema-wide options. + */ + public final SchemaOptions options() { + return this.options; + } + + public final Schema options(SchemaOptions value) { + this.options = value; + return this; + } + + /** + * Parse a JSON fragment and return a schema. + * + * @param value The JSON string value to parse + * @return A logical schema, if the value parses. + */ + public static Optional parse(String value) { + return Json.parse(value, Schema.class); + // TODO: DANOBLE: perform structural validation on the Schema after JSON parsing + } + + /** + * An (optional) list of zero or more logical paths that form the partition key. + *

+ * All paths referenced MUST map to a property within the schema. This field is never null. + * + * @return list of zero or more logical paths that form the partition key + */ + @Nullable + public final List partitionKeys() { + return this.partitionKeys; + } + + public final Schema partitionKeys(@Nullable List value) { + this.partitionKeys = value != null ? value : Collections.emptyList(); + return this; + } + + /** + * An (optional) list of zero or more logical paths that form the primary sort key. + *

+ * All paths referenced MUST map to a property within the schema. This field is never null. + * + * @return list of zero or more logical paths that form the partition key + */ + @Nullable + public final List primarySortKeys() { + return this.primaryKeys; + } + + public final Schema primarySortKeys(ArrayList value) { + this.primaryKeys = value != null ? value : Collections.emptyList(); + return this; + } + + /** + * A list of zero or more property definitions that define the columns within the schema. + *

+ * This field is never null. + * + * @return list of zero or more property definitions that define the columns within the schema + */ + @Nonnull + public final List properties() { + return this.properties; + } + + public final Schema properties(List value) { + this.properties = value != null ? value : Collections.emptyList(); + return this; + } + + /** + * The unique identifier for a schema. + *

+ * Identifiers must be unique within the scope of the database in which they are used. + * + * @return the unique identifier for a schema. + */ + public final SchemaId schemaId() { + return this.id; + } + + public final Schema schemaId(SchemaId value) { + this.id = value; + return this; + } + + /** + * A list of zero or more logical paths that hold data shared by all documents with same partition key. + *

+ * All paths referenced MUST map to a property within the schema. + *

+ * This field is never null. + * + * @return A list of zero or more logical paths that hold data shared by all documents with same partition key. + */ + @Nonnull + public final List staticKeys() { + return this.staticKeys; + } + + public final Schema staticKeys(List value) { + this.staticKeys = value != null ? value : Collections.emptyList(); + return this; + } + + /** + * Returns a JSON string representation of the current {@link Schema}. + * + * @return a JSON string representation of the current {@link Schema} + */ + @Override + public String toString() { + return Json.toString(this); + } + + /** + * The type of this schema. + *

+ * This value MUST be {@link TypeKind#SCHEMA}. + * + * @return the type of this schema. + */ + public final TypeKind type() { + return this.type; + } + + public final Schema type(TypeKind value) { + this.type = value; + return this; + } + + /** + * The version of the HybridRow Schema Definition Language used to encode this schema. + * + * @return the version of the HybridRow Schema Definition Language used to encode this schema. + */ + public final SchemaLanguageVersion version() { + return this.version; + } + + public final Schema version(SchemaLanguageVersion value) { + this.version = value; + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaException.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaException.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaException.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaException.java index 6d53066..458cd52 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaException.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaException.java @@ -1,20 +1,20 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import java.io.Serializable; - -public final class SchemaException extends RuntimeException implements Serializable { - - public SchemaException() { - } - - public SchemaException(String message) { - super(message); - } - - public SchemaException(String message, RuntimeException innerException) { - super(message, innerException); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import java.io.Serializable; + +public final class SchemaException extends RuntimeException implements Serializable { + + public SchemaException() { + } + + public SchemaException(String message) { + super(message); + } + + public SchemaException(String message, RuntimeException innerException) { + super(message, innerException); + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaHash.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaHash.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaHash.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaHash.java index 1eb42e4..f02d0a4 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaHash.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaHash.java @@ -1,213 +1,213 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.azure.data.cosmos.serialization.hybridrow.HashCode128; -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.azure.data.cosmos.serialization.hybridrow.internal.Murmur3Hash; - -import java.util.Optional; -import java.util.stream.Stream; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Strings.lenientFormat; - -public final class SchemaHash { - - /** - * Computes the logical hash for a logical schema. - * - * @param namespace The namespace within which {@code schema} is defined. - * @param schema The logical schema to compute the hash of. - * @param seed The seed to initialized the hash function. - * @return The logical 128-bit hash as a two-tuple (low, high). - */ - public static HashCode128 computeHash(Namespace namespace, Schema schema, HashCode128 seed) { - HashCode128 hash = seed; - - hash = Murmur3Hash.Hash128(schema.schemaId().value(), hash); - hash = Murmur3Hash.Hash128(schema.type().value(), hash); - hash = SchemaHash.computeHash(namespace, schema.options(), hash); - - if (schema.partitionKeys() != null) { - for (PartitionKey partitionKey : schema.partitionKeys()) { - hash = SchemaHash.computeHash(namespace, partitionKey, hash); - } - } - - if (schema.primarySortKeys() != null) { - for (PrimarySortKey p : schema.primarySortKeys()) { - hash = SchemaHash.computeHash(namespace, p, hash); - } - } - - if (schema.staticKeys() != null) { - for (StaticKey p : schema.staticKeys()) { - hash = SchemaHash.computeHash(namespace, p, hash); - } - } - - if (schema.properties() != null) { - for (Property p : schema.properties()) { - hash = SchemaHash.computeHash(namespace, p, hash); - } - } - - return hash; - } - - private static HashCode128 computeHash(Namespace namespace, SchemaOptions options, HashCode128 seed) { - - HashCode128 hash = seed; - - hash = Murmur3Hash.Hash128(options != null && options.disallowUnschematized(), hash); - hash = Murmur3Hash.Hash128(options != null && options.enablePropertyLevelTimestamp(), hash); - hash = Murmur3Hash.Hash128(options != null && options.disableSystemPrefix(), hash); - - return hash; - } - - private static HashCode128 computeHash(Namespace ns, Property p, HashCode128 seed) { - - HashCode128 hash = seed; - - hash = Murmur3Hash.Hash128(p.path(), hash); - hash = SchemaHash.computeHash(ns, p.type(), hash); - - return hash; - } - - private static HashCode128 computeHash(Namespace namespace, PropertyType p, HashCode128 seed) { - - HashCode128 hash = seed; - - hash = Murmur3Hash.Hash128(p.type().value(), hash); - hash = Murmur3Hash.Hash128(p.nullable(), hash); - - if (p.apiType() != null) { - hash = Murmur3Hash.Hash128(p.apiType(), hash); - } - - if (p instanceof PrimitivePropertyType) { - - PrimitivePropertyType pp = (PrimitivePropertyType) p; - - hash = Murmur3Hash.Hash128(pp.storage().value(), hash); - hash = Murmur3Hash.Hash128(pp.length(), hash); - - return hash; - } - - checkState(p instanceof ScopePropertyType); - ScopePropertyType pp = (ScopePropertyType) p; - hash = Murmur3Hash.Hash128(pp.immutable(), hash); - - if (p instanceof ArrayPropertyType) { - ArrayPropertyType spp = (ArrayPropertyType) p; - if (spp.items() != null) { - hash = SchemaHash.computeHash(namespace, spp.items(), hash); - } - return hash; - } - - if (p instanceof ObjectPropertyType) { - ObjectPropertyType spp = (ObjectPropertyType) p; - if (spp.properties() != null) { - for (Property opp : spp.properties()) { - hash = SchemaHash.computeHash(namespace, opp, hash); - } - } - return hash; - } - - if (p instanceof MapPropertyType) { - - MapPropertyType spp = (MapPropertyType) p; - - if (spp.keys() != null) { - hash = SchemaHash.computeHash(namespace, spp.keys(), hash); - } - - if (spp.values() != null) { - hash = SchemaHash.computeHash(namespace, spp.values(), hash); - } - - return hash; - } - - if (p instanceof SetPropertyType) { - - SetPropertyType spp = (SetPropertyType) p; - - if (spp.items() != null) { - hash = SchemaHash.computeHash(namespace, spp.items(), hash); - } - - return hash; - } - - if (p instanceof TaggedPropertyType) { - - TaggedPropertyType spp = (TaggedPropertyType) p; - - if (spp.items() != null) { - for (PropertyType pt : spp.items()) { - hash = SchemaHash.computeHash(namespace, pt, hash); - } - } - - return hash; - } - - if (p instanceof TuplePropertyType) { - - TuplePropertyType spp = (TuplePropertyType) p; - - if (spp.items() != null) { - for (PropertyType pt : spp.items()) { - hash = SchemaHash.computeHash(namespace, pt, hash); - } - } - - return hash; - } - - if (p instanceof UdtPropertyType) { - - Stream schemaStream = namespace.schemas().stream(); - UdtPropertyType spp = (UdtPropertyType) p; - Optional udtSchema; - - if (spp.schemaId() == SchemaId.INVALID) { - udtSchema = schemaStream.filter(schema -> schema.name().equals(spp.name())).findFirst(); - } else { - udtSchema = schemaStream.filter(schema -> schema.schemaId().equals(spp.schemaId())).findFirst(); - udtSchema.ifPresent(schema -> checkState(schema.name().equals(spp.name()), - "Ambiguous schema reference: '%s:%s'", spp.name(), spp.schemaId())); - } - - checkState(udtSchema.isPresent(), "Cannot resolve schema reference '{0}:{1}'", spp.name(), spp.schemaId()); - return SchemaHash.computeHash(namespace, udtSchema.get(), hash); - } - - throw new IllegalStateException(lenientFormat("unrecognized property type: %s", p.getClass())); - } - - private static HashCode128 computeHash(Namespace namespace, PartitionKey key, HashCode128 seed) { - return key == null ? seed : Murmur3Hash.Hash128(key.path(), seed); - } - - private static HashCode128 computeHash(Namespace namespace, PrimarySortKey key, HashCode128 seed) { - HashCode128 hash = seed; - if (key != null) { - hash = Murmur3Hash.Hash128(key.path(), hash); - hash = Murmur3Hash.Hash128(key.direction().value(), hash); - } - return hash; - } - - private static HashCode128 computeHash(Namespace ns, StaticKey key, HashCode128 seed) { - return key == null ? seed : Murmur3Hash.Hash128(key.path(), seed); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.azure.data.cosmos.serialization.hybridrow.HashCode128; +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.azure.data.cosmos.serialization.hybridrow.internal.Murmur3Hash; + +import java.util.Optional; +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.lenientFormat; + +public final class SchemaHash { + + /** + * Computes the logical hash for a logical schema. + * + * @param namespace The namespace within which {@code schema} is defined. + * @param schema The logical schema to compute the hash of. + * @param seed The seed to initialized the hash function. + * @return The logical 128-bit hash as a two-tuple (low, high). + */ + public static HashCode128 computeHash(Namespace namespace, Schema schema, HashCode128 seed) { + HashCode128 hash = seed; + + hash = Murmur3Hash.Hash128(schema.schemaId().value(), hash); + hash = Murmur3Hash.Hash128(schema.type().value(), hash); + hash = SchemaHash.computeHash(namespace, schema.options(), hash); + + if (schema.partitionKeys() != null) { + for (PartitionKey partitionKey : schema.partitionKeys()) { + hash = SchemaHash.computeHash(namespace, partitionKey, hash); + } + } + + if (schema.primarySortKeys() != null) { + for (PrimarySortKey p : schema.primarySortKeys()) { + hash = SchemaHash.computeHash(namespace, p, hash); + } + } + + if (schema.staticKeys() != null) { + for (StaticKey p : schema.staticKeys()) { + hash = SchemaHash.computeHash(namespace, p, hash); + } + } + + if (schema.properties() != null) { + for (Property p : schema.properties()) { + hash = SchemaHash.computeHash(namespace, p, hash); + } + } + + return hash; + } + + private static HashCode128 computeHash(Namespace namespace, SchemaOptions options, HashCode128 seed) { + + HashCode128 hash = seed; + + hash = Murmur3Hash.Hash128(options != null && options.disallowUnschematized(), hash); + hash = Murmur3Hash.Hash128(options != null && options.enablePropertyLevelTimestamp(), hash); + hash = Murmur3Hash.Hash128(options != null && options.disableSystemPrefix(), hash); + + return hash; + } + + private static HashCode128 computeHash(Namespace ns, Property p, HashCode128 seed) { + + HashCode128 hash = seed; + + hash = Murmur3Hash.Hash128(p.path(), hash); + hash = SchemaHash.computeHash(ns, p.type(), hash); + + return hash; + } + + private static HashCode128 computeHash(Namespace namespace, PropertyType p, HashCode128 seed) { + + HashCode128 hash = seed; + + hash = Murmur3Hash.Hash128(p.type().value(), hash); + hash = Murmur3Hash.Hash128(p.nullable(), hash); + + if (p.apiType() != null) { + hash = Murmur3Hash.Hash128(p.apiType(), hash); + } + + if (p instanceof PrimitivePropertyType) { + + PrimitivePropertyType pp = (PrimitivePropertyType) p; + + hash = Murmur3Hash.Hash128(pp.storage().value(), hash); + hash = Murmur3Hash.Hash128(pp.length(), hash); + + return hash; + } + + checkState(p instanceof ScopePropertyType); + ScopePropertyType pp = (ScopePropertyType) p; + hash = Murmur3Hash.Hash128(pp.immutable(), hash); + + if (p instanceof ArrayPropertyType) { + ArrayPropertyType spp = (ArrayPropertyType) p; + if (spp.items() != null) { + hash = SchemaHash.computeHash(namespace, spp.items(), hash); + } + return hash; + } + + if (p instanceof ObjectPropertyType) { + ObjectPropertyType spp = (ObjectPropertyType) p; + if (spp.properties() != null) { + for (Property opp : spp.properties()) { + hash = SchemaHash.computeHash(namespace, opp, hash); + } + } + return hash; + } + + if (p instanceof MapPropertyType) { + + MapPropertyType spp = (MapPropertyType) p; + + if (spp.keys() != null) { + hash = SchemaHash.computeHash(namespace, spp.keys(), hash); + } + + if (spp.values() != null) { + hash = SchemaHash.computeHash(namespace, spp.values(), hash); + } + + return hash; + } + + if (p instanceof SetPropertyType) { + + SetPropertyType spp = (SetPropertyType) p; + + if (spp.items() != null) { + hash = SchemaHash.computeHash(namespace, spp.items(), hash); + } + + return hash; + } + + if (p instanceof TaggedPropertyType) { + + TaggedPropertyType spp = (TaggedPropertyType) p; + + if (spp.items() != null) { + for (PropertyType pt : spp.items()) { + hash = SchemaHash.computeHash(namespace, pt, hash); + } + } + + return hash; + } + + if (p instanceof TuplePropertyType) { + + TuplePropertyType spp = (TuplePropertyType) p; + + if (spp.items() != null) { + for (PropertyType pt : spp.items()) { + hash = SchemaHash.computeHash(namespace, pt, hash); + } + } + + return hash; + } + + if (p instanceof UdtPropertyType) { + + Stream schemaStream = namespace.schemas().stream(); + UdtPropertyType spp = (UdtPropertyType) p; + Optional udtSchema; + + if (spp.schemaId() == SchemaId.INVALID) { + udtSchema = schemaStream.filter(schema -> schema.name().equals(spp.name())).findFirst(); + } else { + udtSchema = schemaStream.filter(schema -> schema.schemaId().equals(spp.schemaId())).findFirst(); + udtSchema.ifPresent(schema -> checkState(schema.name().equals(spp.name()), + "Ambiguous schema reference: '%s:%s'", spp.name(), spp.schemaId())); + } + + checkState(udtSchema.isPresent(), "Cannot resolve schema reference '{0}:{1}'", spp.name(), spp.schemaId()); + return SchemaHash.computeHash(namespace, udtSchema.get(), hash); + } + + throw new IllegalStateException(lenientFormat("unrecognized property type: %s", p.getClass())); + } + + private static HashCode128 computeHash(Namespace namespace, PartitionKey key, HashCode128 seed) { + return key == null ? seed : Murmur3Hash.Hash128(key.path(), seed); + } + + private static HashCode128 computeHash(Namespace namespace, PrimarySortKey key, HashCode128 seed) { + HashCode128 hash = seed; + if (key != null) { + hash = Murmur3Hash.Hash128(key.path(), hash); + hash = Murmur3Hash.Hash128(key.direction().value(), hash); + } + return hash; + } + + private static HashCode128 computeHash(Namespace ns, StaticKey key, HashCode128 seed) { + return key == null ? seed : Murmur3Hash.Hash128(key.path(), seed); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaLanguageVersion.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaLanguageVersion.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaLanguageVersion.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaLanguageVersion.java index 4fb4663..5c68557 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaLanguageVersion.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaLanguageVersion.java @@ -1,70 +1,70 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import java.util.HashMap; - -/** - * Versions of the HybridRow Schema Description Language. - */ -public enum SchemaLanguageVersion { - /** - * Initial version of the HybridRow Schema Description Lanauge. - */ - V1((byte) 0, "v1"); - - public static final int BYTES = Byte.BYTES; - - private static HashMap mappings; - private String friendlyName; - private byte value; - - SchemaLanguageVersion(byte value, String text) { - this.value = value; - this.friendlyName = text; - mappings().put(value, this); - } - - /** - * Returns the friendly name of this enum constant. - * - * @return the friendly name of this enum constant. - * @see #toString() - */ - public String friendlyName() { - return this.friendlyName; - } - - public static SchemaLanguageVersion from(byte value) { - return mappings().get(value); - } - - /** - * Returns the friendly name of this enum constant. - * - * @return the friendly name of this enum constant. - * @see #friendlyName() - */ - @Override - public String toString() { - return this.friendlyName; - } - - public byte value() { - return this.value; - } - - private static HashMap mappings() { - if (mappings == null) { - synchronized (SchemaLanguageVersion.class) { - if (mappings == null) { - mappings = new HashMap<>(); - } - } - } - return mappings; - } - - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import java.util.HashMap; + +/** + * Versions of the HybridRow Schema Description Language. + */ +public enum SchemaLanguageVersion { + /** + * Initial version of the HybridRow Schema Description Lanauge. + */ + V1((byte) 0, "v1"); + + public static final int BYTES = Byte.BYTES; + + private static HashMap mappings; + private String friendlyName; + private byte value; + + SchemaLanguageVersion(byte value, String text) { + this.value = value; + this.friendlyName = text; + mappings().put(value, this); + } + + /** + * Returns the friendly name of this enum constant. + * + * @return the friendly name of this enum constant. + * @see #toString() + */ + public String friendlyName() { + return this.friendlyName; + } + + public static SchemaLanguageVersion from(byte value) { + return mappings().get(value); + } + + /** + * Returns the friendly name of this enum constant. + * + * @return the friendly name of this enum constant. + * @see #friendlyName() + */ + @Override + public String toString() { + return this.friendlyName; + } + + public byte value() { + return this.value; + } + + private static HashMap mappings() { + if (mappings == null) { + synchronized (SchemaLanguageVersion.class) { + if (mappings == null) { + mappings = new HashMap<>(); + } + } + } + return mappings; + } + + } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaOptions.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaOptions.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaOptions.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaOptions.java index 4db307e..040e25f 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaOptions.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaOptions.java @@ -1,66 +1,66 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -/** - * Describes the set of options that apply to the entire schema and the way it is validated. - */ -public class SchemaOptions { - - private boolean disableSystemPrefix; - private boolean disallowUnschematized; - private boolean enablePropertyLevelTimestamp; - - /** - * {@code true} if prefixing system properties with a prefix of {@code "__sys_"} is disabled. - *

- * The system property prefix is required to distinguish properties owned by the store layer. - * - * @return {@code true} if prefixing system properties with a prefix of {@code "__sys_"} is disabled. - */ - public final boolean disableSystemPrefix() { - return this.disableSystemPrefix; - } - - public final void disableSystemPrefix(boolean value) { - this.disableSystemPrefix = value; - } - - /** - * {@code true} if structural schema validation is enabled. - *

- * When structural schema validation is enabled then attempting to store an unschematized - * path in the row, or a value whose type does not conform to the type constraints defined for that - * path within the schema will lead to a schema validation error. When structural schema validation is - * NOT enabled, then storing an unschematized path or non-confirming value will lead to a sparse - * column override of the path. The value will be stored (and any existing value at that path will be - * overwritten). No error will be given. - * - * @return {@code true} if structural schema validation is enabled. - */ - public final boolean disallowUnschematized() { - return this.disallowUnschematized; - } - - public final void disallowUnschematized(boolean value) { - this.disallowUnschematized = value; - } - - /** - * {@code true} if behavior in the Schema that acts based on property level timestamps is triggered. - *

- * In Cassandra, this means that new columns are added for each top level property that has values of the client - * side timestamp. This is then used in conflict resolution to independently resolve each property based on the - * timestamp value of that property. - * - * @return {@code true} if behavior in the Schema that acts based on property level timestamps is triggered. - */ - public final boolean enablePropertyLevelTimestamp() { - return this.enablePropertyLevelTimestamp; - } - - public final void enablePropertyLevelTimestamp(boolean value) { - this.enablePropertyLevelTimestamp = value; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +/** + * Describes the set of options that apply to the entire schema and the way it is validated. + */ +public class SchemaOptions { + + private boolean disableSystemPrefix; + private boolean disallowUnschematized; + private boolean enablePropertyLevelTimestamp; + + /** + * {@code true} if prefixing system properties with a prefix of {@code "__sys_"} is disabled. + *

+ * The system property prefix is required to distinguish properties owned by the store layer. + * + * @return {@code true} if prefixing system properties with a prefix of {@code "__sys_"} is disabled. + */ + public final boolean disableSystemPrefix() { + return this.disableSystemPrefix; + } + + public final void disableSystemPrefix(boolean value) { + this.disableSystemPrefix = value; + } + + /** + * {@code true} if structural schema validation is enabled. + *

+ * When structural schema validation is enabled then attempting to store an unschematized + * path in the row, or a value whose type does not conform to the type constraints defined for that + * path within the schema will lead to a schema validation error. When structural schema validation is + * NOT enabled, then storing an unschematized path or non-confirming value will lead to a sparse + * column override of the path. The value will be stored (and any existing value at that path will be + * overwritten). No error will be given. + * + * @return {@code true} if structural schema validation is enabled. + */ + public final boolean disallowUnschematized() { + return this.disallowUnschematized; + } + + public final void disallowUnschematized(boolean value) { + this.disallowUnschematized = value; + } + + /** + * {@code true} if behavior in the Schema that acts based on property level timestamps is triggered. + *

+ * In Cassandra, this means that new columns are added for each top level property that has values of the client + * side timestamp. This is then used in conflict resolution to independently resolve each property based on the + * timestamp value of that property. + * + * @return {@code true} if behavior in the Schema that acts based on property level timestamps is triggered. + */ + public final boolean enablePropertyLevelTimestamp() { + return this.enablePropertyLevelTimestamp; + } + + public final void enablePropertyLevelTimestamp(boolean value) { + this.enablePropertyLevelTimestamp = value; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaValidator.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaValidator.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaValidator.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaValidator.java index af6af00..65d7be3 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaValidator.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SchemaValidator.java @@ -1,326 +1,326 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.azure.data.cosmos.core.Json; -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.google.common.base.Strings; -import org.checkerframework.checker.nullness.qual.NonNull; - -import javax.annotation.Nonnull; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.lenientFormat; - -public final class SchemaValidator { - - public static void validate(@NonNull final Namespace namespace) { - - checkNotNull(namespace, "expected non-null namespace"); - - final int initialCapacity = namespace.schemas().size(); - - final Map nameDupCheck = new HashMap<>(initialCapacity); - final Map nameVersioningCheck = new HashMap<>(initialCapacity); - final Map idDupCheck = new HashMap<>(initialCapacity); - - for (Schema schema : namespace.schemas()) { - - SchemaIdentification identification = SchemaIdentification.of(schema.name(), schema.schemaId()); - - Assert.isValidIdentifier(identification.name(), "Schema name"); - Assert.isValidSchemaId(identification.id(), "Schema id"); - Assert.duplicateCheck(identification.id(), schema, idDupCheck, "Schema id", "Namespace"); - Assert.duplicateCheck(identification, schema, nameDupCheck, "Schema reference", "Namespace"); - - nameVersioningCheck.compute(schema.name(), (name, count) -> count == null ? 1 : count + 1); - } - - // Enable id-less Schema references for all types with a unique version in the namespace - - for (Schema schema : namespace.schemas()) { - if (nameVersioningCheck.get(schema.name()) == 1) { - Assert.duplicateCheck( - SchemaIdentification.of(schema.name(), SchemaId.NONE), schema, nameDupCheck, - "Schema reference", "Namespace" - ); - } - } - - SchemaValidator.visit(namespace, nameDupCheck, idDupCheck); - } - - /** - * Visit an entire namespace and validate its constraints. - * - * @param namespace The {@link Namespace} to validate. - * @param schemas A map from schema names within the namespace to their schemas. - * @param ids A map from schema ids within the namespace to their schemas. - */ - private static void visit( - Namespace namespace, Map schemas, Map ids) { - for (Schema schema : namespace.schemas()) { - SchemaValidator.visit(schema, schemas, ids); - } - } - - /** - * Visit a single schema and validate its constraints. - * - * @param schema The {@link Schema} to validate. - * @param schemas A map from schema names within the namespace to their schemas. - * @param ids A map from schema ids within the namespace to their schemas. - */ - private static void visit(Schema schema, Map schemas, Map ids) { - - Assert.areEqual( - schema.type(), TypeKind.SCHEMA, lenientFormat("The type of a schema MUST be %s: %s", TypeKind.SCHEMA, - schema.type()) - ); - - HashMap pathDupCheck = new HashMap<>(schema.properties().size()); - - for (Property p : schema.properties()) { - Assert.duplicateCheck(p.path(), p, pathDupCheck, "Property path", "Schema"); - } - - for (PartitionKey pk : schema.partitionKeys()) { - Assert.exists(pk.path(), pathDupCheck, "Partition key column", "Schema"); - } - - for (PrimarySortKey ps : schema.primarySortKeys()) { - Assert.exists(ps.path(), pathDupCheck, "Primary sort key column", "Schema"); - } - - for (StaticKey sk : schema.staticKeys()) { - Assert.exists(sk.path(), pathDupCheck, "Static key column", "Schema"); - } - - for (Property p : schema.properties()) { - SchemaValidator.visit(p, schema, schemas, ids); - } - } - - private static void visit( - Property p, Schema s, Map schemas, Map ids) { - - Assert.isValidIdentifier(p.path(), "Property path"); - SchemaValidator.visit(p.type(), null, schemas, ids); - } - - private static void visit( - PropertyType p, - PropertyType parent, - Map schemas, - Map ids) { - - if (p instanceof PrimitivePropertyType) { - PrimitivePropertyType pp = (PrimitivePropertyType) p; - Assert.isTrue(pp.length() >= 0, "Length MUST be positive"); - if (parent != null) { - Assert.areEqual(pp.storage(), StorageKind.SPARSE, "Nested fields MUST have storage kind SPARSE"); - } - return; - } - if (p instanceof ArrayPropertyType) { - ArrayPropertyType ap = (ArrayPropertyType) p; - if (ap.items() != null) { - SchemaValidator.visit(ap.items(), p, schemas, ids); - } - return; - } - if (p instanceof MapPropertyType) { - MapPropertyType mp = (MapPropertyType) p; - SchemaValidator.visit(mp.keys(), p, schemas, ids); - SchemaValidator.visit(mp.values(), p, schemas, ids); - return; - } - if (p instanceof SetPropertyType) { - SetPropertyType sp = (SetPropertyType) p; - SchemaValidator.visit(sp.items(), p, schemas, ids); - return; - } - if (p instanceof TaggedPropertyType) { - TaggedPropertyType gp = (TaggedPropertyType) p; - for (PropertyType item : gp.items()) { - SchemaValidator.visit(item, p, schemas, ids); - } - return; - } - if (p instanceof TuplePropertyType) { - TuplePropertyType tp = (TuplePropertyType) p; - for (PropertyType item : tp.items()) { - SchemaValidator.visit(item, p, schemas, ids); - } - return; - } - if (p instanceof ObjectPropertyType) { - ObjectPropertyType op = (ObjectPropertyType) p; - Map pathDupCheck = new HashMap<>(op.properties().size()); - for (Property nested : op.properties()) { - Assert.duplicateCheck(nested.path(), nested, pathDupCheck, "Property path", "Object"); - SchemaValidator.visit(nested.type(), p, schemas, ids); - } - return; - } - if (p instanceof UdtPropertyType) { - UdtPropertyType up = (UdtPropertyType) p; - Assert.exists(SchemaIdentification.of(up.name(), up.schemaId()), schemas, "Schema reference", "Namespace"); - if (up.schemaId() != SchemaId.INVALID) { - Schema s = Assert.exists(up.schemaId(), ids, "Schema id", "Namespace"); - Assert.areEqual(up.name(), s.name(), lenientFormat("Schema name '%s' does not match the name of " + - "schema with id '%s': %s", up.name(), up.schemaId(), s.name())); - } - return; - } - throw new IllegalStateException(lenientFormat("Unknown property type: %s", p.getClass())); - } - - private static class Assert { - - /** - * Validate two values are equal. - * - * @param Type of the values to compare. - * @param left The left value to compare. - * @param right The right value to compare. - * @param message Diagnostic message if the comparison fails. - */ - static void areEqual(T left, T right, String message) { - if (!left.equals(right)) { - throw new SchemaException(message); - } - } - - /** - * Validate {@code key} does not already appear within the given scope. - * - * @param The type of the keys within the scope. - * @param The type of the values within the scope. - * @param key The key to check. - * @param value The value to add to the scope if there is no duplicate. - * @param scope The set of existing values within the scope. - * @param label Diagnostic label describing {@code key}. - * @param scopeLabel Diagnostic label describing {@code scope}. - */ - static void duplicateCheck( - TKey key, TValue value, Map scope, String label, String scopeLabel) { - if (scope.containsKey(key)) { - throw new SchemaException(lenientFormat("%s must be unique within a %s: %s", label, scopeLabel, key)); - } - scope.put(key, value); - } - - /** - * Validate {@code key} does appear within the given scope. - * - * @param The type of the keys within the scope. - * @param The type of the values within the scope. - * @param key The key to check. - * @param scope The set of existing values within the scope. - * @param label Diagnostic label describing {@code key}. - * @param scopeLabel Diagnostic label describing {@code scope}. - */ - static TValue exists(TKey key, Map scope, String label, String scopeLabel) { - TValue value = scope.get(key); - if (value == null) { - throw new SchemaException(lenientFormat("%s must exist within a %s: %s", label, scopeLabel, key)); - } - return value; - } - - /** - * Validate a predicate is true. - * - * @param predicate The predicate to check. - * @param message Diagnostic message if the comparison fails. - */ - static void isTrue(boolean predicate, String message) { - if (!predicate) { - throw new SchemaException(message); - } - } - - /** - * Validate {@code identifier} contains only characters valid in a schema identifier. - * - * @param identifier The identifier to check. - * @param label Diagnostic label describing {@code identifier}. - */ - static void isValidIdentifier(String identifier, String label) { - if (Strings.isNullOrEmpty(identifier)) { - throw new SchemaException(lenientFormat("%s must be a valid identifier: %s", label, identifier)); - } - } - - /** - * Validate a {@link SchemaId}. - * - * @param id The id to check. - * @param label Diagnostic label describing {@code id}. - */ - static void isValidSchemaId(SchemaId id, String label) { - if (id == SchemaId.INVALID) { - throw new SchemaException(lenientFormat("%s cannot be 0", label)); - } - } - } - - private static class SchemaIdentification implements Comparable { - - private final SchemaId id; - private final String name; - - private SchemaIdentification(@Nonnull final String name, @Nonnull final SchemaId id) { - checkNotNull(name, "expected non-null name"); - checkNotNull(id, "expected non-null id"); - this.name = name; - this.id = id; - } - - @Override - public int compareTo(@Nonnull SchemaIdentification other) { - checkNotNull(other, "expected non-null other"); - int result = Integer.compare(this.id.value(), other.id.value()); - return result == 0 ? this.name().compareTo(other.name()) : result; - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (null == other || this.getClass() != other.getClass()) { - return false; - } - SchemaIdentification that = (SchemaIdentification) other; - return this.id.equals(that.id) && this.name.equals(that.name); - } - - @Override - public int hashCode() { - return Objects.hash(this.id, this.name); - } - - public SchemaId id() { - return this.id; - } - - public String name() { - return this.name; - } - - public static SchemaIdentification of(@NonNull String name, @NonNull SchemaId id) { - return new SchemaIdentification(name, id); - } - - @Override - public String toString() { - return Json.toString(this); - } - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.azure.data.cosmos.core.Json; +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.google.common.base.Strings; +import org.checkerframework.checker.nullness.qual.NonNull; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.lenientFormat; + +public final class SchemaValidator { + + public static void validate(@NonNull final Namespace namespace) { + + checkNotNull(namespace, "expected non-null namespace"); + + final int initialCapacity = namespace.schemas().size(); + + final Map nameDupCheck = new HashMap<>(initialCapacity); + final Map nameVersioningCheck = new HashMap<>(initialCapacity); + final Map idDupCheck = new HashMap<>(initialCapacity); + + for (Schema schema : namespace.schemas()) { + + SchemaIdentification identification = SchemaIdentification.of(schema.name(), schema.schemaId()); + + Assert.isValidIdentifier(identification.name(), "Schema name"); + Assert.isValidSchemaId(identification.id(), "Schema id"); + Assert.duplicateCheck(identification.id(), schema, idDupCheck, "Schema id", "Namespace"); + Assert.duplicateCheck(identification, schema, nameDupCheck, "Schema reference", "Namespace"); + + nameVersioningCheck.compute(schema.name(), (name, count) -> count == null ? 1 : count + 1); + } + + // Enable id-less Schema references for all types with a unique version in the namespace + + for (Schema schema : namespace.schemas()) { + if (nameVersioningCheck.get(schema.name()) == 1) { + Assert.duplicateCheck( + SchemaIdentification.of(schema.name(), SchemaId.NONE), schema, nameDupCheck, + "Schema reference", "Namespace" + ); + } + } + + SchemaValidator.visit(namespace, nameDupCheck, idDupCheck); + } + + /** + * Visit an entire namespace and validate its constraints. + * + * @param namespace The {@link Namespace} to validate. + * @param schemas A map from schema names within the namespace to their schemas. + * @param ids A map from schema ids within the namespace to their schemas. + */ + private static void visit( + Namespace namespace, Map schemas, Map ids) { + for (Schema schema : namespace.schemas()) { + SchemaValidator.visit(schema, schemas, ids); + } + } + + /** + * Visit a single schema and validate its constraints. + * + * @param schema The {@link Schema} to validate. + * @param schemas A map from schema names within the namespace to their schemas. + * @param ids A map from schema ids within the namespace to their schemas. + */ + private static void visit(Schema schema, Map schemas, Map ids) { + + Assert.areEqual( + schema.type(), TypeKind.SCHEMA, lenientFormat("The type of a schema MUST be %s: %s", TypeKind.SCHEMA, + schema.type()) + ); + + HashMap pathDupCheck = new HashMap<>(schema.properties().size()); + + for (Property p : schema.properties()) { + Assert.duplicateCheck(p.path(), p, pathDupCheck, "Property path", "Schema"); + } + + for (PartitionKey pk : schema.partitionKeys()) { + Assert.exists(pk.path(), pathDupCheck, "Partition key column", "Schema"); + } + + for (PrimarySortKey ps : schema.primarySortKeys()) { + Assert.exists(ps.path(), pathDupCheck, "Primary sort key column", "Schema"); + } + + for (StaticKey sk : schema.staticKeys()) { + Assert.exists(sk.path(), pathDupCheck, "Static key column", "Schema"); + } + + for (Property p : schema.properties()) { + SchemaValidator.visit(p, schema, schemas, ids); + } + } + + private static void visit( + Property p, Schema s, Map schemas, Map ids) { + + Assert.isValidIdentifier(p.path(), "Property path"); + SchemaValidator.visit(p.type(), null, schemas, ids); + } + + private static void visit( + PropertyType p, + PropertyType parent, + Map schemas, + Map ids) { + + if (p instanceof PrimitivePropertyType) { + PrimitivePropertyType pp = (PrimitivePropertyType) p; + Assert.isTrue(pp.length() >= 0, "Length MUST be positive"); + if (parent != null) { + Assert.areEqual(pp.storage(), StorageKind.SPARSE, "Nested fields MUST have storage kind SPARSE"); + } + return; + } + if (p instanceof ArrayPropertyType) { + ArrayPropertyType ap = (ArrayPropertyType) p; + if (ap.items() != null) { + SchemaValidator.visit(ap.items(), p, schemas, ids); + } + return; + } + if (p instanceof MapPropertyType) { + MapPropertyType mp = (MapPropertyType) p; + SchemaValidator.visit(mp.keys(), p, schemas, ids); + SchemaValidator.visit(mp.values(), p, schemas, ids); + return; + } + if (p instanceof SetPropertyType) { + SetPropertyType sp = (SetPropertyType) p; + SchemaValidator.visit(sp.items(), p, schemas, ids); + return; + } + if (p instanceof TaggedPropertyType) { + TaggedPropertyType gp = (TaggedPropertyType) p; + for (PropertyType item : gp.items()) { + SchemaValidator.visit(item, p, schemas, ids); + } + return; + } + if (p instanceof TuplePropertyType) { + TuplePropertyType tp = (TuplePropertyType) p; + for (PropertyType item : tp.items()) { + SchemaValidator.visit(item, p, schemas, ids); + } + return; + } + if (p instanceof ObjectPropertyType) { + ObjectPropertyType op = (ObjectPropertyType) p; + Map pathDupCheck = new HashMap<>(op.properties().size()); + for (Property nested : op.properties()) { + Assert.duplicateCheck(nested.path(), nested, pathDupCheck, "Property path", "Object"); + SchemaValidator.visit(nested.type(), p, schemas, ids); + } + return; + } + if (p instanceof UdtPropertyType) { + UdtPropertyType up = (UdtPropertyType) p; + Assert.exists(SchemaIdentification.of(up.name(), up.schemaId()), schemas, "Schema reference", "Namespace"); + if (up.schemaId() != SchemaId.INVALID) { + Schema s = Assert.exists(up.schemaId(), ids, "Schema id", "Namespace"); + Assert.areEqual(up.name(), s.name(), lenientFormat("Schema name '%s' does not match the name of " + + "schema with id '%s': %s", up.name(), up.schemaId(), s.name())); + } + return; + } + throw new IllegalStateException(lenientFormat("Unknown property type: %s", p.getClass())); + } + + private static class Assert { + + /** + * Validate two values are equal. + * + * @param Type of the values to compare. + * @param left The left value to compare. + * @param right The right value to compare. + * @param message Diagnostic message if the comparison fails. + */ + static void areEqual(T left, T right, String message) { + if (!left.equals(right)) { + throw new SchemaException(message); + } + } + + /** + * Validate {@code key} does not already appear within the given scope. + * + * @param The type of the keys within the scope. + * @param The type of the values within the scope. + * @param key The key to check. + * @param value The value to add to the scope if there is no duplicate. + * @param scope The set of existing values within the scope. + * @param label Diagnostic label describing {@code key}. + * @param scopeLabel Diagnostic label describing {@code scope}. + */ + static void duplicateCheck( + TKey key, TValue value, Map scope, String label, String scopeLabel) { + if (scope.containsKey(key)) { + throw new SchemaException(lenientFormat("%s must be unique within a %s: %s", label, scopeLabel, key)); + } + scope.put(key, value); + } + + /** + * Validate {@code key} does appear within the given scope. + * + * @param The type of the keys within the scope. + * @param The type of the values within the scope. + * @param key The key to check. + * @param scope The set of existing values within the scope. + * @param label Diagnostic label describing {@code key}. + * @param scopeLabel Diagnostic label describing {@code scope}. + */ + static TValue exists(TKey key, Map scope, String label, String scopeLabel) { + TValue value = scope.get(key); + if (value == null) { + throw new SchemaException(lenientFormat("%s must exist within a %s: %s", label, scopeLabel, key)); + } + return value; + } + + /** + * Validate a predicate is true. + * + * @param predicate The predicate to check. + * @param message Diagnostic message if the comparison fails. + */ + static void isTrue(boolean predicate, String message) { + if (!predicate) { + throw new SchemaException(message); + } + } + + /** + * Validate {@code identifier} contains only characters valid in a schema identifier. + * + * @param identifier The identifier to check. + * @param label Diagnostic label describing {@code identifier}. + */ + static void isValidIdentifier(String identifier, String label) { + if (Strings.isNullOrEmpty(identifier)) { + throw new SchemaException(lenientFormat("%s must be a valid identifier: %s", label, identifier)); + } + } + + /** + * Validate a {@link SchemaId}. + * + * @param id The id to check. + * @param label Diagnostic label describing {@code id}. + */ + static void isValidSchemaId(SchemaId id, String label) { + if (id == SchemaId.INVALID) { + throw new SchemaException(lenientFormat("%s cannot be 0", label)); + } + } + } + + private static class SchemaIdentification implements Comparable { + + private final SchemaId id; + private final String name; + + private SchemaIdentification(@Nonnull final String name, @Nonnull final SchemaId id) { + checkNotNull(name, "expected non-null name"); + checkNotNull(id, "expected non-null id"); + this.name = name; + this.id = id; + } + + @Override + public int compareTo(@Nonnull SchemaIdentification other) { + checkNotNull(other, "expected non-null other"); + int result = Integer.compare(this.id.value(), other.id.value()); + return result == 0 ? this.name().compareTo(other.name()) : result; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (null == other || this.getClass() != other.getClass()) { + return false; + } + SchemaIdentification that = (SchemaIdentification) other; + return this.id.equals(that.id) && this.name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.name); + } + + public SchemaId id() { + return this.id; + } + + public String name() { + return this.name; + } + + public static SchemaIdentification of(@NonNull String name, @NonNull SchemaId id) { + return new SchemaIdentification(name, id); + } + + @Override + public String toString() { + return Json.toString(this); + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ScopePropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ScopePropertyType.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ScopePropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ScopePropertyType.java index d8c68e6..aaf5d68 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ScopePropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/ScopePropertyType.java @@ -1,24 +1,24 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -public abstract class ScopePropertyType extends PropertyType { - - private boolean immutable; - - /** - * {@code true} if the property's child elements cannot be mutated in place. - *

- * Immutable properties can still be replaced in their entirety. - * - * @return {@code true} if the property's child elements cannot be mutated in place. - */ - public final boolean immutable() { - return this.immutable; - } - - public final void immutable(boolean value) { - this.immutable = value; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +public abstract class ScopePropertyType extends PropertyType { + + private boolean immutable; + + /** + * {@code true} if the property's child elements cannot be mutated in place. + *

+ * Immutable properties can still be replaced in their entirety. + * + * @return {@code true} if the property's child elements cannot be mutated in place. + */ + public final boolean immutable() { + return this.immutable; + } + + public final void immutable(boolean value) { + this.immutable = value; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SetPropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SetPropertyType.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SetPropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SetPropertyType.java index 6dcdff3..6f231d4 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SetPropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SetPropertyType.java @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -/** - * Set properties represent an unbounded set of zero or more unique items. - *

- * Sets may be typed or untyped. Within typed sets, all items MUST be the same type. The - * type of items is specified via {@link #items}. Typed sets may be stored more efficiently - * than untyped sets. When {@link #items} is unspecified, the set is untyped and its items - * may be heterogeneous. - *

- * Each item within a set must be unique. Uniqueness is defined by the HybridRow encoded sequence - * of bytes for the item. - */ -public class SetPropertyType extends ScopePropertyType { - /** - * (Optional) type of the elements of the set, if a typed set, otherwise null. - */ - private PropertyType items; - - public final PropertyType items() { - return this.items; - } - - public final SetPropertyType items(PropertyType value) { - this.items = value; - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +/** + * Set properties represent an unbounded set of zero or more unique items. + *

+ * Sets may be typed or untyped. Within typed sets, all items MUST be the same type. The + * type of items is specified via {@link #items}. Typed sets may be stored more efficiently + * than untyped sets. When {@link #items} is unspecified, the set is untyped and its items + * may be heterogeneous. + *

+ * Each item within a set must be unique. Uniqueness is defined by the HybridRow encoded sequence + * of bytes for the item. + */ +public class SetPropertyType extends ScopePropertyType { + /** + * (Optional) type of the elements of the set, if a typed set, otherwise null. + */ + private PropertyType items; + + public final PropertyType items() { + return this.items; + } + + public final SetPropertyType items(PropertyType value) { + this.items = value; + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SortDirection.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SortDirection.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SortDirection.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SortDirection.java index 31e54e4..778bbfe 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SortDirection.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/SortDirection.java @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.google.common.base.Suppliers; -import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; - -import java.util.Arrays; -import java.util.function.Supplier; - -/** - * Describes the sort order direction. - */ -public enum SortDirection { - /** - * Sorts from the lowest to the highest value. - */ - ASCENDING(0), - - /** - * Sorts from the highests to the lowest value. - */ - DESCENDING(1); - - public static final int BYTEST = Integer.BYTES; - - private static final Supplier> mappings = Suppliers.memoize(() -> { - SortDirection[] constants = SortDirection.class.getEnumConstants(); - int[] values = new int[constants.length]; - Arrays.setAll(values, index -> constants[index].value); - return new Int2ReferenceArrayMap<>(values, constants); - }); - - private final int value; - - SortDirection(int value) { - this.value = value; - } - - public int value() { - return this.value; - } - - public static SortDirection from(int value) { - return mappings.get().get(value); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; + +import java.util.Arrays; +import java.util.function.Supplier; + +/** + * Describes the sort order direction. + */ +public enum SortDirection { + /** + * Sorts from the lowest to the highest value. + */ + ASCENDING(0), + + /** + * Sorts from the highests to the lowest value. + */ + DESCENDING(1); + + public static final int BYTEST = Integer.BYTES; + + private static final Supplier> mappings = Suppliers.memoize(() -> { + SortDirection[] constants = SortDirection.class.getEnumConstants(); + int[] values = new int[constants.length]; + Arrays.setAll(values, index -> constants[index].value); + return new Int2ReferenceArrayMap<>(values, constants); + }); + + private final int value; + + SortDirection(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + + public static SortDirection from(int value) { + return mappings.get().get(value); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StaticKey.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StaticKey.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StaticKey.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StaticKey.java index ede2e4a..bf6aee4 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StaticKey.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StaticKey.java @@ -1,28 +1,28 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -/** - * Describes a property or property set whose values MUST be the same for all rows that share the same partition key. - */ -public class StaticKey { - - private String path; - - /** - * The logical path of the referenced property. - *

- * Static path MUST refer to properties defined within the same {@link Schema}. - * - * @return the logical path of the referenced property. - */ - public final String path() { - return this.path; - } - - public final StaticKey path(String value) { - this.path = value; - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +/** + * Describes a property or property set whose values MUST be the same for all rows that share the same partition key. + */ +public class StaticKey { + + private String path; + + /** + * The logical path of the referenced property. + *

+ * Static path MUST refer to properties defined within the same {@link Schema}. + * + * @return the logical path of the referenced property. + */ + public final String path() { + return this.path; + } + + public final StaticKey path(String value) { + this.path = value; + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StorageKind.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StorageKind.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StorageKind.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StorageKind.java index 4382539..9ed8d59 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StorageKind.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/StorageKind.java @@ -1,84 +1,84 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.google.common.base.Suppliers; -import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; - -import java.util.Arrays; -import java.util.function.Supplier; - -/** - * Describes the storage placement for primitive properties. - */ -public enum StorageKind { - /** - * The property does not define a column - *

- * This is indicative of an error in the the column specification. - */ - NONE(-1, "none"), - - /** - * The property defines a sparse column - *

- * Columns marked as sparse consume no space in the row when not present. When present they appear in an unordered - * linked list at the end of the row. Access time for sparse columns is proportional to the number of sparse columns - * in the row. - */ - SPARSE(0, "sparse"), - - /** - * The property is a fixed-length, space-reserved column - *

- * The column will consume 1 null-bit, and its byte-width regardless of whether the value is present in the row. - */ - FIXED(1, "fixed"), - - /** - * The property is a variable-length column. - *

- * The column will consume 1 null-bit regardless of whether the value is present. When the value is present it will - * also consume a variable number of bytes to encode the length preceding the actual value. - *

- * When a long value is marked variable then a null-bit is reserved and the value is optionally encoded as - * variable if small enough to fit, otherwise the null-bit is set and the value is encoded as sparse. - */ - VARIABLE(2, "variable"); - - public static final int BYTES = Integer.BYTES; - - private static final Supplier> mappings = Suppliers.memoize(() -> { - StorageKind[] storageKinds = StorageKind.class.getEnumConstants(); - int[] values = new int[storageKinds.length]; - Arrays.setAll(values, index -> storageKinds[index].value); - return new Int2ReferenceArrayMap<>(values, storageKinds); - }); - - private final String friendlyName; - private final int value; - - StorageKind(int value, String friendlyName) { - this.friendlyName = friendlyName; - this.value = value; - } - - public String friendlyName() { - return this.friendlyName; - } - - public static StorageKind from(int value) { - return mappings.get().get(value); - } - - @Override - public String toString() { - return this.friendlyName; - } - - public int value() { - return this.value; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; + +import java.util.Arrays; +import java.util.function.Supplier; + +/** + * Describes the storage placement for primitive properties. + */ +public enum StorageKind { + /** + * The property does not define a column + *

+ * This is indicative of an error in the the column specification. + */ + NONE(-1, "none"), + + /** + * The property defines a sparse column + *

+ * Columns marked as sparse consume no space in the row when not present. When present they appear in an unordered + * linked list at the end of the row. Access time for sparse columns is proportional to the number of sparse columns + * in the row. + */ + SPARSE(0, "sparse"), + + /** + * The property is a fixed-length, space-reserved column + *

+ * The column will consume 1 null-bit, and its byte-width regardless of whether the value is present in the row. + */ + FIXED(1, "fixed"), + + /** + * The property is a variable-length column. + *

+ * The column will consume 1 null-bit regardless of whether the value is present. When the value is present it will + * also consume a variable number of bytes to encode the length preceding the actual value. + *

+ * When a long value is marked variable then a null-bit is reserved and the value is optionally encoded as + * variable if small enough to fit, otherwise the null-bit is set and the value is encoded as sparse. + */ + VARIABLE(2, "variable"); + + public static final int BYTES = Integer.BYTES; + + private static final Supplier> mappings = Suppliers.memoize(() -> { + StorageKind[] storageKinds = StorageKind.class.getEnumConstants(); + int[] values = new int[storageKinds.length]; + Arrays.setAll(values, index -> storageKinds[index].value); + return new Int2ReferenceArrayMap<>(values, storageKinds); + }); + + private final String friendlyName; + private final int value; + + StorageKind(int value, String friendlyName) { + this.friendlyName = friendlyName; + this.value = value; + } + + public String friendlyName() { + return this.friendlyName; + } + + public static StorageKind from(int value) { + return mappings.get().get(value); + } + + @Override + public String toString() { + return this.friendlyName; + } + + public int value() { + return this.value; + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TaggedPropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TaggedPropertyType.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TaggedPropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TaggedPropertyType.java index cd19630..2e55d31 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TaggedPropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TaggedPropertyType.java @@ -1,42 +1,42 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import java.util.ArrayList; -import java.util.List; - -/** - * Tagged properties pair one or more typed values with an API-specific uint8 type code. - *

- * The {@code UInt8} type code is implicitly in position 0 within the resulting tagged and should not be specified in - * {@link #items()}. - */ -public class TaggedPropertyType extends ScopePropertyType { - - public static final int MAX_TAGGED_ARGUMENTS = 2; - public static final int MIN_TAGGED_ARGUMENTS = 1; - - private List items; - - /** - * Initializes a new instance of the {@link TaggedPropertyType} class. - */ - public TaggedPropertyType() { - this.items = new ArrayList<>(); - } - - /** - * Types of the elements of the tagged in element order. - * - * @return a list of property types. - */ - public final List items() { - return this.items; - } - - public final TaggedPropertyType items(List value) { - this.items = value != null ? value : new ArrayList<>(); - return this; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tagged properties pair one or more typed values with an API-specific uint8 type code. + *

+ * The {@code UInt8} type code is implicitly in position 0 within the resulting tagged and should not be specified in + * {@link #items()}. + */ +public class TaggedPropertyType extends ScopePropertyType { + + public static final int MAX_TAGGED_ARGUMENTS = 2; + public static final int MIN_TAGGED_ARGUMENTS = 1; + + private List items; + + /** + * Initializes a new instance of the {@link TaggedPropertyType} class. + */ + public TaggedPropertyType() { + this.items = new ArrayList<>(); + } + + /** + * Types of the elements of the tagged in element order. + * + * @return a list of property types. + */ + public final List items() { + return this.items; + } + + public final TaggedPropertyType items(List value) { + this.items = value != null ? value : new ArrayList<>(); + return this; + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TuplePropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TuplePropertyType.java similarity index 96% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TuplePropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TuplePropertyType.java index e7571ae..3bc7153 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TuplePropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TuplePropertyType.java @@ -1,37 +1,37 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import java.util.ArrayList; -import java.util.List; - -/** - * Tuple properties represent a typed, finite, ordered set of two or more items. - */ -public class TuplePropertyType extends ScopePropertyType { - /** - * Types of the elements of the tuple in element order. - */ - private List items; - - /** - * Initializes a new instance of the {@link TuplePropertyType} class. - */ - public TuplePropertyType() { - this.items = new ArrayList(); - } - - /** - * Types of the elements of the tuple in element order. - * - * @return types of the elements of the tuple in element order. - */ - public final List items() { - return this.items; - } - - public final void items(List value) { - this.items = value != null ? value : new ArrayList(); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tuple properties represent a typed, finite, ordered set of two or more items. + */ +public class TuplePropertyType extends ScopePropertyType { + /** + * Types of the elements of the tuple in element order. + */ + private List items; + + /** + * Initializes a new instance of the {@link TuplePropertyType} class. + */ + public TuplePropertyType() { + this.items = new ArrayList(); + } + + /** + * Types of the elements of the tuple in element order. + * + * @return types of the elements of the tuple in element order. + */ + public final List items() { + return this.items; + } + + public final void items(List value) { + this.items = value != null ? value : new ArrayList(); + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TypeKind.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TypeKind.java similarity index 95% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TypeKind.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TypeKind.java index a3b357d..dc9a6d6 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TypeKind.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/TypeKind.java @@ -1,236 +1,236 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; -import com.google.common.base.Suppliers; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; - -import java.util.Arrays; -import java.util.function.Supplier; - -/** - * Describes the logical type of a property. - */ -public enum TypeKind { - /** - * Reserved. - */ - @JsonEnumDefaultValue - INVALID(0, "invalid"), - - /** - * The literal null. - *

- * When used as a fixed column, only a presence bit is allocated. When used as a sparse column, a sparse value with - * 0-length payload is written. - */ - NULL(1, "null"), - - /** - * A boolean property. - *

- * Boolean properties are allocated a single bit when schematized within a row. - */ - BOOLEAN(2, "bool"), - - /** - * 8-bit signed integer. - */ - INT_8(3, "int8"), - - /** - * 16-bit signed integer. - */ - INT_16(4, "int16"), - - /** - * 32-bit signed integer. - */ - INT_32(5, "int32"), - - /** - * 64-bit signed integer. - */ - INT_64(6, "int64"), - - /** - * 8-bit unsigned integer. - */ - UINT_8(7, "uint8"), - - /** - * 16-bit unsigned integer. - */ - UINT_16(8, "uint16"), - - /** - * 32-bit unsigned integer. - */ - UINT_32(9, "uint32"), - - /** - * 64-bit unsigned integer. - */ - UINT_64(10, "uint64"), - - /** - * Variable length encoded signed integer. - */ - VAR_INT(11, "varint"), - - /** - * Variable length encoded unsigned integer. - */ - VAR_UINT(12, "varuint"), - - /** - * 32-bit IEEE 754 floating point value. - */ - FLOAT_32(13, "float32"), - - /** - * 64-bit IEEE 754 floating point value. - */ - FLOAT_64(14, "float64"), - - /** - * 128-bit IEEE 754-2008 floating point value. - */ - FLOAT_128(15, "float128"), - - /** - * 128-bit floating point value. - * - * @see java.math.BigDecimal - */ - DECIMAL(16, "decimal"), - - /** - * 64-bit date/time value in 100ns increments from C# epoch. - * - * @see java.time.OffsetDateTime - */ - DATE_TIME(17, "datetime"), - - /** - * 64-bit date/time value in milliseconds increments from Unix epoch. - * - * @see com.azure.data.cosmos.serialization.hybridrow.UnixDateTime - */ - UNIX_DATE_TIME(18, "unixdatetime"), - - /** - * 128-bit globally unique identifier (in little-endian byte order). - */ - GUID(19, "guid"), - - /** - * 12-byte MongoDB Object Identifier (in little-endian byte order). - */ - MONGODB_OBJECT_ID(20, "mongodb.objectid"), - - /** - * Zero to MAX_ROW_SIZE bytes encoded as UTF-8 code points. - */ - UTF_8(21, "utf8"), - - /** - * Zero to MAX_ROW_SIZE untyped bytes. - */ - BINARY(22, "binary"), - - /** - * An object property. - */ - OBJECT(23, "object"), - - /** - * An array property, either typed or untyped. - */ - ARRAY(24, "array"), - - /** - * A set property, either typed or untyped. - */ - SET(25, "set"), - - /** - * A map property, either typed or untyped. - */ - MAP(26, "map"), - - /** - * A tuple property. Tuples are typed, finite, ordered, sets. - */ - TUPLE(27, "tuple"), - - /** - * A tagged property. - *

- * Tagged properties pair one or more typed values with an API-specific uint8 type code. - */ - TAGGED(28, "tagged"), - - /** - * A row with schema. - *

- * May define either a top-level table schema or a UDT (nested row). - */ - SCHEMA(29, "schema"), - - /** - * An untyped sparse field. - *

- * May only be used to define the type of a field within a nested scope. - */ - ANY(30, "any"); - - public static final int BYTES = Integer.BYTES; - - private static Supplier> mappings = Suppliers.memoize(() -> { - TypeKind[] constants = TypeKind.class.getEnumConstants(); - int[] values = new int[constants.length]; - Arrays.setAll(values, index -> constants[index].value); - return new Int2ReferenceOpenHashMap<>(values, constants); - }); - - private final String friendlyName; - private final int value; - - TypeKind(final int value, final String friendlyName) { - this.friendlyName = friendlyName; - this.value = value; - } - - /** - * Returns the friendly name of this enum constant. - * - * @return the friendly name of this enum constant. - * @see #toString() - */ - public String friendlyName() { - return this.friendlyName; - } - - public static TypeKind from(int value) { - return mappings.get().get(value); - } - - /** - * Returns the friendly name of this enum constant. - * - * @return the friendly name of this enum constant. - * @see #friendlyName() - */ - @Override - public String toString() { - return this.friendlyName; - } - - public int value() { - return this.value; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; + +import java.util.Arrays; +import java.util.function.Supplier; + +/** + * Describes the logical type of a property. + */ +public enum TypeKind { + /** + * Reserved. + */ + @JsonEnumDefaultValue + INVALID(0, "invalid"), + + /** + * The literal null. + *

+ * When used as a fixed column, only a presence bit is allocated. When used as a sparse column, a sparse value with + * 0-length payload is written. + */ + NULL(1, "null"), + + /** + * A boolean property. + *

+ * Boolean properties are allocated a single bit when schematized within a row. + */ + BOOLEAN(2, "bool"), + + /** + * 8-bit signed integer. + */ + INT_8(3, "int8"), + + /** + * 16-bit signed integer. + */ + INT_16(4, "int16"), + + /** + * 32-bit signed integer. + */ + INT_32(5, "int32"), + + /** + * 64-bit signed integer. + */ + INT_64(6, "int64"), + + /** + * 8-bit unsigned integer. + */ + UINT_8(7, "uint8"), + + /** + * 16-bit unsigned integer. + */ + UINT_16(8, "uint16"), + + /** + * 32-bit unsigned integer. + */ + UINT_32(9, "uint32"), + + /** + * 64-bit unsigned integer. + */ + UINT_64(10, "uint64"), + + /** + * Variable length encoded signed integer. + */ + VAR_INT(11, "varint"), + + /** + * Variable length encoded unsigned integer. + */ + VAR_UINT(12, "varuint"), + + /** + * 32-bit IEEE 754 floating point value. + */ + FLOAT_32(13, "float32"), + + /** + * 64-bit IEEE 754 floating point value. + */ + FLOAT_64(14, "float64"), + + /** + * 128-bit IEEE 754-2008 floating point value. + */ + FLOAT_128(15, "float128"), + + /** + * 128-bit floating point value. + * + * @see java.math.BigDecimal + */ + DECIMAL(16, "decimal"), + + /** + * 64-bit date/time value in 100ns increments from C# epoch. + * + * @see java.time.OffsetDateTime + */ + DATE_TIME(17, "datetime"), + + /** + * 64-bit date/time value in milliseconds increments from Unix epoch. + * + * @see com.azure.data.cosmos.serialization.hybridrow.UnixDateTime + */ + UNIX_DATE_TIME(18, "unixdatetime"), + + /** + * 128-bit globally unique identifier (in little-endian byte order). + */ + GUID(19, "guid"), + + /** + * 12-byte MongoDB Object Identifier (in little-endian byte order). + */ + MONGODB_OBJECT_ID(20, "mongodb.objectid"), + + /** + * Zero to MAX_ROW_SIZE bytes encoded as UTF-8 code points. + */ + UTF_8(21, "utf8"), + + /** + * Zero to MAX_ROW_SIZE untyped bytes. + */ + BINARY(22, "binary"), + + /** + * An object property. + */ + OBJECT(23, "object"), + + /** + * An array property, either typed or untyped. + */ + ARRAY(24, "array"), + + /** + * A set property, either typed or untyped. + */ + SET(25, "set"), + + /** + * A map property, either typed or untyped. + */ + MAP(26, "map"), + + /** + * A tuple property. Tuples are typed, finite, ordered, sets. + */ + TUPLE(27, "tuple"), + + /** + * A tagged property. + *

+ * Tagged properties pair one or more typed values with an API-specific uint8 type code. + */ + TAGGED(28, "tagged"), + + /** + * A row with schema. + *

+ * May define either a top-level table schema or a UDT (nested row). + */ + SCHEMA(29, "schema"), + + /** + * An untyped sparse field. + *

+ * May only be used to define the type of a field within a nested scope. + */ + ANY(30, "any"); + + public static final int BYTES = Integer.BYTES; + + private static Supplier> mappings = Suppliers.memoize(() -> { + TypeKind[] constants = TypeKind.class.getEnumConstants(); + int[] values = new int[constants.length]; + Arrays.setAll(values, index -> constants[index].value); + return new Int2ReferenceOpenHashMap<>(values, constants); + }); + + private final String friendlyName; + private final int value; + + TypeKind(final int value, final String friendlyName) { + this.friendlyName = friendlyName; + this.value = value; + } + + /** + * Returns the friendly name of this enum constant. + * + * @return the friendly name of this enum constant. + * @see #toString() + */ + public String friendlyName() { + return this.friendlyName; + } + + public static TypeKind from(int value) { + return mappings.get().get(value); + } + + /** + * Returns the friendly name of this enum constant. + * + * @return the friendly name of this enum constant. + * @see #friendlyName() + */ + @Override + public String toString() { + return this.friendlyName; + } + + public int value() { + return this.value; + } +} diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/UdtPropertyType.java b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/UdtPropertyType.java similarity index 97% rename from java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/UdtPropertyType.java rename to experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/UdtPropertyType.java index 34e9bfc..a1f64af 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/UdtPropertyType.java +++ b/experimental/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/schemas/UdtPropertyType.java @@ -1,76 +1,76 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.data.cosmos.serialization.hybridrow.schemas; - -import com.azure.data.cosmos.serialization.hybridrow.SchemaId; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * UDT properties represent nested structures with an independent schema. - *

- * UDT properties include a nested row within an existing row as a column. The schema of the nested row may be evolved - * independently of the outer row. Changes to the independent schema affect all outer schemas where the UDT is used. - */ -public class UdtPropertyType extends ScopePropertyType { - - @JsonProperty(required = true) - private String name; - - @JsonProperty(required = true) - private SchemaId id; - - /** - * The name of the UDT schema defining the structure of a nested row. - *

- * The UDT schema MUST be defined within the same {@link Namespace} as the schema that references it. - * - * @return the identifier of the UDT schema defining the structure of a nested row. - */ - public final String name() { - return this.name; - } - - /** - * Sets the name of the UDT schema defining the structure of a nested row. - *

- * The UDT schema MUST be defined within the same {@link Namespace} as the schema that references it. - * - * @param value the name of the UDT schema defining the structure of a nested row. - * @return a reference to this {@link UdtPropertyType}. - */ - public final UdtPropertyType name(String value) { - this.name = value; - return this; - } - - /** - * The unique identifier of the UDT schema defining the structure of a nested row. - *

- * Optional uniqueifier if multiple versions of {@link #name} appears within the {@link Namespace}. - *

- * If multiple versions of a UDT are defined within a {@link Namespace} the globally unique identifier of the - * specific version referenced MUST be provided. - * - * @return the unique identifier of the UDT schema defining the structure of a nested row or {@code null}. - */ - public final SchemaId schemaId() { - return this.id; - } - - /** - * Sets the unique identifier of the UDT schema defining the structure of a nested row. - *

- * Optional uniqueifier if multiple versions of {@link #name} appears within the {@link Namespace}. - *

- * If multiple versions of a UDT are defined within a {@link Namespace} the globally unique identifier of the - * specific version referenced MUST be provided. - * - * @param value the unique identifier of the UDT schema defining the structure of a nested row or {@code null}. - * @return a reference to this {@link UdtPropertyType}. - */ - public final UdtPropertyType schemaId(SchemaId value) { - this.id = value; - return this; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.cosmos.serialization.hybridrow.schemas; + +import com.azure.data.cosmos.serialization.hybridrow.SchemaId; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * UDT properties represent nested structures with an independent schema. + *

+ * UDT properties include a nested row within an existing row as a column. The schema of the nested row may be evolved + * independently of the outer row. Changes to the independent schema affect all outer schemas where the UDT is used. + */ +public class UdtPropertyType extends ScopePropertyType { + + @JsonProperty(required = true) + private String name; + + @JsonProperty(required = true) + private SchemaId id; + + /** + * The name of the UDT schema defining the structure of a nested row. + *

+ * The UDT schema MUST be defined within the same {@link Namespace} as the schema that references it. + * + * @return the identifier of the UDT schema defining the structure of a nested row. + */ + public final String name() { + return this.name; + } + + /** + * Sets the name of the UDT schema defining the structure of a nested row. + *

+ * The UDT schema MUST be defined within the same {@link Namespace} as the schema that references it. + * + * @param value the name of the UDT schema defining the structure of a nested row. + * @return a reference to this {@link UdtPropertyType}. + */ + public final UdtPropertyType name(String value) { + this.name = value; + return this; + } + + /** + * The unique identifier of the UDT schema defining the structure of a nested row. + *

+ * Optional uniqueifier if multiple versions of {@link #name} appears within the {@link Namespace}. + *

+ * If multiple versions of a UDT are defined within a {@link Namespace} the globally unique identifier of the + * specific version referenced MUST be provided. + * + * @return the unique identifier of the UDT schema defining the structure of a nested row or {@code null}. + */ + public final SchemaId schemaId() { + return this.id; + } + + /** + * Sets the unique identifier of the UDT schema defining the structure of a nested row. + *

+ * Optional uniqueifier if multiple versions of {@link #name} appears within the {@link Namespace}. + *

+ * If multiple versions of a UDT are defined within a {@link Namespace} the globally unique identifier of the + * specific version referenced MUST be provided. + * + * @param value the unique identifier of the UDT schema defining the structure of a nested row or {@code null}. + * @return a reference to this {@link UdtPropertyType}. + */ + public final UdtPropertyType schemaId(SchemaId value) { + this.id = value; + return this; + } +} diff --git a/java/src/test/java/com/azure/data/cosmos/core/Utf8StringTest.java b/experimental/java/src/test/java/com/azure/data/cosmos/core/Utf8StringTest.java similarity index 100% rename from java/src/test/java/com/azure/data/cosmos/core/Utf8StringTest.java rename to experimental/java/src/test/java/com/azure/data/cosmos/core/Utf8StringTest.java diff --git a/java/src/test/java/com/azure/data/cosmos/core/UtfAnyStringTest.java b/experimental/java/src/test/java/com/azure/data/cosmos/core/UtfAnyStringTest.java similarity index 100% rename from java/src/test/java/com/azure/data/cosmos/core/UtfAnyStringTest.java rename to experimental/java/src/test/java/com/azure/data/cosmos/core/UtfAnyStringTest.java diff --git a/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodecTest.java b/experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodecTest.java similarity index 100% rename from java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodecTest.java rename to experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DateTimeCodecTest.java diff --git a/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodecTest.java b/experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodecTest.java similarity index 100% rename from java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodecTest.java rename to experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/DecimalCodecTest.java diff --git a/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodecTest.java b/experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodecTest.java similarity index 100% rename from java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodecTest.java rename to experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/codecs/GuidCodecTest.java diff --git a/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderTest.java b/experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderTest.java similarity index 100% rename from java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderTest.java rename to experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderTest.java diff --git a/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchemaTest.java b/experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchemaTest.java similarity index 100% rename from java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchemaTest.java rename to experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/layouts/SystemSchemaTest.java diff --git a/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/schemas/NamespaceTest.java b/experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/schemas/NamespaceTest.java similarity index 100% rename from java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/schemas/NamespaceTest.java rename to experimental/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/schemas/NamespaceTest.java diff --git a/java/src/test/resources/log4j.properties b/experimental/java/src/test/resources/log4j.properties similarity index 100% rename from java/src/test/resources/log4j.properties rename to experimental/java/src/test/resources/log4j.properties diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/BatchApiSchema.json b/experimental/java/test-data/BatchApiSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/BatchApiSchema.json rename to experimental/java/test-data/BatchApiSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/CoverageSchema.json b/experimental/java/test-data/CoverageSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/CoverageSchema.json rename to experimental/java/test-data/CoverageSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/CrossVersioningExpected.json b/experimental/java/test-data/CrossVersioningExpected.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/CrossVersioningExpected.json rename to experimental/java/test-data/CrossVersioningExpected.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/CrossVersioningSchema.json b/experimental/java/test-data/CrossVersioningSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/CrossVersioningSchema.json rename to experimental/java/test-data/CrossVersioningSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/CustomerSchema.json b/experimental/java/test-data/CustomerSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/CustomerSchema.json rename to experimental/java/test-data/CustomerSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/MovieSchema.json b/experimental/java/test-data/MovieSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/MovieSchema.json rename to experimental/java/test-data/MovieSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/NullableSchema.json b/experimental/java/test-data/NullableSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/NullableSchema.json rename to experimental/java/test-data/NullableSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/PerfCounterSchema.json b/experimental/java/test-data/PerfCounterSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/PerfCounterSchema.json rename to experimental/java/test-data/PerfCounterSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/ReaderSchema.json b/experimental/java/test-data/ReaderSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/ReaderSchema.json rename to experimental/java/test-data/ReaderSchema.json diff --git a/test-data/RootSegment.bin b/experimental/java/test-data/RootSegment.bin similarity index 100% rename from test-data/RootSegment.bin rename to experimental/java/test-data/RootSegment.bin diff --git a/test-data/RootSegment.hybridrow b/experimental/java/test-data/RootSegment.hybridrow similarity index 100% rename from test-data/RootSegment.hybridrow rename to experimental/java/test-data/RootSegment.hybridrow diff --git a/test-data/RootSegment.json b/experimental/java/test-data/RootSegment.json similarity index 100% rename from test-data/RootSegment.json rename to experimental/java/test-data/RootSegment.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/SchemaHashCoverageSchema.json b/experimental/java/test-data/SchemaHashCoverageSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/SchemaHashCoverageSchema.json rename to experimental/java/test-data/SchemaHashCoverageSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/TagSchema.json b/experimental/java/test-data/TagSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/TagSchema.json rename to experimental/java/test-data/TagSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/TaggedApiSchema.json b/experimental/java/test-data/TaggedApiSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/TaggedApiSchema.json rename to experimental/java/test-data/TaggedApiSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TestData/TodoSchema.json b/experimental/java/test-data/TodoSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/TestData/TodoSchema.json rename to experimental/java/test-data/TodoSchema.json diff --git a/schemas/SystemSchema.json b/schemas/SystemSchema.json deleted file mode 100644 index bae75f3..0000000 --- a/schemas/SystemSchema.json +++ /dev/null @@ -1,54 +0,0 @@ -// HybridRow RecordIO Schema -{ - "name": "Microsoft.Azure.Cosmos.HybridRow.RecordIO", - "version": "v1", - "schemas": [ - { - "name": "EmptySchema", - "id": 2147473650, - "type": "schema", - "properties": [] - }, - { - "name": "Segment", - "id": 2147473648, - "type": "schema", - "properties": [ - { - "path": "length", - "type": { "type": "int32", "storage": "fixed" }, - "comment": - "(Required) length (in bytes) of this RecordIO segment header itself. Does NOT include the length of the records that follow." - }, - { - "path": "comment", - "type": { "type": "utf8", "storage": "sparse" }, - "comment": "A comment describing the data in this RecordIO segment." - }, - { - // TODO: this should be converted to a HybridRow UDT instead. - "path": "sdl", - "type": { "type": "utf8", "storage": "sparse" }, - "comment": "A HybridRow Schema in SDL (json-format)." - } - ] - }, - { - "name": "Record", - "id": 2147473649, - "type": "schema", - "properties": [ - { - "path": "length", - "type": { "type": "int32", "storage": "fixed", "nullable": false }, - "comment": "(Required) length (in bytes) of the HybridRow value that follows this record header." - }, - { - "path": "crc32", - "type": { "type": "uint32", "storage": "fixed", "nullable": false }, - "comment": "(Optional) CRC-32 as described in ISO 3309." - } - ] - } - ] -} diff --git a/src/CodeAnalysis.ruleset b/src/CodeAnalysis.ruleset new file mode 100644 index 0000000..11994ed --- /dev/null +++ b/src/CodeAnalysis.ruleset @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Core/Core.Native.Tests.Unit/Base64UnitTests.cpp b/src/Core/Core.Native.Tests.Unit/Base64UnitTests.cpp new file mode 100644 index 0000000..1339bbb --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/Base64UnitTests.cpp @@ -0,0 +1,75 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include +#include +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + using Logger = Microsoft::VisualStudio::CppUnitTestFramework::Logger; + + TEST_CLASS(Base64UnitTests) + { + public: + + template + void Roundtrip() + { + std::array bytes{}; + std::array bytes2{}; + std::array(bytes.size()))> chars{}; + + std::random_device seedGenerator{}; + std::mt19937 rand{seedGenerator()}; + const std::uniform_int_distribution distribution{}; + + cdb_core::Span bb = cdb_core::MemoryMarshal::Cast(cdb_core::Span{bytes}); + for (auto& b : bb) + { + b = distribution(rand); + } + std::fill(chars.begin(), chars.end(), '\0'); + + cdb_core::Span encoded = cdb_core::Base64::Encode(bytes, cdb_core::Span{chars}); + Logger::WriteMessage(&encoded[0]); + cdb_core::Span decoded = cdb_core::Base64::Decode(cdb_core::ReadOnlySpan{encoded}, cdb_core::Span{bytes2}); + + Assert::AreEqual(static_cast(bytes.size()), decoded.Length()); + for (uint32_t i = 0; i < decoded.Length(); i++) + { + Assert::AreEqual(bytes[i], decoded[i]); + } + } + + ///

+ /// Tests whether a byte string properly round-trips as Base64 text. + /// + TEST_METHOD(RoundtripA) + { + Roundtrip(); + } + + /// + /// Tests whether a byte string properly round-trips as Base64 wide text. + /// + TEST_METHOD(RoundtripW) + { + Roundtrip(); + } + }; +} + +// Additional string function for test diagnostics. +namespace Microsoft::VisualStudio::CppUnitTestFramework +{ + template<> + inline std::wstring ToString(const std::byte& q) + { + return cdb_core::make_string(L"{%u}", q); + } +} diff --git a/src/Core/Core.Native.Tests.Unit/ContractUnitTests.cpp b/src/Core/Core.Native.Tests.Unit/ContractUnitTests.cpp new file mode 100644 index 0000000..5b0b799 --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/ContractUnitTests.cpp @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + TEST_CLASS(ContractUnitTests) + { + public: + + /// + /// Tests whether the string formatting in Contract works correctly for both + /// empty and non-empty strings and views. + /// + TEST_METHOD(ContractFormatting) + { + std::wstring error = cdb_core::Contract::MakeError("Assert", ""); + Assert::AreEqual(0ull, error.find(L"Assert"s)); + error = cdb_core::Contract::MakeError("Assert", std::string_view()); + Assert::AreEqual(0ull, error.find(L"Assert"s)); + error = cdb_core::Contract::MakeError("Assert", "Some error message."); + Assert::AreEqual(0ull, error.find(L"Assert"s)); + Assert::AreNotEqual(std::string::npos, error.find(L"Some error message."s)); + + // ReSharper disable once StringLiteralTypo + error = cdb_core::Contract::MakeError("Assert", "Some error message \0 with a null ASDFGHJKL."); + Assert::AreEqual(0ull, error.find(L"Assert"s)); + Assert::AreNotEqual(std::string::npos, error.find(L"Some error message "s)); + Assert::AreEqual(std::string::npos, error.find(L"ASDFGHJKL"s)); + + // ReSharper disable once StringLiteralTypo + const std::string_view truncated = "some very long message ASDFGHJKL"; + error = cdb_core::Contract::MakeError("Assert", truncated.substr(0, 4)); + Assert::AreEqual(0ull, error.find(L"Assert"s)); + Assert::AreNotEqual(std::string::npos, error.find(L"some"s)); + Assert::AreEqual(std::string::npos, error.find(L"ASDFGHJKL"s)); + } + }; +} diff --git a/src/Core/Core.Native.Tests.Unit/Crc32UnitTests.cpp b/src/Core/Core.Native.Tests.Unit/Crc32UnitTests.cpp new file mode 100644 index 0000000..83d24f0 --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/Crc32UnitTests.cpp @@ -0,0 +1,162 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + using Logger = Microsoft::VisualStudio::CppUnitTestFramework::Logger; + + TEST_CLASS(Crc32UnitTests) + { + constexpr static std::array Sample1 + { + {byte{0x45}, byte{0xB1}, byte{0xD6}, byte{0xC7}, byte{0x81}, byte{0xE1}, byte{0x3F}} + }; + constexpr static std::array Sample2 + { + { + byte{0xE5}, + byte{0x78}, + byte{0xBA}, + byte{0xD5}, + byte{0x00}, + byte{0xA2}, + byte{0x98}, + byte{0xFE}, + byte{0xF1}, + byte{0xEF}, + byte{0x2A}, + byte{0x90}, + byte{0x6B}, + byte{0xC9}, + byte{0x85}, + byte{0x22}, + byte{0x00}, + byte{0xA5}, + byte{0xEC}, + byte{0x20}, + byte{0x23}, + byte{0xF6}, + byte{0xB2} + } + }; + constexpr static std::array Sample3 + { + { + byte{0xC3}, + byte{0x91}, + byte{0x0B}, + byte{0x50}, + byte{0xAF}, + byte{0x59}, + byte{0x5B}, + byte{0x30}, + byte{0x24}, + byte{0xDA}, + byte{0x22}, + byte{0x3C}, + byte{0x30}, + byte{0xBA}, + byte{0xDB}, + byte{0x1C}, + byte{0x18}, + byte{0x6F}, + byte{0xBB}, + byte{0xE6}, + byte{0x0B}, + byte{0x70}, + byte{0x0E} + } + }; + constexpr static std::array Sample4{}; + constexpr static std::array Sample5 + { + byte{0xB4}, + byte{0xC7}, + byte{0xDB}, + byte{0xF4}, + byte{0x1F}, + byte{0x18}, + byte{0xEE}, + byte{0xC5}, + byte{0x67}, + byte{0x12}, + byte{0x6E}, + byte{0x96}, + byte{0x47}, + byte{0x4E}, + byte{0x98}, + byte{0x94}, + byte{0xFA}, + byte{0x6B}, + byte{0x90}, + byte{0xA6}, + byte{0x48}, + byte{0xF2} + }; + + constexpr static std::array, 5> s_samples + { + { + cdb_core::ReadOnlySpan{Sample1}, + cdb_core::ReadOnlySpan{Sample2}, + cdb_core::ReadOnlySpan{Sample3}, + cdb_core::ReadOnlySpan{Sample4}, + cdb_core::ReadOnlySpan{Sample5}, + } + }; + + constexpr static uint32_t Expected[] + { + 2786232081u, + 1744821187u, + 2853437495u, + 0u, + 2029626740u, + }; + + template + static int64_t MeasureLoop(std::array, N> samples) + { + const int outerLoopCount = 10000; + cdb_core::Stopwatch watch{}; + + watch.Start(); + for (int j = 0; j < outerLoopCount; j++) + { + for (auto sample : samples) + { + [[maybe_unused]] int32_t crc = cdb_core::Crc32::Update(0, sample); + } + } + + watch.Stop(); + return watch.ElapsedRaw(); + } + + BEGIN_TEST_METHOD_ATTRIBUTE(Crc32Check) + TEST_OWNER(L"jthunter") + END_TEST_METHOD_ATTRIBUTE() + + TEST_METHOD(Crc32Check) + { + // Warm up the loop and verify correctness. + for (size_t i = 0; i < s_samples.size(); i++) + { + cdb_core::ReadOnlySpan sample = s_samples[i]; + uint32_t c1 = cdb_core::Crc32::Update(0, sample); + Assert::AreEqual(Expected[i], c1); + } + + // Measure performance. + int64_t ticks = MeasureLoop(s_samples); + Logger::WriteMessage(cdb_core::make_string("Crc32: %lld", ticks).c_str()); + } + }; +} diff --git a/src/Core/Core.Native.Tests.Unit/DeepCompareUnitTests.cpp b/src/Core/Core.Native.Tests.Unit/DeepCompareUnitTests.cpp new file mode 100644 index 0000000..f92c6fc --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/DeepCompareUnitTests.cpp @@ -0,0 +1,62 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + TEST_CLASS(DeepCompareUnitTests) + { + public: + + /// + /// Tests whether deep compare works correctly on object trees. + /// + TEST_METHOD(ObjectTrees) + { + std::vector v1{0, 1, 2, 3}; + Assert::IsTrue(cdb_core::DeepCompare(v1, v1)); + Assert::IsTrue(cdb_core::DeepCompare(v1, std::vector{0, 1, 2, 3})); + Assert::IsFalse(cdb_core::DeepCompare(v1, std::vector{0, 1, 2, 3, 4})); + Assert::IsFalse(cdb_core::DeepCompare(v1, std::vector{0, 1, 2, 4})); + Assert::IsFalse(cdb_core::DeepCompare(v1, std::vector{9, 1, 2, 3})); + + std::unique_ptr u1 = std::make_unique(42); + Assert::IsTrue(cdb_core::DeepCompare(std::unique_ptr{}, std::unique_ptr{})); + Assert::IsTrue(cdb_core::DeepCompare(u1, u1)); + Assert::IsTrue(cdb_core::DeepCompare(u1, std::make_unique(42))); + Assert::IsFalse(cdb_core::DeepCompare(u1, std::make_unique(43))); + + ObjectTree o1{std::move(v1), std::move(u1)}; + Assert::IsTrue(cdb_core::DeepCompare(o1, o1)); + Assert::IsTrue(cdb_core::DeepCompare(o1, ObjectTree{{0, 1, 2, 3}, std::make_unique(42)})); + Assert::IsFalse(cdb_core::DeepCompare(o1, ObjectTree{{0, 1, 2, 4}, std::make_unique(42)})); + Assert::IsFalse(cdb_core::DeepCompare(o1, ObjectTree{{0, 1, 2, 3}, std::make_unique(43)})); + } + + struct ObjectTree final + { + std::vector V; + std::unique_ptr U; + }; + }; +} + +namespace cdb_core +{ + template<> + struct DeepComparer + { + using value_type = cdb_core_test::DeepCompareUnitTests::ObjectTree; + + bool operator()(const value_type& x, const value_type& y) const noexcept + { + return cdb_core::DeepCompare(x.V, y.V) && cdb_core::DeepCompare(x.U, y.U); + } + }; +} diff --git a/src/Core/Core.Native.Tests.Unit/HashCodeUnitTests.cpp b/src/Core/Core.Native.Tests.Unit/HashCodeUnitTests.cpp new file mode 100644 index 0000000..da055b2 --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/HashCodeUnitTests.cpp @@ -0,0 +1,209 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + struct ConstHashCodeType {}; + + struct ConstComparer + { + constexpr static size_t ConstantValue = 1234; + + std::size_t operator()(cdb_core_test::ConstHashCodeType const&) const noexcept + { + return ConstantValue; + } + }; + + TEST_CLASS(HashCodeUnitTests) + { + public: + + TEST_METHOD(AddHashCode) + { + cdb_core::HashCode hc1{}; + hc1.Add("Hello"sv); + + cdb_core::HashCode hc2{}; + hc2.AddHash(std::hash{}.operator()("Hello"sv)); + + Assert::AreEqual(hc1.ToHashCode(), hc2.ToHashCode()); + } + + TEST_METHOD(AddGeneric) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(ConstHashCodeType{}); + + cdb_core::HashCode expected{}; + expected.Add(1); + expected.AddHash(ConstComparer::ConstantValue); + + Assert::AreEqual(expected.ToHashCode(), hc.ToHashCode()); + } + + TEST_METHOD(AddNull) + { + cdb_core::HashCode hc{}; + hc.Add(static_cast(nullptr)); + + cdb_core::HashCode expected{}; + expected.AddHash(std::hash{}.operator()(nullptr)); + + Assert::AreEqual(expected.ToHashCode(), hc.ToHashCode()); + } + + TEST_METHOD(AddGenericEqualityComparer) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(ConstHashCodeType{}); + + cdb_core::HashCode expected{}; + expected.Add(1); + expected.AddHash(ConstComparer::ConstantValue); + + Assert::AreEqual(expected.ToHashCode(), hc.ToHashCode()); + } + + TEST_METHOD(Combine) + { + std::vector hcs = + { + cdb_core::HashCode::Combine(1), + cdb_core::HashCode::Combine(1, 2), + cdb_core::HashCode::Combine(1, 2, 3), + cdb_core::HashCode::Combine(1, 2, 3, 4), + cdb_core::HashCode::Combine(1, 2, 3, 4, 5), + cdb_core::HashCode::Combine(1, 2, 3, 4, 5, 6), + cdb_core::HashCode::Combine(1, 2, 3, 4, 5, 6, 7), + cdb_core::HashCode::Combine(1, 2, 3, 4, 5, 6, 7, 8), + + cdb_core::HashCode::Combine(2), + cdb_core::HashCode::Combine(2, 3), + cdb_core::HashCode::Combine(2, 3, 4), + cdb_core::HashCode::Combine(2, 3, 4, 5), + cdb_core::HashCode::Combine(2, 3, 4, 5, 6), + cdb_core::HashCode::Combine(2, 3, 4, 5, 6, 7), + cdb_core::HashCode::Combine(2, 3, 4, 5, 6, 7, 8), + cdb_core::HashCode::Combine(2, 3, 4, 5, 6, 7, 8, 9), + }; + + for (size_t i = 0; i < hcs.size(); i++) + { + for (size_t j = 0; j < hcs.size(); j++) + { + if (i == j) + { + continue; + } + Assert::AreNotEqual(hcs[i], hcs[j]); + } + } + } + + TEST_METHOD(CombineAdd1) + { + cdb_core::HashCode hc{}; + hc.Add(1); + Assert::AreEqual(hc.ToHashCode(), cdb_core::HashCode::Combine(1)); + } + + TEST_METHOD(CombineAdd2) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(2); + Assert::AreEqual(hc.ToHashCode(), cdb_core::HashCode::Combine(1, 2)); + } + + TEST_METHOD(CombineAdd3) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(2); + hc.Add(3); + Assert::AreEqual(hc.ToHashCode(), cdb_core::HashCode::Combine(1, 2, 3)); + } + + TEST_METHOD(CombineAdd4) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + Assert::AreEqual(hc.ToHashCode(), cdb_core::HashCode::Combine(1, 2, 3, 4)); + } + + TEST_METHOD(CombineAdd5) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + Assert::AreEqual(hc.ToHashCode(), cdb_core::HashCode::Combine(1, 2, 3, 4, 5)); + } + + TEST_METHOD(CombineAdd6) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + Assert::AreEqual(hc.ToHashCode(), cdb_core::HashCode::Combine(1, 2, 3, 4, 5, 6)); + } + + TEST_METHOD(CombineAdd7) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + hc.Add(7); + Assert::AreEqual(hc.ToHashCode(), cdb_core::HashCode::Combine(1, 2, 3, 4, 5, 6, 7)); + } + + TEST_METHOD(CombineAdd8) + { + cdb_core::HashCode hc{}; + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + hc.Add(7); + hc.Add(8); + Assert::AreEqual(hc.ToHashCode(), cdb_core::HashCode::Combine(1, 2, 3, 4, 5, 6, 7, 8)); + } + }; +} + +namespace std +{ + template<> + struct hash + { + std::size_t operator()(cdb_core_test::ConstHashCodeType const&) const noexcept + { + return cdb_core_test::ConstComparer::ConstantValue; + } + }; +} diff --git a/src/Core/Core.Native.Tests.Unit/MemoryUnitTests.cpp b/src/Core/Core.Native.Tests.Unit/MemoryUnitTests.cpp new file mode 100644 index 0000000..388d01c --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/MemoryUnitTests.cpp @@ -0,0 +1,230 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + using Logger = Microsoft::VisualStudio::CppUnitTestFramework::Logger; + + template + void TestRange(T m, int start, int length) + { + int i = 0; + for (auto b : m.AsSpan()) + { + Assert::AreEqual(static_cast(i + start), b); + i++; + } + Assert::AreEqual(length, i); + Assert::AreEqual(static_cast(length), m.Length()); + Assert::AreEqual(length == 0, m.IsEmpty()); + } + + TEST_CLASS(MemoryUnitTests) + { + public: + + TEST_METHOD(SliceTest) + { + Logger::WriteMessage("Memory:"); + cdb_core::Memory m{new byte[10], 10}; + for (int i = 0; i < 10; i++) + { + m.AsSpan()[i] = static_cast(i); + } + TestRange(m, 0, 10); + + { + const cdb_core::ReadOnlyMemory m1 = static_cast&>(m).Slice(5); + TestRange(m1, 5, 5); + } + + { + const auto m2 = m.Slice(5, 2); + TestRange(m2, 5, 2); + } + + { + const auto m3 = m.Slice(10); + TestRange(m3, 0, 0); + } + + { + const auto m4 = m.Slice(0, 0); + TestRange(m4, 10, 0); + } + + { + cdb_core::Memory m5{new byte[5], 5}; + m.Slice(5, 5).AsSpan().CopyTo(m5.AsSpan()); + TestRange(m5, 5, 5); + } + } + + TEST_METHOD(CopyTest) + { + Logger::WriteMessage("Memory:"); + cdb_core::Memory m{new byte[10], 10}; + for (int i = 0; i < 10; i++) + { + m.AsSpan()[i] = static_cast(i); + } + TestRange(m, 0, 10); + + { + cdb_core::Memory m2{new byte[2], 2}; + m.Slice(5, 2).AsSpan().CopyTo(m2.AsSpan()); + TestRange(m2, 5, 2); + } + + { + cdb_core::Memory m3{new byte[0], 0}; + m.Slice(10).AsSpan().CopyTo(m3.AsSpan()); + TestRange(m3, 0, 0); + } + + { + cdb_core::Memory m3{nullptr, 0}; + m.Slice(10).AsSpan().CopyTo(m3.AsSpan()); + TestRange(m3, 0, 0); + } + + { + cdb_core::Memory m5{new byte[5], 5}; + m.Slice(5, 5).AsSpan().CopyTo(m5.AsSpan()); + TestRange(m5, 5, 5); + } + + { + cdb_core::Memory m6{m.AsSpan()}; + TestRange(m6, 0, 10); + } + + { + std::vector v{}; + v.reserve(m.Length()); + for (auto b : m.AsSpan()) { v.emplace_back(b); } + cdb_core::Memory m7{v}; + TestRange(m7, 0, 10); + } + } + + TEST_METHOD(CastTest) + { + const int num = 3; + cdb_core::Memory m{new int32_t[num], static_cast(num)}; + for (int i = 0; i < num; i++) + { + m.AsSpan()[i] = i + 1; + } + + cdb_core::Span mb = cdb_core::MemoryMarshal::Cast(m.AsSpan()); + byte expectedBytes[] = { + static_cast(1), + static_cast(0), + static_cast(0), + static_cast(0), + static_cast(2), + static_cast(0), + static_cast(0), + static_cast(0), + static_cast(3), + static_cast(0), + static_cast(0), + static_cast(0), + }; + for (size_t i = 0; i < std::size(expectedBytes); i++) + { + Assert::AreEqual(expectedBytes[i], mb[static_cast(i)], cdb_core::make_string(L"i = %llu", i).c_str()); + } + Assert::AreEqual(std::size(expectedBytes), static_cast(mb.Length())); + + cdb_core::Span m64 = cdb_core::MemoryMarshal::Cast(m.AsSpan()); + int64_t expected64[] = {static_cast(0x200000001)}; + for (size_t i = 0; i < std::size(expected64); i++) + { + Assert::AreEqual(expected64[i], m64[static_cast(i)], cdb_core::make_string(L"i = %llu", i).c_str()); + } + Assert::AreEqual(std::size(expected64), static_cast(m64.Length())); + } + + template + void MemoryEqualityTest(T a, T b, T c, T d) + { + // ReSharper disable once CppIdenticalOperandsInBinaryExpression + Assert::IsTrue(a == a); + Assert::AreNotEqual(a, b); + Assert::IsFalse(a == b); + Assert::IsTrue(a != b); + Assert::AreEqual(a, c); + Assert::IsTrue(a == c); + Assert::IsFalse(a != c); + Assert::AreNotEqual(a, d); + Assert::IsFalse(a == d); + Assert::IsTrue(a != d); + } + + TEST_METHOD(EqualityTest) + { + cdb_core::Memory a = cdb_core::Memory{5}; + cdb_core::Memory b = cdb_core::Memory{5}; + cdb_core::Memory c = a.Slice(0); + cdb_core::Memory d = a.Slice(1); + + MemoryEqualityTest(a, b, c, d); + MemoryEqualityTest(a.AsSpan(), b.AsSpan(), c.AsSpan(), d.AsSpan()); + MemoryEqualityTest( + cdb_core::ReadOnlySpan(a.AsSpan()), + cdb_core::ReadOnlySpan(b.AsSpan()), + cdb_core::ReadOnlySpan(c.AsSpan()), + cdb_core::ReadOnlySpan(d.AsSpan())); + } + }; +} + +namespace Microsoft::VisualStudio::CppUnitTestFramework +{ + template<> inline std::wstring ToString(const std::byte& q) + { + RETURN_WIDE_STRING(static_cast(q)); + } + + template<> inline std::wstring ToString(const std::byte* q) + { + RETURN_WIDE_STRING(q); + } +} + +namespace Microsoft::VisualStudio::CppUnitTestFramework +{ + template<> + std::wstring ToString>( + const cdb_core::Memory& q) + { + return cdb_core::make_string(L"{p:%p l:%u}", &q.AsSpan()[0], q.Length()); + } +} + +namespace Microsoft::VisualStudio::CppUnitTestFramework +{ + template<> + std::wstring ToString>( + const cdb_core::Span& q) + { + return cdb_core::make_string(L"{p:%p l:%u}", &q[0], q.Length()); + } +} + +namespace Microsoft::VisualStudio::CppUnitTestFramework +{ + template<> + std::wstring ToString>( + const cdb_core::ReadOnlySpan& q) + { + return cdb_core::make_string(L"{p:%p l:%u}", &q[0], q.Length()); + } +} diff --git a/src/Core/Core.Native.Tests.Unit/Microsoft.Azure.Cosmos.Core.Native.Tests.Unit.vcxproj b/src/Core/Core.Native.Tests.Unit/Microsoft.Azure.Cosmos.Core.Native.Tests.Unit.vcxproj new file mode 100644 index 0000000..8dc05da --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/Microsoft.Azure.Cosmos.Core.Native.Tests.Unit.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {BE99633B-5D7D-41DA-8550-035E6689B243} + Win32Proj + cdb_core_tests + 10.0 + NativeUnitTestProject + DynamicLibrary + + + + true + false + + + false + false + + + + true + + + false + + + + Use + Disabled + EnableFastChecks + MultiThreadedDebugDLL + Level3 + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + _DEBUG;%(PreprocessorDefinitions) + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + Use + Level3 + true + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + pch.h + MultiThreadedDLL + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + Create + + + + + + + + {eaed7d41-3de6-4c41-a0e4-40d53ea3daba} + + + + \ No newline at end of file diff --git a/src/Core/Core.Native.Tests.Unit/StringsUnitTests.cpp b/src/Core/Core.Native.Tests.Unit/StringsUnitTests.cpp new file mode 100644 index 0000000..25aef9a --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/StringsUnitTests.cpp @@ -0,0 +1,111 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + using Logger = Microsoft::VisualStudio::CppUnitTestFramework::Logger; + + TEST_CLASS(StringsUnitTests) + { + public: + /// + /// Verifies the code samples used in the Doc Comments actually compile. + /// + TEST_METHOD(DocCommentsExamples) + { + { + std::string u8 = cdb_core::make_string("foo: %s", "some value"); + std::wstring u16 = cdb_core::string_join(L"foo: %s", L"some wide-string value"); + tla::string s = cdb_core::string_join("foo: %s", "some value"); + } + + { + Logger::WriteMessage(cdb_core::make_string("foo: %s", "some value").c_str()); + Logger::WriteMessage(cdb_core::make_string(L"foo: %s", L"some wide-string value").c_str()); + } + + { + std::string u8 = cdb_core::string_join("a", "b", "c"); + std::wstring u16 = cdb_core::string_join(L"a", L"b", L"c"); + tla::string s = cdb_core::string_join("a", "b", "c"); + } + } + + /// + /// Verifies that make_string inserts the arguments in the proper order. + /// + TEST_METHOD(MakeStringOrderTest) + { + MakeStringOrder("%s %s %s", "a", "b", "c", "a b c"); + MakeStringOrder(L"%s %s %s", L"a", L"b", L"c", L"a b c"); + MakeStringOrder("%s %s %s", "a", "b", "c", "a b c"); + MakeStringOrder(L"%s %s %s", L"a", L"b", L"c", L"a b c"); + } + + /// + /// Verifies that make_string properly supports string precision format specifiers. + /// + TEST_METHOD(MakeStringPrecisionTest) + { + MakeStringPrecision("a %.*s c", "b"sv, "a b c"); + MakeStringPrecision(L"a %.*s c", L"b"sv, L"a b c"); + MakeStringPrecision("a %.*s c", "b"sv, "a b c"); + MakeStringPrecision(L"a %.*s c", L"b"sv, L"a b c"); + } + + /// + /// Verifies that string join return type inference works across various return types. + /// + TEST_METHOD(StringJoinTest) + { + StringJoin("a", "b", "c", "abc"); + StringJoin(L"a", L"b", L"c", L"abc"); + StringJoin("a", "b", "c", "abc"); + StringJoin(L"a", L"b", L"c", L"abc"); + } + + /// + /// Verifies that string join accepts heterogeneous argument types as long as the return + /// value has an append operator with that rvalue. + /// + TEST_METHOD(HeterogeneousStringJoinTest) + { + const std::string u8 = cdb_core::string_join("a", "b"s, "c"sv); + Assert::AreEqual("abc", u8.c_str()); + const tla::string s = cdb_core::string_join("a", "b"s, "c"sv); + Assert::AreEqual("abc", s.c_str()); + } + + private: + template + void StringJoin(const TElem* a, const TElem* b, const TElem* c, const TElem* expected) + { + Logger::WriteMessage(cdb_core::make_string("%s\n", typeid(TString).name()).c_str()); + TString abc = cdb_core::string_join(a, b, c); + Assert::AreEqual(expected, abc.c_str()); + + TString abc2 = cdb_core::string_join(TString(a), TString(b), TString(c)); + Assert::AreEqual(expected, abc2.c_str()); + } + + template + void MakeStringOrder(const TElem* format, const TElem* a, const TElem* b, const TElem* c, const TElem* expected) + { + TString s = cdb_core::make_string(format, a, b, c); + Assert::AreEqual(expected, s.data()); + } + + template + void MakeStringPrecision(const TElem* format, std::basic_string_view arg, const TElem* expected) + { + TString s = cdb_core::make_string(format, arg.size(), arg.data()); + Assert::AreEqual(expected, s.data()); + } + }; +} diff --git a/src/Core/Core.Native.Tests.Unit/TimeSpanTests.cpp b/src/Core/Core.Native.Tests.Unit/TimeSpanTests.cpp new file mode 100644 index 0000000..826cc05 --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/TimeSpanTests.cpp @@ -0,0 +1,41 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + TEST_CLASS(TimeSpanTests) + { + TEST_METHOD(TestTimeSpanCreationMilliseconds) + { + for (int64_t i = INT16_MIN; i < INT16_MAX; i += 97) + { + cdb_core::TimeSpan timeSpan = cdb_core::TimeSpan::FromMilliseconds(i); + Assert::AreEqual(i, timeSpan.GetTotalMilliseconds()); + Assert::AreEqual(i / 1000, timeSpan.GetTotalSeconds()); + + // 10,000 ticks in 1 ms + Assert::AreEqual(i * 10000, timeSpan.GetTotalTicks()); + } + } + + TEST_METHOD(TestTimeSpanCreationSeconds) + { + for (int64_t i = INT16_MIN; i < INT16_MAX; i += 137) + { + cdb_core::TimeSpan timeSpan = cdb_core::TimeSpan::FromSeconds(i); + Assert::AreEqual(i * 1000, timeSpan.GetTotalMilliseconds()); + Assert::AreEqual(i, timeSpan.GetTotalSeconds()); + + // 10,000,000 ticks in 1s + Assert::AreEqual(i * 10000000, timeSpan.GetTotalTicks()); + } + } + }; +} diff --git a/src/Core/Core.Native.Tests.Unit/TlaAllocatorUnitTests.cpp b/src/Core/Core.Native.Tests.Unit/TlaAllocatorUnitTests.cpp new file mode 100644 index 0000000..81f930f --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/TlaAllocatorUnitTests.cpp @@ -0,0 +1,41 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" + +namespace cdb_core_test +{ + using namespace tla::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + using Logger = Microsoft::VisualStudio::CppUnitTestFramework::Logger; + + TEST_CLASS(TlaAllocatorUnitTests) + { + public: + TEST_METHOD(StringTest) noexcept + { + const tla::string abc = cdb_core::string_join("a", "b", "c"_s); + Assert::AreEqual("abc", abc.c_str()); + + const tla::string abc2 = cdb_core::string_join(tla::string("a"), tla::string("b"), tla::string("c")); + Assert::AreEqual("abc", abc2.c_str()); + + const tla::wstring abc3 = cdb_core::string_join(tla::wstring(L"a"), tla::wstring(L"b"), tla::wstring(L"c")); + Assert::AreEqual(L"abc", abc3.c_str()); + + struct T + { + static size_t GetHashCode() noexcept { return 0; } + }; + + Logger::WriteMessage(typeid(&T::GetHashCode).name()); + Logger::WriteMessage("\n"); + Logger::WriteMessage(typeid(size_t (T::*)() const noexcept).name()); + Logger::WriteMessage("\n"); + const bool s = std::is_same::value; + Logger::WriteMessage(cdb_core::make_string("%s\n", s ? "true" : "false").c_str()); + } + }; +} diff --git a/src/Core/Core.Native.Tests.Unit/pch.cpp b/src/Core/Core.Native.Tests.Unit/pch.cpp new file mode 100644 index 0000000..690da7f --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/pch.cpp @@ -0,0 +1,5 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" diff --git a/src/Core/Core.Native.Tests.Unit/pch.h b/src/Core/Core.Native.Tests.Unit/pch.h new file mode 100644 index 0000000..a0c1eaa --- /dev/null +++ b/src/Core/Core.Native.Tests.Unit/pch.h @@ -0,0 +1,11 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include +#include +using std::byte; + +#include "../Core.Native/Core.Native.h" diff --git a/src/Core/Core.Native/Base64.h b/src/Core/Core.Native/Base64.h new file mode 100644 index 0000000..b1a1960 --- /dev/null +++ b/src/Core/Core.Native/Base64.h @@ -0,0 +1,557 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "Contract.h" +#include "ReadOnlySpan.h" +#include "Span.h" + +namespace cdb_core +{ + template struct ReadOnlySpan; + template struct Span; + + struct Base64 final + { + enum class Flags + { + None = 0, + NoPad = 1, + NoLinefeed = 2, + Url = 4, + }; + + // length functions + constexpr static uint32_t GetEncodeRequiredLength(uint32_t srcLen, Flags flags = Flags::None) noexcept; + template + constexpr static uint32_t GetDecodeRequiredLength(std::basic_string_view src) noexcept; + constexpr static uint32_t GetDecodeRequiredLength(uint32_t srcLen) noexcept; + + /// + /// Converts a sequence of bytes into a Base64 text string. + /// + /// The kind of text element. + /// The source bytes to convert. + /// A buffer to receive the text string. This buffer MUST be at least as large as indicated by . + /// Optional flags + /// A sub-span over containing the encoded string. + template + constexpr static Span Encode(ReadOnlySpan src, Span dest, + Flags flags = Flags::None) noexcept; + + /// + /// Converts a Base64 text string back into a sequence of bytes. + /// + /// The kind of text element. + /// The source Base64 string to convert. + /// A buffer to receive the byte sequence. This buffer MUST be at least as large as indicated by . + /// Optional flags + /// A sub-span over containing the encoded string. + template + constexpr static Span Decode(std::basic_string_view src, Span dest, + Flags flags = Flags::None) noexcept; + + /// + /// Converts a Base64 text string back into a sequence of bytes. + /// + /// The kind of text element. + /// The source Base64 string to convert. + /// A buffer to receive the byte sequence. This buffer MUST be at least as large as indicated by . + /// Optional flags + /// A sub-span over containing the encoded string. + template + constexpr static Span Decode(ReadOnlySpan src, Span dest, + Flags flags = Flags::None) noexcept; + private: + // Helper functions + // Overloaded function to get encoding characters + // i from 0 to 63 -> base64 encoding character + // i = 64 '=' + // i = 65 '\r' + // i = 66 '\n' + // i = 67 '\0' + template constexpr static TChar EncodeBase64Char(std::byte nIndex, Flags flags) noexcept; + template constexpr static int DecodeBase64Char(TChar ch, Flags flags) noexcept; + }; + + template constexpr TChar Base64::EncodeBase64Char(std::byte nIndex, Flags flags) noexcept + { + // ReSharper disable once CppStaticAssertFailure + static_assert(false, "Unknown TChar"); + return 0; + } + + template constexpr int Base64::DecodeBase64Char(const TChar ch, Flags flags) noexcept + { + // ReSharper disable once CppStaticAssertFailure + static_assert(false, "Unknown TChar"); + return 0; + } + + constexpr Base64::Flags operator|(Base64::Flags a, Base64::Flags b) noexcept + { + return static_cast(static_cast(a) | static_cast(b)); + } + + constexpr Base64::Flags operator&(Base64::Flags a, Base64::Flags b) noexcept + { + return static_cast(static_cast(a) & static_cast(b)); + } + + template + constexpr Span Base64::Encode(ReadOnlySpan src, Span dest, Flags flags) noexcept + { + Contract::Requires(dest.Length() >= GetEncodeRequiredLength(src.Length(), flags)); + + const std::byte* pSrc = &src[0]; + int srcLen = src.Length(); + TChar* pDest = &dest[0]; + + int written(0); + int len1((srcLen / 3) * 4); + int len2(len1 / 76); + int len3(19); + + for (int i = 0; i <= len2; i++) + { + if (i == len2) + { + len3 = (len1 % 76) / 4; + } + + for (int j = 0; j < len3; j++) + { + uint32_t current(0); + for (int n = 0; n < 3; n++) + { + current |= static_cast(*pSrc++); + current <<= 8; + } + for (int k = 0; k < 4; k++) + { + std::byte b = static_cast(current >> 26); + *pDest = EncodeBase64Char(b, flags); + ++pDest; + current <<= 6; + } + } + written += len3 * 4; + + if ((flags & Flags::NoLinefeed) == Flags::None) + { + // Insert \r\n here + *pDest = EncodeBase64Char(byte{65}, Flags::None); + ++pDest; + *pDest = EncodeBase64Char(byte{66}, Flags::None); + ++pDest; + written += 2; + } + } + + if ((written != 0) && ((flags & Flags::NoLinefeed) == Flags::None)) + { + pDest -= 2; + written -= 2; + } + + len2 = (srcLen % 3) ? (srcLen % 3 + 1) : 0; + if (len2) + { + uint32_t current(0); + for (int n = 0; n < 3; n++) + { + if (n < (srcLen % 3)) + { + current |= static_cast(*pSrc++); + } + current <<= 8; + } + for (int k = 0; k < len2; k++) + { + std::byte b = static_cast(current >> 26); + *pDest = EncodeBase64Char(b, flags); + ++pDest; + current <<= 6; + } + written += len2; + if ((flags & Flags::NoPad) == Flags::None) + { + len3 = len2 ? 4 - len2 : 0; + for (int j = 0; j < len3; j++) + { + // Insert '=' here + *pDest = EncodeBase64Char(byte{64}, flags); + ++pDest; + } + written += len3; + } + } + + return dest.Slice(0, written); + } + + template + constexpr Span Base64::Decode(std::basic_string_view src, Span dest, + Flags flags) noexcept + { + Contract::Requires(static_cast(src.size()) <= UINT32_MAX); + return Base64::Decode(ReadOnlySpan{src.data(), static_cast(src.size())}, dest, flags); + } + + template + constexpr Span Base64::Decode(ReadOnlySpan src, Span dest, Flags flags) noexcept + { + const TChar* pSrc = &src[0]; + std::byte* pDest = &dest[0]; + + // Walk the source buffer, each four character sequence is converted to 3 bytes. + // CRLFs and =, and any characters not in the encoding table are skipped. + const TChar* pEnd = pSrc + src.Length(); + uint32_t written = 0; + while (pSrc < pEnd && (*pSrc) != 0) + { + uint32_t current = 0; + uint32_t bits = 0; + for (int i = 0; i < 4; i++) + { + if (pSrc >= pEnd) + { + break; + } + + int ch = DecodeBase64Char(*pSrc, flags); + ++pSrc; + if (ch == -1) + { + // skip this char + // TODO: Change signature of method to support failure. + i--; + continue; + } + current <<= 6; + current |= ch; + bits += 6; + } + + Contract::Invariant(written + (bits / 8) <= dest.Length()); + + // current has the 3 bytes to write to the output buffer, left to right + current <<= 24 - bits; + for (uint32_t i = 0; i < bits / 8; i++) + { + *pDest = static_cast((current & 0x00ff0000) >> 16); + pDest++; + current <<= 8; + written++; + } + } + + return dest.Slice(0, written); + } + + constexpr uint32_t Base64::GetEncodeRequiredLength(const uint32_t srcLen, const Flags flags) noexcept + { + uint32_t nSrcLen4 = srcLen * 4; + uint32_t retval = nSrcLen4 / 3; + if ((flags & Flags::NoPad) == Flags::None) + { + retval += srcLen % 3; + } + + uint32_t numLinefeed = retval / 76 + 1; + uint32_t onLastLine = retval % 76; + + if (onLastLine && onLastLine % 4) + { + retval += 4 - (onLastLine % 4); + } + + numLinefeed *= 2; + if ((flags & Flags::NoLinefeed) == Flags::None) + { + retval += numLinefeed; + } + + return retval; + } + + template + constexpr uint32_t Base64::GetDecodeRequiredLength(std::basic_string_view src) noexcept + { + Contract::Requires(static_cast(src.size()) <= UINT32_MAX); + return static_cast(src.size()); + } + + constexpr uint32_t Base64::GetDecodeRequiredLength(const uint32_t srcLen) noexcept + { + return srcLen; + } + + constexpr char Base64CharEncodingTable[68] = { + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '+', + '/', + '=', + '\r', + '\n', + '\0' + }; + + template<> + constexpr char Base64::EncodeBase64Char(std::byte index, const Flags flags) noexcept + { + char retval = Base64CharEncodingTable[static_cast(index)]; + if ((flags & Flags::Url) != Flags::None) + { + if (retval == '+') + { + retval = '-'; + } + if (retval == '/') + { + retval = '_'; + } + } + return retval; + } + + constexpr wchar_t Base64WideEncodingTable[68] = { + L'A', + L'B', + L'C', + L'D', + L'E', + L'F', + L'G', + L'H', + L'I', + L'J', + L'K', + L'L', + L'M', + L'N', + L'O', + L'P', + L'Q', + L'R', + L'S', + L'T', + L'U', + L'V', + L'W', + L'X', + L'Y', + L'Z', + L'a', + L'b', + L'c', + L'd', + L'e', + L'f', + L'g', + L'h', + L'i', + L'j', + L'k', + L'l', + L'm', + L'n', + L'o', + L'p', + L'q', + L'r', + L's', + L't', + L'u', + L'v', + L'w', + L'x', + L'y', + L'z', + L'0', + L'1', + L'2', + L'3', + L'4', + L'5', + L'6', + L'7', + L'8', + L'9', + L'+', + L'/', + L'=', + L'\r', + L'\n', + L'\0' + }; + + template<> + constexpr wchar_t Base64::EncodeBase64Char(std::byte index, const Flags flags) noexcept + { + wchar_t retval = Base64WideEncodingTable[static_cast(index)]; + + if ((flags & Flags::Url) != Flags::None) + { + if (retval == L'+') + { + retval = L'-'; + } + if (retval == L'/') + { + retval = L'_'; + } + } + return retval; + } + + template<> + constexpr int Base64::DecodeBase64Char(const char ch, const Flags flags) noexcept + { + // returns -1 if the character is invalid + // or should be skipped + // otherwise, returns the 6-bit code for the character + // from the encoding table + if (ch >= 'A' && ch <= 'Z') + { + return ch - 'A' + 0; // 0 range starts at 'A' + } + if (ch >= 'a' && ch <= 'z') + { + return ch - 'a' + 26; // 26 range starts at 'a' + } + if (ch >= '0' && ch <= '9') + { + return ch - '0' + 52; // 52 range starts at '0' + } + + if ((flags & Flags::Url) != Flags::None) + { + if (ch == '-') + { + return 62; // base64url '+' -> '-' + } + if (ch == '_') + { + return 63; // base64url '/' -> '_' + } + } + + if (ch == '+') + { + return 62; // base64 '+' is 62 + } + if (ch == '/') + { + return 63; // base64 '/' is 63 + } + + return -1; + } + + template<> + constexpr int Base64::DecodeBase64Char(const wchar_t ch, const Flags flags) noexcept + { + // returns -1 if the character is invalid + // or should be skipped + // otherwise, returns the 6-bit code for the character + // from the encoding table + if (ch >= L'A' && ch <= L'Z') + { + return ch - L'A' + 0; // 0 range starts at 'A' + } + if (ch >= L'a' && ch <= L'z') + { + return ch - L'a' + 26; // 26 range starts at 'a' + } + if (ch >= L'0' && ch <= L'9') + { + return ch - L'0' + 52; // 52 range starts at '0' + } + + if ((flags & Flags::Url) != Flags::None) + { + if (ch == L'-') + { + return 62; // base64url '+' -> '-' + } + if (ch == L'_') + { + return 63; // base64url '/' -> '_' + } + } + + if (ch == L'+') + { + return 62; // base64 '+' is 62 + } + if (ch == L'/') + { + return 63; // base64 '/' is 63 + } + + return -1; + } +} diff --git a/src/Core/Core.Native/Blittable.h b/src/Core/Core.Native/Blittable.h new file mode 100644 index 0000000..2f6e1e7 --- /dev/null +++ b/src/Core/Core.Native/Blittable.h @@ -0,0 +1,19 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +namespace cdb_core +{ + template + struct is_blittable + { + constexpr static bool value = + std::is_nothrow_default_constructible_v && + std::is_nothrow_copy_constructible_v && + std::is_nothrow_copy_assignable_v; + }; + + template + inline constexpr bool is_blittable_v = is_blittable::value; +} diff --git a/src/Core/Core.Native/Contract.cpp b/src/Core/Core.Native/Contract.cpp new file mode 100644 index 0000000..5b154a7 --- /dev/null +++ b/src/Core/Core.Native/Contract.cpp @@ -0,0 +1,26 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "Contract.h" +#include "Strings.h" + +namespace cdb_core +{ + std::wstring Contract::MakeError(std::string_view api, std::string_view message) + { + if (message.empty()) + { + return make_string(L"%.*S", api.size(), api.data()); + } + return make_string(L"%.*S Failure: %.*S", api.size(), api.data(), message.size(), message.data()); + } + + [[noreturn]] void Contract::Fail(std::string_view api, std::string_view message) + { + std::wstring error = MakeError(api, message); + _ASSERT_EXPR(false, error.c_str()); + std::terminate(); + } +} diff --git a/src/Core/Core.Native/Contract.h b/src/Core/Core.Native/Contract.h new file mode 100644 index 0000000..428937c --- /dev/null +++ b/src/Core/Core.Native/Contract.h @@ -0,0 +1,92 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +// Forward Declarations +namespace cdb_core_test +{ + class ContractUnitTests; +} + +namespace cdb_core +{ + class Contract final + { + public: + Contract() = delete; + ~Contract() = delete; + Contract(const Contract& other) = delete; + Contract(Contract&& other) noexcept = delete; + Contract& operator=(const Contract& other) = delete; + Contract& operator=(Contract&& other) noexcept = delete; + + constexpr static void Assert([[maybe_unused]] bool condition) + { + #if _DEBUG + if (!condition) + { + Fail("Assert", std::string_view()); + } + #endif + } + + constexpr static void Assert([[maybe_unused]] bool condition, std::string_view message) + { + #if _DEBUG + if (!condition) + { + Fail("Assert", message); + } + #endif + } + + constexpr static void Requires(bool condition) + { + if (!condition) + { + Fail("Requires", std::string_view()); + } + } + + constexpr static void Requires(bool condition, std::string_view message) + { + if (!condition) + { + Fail("Requires", message); + } + } + + constexpr static void Invariant(bool condition) + { + if (!condition) + { + Fail("Invariant", std::string_view()); + } + } + + constexpr static void Invariant(bool condition, std::string_view message) + { + if (!condition) + { + Fail("Invariant", message); + } + } + + [[noreturn]] static void Fail() + { + Fail("Fail", std::string_view()); + } + + [[noreturn]] static void Fail(std::string_view message) + { + Fail("Fail", message); + } + + private: + friend class cdb_core_test::ContractUnitTests; + [[noreturn]] static void Fail(std::string_view api, std::string_view message); + [[nodiscard]] static std::wstring MakeError(std::string_view api, std::string_view message); + }; +} diff --git a/src/Core/Core.Native/Core.Native.h b/src/Core/Core.Native/Core.Native.h new file mode 100644 index 0000000..72458fa --- /dev/null +++ b/src/Core/Core.Native/Core.Native.h @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "framework.h" + +#include "Contract.h" +#include "HashCode.h" +#include "tla.h" +#include "Endian.h" +#include "Blittable.h" +#include "IsAllSame.h" +#include "Span.h" +#include "ReadOnlySpan.h" +#include "MemoryMarshal.h" +#include "Memory.h" +#include "ReadOnlyMemory.h" +#include "Strings.h" +#include "make_unique.h" +#include "Stringable.h" +#include "EqualityComparable.h" +#include "DeepCompare.h" +#include "Hashable.h" +#include "Utf8Span.h" +#include "ref_ptr.h" +#include "Failure.h" +#include "Result.h" +#include "TimeSpan.h" +#include "Base64.h" +#include "Stopwatch.h" +#include "Crc32.h" diff --git a/src/Core/Core.Native/Crc32.cpp b/src/Core/Core.Native/Crc32.cpp new file mode 100644 index 0000000..7457b18 --- /dev/null +++ b/src/Core/Core.Native/Crc32.cpp @@ -0,0 +1,2159 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include +#include "Crc32.h" + +#include "Contract.h" +#include "ReadOnlySpan.h" + +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace cdb_core +{ + // Licensed to the .NET Foundation under one or more agreements. + // The .NET Foundation licenses this file to you under the MIT license. + // See the LICENSE file in the project root for more information. + // + // File implements Slicing-by-8 CRC Generation, as described in + // "Novel Table Lookup-Based Algorithms for High-Performance CRC Generation" + // IEEE TRANSACTIONS ON COMPUTERS, VOL. 57, NO. 11, NOVEMBER 2008 + // + // Copyright(c) 2004-2006 Intel Corporation - All Rights Reserved + // + // This software program is licensed subject to the BSD License, + // available at http://www.opensource.org/licenses/bsd-license.html. + + // Generated tables for managed crc calculation. + // Each table n (starting at 0) contains remainders from the long division of + // all possible byte values, shifted by an offset of (n * 4 bits). + // The divisor used is the crc32 standard polynomial 0xEDB88320 + // Please see cited paper for more details. + constexpr static uint32_t CrcTable0[256] + { + 0x00000000u, + 0x77073096u, + 0xee0e612cu, + 0x990951bau, + 0x076dc419u, + 0x706af48fu, + 0xe963a535u, + 0x9e6495a3u, + 0x0edb8832u, + 0x79dcb8a4u, + 0xe0d5e91eu, + 0x97d2d988u, + 0x09b64c2bu, + 0x7eb17cbdu, + 0xe7b82d07u, + 0x90bf1d91u, + 0x1db71064u, + 0x6ab020f2u, + 0xf3b97148u, + 0x84be41deu, + 0x1adad47du, + 0x6ddde4ebu, + 0xf4d4b551u, + 0x83d385c7u, + 0x136c9856u, + 0x646ba8c0u, + 0xfd62f97au, + 0x8a65c9ecu, + 0x14015c4fu, + 0x63066cd9u, + 0xfa0f3d63u, + 0x8d080df5u, + 0x3b6e20c8u, + 0x4c69105eu, + 0xd56041e4u, + 0xa2677172u, + 0x3c03e4d1u, + 0x4b04d447u, + 0xd20d85fdu, + 0xa50ab56bu, + 0x35b5a8fau, + 0x42b2986cu, + 0xdbbbc9d6u, + 0xacbcf940u, + 0x32d86ce3u, + 0x45df5c75u, + 0xdcd60dcfu, + 0xabd13d59u, + 0x26d930acu, + 0x51de003au, + 0xc8d75180u, + 0xbfd06116u, + 0x21b4f4b5u, + 0x56b3c423u, + 0xcfba9599u, + 0xb8bda50fu, + 0x2802b89eu, + 0x5f058808u, + 0xc60cd9b2u, + 0xb10be924u, + 0x2f6f7c87u, + 0x58684c11u, + 0xc1611dabu, + 0xb6662d3du, + 0x76dc4190u, + 0x01db7106u, + 0x98d220bcu, + 0xefd5102au, + 0x71b18589u, + 0x06b6b51fu, + 0x9fbfe4a5u, + 0xe8b8d433u, + 0x7807c9a2u, + 0x0f00f934u, + 0x9609a88eu, + 0xe10e9818u, + 0x7f6a0dbbu, + 0x086d3d2du, + 0x91646c97u, + 0xe6635c01u, + 0x6b6b51f4u, + 0x1c6c6162u, + 0x856530d8u, + 0xf262004eu, + 0x6c0695edu, + 0x1b01a57bu, + 0x8208f4c1u, + 0xf50fc457u, + 0x65b0d9c6u, + 0x12b7e950u, + 0x8bbeb8eau, + 0xfcb9887cu, + 0x62dd1ddfu, + 0x15da2d49u, + 0x8cd37cf3u, + 0xfbd44c65u, + 0x4db26158u, + 0x3ab551ceu, + 0xa3bc0074u, + 0xd4bb30e2u, + 0x4adfa541u, + 0x3dd895d7u, + 0xa4d1c46du, + 0xd3d6f4fbu, + 0x4369e96au, + 0x346ed9fcu, + 0xad678846u, + 0xda60b8d0u, + 0x44042d73u, + 0x33031de5u, + 0xaa0a4c5fu, + 0xdd0d7cc9u, + 0x5005713cu, + 0x270241aau, + 0xbe0b1010u, + 0xc90c2086u, + 0x5768b525u, + 0x206f85b3u, + 0xb966d409u, + 0xce61e49fu, + 0x5edef90eu, + 0x29d9c998u, + 0xb0d09822u, + 0xc7d7a8b4u, + 0x59b33d17u, + 0x2eb40d81u, + 0xb7bd5c3bu, + 0xc0ba6cadu, + 0xedb88320u, + 0x9abfb3b6u, + 0x03b6e20cu, + 0x74b1d29au, + 0xead54739u, + 0x9dd277afu, + 0x04db2615u, + 0x73dc1683u, + 0xe3630b12u, + 0x94643b84u, + 0x0d6d6a3eu, + 0x7a6a5aa8u, + 0xe40ecf0bu, + 0x9309ff9du, + 0x0a00ae27u, + 0x7d079eb1u, + 0xf00f9344u, + 0x8708a3d2u, + 0x1e01f268u, + 0x6906c2feu, + 0xf762575du, + 0x806567cbu, + 0x196c3671u, + 0x6e6b06e7u, + 0xfed41b76u, + 0x89d32be0u, + 0x10da7a5au, + 0x67dd4accu, + 0xf9b9df6fu, + 0x8ebeeff9u, + 0x17b7be43u, + 0x60b08ed5u, + 0xd6d6a3e8u, + 0xa1d1937eu, + 0x38d8c2c4u, + 0x4fdff252u, + 0xd1bb67f1u, + 0xa6bc5767u, + 0x3fb506ddu, + 0x48b2364bu, + 0xd80d2bdau, + 0xaf0a1b4cu, + 0x36034af6u, + 0x41047a60u, + 0xdf60efc3u, + 0xa867df55u, + 0x316e8eefu, + 0x4669be79u, + 0xcb61b38cu, + 0xbc66831au, + 0x256fd2a0u, + 0x5268e236u, + 0xcc0c7795u, + 0xbb0b4703u, + 0x220216b9u, + 0x5505262fu, + 0xc5ba3bbeu, + 0xb2bd0b28u, + 0x2bb45a92u, + 0x5cb36a04u, + 0xc2d7ffa7u, + 0xb5d0cf31u, + 0x2cd99e8bu, + 0x5bdeae1du, + 0x9b64c2b0u, + 0xec63f226u, + 0x756aa39cu, + 0x026d930au, + 0x9c0906a9u, + 0xeb0e363fu, + 0x72076785u, + 0x05005713u, + 0x95bf4a82u, + 0xe2b87a14u, + 0x7bb12baeu, + 0x0cb61b38u, + 0x92d28e9bu, + 0xe5d5be0du, + 0x7cdcefb7u, + 0x0bdbdf21u, + 0x86d3d2d4u, + 0xf1d4e242u, + 0x68ddb3f8u, + 0x1fda836eu, + 0x81be16cdu, + 0xf6b9265bu, + 0x6fb077e1u, + 0x18b74777u, + 0x88085ae6u, + 0xff0f6a70u, + 0x66063bcau, + 0x11010b5cu, + 0x8f659effu, + 0xf862ae69u, + 0x616bffd3u, + 0x166ccf45u, + 0xa00ae278u, + 0xd70dd2eeu, + 0x4e048354u, + 0x3903b3c2u, + 0xa7672661u, + 0xd06016f7u, + 0x4969474du, + 0x3e6e77dbu, + 0xaed16a4au, + 0xd9d65adcu, + 0x40df0b66u, + 0x37d83bf0u, + 0xa9bcae53u, + 0xdebb9ec5u, + 0x47b2cf7fu, + 0x30b5ffe9u, + 0xbdbdf21cu, + 0xcabac28au, + 0x53b39330u, + 0x24b4a3a6u, + 0xbad03605u, + 0xcdd70693u, + 0x54de5729u, + 0x23d967bfu, + 0xb3667a2eu, + 0xc4614ab8u, + 0x5d681b02u, + 0x2a6f2b94u, + 0xb40bbe37u, + 0xc30c8ea1u, + 0x5a05df1bu, + 0x2d02ef8du, + }; + + constexpr static uint32_t CrcTable1[256] + { + 0x00000000u, + 0x191B3141u, + 0x32366282u, + 0x2B2D53C3u, + 0x646CC504u, + 0x7D77F445u, + 0x565AA786u, + 0x4F4196C7u, + 0xC8D98A08u, + 0xD1C2BB49u, + 0xFAEFE88Au, + 0xE3F4D9CBu, + 0xACB54F0Cu, + 0xB5AE7E4Du, + 0x9E832D8Eu, + 0x87981CCFu, + 0x4AC21251u, + 0x53D92310u, + 0x78F470D3u, + 0x61EF4192u, + 0x2EAED755u, + 0x37B5E614u, + 0x1C98B5D7u, + 0x05838496u, + 0x821B9859u, + 0x9B00A918u, + 0xB02DFADBu, + 0xA936CB9Au, + 0xE6775D5Du, + 0xFF6C6C1Cu, + 0xD4413FDFu, + 0xCD5A0E9Eu, + 0x958424A2u, + 0x8C9F15E3u, + 0xA7B24620u, + 0xBEA97761u, + 0xF1E8E1A6u, + 0xE8F3D0E7u, + 0xC3DE8324u, + 0xDAC5B265u, + 0x5D5DAEAAu, + 0x44469FEBu, + 0x6F6BCC28u, + 0x7670FD69u, + 0x39316BAEu, + 0x202A5AEFu, + 0x0B07092Cu, + 0x121C386Du, + 0xDF4636F3u, + 0xC65D07B2u, + 0xED705471u, + 0xF46B6530u, + 0xBB2AF3F7u, + 0xA231C2B6u, + 0x891C9175u, + 0x9007A034u, + 0x179FBCFBu, + 0x0E848DBAu, + 0x25A9DE79u, + 0x3CB2EF38u, + 0x73F379FFu, + 0x6AE848BEu, + 0x41C51B7Du, + 0x58DE2A3Cu, + 0xF0794F05u, + 0xE9627E44u, + 0xC24F2D87u, + 0xDB541CC6u, + 0x94158A01u, + 0x8D0EBB40u, + 0xA623E883u, + 0xBF38D9C2u, + 0x38A0C50Du, + 0x21BBF44Cu, + 0x0A96A78Fu, + 0x138D96CEu, + 0x5CCC0009u, + 0x45D73148u, + 0x6EFA628Bu, + 0x77E153CAu, + 0xBABB5D54u, + 0xA3A06C15u, + 0x888D3FD6u, + 0x91960E97u, + 0xDED79850u, + 0xC7CCA911u, + 0xECE1FAD2u, + 0xF5FACB93u, + 0x7262D75Cu, + 0x6B79E61Du, + 0x4054B5DEu, + 0x594F849Fu, + 0x160E1258u, + 0x0F152319u, + 0x243870DAu, + 0x3D23419Bu, + 0x65FD6BA7u, + 0x7CE65AE6u, + 0x57CB0925u, + 0x4ED03864u, + 0x0191AEA3u, + 0x188A9FE2u, + 0x33A7CC21u, + 0x2ABCFD60u, + 0xAD24E1AFu, + 0xB43FD0EEu, + 0x9F12832Du, + 0x8609B26Cu, + 0xC94824ABu, + 0xD05315EAu, + 0xFB7E4629u, + 0xE2657768u, + 0x2F3F79F6u, + 0x362448B7u, + 0x1D091B74u, + 0x04122A35u, + 0x4B53BCF2u, + 0x52488DB3u, + 0x7965DE70u, + 0x607EEF31u, + 0xE7E6F3FEu, + 0xFEFDC2BFu, + 0xD5D0917Cu, + 0xCCCBA03Du, + 0x838A36FAu, + 0x9A9107BBu, + 0xB1BC5478u, + 0xA8A76539u, + 0x3B83984Bu, + 0x2298A90Au, + 0x09B5FAC9u, + 0x10AECB88u, + 0x5FEF5D4Fu, + 0x46F46C0Eu, + 0x6DD93FCDu, + 0x74C20E8Cu, + 0xF35A1243u, + 0xEA412302u, + 0xC16C70C1u, + 0xD8774180u, + 0x9736D747u, + 0x8E2DE606u, + 0xA500B5C5u, + 0xBC1B8484u, + 0x71418A1Au, + 0x685ABB5Bu, + 0x4377E898u, + 0x5A6CD9D9u, + 0x152D4F1Eu, + 0x0C367E5Fu, + 0x271B2D9Cu, + 0x3E001CDDu, + 0xB9980012u, + 0xA0833153u, + 0x8BAE6290u, + 0x92B553D1u, + 0xDDF4C516u, + 0xC4EFF457u, + 0xEFC2A794u, + 0xF6D996D5u, + 0xAE07BCE9u, + 0xB71C8DA8u, + 0x9C31DE6Bu, + 0x852AEF2Au, + 0xCA6B79EDu, + 0xD37048ACu, + 0xF85D1B6Fu, + 0xE1462A2Eu, + 0x66DE36E1u, + 0x7FC507A0u, + 0x54E85463u, + 0x4DF36522u, + 0x02B2F3E5u, + 0x1BA9C2A4u, + 0x30849167u, + 0x299FA026u, + 0xE4C5AEB8u, + 0xFDDE9FF9u, + 0xD6F3CC3Au, + 0xCFE8FD7Bu, + 0x80A96BBCu, + 0x99B25AFDu, + 0xB29F093Eu, + 0xAB84387Fu, + 0x2C1C24B0u, + 0x350715F1u, + 0x1E2A4632u, + 0x07317773u, + 0x4870E1B4u, + 0x516BD0F5u, + 0x7A468336u, + 0x635DB277u, + 0xCBFAD74Eu, + 0xD2E1E60Fu, + 0xF9CCB5CCu, + 0xE0D7848Du, + 0xAF96124Au, + 0xB68D230Bu, + 0x9DA070C8u, + 0x84BB4189u, + 0x03235D46u, + 0x1A386C07u, + 0x31153FC4u, + 0x280E0E85u, + 0x674F9842u, + 0x7E54A903u, + 0x5579FAC0u, + 0x4C62CB81u, + 0x8138C51Fu, + 0x9823F45Eu, + 0xB30EA79Du, + 0xAA1596DCu, + 0xE554001Bu, + 0xFC4F315Au, + 0xD7626299u, + 0xCE7953D8u, + 0x49E14F17u, + 0x50FA7E56u, + 0x7BD72D95u, + 0x62CC1CD4u, + 0x2D8D8A13u, + 0x3496BB52u, + 0x1FBBE891u, + 0x06A0D9D0u, + 0x5E7EF3ECu, + 0x4765C2ADu, + 0x6C48916Eu, + 0x7553A02Fu, + 0x3A1236E8u, + 0x230907A9u, + 0x0824546Au, + 0x113F652Bu, + 0x96A779E4u, + 0x8FBC48A5u, + 0xA4911B66u, + 0xBD8A2A27u, + 0xF2CBBCE0u, + 0xEBD08DA1u, + 0xC0FDDE62u, + 0xD9E6EF23u, + 0x14BCE1BDu, + 0x0DA7D0FCu, + 0x268A833Fu, + 0x3F91B27Eu, + 0x70D024B9u, + 0x69CB15F8u, + 0x42E6463Bu, + 0x5BFD777Au, + 0xDC656BB5u, + 0xC57E5AF4u, + 0xEE530937u, + 0xF7483876u, + 0xB809AEB1u, + 0xA1129FF0u, + 0x8A3FCC33u, + 0x9324FD72u, + }; + + constexpr static uint32_t CrcTable2[256] + { + 0x00000000u, + 0x01C26A37u, + 0x0384D46Eu, + 0x0246BE59u, + 0x0709A8DCu, + 0x06CBC2EBu, + 0x048D7CB2u, + 0x054F1685u, + 0x0E1351B8u, + 0x0FD13B8Fu, + 0x0D9785D6u, + 0x0C55EFE1u, + 0x091AF964u, + 0x08D89353u, + 0x0A9E2D0Au, + 0x0B5C473Du, + 0x1C26A370u, + 0x1DE4C947u, + 0x1FA2771Eu, + 0x1E601D29u, + 0x1B2F0BACu, + 0x1AED619Bu, + 0x18ABDFC2u, + 0x1969B5F5u, + 0x1235F2C8u, + 0x13F798FFu, + 0x11B126A6u, + 0x10734C91u, + 0x153C5A14u, + 0x14FE3023u, + 0x16B88E7Au, + 0x177AE44Du, + 0x384D46E0u, + 0x398F2CD7u, + 0x3BC9928Eu, + 0x3A0BF8B9u, + 0x3F44EE3Cu, + 0x3E86840Bu, + 0x3CC03A52u, + 0x3D025065u, + 0x365E1758u, + 0x379C7D6Fu, + 0x35DAC336u, + 0x3418A901u, + 0x3157BF84u, + 0x3095D5B3u, + 0x32D36BEAu, + 0x331101DDu, + 0x246BE590u, + 0x25A98FA7u, + 0x27EF31FEu, + 0x262D5BC9u, + 0x23624D4Cu, + 0x22A0277Bu, + 0x20E69922u, + 0x2124F315u, + 0x2A78B428u, + 0x2BBADE1Fu, + 0x29FC6046u, + 0x283E0A71u, + 0x2D711CF4u, + 0x2CB376C3u, + 0x2EF5C89Au, + 0x2F37A2ADu, + 0x709A8DC0u, + 0x7158E7F7u, + 0x731E59AEu, + 0x72DC3399u, + 0x7793251Cu, + 0x76514F2Bu, + 0x7417F172u, + 0x75D59B45u, + 0x7E89DC78u, + 0x7F4BB64Fu, + 0x7D0D0816u, + 0x7CCF6221u, + 0x798074A4u, + 0x78421E93u, + 0x7A04A0CAu, + 0x7BC6CAFDu, + 0x6CBC2EB0u, + 0x6D7E4487u, + 0x6F38FADEu, + 0x6EFA90E9u, + 0x6BB5866Cu, + 0x6A77EC5Bu, + 0x68315202u, + 0x69F33835u, + 0x62AF7F08u, + 0x636D153Fu, + 0x612BAB66u, + 0x60E9C151u, + 0x65A6D7D4u, + 0x6464BDE3u, + 0x662203BAu, + 0x67E0698Du, + 0x48D7CB20u, + 0x4915A117u, + 0x4B531F4Eu, + 0x4A917579u, + 0x4FDE63FCu, + 0x4E1C09CBu, + 0x4C5AB792u, + 0x4D98DDA5u, + 0x46C49A98u, + 0x4706F0AFu, + 0x45404EF6u, + 0x448224C1u, + 0x41CD3244u, + 0x400F5873u, + 0x4249E62Au, + 0x438B8C1Du, + 0x54F16850u, + 0x55330267u, + 0x5775BC3Eu, + 0x56B7D609u, + 0x53F8C08Cu, + 0x523AAABBu, + 0x507C14E2u, + 0x51BE7ED5u, + 0x5AE239E8u, + 0x5B2053DFu, + 0x5966ED86u, + 0x58A487B1u, + 0x5DEB9134u, + 0x5C29FB03u, + 0x5E6F455Au, + 0x5FAD2F6Du, + 0xE1351B80u, + 0xE0F771B7u, + 0xE2B1CFEEu, + 0xE373A5D9u, + 0xE63CB35Cu, + 0xE7FED96Bu, + 0xE5B86732u, + 0xE47A0D05u, + 0xEF264A38u, + 0xEEE4200Fu, + 0xECA29E56u, + 0xED60F461u, + 0xE82FE2E4u, + 0xE9ED88D3u, + 0xEBAB368Au, + 0xEA695CBDu, + 0xFD13B8F0u, + 0xFCD1D2C7u, + 0xFE976C9Eu, + 0xFF5506A9u, + 0xFA1A102Cu, + 0xFBD87A1Bu, + 0xF99EC442u, + 0xF85CAE75u, + 0xF300E948u, + 0xF2C2837Fu, + 0xF0843D26u, + 0xF1465711u, + 0xF4094194u, + 0xF5CB2BA3u, + 0xF78D95FAu, + 0xF64FFFCDu, + 0xD9785D60u, + 0xD8BA3757u, + 0xDAFC890Eu, + 0xDB3EE339u, + 0xDE71F5BCu, + 0xDFB39F8Bu, + 0xDDF521D2u, + 0xDC374BE5u, + 0xD76B0CD8u, + 0xD6A966EFu, + 0xD4EFD8B6u, + 0xD52DB281u, + 0xD062A404u, + 0xD1A0CE33u, + 0xD3E6706Au, + 0xD2241A5Du, + 0xC55EFE10u, + 0xC49C9427u, + 0xC6DA2A7Eu, + 0xC7184049u, + 0xC25756CCu, + 0xC3953CFBu, + 0xC1D382A2u, + 0xC011E895u, + 0xCB4DAFA8u, + 0xCA8FC59Fu, + 0xC8C97BC6u, + 0xC90B11F1u, + 0xCC440774u, + 0xCD866D43u, + 0xCFC0D31Au, + 0xCE02B92Du, + 0x91AF9640u, + 0x906DFC77u, + 0x922B422Eu, + 0x93E92819u, + 0x96A63E9Cu, + 0x976454ABu, + 0x9522EAF2u, + 0x94E080C5u, + 0x9FBCC7F8u, + 0x9E7EADCFu, + 0x9C381396u, + 0x9DFA79A1u, + 0x98B56F24u, + 0x99770513u, + 0x9B31BB4Au, + 0x9AF3D17Du, + 0x8D893530u, + 0x8C4B5F07u, + 0x8E0DE15Eu, + 0x8FCF8B69u, + 0x8A809DECu, + 0x8B42F7DBu, + 0x89044982u, + 0x88C623B5u, + 0x839A6488u, + 0x82580EBFu, + 0x801EB0E6u, + 0x81DCDAD1u, + 0x8493CC54u, + 0x8551A663u, + 0x8717183Au, + 0x86D5720Du, + 0xA9E2D0A0u, + 0xA820BA97u, + 0xAA6604CEu, + 0xABA46EF9u, + 0xAEEB787Cu, + 0xAF29124Bu, + 0xAD6FAC12u, + 0xACADC625u, + 0xA7F18118u, + 0xA633EB2Fu, + 0xA4755576u, + 0xA5B73F41u, + 0xA0F829C4u, + 0xA13A43F3u, + 0xA37CFDAAu, + 0xA2BE979Du, + 0xB5C473D0u, + 0xB40619E7u, + 0xB640A7BEu, + 0xB782CD89u, + 0xB2CDDB0Cu, + 0xB30FB13Bu, + 0xB1490F62u, + 0xB08B6555u, + 0xBBD72268u, + 0xBA15485Fu, + 0xB853F606u, + 0xB9919C31u, + 0xBCDE8AB4u, + 0xBD1CE083u, + 0xBF5A5EDAu, + 0xBE9834EDu, + }; + + constexpr static uint32_t CrcTable3[256] + { + 0x00000000u, + 0xB8BC6765u, + 0xAA09C88Bu, + 0x12B5AFEEu, + 0x8F629757u, + 0x37DEF032u, + 0x256B5FDCu, + 0x9DD738B9u, + 0xC5B428EFu, + 0x7D084F8Au, + 0x6FBDE064u, + 0xD7018701u, + 0x4AD6BFB8u, + 0xF26AD8DDu, + 0xE0DF7733u, + 0x58631056u, + 0x5019579Fu, + 0xE8A530FAu, + 0xFA109F14u, + 0x42ACF871u, + 0xDF7BC0C8u, + 0x67C7A7ADu, + 0x75720843u, + 0xCDCE6F26u, + 0x95AD7F70u, + 0x2D111815u, + 0x3FA4B7FBu, + 0x8718D09Eu, + 0x1ACFE827u, + 0xA2738F42u, + 0xB0C620ACu, + 0x087A47C9u, + 0xA032AF3Eu, + 0x188EC85Bu, + 0x0A3B67B5u, + 0xB28700D0u, + 0x2F503869u, + 0x97EC5F0Cu, + 0x8559F0E2u, + 0x3DE59787u, + 0x658687D1u, + 0xDD3AE0B4u, + 0xCF8F4F5Au, + 0x7733283Fu, + 0xEAE41086u, + 0x525877E3u, + 0x40EDD80Du, + 0xF851BF68u, + 0xF02BF8A1u, + 0x48979FC4u, + 0x5A22302Au, + 0xE29E574Fu, + 0x7F496FF6u, + 0xC7F50893u, + 0xD540A77Du, + 0x6DFCC018u, + 0x359FD04Eu, + 0x8D23B72Bu, + 0x9F9618C5u, + 0x272A7FA0u, + 0xBAFD4719u, + 0x0241207Cu, + 0x10F48F92u, + 0xA848E8F7u, + 0x9B14583Du, + 0x23A83F58u, + 0x311D90B6u, + 0x89A1F7D3u, + 0x1476CF6Au, + 0xACCAA80Fu, + 0xBE7F07E1u, + 0x06C36084u, + 0x5EA070D2u, + 0xE61C17B7u, + 0xF4A9B859u, + 0x4C15DF3Cu, + 0xD1C2E785u, + 0x697E80E0u, + 0x7BCB2F0Eu, + 0xC377486Bu, + 0xCB0D0FA2u, + 0x73B168C7u, + 0x6104C729u, + 0xD9B8A04Cu, + 0x446F98F5u, + 0xFCD3FF90u, + 0xEE66507Eu, + 0x56DA371Bu, + 0x0EB9274Du, + 0xB6054028u, + 0xA4B0EFC6u, + 0x1C0C88A3u, + 0x81DBB01Au, + 0x3967D77Fu, + 0x2BD27891u, + 0x936E1FF4u, + 0x3B26F703u, + 0x839A9066u, + 0x912F3F88u, + 0x299358EDu, + 0xB4446054u, + 0x0CF80731u, + 0x1E4DA8DFu, + 0xA6F1CFBAu, + 0xFE92DFECu, + 0x462EB889u, + 0x549B1767u, + 0xEC277002u, + 0x71F048BBu, + 0xC94C2FDEu, + 0xDBF98030u, + 0x6345E755u, + 0x6B3FA09Cu, + 0xD383C7F9u, + 0xC1366817u, + 0x798A0F72u, + 0xE45D37CBu, + 0x5CE150AEu, + 0x4E54FF40u, + 0xF6E89825u, + 0xAE8B8873u, + 0x1637EF16u, + 0x048240F8u, + 0xBC3E279Du, + 0x21E91F24u, + 0x99557841u, + 0x8BE0D7AFu, + 0x335CB0CAu, + 0xED59B63Bu, + 0x55E5D15Eu, + 0x47507EB0u, + 0xFFEC19D5u, + 0x623B216Cu, + 0xDA874609u, + 0xC832E9E7u, + 0x708E8E82u, + 0x28ED9ED4u, + 0x9051F9B1u, + 0x82E4565Fu, + 0x3A58313Au, + 0xA78F0983u, + 0x1F336EE6u, + 0x0D86C108u, + 0xB53AA66Du, + 0xBD40E1A4u, + 0x05FC86C1u, + 0x1749292Fu, + 0xAFF54E4Au, + 0x322276F3u, + 0x8A9E1196u, + 0x982BBE78u, + 0x2097D91Du, + 0x78F4C94Bu, + 0xC048AE2Eu, + 0xD2FD01C0u, + 0x6A4166A5u, + 0xF7965E1Cu, + 0x4F2A3979u, + 0x5D9F9697u, + 0xE523F1F2u, + 0x4D6B1905u, + 0xF5D77E60u, + 0xE762D18Eu, + 0x5FDEB6EBu, + 0xC2098E52u, + 0x7AB5E937u, + 0x680046D9u, + 0xD0BC21BCu, + 0x88DF31EAu, + 0x3063568Fu, + 0x22D6F961u, + 0x9A6A9E04u, + 0x07BDA6BDu, + 0xBF01C1D8u, + 0xADB46E36u, + 0x15080953u, + 0x1D724E9Au, + 0xA5CE29FFu, + 0xB77B8611u, + 0x0FC7E174u, + 0x9210D9CDu, + 0x2AACBEA8u, + 0x38191146u, + 0x80A57623u, + 0xD8C66675u, + 0x607A0110u, + 0x72CFAEFEu, + 0xCA73C99Bu, + 0x57A4F122u, + 0xEF189647u, + 0xFDAD39A9u, + 0x45115ECCu, + 0x764DEE06u, + 0xCEF18963u, + 0xDC44268Du, + 0x64F841E8u, + 0xF92F7951u, + 0x41931E34u, + 0x5326B1DAu, + 0xEB9AD6BFu, + 0xB3F9C6E9u, + 0x0B45A18Cu, + 0x19F00E62u, + 0xA14C6907u, + 0x3C9B51BEu, + 0x842736DBu, + 0x96929935u, + 0x2E2EFE50u, + 0x2654B999u, + 0x9EE8DEFCu, + 0x8C5D7112u, + 0x34E11677u, + 0xA9362ECEu, + 0x118A49ABu, + 0x033FE645u, + 0xBB838120u, + 0xE3E09176u, + 0x5B5CF613u, + 0x49E959FDu, + 0xF1553E98u, + 0x6C820621u, + 0xD43E6144u, + 0xC68BCEAAu, + 0x7E37A9CFu, + 0xD67F4138u, + 0x6EC3265Du, + 0x7C7689B3u, + 0xC4CAEED6u, + 0x591DD66Fu, + 0xE1A1B10Au, + 0xF3141EE4u, + 0x4BA87981u, + 0x13CB69D7u, + 0xAB770EB2u, + 0xB9C2A15Cu, + 0x017EC639u, + 0x9CA9FE80u, + 0x241599E5u, + 0x36A0360Bu, + 0x8E1C516Eu, + 0x866616A7u, + 0x3EDA71C2u, + 0x2C6FDE2Cu, + 0x94D3B949u, + 0x090481F0u, + 0xB1B8E695u, + 0xA30D497Bu, + 0x1BB12E1Eu, + 0x43D23E48u, + 0xFB6E592Du, + 0xE9DBF6C3u, + 0x516791A6u, + 0xCCB0A91Fu, + 0x740CCE7Au, + 0x66B96194u, + 0xDE0506F1u, + }; + + constexpr static uint32_t CrcTable4[256] + { + 0x00000000u, + 0x3D6029B0u, + 0x7AC05360u, + 0x47A07AD0u, + 0xF580A6C0u, + 0xC8E08F70u, + 0x8F40F5A0u, + 0xB220DC10u, + 0x30704BC1u, + 0x0D106271u, + 0x4AB018A1u, + 0x77D03111u, + 0xC5F0ED01u, + 0xF890C4B1u, + 0xBF30BE61u, + 0x825097D1u, + 0x60E09782u, + 0x5D80BE32u, + 0x1A20C4E2u, + 0x2740ED52u, + 0x95603142u, + 0xA80018F2u, + 0xEFA06222u, + 0xD2C04B92u, + 0x5090DC43u, + 0x6DF0F5F3u, + 0x2A508F23u, + 0x1730A693u, + 0xA5107A83u, + 0x98705333u, + 0xDFD029E3u, + 0xE2B00053u, + 0xC1C12F04u, + 0xFCA106B4u, + 0xBB017C64u, + 0x866155D4u, + 0x344189C4u, + 0x0921A074u, + 0x4E81DAA4u, + 0x73E1F314u, + 0xF1B164C5u, + 0xCCD14D75u, + 0x8B7137A5u, + 0xB6111E15u, + 0x0431C205u, + 0x3951EBB5u, + 0x7EF19165u, + 0x4391B8D5u, + 0xA121B886u, + 0x9C419136u, + 0xDBE1EBE6u, + 0xE681C256u, + 0x54A11E46u, + 0x69C137F6u, + 0x2E614D26u, + 0x13016496u, + 0x9151F347u, + 0xAC31DAF7u, + 0xEB91A027u, + 0xD6F18997u, + 0x64D15587u, + 0x59B17C37u, + 0x1E1106E7u, + 0x23712F57u, + 0x58F35849u, + 0x659371F9u, + 0x22330B29u, + 0x1F532299u, + 0xAD73FE89u, + 0x9013D739u, + 0xD7B3ADE9u, + 0xEAD38459u, + 0x68831388u, + 0x55E33A38u, + 0x124340E8u, + 0x2F236958u, + 0x9D03B548u, + 0xA0639CF8u, + 0xE7C3E628u, + 0xDAA3CF98u, + 0x3813CFCBu, + 0x0573E67Bu, + 0x42D39CABu, + 0x7FB3B51Bu, + 0xCD93690Bu, + 0xF0F340BBu, + 0xB7533A6Bu, + 0x8A3313DBu, + 0x0863840Au, + 0x3503ADBAu, + 0x72A3D76Au, + 0x4FC3FEDAu, + 0xFDE322CAu, + 0xC0830B7Au, + 0x872371AAu, + 0xBA43581Au, + 0x9932774Du, + 0xA4525EFDu, + 0xE3F2242Du, + 0xDE920D9Du, + 0x6CB2D18Du, + 0x51D2F83Du, + 0x167282EDu, + 0x2B12AB5Du, + 0xA9423C8Cu, + 0x9422153Cu, + 0xD3826FECu, + 0xEEE2465Cu, + 0x5CC29A4Cu, + 0x61A2B3FCu, + 0x2602C92Cu, + 0x1B62E09Cu, + 0xF9D2E0CFu, + 0xC4B2C97Fu, + 0x8312B3AFu, + 0xBE729A1Fu, + 0x0C52460Fu, + 0x31326FBFu, + 0x7692156Fu, + 0x4BF23CDFu, + 0xC9A2AB0Eu, + 0xF4C282BEu, + 0xB362F86Eu, + 0x8E02D1DEu, + 0x3C220DCEu, + 0x0142247Eu, + 0x46E25EAEu, + 0x7B82771Eu, + 0xB1E6B092u, + 0x8C869922u, + 0xCB26E3F2u, + 0xF646CA42u, + 0x44661652u, + 0x79063FE2u, + 0x3EA64532u, + 0x03C66C82u, + 0x8196FB53u, + 0xBCF6D2E3u, + 0xFB56A833u, + 0xC6368183u, + 0x74165D93u, + 0x49767423u, + 0x0ED60EF3u, + 0x33B62743u, + 0xD1062710u, + 0xEC660EA0u, + 0xABC67470u, + 0x96A65DC0u, + 0x248681D0u, + 0x19E6A860u, + 0x5E46D2B0u, + 0x6326FB00u, + 0xE1766CD1u, + 0xDC164561u, + 0x9BB63FB1u, + 0xA6D61601u, + 0x14F6CA11u, + 0x2996E3A1u, + 0x6E369971u, + 0x5356B0C1u, + 0x70279F96u, + 0x4D47B626u, + 0x0AE7CCF6u, + 0x3787E546u, + 0x85A73956u, + 0xB8C710E6u, + 0xFF676A36u, + 0xC2074386u, + 0x4057D457u, + 0x7D37FDE7u, + 0x3A978737u, + 0x07F7AE87u, + 0xB5D77297u, + 0x88B75B27u, + 0xCF1721F7u, + 0xF2770847u, + 0x10C70814u, + 0x2DA721A4u, + 0x6A075B74u, + 0x576772C4u, + 0xE547AED4u, + 0xD8278764u, + 0x9F87FDB4u, + 0xA2E7D404u, + 0x20B743D5u, + 0x1DD76A65u, + 0x5A7710B5u, + 0x67173905u, + 0xD537E515u, + 0xE857CCA5u, + 0xAFF7B675u, + 0x92979FC5u, + 0xE915E8DBu, + 0xD475C16Bu, + 0x93D5BBBBu, + 0xAEB5920Bu, + 0x1C954E1Bu, + 0x21F567ABu, + 0x66551D7Bu, + 0x5B3534CBu, + 0xD965A31Au, + 0xE4058AAAu, + 0xA3A5F07Au, + 0x9EC5D9CAu, + 0x2CE505DAu, + 0x11852C6Au, + 0x562556BAu, + 0x6B457F0Au, + 0x89F57F59u, + 0xB49556E9u, + 0xF3352C39u, + 0xCE550589u, + 0x7C75D999u, + 0x4115F029u, + 0x06B58AF9u, + 0x3BD5A349u, + 0xB9853498u, + 0x84E51D28u, + 0xC34567F8u, + 0xFE254E48u, + 0x4C059258u, + 0x7165BBE8u, + 0x36C5C138u, + 0x0BA5E888u, + 0x28D4C7DFu, + 0x15B4EE6Fu, + 0x521494BFu, + 0x6F74BD0Fu, + 0xDD54611Fu, + 0xE03448AFu, + 0xA794327Fu, + 0x9AF41BCFu, + 0x18A48C1Eu, + 0x25C4A5AEu, + 0x6264DF7Eu, + 0x5F04F6CEu, + 0xED242ADEu, + 0xD044036Eu, + 0x97E479BEu, + 0xAA84500Eu, + 0x4834505Du, + 0x755479EDu, + 0x32F4033Du, + 0x0F942A8Du, + 0xBDB4F69Du, + 0x80D4DF2Du, + 0xC774A5FDu, + 0xFA148C4Du, + 0x78441B9Cu, + 0x4524322Cu, + 0x028448FCu, + 0x3FE4614Cu, + 0x8DC4BD5Cu, + 0xB0A494ECu, + 0xF704EE3Cu, + 0xCA64C78Cu, + }; + + constexpr static uint32_t CrcTable5[256] + { + 0x00000000u, + 0xCB5CD3A5u, + 0x4DC8A10Bu, + 0x869472AEu, + 0x9B914216u, + 0x50CD91B3u, + 0xD659E31Du, + 0x1D0530B8u, + 0xEC53826Du, + 0x270F51C8u, + 0xA19B2366u, + 0x6AC7F0C3u, + 0x77C2C07Bu, + 0xBC9E13DEu, + 0x3A0A6170u, + 0xF156B2D5u, + 0x03D6029Bu, + 0xC88AD13Eu, + 0x4E1EA390u, + 0x85427035u, + 0x9847408Du, + 0x531B9328u, + 0xD58FE186u, + 0x1ED33223u, + 0xEF8580F6u, + 0x24D95353u, + 0xA24D21FDu, + 0x6911F258u, + 0x7414C2E0u, + 0xBF481145u, + 0x39DC63EBu, + 0xF280B04Eu, + 0x07AC0536u, + 0xCCF0D693u, + 0x4A64A43Du, + 0x81387798u, + 0x9C3D4720u, + 0x57619485u, + 0xD1F5E62Bu, + 0x1AA9358Eu, + 0xEBFF875Bu, + 0x20A354FEu, + 0xA6372650u, + 0x6D6BF5F5u, + 0x706EC54Du, + 0xBB3216E8u, + 0x3DA66446u, + 0xF6FAB7E3u, + 0x047A07ADu, + 0xCF26D408u, + 0x49B2A6A6u, + 0x82EE7503u, + 0x9FEB45BBu, + 0x54B7961Eu, + 0xD223E4B0u, + 0x197F3715u, + 0xE82985C0u, + 0x23755665u, + 0xA5E124CBu, + 0x6EBDF76Eu, + 0x73B8C7D6u, + 0xB8E41473u, + 0x3E7066DDu, + 0xF52CB578u, + 0x0F580A6Cu, + 0xC404D9C9u, + 0x4290AB67u, + 0x89CC78C2u, + 0x94C9487Au, + 0x5F959BDFu, + 0xD901E971u, + 0x125D3AD4u, + 0xE30B8801u, + 0x28575BA4u, + 0xAEC3290Au, + 0x659FFAAFu, + 0x789ACA17u, + 0xB3C619B2u, + 0x35526B1Cu, + 0xFE0EB8B9u, + 0x0C8E08F7u, + 0xC7D2DB52u, + 0x4146A9FCu, + 0x8A1A7A59u, + 0x971F4AE1u, + 0x5C439944u, + 0xDAD7EBEAu, + 0x118B384Fu, + 0xE0DD8A9Au, + 0x2B81593Fu, + 0xAD152B91u, + 0x6649F834u, + 0x7B4CC88Cu, + 0xB0101B29u, + 0x36846987u, + 0xFDD8BA22u, + 0x08F40F5Au, + 0xC3A8DCFFu, + 0x453CAE51u, + 0x8E607DF4u, + 0x93654D4Cu, + 0x58399EE9u, + 0xDEADEC47u, + 0x15F13FE2u, + 0xE4A78D37u, + 0x2FFB5E92u, + 0xA96F2C3Cu, + 0x6233FF99u, + 0x7F36CF21u, + 0xB46A1C84u, + 0x32FE6E2Au, + 0xF9A2BD8Fu, + 0x0B220DC1u, + 0xC07EDE64u, + 0x46EAACCAu, + 0x8DB67F6Fu, + 0x90B34FD7u, + 0x5BEF9C72u, + 0xDD7BEEDCu, + 0x16273D79u, + 0xE7718FACu, + 0x2C2D5C09u, + 0xAAB92EA7u, + 0x61E5FD02u, + 0x7CE0CDBAu, + 0xB7BC1E1Fu, + 0x31286CB1u, + 0xFA74BF14u, + 0x1EB014D8u, + 0xD5ECC77Du, + 0x5378B5D3u, + 0x98246676u, + 0x852156CEu, + 0x4E7D856Bu, + 0xC8E9F7C5u, + 0x03B52460u, + 0xF2E396B5u, + 0x39BF4510u, + 0xBF2B37BEu, + 0x7477E41Bu, + 0x6972D4A3u, + 0xA22E0706u, + 0x24BA75A8u, + 0xEFE6A60Du, + 0x1D661643u, + 0xD63AC5E6u, + 0x50AEB748u, + 0x9BF264EDu, + 0x86F75455u, + 0x4DAB87F0u, + 0xCB3FF55Eu, + 0x006326FBu, + 0xF135942Eu, + 0x3A69478Bu, + 0xBCFD3525u, + 0x77A1E680u, + 0x6AA4D638u, + 0xA1F8059Du, + 0x276C7733u, + 0xEC30A496u, + 0x191C11EEu, + 0xD240C24Bu, + 0x54D4B0E5u, + 0x9F886340u, + 0x828D53F8u, + 0x49D1805Du, + 0xCF45F2F3u, + 0x04192156u, + 0xF54F9383u, + 0x3E134026u, + 0xB8873288u, + 0x73DBE12Du, + 0x6EDED195u, + 0xA5820230u, + 0x2316709Eu, + 0xE84AA33Bu, + 0x1ACA1375u, + 0xD196C0D0u, + 0x5702B27Eu, + 0x9C5E61DBu, + 0x815B5163u, + 0x4A0782C6u, + 0xCC93F068u, + 0x07CF23CDu, + 0xF6999118u, + 0x3DC542BDu, + 0xBB513013u, + 0x700DE3B6u, + 0x6D08D30Eu, + 0xA65400ABu, + 0x20C07205u, + 0xEB9CA1A0u, + 0x11E81EB4u, + 0xDAB4CD11u, + 0x5C20BFBFu, + 0x977C6C1Au, + 0x8A795CA2u, + 0x41258F07u, + 0xC7B1FDA9u, + 0x0CED2E0Cu, + 0xFDBB9CD9u, + 0x36E74F7Cu, + 0xB0733DD2u, + 0x7B2FEE77u, + 0x662ADECFu, + 0xAD760D6Au, + 0x2BE27FC4u, + 0xE0BEAC61u, + 0x123E1C2Fu, + 0xD962CF8Au, + 0x5FF6BD24u, + 0x94AA6E81u, + 0x89AF5E39u, + 0x42F38D9Cu, + 0xC467FF32u, + 0x0F3B2C97u, + 0xFE6D9E42u, + 0x35314DE7u, + 0xB3A53F49u, + 0x78F9ECECu, + 0x65FCDC54u, + 0xAEA00FF1u, + 0x28347D5Fu, + 0xE368AEFAu, + 0x16441B82u, + 0xDD18C827u, + 0x5B8CBA89u, + 0x90D0692Cu, + 0x8DD55994u, + 0x46898A31u, + 0xC01DF89Fu, + 0x0B412B3Au, + 0xFA1799EFu, + 0x314B4A4Au, + 0xB7DF38E4u, + 0x7C83EB41u, + 0x6186DBF9u, + 0xAADA085Cu, + 0x2C4E7AF2u, + 0xE712A957u, + 0x15921919u, + 0xDECECABCu, + 0x585AB812u, + 0x93066BB7u, + 0x8E035B0Fu, + 0x455F88AAu, + 0xC3CBFA04u, + 0x089729A1u, + 0xF9C19B74u, + 0x329D48D1u, + 0xB4093A7Fu, + 0x7F55E9DAu, + 0x6250D962u, + 0xA90C0AC7u, + 0x2F987869u, + 0xE4C4ABCCu, + }; + + constexpr static uint32_t CrcTable6[256] + + { + 0x00000000u, + 0xA6770BB4u, + 0x979F1129u, + 0x31E81A9Du, + 0xF44F2413u, + 0x52382FA7u, + 0x63D0353Au, + 0xC5A73E8Eu, + 0x33EF4E67u, + 0x959845D3u, + 0xA4705F4Eu, + 0x020754FAu, + 0xC7A06A74u, + 0x61D761C0u, + 0x503F7B5Du, + 0xF64870E9u, + 0x67DE9CCEu, + 0xC1A9977Au, + 0xF0418DE7u, + 0x56368653u, + 0x9391B8DDu, + 0x35E6B369u, + 0x040EA9F4u, + 0xA279A240u, + 0x5431D2A9u, + 0xF246D91Du, + 0xC3AEC380u, + 0x65D9C834u, + 0xA07EF6BAu, + 0x0609FD0Eu, + 0x37E1E793u, + 0x9196EC27u, + 0xCFBD399Cu, + 0x69CA3228u, + 0x582228B5u, + 0xFE552301u, + 0x3BF21D8Fu, + 0x9D85163Bu, + 0xAC6D0CA6u, + 0x0A1A0712u, + 0xFC5277FBu, + 0x5A257C4Fu, + 0x6BCD66D2u, + 0xCDBA6D66u, + 0x081D53E8u, + 0xAE6A585Cu, + 0x9F8242C1u, + 0x39F54975u, + 0xA863A552u, + 0x0E14AEE6u, + 0x3FFCB47Bu, + 0x998BBFCFu, + 0x5C2C8141u, + 0xFA5B8AF5u, + 0xCBB39068u, + 0x6DC49BDCu, + 0x9B8CEB35u, + 0x3DFBE081u, + 0x0C13FA1Cu, + 0xAA64F1A8u, + 0x6FC3CF26u, + 0xC9B4C492u, + 0xF85CDE0Fu, + 0x5E2BD5BBu, + 0x440B7579u, + 0xE27C7ECDu, + 0xD3946450u, + 0x75E36FE4u, + 0xB044516Au, + 0x16335ADEu, + 0x27DB4043u, + 0x81AC4BF7u, + 0x77E43B1Eu, + 0xD19330AAu, + 0xE07B2A37u, + 0x460C2183u, + 0x83AB1F0Du, + 0x25DC14B9u, + 0x14340E24u, + 0xB2430590u, + 0x23D5E9B7u, + 0x85A2E203u, + 0xB44AF89Eu, + 0x123DF32Au, + 0xD79ACDA4u, + 0x71EDC610u, + 0x4005DC8Du, + 0xE672D739u, + 0x103AA7D0u, + 0xB64DAC64u, + 0x87A5B6F9u, + 0x21D2BD4Du, + 0xE47583C3u, + 0x42028877u, + 0x73EA92EAu, + 0xD59D995Eu, + 0x8BB64CE5u, + 0x2DC14751u, + 0x1C295DCCu, + 0xBA5E5678u, + 0x7FF968F6u, + 0xD98E6342u, + 0xE86679DFu, + 0x4E11726Bu, + 0xB8590282u, + 0x1E2E0936u, + 0x2FC613ABu, + 0x89B1181Fu, + 0x4C162691u, + 0xEA612D25u, + 0xDB8937B8u, + 0x7DFE3C0Cu, + 0xEC68D02Bu, + 0x4A1FDB9Fu, + 0x7BF7C102u, + 0xDD80CAB6u, + 0x1827F438u, + 0xBE50FF8Cu, + 0x8FB8E511u, + 0x29CFEEA5u, + 0xDF879E4Cu, + 0x79F095F8u, + 0x48188F65u, + 0xEE6F84D1u, + 0x2BC8BA5Fu, + 0x8DBFB1EBu, + 0xBC57AB76u, + 0x1A20A0C2u, + 0x8816EAF2u, + 0x2E61E146u, + 0x1F89FBDBu, + 0xB9FEF06Fu, + 0x7C59CEE1u, + 0xDA2EC555u, + 0xEBC6DFC8u, + 0x4DB1D47Cu, + 0xBBF9A495u, + 0x1D8EAF21u, + 0x2C66B5BCu, + 0x8A11BE08u, + 0x4FB68086u, + 0xE9C18B32u, + 0xD82991AFu, + 0x7E5E9A1Bu, + 0xEFC8763Cu, + 0x49BF7D88u, + 0x78576715u, + 0xDE206CA1u, + 0x1B87522Fu, + 0xBDF0599Bu, + 0x8C184306u, + 0x2A6F48B2u, + 0xDC27385Bu, + 0x7A5033EFu, + 0x4BB82972u, + 0xEDCF22C6u, + 0x28681C48u, + 0x8E1F17FCu, + 0xBFF70D61u, + 0x198006D5u, + 0x47ABD36Eu, + 0xE1DCD8DAu, + 0xD034C247u, + 0x7643C9F3u, + 0xB3E4F77Du, + 0x1593FCC9u, + 0x247BE654u, + 0x820CEDE0u, + 0x74449D09u, + 0xD23396BDu, + 0xE3DB8C20u, + 0x45AC8794u, + 0x800BB91Au, + 0x267CB2AEu, + 0x1794A833u, + 0xB1E3A387u, + 0x20754FA0u, + 0x86024414u, + 0xB7EA5E89u, + 0x119D553Du, + 0xD43A6BB3u, + 0x724D6007u, + 0x43A57A9Au, + 0xE5D2712Eu, + 0x139A01C7u, + 0xB5ED0A73u, + 0x840510EEu, + 0x22721B5Au, + 0xE7D525D4u, + 0x41A22E60u, + 0x704A34FDu, + 0xD63D3F49u, + 0xCC1D9F8Bu, + 0x6A6A943Fu, + 0x5B828EA2u, + 0xFDF58516u, + 0x3852BB98u, + 0x9E25B02Cu, + 0xAFCDAAB1u, + 0x09BAA105u, + 0xFFF2D1ECu, + 0x5985DA58u, + 0x686DC0C5u, + 0xCE1ACB71u, + 0x0BBDF5FFu, + 0xADCAFE4Bu, + 0x9C22E4D6u, + 0x3A55EF62u, + 0xABC30345u, + 0x0DB408F1u, + 0x3C5C126Cu, + 0x9A2B19D8u, + 0x5F8C2756u, + 0xF9FB2CE2u, + 0xC813367Fu, + 0x6E643DCBu, + 0x982C4D22u, + 0x3E5B4696u, + 0x0FB35C0Bu, + 0xA9C457BFu, + 0x6C636931u, + 0xCA146285u, + 0xFBFC7818u, + 0x5D8B73ACu, + 0x03A0A617u, + 0xA5D7ADA3u, + 0x943FB73Eu, + 0x3248BC8Au, + 0xF7EF8204u, + 0x519889B0u, + 0x6070932Du, + 0xC6079899u, + 0x304FE870u, + 0x9638E3C4u, + 0xA7D0F959u, + 0x01A7F2EDu, + 0xC400CC63u, + 0x6277C7D7u, + 0x539FDD4Au, + 0xF5E8D6FEu, + 0x647E3AD9u, + 0xC209316Du, + 0xF3E12BF0u, + 0x55962044u, + 0x90311ECAu, + 0x3646157Eu, + 0x07AE0FE3u, + 0xA1D90457u, + 0x579174BEu, + 0xF1E67F0Au, + 0xC00E6597u, + 0x66796E23u, + 0xA3DE50ADu, + 0x05A95B19u, + 0x34414184u, + 0x92364A30u, + }; + + constexpr static uint32_t CrcTable7[256] + { + 0x00000000u, + 0xCCAA009Eu, + 0x4225077Du, + 0x8E8F07E3u, + 0x844A0EFAu, + 0x48E00E64u, + 0xC66F0987u, + 0x0AC50919u, + 0xD3E51BB5u, + 0x1F4F1B2Bu, + 0x91C01CC8u, + 0x5D6A1C56u, + 0x57AF154Fu, + 0x9B0515D1u, + 0x158A1232u, + 0xD92012ACu, + 0x7CBB312Bu, + 0xB01131B5u, + 0x3E9E3656u, + 0xF23436C8u, + 0xF8F13FD1u, + 0x345B3F4Fu, + 0xBAD438ACu, + 0x767E3832u, + 0xAF5E2A9Eu, + 0x63F42A00u, + 0xED7B2DE3u, + 0x21D12D7Du, + 0x2B142464u, + 0xE7BE24FAu, + 0x69312319u, + 0xA59B2387u, + 0xF9766256u, + 0x35DC62C8u, + 0xBB53652Bu, + 0x77F965B5u, + 0x7D3C6CACu, + 0xB1966C32u, + 0x3F196BD1u, + 0xF3B36B4Fu, + 0x2A9379E3u, + 0xE639797Du, + 0x68B67E9Eu, + 0xA41C7E00u, + 0xAED97719u, + 0x62737787u, + 0xECFC7064u, + 0x205670FAu, + 0x85CD537Du, + 0x496753E3u, + 0xC7E85400u, + 0x0B42549Eu, + 0x01875D87u, + 0xCD2D5D19u, + 0x43A25AFAu, + 0x8F085A64u, + 0x562848C8u, + 0x9A824856u, + 0x140D4FB5u, + 0xD8A74F2Bu, + 0xD2624632u, + 0x1EC846ACu, + 0x9047414Fu, + 0x5CED41D1u, + 0x299DC2EDu, + 0xE537C273u, + 0x6BB8C590u, + 0xA712C50Eu, + 0xADD7CC17u, + 0x617DCC89u, + 0xEFF2CB6Au, + 0x2358CBF4u, + 0xFA78D958u, + 0x36D2D9C6u, + 0xB85DDE25u, + 0x74F7DEBBu, + 0x7E32D7A2u, + 0xB298D73Cu, + 0x3C17D0DFu, + 0xF0BDD041u, + 0x5526F3C6u, + 0x998CF358u, + 0x1703F4BBu, + 0xDBA9F425u, + 0xD16CFD3Cu, + 0x1DC6FDA2u, + 0x9349FA41u, + 0x5FE3FADFu, + 0x86C3E873u, + 0x4A69E8EDu, + 0xC4E6EF0Eu, + 0x084CEF90u, + 0x0289E689u, + 0xCE23E617u, + 0x40ACE1F4u, + 0x8C06E16Au, + 0xD0EBA0BBu, + 0x1C41A025u, + 0x92CEA7C6u, + 0x5E64A758u, + 0x54A1AE41u, + 0x980BAEDFu, + 0x1684A93Cu, + 0xDA2EA9A2u, + 0x030EBB0Eu, + 0xCFA4BB90u, + 0x412BBC73u, + 0x8D81BCEDu, + 0x8744B5F4u, + 0x4BEEB56Au, + 0xC561B289u, + 0x09CBB217u, + 0xAC509190u, + 0x60FA910Eu, + 0xEE7596EDu, + 0x22DF9673u, + 0x281A9F6Au, + 0xE4B09FF4u, + 0x6A3F9817u, + 0xA6959889u, + 0x7FB58A25u, + 0xB31F8ABBu, + 0x3D908D58u, + 0xF13A8DC6u, + 0xFBFF84DFu, + 0x37558441u, + 0xB9DA83A2u, + 0x7570833Cu, + 0x533B85DAu, + 0x9F918544u, + 0x111E82A7u, + 0xDDB48239u, + 0xD7718B20u, + 0x1BDB8BBEu, + 0x95548C5Du, + 0x59FE8CC3u, + 0x80DE9E6Fu, + 0x4C749EF1u, + 0xC2FB9912u, + 0x0E51998Cu, + 0x04949095u, + 0xC83E900Bu, + 0x46B197E8u, + 0x8A1B9776u, + 0x2F80B4F1u, + 0xE32AB46Fu, + 0x6DA5B38Cu, + 0xA10FB312u, + 0xABCABA0Bu, + 0x6760BA95u, + 0xE9EFBD76u, + 0x2545BDE8u, + 0xFC65AF44u, + 0x30CFAFDAu, + 0xBE40A839u, + 0x72EAA8A7u, + 0x782FA1BEu, + 0xB485A120u, + 0x3A0AA6C3u, + 0xF6A0A65Du, + 0xAA4DE78Cu, + 0x66E7E712u, + 0xE868E0F1u, + 0x24C2E06Fu, + 0x2E07E976u, + 0xE2ADE9E8u, + 0x6C22EE0Bu, + 0xA088EE95u, + 0x79A8FC39u, + 0xB502FCA7u, + 0x3B8DFB44u, + 0xF727FBDAu, + 0xFDE2F2C3u, + 0x3148F25Du, + 0xBFC7F5BEu, + 0x736DF520u, + 0xD6F6D6A7u, + 0x1A5CD639u, + 0x94D3D1DAu, + 0x5879D144u, + 0x52BCD85Du, + 0x9E16D8C3u, + 0x1099DF20u, + 0xDC33DFBEu, + 0x0513CD12u, + 0xC9B9CD8Cu, + 0x4736CA6Fu, + 0x8B9CCAF1u, + 0x8159C3E8u, + 0x4DF3C376u, + 0xC37CC495u, + 0x0FD6C40Bu, + 0x7AA64737u, + 0xB60C47A9u, + 0x3883404Au, + 0xF42940D4u, + 0xFEEC49CDu, + 0x32464953u, + 0xBCC94EB0u, + 0x70634E2Eu, + 0xA9435C82u, + 0x65E95C1Cu, + 0xEB665BFFu, + 0x27CC5B61u, + 0x2D095278u, + 0xE1A352E6u, + 0x6F2C5505u, + 0xA386559Bu, + 0x061D761Cu, + 0xCAB77682u, + 0x44387161u, + 0x889271FFu, + 0x825778E6u, + 0x4EFD7878u, + 0xC0727F9Bu, + 0x0CD87F05u, + 0xD5F86DA9u, + 0x19526D37u, + 0x97DD6AD4u, + 0x5B776A4Au, + 0x51B26353u, + 0x9D1863CDu, + 0x1397642Eu, + 0xDF3D64B0u, + 0x83D02561u, + 0x4F7A25FFu, + 0xC1F5221Cu, + 0x0D5F2282u, + 0x079A2B9Bu, + 0xCB302B05u, + 0x45BF2CE6u, + 0x89152C78u, + 0x50353ED4u, + 0x9C9F3E4Au, + 0x121039A9u, + 0xDEBA3937u, + 0xD47F302Eu, + 0x18D530B0u, + 0x965A3753u, + 0x5AF037CDu, + 0xFF6B144Au, + 0x33C114D4u, + 0xBD4E1337u, + 0x71E413A9u, + 0x7B211AB0u, + 0xB78B1A2Eu, + 0x39041DCDu, + 0xF5AE1D53u, + 0x2C8E0FFFu, + 0xE0240F61u, + 0x6EAB0882u, + 0xA201081Cu, + 0xA8C40105u, + 0x646E019Bu, + 0xEAE10678u, + 0x264B06E6u, + }; + + uint32_t Crc32::Update(uint32_t crc32, ReadOnlySpan span) noexcept + { + Contract::Assert(std::endian::native == std::endian::little, "Little Endian expected"); + + crc32 ^= 0xFFFFFFFFU; + int offset = 0; + uint32_t runningLength = (span.Length() / 8) * 8; + uint32_t endBytes = span.Length() - runningLength; + + const uint32_t* words = span.Length() == 0 ? nullptr : reinterpret_cast(&span[0]); + for (uint32_t i = 0; i < runningLength / 8; i++) + { + crc32 ^= words[offset]; + offset += 1; + uint32_t term1 = CrcTable7[crc32 & 0x000000FF] ^ + CrcTable6[(crc32 >> 8) & 0x000000FF]; + + uint32_t term2 = crc32 >> 16; + crc32 = term1 ^ + CrcTable5[term2 & 0x000000FF] ^ + CrcTable4[(term2 >> 8) & 0x000000FF]; + + uint32_t term3 = words[offset]; + offset += 1; + term1 = CrcTable3[term3 & 0x000000FF] ^ + CrcTable2[(term3 >> 8) & 0x000000FF]; + + term2 = term3 >> 16; + crc32 ^= term1 ^ + CrcTable1[term2 & 0x000000FF] ^ + CrcTable0[(term2 >> 8) & 0x000000FF]; + } + + offset = runningLength; + for (uint32_t i = 0; i < endBytes; i++) + { + crc32 = CrcTable0[(crc32 ^ static_cast(span[offset++])) & 0x000000FF] ^ (crc32 >> 8); + } + + crc32 ^= 0xFFFFFFFFU; + return crc32; + } +} diff --git a/src/Core/Core.Native/Crc32.h b/src/Core/Core.Native/Crc32.h new file mode 100644 index 0000000..cafac56 --- /dev/null +++ b/src/Core/Core.Native/Crc32.h @@ -0,0 +1,37 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_core +{ + template struct ReadOnlySpan; + + // Licensed to the .NET Foundation under one or more agreements. + // The .NET Foundation licenses this file to you under the MIT license. + // See the LICENSE file in the project root for more information. + // + // File implements Slicing-by-8 CRC Generation, as described in + // "Novel Table Lookup-Based Algorithms for High-Performance CRC Generation" + // IEEE TRANSACTIONS ON COMPUTERS, VOL. 57, NO. 11, NOVEMBER 2008 + // + // Copyright(c) 2004-2006 Intel Corporation - All Rights Reserved + // + // This software program is licensed subject to the BSD License, + // available at http://www.opensource.org/licenses/bsd-license.html. + + /// CRC Generator. + struct Crc32 final + { + // Static Class + [[nodiscard]] Crc32() = delete; + ~Crc32() = delete; + Crc32(const Crc32& other) = delete; + Crc32(Crc32&& other) noexcept = delete; + Crc32& operator=(const Crc32& other) = delete; + Crc32& operator=(Crc32&& other) noexcept = delete; + + [[nodiscard]] static uint32_t Update(uint32_t crc32, ReadOnlySpan span) noexcept; + }; +} diff --git a/src/Core/Core.Native/DeepCompare.h b/src/Core/Core.Native/DeepCompare.h new file mode 100644 index 0000000..c4bfe57 --- /dev/null +++ b/src/Core/Core.Native/DeepCompare.h @@ -0,0 +1,61 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include + +namespace cdb_core +{ + template + struct DeepComparer final + { + bool operator()(const T& x, const T& y) const noexcept { return x == y; } + }; + + template + static bool DeepCompare(const T& x, const T& y) noexcept + { + return DeepComparer{}(x, y); + } + + template + struct DeepComparer> final + { + bool operator()(const std::vector& x, const std::vector& y) const noexcept + { + if (x.size() != y.size()) + { + return false; + } + + for (size_t i = 0; i < x.size(); i++) + { + if (!DeepCompare(x[i], y[i])) + { + return false; + } + } + + return true; + } + }; + + template + struct DeepComparer> final + { + bool operator()(const std::unique_ptr& x, const std::unique_ptr& y) const noexcept + { + if ((x == nullptr) && (y == nullptr)) + { + return true; + } + if ((x == nullptr) || (y == nullptr)) + { + return false; + } + return DeepCompare(*x, *y); + } + }; +} diff --git a/src/Core/Core.Native/Endian.h b/src/Core/Core.Native/Endian.h new file mode 100644 index 0000000..17a1637 --- /dev/null +++ b/src/Core/Core.Native/Endian.h @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_core +{ + struct Endian final + { + // Static Class + Endian() = delete; + ~Endian() = delete; + Endian(const Endian& other) = delete; + Endian(Endian&& other) noexcept = delete; + Endian& operator=(const Endian& other) = delete; + Endian& operator=(Endian&& other) noexcept = delete; + + constexpr static bool IsLittleEndian() noexcept; + constexpr static bool IsBigEndian() noexcept; + }; + + constexpr bool Endian::IsLittleEndian() noexcept + { + // TODO: this should use std::endian when we move to C++ v20. + return true; + } + + constexpr bool Endian::IsBigEndian() noexcept + { + return !Endian::IsLittleEndian(); + } +} diff --git a/src/Core/Core.Native/EqualityComparable.h b/src/Core/Core.Native/EqualityComparable.h new file mode 100644 index 0000000..a5d0d13 --- /dev/null +++ b/src/Core/Core.Native/EqualityComparable.h @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_core +{ + template + struct is_equality_comparable : std::false_type { }; + + /// + /// Check for operator==. + /// + // ReSharper disable once CppIdenticalOperandsInBinaryExpression + template + struct is_equality_comparable() == std::declval(), ( + void)0)>::type> : std::true_type { }; + + template + inline constexpr bool is_equality_comparable_v = is_equality_comparable::value; +} diff --git a/src/Core/Core.Native/Failure.cpp b/src/Core/Core.Native/Failure.cpp new file mode 100644 index 0000000..aa1cfee --- /dev/null +++ b/src/Core/Core.Native/Failure.cpp @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "Failure.h" + +namespace cdb_core +{ + const Failure Failure::None = Failure(); + + Failure& Failure::operator=(const Failure& other) + { + if (this == &other) + { + return *this; + } + + m_failureCode = other.m_failureCode; + m_hResult = other.m_hResult; + + return *this; + } +} diff --git a/src/Core/Core.Native/Failure.h b/src/Core/Core.Native/Failure.h new file mode 100644 index 0000000..35a6d04 --- /dev/null +++ b/src/Core/Core.Native/Failure.h @@ -0,0 +1,72 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "Contract.h" + +namespace cdb_core +{ + /// + /// Failure is a wrapper around Failed states in the system. + /// + /// + /// Encapsulates a Failure Code specific to an application + /// and an associated Win32 HResult if the failure originated + /// from a windows API call. + /// + class [[nodiscard]] Failure + { + public: + static const Failure None; + constexpr static uint32_t NoneCode = 0; + + template::value, int>> + Failure(const TEnum failureCode) noexcept; + template::value, int>> + Failure(const TEnum failureCode, const HRESULT hResult) noexcept; + ~Failure() = default; + Failure(const Failure&) = default; + Failure(Failure&&) = default; + + Failure& operator=(const Failure& other); + Failure& operator=(Failure&&) = default; + + [[nodiscard]] bool IsFailed() const noexcept; + + [[nodiscard]] uint32_t GetFailureCode() const noexcept; + + template::value, int>> + [[nodiscard]] TEnum GetFailureCode() const noexcept; + + [[nodiscard]] HRESULT GetHResult() const noexcept; + + private: + + constexpr explicit Failure() : m_failureCode(NoneCode), m_hResult{0} { } + + uint32_t m_failureCode; + HRESULT m_hResult; + }; + + template + Failure::Failure(const TEnum failureCode) noexcept : Failure(failureCode, S_OK) { } + + template + Failure::Failure(const TEnum failureCode, const HRESULT hResult) noexcept : + m_failureCode(static_cast(failureCode)), + m_hResult(hResult) + { + Contract::Requires(m_failureCode != 0, + "Failure constructor should not use FailureCode::None. Use Failure::None instead"); + } + + inline bool Failure::IsFailed() const noexcept { return m_failureCode != NoneCode; } + + inline uint32_t Failure::GetFailureCode() const noexcept { return m_failureCode; } + + template + TEnum Failure::GetFailureCode() const noexcept { return static_cast(m_failureCode); } + + inline HRESULT Failure::GetHResult() const noexcept { return m_hResult; } +} diff --git a/src/Core/Core.Native/HashCode.h b/src/Core/Core.Native/HashCode.h new file mode 100644 index 0000000..c4d5bc3 --- /dev/null +++ b/src/Core/Core.Native/HashCode.h @@ -0,0 +1,498 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +// ReSharper disable four CommentTypo +/* + + This xxHash64 implementation is based on the Netcore xxHash32 C# port which was in turn + based on the xxHash32 code published by Yann Collet: + + https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c + + xxHash - Fast Hash algorithm + Copyright (C) 2012-2016, Yann Collet + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - xxHash homepage: http://www.xxhash.com + - xxHash source repository : https://github.com/Cyan4973/xxHash + +*/ + +#include + +namespace cdb_core +{ + /// Utility functions for combining hash codes.. + struct HashCode final + { + template> + constexpr static size_t Combine(const T1& value1) + { + // Provide a way of diffusing bits from something with a limited + // input hash space. For example, many enums only have a few + // possible hashes, only using the bottom few bits of the code. Some + // collections are built on the assumption that hashes are spread + // over a larger space, so diffusing the bits may help the + // collection work more efficiently. + + uint64_t hc1 = static_cast(H1{}.operator()(value1)); + + uint64_t hash = MixEmptyState(); + hash += 8; + + hash = QueueRound(hash, hc1); + + hash = MixFinal(hash); + return static_cast(hash); + } + + template, typename H2 = std::hash> + constexpr static size_t Combine(const T1& value1, const T2& value2) + { + uint64_t hc1 = static_cast(H1{}.operator()(value1)); + uint64_t hc2 = static_cast(H2{}.operator()(value2)); + + uint64_t hash = MixEmptyState(); + hash += 16; + + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + + hash = MixFinal(hash); + return static_cast(hash); + } + + template< + typename T1, + typename T2, + typename T3, + typename H1 = std::hash, + typename H2 = std::hash, + typename H3 = std::hash> + constexpr static size_t Combine(const T1& value1, const T2& value2, const T3& value3) + { + uint64_t hc1 = static_cast(H1{}.operator()(value1)); + uint64_t hc2 = static_cast(H2{}.operator()(value2)); + uint64_t hc3 = static_cast(H3{}.operator()(value3)); + + uint64_t hash = MixEmptyState(); + hash += 24; + + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = QueueRound(hash, hc3); + + hash = MixFinal(hash); + return static_cast(hash); + } + + template< + typename T1, + typename T2, + typename T3, + typename T4, + typename H1 = std::hash, + typename H2 = std::hash, + typename H3 = std::hash, + typename H4 = std::hash> + constexpr static size_t Combine(const T1& value1, const T2& value2, const T3& value3, const T4& value4) + { + uint64_t hc1 = static_cast(H1{}.operator()(value1)); + uint64_t hc2 = static_cast(H2{}.operator()(value2)); + uint64_t hc3 = static_cast(H3{}.operator()(value3)); + uint64_t hc4 = static_cast(H4{}.operator()(value4)); + + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t v4; + Initialize(v1, v2, v3, v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint64_t hash = MixState(v1, v2, v3, v4); + hash += 32; + + hash = MixFinal(hash); + return static_cast(hash); + } + + template< + typename T1, + typename T2, + typename T3, + typename T4, + typename T5, + typename H1 = std::hash, + typename H2 = std::hash, + typename H3 = std::hash, + typename H4 = std::hash, + typename H5 = std::hash> + constexpr static size_t Combine(const T1& value1, const T2& value2, const T3& value3, const T4& value4, + const T5& value5) + { + uint64_t hc1 = static_cast(H1{}.operator()(value1)); + uint64_t hc2 = static_cast(H2{}.operator()(value2)); + uint64_t hc3 = static_cast(H3{}.operator()(value3)); + uint64_t hc4 = static_cast(H4{}.operator()(value4)); + uint64_t hc5 = static_cast(H5{}.operator()(value5)); + + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t v4; + Initialize(v1, v2, v3, v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint64_t hash = MixState(v1, v2, v3, v4); + hash += 40; + + hash = QueueRound(hash, hc5); + + hash = MixFinal(hash); + return static_cast(hash); + } + + template< + typename T1, + typename T2, + typename T3, + typename T4, + typename T5, + typename T6, + typename H1 = std::hash, + typename H2 = std::hash, + typename H3 = std::hash, + typename H4 = std::hash, + typename H5 = std::hash, + typename H6 = std::hash> + constexpr static size_t Combine(const T1& value1, const T2& value2, const T3& value3, const T4& value4, + const T5& value5, const T6& value6) + { + uint64_t hc1 = static_cast(H1{}.operator()(value1)); + uint64_t hc2 = static_cast(H2{}.operator()(value2)); + uint64_t hc3 = static_cast(H3{}.operator()(value3)); + uint64_t hc4 = static_cast(H4{}.operator()(value4)); + uint64_t hc5 = static_cast(H5{}.operator()(value5)); + uint64_t hc6 = static_cast(H6{}.operator()(value6)); + + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t v4; + Initialize(v1, v2, v3, v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint64_t hash = MixState(v1, v2, v3, v4); + hash += 48; + + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + + hash = MixFinal(hash); + return static_cast(hash); + } + + template< + typename T1, + typename T2, + typename T3, + typename T4, + typename T5, + typename T6, + typename T7, + typename H1 = std::hash, + typename H2 = std::hash, + typename H3 = std::hash, + typename H4 = std::hash, + typename H5 = std::hash, + typename H6 = std::hash, + typename H7 = std::hash> + constexpr static size_t Combine(const T1& value1, const T2& value2, const T3& value3, const T4& value4, + const T5& value5, const T6& value6, const T7& value7) + { + uint64_t hc1 = static_cast(H1{}.operator()(value1)); + uint64_t hc2 = static_cast(H2{}.operator()(value2)); + uint64_t hc3 = static_cast(H3{}.operator()(value3)); + uint64_t hc4 = static_cast(H4{}.operator()(value4)); + uint64_t hc5 = static_cast(H5{}.operator()(value5)); + uint64_t hc6 = static_cast(H6{}.operator()(value6)); + uint64_t hc7 = static_cast(H7{}.operator()(value7)); + + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t v4; + Initialize(v1, v2, v3, v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint64_t hash = MixState(v1, v2, v3, v4); + hash += 56; + + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = QueueRound(hash, hc7); + + hash = MixFinal(hash); + return static_cast(hash); + } + + template< + typename T1, + typename T2, + typename T3, + typename T4, + typename T5, + typename T6, + typename T7, + typename T8, + typename H1 = std::hash, + typename H2 = std::hash, + typename H3 = std::hash, + typename H4 = std::hash, + typename H5 = std::hash, + typename H6 = std::hash, + typename H7 = std::hash, + typename H8 = std::hash> + constexpr static size_t Combine(const T1& value1, const T2& value2, const T3& value3, const T4& value4, + const T5& value5, const T6& value6, const T7& value7, const T8& value8) + { + uint64_t hc1 = static_cast(H1{}.operator()(value1)); + uint64_t hc2 = static_cast(H2{}.operator()(value2)); + uint64_t hc3 = static_cast(H3{}.operator()(value3)); + uint64_t hc4 = static_cast(H4{}.operator()(value4)); + uint64_t hc5 = static_cast(H5{}.operator()(value5)); + uint64_t hc6 = static_cast(H6{}.operator()(value6)); + uint64_t hc7 = static_cast(H7{}.operator()(value7)); + uint64_t hc8 = static_cast(H8{}.operator()(value8)); + + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t v4; + Initialize(v1, v2, v3, v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + v1 = Round(v1, hc5); + v2 = Round(v2, hc6); + v3 = Round(v3, hc7); + v4 = Round(v4, hc8); + + uint64_t hash = MixState(v1, v2, v3, v4); + hash += 64; + + hash = MixFinal(hash); + return static_cast(hash); + } + + template> + constexpr void Add(const T1& value) noexcept + { + AddHash(static_cast(H1{}.operator()(value))); + } + + constexpr void AddHash(uint64_t value) noexcept + { + // The original xxHash works as follows: + // 0. Initialize immediately. We can't do this in a struct (no + // default ctor). + // 1. Accumulate blocks of length 32 (4 uints) into 4 accumulators. + // 2. Accumulate remaining blocks of length 4 (1 uint64_t) into the + // hash. + // 3. Accumulate remaining blocks of length 1 into the hash. + + // There is no need for #3 as this type only accepts int64s. _queue1, + // _queue2 and _queue3 are basically a buffer so that when + // ToHashCode is called we can execute #2 correctly. + + // We need to initialize the xxHash64 state (_v1 to _v4) lazily (see + // #0) and the last place that can be done if you look at the + // original code is just before the first block of 32 bytes is mixed + // in. The xxHash64 state is never used for streams containing fewer + // than 64 bytes. + + // To see what's really going on here, have a look at the Combine + // methods. + + // Storing the value of _length locally shaves of quite a few bytes + // in the resulting machine code. + uint64_t previousLength = m_length++; + uint64_t position = previousLength % 4; + + // Switch can't be inlined. + + if (position == 0) + { + m_queue1 = value; + } + else if (position == 1) + { + m_queue2 = value; + } + else if (position == 2) + { + m_queue3 = value; + } + else // position == 3 + { + if (previousLength == 3) + { + Initialize(m_v1, m_v2, m_v3, m_v4); + } + + m_v1 = Round(m_v1, m_queue1); + m_v2 = Round(m_v2, m_queue2); + m_v3 = Round(m_v3, m_queue3); + m_v4 = Round(m_v4, value); + } + } + + [[nodiscard]] constexpr size_t ToHashCode() const noexcept + { + // Storing the value of _length locally shaves of quite a few bytes + // in the resulting machine code. + uint64_t length = m_length; + + // position refers to the *next* queue position in this method, so + // position == 1 means that _queue1 is populated; _queue2 would have + // been populated on the next call to Add. + uint64_t position = length % 4; + + // If the length is less than 4, _v1 to _v4 don't contain anything + // yet. xxHash64 treats this differently. + + uint64_t hash = length < 4 ? MixEmptyState() : MixState(m_v1, m_v2, m_v3, m_v4); + + // _length is incremented once per Add(uint64_t) and is therefore 8 + // times too small (xxHash length is in bytes, not int64s). + + hash += length * 8; + + // Mix what remains in the queue + + // Switch can't be inlined right now, so use as few branches as + // possible by manually excluding impossible scenarios (position > 1 + // is always false if position is not > 0). + if (position > 0) + { + hash = QueueRound(hash, m_queue1); + if (position > 1) + { + hash = QueueRound(hash, m_queue2); + if (position > 2) + { + hash = QueueRound(hash, m_queue3); + } + } + } + + hash = MixFinal(hash); + return static_cast(hash); + } + + private: + + constexpr static void Initialize(uint64_t& v1, uint64_t& v2, uint64_t& v3, uint64_t& v4) + { + v1 = s_seed + Prime1 + Prime2; + v2 = s_seed + Prime2; + v3 = s_seed; + v4 = s_seed - Prime1; + } + + constexpr static uint64_t RotateLeft(uint64_t x, int r) + { + return ((x << r) | (x >> (64 - r))); + } + + constexpr static uint64_t Round(uint64_t hash, uint64_t input) + { + return RotateLeft(hash + input * Prime2, 31) * Prime1; + } + + constexpr static uint64_t QueueRound(uint64_t hash, uint64_t queuedValue) + { + return RotateLeft(hash ^ Round(0, queuedValue), 27) * Prime1 + Prime4; + } + + constexpr static uint64_t MixState(uint64_t v1, uint64_t v2, uint64_t v3, uint64_t v4) + { + return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); + } + + constexpr static uint64_t MixEmptyState() + { + return s_seed + Prime5; + } + + constexpr static uint64_t MixFinal(uint64_t hash) + { + hash ^= hash >> 33; + hash *= Prime2; + hash ^= hash >> 29; + hash *= Prime3; + hash ^= hash >> 32; + return hash; + } + + private: + static constexpr uint64_t s_seed = 0x9FB21C651E98DF25ULL; + + static constexpr uint64_t Prime1 = 11400714785074694791ULL; + static constexpr uint64_t Prime2 = 14029467366897019727ULL; + static constexpr uint64_t Prime3 = 1609587929392839161ULL; + static constexpr uint64_t Prime4 = 9650029242287828579ULL; + static constexpr uint64_t Prime5 = 2870177450012600261ULL; + + uint64_t m_v1{0}, m_v2{0}, m_v3{0}, m_v4{0}; + uint64_t m_queue1{0}, m_queue2{0}, m_queue3{0}; + uint64_t m_length{0}; + }; +} diff --git a/src/Core/Core.Native/Hashable.h b/src/Core/Core.Native/Hashable.h new file mode 100644 index 0000000..9148505 --- /dev/null +++ b/src/Core/Core.Native/Hashable.h @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_core +{ + /// Check for a GetHashCode method. + /// Method must have this signature: + /// size_t GetHashCode() const noexcept + /// + template + struct is_hashable + { + private: + template + constexpr static typename std::is_same::type Check(T*) + { + return std::true_type{}; + } + + template + constexpr static std::false_type Check(...) { return std::false_type{}; } + + public: + constexpr static bool value = decltype( + Check>>>(nullptr))::value; + }; + + template + inline constexpr bool is_hashable_v = is_hashable::value; +} diff --git a/src/Core/Core.Native/IsAllSame.h b/src/Core/Core.Native/IsAllSame.h new file mode 100644 index 0000000..ead3575 --- /dev/null +++ b/src/Core/Core.Native/IsAllSame.h @@ -0,0 +1,25 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_core +{ + template + struct is_all_same + { + constexpr static bool value = + std::is_same::value && is_all_same::value; + }; + + template + struct is_all_same + { + constexpr static bool value = std::is_same::value; + }; + + template + inline constexpr bool is_all_same_v = is_all_same::value; +} diff --git a/src/Core/Core.Native/Memory.h b/src/Core/Core.Native/Memory.h new file mode 100644 index 0000000..c368f1b --- /dev/null +++ b/src/Core/Core.Native/Memory.h @@ -0,0 +1,171 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include + +#include "Blittable.h" +#include "Contract.h" +#include "Span.h" + +namespace cdb_core +{ + template struct ReadOnlyMemory; + + /// + /// Memory represents a owned (possibly shared) contiguous region of arbitrary memory. + /// + template + struct Memory final + { + static_assert(is_blittable_v, "Only blittable types can be used in a Memory."); + + constexpr Memory() noexcept; + ~Memory() noexcept = default; + explicit constexpr Memory(uint32_t length) noexcept; + explicit constexpr Memory(const ReadOnlySpan& buffer) noexcept; + constexpr Memory(T* buffer, uint32_t length) noexcept; + template, + std::is_nothrow_invocable>, int> = 0> + Memory(T* buffer, uint32_t length, Destructor destructor); + + Memory(const Memory&) noexcept = default; + Memory(Memory&&) noexcept = default; + Memory& operator=(const Memory& other) noexcept = default; + Memory& operator=(Memory&& other) noexcept = default; + + /// The number of items in the Memory. + [[nodiscard]] uint32_t Length() const noexcept; + + [[nodiscard]] operator ReadOnlyMemory() const noexcept; + + [[nodiscard]] ReadOnlySpan AsSpan() const noexcept; + + [[nodiscard]] Span AsSpan() noexcept; + + /// Returns true if length is 0. + [[nodiscard]] bool IsEmpty() const noexcept; + + [[nodiscard]] ReadOnlyMemory Slice(uint32_t start) const noexcept; + [[nodiscard]] Memory Slice(uint32_t start) noexcept; + + [[nodiscard]] ReadOnlyMemory Slice(uint32_t start, uint32_t length) const noexcept; + [[nodiscard]] Memory Slice(uint32_t start, uint32_t length) noexcept; + + bool operator==(const Memory& rhs) const noexcept; + bool operator!=(const Memory& rhs) const noexcept; + + private: + Memory(std::shared_ptr buffer, uint32_t index, uint32_t length) noexcept; + + std::shared_ptr m_buffer; + uint32_t m_index; + uint32_t m_length; + }; + + template + constexpr Memory::Memory() noexcept : + m_buffer(), + m_index(0), + m_length(0) {} + + template + constexpr Memory::Memory(uint32_t length) noexcept : + m_buffer(reinterpret_cast(new T[length])), + m_index(0), + m_length(length) {} + + template + constexpr Memory::Memory(const ReadOnlySpan& buffer) noexcept : + m_buffer(buffer.Length() == 0 ? nullptr : reinterpret_cast(new T[buffer.Length()])), + m_index(0), + m_length(buffer.Length()) + { + buffer.CopyTo(AsSpan()); + } + + template + constexpr Memory::Memory(T* buffer, uint32_t length) noexcept : + m_buffer(reinterpret_cast(buffer)), + m_index(0), + m_length(length) {} + + template + template, + std::is_nothrow_invocable>, int>> + Memory::Memory(T* buffer, uint32_t length, Destructor destructor) : m_buffer(reinterpret_cast(buffer), + destructor), + m_index(0), + m_length(length) {} + + template + Memory::Memory(std::shared_ptr buffer, uint32_t index, uint32_t length) noexcept : + m_buffer(std::move(buffer)), + m_index(index), + m_length(length) {} + + template + uint32_t Memory::Length() const noexcept { return m_length; } + + template + Memory::operator ReadOnlyMemory() const noexcept + { + return ReadOnlyMemory(reinterpret_cast(m_buffer.get()) + m_index, m_length); + } + + template + ReadOnlySpan Memory::AsSpan() const noexcept + { + return ReadOnlySpan(reinterpret_cast(m_buffer.get()) + m_index, m_length); + } + + template + Span Memory::AsSpan() noexcept + { + return Span(reinterpret_cast(m_buffer.get()) + m_index, m_length); + } + + template + bool Memory::IsEmpty() const noexcept { return m_length == 0; } + + template + ReadOnlyMemory Memory::Slice(uint32_t start, uint32_t length) const noexcept + { + Contract::Requires(static_cast(start) + static_cast(length) <= static_cast(m_length)); + return ReadOnlyMemory(m_buffer, m_index + start, length); + } + + template + Memory Memory::Slice(uint32_t start, uint32_t length) noexcept + { + Contract::Requires(static_cast(start) + static_cast(length) <= static_cast(m_length)); + return Memory(m_buffer, m_index + start, length); + } + + template + ReadOnlyMemory Memory::Slice(uint32_t start) const noexcept + { + Contract::Requires(start <= m_length); + return ReadOnlyMemory(m_buffer, m_index + start, m_length - start); + } + + template + Memory Memory::Slice(uint32_t start) noexcept + { + Contract::Requires(start <= m_length); + return Memory(m_buffer, m_index + start, m_length - start); + } + + template + bool Memory::operator==(const Memory& rhs) const noexcept + { + return m_buffer == rhs.m_buffer && m_index == rhs.m_index && m_length == rhs.m_length; + } + + template + bool Memory::operator!=(const Memory& rhs) const noexcept { return !operator==(rhs); } +} diff --git a/src/Core/Core.Native/MemoryMarshal.h b/src/Core/Core.Native/MemoryMarshal.h new file mode 100644 index 0000000..e8993ca --- /dev/null +++ b/src/Core/Core.Native/MemoryMarshal.h @@ -0,0 +1,62 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "Memory.h" +#include "Span.h" +#include "ReadOnlySpan.h" + +namespace cdb_core +{ + struct MemoryMarshal final + { + MemoryMarshal() = delete; + + template + static Span Cast(Span source) + { + const size_t lengthU = (source.m_length * sizeof(T)) / sizeof(U); + return Span(reinterpret_cast(source.m_pointer), static_cast(lengthU)); + } + + template + static void Write(Span destination, const T& value) + { + static_assert(is_blittable_v, "Only blittable types can be used in a Span."); + Contract::Requires(destination.m_length >= sizeof(T)); + *reinterpret_cast(destination.m_pointer) = value; + } + + template + static const T& Read(const Span& source) + { + static_assert(is_blittable_v, "Only blittable types can be used in a Span."); + Contract::Requires(source.m_length >= sizeof(T)); + return *reinterpret_cast(source.m_pointer); + } + + template + static ReadOnlySpan Cast(const ReadOnlySpan source) + { + const size_t lengthU = (source.m_length * sizeof(T)) / sizeof(U); + return ReadOnlySpan(reinterpret_cast(source.m_pointer), static_cast(lengthU)); + } + + template + static const T& Read(const ReadOnlySpan& source) + { + static_assert(is_blittable_v, "Only blittable types can be used in a ReadOnlySpan."); + Contract::Requires(source.m_length >= sizeof(T)); + return *reinterpret_cast(source.m_pointer); + } + + template + [[nodiscard]] static Memory AsMemory(const ReadOnlyMemory& source) noexcept + { + static_assert(is_blittable_v, "Only blittable types can be used in a ReadOnlySpan."); + return Memory(reinterpret_cast(source.m_buffer.get()) + source.m_index, source.m_length); + } +}; +} diff --git a/src/Core/Core.Native/Microsoft.Azure.Cosmos.Core.Native.vcxproj b/src/Core/Core.Native/Microsoft.Azure.Cosmos.Core.Native.vcxproj new file mode 100644 index 0000000..17a7f5d --- /dev/null +++ b/src/Core/Core.Native/Microsoft.Azure.Cosmos.Core.Native.vcxproj @@ -0,0 +1,107 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA} + Win32Proj + cdb_core + StaticLibrary + 10.0 + + + + true + + + false + + + + true + + + false + + + + Use + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + pch.h + + + Windows + + + rpcrt4.lib;%(AdditionalDependencies) + + + + + Use + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + pch.h + + + Windows + + + rpcrt4.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + \ No newline at end of file diff --git a/src/Core/Core.Native/ReadOnlyMemory.h b/src/Core/Core.Native/ReadOnlyMemory.h new file mode 100644 index 0000000..9df9e9e --- /dev/null +++ b/src/Core/Core.Native/ReadOnlyMemory.h @@ -0,0 +1,132 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include + +#include "Blittable.h" +#include "Contract.h" +#include "ReadOnlySpan.h" + +namespace cdb_core +{ + struct MemoryMarshal; + + /// + /// ReadOnlyMemory represents a read-only view of a owned (possibly shared) contiguous region of arbitrary memory. + /// + template + struct ReadOnlyMemory final + { + static_assert(is_blittable_v, "Only blittable types can be used in a ReadOnlyMemory."); + + constexpr ReadOnlyMemory() noexcept; + ~ReadOnlyMemory() noexcept = default; + explicit constexpr ReadOnlyMemory(const ReadOnlySpan& buffer) noexcept; + template, + std::is_nothrow_invocable>, int> = 0> + ReadOnlyMemory(T* buffer, uint32_t length, Destructor destructor); + + ReadOnlyMemory(const ReadOnlyMemory&) noexcept = default; + ReadOnlyMemory(ReadOnlyMemory&&) noexcept = default; + ReadOnlyMemory& operator=(const ReadOnlyMemory& other) noexcept = default; + ReadOnlyMemory& operator=(ReadOnlyMemory&& other) noexcept = default; + + /// The number of items in the ReadOnlyMemory. + [[nodiscard]] + uint32_t Length() const noexcept; + + [[nodiscard]] + ReadOnlySpan AsSpan() const noexcept; + + /// Returns true if length is 0. + [[nodiscard]] + bool IsEmpty() const noexcept; + + [[nodiscard]] + ReadOnlyMemory Slice(uint32_t start) const noexcept; + + [[nodiscard]] + ReadOnlyMemory Slice(uint32_t start, uint32_t length) const noexcept; + + bool operator==(const ReadOnlyMemory& rhs) const noexcept; + bool operator!=(const ReadOnlyMemory& rhs) const noexcept; + + private: + friend struct Memory; + friend struct MemoryMarshal; + ReadOnlyMemory(std::shared_ptr buffer, uint32_t index, uint32_t length) noexcept; + + std::shared_ptr m_buffer; + uint32_t m_index; + uint32_t m_length; + }; + + template + constexpr ReadOnlyMemory::ReadOnlyMemory() noexcept : + m_buffer(), + m_index(0), + m_length(0) {} + + template + constexpr ReadOnlyMemory::ReadOnlyMemory(const ReadOnlySpan& buffer) noexcept : + m_buffer(buffer.Length() == 0 ? nullptr : reinterpret_cast(new T[buffer.Length()])), + m_index(0), + m_length(buffer.Length()) + { + buffer.CopyTo(AsSpan()); + } + + template + template, + std::is_nothrow_invocable>, int>> + ReadOnlyMemory::ReadOnlyMemory(T* buffer, uint32_t length, Destructor destructor) : m_buffer(reinterpret_cast(buffer), + destructor), + m_index(0), + m_length(length) {} + + template + ReadOnlyMemory::ReadOnlyMemory(std::shared_ptr buffer, uint32_t index, uint32_t length) noexcept : + m_buffer(std::move(buffer)), + m_index(index), + m_length(length) {} + + template + uint32_t ReadOnlyMemory::Length() const noexcept { return m_length; } + + template + ReadOnlySpan ReadOnlyMemory::AsSpan() const noexcept + { + return ReadOnlySpan(reinterpret_cast(m_buffer.get()) + m_index, m_length); + } + + template + bool ReadOnlyMemory::IsEmpty() const noexcept { return m_length == 0; } + + template + ReadOnlyMemory ReadOnlyMemory::Slice(uint32_t start, uint32_t length) const noexcept + { + Contract::Requires(static_cast(start) + static_cast(length) <= static_cast(m_length)); + return ReadOnlyMemory(m_buffer, m_index + start, length); + } + + template + ReadOnlyMemory ReadOnlyMemory::Slice(uint32_t start) const noexcept + { + Contract::Requires(start <= m_length); + return ReadOnlyMemory(m_buffer, m_index + start, m_length - start); + } + + template + bool ReadOnlyMemory::operator==(const ReadOnlyMemory& rhs) const noexcept + { + return m_buffer == rhs.m_buffer && m_index == rhs.m_index && m_length == rhs.m_length; + } + + template + bool ReadOnlyMemory::operator!=(const ReadOnlyMemory& rhs) const noexcept { return !operator==(rhs); } +} diff --git a/src/Core/Core.Native/ReadOnlySpan.h b/src/Core/Core.Native/ReadOnlySpan.h new file mode 100644 index 0000000..520fef9 --- /dev/null +++ b/src/Core/Core.Native/ReadOnlySpan.h @@ -0,0 +1,173 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include +#include +#include "Blittable.h" + +namespace cdb_core +{ + template struct Span; + + /// A readonly, bounds-checked, pointer to an buffer. + /// + template + struct ReadOnlySpan + { + static_assert(is_blittable_v, "Only blittable types can be used in a ReadOnlySpan."); + + constexpr ReadOnlySpan() noexcept; + ~ReadOnlySpan() noexcept = default; + constexpr ReadOnlySpan(const T* pointer, uint32_t length) noexcept; + template constexpr ReadOnlySpan(const std::array&) noexcept; + constexpr ReadOnlySpan(const std::vector&) noexcept; + constexpr ReadOnlySpan(const Span&) noexcept; + ReadOnlySpan(const ReadOnlySpan&) noexcept = default; + ReadOnlySpan(ReadOnlySpan&&) noexcept = default; + ReadOnlySpan& operator=(const ReadOnlySpan& other) noexcept = default; + ReadOnlySpan& operator=(ReadOnlySpan&& other) noexcept = default; + + /// Returns a reference to specified element of the ReadOnlySpan. + constexpr const T& operator[](uint32_t index) const noexcept; + + /// The number of items in the span. + [[nodiscard]] + constexpr uint32_t Length() const noexcept; + + /// Returns true if Length is 0. + [[nodiscard]] + constexpr bool IsEmpty() const noexcept; + + bool operator==(const ReadOnlySpan& rhs) const noexcept; + bool operator!=(const ReadOnlySpan& rhs) const noexcept; + + [[nodiscard]] + ReadOnlySpan Slice(uint32_t start) const noexcept; + + [[nodiscard]] + ReadOnlySpan Slice(uint32_t start, uint32_t length) const noexcept; + + void CopyTo(Span destination) const noexcept; + + template>> + [[nodiscard]] constexpr int SequenceCompareTo(ReadOnlySpan other) const noexcept; + + template>> + [[nodiscard]] constexpr bool SequenceEqual(ReadOnlySpan other) const noexcept; + + struct ReadOnlySpanIter + { + bool operator==(const ReadOnlySpanIter& other) const noexcept { return m_pointer == other.m_pointer; } + bool operator!=(const ReadOnlySpanIter& other) const noexcept { return m_pointer != other.m_pointer; } + + const ReadOnlySpanIter& operator++() noexcept + { + ++m_pointer; + return *this; + } + + const T& operator*() const noexcept { return *m_pointer; } + private: + explicit ReadOnlySpanIter(const T* p) : m_pointer(p) {} + friend struct ReadOnlySpan; + const T* m_pointer; + }; + + [[nodiscard]] ReadOnlySpanIter begin() const noexcept { return ReadOnlySpanIter{m_pointer}; } + [[nodiscard]] ReadOnlySpanIter end() const noexcept { return ReadOnlySpanIter{m_pointer + m_length}; } + + private: + friend struct MemoryMarshal; + template friend struct Span; + + const T* m_pointer; + uint32_t m_length; + }; + + template constexpr ReadOnlySpan::ReadOnlySpan() noexcept : m_pointer(nullptr), m_length(0) {} + + template constexpr ReadOnlySpan::ReadOnlySpan(const T* pointer, uint32_t length) noexcept : + m_pointer(pointer), + m_length(length) { } + + template template constexpr ReadOnlySpan::ReadOnlySpan(const std::array& arr) noexcept : + m_pointer{arr.data()}, + m_length{static_cast(arr.size())} { } + + // Class Template Argument Deduction (CTAD) hint + template ReadOnlySpan(std::array&) noexcept -> ReadOnlySpan; + + template constexpr ReadOnlySpan::ReadOnlySpan(const std::vector& arr) noexcept : + m_pointer{arr.data()}, + m_length{static_cast(arr.size())} { } + + // Class Template Argument Deduction (CTAD) hint + template ReadOnlySpan(const std::vector& arr) noexcept -> ReadOnlySpan; + + template constexpr ReadOnlySpan::ReadOnlySpan(const Span& span) noexcept : + m_pointer(span.m_pointer), + m_length(span.m_length) { } + + // Class Template Argument Deduction (CTAD) hint + template ReadOnlySpan(const Span& span) noexcept -> ReadOnlySpan; + + template constexpr const T& ReadOnlySpan::operator[](uint32_t index) const noexcept + { + Contract::Assert(index < m_length); + return m_pointer[index]; + } + + template constexpr uint32_t ReadOnlySpan::Length() const noexcept { return m_length; } + + template constexpr bool ReadOnlySpan::IsEmpty() const noexcept { return m_length == 0; } + + template + bool ReadOnlySpan::operator==(const ReadOnlySpan& rhs) const noexcept + { + return m_pointer == rhs.m_pointer && m_length == rhs.m_length; + } + + template + bool ReadOnlySpan::operator!=(const ReadOnlySpan& rhs) const noexcept { return !operator==(rhs); } + + template + ReadOnlySpan ReadOnlySpan::Slice(uint32_t start, uint32_t length) const noexcept + { + Contract::Requires(static_cast(start) + static_cast(length) <= static_cast(m_length)); + return ReadOnlySpan(m_pointer + start, length); + } + + template + ReadOnlySpan ReadOnlySpan::Slice(uint32_t start) const noexcept + { + Contract::Requires(start <= m_length); + return ReadOnlySpan(m_pointer + start, m_length - start); + } + + template void ReadOnlySpan::CopyTo(Span destination) const noexcept + { + Contract::Requires(m_length <= destination.m_length); + ::memmove_s(static_cast(destination.m_pointer), destination.m_length * sizeof(T), + static_cast(const_cast(m_pointer)), m_length * sizeof(T)); + } + + template + template + constexpr int ReadOnlySpan::SequenceCompareTo(ReadOnlySpan other) const noexcept + { + uint32_t len = std::min(m_length, other.m_length); + int cmp = ::memcmp(m_pointer, other.m_pointer, len * sizeof(T)); + return (cmp == 0) ? static_cast(m_length - other.m_length) : cmp; + } + + template + template + constexpr bool ReadOnlySpan::SequenceEqual(ReadOnlySpan other) const noexcept + { + return m_length == other.m_length && + ::memcmp(m_pointer, other.m_pointer, m_length * sizeof(T)) == 0; + } +} diff --git a/src/Core/Core.Native/Result.h b/src/Core/Core.Native/Result.h new file mode 100644 index 0000000..71e381e --- /dev/null +++ b/src/Core/Core.Native/Result.h @@ -0,0 +1,98 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "Failure.h" +#include + +namespace cdb_core +{ + /// + /// Result is analogous to an "Either" - holds a successful + /// result or a failure. Result<> is a value type. + /// + template + class [[nodiscard]] Result + { + public: + constexpr Result(TResult result) noexcept; + constexpr Result(Failure failure) noexcept; + Result(Result&&) noexcept = default; + ~Result() = default; + Result& operator=(Result&&) noexcept = default; + + [[nodiscard]] bool IsSuccess() const; + TResult GetResult(); + + [[nodiscard]] Failure GetFailure() const noexcept; + + private: + std::variant m_value; + }; + + template + constexpr Result::Result(TResult result) noexcept : m_value{ std::in_place_index<0>, std::move(result) } { } + + template + constexpr Result::Result(Failure failure) noexcept : m_value{ std::in_place_index<1>, std::move(failure) } { } + + template + TResult Result::GetResult() + { + Contract::Requires(IsSuccess()); + return std::move(std::get<0>(m_value)); + } + + template + bool Result::IsSuccess() const + { + return m_value.index() == 0; + } + + template + Failure Result::GetFailure() const noexcept + { + Contract::Requires(!IsSuccess()); + return std::move(std::get<1>(m_value)); + } + + template<> + class [[nodiscard]] Result + { + public: + Result(); + explicit Result(Failure failure); + Result(const Result&) = default; + Result(Result&&) = default; + Result& operator=(const Result&) = default; + Result& operator=(Result&&) = default; + + [[nodiscard]] + bool IsSuccess() const noexcept; + + [[nodiscard]] + Failure GetFailure() const noexcept; + + private: + Failure m_failure; + }; + + inline Result::Result() : m_failure(Failure::None) { } + + inline Result::Result(Failure failure) : m_failure(failure) + { + Contract::Requires(failure.IsFailed(), + "Result constructed with Failure must be failed. Use Result() if no failure occurred"); + } + + inline bool Result::IsSuccess() const noexcept + { + return !m_failure.IsFailed(); + } + + inline Failure Result::GetFailure() const noexcept + { + return m_failure; + } +} diff --git a/src/Core/Core.Native/Span.h b/src/Core/Core.Native/Span.h new file mode 100644 index 0000000..ffd5d11 --- /dev/null +++ b/src/Core/Core.Native/Span.h @@ -0,0 +1,192 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include +#include "Blittable.h" + +namespace cdb_core +{ + template struct ReadOnlySpan; + + /// A bounds-checked, pointer to an buffer. + /// + template + struct Span + { + static_assert(is_blittable_v, "Only blittable types can be used in a Span."); + + constexpr Span() noexcept; + ~Span() noexcept = default; + constexpr Span(T* pointer, uint32_t length) noexcept; + template constexpr Span(std::array&) noexcept; + constexpr Span(std::vector&) noexcept; + Span(const Span&) noexcept = default; + Span(Span&&) noexcept = default; + Span& operator=(const Span& other) noexcept = default; + Span& operator=(Span&& other) noexcept = default; + + /// Returns a reference to specified element of the Span. + const T& operator[](uint32_t index) const noexcept; + T& operator[](uint32_t index) noexcept; + + /// The number of items in the span. + [[nodiscard]] uint32_t Length() const noexcept; + + /// Returns true if Length is 0. + [[nodiscard]] bool IsEmpty() const noexcept; + + bool operator==(const Span& rhs) const noexcept; + bool operator!=(const Span& rhs) const noexcept; + + [[nodiscard]] ReadOnlySpan Slice(uint32_t start) const noexcept; + [[nodiscard]] Span Slice(uint32_t start) noexcept; + + [[nodiscard]] ReadOnlySpan Slice(uint32_t start, uint32_t length) const noexcept; + [[nodiscard]] Span Slice(uint32_t start, uint32_t length) noexcept; + + void CopyTo(Span destination) const noexcept; + + template>> + void Fill(std::byte value) noexcept; + + template>> + [[nodiscard]] constexpr int SequenceCompareTo(ReadOnlySpan other) const noexcept; + + template>> + [[nodiscard]] constexpr bool SequenceEqual(ReadOnlySpan other) const noexcept; + + struct SpanIter + { + bool operator==(const SpanIter& other) const noexcept { return m_pointer == other.m_pointer; } + bool operator!=(const SpanIter& other) const noexcept { return m_pointer != other.m_pointer; } + + const SpanIter& operator++() noexcept + { + ++m_pointer; + return *this; + } + + T& operator*() const noexcept { return *m_pointer; } + private: + explicit SpanIter(T* p) : m_pointer(p) {} + friend struct Span; + T* m_pointer; + }; + + [[nodiscard]] SpanIter begin() const noexcept { return SpanIter{m_pointer}; } + [[nodiscard]] SpanIter end() const noexcept { return SpanIter{m_pointer + m_length}; } + + private: + friend struct MemoryMarshal; + template friend struct ReadOnlySpan; + + T* m_pointer; + uint32_t m_length; + }; + + template constexpr Span::Span() noexcept : m_pointer(nullptr), m_length(0) {} + + template + constexpr Span::Span(T* pointer, uint32_t length) noexcept : m_pointer(pointer), m_length(length) { } + + template template constexpr Span::Span(std::array& arr) noexcept : + m_pointer{arr.data()}, + m_length{static_cast(arr.size())} { } + + // Class Template Argument Deduction (CTAD) hint + template Span(std::array&) noexcept -> Span; + + template constexpr Span::Span(std::vector& arr) noexcept : + m_pointer{arr.data()}, + m_length{static_cast(arr.size())} { } + + // Class Template Argument Deduction (CTAD) hint + template Span(std::vector&) noexcept -> Span; + + template const T& Span::operator[](uint32_t index) const noexcept + { + Contract::Assert(index < m_length); + return m_pointer[index]; + } + + template T& Span::operator[](uint32_t index) noexcept + { + Contract::Assert(index < m_length); + return m_pointer[index]; + } + + template uint32_t Span::Length() const noexcept { return m_length; } + + template bool Span::IsEmpty() const noexcept { return m_length == 0; } + + template + bool Span::operator==(const Span& rhs) const noexcept + { + return m_pointer == rhs.m_pointer && m_length == rhs.m_length; + } + + template + bool Span::operator!=(const Span& rhs) const noexcept { return !operator==(rhs); } + + template + ReadOnlySpan Span::Slice(uint32_t start, uint32_t length) const noexcept + { + Contract::Requires(static_cast(start) + static_cast(length) <= static_cast(m_length)); + return Span(m_pointer + start, length); + } + + template + Span Span::Slice(uint32_t start, uint32_t length) noexcept + { + Contract::Requires(static_cast(start) + static_cast(length) <= static_cast(m_length)); + return Span(m_pointer + start, length); + } + + template + ReadOnlySpan Span::Slice(uint32_t start) const noexcept + { + Contract::Requires(start <= m_length); + return ReadOnlySpan(m_pointer + start, m_length - start); + } + + template + Span Span::Slice(uint32_t start) noexcept + { + Contract::Requires(start <= m_length); + return Span(m_pointer + start, m_length - start); + } + + template void Span::CopyTo(Span destination) const noexcept + { + Contract::Requires(m_length <= destination.m_length); + ::memmove_s(static_cast(destination.m_pointer), destination.m_length * sizeof(T), + static_cast(m_pointer), m_length * sizeof(T)); + } + + template + template + void Span::Fill(std::byte value) noexcept + { + ::memset(m_pointer, static_cast(value), m_length * sizeof(T)); + } + + template + template + constexpr int Span::SequenceCompareTo(ReadOnlySpan other) const noexcept + { + uint32_t len = std::min(m_length, other.m_length); + int cmp = ::memcmp(m_pointer, other.m_pointer, len * sizeof(T)); + return (cmp == 0) ? static_cast(m_length - other.m_length) : cmp; + } + + template + template + constexpr bool Span::SequenceEqual(ReadOnlySpan other) const noexcept + { + return m_length == other.m_length && + ::memcmp(m_pointer, other.m_pointer, m_length * sizeof(T)) == 0; + } +} diff --git a/src/Core/Core.Native/Stopwatch.cpp b/src/Core/Core.Native/Stopwatch.cpp new file mode 100644 index 0000000..5bfa5a9 --- /dev/null +++ b/src/Core/Core.Native/Stopwatch.cpp @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "Stopwatch.h" + +namespace cdb_core +{ + static LARGE_INTEGER InitFrequency() noexcept + { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + return freq; + } + + LARGE_INTEGER Stopwatch::s_frequency = InitFrequency(); + + void Stopwatch::Start() noexcept + { + if (m_running) + { + return; + } + m_running = true; + QueryPerformanceCounter(&m_start); + } + + void Stopwatch::Stop() noexcept + { + if (!m_running) + { + return; + } + m_running = false; + LARGE_INTEGER end; + QueryPerformanceCounter(&end); + m_elapsed.QuadPart += end.QuadPart - m_start.QuadPart; + } + + void Stopwatch::Reset() noexcept + { + m_elapsed.QuadPart = 0; + } +} diff --git a/src/Core/Core.Native/Stopwatch.h b/src/Core/Core.Native/Stopwatch.h new file mode 100644 index 0000000..63dbb6b --- /dev/null +++ b/src/Core/Core.Native/Stopwatch.h @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "TimeSpan.h" + +namespace cdb_core +{ + class Stopwatch final + { + public: + Stopwatch() noexcept = default; + ~Stopwatch() noexcept = default; + Stopwatch(const Stopwatch& other) = default; + Stopwatch(Stopwatch&& other) noexcept = default; + Stopwatch& operator=(const Stopwatch& other) = default; + Stopwatch& operator=(Stopwatch&& other) noexcept = default; + + void Start() noexcept; + void Stop() noexcept; + void Reset() noexcept; + [[nodiscard]] TimeSpan Elapsed() const noexcept; + + [[nodiscard]] int64_t ElapsedRaw() const noexcept; + [[nodiscard]] static TimeSpan AsTimeSpan(int64_t raw) noexcept; + + private: + bool m_running; + LARGE_INTEGER m_start; + LARGE_INTEGER m_elapsed; + + static LARGE_INTEGER s_frequency; + }; + + inline int64_t Stopwatch::ElapsedRaw() const noexcept { return m_elapsed.QuadPart; } + + inline TimeSpan Stopwatch::Elapsed() const noexcept + { + return Stopwatch::AsTimeSpan(m_elapsed.QuadPart); + } + + inline TimeSpan Stopwatch::AsTimeSpan(int64_t raw) noexcept + { + return TimeSpan::FromTicks((raw * 10000000) / s_frequency.QuadPart); + } +} diff --git a/src/Core/Core.Native/Stringable.h b/src/Core/Core.Native/Stringable.h new file mode 100644 index 0000000..abd3434 --- /dev/null +++ b/src/Core/Core.Native/Stringable.h @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_core +{ + /// Check for a ToString method. + /// Method must have this signature: + /// TString ToString() const noexcept + /// + template + struct is_stringable + { + private: + template + constexpr static typename std::is_same::type Check(T*) + { + return std::true_type{}; + }; + + template + constexpr static std::false_type Check(...) { return std::false_type{}; }; + public: + constexpr static bool value = decltype(Check>> + >(nullptr))::value; + }; + + template + inline constexpr bool is_stringable_v = is_stringable::value; +} diff --git a/src/Core/Core.Native/Strings.h b/src/Core/Core.Native/Strings.h new file mode 100644 index 0000000..263611a --- /dev/null +++ b/src/Core/Core.Native/Strings.h @@ -0,0 +1,226 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include + +#include "tla.h" + +namespace cdb_core +{ + /// + /// These types are not meant to be used directly but instead exist to facilitate return type inference. + /// + namespace Internal + { + // Forward references. + template struct StringFormat; + template struct Joiner; + } + + /// + /// Makes a string using printf style format specifiers. + /// + /// The type of the format string. + /// The types of the arguments to the format specifiers. + /// The format string. + /// The arguments to the format specifiers. + /// A string object. + /// + /// The return type of this method is not meant to be used directly, but instead to facilitate return type + /// inference. E.g. + /// + /// std::string u8 = make_string("foo: %s", "some value"); + /// std::wstring u16 = string_join(L"foo: %s", L"some wide-string value"); + /// tla::string s = string_join("foo: %s", "some value"); + /// + /// + template + Internal::StringFormat make_string(TFormat format, TArgs ... args) + { + return Internal::StringFormat(format, args...); + } + + /// + /// Makes a string using printf style format specifiers. + /// + /// This is an explicit version of the above method for return type inference. + /// The type of the format string. + /// The types of the arguments to the format specifiers. + /// The format string. + /// The arguments to the format specifiers. + /// A string object. + /// + /// This overload allows you to specify the return type explicitly for use in expressions where + /// the return value will be consumed instead of bound to an lvalue. + /// + /// Logger::WriteMessage(make_string<std::string>("foo: %s", "some value").c_str()); + /// Logger::WriteMessage(make_string<std::wstring>(L"foo: %s", L"some wide-string value").c_str()); + /// + /// + template + TResult make_string(TFormat format, TArgs ... args) + { + return Internal::StringFormat(format, args...); + } + + /// + /// Makes a string by combining existing (possibly heterogeneous) string elements together. + /// + /// The types of the string elements to join. + /// The string elements to join. + /// A string object. + /// + /// The return type of this method is not meant to be used directly, but instead to facilitate return type + /// inference. E.g. + /// + /// std::string u8 = string_join("a", "b", "c"); + /// std::wstring u16 = string_join(L"a", L"b", L"c"); + /// tla::string s = string_join("a", "b", "c"); + /// + /// + template + Internal::Joiner string_join(Args ... args) + { + return Internal::Joiner{args...}; + } + + namespace Internal + { + template + struct StringFormatter {}; + + template<> + struct StringFormatter<> + { + [[nodiscard]] constexpr StringFormatter() noexcept = default; + ~StringFormatter() noexcept = default; + StringFormatter(const StringFormatter<>& other) noexcept = delete; + StringFormatter(StringFormatter<>&& other) noexcept = default; + StringFormatter<>& operator=(const StringFormatter<>& other) noexcept = delete; + StringFormatter<>& operator=(StringFormatter<>&& other) noexcept = delete; + + #pragma warning(push) + #pragma warning( disable: 4840 ) + template + std::enable_if_t, TResult> Format( + TFormat format, TFormatArgs ... args) + { + const size_t size = snprintf(nullptr, 0, &format[0], args ...) + 1; // Extra space for '\0' + std::unique_ptr buf(new char[size]); + snprintf(buf.get(), size, &format[0], args...); + return TResult(buf.get(), buf.get() + size - 1); // exclude terminating null + } + + #pragma warning( disable: 4996 ) + template + std::enable_if_t, TResult> Format( + TFormat format, TFormatArgs ... args) + { + const size_t size = _snwprintf(nullptr, 0, &format[0], args ...) + 1; // Extra space for '\0' + std::unique_ptr buf(new wchar_t[size]); + _snwprintf(buf.get(), size, &format[0], args...); + return TResult(buf.get(), buf.get() + size - 1); // exclude terminating null + } + #pragma warning( pop ) + }; + + template + struct StringFormatter : private StringFormatter + { + using Base = StringFormatter; + + [[nodiscard]] constexpr StringFormatter(T first, Args ... args) noexcept : Base{args...}, m_first{first} {} + ~StringFormatter() noexcept = default; + StringFormatter(const StringFormatter& other) noexcept = delete; + StringFormatter(StringFormatter&& other) noexcept = default; + StringFormatter& operator=(const StringFormatter& other) noexcept = delete; + StringFormatter& operator=(StringFormatter&& other) noexcept = delete; + + protected: + template + TResult Format(TFormat format, TFormatArgs ... args) + { + return Base::template Format(format, args..., m_first); + } + + private: + T m_first; + }; + + template + struct StringFormat : private StringFormatter + { + using Base = StringFormatter; + + [[nodiscard]] constexpr StringFormat(TFormat format, Args ... args) noexcept : Base{args...}, m_format{format} {} + ~StringFormat() noexcept = default; + StringFormat(const StringFormat& other) noexcept = delete; + StringFormat(StringFormat&& other) noexcept = default; + StringFormat& operator=(const StringFormat& other) noexcept = delete; + StringFormat& operator=(StringFormat&& other) noexcept = delete; + + // ReSharper disable once CppNonExplicitConversionOperator + template + operator std::basic_string() + { + return Base::template Format>(m_format); + } + + private: + TFormat m_format; + }; + + template + struct Joiner {}; + + template<> + struct Joiner<> + { + [[nodiscard]] constexpr Joiner() noexcept = default; + ~Joiner() noexcept = default; + Joiner(const Joiner<>& other) noexcept = delete; + Joiner(Joiner<>&& other) noexcept = default; + Joiner<>& operator=(const Joiner<>& other) noexcept = delete; + Joiner<>& operator=(Joiner<>&& other) noexcept = delete; + + template + static void Add(TResult& ret) { } + }; + + template + struct Joiner : private Joiner + { + using Base = Joiner; + + [[nodiscard]] constexpr Joiner(T first, Args ... args) noexcept : Base{args...}, m_first{first} {} + ~Joiner() noexcept = default; + Joiner(const Joiner& other) noexcept = delete; + Joiner(Joiner&& other) noexcept = default; + Joiner& operator=(const Joiner& other) noexcept = delete; + Joiner& operator=(Joiner&& other) noexcept = delete; + + // ReSharper disable once CppNonExplicitConversionOperator + template + operator TResult() + { + TResult ret{m_first}; + Base::Add(ret); + return ret; + } + + protected: + template + void Add(TResult& ret) + { + ret += m_first; + Base::Add(ret); + } + + private: + T m_first; + }; + } +} diff --git a/src/Core/Core.Native/TimeSpan.h b/src/Core/Core.Native/TimeSpan.h new file mode 100644 index 0000000..6828e89 --- /dev/null +++ b/src/Core/Core.Native/TimeSpan.h @@ -0,0 +1,83 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include + +namespace cdb_core +{ + /// + /// Represents a time interval. + /// + /// + /// TimeSpan mimics the behavior of C# TimeSpan and provides APIs for getting time intervals + /// in similar APIs for various durations. + /// + struct TimeSpan final + { + public: + static TimeSpan FromTicks(int64_t oneHundredNanosecondTicks) noexcept; + static TimeSpan FromMilliseconds(int64_t milliseconds) noexcept; + static TimeSpan FromSeconds(int64_t seconds) noexcept; + + [[nodiscard]] int64_t GetTotalMilliseconds() const noexcept; + [[nodiscard]] int64_t GetTotalSeconds() const noexcept; + [[nodiscard]] int64_t GetTotalTicks() const noexcept; + + [[nodiscard]] double GetMilliseconds() const noexcept; + [[nodiscard]] double GetMicroseconds() const noexcept; + + private: + using durationTicks = std::chrono::duration>; + constexpr TimeSpan(durationTicks ticks); + durationTicks m_ticks; + }; + + constexpr TimeSpan::TimeSpan(durationTicks ticks) : m_ticks(ticks) { } + + inline TimeSpan TimeSpan::FromTicks(int64_t oneHundredNanosecondTicks) noexcept + { + durationTicks durationTicks{oneHundredNanosecondTicks}; + return {durationTicks}; + } + + inline TimeSpan TimeSpan::FromMilliseconds(int64_t milliseconds) noexcept + { + std::chrono::milliseconds durationMs{milliseconds}; + return {std::chrono::duration_cast(durationMs)}; + } + + inline TimeSpan TimeSpan::FromSeconds(int64_t seconds) noexcept + { + std::chrono::seconds durationSec{seconds}; + return {std::chrono::duration_cast(durationSec)}; + } + + [[nodiscard]] inline int64_t TimeSpan::GetTotalMilliseconds() const noexcept + { + return std::chrono::duration_cast(m_ticks).count(); + } + + [[nodiscard]] inline int64_t TimeSpan::GetTotalSeconds() const noexcept + { + return std::chrono::duration_cast(m_ticks).count(); + } + + [[nodiscard]] inline int64_t TimeSpan::GetTotalTicks() const noexcept + { + return m_ticks.count(); + } + + [[nodiscard]] inline double TimeSpan::GetMilliseconds() const noexcept + { + return + std::chrono::duration_cast>(m_ticks).count(); + } + + [[nodiscard]] inline double TimeSpan::GetMicroseconds() const noexcept + { + return std::chrono::duration_cast>(m_ticks).count(); + } +} diff --git a/src/Core/Core.Native/Utf8Span.h b/src/Core/Core.Native/Utf8Span.h new file mode 100644 index 0000000..fc717de --- /dev/null +++ b/src/Core/Core.Native/Utf8Span.h @@ -0,0 +1,44 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "ReadOnlySpan.h" + +namespace cdb_core +{ + struct Utf8Span final + { + Utf8Span() = delete; + ~Utf8Span() = delete; + Utf8Span(const Utf8Span& other) = delete; + Utf8Span(Utf8Span&& other) noexcept = delete; + Utf8Span& operator=(const Utf8Span& other) = delete; + Utf8Span& operator=(Utf8Span&& other) noexcept = delete; + + /// Creates a without validating the underlying bytes. + /// The bytes claiming to be UTF8. + /// A wrapping . + /// + /// This method is dangerous as consumers of the must assume the + /// underlying bytes are indeed valid UTF8. The method should only be used when the UTF8 + /// sequence has already been externally valid or is known to be valid by construction. + /// + constexpr static std::string_view UnsafeFromUtf8BytesNoValidation(const ReadOnlySpan& utf8Bytes) noexcept + { + return (utf8Bytes.IsEmpty()) + ? std::string_view{} + : std::string_view{reinterpret_cast(&utf8Bytes[0]), utf8Bytes.Length()}; + } + + /// The UTF8 byte sequence. + constexpr static ReadOnlySpan GetSpan(const std::string_view utf8) + { + // ReSharper disable once CppCStyleCast + return ReadOnlySpan{ + (const std::byte*)(utf8.data()), // NOLINT(clang-diagnostic-old-style-cast) + static_cast(utf8.size()) + }; + } + }; +} diff --git a/src/Core/Core.Native/framework.h b/src/Core/Core.Native/framework.h new file mode 100644 index 0000000..c866f2f --- /dev/null +++ b/src/Core/Core.Native/framework.h @@ -0,0 +1,20 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#define STRICT +#define WIN32_LEAN_AND_MEAN +#define ENABLE_INTSAFE_SIGNED_FUNCTIONS +#define NOMINMAX +#include "Windows.h" + +#include +#include +#include +#include +#include +#include + +using std::byte; diff --git a/src/Core/Core.Native/make_unique.h b/src/Core/Core.Native/make_unique.h new file mode 100644 index 0000000..ba6cd71 --- /dev/null +++ b/src/Core/Core.Native/make_unique.h @@ -0,0 +1,64 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_core +{ + // forward declaration + template + struct make_unique_with_traits; + + // function pointer + template + struct make_unique_with_traits + { + using return_type = R; + using arg_type = Arg; + }; + + // member function pointer + template + struct make_unique_with_traits : make_unique_with_traits {}; + + // const member function pointer + template + struct make_unique_with_traits : make_unique_with_traits {}; + + /// Trait to derive return type and argument type from a generic callable. + template + struct make_unique_with_traits + { + using call_type = make_unique_with_traits; + using return_type = typename call_type::return_type; + using arg_type = std::remove_reference_t; + }; + + // ReSharper disable once CppInconsistentNaming + /// Makes a new object that uses delayed initialization. + template, + typename T = typename Traits::arg_type, + typename = std::enable_if_t>> + static std::unique_ptr make_unique_with(TCallable&& initializer) + { + std::unique_ptr p = std::make_unique(); + initializer(*p); + return std::move(p); + } + + // ReSharper disable once CppInconsistentNaming + /// Makes a new object that uses delayed initialization. + template, + typename T = typename Traits::arg_type, + typename = std::enable_if_t>> + static T make_with(TCallable&& initializer) + { + T retval = T(); + initializer(retval); + return std::move(retval); + } +} diff --git a/src/Core/Core.Native/pch.cpp b/src/Core/Core.Native/pch.cpp new file mode 100644 index 0000000..690da7f --- /dev/null +++ b/src/Core/Core.Native/pch.cpp @@ -0,0 +1,5 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" diff --git a/src/Core/Core.Native/pch.h b/src/Core/Core.Native/pch.h new file mode 100644 index 0000000..ee1da47 --- /dev/null +++ b/src/Core/Core.Native/pch.h @@ -0,0 +1,7 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "framework.h" diff --git a/src/Core/Core.Native/ref_ptr.h b/src/Core/Core.Native/ref_ptr.h new file mode 100644 index 0000000..b3c30b5 --- /dev/null +++ b/src/Core/Core.Native/ref_ptr.h @@ -0,0 +1,206 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_core +{ + // ReSharper disable once CppInconsistentNaming + template + class ref_ptr final + { + public: + + template + // ReSharper disable once CppInconsistentNaming + [[nodiscard]] constexpr static ref_ptr make(ArgsType&&... args); + + constexpr ref_ptr() noexcept + { + m_p = nullptr; + } + + constexpr ref_ptr(T* lp) noexcept + { + m_p = lp; + + if (m_p != nullptr) + { + m_p->AddRef(); + } + } + + constexpr ref_ptr(const ref_ptr& lp) noexcept + { + m_p = lp.m_p; + + if (m_p != nullptr) + { + m_p->AddRef(); + } + } + + constexpr ref_ptr(ref_ptr&& lp) noexcept + { + m_p = lp.m_p; + lp.m_p = nullptr; + } + + ~ref_ptr() noexcept + { + if (m_p != nullptr) + { + m_p->Release(); + m_p = nullptr; // Make sure we AV in case someone is using CComObjectPtr after Release + } + } + + constexpr const T& operator*() const noexcept + { + return *m_p; + } + + constexpr T& operator*() noexcept + { + return *m_p; + } + + constexpr const T* operator->() const noexcept + { + return m_p; + } + + constexpr T* operator->() noexcept + { + return m_p; + } + + constexpr bool operator!() const noexcept + { + return (m_p == nullptr); + } + + constexpr bool operator<(T* pT) const noexcept + { + return (m_p < pT); + } + + constexpr bool operator>(T* pT) const noexcept + { + return (m_p < pT); + } + + constexpr bool operator<=(T* pT) const noexcept + { + return (m_p <= pT); + } + + constexpr bool operator>=(T* pT) const noexcept + { + return (m_p < pT); + } + + constexpr bool operator==(T* pT) const noexcept + { + return (m_p == pT); + } + + constexpr bool operator!=(T* pT) const noexcept + { + return (m_p != pT); + } + + constexpr bool operator==(ref_ptr pT) const noexcept + { + return (m_p == pT.m_p); + } + + constexpr bool operator!=(ref_ptr pT) const noexcept + { + return (m_p != pT.m_p); + } + + constexpr void reset(T* lp) noexcept + { + if (m_p != lp) + { + if (lp != nullptr) + { + lp->AddRef(); + } + + if (m_p != nullptr) + { + m_p->Release(); + } + + m_p = lp; + } + } + + // Release the interface and set to nullptr + constexpr void reset() noexcept + { + T* pTemp = m_p; + if (pTemp != nullptr) + { + m_p = nullptr; + pTemp->Release(); + } + } + + constexpr ref_ptr& operator=(T* lp) noexcept + { + reset(lp); + return *this; + } + + constexpr ref_ptr& operator=(const ref_ptr& lp) noexcept // NOLINT(bugprone-unhandled-self-assignment, cert-oop54-cpp) + { + reset(lp.m_p); + return *this; + } + + constexpr ref_ptr& operator=(ref_ptr&& lp) noexcept + { + reset(); + m_p = lp.m_p; + lp.m_p = nullptr; + return *this; + } + + // Attach to an existing interface (does not AddRef) + constexpr void attach(T* p2) noexcept + { + if (m_p != nullptr) + { + m_p->Release(); + } + m_p = p2; + } + + // Detach the interface (does not Release) + constexpr T* release() noexcept + { + T* pt = m_p; + m_p = nullptr; + return pt; + } + + [[nodiscard]] constexpr T* get() const noexcept + { + return m_p; + } + + private: + T* m_p; + }; + + template + template + [[nodiscard]] constexpr ref_ptr ref_ptr::make(ArgsType&&... args) + { + return std::move(ref_ptr(new T(std::forward(args)...))); + } +} diff --git a/src/Core/Core.Native/tla.h b/src/Core/Core.Native/tla.h new file mode 100644 index 0000000..bbceb18 --- /dev/null +++ b/src/Core/Core.Native/tla.h @@ -0,0 +1,142 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Contract.h" + +namespace tla +{ + template + struct allocator final : private std::allocator + { + static_assert(!std::is_const_v, "The C++ Standard forbids containers of const elements " + "because allocator is ill-formed."); + + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + + using size_type = size_t; + using difference_type = ptrdiff_t; + + using propagate_on_container_move_assignment = std::true_type; + using is_always_equal = std::true_type; + + [[nodiscard]] constexpr allocator() noexcept = default; + ~allocator() noexcept = default; + [[nodiscard]] constexpr allocator(const allocator& other) noexcept = default; + [[nodiscard]] constexpr allocator(allocator&& other) noexcept = default; + constexpr allocator& operator=(const allocator& other) noexcept = default; + constexpr allocator& operator=(allocator&& other) noexcept = default; + + template + struct rebind + { + using other = allocator; + }; + + template + [[nodiscard]] constexpr allocator(const allocator&) noexcept {} + + [[nodiscard]] T* allocate(const size_t n) noexcept + { + try + { + return std::allocator::allocate(n); + } + catch (std::bad_alloc&) + { + cdb_core::Contract::Fail("Out of memory."); + } + } + + void deallocate(T* const p, const size_t n) noexcept + { + std::allocator::deallocate(p, n); + } + }; + + using string = std::basic_string, tla::allocator>; + using string_view = std::string_view; + using wstring = std::basic_string, tla::allocator>; + using wstring_view = std::wstring_view; + + inline namespace literals + { + inline namespace string_literals + { + [[nodiscard]] inline string operator"" _s(const char* _Str, size_t _Len) + { + return string(_Str, _Len); + } + + [[nodiscard]] inline wstring operator"" _s(const wchar_t* _Str, size_t _Len) + { + return wstring(_Str, _Len); + } + + [[nodiscard]] constexpr string_view operator"" _sv(const char* _Str, size_t _Len) noexcept + { + return string_view(_Str, _Len); + } + + [[nodiscard]] constexpr wstring_view operator"" _sv(const wchar_t* _Str, size_t _Len) noexcept + { + return wstring_view(_Str, _Len); + } + } + } + + template + using vector = std::vector>; + + template + using deque = std::deque>; + + template> + using stack = std::stack; + + template + using forward_list = std::forward_list>; + + template + using list = std::list>; + + template> + using set = std::set>; + + template> + using map = std::map>>; + + template> + using multiset = std::multiset>; + + template> + using multimap = std::multimap>>; + + template, class Pred = std::equal_to> + using unordered_set = std::unordered_set>; + + template, class Pred = std::equal_to> + using unordered_map = std::unordered_map>>; + + template, class Pred = std::equal_to> + using unordered_multiset = std::unordered_multiset>; + + template, class Pred = std::equal_to> + using unordered_multimap = std::unordered_multimap>>; +} diff --git a/src/Core/Core/ClockResolution.cs b/src/Core/Core/ClockResolution.cs new file mode 100644 index 0000000..efac421 --- /dev/null +++ b/src/Core/Core/ClockResolution.cs @@ -0,0 +1,65 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core +{ + using System; + using System.Runtime.InteropServices; + using System.Threading; + + public sealed class ClockResolution : IDisposable + { + private static readonly Timecaps TimeCapabilities; + private readonly TimeSpan period; + private int disposed; + + static ClockResolution() + { + int result = ClockResolution.TimeGetDevCaps(ref ClockResolution.TimeCapabilities, Marshal.SizeOf(typeof(Timecaps))); + Contract.Assert(result == 0); + } + + public ClockResolution(TimeSpan period) + { + int millis = Convert.ToInt32(period.TotalMilliseconds); + millis = Math.Min(Math.Max(ClockResolution.TimeCapabilities.PeriodMin, millis), ClockResolution.TimeCapabilities.PeriodMax); + + int result = ClockResolution.TimeBeginPeriod(millis); + Contract.Assert(result == 0); + this.period = TimeSpan.FromMilliseconds(millis); + } + + public static TimeSpan MinimumPeriod => TimeSpan.FromMilliseconds(ClockResolution.TimeCapabilities.PeriodMin); + + public static TimeSpan MaximumPeriod => TimeSpan.FromMilliseconds(ClockResolution.TimeCapabilities.PeriodMax); + + public TimeSpan Period => this.period; + + public void Dispose() + { + if (Interlocked.CompareExchange(ref this.disposed, 1, 0) == 0) + { + int millis = Convert.ToInt32(this.period.TotalMilliseconds); + _ = ClockResolution.TimeEndPeriod(millis); + } + } + + [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps")] + private static extern int TimeGetDevCaps(ref Timecaps ptc, int cbtc); + + [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] + private static extern int TimeBeginPeriod(int uPeriod); + + [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")] + private static extern int TimeEndPeriod(int uPeriod); + + [StructLayout(LayoutKind.Sequential)] + private struct Timecaps + { + public readonly int PeriodMin; + + public readonly int PeriodMax; + } + } +} diff --git a/src/Core/Core/Collections/Trie.cs b/src/Core/Core/Collections/Trie.cs new file mode 100644 index 0000000..1e04b0e --- /dev/null +++ b/src/Core/Core/Collections/Trie.cs @@ -0,0 +1,281 @@ +namespace Microsoft.Azure.Cosmos.Core.Collections +{ + using System; + using System.Runtime.CompilerServices; + + /// A trie, otherwise known as a prefix tree. + /// + /// A trie is a map from key to value. Keys are formed by a sequence of symbols in some alphabet. + /// Values are merely round-tripped by the data structure and are not otherwise interpreted. + ///

+ /// Keys and symbols are compared using the binary collation (i.e. memcmp). Symbols must therefore + /// be an unmanaged type (i.e. convertible to byte). + ///

+ ///

See https://en.wikipedia.org/wiki/Trie for a detailed description of the Trie data structure.

+ ///
+ /// The type of the individual elements that form a key. + /// The type of the value. + public class Trie + where TSymbol : unmanaged + { + /// The smallest capacity allocated. + private const int MinCapacity = 20; + + /// All available allocated storage space. + private Memory capacity; + + /// + /// A view of a subset of that forms the current active nodes of the + /// tree. + /// + /// + /// The trie is encoded as a variable branching n-ary tree of 's. The tree begins + /// at the root (which has no symbol). Each non-root node encodes a single symbol. The path from the + /// root to a node encodes a unique sequence of symbols. If the sequence represents a key within the + /// trie then its terminal flag is set and it also stores a value. Interior nodes of the tree may be + /// terminal (implying that two keys, one of which is a proper prefix of the other, both exist within + /// the trie). + ///

+ /// Children of a node form a linked list, the head of which is pointed to by the + /// and the siblings of which are linked together via + /// . + ///

+ ///

+ /// The 0'th element of this view is always the root node which will store the value for the empty + /// sequence (if it has been added). Both the children linked list and the n-ary tree left nodes are + /// terminated by the address 0, which can never be a valid child because the root is always in + /// position 0. + ///

+ ///
+ private Memory tree; + + /// Initializes a new instance of the class. + /// The (optional) initial capacity. + public Trie(int initialCapacity = 0) + { + Contract.Requires(initialCapacity >= 0); + + // Allocate some initial capacity. + this.capacity = new Node[initialCapacity < Trie.MinCapacity ? Trie.MinCapacity : initialCapacity]; + + // Insert the root. The root will store the value for the empty sequence. + this.tree = this.capacity.Slice(0, 1); + } + + /// Add an item to the trie. + /// The key to be added. + /// The value to be associated with the key. + /// True if the item was added, false if the key already exists in the trie. + public bool TryAdd(ReadOnlySpan key, TValue value) + { + int cur = 0; + foreach (TSymbol s in key) + { + if (this.FindChild(s, cur, out int child)) + { + cur = child; + } + else + { + int address = this.AllocNode(s); + if (child == 0) + { + this.tree.Span[cur].Child = address; + } + else + { + this.tree.Span[child].Next = address; + } + + cur = address; + } + } + + if ((this.tree.Span[cur].Flags & Flags.Terminal) != 0) + { + // Already exists. + return false; + } + + this.tree.Span[cur].Flags |= Flags.Terminal; + this.tree.Span[cur].Value = value; + return true; + } + + /// Add an item to the trie. if the key exists, replace the existing item. + /// The key to be added. + /// The value to be associated with the key. + public void AddOrUpdate(ReadOnlySpan key, TValue value) + { + int cur = 0; + foreach (TSymbol s in key) + { + if (this.FindChild(s, cur, out int child)) + { + cur = child; + } + else + { + int address = this.AllocNode(s); + if (child == 0) + { + this.tree.Span[cur].Child = address; + } + else + { + this.tree.Span[child].Next = address; + } + + cur = address; + } + } + + this.tree.Span[cur].Flags |= Flags.Terminal; + this.tree.Span[cur].Value = value; + } + + /// Find an item within a trie. + /// The key to be added. + /// + /// If was found then the value to be associated with the + /// key, otherwise default. + /// + /// True if the item was found, false otherwise. + public bool TryGetValue(ReadOnlySpan key, out TValue value) + { + int cur = 0; + foreach (TSymbol s in key) + { + if (!this.FindChild(s, cur, out cur)) + { + value = default; + return false; + } + } + + if ((this.tree.Span[cur].Flags & Flags.Terminal) == 0) + { + value = default; + return false; + } + + value = this.tree.Span[cur].Value; + return true; + } + + /// Safe memory compare of . + /// The left argument. + /// The right argument. + /// + /// True if the byte sequence of exactly matches the byte sequence of + /// . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe bool SymbolEquals(TSymbol left, TSymbol right) + { + ReadOnlySpan p = new ReadOnlySpan(&left, sizeof(TSymbol)); + ReadOnlySpan q = new ReadOnlySpan(&right, sizeof(TSymbol)); + return p.SequenceEqual(q); + } + + /// Add a new unattached node to the tree. + /// The symbol for the node. + /// The address of the new node. + private int AllocNode(TSymbol s) + { + int node = this.tree.Length; + if (this.tree.Length == this.capacity.Length) + { + this.capacity = new Node[this.capacity.Length * 2]; + this.tree.CopyTo(this.capacity); + } + + this.tree = this.capacity.Slice(0, this.tree.Length + 1); + + this.tree.Span[node] = new Node(Flags.None, s, 0, 0, default); + return node; + } + + /// Find a child node of with a matching symbol. + /// The symbol to match. + /// The address of the parent. + /// + /// If successful, the address of the child, otherwise the address of the previous + /// in the child link list where a new child should be added. 0 if the parent has no children. + /// + /// True if a child with matching symbol was found, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool FindChild(TSymbol s, int parent, out int child) + { + int last = 0; + child = this.tree.Span[parent].Child; + while (child != 0) + { + if (typeof(TSymbol) == typeof(byte)) + { + unsafe + { + TSymbol t = this.tree.Span[child].Symbol; + if (*(byte*)&t == *(byte*)&s) + { + return true; + } + } + } + else if (Trie.SymbolEquals(this.tree.Span[child].Symbol, s)) + { + return true; + } + + last = child; + child = this.tree.Span[child].Next; + } + + child = last; + return false; + } + + private struct Node + { + /// Flags indicating characteristics of the node. + public Flags Flags; + + /// The symbol forming the key prefix added by this node. + public readonly TSymbol Symbol; + + /// A pointer to the index of the first child node of this node. + public int Child; + + /// + /// A pointer to the index of the next sibling of this node (next child of this node's + /// parent). + /// + public int Next; + + /// + /// If the current node is a terminal, the value associated with current prefix, otherwise + /// undefined. + /// + public TValue Value; + + public Node(Flags flags, TSymbol symbol, int child, int next, TValue value) + { + this.Flags = flags; + this.Symbol = symbol; + this.Child = child; + this.Next = next; + this.Value = value; + } + } + + [Flags] + private enum Flags + { + /// Interior node within the trie that carries no value. + None = 0, + + /// Either interior or left node within the trie that has a value. + Terminal = 1 + } + } +} diff --git a/src/Core/Core/Contract.cs b/src/Core/Core/Contract.cs new file mode 100644 index 0000000..a8edfa4 --- /dev/null +++ b/src/Core/Core/Contract.cs @@ -0,0 +1,94 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core +{ + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + /// + /// Used to check contract invariants in either Debug-only (Assert) + /// or Debug and Release (Requires, Invariant, etc.). Contract failures + /// will attempt to break into the debugger, will trace the error, and + /// will throw a ContractViolationException. + /// + [ExcludeFromCodeCoverage] + public static class Contract + { + [Conditional("DEBUG")] + public static void Assert(bool condition) + { + if (!condition) + { + Contract.Fail("Assert", string.Empty); + } + } + + [Conditional("DEBUG")] + public static void Assert(bool condition, string message) + { + if (!condition) + { + Contract.Fail("Assert", message); + } + } + + public static void Requires(bool condition) + { + if (!condition) + { + Contract.Fail("Requires", string.Empty); + } + } + + public static void Requires(bool condition, string message) + { + if (!condition) + { + Contract.Fail("Requires", message); + } + } + + public static void Invariant(bool condition) + { + if (!condition) + { + Contract.Fail("Invariant", string.Empty); + } + } + + public static void Invariant(bool condition, string message) + { + if (!condition) + { + Contract.Fail("Invariant", message); + } + } + + public static void Fail() + { + Contract.Fail("Fail", string.Empty); + } + + public static void Fail(string message) + { + Contract.Fail("Fail", message); + } + + private static void Fail(string api, string message) + { + StackTrace stack = new StackTrace(2, true); + string error = $"{api} Failure: {message}\n\tStack: {stack}"; + Trace.TraceError(error); + Trace.Flush(); + + // Try breaking into the debugger if attached. + Debugger.Break(); + + // This exception should NEVER be caught. + // TODO: make this uncatchable. + throw new ContractViolationException(error); + } + } +} diff --git a/src/Core/Core/ContractViolationException.cs b/src/Core/Core/ContractViolationException.cs new file mode 100644 index 0000000..dd73bc2 --- /dev/null +++ b/src/Core/Core/ContractViolationException.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; + + [Serializable] + [ExcludeFromCodeCoverage] + public class ContractViolationException : Exception + { + public ContractViolationException() + { + } + + public ContractViolationException(string message) + : base(message) + { + } + + public ContractViolationException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected ContractViolationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/Core/Core/Crc32.cs b/src/Core/Core/Crc32.cs new file mode 100644 index 0000000..4e91434 --- /dev/null +++ b/src/Core/Core/Crc32.cs @@ -0,0 +1,526 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core +{ + using System; + using System.Runtime.InteropServices; + + // Licensed to the .NET Foundation under one or more agreements. + // The .NET Foundation licenses this file to you under the MIT license. + // See the LICENSE file in the project root for more information. + // + // File implements Slicing-by-8 CRC Generation, as described in + // "Novel Table Lookup-Based Algorithms for High-Performance CRC Generation" + // IEEE TRANSACTIONS ON COMPUTERS, VOL. 57, NO. 11, NOVEMBER 2008 + // + // Copyright(c) 2004-2006 Intel Corporation - All Rights Reserved + // + // This software program is licensed subject to the BSD License, + // available at http://www.opensource.org/licenses/bsd-license.html. + + /// + /// CRC Generator. + /// + public static class Crc32 + { + // Generated tables for managed crc calculation. + // Each table n (starting at 0) contains remainders from the long division of + // all possible byte values, shifted by an offset of (n * 4 bits). + // The divisor used is the crc32 standard polynomial 0xEDB88320 + // Please see cited paper for more details. + private static readonly uint[] CrcTable0 = new uint[256] + { + 0x00000000u, 0x77073096u, 0xee0e612cu, 0x990951bau, 0x076dc419u, + 0x706af48fu, 0xe963a535u, 0x9e6495a3u, 0x0edb8832u, 0x79dcb8a4u, + 0xe0d5e91eu, 0x97d2d988u, 0x09b64c2bu, 0x7eb17cbdu, 0xe7b82d07u, + 0x90bf1d91u, 0x1db71064u, 0x6ab020f2u, 0xf3b97148u, 0x84be41deu, + 0x1adad47du, 0x6ddde4ebu, 0xf4d4b551u, 0x83d385c7u, 0x136c9856u, + 0x646ba8c0u, 0xfd62f97au, 0x8a65c9ecu, 0x14015c4fu, 0x63066cd9u, + 0xfa0f3d63u, 0x8d080df5u, 0x3b6e20c8u, 0x4c69105eu, 0xd56041e4u, + 0xa2677172u, 0x3c03e4d1u, 0x4b04d447u, 0xd20d85fdu, 0xa50ab56bu, + 0x35b5a8fau, 0x42b2986cu, 0xdbbbc9d6u, 0xacbcf940u, 0x32d86ce3u, + 0x45df5c75u, 0xdcd60dcfu, 0xabd13d59u, 0x26d930acu, 0x51de003au, + 0xc8d75180u, 0xbfd06116u, 0x21b4f4b5u, 0x56b3c423u, 0xcfba9599u, + 0xb8bda50fu, 0x2802b89eu, 0x5f058808u, 0xc60cd9b2u, 0xb10be924u, + 0x2f6f7c87u, 0x58684c11u, 0xc1611dabu, 0xb6662d3du, 0x76dc4190u, + 0x01db7106u, 0x98d220bcu, 0xefd5102au, 0x71b18589u, 0x06b6b51fu, + 0x9fbfe4a5u, 0xe8b8d433u, 0x7807c9a2u, 0x0f00f934u, 0x9609a88eu, + 0xe10e9818u, 0x7f6a0dbbu, 0x086d3d2du, 0x91646c97u, 0xe6635c01u, + 0x6b6b51f4u, 0x1c6c6162u, 0x856530d8u, 0xf262004eu, 0x6c0695edu, + 0x1b01a57bu, 0x8208f4c1u, 0xf50fc457u, 0x65b0d9c6u, 0x12b7e950u, + 0x8bbeb8eau, 0xfcb9887cu, 0x62dd1ddfu, 0x15da2d49u, 0x8cd37cf3u, + 0xfbd44c65u, 0x4db26158u, 0x3ab551ceu, 0xa3bc0074u, 0xd4bb30e2u, + 0x4adfa541u, 0x3dd895d7u, 0xa4d1c46du, 0xd3d6f4fbu, 0x4369e96au, + 0x346ed9fcu, 0xad678846u, 0xda60b8d0u, 0x44042d73u, 0x33031de5u, + 0xaa0a4c5fu, 0xdd0d7cc9u, 0x5005713cu, 0x270241aau, 0xbe0b1010u, + 0xc90c2086u, 0x5768b525u, 0x206f85b3u, 0xb966d409u, 0xce61e49fu, + 0x5edef90eu, 0x29d9c998u, 0xb0d09822u, 0xc7d7a8b4u, 0x59b33d17u, + 0x2eb40d81u, 0xb7bd5c3bu, 0xc0ba6cadu, 0xedb88320u, 0x9abfb3b6u, + 0x03b6e20cu, 0x74b1d29au, 0xead54739u, 0x9dd277afu, 0x04db2615u, + 0x73dc1683u, 0xe3630b12u, 0x94643b84u, 0x0d6d6a3eu, 0x7a6a5aa8u, + 0xe40ecf0bu, 0x9309ff9du, 0x0a00ae27u, 0x7d079eb1u, 0xf00f9344u, + 0x8708a3d2u, 0x1e01f268u, 0x6906c2feu, 0xf762575du, 0x806567cbu, + 0x196c3671u, 0x6e6b06e7u, 0xfed41b76u, 0x89d32be0u, 0x10da7a5au, + 0x67dd4accu, 0xf9b9df6fu, 0x8ebeeff9u, 0x17b7be43u, 0x60b08ed5u, + 0xd6d6a3e8u, 0xa1d1937eu, 0x38d8c2c4u, 0x4fdff252u, 0xd1bb67f1u, + 0xa6bc5767u, 0x3fb506ddu, 0x48b2364bu, 0xd80d2bdau, 0xaf0a1b4cu, + 0x36034af6u, 0x41047a60u, 0xdf60efc3u, 0xa867df55u, 0x316e8eefu, + 0x4669be79u, 0xcb61b38cu, 0xbc66831au, 0x256fd2a0u, 0x5268e236u, + 0xcc0c7795u, 0xbb0b4703u, 0x220216b9u, 0x5505262fu, 0xc5ba3bbeu, + 0xb2bd0b28u, 0x2bb45a92u, 0x5cb36a04u, 0xc2d7ffa7u, 0xb5d0cf31u, + 0x2cd99e8bu, 0x5bdeae1du, 0x9b64c2b0u, 0xec63f226u, 0x756aa39cu, + 0x026d930au, 0x9c0906a9u, 0xeb0e363fu, 0x72076785u, 0x05005713u, + 0x95bf4a82u, 0xe2b87a14u, 0x7bb12baeu, 0x0cb61b38u, 0x92d28e9bu, + 0xe5d5be0du, 0x7cdcefb7u, 0x0bdbdf21u, 0x86d3d2d4u, 0xf1d4e242u, + 0x68ddb3f8u, 0x1fda836eu, 0x81be16cdu, 0xf6b9265bu, 0x6fb077e1u, + 0x18b74777u, 0x88085ae6u, 0xff0f6a70u, 0x66063bcau, 0x11010b5cu, + 0x8f659effu, 0xf862ae69u, 0x616bffd3u, 0x166ccf45u, 0xa00ae278u, + 0xd70dd2eeu, 0x4e048354u, 0x3903b3c2u, 0xa7672661u, 0xd06016f7u, + 0x4969474du, 0x3e6e77dbu, 0xaed16a4au, 0xd9d65adcu, 0x40df0b66u, + 0x37d83bf0u, 0xa9bcae53u, 0xdebb9ec5u, 0x47b2cf7fu, 0x30b5ffe9u, + 0xbdbdf21cu, 0xcabac28au, 0x53b39330u, 0x24b4a3a6u, 0xbad03605u, + 0xcdd70693u, 0x54de5729u, 0x23d967bfu, 0xb3667a2eu, 0xc4614ab8u, + 0x5d681b02u, 0x2a6f2b94u, 0xb40bbe37u, 0xc30c8ea1u, 0x5a05df1bu, + 0x2d02ef8du, + }; + + private static readonly uint[] CrcTable1 = new uint[256] + { + 0x00000000u, 0x191B3141u, 0x32366282u, 0x2B2D53C3u, 0x646CC504u, + 0x7D77F445u, 0x565AA786u, 0x4F4196C7u, 0xC8D98A08u, 0xD1C2BB49u, + 0xFAEFE88Au, 0xE3F4D9CBu, 0xACB54F0Cu, 0xB5AE7E4Du, 0x9E832D8Eu, + 0x87981CCFu, 0x4AC21251u, 0x53D92310u, 0x78F470D3u, 0x61EF4192u, + 0x2EAED755u, 0x37B5E614u, 0x1C98B5D7u, 0x05838496u, 0x821B9859u, + 0x9B00A918u, 0xB02DFADBu, 0xA936CB9Au, 0xE6775D5Du, 0xFF6C6C1Cu, + 0xD4413FDFu, 0xCD5A0E9Eu, 0x958424A2u, 0x8C9F15E3u, 0xA7B24620u, + 0xBEA97761u, 0xF1E8E1A6u, 0xE8F3D0E7u, 0xC3DE8324u, 0xDAC5B265u, + 0x5D5DAEAAu, 0x44469FEBu, 0x6F6BCC28u, 0x7670FD69u, 0x39316BAEu, + 0x202A5AEFu, 0x0B07092Cu, 0x121C386Du, 0xDF4636F3u, 0xC65D07B2u, + 0xED705471u, 0xF46B6530u, 0xBB2AF3F7u, 0xA231C2B6u, 0x891C9175u, + 0x9007A034u, 0x179FBCFBu, 0x0E848DBAu, 0x25A9DE79u, 0x3CB2EF38u, + 0x73F379FFu, 0x6AE848BEu, 0x41C51B7Du, 0x58DE2A3Cu, 0xF0794F05u, + 0xE9627E44u, 0xC24F2D87u, 0xDB541CC6u, 0x94158A01u, 0x8D0EBB40u, + 0xA623E883u, 0xBF38D9C2u, 0x38A0C50Du, 0x21BBF44Cu, 0x0A96A78Fu, + 0x138D96CEu, 0x5CCC0009u, 0x45D73148u, 0x6EFA628Bu, 0x77E153CAu, + 0xBABB5D54u, 0xA3A06C15u, 0x888D3FD6u, 0x91960E97u, 0xDED79850u, + 0xC7CCA911u, 0xECE1FAD2u, 0xF5FACB93u, 0x7262D75Cu, 0x6B79E61Du, + 0x4054B5DEu, 0x594F849Fu, 0x160E1258u, 0x0F152319u, 0x243870DAu, + 0x3D23419Bu, 0x65FD6BA7u, 0x7CE65AE6u, 0x57CB0925u, 0x4ED03864u, + 0x0191AEA3u, 0x188A9FE2u, 0x33A7CC21u, 0x2ABCFD60u, 0xAD24E1AFu, + 0xB43FD0EEu, 0x9F12832Du, 0x8609B26Cu, 0xC94824ABu, 0xD05315EAu, + 0xFB7E4629u, 0xE2657768u, 0x2F3F79F6u, 0x362448B7u, 0x1D091B74u, + 0x04122A35u, 0x4B53BCF2u, 0x52488DB3u, 0x7965DE70u, 0x607EEF31u, + 0xE7E6F3FEu, 0xFEFDC2BFu, 0xD5D0917Cu, 0xCCCBA03Du, 0x838A36FAu, + 0x9A9107BBu, 0xB1BC5478u, 0xA8A76539u, 0x3B83984Bu, 0x2298A90Au, + 0x09B5FAC9u, 0x10AECB88u, 0x5FEF5D4Fu, 0x46F46C0Eu, 0x6DD93FCDu, + 0x74C20E8Cu, 0xF35A1243u, 0xEA412302u, 0xC16C70C1u, 0xD8774180u, + 0x9736D747u, 0x8E2DE606u, 0xA500B5C5u, 0xBC1B8484u, 0x71418A1Au, + 0x685ABB5Bu, 0x4377E898u, 0x5A6CD9D9u, 0x152D4F1Eu, 0x0C367E5Fu, + 0x271B2D9Cu, 0x3E001CDDu, 0xB9980012u, 0xA0833153u, 0x8BAE6290u, + 0x92B553D1u, 0xDDF4C516u, 0xC4EFF457u, 0xEFC2A794u, 0xF6D996D5u, + 0xAE07BCE9u, 0xB71C8DA8u, 0x9C31DE6Bu, 0x852AEF2Au, 0xCA6B79EDu, + 0xD37048ACu, 0xF85D1B6Fu, 0xE1462A2Eu, 0x66DE36E1u, 0x7FC507A0u, + 0x54E85463u, 0x4DF36522u, 0x02B2F3E5u, 0x1BA9C2A4u, 0x30849167u, + 0x299FA026u, 0xE4C5AEB8u, 0xFDDE9FF9u, 0xD6F3CC3Au, 0xCFE8FD7Bu, + 0x80A96BBCu, 0x99B25AFDu, 0xB29F093Eu, 0xAB84387Fu, 0x2C1C24B0u, + 0x350715F1u, 0x1E2A4632u, 0x07317773u, 0x4870E1B4u, 0x516BD0F5u, + 0x7A468336u, 0x635DB277u, 0xCBFAD74Eu, 0xD2E1E60Fu, 0xF9CCB5CCu, + 0xE0D7848Du, 0xAF96124Au, 0xB68D230Bu, 0x9DA070C8u, 0x84BB4189u, + 0x03235D46u, 0x1A386C07u, 0x31153FC4u, 0x280E0E85u, 0x674F9842u, + 0x7E54A903u, 0x5579FAC0u, 0x4C62CB81u, 0x8138C51Fu, 0x9823F45Eu, + 0xB30EA79Du, 0xAA1596DCu, 0xE554001Bu, 0xFC4F315Au, 0xD7626299u, + 0xCE7953D8u, 0x49E14F17u, 0x50FA7E56u, 0x7BD72D95u, 0x62CC1CD4u, + 0x2D8D8A13u, 0x3496BB52u, 0x1FBBE891u, 0x06A0D9D0u, 0x5E7EF3ECu, + 0x4765C2ADu, 0x6C48916Eu, 0x7553A02Fu, 0x3A1236E8u, 0x230907A9u, + 0x0824546Au, 0x113F652Bu, 0x96A779E4u, 0x8FBC48A5u, 0xA4911B66u, + 0xBD8A2A27u, 0xF2CBBCE0u, 0xEBD08DA1u, 0xC0FDDE62u, 0xD9E6EF23u, + 0x14BCE1BDu, 0x0DA7D0FCu, 0x268A833Fu, 0x3F91B27Eu, 0x70D024B9u, + 0x69CB15F8u, 0x42E6463Bu, 0x5BFD777Au, 0xDC656BB5u, 0xC57E5AF4u, + 0xEE530937u, 0xF7483876u, 0xB809AEB1u, 0xA1129FF0u, 0x8A3FCC33u, + 0x9324FD72u, + }; + + private static readonly uint[] CrcTable2 = new uint[256] + { + 0x00000000u, 0x01C26A37u, 0x0384D46Eu, 0x0246BE59u, 0x0709A8DCu, + 0x06CBC2EBu, 0x048D7CB2u, 0x054F1685u, 0x0E1351B8u, 0x0FD13B8Fu, + 0x0D9785D6u, 0x0C55EFE1u, 0x091AF964u, 0x08D89353u, 0x0A9E2D0Au, + 0x0B5C473Du, 0x1C26A370u, 0x1DE4C947u, 0x1FA2771Eu, 0x1E601D29u, + 0x1B2F0BACu, 0x1AED619Bu, 0x18ABDFC2u, 0x1969B5F5u, 0x1235F2C8u, + 0x13F798FFu, 0x11B126A6u, 0x10734C91u, 0x153C5A14u, 0x14FE3023u, + 0x16B88E7Au, 0x177AE44Du, 0x384D46E0u, 0x398F2CD7u, 0x3BC9928Eu, + 0x3A0BF8B9u, 0x3F44EE3Cu, 0x3E86840Bu, 0x3CC03A52u, 0x3D025065u, + 0x365E1758u, 0x379C7D6Fu, 0x35DAC336u, 0x3418A901u, 0x3157BF84u, + 0x3095D5B3u, 0x32D36BEAu, 0x331101DDu, 0x246BE590u, 0x25A98FA7u, + 0x27EF31FEu, 0x262D5BC9u, 0x23624D4Cu, 0x22A0277Bu, 0x20E69922u, + 0x2124F315u, 0x2A78B428u, 0x2BBADE1Fu, 0x29FC6046u, 0x283E0A71u, + 0x2D711CF4u, 0x2CB376C3u, 0x2EF5C89Au, 0x2F37A2ADu, 0x709A8DC0u, + 0x7158E7F7u, 0x731E59AEu, 0x72DC3399u, 0x7793251Cu, 0x76514F2Bu, + 0x7417F172u, 0x75D59B45u, 0x7E89DC78u, 0x7F4BB64Fu, 0x7D0D0816u, + 0x7CCF6221u, 0x798074A4u, 0x78421E93u, 0x7A04A0CAu, 0x7BC6CAFDu, + 0x6CBC2EB0u, 0x6D7E4487u, 0x6F38FADEu, 0x6EFA90E9u, 0x6BB5866Cu, + 0x6A77EC5Bu, 0x68315202u, 0x69F33835u, 0x62AF7F08u, 0x636D153Fu, + 0x612BAB66u, 0x60E9C151u, 0x65A6D7D4u, 0x6464BDE3u, 0x662203BAu, + 0x67E0698Du, 0x48D7CB20u, 0x4915A117u, 0x4B531F4Eu, 0x4A917579u, + 0x4FDE63FCu, 0x4E1C09CBu, 0x4C5AB792u, 0x4D98DDA5u, 0x46C49A98u, + 0x4706F0AFu, 0x45404EF6u, 0x448224C1u, 0x41CD3244u, 0x400F5873u, + 0x4249E62Au, 0x438B8C1Du, 0x54F16850u, 0x55330267u, 0x5775BC3Eu, + 0x56B7D609u, 0x53F8C08Cu, 0x523AAABBu, 0x507C14E2u, 0x51BE7ED5u, + 0x5AE239E8u, 0x5B2053DFu, 0x5966ED86u, 0x58A487B1u, 0x5DEB9134u, + 0x5C29FB03u, 0x5E6F455Au, 0x5FAD2F6Du, 0xE1351B80u, 0xE0F771B7u, + 0xE2B1CFEEu, 0xE373A5D9u, 0xE63CB35Cu, 0xE7FED96Bu, 0xE5B86732u, + 0xE47A0D05u, 0xEF264A38u, 0xEEE4200Fu, 0xECA29E56u, 0xED60F461u, + 0xE82FE2E4u, 0xE9ED88D3u, 0xEBAB368Au, 0xEA695CBDu, 0xFD13B8F0u, + 0xFCD1D2C7u, 0xFE976C9Eu, 0xFF5506A9u, 0xFA1A102Cu, 0xFBD87A1Bu, + 0xF99EC442u, 0xF85CAE75u, 0xF300E948u, 0xF2C2837Fu, 0xF0843D26u, + 0xF1465711u, 0xF4094194u, 0xF5CB2BA3u, 0xF78D95FAu, 0xF64FFFCDu, + 0xD9785D60u, 0xD8BA3757u, 0xDAFC890Eu, 0xDB3EE339u, 0xDE71F5BCu, + 0xDFB39F8Bu, 0xDDF521D2u, 0xDC374BE5u, 0xD76B0CD8u, 0xD6A966EFu, + 0xD4EFD8B6u, 0xD52DB281u, 0xD062A404u, 0xD1A0CE33u, 0xD3E6706Au, + 0xD2241A5Du, 0xC55EFE10u, 0xC49C9427u, 0xC6DA2A7Eu, 0xC7184049u, + 0xC25756CCu, 0xC3953CFBu, 0xC1D382A2u, 0xC011E895u, 0xCB4DAFA8u, + 0xCA8FC59Fu, 0xC8C97BC6u, 0xC90B11F1u, 0xCC440774u, 0xCD866D43u, + 0xCFC0D31Au, 0xCE02B92Du, 0x91AF9640u, 0x906DFC77u, 0x922B422Eu, + 0x93E92819u, 0x96A63E9Cu, 0x976454ABu, 0x9522EAF2u, 0x94E080C5u, + 0x9FBCC7F8u, 0x9E7EADCFu, 0x9C381396u, 0x9DFA79A1u, 0x98B56F24u, + 0x99770513u, 0x9B31BB4Au, 0x9AF3D17Du, 0x8D893530u, 0x8C4B5F07u, + 0x8E0DE15Eu, 0x8FCF8B69u, 0x8A809DECu, 0x8B42F7DBu, 0x89044982u, + 0x88C623B5u, 0x839A6488u, 0x82580EBFu, 0x801EB0E6u, 0x81DCDAD1u, + 0x8493CC54u, 0x8551A663u, 0x8717183Au, 0x86D5720Du, 0xA9E2D0A0u, + 0xA820BA97u, 0xAA6604CEu, 0xABA46EF9u, 0xAEEB787Cu, 0xAF29124Bu, + 0xAD6FAC12u, 0xACADC625u, 0xA7F18118u, 0xA633EB2Fu, 0xA4755576u, + 0xA5B73F41u, 0xA0F829C4u, 0xA13A43F3u, 0xA37CFDAAu, 0xA2BE979Du, + 0xB5C473D0u, 0xB40619E7u, 0xB640A7BEu, 0xB782CD89u, 0xB2CDDB0Cu, + 0xB30FB13Bu, 0xB1490F62u, 0xB08B6555u, 0xBBD72268u, 0xBA15485Fu, + 0xB853F606u, 0xB9919C31u, 0xBCDE8AB4u, 0xBD1CE083u, 0xBF5A5EDAu, + 0xBE9834EDu, + }; + + private static readonly uint[] CrcTable3 = new uint[256] + { + 0x00000000u, 0xB8BC6765u, 0xAA09C88Bu, 0x12B5AFEEu, 0x8F629757u, + 0x37DEF032u, 0x256B5FDCu, 0x9DD738B9u, 0xC5B428EFu, 0x7D084F8Au, + 0x6FBDE064u, 0xD7018701u, 0x4AD6BFB8u, 0xF26AD8DDu, 0xE0DF7733u, + 0x58631056u, 0x5019579Fu, 0xE8A530FAu, 0xFA109F14u, 0x42ACF871u, + 0xDF7BC0C8u, 0x67C7A7ADu, 0x75720843u, 0xCDCE6F26u, 0x95AD7F70u, + 0x2D111815u, 0x3FA4B7FBu, 0x8718D09Eu, 0x1ACFE827u, 0xA2738F42u, + 0xB0C620ACu, 0x087A47C9u, 0xA032AF3Eu, 0x188EC85Bu, 0x0A3B67B5u, + 0xB28700D0u, 0x2F503869u, 0x97EC5F0Cu, 0x8559F0E2u, 0x3DE59787u, + 0x658687D1u, 0xDD3AE0B4u, 0xCF8F4F5Au, 0x7733283Fu, 0xEAE41086u, + 0x525877E3u, 0x40EDD80Du, 0xF851BF68u, 0xF02BF8A1u, 0x48979FC4u, + 0x5A22302Au, 0xE29E574Fu, 0x7F496FF6u, 0xC7F50893u, 0xD540A77Du, + 0x6DFCC018u, 0x359FD04Eu, 0x8D23B72Bu, 0x9F9618C5u, 0x272A7FA0u, + 0xBAFD4719u, 0x0241207Cu, 0x10F48F92u, 0xA848E8F7u, 0x9B14583Du, + 0x23A83F58u, 0x311D90B6u, 0x89A1F7D3u, 0x1476CF6Au, 0xACCAA80Fu, + 0xBE7F07E1u, 0x06C36084u, 0x5EA070D2u, 0xE61C17B7u, 0xF4A9B859u, + 0x4C15DF3Cu, 0xD1C2E785u, 0x697E80E0u, 0x7BCB2F0Eu, 0xC377486Bu, + 0xCB0D0FA2u, 0x73B168C7u, 0x6104C729u, 0xD9B8A04Cu, 0x446F98F5u, + 0xFCD3FF90u, 0xEE66507Eu, 0x56DA371Bu, 0x0EB9274Du, 0xB6054028u, + 0xA4B0EFC6u, 0x1C0C88A3u, 0x81DBB01Au, 0x3967D77Fu, 0x2BD27891u, + 0x936E1FF4u, 0x3B26F703u, 0x839A9066u, 0x912F3F88u, 0x299358EDu, + 0xB4446054u, 0x0CF80731u, 0x1E4DA8DFu, 0xA6F1CFBAu, 0xFE92DFECu, + 0x462EB889u, 0x549B1767u, 0xEC277002u, 0x71F048BBu, 0xC94C2FDEu, + 0xDBF98030u, 0x6345E755u, 0x6B3FA09Cu, 0xD383C7F9u, 0xC1366817u, + 0x798A0F72u, 0xE45D37CBu, 0x5CE150AEu, 0x4E54FF40u, 0xF6E89825u, + 0xAE8B8873u, 0x1637EF16u, 0x048240F8u, 0xBC3E279Du, 0x21E91F24u, + 0x99557841u, 0x8BE0D7AFu, 0x335CB0CAu, 0xED59B63Bu, 0x55E5D15Eu, + 0x47507EB0u, 0xFFEC19D5u, 0x623B216Cu, 0xDA874609u, 0xC832E9E7u, + 0x708E8E82u, 0x28ED9ED4u, 0x9051F9B1u, 0x82E4565Fu, 0x3A58313Au, + 0xA78F0983u, 0x1F336EE6u, 0x0D86C108u, 0xB53AA66Du, 0xBD40E1A4u, + 0x05FC86C1u, 0x1749292Fu, 0xAFF54E4Au, 0x322276F3u, 0x8A9E1196u, + 0x982BBE78u, 0x2097D91Du, 0x78F4C94Bu, 0xC048AE2Eu, 0xD2FD01C0u, + 0x6A4166A5u, 0xF7965E1Cu, 0x4F2A3979u, 0x5D9F9697u, 0xE523F1F2u, + 0x4D6B1905u, 0xF5D77E60u, 0xE762D18Eu, 0x5FDEB6EBu, 0xC2098E52u, + 0x7AB5E937u, 0x680046D9u, 0xD0BC21BCu, 0x88DF31EAu, 0x3063568Fu, + 0x22D6F961u, 0x9A6A9E04u, 0x07BDA6BDu, 0xBF01C1D8u, 0xADB46E36u, + 0x15080953u, 0x1D724E9Au, 0xA5CE29FFu, 0xB77B8611u, 0x0FC7E174u, + 0x9210D9CDu, 0x2AACBEA8u, 0x38191146u, 0x80A57623u, 0xD8C66675u, + 0x607A0110u, 0x72CFAEFEu, 0xCA73C99Bu, 0x57A4F122u, 0xEF189647u, + 0xFDAD39A9u, 0x45115ECCu, 0x764DEE06u, 0xCEF18963u, 0xDC44268Du, + 0x64F841E8u, 0xF92F7951u, 0x41931E34u, 0x5326B1DAu, 0xEB9AD6BFu, + 0xB3F9C6E9u, 0x0B45A18Cu, 0x19F00E62u, 0xA14C6907u, 0x3C9B51BEu, + 0x842736DBu, 0x96929935u, 0x2E2EFE50u, 0x2654B999u, 0x9EE8DEFCu, + 0x8C5D7112u, 0x34E11677u, 0xA9362ECEu, 0x118A49ABu, 0x033FE645u, + 0xBB838120u, 0xE3E09176u, 0x5B5CF613u, 0x49E959FDu, 0xF1553E98u, + 0x6C820621u, 0xD43E6144u, 0xC68BCEAAu, 0x7E37A9CFu, 0xD67F4138u, + 0x6EC3265Du, 0x7C7689B3u, 0xC4CAEED6u, 0x591DD66Fu, 0xE1A1B10Au, + 0xF3141EE4u, 0x4BA87981u, 0x13CB69D7u, 0xAB770EB2u, 0xB9C2A15Cu, + 0x017EC639u, 0x9CA9FE80u, 0x241599E5u, 0x36A0360Bu, 0x8E1C516Eu, + 0x866616A7u, 0x3EDA71C2u, 0x2C6FDE2Cu, 0x94D3B949u, 0x090481F0u, + 0xB1B8E695u, 0xA30D497Bu, 0x1BB12E1Eu, 0x43D23E48u, 0xFB6E592Du, + 0xE9DBF6C3u, 0x516791A6u, 0xCCB0A91Fu, 0x740CCE7Au, 0x66B96194u, + 0xDE0506F1u, + }; + + private static readonly uint[] CrcTable4 = new uint[256] + { + 0x00000000u, 0x3D6029B0u, 0x7AC05360u, 0x47A07AD0u, 0xF580A6C0u, + 0xC8E08F70u, 0x8F40F5A0u, 0xB220DC10u, 0x30704BC1u, 0x0D106271u, + 0x4AB018A1u, 0x77D03111u, 0xC5F0ED01u, 0xF890C4B1u, 0xBF30BE61u, + 0x825097D1u, 0x60E09782u, 0x5D80BE32u, 0x1A20C4E2u, 0x2740ED52u, + 0x95603142u, 0xA80018F2u, 0xEFA06222u, 0xD2C04B92u, 0x5090DC43u, + 0x6DF0F5F3u, 0x2A508F23u, 0x1730A693u, 0xA5107A83u, 0x98705333u, + 0xDFD029E3u, 0xE2B00053u, 0xC1C12F04u, 0xFCA106B4u, 0xBB017C64u, + 0x866155D4u, 0x344189C4u, 0x0921A074u, 0x4E81DAA4u, 0x73E1F314u, + 0xF1B164C5u, 0xCCD14D75u, 0x8B7137A5u, 0xB6111E15u, 0x0431C205u, + 0x3951EBB5u, 0x7EF19165u, 0x4391B8D5u, 0xA121B886u, 0x9C419136u, + 0xDBE1EBE6u, 0xE681C256u, 0x54A11E46u, 0x69C137F6u, 0x2E614D26u, + 0x13016496u, 0x9151F347u, 0xAC31DAF7u, 0xEB91A027u, 0xD6F18997u, + 0x64D15587u, 0x59B17C37u, 0x1E1106E7u, 0x23712F57u, 0x58F35849u, + 0x659371F9u, 0x22330B29u, 0x1F532299u, 0xAD73FE89u, 0x9013D739u, + 0xD7B3ADE9u, 0xEAD38459u, 0x68831388u, 0x55E33A38u, 0x124340E8u, + 0x2F236958u, 0x9D03B548u, 0xA0639CF8u, 0xE7C3E628u, 0xDAA3CF98u, + 0x3813CFCBu, 0x0573E67Bu, 0x42D39CABu, 0x7FB3B51Bu, 0xCD93690Bu, + 0xF0F340BBu, 0xB7533A6Bu, 0x8A3313DBu, 0x0863840Au, 0x3503ADBAu, + 0x72A3D76Au, 0x4FC3FEDAu, 0xFDE322CAu, 0xC0830B7Au, 0x872371AAu, + 0xBA43581Au, 0x9932774Du, 0xA4525EFDu, 0xE3F2242Du, 0xDE920D9Du, + 0x6CB2D18Du, 0x51D2F83Du, 0x167282EDu, 0x2B12AB5Du, 0xA9423C8Cu, + 0x9422153Cu, 0xD3826FECu, 0xEEE2465Cu, 0x5CC29A4Cu, 0x61A2B3FCu, + 0x2602C92Cu, 0x1B62E09Cu, 0xF9D2E0CFu, 0xC4B2C97Fu, 0x8312B3AFu, + 0xBE729A1Fu, 0x0C52460Fu, 0x31326FBFu, 0x7692156Fu, 0x4BF23CDFu, + 0xC9A2AB0Eu, 0xF4C282BEu, 0xB362F86Eu, 0x8E02D1DEu, 0x3C220DCEu, + 0x0142247Eu, 0x46E25EAEu, 0x7B82771Eu, 0xB1E6B092u, 0x8C869922u, + 0xCB26E3F2u, 0xF646CA42u, 0x44661652u, 0x79063FE2u, 0x3EA64532u, + 0x03C66C82u, 0x8196FB53u, 0xBCF6D2E3u, 0xFB56A833u, 0xC6368183u, + 0x74165D93u, 0x49767423u, 0x0ED60EF3u, 0x33B62743u, 0xD1062710u, + 0xEC660EA0u, 0xABC67470u, 0x96A65DC0u, 0x248681D0u, 0x19E6A860u, + 0x5E46D2B0u, 0x6326FB00u, 0xE1766CD1u, 0xDC164561u, 0x9BB63FB1u, + 0xA6D61601u, 0x14F6CA11u, 0x2996E3A1u, 0x6E369971u, 0x5356B0C1u, + 0x70279F96u, 0x4D47B626u, 0x0AE7CCF6u, 0x3787E546u, 0x85A73956u, + 0xB8C710E6u, 0xFF676A36u, 0xC2074386u, 0x4057D457u, 0x7D37FDE7u, + 0x3A978737u, 0x07F7AE87u, 0xB5D77297u, 0x88B75B27u, 0xCF1721F7u, + 0xF2770847u, 0x10C70814u, 0x2DA721A4u, 0x6A075B74u, 0x576772C4u, + 0xE547AED4u, 0xD8278764u, 0x9F87FDB4u, 0xA2E7D404u, 0x20B743D5u, + 0x1DD76A65u, 0x5A7710B5u, 0x67173905u, 0xD537E515u, 0xE857CCA5u, + 0xAFF7B675u, 0x92979FC5u, 0xE915E8DBu, 0xD475C16Bu, 0x93D5BBBBu, + 0xAEB5920Bu, 0x1C954E1Bu, 0x21F567ABu, 0x66551D7Bu, 0x5B3534CBu, + 0xD965A31Au, 0xE4058AAAu, 0xA3A5F07Au, 0x9EC5D9CAu, 0x2CE505DAu, + 0x11852C6Au, 0x562556BAu, 0x6B457F0Au, 0x89F57F59u, 0xB49556E9u, + 0xF3352C39u, 0xCE550589u, 0x7C75D999u, 0x4115F029u, 0x06B58AF9u, + 0x3BD5A349u, 0xB9853498u, 0x84E51D28u, 0xC34567F8u, 0xFE254E48u, + 0x4C059258u, 0x7165BBE8u, 0x36C5C138u, 0x0BA5E888u, 0x28D4C7DFu, + 0x15B4EE6Fu, 0x521494BFu, 0x6F74BD0Fu, 0xDD54611Fu, 0xE03448AFu, + 0xA794327Fu, 0x9AF41BCFu, 0x18A48C1Eu, 0x25C4A5AEu, 0x6264DF7Eu, + 0x5F04F6CEu, 0xED242ADEu, 0xD044036Eu, 0x97E479BEu, 0xAA84500Eu, + 0x4834505Du, 0x755479EDu, 0x32F4033Du, 0x0F942A8Du, 0xBDB4F69Du, + 0x80D4DF2Du, 0xC774A5FDu, 0xFA148C4Du, 0x78441B9Cu, 0x4524322Cu, + 0x028448FCu, 0x3FE4614Cu, 0x8DC4BD5Cu, 0xB0A494ECu, 0xF704EE3Cu, + 0xCA64C78Cu, + }; + + private static readonly uint[] CrcTable5 = new uint[256] + { + 0x00000000u, 0xCB5CD3A5u, 0x4DC8A10Bu, 0x869472AEu, 0x9B914216u, + 0x50CD91B3u, 0xD659E31Du, 0x1D0530B8u, 0xEC53826Du, 0x270F51C8u, + 0xA19B2366u, 0x6AC7F0C3u, 0x77C2C07Bu, 0xBC9E13DEu, 0x3A0A6170u, + 0xF156B2D5u, 0x03D6029Bu, 0xC88AD13Eu, 0x4E1EA390u, 0x85427035u, + 0x9847408Du, 0x531B9328u, 0xD58FE186u, 0x1ED33223u, 0xEF8580F6u, + 0x24D95353u, 0xA24D21FDu, 0x6911F258u, 0x7414C2E0u, 0xBF481145u, + 0x39DC63EBu, 0xF280B04Eu, 0x07AC0536u, 0xCCF0D693u, 0x4A64A43Du, + 0x81387798u, 0x9C3D4720u, 0x57619485u, 0xD1F5E62Bu, 0x1AA9358Eu, + 0xEBFF875Bu, 0x20A354FEu, 0xA6372650u, 0x6D6BF5F5u, 0x706EC54Du, + 0xBB3216E8u, 0x3DA66446u, 0xF6FAB7E3u, 0x047A07ADu, 0xCF26D408u, + 0x49B2A6A6u, 0x82EE7503u, 0x9FEB45BBu, 0x54B7961Eu, 0xD223E4B0u, + 0x197F3715u, 0xE82985C0u, 0x23755665u, 0xA5E124CBu, 0x6EBDF76Eu, + 0x73B8C7D6u, 0xB8E41473u, 0x3E7066DDu, 0xF52CB578u, 0x0F580A6Cu, + 0xC404D9C9u, 0x4290AB67u, 0x89CC78C2u, 0x94C9487Au, 0x5F959BDFu, + 0xD901E971u, 0x125D3AD4u, 0xE30B8801u, 0x28575BA4u, 0xAEC3290Au, + 0x659FFAAFu, 0x789ACA17u, 0xB3C619B2u, 0x35526B1Cu, 0xFE0EB8B9u, + 0x0C8E08F7u, 0xC7D2DB52u, 0x4146A9FCu, 0x8A1A7A59u, 0x971F4AE1u, + 0x5C439944u, 0xDAD7EBEAu, 0x118B384Fu, 0xE0DD8A9Au, 0x2B81593Fu, + 0xAD152B91u, 0x6649F834u, 0x7B4CC88Cu, 0xB0101B29u, 0x36846987u, + 0xFDD8BA22u, 0x08F40F5Au, 0xC3A8DCFFu, 0x453CAE51u, 0x8E607DF4u, + 0x93654D4Cu, 0x58399EE9u, 0xDEADEC47u, 0x15F13FE2u, 0xE4A78D37u, + 0x2FFB5E92u, 0xA96F2C3Cu, 0x6233FF99u, 0x7F36CF21u, 0xB46A1C84u, + 0x32FE6E2Au, 0xF9A2BD8Fu, 0x0B220DC1u, 0xC07EDE64u, 0x46EAACCAu, + 0x8DB67F6Fu, 0x90B34FD7u, 0x5BEF9C72u, 0xDD7BEEDCu, 0x16273D79u, + 0xE7718FACu, 0x2C2D5C09u, 0xAAB92EA7u, 0x61E5FD02u, 0x7CE0CDBAu, + 0xB7BC1E1Fu, 0x31286CB1u, 0xFA74BF14u, 0x1EB014D8u, 0xD5ECC77Du, + 0x5378B5D3u, 0x98246676u, 0x852156CEu, 0x4E7D856Bu, 0xC8E9F7C5u, + 0x03B52460u, 0xF2E396B5u, 0x39BF4510u, 0xBF2B37BEu, 0x7477E41Bu, + 0x6972D4A3u, 0xA22E0706u, 0x24BA75A8u, 0xEFE6A60Du, 0x1D661643u, + 0xD63AC5E6u, 0x50AEB748u, 0x9BF264EDu, 0x86F75455u, 0x4DAB87F0u, + 0xCB3FF55Eu, 0x006326FBu, 0xF135942Eu, 0x3A69478Bu, 0xBCFD3525u, + 0x77A1E680u, 0x6AA4D638u, 0xA1F8059Du, 0x276C7733u, 0xEC30A496u, + 0x191C11EEu, 0xD240C24Bu, 0x54D4B0E5u, 0x9F886340u, 0x828D53F8u, + 0x49D1805Du, 0xCF45F2F3u, 0x04192156u, 0xF54F9383u, 0x3E134026u, + 0xB8873288u, 0x73DBE12Du, 0x6EDED195u, 0xA5820230u, 0x2316709Eu, + 0xE84AA33Bu, 0x1ACA1375u, 0xD196C0D0u, 0x5702B27Eu, 0x9C5E61DBu, + 0x815B5163u, 0x4A0782C6u, 0xCC93F068u, 0x07CF23CDu, 0xF6999118u, + 0x3DC542BDu, 0xBB513013u, 0x700DE3B6u, 0x6D08D30Eu, 0xA65400ABu, + 0x20C07205u, 0xEB9CA1A0u, 0x11E81EB4u, 0xDAB4CD11u, 0x5C20BFBFu, + 0x977C6C1Au, 0x8A795CA2u, 0x41258F07u, 0xC7B1FDA9u, 0x0CED2E0Cu, + 0xFDBB9CD9u, 0x36E74F7Cu, 0xB0733DD2u, 0x7B2FEE77u, 0x662ADECFu, + 0xAD760D6Au, 0x2BE27FC4u, 0xE0BEAC61u, 0x123E1C2Fu, 0xD962CF8Au, + 0x5FF6BD24u, 0x94AA6E81u, 0x89AF5E39u, 0x42F38D9Cu, 0xC467FF32u, + 0x0F3B2C97u, 0xFE6D9E42u, 0x35314DE7u, 0xB3A53F49u, 0x78F9ECECu, + 0x65FCDC54u, 0xAEA00FF1u, 0x28347D5Fu, 0xE368AEFAu, 0x16441B82u, + 0xDD18C827u, 0x5B8CBA89u, 0x90D0692Cu, 0x8DD55994u, 0x46898A31u, + 0xC01DF89Fu, 0x0B412B3Au, 0xFA1799EFu, 0x314B4A4Au, 0xB7DF38E4u, + 0x7C83EB41u, 0x6186DBF9u, 0xAADA085Cu, 0x2C4E7AF2u, 0xE712A957u, + 0x15921919u, 0xDECECABCu, 0x585AB812u, 0x93066BB7u, 0x8E035B0Fu, + 0x455F88AAu, 0xC3CBFA04u, 0x089729A1u, 0xF9C19B74u, 0x329D48D1u, + 0xB4093A7Fu, 0x7F55E9DAu, 0x6250D962u, 0xA90C0AC7u, 0x2F987869u, + 0xE4C4ABCCu, + }; + + private static readonly uint[] CrcTable6 = new uint[256] + { + 0x00000000u, 0xA6770BB4u, 0x979F1129u, 0x31E81A9Du, 0xF44F2413u, + 0x52382FA7u, 0x63D0353Au, 0xC5A73E8Eu, 0x33EF4E67u, 0x959845D3u, + 0xA4705F4Eu, 0x020754FAu, 0xC7A06A74u, 0x61D761C0u, 0x503F7B5Du, + 0xF64870E9u, 0x67DE9CCEu, 0xC1A9977Au, 0xF0418DE7u, 0x56368653u, + 0x9391B8DDu, 0x35E6B369u, 0x040EA9F4u, 0xA279A240u, 0x5431D2A9u, + 0xF246D91Du, 0xC3AEC380u, 0x65D9C834u, 0xA07EF6BAu, 0x0609FD0Eu, + 0x37E1E793u, 0x9196EC27u, 0xCFBD399Cu, 0x69CA3228u, 0x582228B5u, + 0xFE552301u, 0x3BF21D8Fu, 0x9D85163Bu, 0xAC6D0CA6u, 0x0A1A0712u, + 0xFC5277FBu, 0x5A257C4Fu, 0x6BCD66D2u, 0xCDBA6D66u, 0x081D53E8u, + 0xAE6A585Cu, 0x9F8242C1u, 0x39F54975u, 0xA863A552u, 0x0E14AEE6u, + 0x3FFCB47Bu, 0x998BBFCFu, 0x5C2C8141u, 0xFA5B8AF5u, 0xCBB39068u, + 0x6DC49BDCu, 0x9B8CEB35u, 0x3DFBE081u, 0x0C13FA1Cu, 0xAA64F1A8u, + 0x6FC3CF26u, 0xC9B4C492u, 0xF85CDE0Fu, 0x5E2BD5BBu, 0x440B7579u, + 0xE27C7ECDu, 0xD3946450u, 0x75E36FE4u, 0xB044516Au, 0x16335ADEu, + 0x27DB4043u, 0x81AC4BF7u, 0x77E43B1Eu, 0xD19330AAu, 0xE07B2A37u, + 0x460C2183u, 0x83AB1F0Du, 0x25DC14B9u, 0x14340E24u, 0xB2430590u, + 0x23D5E9B7u, 0x85A2E203u, 0xB44AF89Eu, 0x123DF32Au, 0xD79ACDA4u, + 0x71EDC610u, 0x4005DC8Du, 0xE672D739u, 0x103AA7D0u, 0xB64DAC64u, + 0x87A5B6F9u, 0x21D2BD4Du, 0xE47583C3u, 0x42028877u, 0x73EA92EAu, + 0xD59D995Eu, 0x8BB64CE5u, 0x2DC14751u, 0x1C295DCCu, 0xBA5E5678u, + 0x7FF968F6u, 0xD98E6342u, 0xE86679DFu, 0x4E11726Bu, 0xB8590282u, + 0x1E2E0936u, 0x2FC613ABu, 0x89B1181Fu, 0x4C162691u, 0xEA612D25u, + 0xDB8937B8u, 0x7DFE3C0Cu, 0xEC68D02Bu, 0x4A1FDB9Fu, 0x7BF7C102u, + 0xDD80CAB6u, 0x1827F438u, 0xBE50FF8Cu, 0x8FB8E511u, 0x29CFEEA5u, + 0xDF879E4Cu, 0x79F095F8u, 0x48188F65u, 0xEE6F84D1u, 0x2BC8BA5Fu, + 0x8DBFB1EBu, 0xBC57AB76u, 0x1A20A0C2u, 0x8816EAF2u, 0x2E61E146u, + 0x1F89FBDBu, 0xB9FEF06Fu, 0x7C59CEE1u, 0xDA2EC555u, 0xEBC6DFC8u, + 0x4DB1D47Cu, 0xBBF9A495u, 0x1D8EAF21u, 0x2C66B5BCu, 0x8A11BE08u, + 0x4FB68086u, 0xE9C18B32u, 0xD82991AFu, 0x7E5E9A1Bu, 0xEFC8763Cu, + 0x49BF7D88u, 0x78576715u, 0xDE206CA1u, 0x1B87522Fu, 0xBDF0599Bu, + 0x8C184306u, 0x2A6F48B2u, 0xDC27385Bu, 0x7A5033EFu, 0x4BB82972u, + 0xEDCF22C6u, 0x28681C48u, 0x8E1F17FCu, 0xBFF70D61u, 0x198006D5u, + 0x47ABD36Eu, 0xE1DCD8DAu, 0xD034C247u, 0x7643C9F3u, 0xB3E4F77Du, + 0x1593FCC9u, 0x247BE654u, 0x820CEDE0u, 0x74449D09u, 0xD23396BDu, + 0xE3DB8C20u, 0x45AC8794u, 0x800BB91Au, 0x267CB2AEu, 0x1794A833u, + 0xB1E3A387u, 0x20754FA0u, 0x86024414u, 0xB7EA5E89u, 0x119D553Du, + 0xD43A6BB3u, 0x724D6007u, 0x43A57A9Au, 0xE5D2712Eu, 0x139A01C7u, + 0xB5ED0A73u, 0x840510EEu, 0x22721B5Au, 0xE7D525D4u, 0x41A22E60u, + 0x704A34FDu, 0xD63D3F49u, 0xCC1D9F8Bu, 0x6A6A943Fu, 0x5B828EA2u, + 0xFDF58516u, 0x3852BB98u, 0x9E25B02Cu, 0xAFCDAAB1u, 0x09BAA105u, + 0xFFF2D1ECu, 0x5985DA58u, 0x686DC0C5u, 0xCE1ACB71u, 0x0BBDF5FFu, + 0xADCAFE4Bu, 0x9C22E4D6u, 0x3A55EF62u, 0xABC30345u, 0x0DB408F1u, + 0x3C5C126Cu, 0x9A2B19D8u, 0x5F8C2756u, 0xF9FB2CE2u, 0xC813367Fu, + 0x6E643DCBu, 0x982C4D22u, 0x3E5B4696u, 0x0FB35C0Bu, 0xA9C457BFu, + 0x6C636931u, 0xCA146285u, 0xFBFC7818u, 0x5D8B73ACu, 0x03A0A617u, + 0xA5D7ADA3u, 0x943FB73Eu, 0x3248BC8Au, 0xF7EF8204u, 0x519889B0u, + 0x6070932Du, 0xC6079899u, 0x304FE870u, 0x9638E3C4u, 0xA7D0F959u, + 0x01A7F2EDu, 0xC400CC63u, 0x6277C7D7u, 0x539FDD4Au, 0xF5E8D6FEu, + 0x647E3AD9u, 0xC209316Du, 0xF3E12BF0u, 0x55962044u, 0x90311ECAu, + 0x3646157Eu, 0x07AE0FE3u, 0xA1D90457u, 0x579174BEu, 0xF1E67F0Au, + 0xC00E6597u, 0x66796E23u, 0xA3DE50ADu, 0x05A95B19u, 0x34414184u, + 0x92364A30u, + }; + + private static readonly uint[] CrcTable7 = new uint[256] + { + 0x00000000u, 0xCCAA009Eu, 0x4225077Du, 0x8E8F07E3u, 0x844A0EFAu, + 0x48E00E64u, 0xC66F0987u, 0x0AC50919u, 0xD3E51BB5u, 0x1F4F1B2Bu, + 0x91C01CC8u, 0x5D6A1C56u, 0x57AF154Fu, 0x9B0515D1u, 0x158A1232u, + 0xD92012ACu, 0x7CBB312Bu, 0xB01131B5u, 0x3E9E3656u, 0xF23436C8u, + 0xF8F13FD1u, 0x345B3F4Fu, 0xBAD438ACu, 0x767E3832u, 0xAF5E2A9Eu, + 0x63F42A00u, 0xED7B2DE3u, 0x21D12D7Du, 0x2B142464u, 0xE7BE24FAu, + 0x69312319u, 0xA59B2387u, 0xF9766256u, 0x35DC62C8u, 0xBB53652Bu, + 0x77F965B5u, 0x7D3C6CACu, 0xB1966C32u, 0x3F196BD1u, 0xF3B36B4Fu, + 0x2A9379E3u, 0xE639797Du, 0x68B67E9Eu, 0xA41C7E00u, 0xAED97719u, + 0x62737787u, 0xECFC7064u, 0x205670FAu, 0x85CD537Du, 0x496753E3u, + 0xC7E85400u, 0x0B42549Eu, 0x01875D87u, 0xCD2D5D19u, 0x43A25AFAu, + 0x8F085A64u, 0x562848C8u, 0x9A824856u, 0x140D4FB5u, 0xD8A74F2Bu, + 0xD2624632u, 0x1EC846ACu, 0x9047414Fu, 0x5CED41D1u, 0x299DC2EDu, + 0xE537C273u, 0x6BB8C590u, 0xA712C50Eu, 0xADD7CC17u, 0x617DCC89u, + 0xEFF2CB6Au, 0x2358CBF4u, 0xFA78D958u, 0x36D2D9C6u, 0xB85DDE25u, + 0x74F7DEBBu, 0x7E32D7A2u, 0xB298D73Cu, 0x3C17D0DFu, 0xF0BDD041u, + 0x5526F3C6u, 0x998CF358u, 0x1703F4BBu, 0xDBA9F425u, 0xD16CFD3Cu, + 0x1DC6FDA2u, 0x9349FA41u, 0x5FE3FADFu, 0x86C3E873u, 0x4A69E8EDu, + 0xC4E6EF0Eu, 0x084CEF90u, 0x0289E689u, 0xCE23E617u, 0x40ACE1F4u, + 0x8C06E16Au, 0xD0EBA0BBu, 0x1C41A025u, 0x92CEA7C6u, 0x5E64A758u, + 0x54A1AE41u, 0x980BAEDFu, 0x1684A93Cu, 0xDA2EA9A2u, 0x030EBB0Eu, + 0xCFA4BB90u, 0x412BBC73u, 0x8D81BCEDu, 0x8744B5F4u, 0x4BEEB56Au, + 0xC561B289u, 0x09CBB217u, 0xAC509190u, 0x60FA910Eu, 0xEE7596EDu, + 0x22DF9673u, 0x281A9F6Au, 0xE4B09FF4u, 0x6A3F9817u, 0xA6959889u, + 0x7FB58A25u, 0xB31F8ABBu, 0x3D908D58u, 0xF13A8DC6u, 0xFBFF84DFu, + 0x37558441u, 0xB9DA83A2u, 0x7570833Cu, 0x533B85DAu, 0x9F918544u, + 0x111E82A7u, 0xDDB48239u, 0xD7718B20u, 0x1BDB8BBEu, 0x95548C5Du, + 0x59FE8CC3u, 0x80DE9E6Fu, 0x4C749EF1u, 0xC2FB9912u, 0x0E51998Cu, + 0x04949095u, 0xC83E900Bu, 0x46B197E8u, 0x8A1B9776u, 0x2F80B4F1u, + 0xE32AB46Fu, 0x6DA5B38Cu, 0xA10FB312u, 0xABCABA0Bu, 0x6760BA95u, + 0xE9EFBD76u, 0x2545BDE8u, 0xFC65AF44u, 0x30CFAFDAu, 0xBE40A839u, + 0x72EAA8A7u, 0x782FA1BEu, 0xB485A120u, 0x3A0AA6C3u, 0xF6A0A65Du, + 0xAA4DE78Cu, 0x66E7E712u, 0xE868E0F1u, 0x24C2E06Fu, 0x2E07E976u, + 0xE2ADE9E8u, 0x6C22EE0Bu, 0xA088EE95u, 0x79A8FC39u, 0xB502FCA7u, + 0x3B8DFB44u, 0xF727FBDAu, 0xFDE2F2C3u, 0x3148F25Du, 0xBFC7F5BEu, + 0x736DF520u, 0xD6F6D6A7u, 0x1A5CD639u, 0x94D3D1DAu, 0x5879D144u, + 0x52BCD85Du, 0x9E16D8C3u, 0x1099DF20u, 0xDC33DFBEu, 0x0513CD12u, + 0xC9B9CD8Cu, 0x4736CA6Fu, 0x8B9CCAF1u, 0x8159C3E8u, 0x4DF3C376u, + 0xC37CC495u, 0x0FD6C40Bu, 0x7AA64737u, 0xB60C47A9u, 0x3883404Au, + 0xF42940D4u, 0xFEEC49CDu, 0x32464953u, 0xBCC94EB0u, 0x70634E2Eu, + 0xA9435C82u, 0x65E95C1Cu, 0xEB665BFFu, 0x27CC5B61u, 0x2D095278u, + 0xE1A352E6u, 0x6F2C5505u, 0xA386559Bu, 0x061D761Cu, 0xCAB77682u, + 0x44387161u, 0x889271FFu, 0x825778E6u, 0x4EFD7878u, 0xC0727F9Bu, + 0x0CD87F05u, 0xD5F86DA9u, 0x19526D37u, 0x97DD6AD4u, 0x5B776A4Au, + 0x51B26353u, 0x9D1863CDu, 0x1397642Eu, 0xDF3D64B0u, 0x83D02561u, + 0x4F7A25FFu, 0xC1F5221Cu, 0x0D5F2282u, 0x079A2B9Bu, 0xCB302B05u, + 0x45BF2CE6u, 0x89152C78u, 0x50353ED4u, 0x9C9F3E4Au, 0x121039A9u, + 0xDEBA3937u, 0xD47F302Eu, 0x18D530B0u, 0x965A3753u, 0x5AF037CDu, + 0xFF6B144Au, 0x33C114D4u, 0xBD4E1337u, 0x71E413A9u, 0x7B211AB0u, + 0xB78B1A2Eu, 0x39041DCDu, 0xF5AE1D53u, 0x2C8E0FFFu, 0xE0240F61u, + 0x6EAB0882u, 0xA201081Cu, 0xA8C40105u, 0x646E019Bu, 0xEAE10678u, + 0x264B06E6u, + }; + + public static unsafe uint Update(uint crc32, ReadOnlySpan span) + { + Contract.Assert(BitConverter.IsLittleEndian, "Little Endian expected"); + + crc32 ^= 0xFFFFFFFFU; + int offset = 0; + int runningLength = (span.Length / 8) * 8; + int endBytes = span.Length - runningLength; + + fixed (uint* words = MemoryMarshal.Cast(span)) + { + for (int i = 0; i < runningLength / 8; i++) + { + crc32 ^= words[offset]; + offset += 1; + uint term1 = Crc32.CrcTable7[crc32 & 0x000000FF] ^ + Crc32.CrcTable6[(crc32 >> 8) & 0x000000FF]; + + uint term2 = crc32 >> 16; + crc32 = term1 ^ + Crc32.CrcTable5[term2 & 0x000000FF] ^ + Crc32.CrcTable4[(term2 >> 8) & 0x000000FF]; + + uint term3 = words[offset]; + offset += 1; + term1 = Crc32.CrcTable3[term3 & 0x000000FF] ^ + Crc32.CrcTable2[(term3 >> 8) & 0x000000FF]; + + term2 = term3 >> 16; + crc32 ^= term1 ^ + Crc32.CrcTable1[term2 & 0x000000FF] ^ + Crc32.CrcTable0[(term2 >> 8) & 0x000000FF]; + } + } + + offset = runningLength; + for (int i = 0; i < endBytes; i++) + { + crc32 = Crc32.CrcTable0[(crc32 ^ span[offset++]) & 0x000000FF] ^ (crc32 >> 8); + } + + crc32 ^= 0xFFFFFFFFU; + return crc32; + } + } +} diff --git a/src/Core/Core/Linear.cs b/src/Core/Core/Linear.cs new file mode 100644 index 0000000..7525a54 --- /dev/null +++ b/src/Core/Core/Linear.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core +{ + using System.Runtime.CompilerServices; + + public static class Linear + { + /// + /// Perform the managed equivalent of std::move by returning the value at + /// while simultaneously assigning to + /// . + /// + /// The type of the value to transfer. + /// A reference to the field whose value should be transferred. + /// The value transferred. + /// + /// The value of after the transfer is always + /// . The value is considered "consumed". + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Move(ref T src) + { + T retval = src; + src = default; + return retval; + } + } +} diff --git a/src/Core/Core/Microsoft.Azure.Cosmos.Core.csproj b/src/Core/Core/Microsoft.Azure.Cosmos.Core.csproj new file mode 100644 index 0000000..a932fbb --- /dev/null +++ b/src/Core/Core/Microsoft.Azure.Cosmos.Core.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + true + + + + + + + diff --git a/src/Core/Core/SpanHexExtensions.cs b/src/Core/Core/SpanHexExtensions.cs new file mode 100644 index 0000000..504e867 --- /dev/null +++ b/src/Core/Core/SpanHexExtensions.cs @@ -0,0 +1,136 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core +{ + using System; + using System.Globalization; + + public static class SpanHexExtensions + { + private static readonly byte[] DecodeTable = + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + + private static readonly uint[] EncodeTable = SpanHexExtensions.Initialize(); + + public static unsafe string ToHexString(this ReadOnlySpan bytes) + { + int len = bytes.Length; + string result = new string((char)0, len * 2); + fixed (uint* lp = SpanHexExtensions.EncodeTable) + { + fixed (byte* bp = bytes) + { + fixed (char* rp = result) + { + for (int i = 0; i < len; i++) + { + ((uint*)rp)[i] = lp[bp[i]]; + } + } + } + } + + return result; + } + + public static bool TryParseHexString(this ReadOnlySpan hexChars, out byte[] result) + { + Contract.Requires(hexChars.Length % 2 == 0); + + int len = hexChars.Length; + result = new byte[len / 2]; + if (!hexChars.TryParseHexString(result.AsSpan())) + { + result = default; + return false; + } + + return true; + } + + public static unsafe bool TryParseHexString(this ReadOnlySpan hexChars, Span result) + { + Contract.Requires(hexChars.Length % 2 == 0); + Contract.Requires(result.Length == hexChars.Length / 2); + + int len = hexChars.Length; + fixed (byte* lp = SpanHexExtensions.DecodeTable) + { + fixed (char* cp = hexChars) + { + fixed (byte* rp = result) + { + for (int i = 0; i < len; i += 2) + { + int c1 = cp[i]; + if (c1 < 0 || c1 > 255) + { + return false; + } + + byte b1 = lp[c1]; + if (b1 == 255) + { + return false; + } + + int c2 = cp[i + 1]; + if (c2 < 0 || c2 > 255) + { + return false; + } + + byte b2 = lp[c2]; + if (b2 == 255) + { + return false; + } + + rp[i / 2] = (byte)((b1 << 4) | b2); + } + } + } + } + + return true; + } + + private static uint[] Initialize() + { + uint[] result = new uint[256]; + for (int i = 0; i < 256; i++) + { + string s = i.ToString("X2", CultureInfo.InvariantCulture); + if (BitConverter.IsLittleEndian) + { + result[i] = s[0] + ((uint)s[1] << 16); + } + else + { + result[i] = s[1] + ((uint)s[0] << 16); + } + } + + return result; + } + } +} diff --git a/src/Core/Core/Utf8/Readme.md b/src/Core/Core/Utf8/Readme.md new file mode 100644 index 0000000..27cdeec --- /dev/null +++ b/src/Core/Core/Utf8/Readme.md @@ -0,0 +1,34 @@ +This directory contains types derived from +[dotnet/corefxlab](https://github.com/dotnet/corefxlab) repo. This repo contains designs +proposed by the CLR team but not yet committed for inclusion in either the C# language +or the standard .NET Framework. The types included here (e.g. Utf8Span) may **never** +appear in the official standard. Including the types here lays a foundation for adopting +these types **if** they do become standard in the future. + +[[_TOC_]] + + +## Utf8Span +A readonly struct wrapping a sequence of bytes that are guaranteed to be a valid UTF8 +encoded string. + +A `Utf8Span` can be created over a `ReadOnlySpan` at the cost of validating the +byte sequence. Once the byte sequence has been validated then a `Utf8Span` can be passed +around safely without re-validating the content as UTF8. The type system is used to +enforce the correctness. + +## Utf8String +A readonly class wrapping a sequence of bytes that are guaranteed to be a valid UTF8 +encoded string. + +`Utf8String` is the heap equivalent of `Utf8Span` and provides the same capabilities. +`Utf8String` can be implicitly converted to `Utf8Span`. This conversion is guaranteed +to be cheap and non-allocating. Converting from a `Utf8Span` to a `Utf8String`, however, +requires a blittable copy of the content (but not re-validation). Additionally, `Utf8String` +can be converted to a `string` object via the expected transcode process. This operation is expensive. + +## UtfAnyString +A readonly struct wrapping either a `Utf8String` or a `string` object. The `UtfAnyString` +enables API's to accept both UTF8 and UTF16 encoded strings without requiring overloads. +UtfAnyString provides implicit conversion **from** either type, but explicit convert **to** +either type (because such a conversion *may* require a transcoding copy). \ No newline at end of file diff --git a/src/Core/Core/Utf8/Utf16LittleEndianCodePointEnumerator.cs b/src/Core/Core/Utf8/Utf16LittleEndianCodePointEnumerator.cs new file mode 100644 index 0000000..448af0a --- /dev/null +++ b/src/Core/Core/Utf8/Utf16LittleEndianCodePointEnumerator.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core.Utf8 +{ + using System; + + internal struct Utf16LittleEndianCodePointEnumerator + { + private readonly string str; + private int index; + private uint codePoint; + private bool hasValue; + + public Utf16LittleEndianCodePointEnumerator(string str) + { + Contract.Assert(BitConverter.IsLittleEndian); + + this.str = str; + this.index = 0; + this.codePoint = 0; + this.hasValue = false; + } + + public uint Current + { + get + { + Contract.Requires(this.hasValue); + return this.codePoint; + } + } + + public bool MoveNext() + { + if (this.index >= this.str.Length) + { + this.hasValue = false; + } + else + { + this.hasValue = Utf8Helper.TryDecodeCodePointFromString(this.str, this.index, out this.codePoint, out int charsConsumed); + Contract.Invariant(this.hasValue && charsConsumed > 0, "Invalid code point!"); + this.index += charsConsumed; + } + + return this.hasValue; + } + } +} diff --git a/src/Core/Core/Utf8/Utf8CodePointEnumerator.cs b/src/Core/Core/Utf8/Utf8CodePointEnumerator.cs new file mode 100644 index 0000000..8c96950 --- /dev/null +++ b/src/Core/Core/Utf8/Utf8CodePointEnumerator.cs @@ -0,0 +1,49 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core.Utf8 +{ + using System; + + public ref struct Utf8CodePointEnumerator + { + private readonly ReadOnlySpan utf8Bytes; + private int index; + private uint codePoint; + private bool hasValue; + + public Utf8CodePointEnumerator(ReadOnlySpan utf8Bytes) + { + this.utf8Bytes = utf8Bytes; + this.index = 0; + this.codePoint = 0; + this.hasValue = false; + } + + public uint Current + { + get + { + Contract.Requires(this.hasValue); + return this.codePoint; + } + } + + public bool MoveNext() + { + if (this.index >= this.utf8Bytes.Length) + { + this.hasValue = false; + } + else + { + this.hasValue = Utf8Helper.TryDecodeCodePoint(this.utf8Bytes, this.index, out this.codePoint, out int bytesConsumed); + Contract.Invariant(this.hasValue && bytesConsumed > 0, "Invalid code point!"); + this.index += bytesConsumed; + } + + return this.hasValue; + } + } +} diff --git a/src/Core/Core/Utf8/Utf8Helper.cs b/src/Core/Core/Utf8/Utf8Helper.cs new file mode 100644 index 0000000..06ae44a --- /dev/null +++ b/src/Core/Core/Utf8/Utf8Helper.cs @@ -0,0 +1,134 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Core.Utf8 +{ + using System; + + internal static class Utf8Helper + { + public const int MaxCodeUnitsPerCodePoint = 4; + + public static bool TryDecodeCodePoint(ReadOnlySpan utf8, int index, out uint codePoint, out int bytesConsumed) + { + if (index >= utf8.Length) + { + codePoint = default; + bytesConsumed = 0; + return false; + } + + byte first = utf8[index]; + + bytesConsumed = Utf8Helper.GetEncodedBytes(first); + if (bytesConsumed == 0 || utf8.Length - index < bytesConsumed) + { + bytesConsumed = 0; + codePoint = default; + return false; + } + + switch (bytesConsumed) + { + case 1: + codePoint = first; + break; + + case 2: + codePoint = (uint)(first & Utf8Helper.B0001_1111U); + break; + + case 3: + codePoint = (uint)(first & Utf8Helper.B0000_1111U); + break; + + case 4: + codePoint = (uint)(first & Utf8Helper.B0000_0111U); + break; + + default: + codePoint = default; + bytesConsumed = 0; + return false; + } + + for (int i = 1; i < bytesConsumed; i++) + { + uint current = utf8[index + i]; + if ((current & Utf8Helper.B1100_0000U) != Utf8Helper.B1000_0000U) + { + bytesConsumed = 0; + codePoint = default; + return false; + } + + codePoint = (codePoint << 6) | (Utf8Helper.B0011_1111U & current); + } + + return true; + } + + public static bool TryDecodeCodePointFromString(string s, int index, out uint codePoint, out int encodedChars) + { + if (index < 0 || index >= s.Length) + { + codePoint = default; + encodedChars = 0; + return false; + } + + if (index == s.Length - 1 && char.IsSurrogate(s[index])) + { + codePoint = default; + encodedChars = 0; + return false; + } + + encodedChars = char.IsHighSurrogate(s[index]) ? 2 : 1; + codePoint = unchecked((uint)char.ConvertToUtf32(s, index)); + + return true; + } + + private static int GetEncodedBytes(byte b) + { + if ((b & Utf8Helper.B1000_0000U) == 0) + { + return 1; + } + + if ((b & Utf8Helper.B1110_0000U) == Utf8Helper.B1100_0000U) + { + return 2; + } + + if ((b & Utf8Helper.B1111_0000U) == Utf8Helper.B1110_0000U) + { + return 3; + } + + if ((b & Utf8Helper.B1111_1000U) == Utf8Helper.B1111_0000U) + { + return 4; + } + + return 0; + } + + // ReSharper disable InconsistentNaming +#pragma warning disable SA1310 // Field names should not contain underscore + private const byte B0000_0111U = 0x07; //7 + private const byte B0000_1111U = 0x0F; //15 + private const byte B0001_1111U = 0x1F; //31 + private const byte B0011_1111U = 0x3F; //63 + private const byte B1000_0000U = 0x80; //128 + private const byte B1100_0000U = 0xC0; //192 + private const byte B1110_0000U = 0xE0; //224 + private const byte B1111_0000U = 0xF0; //240 + private const byte B1111_1000U = 0xF8; //248 + + // ReSharper restore InconsistentNaming +#pragma warning restore SA1310 // Field names should not contain underscore + } +} diff --git a/src/Core/Core/Utf8/Utf8Span.cs b/src/Core/Core/Utf8/Utf8Span.cs new file mode 100644 index 0000000..ebe5e17 --- /dev/null +++ b/src/Core/Core/Utf8/Utf8Span.cs @@ -0,0 +1,397 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1066 // Type {0} should implement IEquatable because it overrides Equals + +namespace Microsoft.Azure.Cosmos.Core.Utf8 +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using System.Text; + + // ReSharper disable once UseNameofExpression + [DebuggerDisplay("{ToString()}")] + public readonly ref struct Utf8Span + { + public static Utf8Span Empty => default; + + private readonly ReadOnlySpan buffer; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Utf8Span(ReadOnlySpan utf8Bytes) + { + this.buffer = utf8Bytes; + } + + /// Parses the sequence of bytes to prove it is valid UTF8. + /// The bytes to validate. + /// + /// If the sequence validates a that wraps the bytes in + /// , otherwise . + /// + /// True if the sequence validates, false otherwise. + public static bool TryParseUtf8Bytes(ReadOnlySpan utf8Bytes, out Utf8Span span) + { + int invalidIndex = Utf8Util.GetIndexOfFirstInvalidUtf8Sequence(utf8Bytes, out int _, out int _); + if (invalidIndex != -1) + { + span = default; + return false; + } + + span = new Utf8Span(utf8Bytes); + return true; + } + + /// Creates a without validating the underlying bytes. + /// The bytes claiming to be UTF8. + /// A wrapping . + /// + /// This method is dangerous as consumers of the must assume the + /// underlying bytes are indeed valid UTF8. The method should only be used when the UTF8 + /// sequence has already been externally valid or is known to be valid by construction. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Utf8Span UnsafeFromUtf8BytesNoValidation(ReadOnlySpan utf8Bytes) + { + return new Utf8Span(utf8Bytes); + } + + /// Creates a from a UTF16 encoding string. + /// The UTF16 encoding string. + /// A new . + /// + /// This method must transcode the UTF16 into UTF8 which both requires allocation and is a + /// size of data operation. + /// + public static Utf8Span TranscodeUtf16(string utf16String) + { + Contract.Requires(utf16String != null); + + if (string.IsNullOrEmpty(utf16String)) + { + return new Utf8Span(ReadOnlySpan.Empty); + } + + return new Utf8Span(Encoding.UTF8.GetBytes(utf16String)); + } + + /// The UTF8 byte sequence. + public ReadOnlySpan Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.buffer; + } + + /// The length in bytes of the UTF8 encoding. + public int Length => this.Span.Length; + + /// True if the length is empty. + public bool IsEmpty => this.Span.Length == 0; + + /// Non-allocating enumeration of each code point in the UTF8 stream. + public Utf8CodePointEnumerator GetEnumerator() + { + return new Utf8CodePointEnumerator(this.buffer); + } + + public override string ToString() + { + if (this.buffer.IsEmpty) + { + return string.Empty; + } + + unsafe + { + // ReSharper disable once ImpureMethodCallOnReadonlyValueField + fixed (byte* bytes = &this.buffer.GetPinnableReference()) + { + return Encoding.UTF8.GetString(bytes, this.buffer.Length); + } + } + } + + public bool ReferenceEquals(Utf8Span other) + { + return this.buffer == other.buffer; + } + + public bool Equals(Utf8Span other) + { + return this.buffer.SequenceEqual(other.buffer); + } + + public bool Equals(string other) + { + Contract.Requires(other != null); + + Utf8CodePointEnumerator thisEnumerator = this.GetEnumerator(); + Utf16LittleEndianCodePointEnumerator otherEnumerator = new Utf16LittleEndianCodePointEnumerator(other); + + while (true) + { + bool hasNext = thisEnumerator.MoveNext(); + if (hasNext != otherEnumerator.MoveNext()) + { + return false; + } + + if (!hasNext) + { + return true; + } + + if (thisEnumerator.Current != otherEnumerator.Current) + { + return false; + } + } + } + + public override bool Equals(object obj) + { + switch (obj) + { + case string s: + return this.Equals(s); + case Utf8String s: + return this.Equals(s.Span); + default: + return false; + } + } + + public override int GetHashCode() + { + unchecked + { + uint hash1 = 5381; + uint hash2 = hash1; + + Utf8CodePointEnumerator thisEnumerator = this.GetEnumerator(); + for (int i = 0; thisEnumerator.MoveNext(); i++) + { + uint c = thisEnumerator.Current; + if (i % 2 == 0) + { + hash1 = ((hash1 << 5) + hash1) ^ c; + } + else + { + hash2 = ((hash2 << 5) + hash2) ^ c; + } + } + + return (int)(hash1 + (hash2 * 1566083941)); + } + } + + public static bool operator ==(Utf8Span left, Utf8Span right) + { + return left.Equals(right); + } + + public static bool operator !=(Utf8Span left, Utf8Span right) + { + return !left.Equals(right); + } + + public static bool operator ==(Utf8Span left, string right) + { + return left.Equals(right); + } + + public static bool operator !=(Utf8Span left, string right) + { + return !left.Equals(right); + } + + public static bool operator ==(string left, Utf8Span right) + { + return right.Equals(left); + } + + public static bool operator !=(string left, Utf8Span right) + { + return !right.Equals(left); + } + + public static bool operator <(Utf8Span left, Utf8Span right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator <=(Utf8Span left, Utf8Span right) + { + return left.CompareTo(right) <= 0; + } + + public static bool operator >(Utf8Span left, Utf8Span right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator >=(Utf8Span left, Utf8Span right) + { + return left.CompareTo(right) >= 0; + } + + public static bool operator <(Utf8Span left, string right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator <=(Utf8Span left, string right) + { + return left.CompareTo(right) <= 0; + } + + public static bool operator >(Utf8Span left, string right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator >=(Utf8Span left, string right) + { + return left.CompareTo(right) >= 0; + } + + public static bool operator <(string left, Utf8Span right) + { + return right.CompareTo(left) >= 0; + } + + public static bool operator <=(string left, Utf8Span right) + { + return right.CompareTo(left) > 0; + } + + public static bool operator >(string left, Utf8Span right) + { + return right.CompareTo(left) <= 0; + } + + public static bool operator >=(string left, Utf8Span right) + { + return right.CompareTo(left) < 0; + } + + public int CompareTo(Utf8Span other) + { + ReadOnlySpan left = this.Span; + ReadOnlySpan right = other.Span; + int minLength = left.Length; + if (minLength > right.Length) + { + minLength = right.Length; + } + + for (int i = 0; i < minLength; i++) + { + int result = left[i].CompareTo(right[i]); + if (result != 0) + { + return result; + } + } + + return left.Length.CompareTo(right.Length); + } + + public int CompareTo(string other) + { + Contract.Requires(other != null); + + Utf8CodePointEnumerator thisEnumerator = this.GetEnumerator(); + Utf16LittleEndianCodePointEnumerator otherEnumerator = new Utf16LittleEndianCodePointEnumerator(other); + + while (true) + { + bool thisHasNext = thisEnumerator.MoveNext(); + bool otherHasNext = otherEnumerator.MoveNext(); + if (!thisHasNext && !otherHasNext) + { + return 0; + } + + if (!thisHasNext) + { + return -1; + } + + if (!otherHasNext) + { + return 1; + } + + uint thisCurrent = thisEnumerator.Current; + uint otherCurrent = otherEnumerator.Current; + + if (thisCurrent == otherCurrent) + { + continue; + } + + return thisCurrent.CompareTo(otherCurrent); + } + } + + /// + /// Returns true if this starts with (or equals) the second. + /// + /// The to compare. + /// If starts with. + public bool StartsWith(Utf8Span pattern) + { + return this.Span.StartsWith(pattern.Span); + } + + /// + /// Returns true if this ends with (or equals) the second. + /// + /// The to compare. + /// If starts with. + public bool EndsWith(Utf8Span pattern) + { + return this.Span.EndsWith(pattern.Span); + } + + /// + /// Returns true if a specified occurs within this . + /// + /// The to compare. + /// If contains. + public bool Contains(Utf8Span pattern) + { + return this.Span.IndexOf(pattern.Span) >= 0; + } + + /// + /// Splits a around first occurrence of into the left and right segments. + /// The pattern is not included in either left or right results. + /// + /// The split around. + /// The before the pattern. + /// The after the pattern. + /// True if success, false if does not contain pattern. + public bool TrySplitFirst(Utf8Span pattern, out Utf8Span left, out Utf8Span right) + { + int indexOfPattern = this.Span.IndexOf(pattern.Span); + + if (indexOfPattern < 0) + { + left = default; + right = default; + return false; + } + + left = new Utf8Span(this.Span.Slice(0, indexOfPattern)); + right = new Utf8Span(this.Span.Slice(indexOfPattern + pattern.Length)); + + return true; + } + } +} diff --git a/src/Core/Core/Utf8/Utf8String.cs b/src/Core/Core/Utf8/Utf8String.cs new file mode 100644 index 0000000..052d9f1 --- /dev/null +++ b/src/Core/Core/Utf8/Utf8String.cs @@ -0,0 +1,382 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1066 // Type {0} should implement IEquatable because it overrides Equals +#pragma warning disable CA2225 // Operator overloads have named alternates +#pragma warning disable IDE0041 // Use 'is null' check + +namespace Microsoft.Azure.Cosmos.Core.Utf8 +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using System.Text; + + // ReSharper disable once UseNameofExpression + [DebuggerDisplay("{ToString()}")] + public class Utf8String : IEquatable, IComparable, IEquatable, IComparable + { + private readonly ReadOnlyMemory buffer; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Utf8String(ReadOnlyMemory utf8Bytes) + { + this.buffer = utf8Bytes; + } + + public static readonly Utf8String Empty = new Utf8String(default); + + /// The UTF8 byte sequence. + public Utf8Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Utf8Span.UnsafeFromUtf8BytesNoValidation(this.buffer.Span); + } + + /// The length in bytes of the UTF8 encoding. + public int Length => this.buffer.Length; + + /// True if the length is empty. + public bool IsEmpty => this.buffer.Length == 0; + + /// Parses the sequence of bytes to prove it is valid UTF8. + /// The bytes to validate. + /// + /// If the sequence validates a that wraps the bytes in + /// , otherwise . + /// + /// The new takes ownership of . + /// True if the sequence validates, false otherwise. + public static bool TryParseUtf8Bytes(ReadOnlyMemory utf8Bytes, out Utf8String str) + { + int invalidIndex = Utf8Util.GetIndexOfFirstInvalidUtf8Sequence(utf8Bytes.Span, out int _, out int _); + if (invalidIndex != -1) + { + str = default; + return false; + } + + str = new Utf8String(utf8Bytes); + return true; + } + + /// Creates a without validating the underlying bytes. + /// The bytes claiming to be UTF8. + /// A wrapping . + /// + /// This method is dangerous as consumers of the must assume the + /// underlying bytes are indeed valid UTF8. The method should only be used when the UTF8 + /// sequence has already been externally valid or is known to be valid by construction. + /// + /// The new takes ownership of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Utf8String UnsafeFromUtf8BytesNoValidation(ReadOnlyMemory utf8Bytes) + { + Contract.Assert(Utf8Util.GetIndexOfFirstInvalidUtf8Sequence(utf8Bytes.Span, out int _, out int _) == -1); + return new Utf8String(utf8Bytes); + } + + /// Creates a from a . + /// The bytes that are UTF8. + /// A with contents from . + public static Utf8String CopyFrom(Utf8Span span) + { + Contract.Assert(Utf8Util.GetIndexOfFirstInvalidUtf8Sequence(span.Span, out int _, out int _) == -1); + return new Utf8String(span.Span.ToArray()); + } + + /// Creates a from a UTF16 encoding string. + /// The UTF16 encoding string. + /// A new . + /// + /// This method must transcode the UTF16 into UTF8 which both requires allocation and is a + /// size of data operation. + /// + public static Utf8String TranscodeUtf16(string utf16String) + { + if (object.ReferenceEquals(utf16String, null)) + { + return null; + } + + if (string.IsNullOrEmpty(utf16String)) + { + return new Utf8String(ReadOnlyMemory.Empty); + } + + return new Utf8String(Encoding.UTF8.GetBytes(utf16String)); + } + + /// over the string's content. + /// The string whose content is returned. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Utf8Span(Utf8String utf8String) + { + return (utf8String == null) ? default : utf8String.Span; + } + + public static implicit operator UtfAnyString(Utf8String utf8String) + { + return new UtfAnyString(utf8String); + } + + public static bool operator ==(Utf8String left, Utf8String right) + { + if (object.ReferenceEquals(null, left)) + { + return object.ReferenceEquals(null, right); + } + + return left.Equals(right); + } + + public static bool operator !=(Utf8String left, Utf8String right) + { + if (object.ReferenceEquals(null, left)) + { + return !object.ReferenceEquals(null, right); + } + + return !left.Equals(right); + } + + public static bool operator <(Utf8String left, Utf8String right) + { + return object.ReferenceEquals(left, null) ? !object.ReferenceEquals(right, null) : left.CompareTo(right) < 0; + } + + public static bool operator <=(Utf8String left, Utf8String right) + { + return object.ReferenceEquals(left, null) || left.CompareTo(right) <= 0; + } + + public static bool operator >(Utf8String left, Utf8String right) + { + return !object.ReferenceEquals(left, null) && left.CompareTo(right) > 0; + } + + public static bool operator >=(Utf8String left, Utf8String right) + { + return object.ReferenceEquals(left, null) ? object.ReferenceEquals(right, null) : left.CompareTo(right) >= 0; + } + + public static bool operator <(Utf8String left, string right) + { + return object.ReferenceEquals(left, null) ? !object.ReferenceEquals(right, null) : left.CompareTo(right) < 0; + } + + public static bool operator <=(Utf8String left, string right) + { + return object.ReferenceEquals(left, null) || left.CompareTo(right) <= 0; + } + + public static bool operator >(Utf8String left, string right) + { + return !object.ReferenceEquals(left, null) && left.CompareTo(right) > 0; + } + + public static bool operator >=(Utf8String left, string right) + { + return object.ReferenceEquals(left, null) ? object.ReferenceEquals(right, null) : left.CompareTo(right) >= 0; + } + + public static bool operator <(string left, Utf8String right) + { + return object.ReferenceEquals(right, null) ? object.ReferenceEquals(left, null) : right.CompareTo(left) >= 0; + } + + public static bool operator <=(string left, Utf8String right) + { + return !object.ReferenceEquals(right, null) && right.CompareTo(left) > 0; + } + + public static bool operator >(string left, Utf8String right) + { + return object.ReferenceEquals(right, null) || right.CompareTo(left) <= 0; + } + + public static bool operator >=(string left, Utf8String right) + { + return object.ReferenceEquals(right, null) ? !object.ReferenceEquals(left, null) : right.CompareTo(left) < 0; + } + + /// Non-allocating enumeration of each code point in the UTF8 stream. + public Utf8CodePointEnumerator GetEnumerator() + { + return this.Span.GetEnumerator(); + } + + public override string ToString() + { + return this.Span.ToString(); + } + + public bool Equals(Utf8Span other) + { + return this.Span.Equals(other); + } + + public bool Equals(Utf8String other) + { + if (object.ReferenceEquals(null, other)) + { + return false; + } + + if (object.ReferenceEquals(this, other)) + { + return true; + } + + return this.Span.Equals(other.Span); + } + + public bool Equals(string other) + { + if (object.ReferenceEquals(null, other)) + { + return false; + } + + return this.Span.Equals(other); + } + + public override bool Equals(object other) + { + if (object.ReferenceEquals(null, other)) + { + return false; + } + + if (object.ReferenceEquals(this, other)) + { + return true; + } + + return this.Span.Equals(other); + } + + public override int GetHashCode() + { + return this.Span.GetHashCode(); + } + + public int CompareTo(Utf8String other) + { + if (other == null) + { + return 1; + } + + return this.Span.CompareTo(other.Span); + } + + public int CompareTo(string other) + { + if (other == null) + { + return 1; + } + + return this.Span.CompareTo(other); + } + + /// + /// Splits a around first occurrence of into the left and right segments. + /// The pattern is not included in either left or right results. + /// + /// The split around. + /// The before the pattern. + /// The after the pattern. + /// True if success, false if does not contain pattern. + public bool TrySplitFirst(Utf8Span pattern, out Utf8String left, out Utf8String right) + { + int indexOfPattern = this.buffer.Span.IndexOf(pattern.Span); + + if (indexOfPattern < 0) + { + left = default; + right = default; + return false; + } + + left = new Utf8String(this.buffer.Slice(0, indexOfPattern)); + right = new Utf8String(this.buffer.Slice(indexOfPattern + pattern.Length)); + + return true; + } + + /// + /// Removes given from start and outputs resultant . + /// + /// The to remove. + /// The with value removed from start. + /// Is success. + public bool TryTrimLeft(Utf8Span pattern, out Utf8String output) + { + if (!this.Span.StartsWith(pattern)) + { + output = default; + return false; + } + + output = new Utf8String(this.buffer.Slice(pattern.Length)); + + return true; + } + + /// + /// Removes given from end and outputs resultant . + /// + /// The to remove. + /// The with value removed from end. + /// Is success. + public bool TryTrimRight(Utf8Span pattern, out Utf8String output) + { + if (!this.Span.EndsWith(pattern)) + { + output = default; + return false; + } + + output = new Utf8String(this.buffer.Slice(0, this.buffer.Length - pattern.Length)); + + return true; + } + + /// + /// Removes given from the start and + /// from the end and outputs resultant . + /// + /// The to remove from left. + /// The to remove from right. + /// The with value removed from end. + /// Will return false if patterns overlap in string to trim. + /// Is success. + public bool TryTrim(Utf8Span leftPattern, Utf8Span rightPattern, out Utf8String output) + { + if (!this.Span.StartsWith(leftPattern) || + !this.Span.EndsWith(rightPattern) || + (this.buffer.Length < leftPattern.Length + rightPattern.Length)) + { + output = default; + return false; + } + + output = new Utf8String(this.buffer.Slice(leftPattern.Length, this.buffer.Length - leftPattern.Length - rightPattern.Length)); + + return true; + } + + /// + /// Indicates whether the specified is null or an empty. + /// + /// + /// If null or empty. + public static bool IsNullOrEmpty(Utf8String utf8String) + { + return utf8String == null || utf8String.IsEmpty; + } + } +} diff --git a/src/Core/Core/Utf8/Utf8Util.Helpers.cs b/src/Core/Core/Utf8/Utf8Util.Helpers.cs new file mode 100644 index 0000000..e783465 --- /dev/null +++ b/src/Core/Core/Utf8/Utf8Util.Helpers.cs @@ -0,0 +1,419 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable SA1601 // Partial elements should be documented +#pragma warning disable SA1119 // Statement should not use unnecessary parenthesis + +// +// This file contains utility methods shared by the UTF-8 workhorse methods. +namespace Microsoft.Azure.Cosmos.Core.Utf8 +{ + using System; + using System.Runtime.CompilerServices; + + internal static partial class Utf8Util + { + /// + /// Returns iff is between + /// and , inclusive. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsInRangeInclusive(uint value, uint lowerBound, uint upperBound) + { + return unchecked((value - lowerBound) <= (upperBound - lowerBound)); + } + + /// Casts an to an without overflow checking. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ConvertIntPtrToInt32WithoutOverflowCheck(IntPtr value) + { + if (IntPtr.Size == 4) + { + return (int)value; + } + else + { + return (int)(long)value; + } + } + + /// + /// Given a 24-bit integer which represents a three-byte buffer read in machine endianness, + /// counts the number of consecutive ASCII bytes starting from the beginning of the buffer. Returns a + /// value 0 - 3, inclusive. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint CountNumberOfLeadingAsciiBytesFrom24BitInteger(uint value) + { + // The 'allBytesUpToNowAreAscii' DWORD uses bit twiddling to hold a 1 or a 0 depending + // on whether all processed bytes were ASCII. Then we accumulate all of the + // results to calculate how many consecutive ASCII bytes are present. + value = ~value; + + if (BitConverter.IsLittleEndian) + { + // Read first byte + uint allBytesUpToNowAreAscii = (value >>= 7) & 1; + uint numAsciiBytes = allBytesUpToNowAreAscii; + + // Read second byte + allBytesUpToNowAreAscii &= (value >>= 8); + numAsciiBytes += allBytesUpToNowAreAscii; + + // Read third byte + allBytesUpToNowAreAscii &= (value >>= 8); + numAsciiBytes += allBytesUpToNowAreAscii; + + return numAsciiBytes; + } + else + { + // Read first byte + uint allBytesUpToNowAreAscii = (value = Utf8Util.ROL32(value, 1)) & 1; + uint numAsciiBytes = allBytesUpToNowAreAscii; + + // Read second byte + allBytesUpToNowAreAscii &= (value = Utf8Util.ROL32(value, 8)); + numAsciiBytes += allBytesUpToNowAreAscii; + + // Read third byte + allBytesUpToNowAreAscii &= (_ = Utf8Util.ROL32(value, 8)); + numAsciiBytes += allBytesUpToNowAreAscii; + + return numAsciiBytes; + } + } + + /// Returns iff all bytes in are ASCII. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordAllBytesAreAscii(uint value) + { + return ((value & 0x80808080U) == 0U); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the buffer contains two UTF-8 sequences that match the mask [ 110yyyyy + /// 10xxxxxx 110yyyyy 10xxxxxx ]. This method *does not* validate that the sequences are well-formed; + /// the caller must still perform overlong form checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordBeginsAndEndsWithUtf8TwoByteMask(uint value) + { + // The code in this method is equivalent to the code + // below, but the JIT is able to inline + optimize it + // better in release builds. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0xC0E0C0E0U; + // const uint comparand = 0x80C080C0U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0xE0C0E0C0U; + // const uint comparand = 0xC080C080U; + // return ((value & mask) == comparand); + // } + return (BitConverter.IsLittleEndian && ((value & 0xC0E0C0E0U) == 0x80C080C0U)) || + (!BitConverter.IsLittleEndian && ((value & 0xE0C0E0C0U) == 0xC080C080U)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the first two bytes of the buffer are an overlong representation of a + /// sequence that should be represented as one byte. This method *does not* validate that the sequence + /// matches the appropriate 2-byte sequence mask (see ). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordBeginsWithOverlongUtf8TwoByteSequence(uint value) + { + // ASSUMPTION: Caller has already checked the '110yyyyy 10xxxxxx' mask of the input. + Contract.Assert(Utf8Util.DWordBeginsWithUtf8TwoByteMask(value)); + + // Per Table 3-7, first byte of two-byte sequence must be within range C2 .. DF. + // Since we already validated it's 80 <= ?? <= DF (per mask check earlier), now only need + // to check that it's < C2. + return (BitConverter.IsLittleEndian && (unchecked((byte)value) < (byte)0xC2)) || (!BitConverter.IsLittleEndian && (value < 0xC2000000U)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the first four bytes of the buffer match the UTF-8 4-byte sequence mask + /// [ 11110www 10zzzzzz 10yyyyyy 10xxxxxx ]. This method *does not* validate that the sequence is + /// well-formed; the caller must still perform overlong form or out-of-range checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordBeginsWithUtf8FourByteMask(uint value) + { + // The code in this method is equivalent to the code + // below, but the JIT is able to inline + optimize it + // better in release builds. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0xC0C0C0F8U; + // const uint comparand = 0x808080F0U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0xF8C0C0C0U; + // const uint comparand = 0xF0808000U; + // return ((value & mask) == comparand); + // } + return (BitConverter.IsLittleEndian && ((value & 0xC0C0C0F8U) == 0x808080F0U)) || + (!BitConverter.IsLittleEndian && ((value & 0xF8C0C0C0U) == 0xF0808000U)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the first three bytes of the buffer match the UTF-8 3-byte sequence + /// mask [ 1110zzzz 10yyyyyy 10xxxxxx ]. This method *does not* validate that the sequence is + /// well-formed; the caller must still perform overlong form or surrogate checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordBeginsWithUtf8ThreeByteMask(uint value) + { + // The code in this method is equivalent to the code + // below, but the JIT is able to inline + optimize it + // better in release builds. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0x00C0C0F0U; + // const uint comparand = 0x008080E0U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0xF0C0C000U; + // const uint comparand = 0xE0808000U; + // return ((value & mask) == comparand); + // } + return (BitConverter.IsLittleEndian && ((value & 0x00C0C0F0U) == 0x008080E0U)) || + (!BitConverter.IsLittleEndian && ((value & 0xF0C0C000U) == 0xE0808000U)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the first two bytes of the buffer match the UTF-8 2-byte sequence mask + /// [ 110yyyyy 10xxxxxx ]. This method *does not* validate that the sequence is well-formed; the caller + /// must still perform overlong form checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordBeginsWithUtf8TwoByteMask(uint value) + { + // The code in this method is equivalent to the code + // below, but the JIT is able to inline + optimize it + // better in release builds. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0x0000C0E0U; + // const uint comparand = 0x000080C0U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0xE0C00000U; + // const uint comparand = 0xC0800000U; + // return ((value & mask) == comparand); + // } + return (BitConverter.IsLittleEndian && ((value & 0x0000C0E0U) == 0x000080C0U)) || + (!BitConverter.IsLittleEndian && ((value & 0xE0C00000U) == 0xC0800000U)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the first two bytes of the buffer are an overlong representation of a + /// sequence that should be represented as one byte. This method *does not* validate that the sequence + /// matches the appropriate 2-byte sequence mask (see ). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordEndsWithOverlongUtf8TwoByteSequence(uint value) + { + // ASSUMPTION: Caller has already checked the '110yyyyy 10xxxxxx' mask of the input. + Contract.Assert(Utf8Util.DWordEndsWithUtf8TwoByteMask(value)); + + // Per Table 3-7, first byte of two-byte sequence must be within range C2 .. DF. + // We already validated that it's 80 .. DF (per mask check earlier). + // C2 = 1100 0010 + // DF = 1101 1111 + // This means that we can AND the leading byte with the mask 0001 1110 (1E), + // and if the result is zero the sequence is overlong. + return (BitConverter.IsLittleEndian && ((value & 0x001E0000U) == 0U)) || (!BitConverter.IsLittleEndian && ((value & 0x1E00U) == 0U)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the last two bytes of the buffer match the UTF-8 2-byte sequence mask [ + /// 110yyyyy 10xxxxxx ]. This method *does not* validate that the sequence is well-formed; the caller + /// must still perform overlong form checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordEndsWithUtf8TwoByteMask(uint value) + { + // The code in this method is equivalent to the code + // below, but the JIT is able to inline + optimize it + // better in release builds. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0xC0E00000U; + // const uint comparand = 0x80C00000U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0x0000E0C0U; + // const uint comparand = 0x0000C080U; + // return ((value & mask) == comparand); + // } + return (BitConverter.IsLittleEndian && ((value & 0xC0E00000U) == 0x80C00000U)) || + (!BitConverter.IsLittleEndian && ((value & 0x0000E0C0U) == 0x0000C080U)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD on a little-endian machine, returns + /// iff the first two bytes of the buffer are a well-formed UTF-8 two-byte + /// sequence. This wraps the mask check and the overlong check into a single operation. Returns + /// if running on a big-endian machine. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordBeginsWithValidUtf8TwoByteSequenceLittleEndian(uint value) + { + // Per Table 3-7, valid 2-byte sequences are [ C2..DF ] [ 80..BF ]. + // In little-endian, that would be represented as: + // [ ######## ######## 10xxxxxx 110yyyyy ]. + // Due to the little-endian representation we can perform a trick by ANDing the low + // WORD with the bitmask [ 11000000 11111111 ] and checking that the value is within + // the range [ 11000000_11000010, 11000000_11011111 ]. This performs both the + // 2-byte-sequence bitmask check and overlong form validation with one comparison. + Contract.Assert(BitConverter.IsLittleEndian); + + return (BitConverter.IsLittleEndian && Utf8Util.IsInRangeInclusive(value & 0xC0FFU, 0x80C2U, 0x80DFU)) || + (!BitConverter.IsLittleEndian && false); // this line - while weird - helps JIT produce optimal code + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD on a little-endian machine, returns + /// iff the last two bytes of the buffer are a well-formed UTF-8 two-byte + /// sequence. This wraps the mask check and the overlong check into a single operation. Returns + /// if running on a big-endian machine. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordEndsWithValidUtf8TwoByteSequenceLittleEndian(uint value) + { + // See comments in DWordBeginsWithValidUtf8TwoByteSequenceLittleEndian. + Contract.Assert(BitConverter.IsLittleEndian); + + return (BitConverter.IsLittleEndian && Utf8Util.IsInRangeInclusive(value & 0xC0FF0000U, 0x80C20000U, 0x80DF0000U)) || + (!BitConverter.IsLittleEndian && false); // this line - while weird - helps JIT produce optimal code + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the fourth byte of the buffer is ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordFourthByteIsAscii(uint value) + { + // The code in this method is equivalent to the code + // below, but the JIT is able to inline + optimize it + // better in release builds. + // + // if (BitConverter.IsLittleEndian) + // { + // return ((int)value >= 0); + // } + // else + // { + // return ((value & 0x80U) == 0U); + // } + return (BitConverter.IsLittleEndian && unchecked((int)value >= 0)) || (!BitConverter.IsLittleEndian && ((value & 0x80U) == 0U)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, returns + /// iff the third byte of the buffer is ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DWordThirdByteIsAscii(uint value) + { + // The code in this method is equivalent to the code + // below, but the JIT is able to inline + optimize it + // better in release builds. + // + // if (BitConverter.IsLittleEndian) + // { + // return ((value & 0x800000U) == 0U); + // } + // else + // { + // return ((value & 0x8000U) == 0U); + // } + return (BitConverter.IsLittleEndian && ((value & 0x800000U) == 0U)) || (!BitConverter.IsLittleEndian && ((value & 0x8000U) == 0U)); + } + + /// + /// Given a memory reference, returns the number of bytes that must be added to the reference + /// before the reference is DWORD-aligned. Returns a number in the range 0 - 3, inclusive. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int GetNumberOfBytesToNextDWordAlignment(ref byte @ref) + { + return unchecked((int)((uint)sizeof(uint) - ((uint)Unsafe.AsPointer(ref @ref) % sizeof(uint)))); + } + + /// Returns iff ( <= ). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe bool IntPtrIsLessThanOrEqualTo(IntPtr a, IntPtr b) + { + return (a.ToPointer() <= b.ToPointer()); + } + + /// + /// Returns iff the low byte of is a UTF-8 + /// continuation byte (10xxxxxx). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsUtf8ContinuationByte(uint value) + { + return ((value & 0xC0U) == 0x80U); + } + + /// Returns the OR of the next two DWORDs in the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ReadAndFoldTwoDWordsUnaligned(ref byte buffer) + { + return Unsafe.ReadUnaligned(ref buffer) | Unsafe.ReadUnaligned(ref Unsafe.Add(ref buffer, sizeof(uint))); + } + + /// Returns the OR of the next two QWORDs in the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ReadAndFoldTwoQWordsUnaligned(ref byte buffer) + { + return Unsafe.ReadUnaligned(ref buffer) | Unsafe.ReadUnaligned(ref Unsafe.Add(ref buffer, sizeof(ulong))); + } + + /// + /// Rotates a DWORD left. The JIT is smart enough to turn this into a ROL / ROR + /// instruction. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ROL32(uint value, int shift) + { + return (value << shift) | (value >> (32 - shift)); + } + + /// Returns iff all bytes in are ASCII. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool QWordAllBytesAreAscii(ulong value) + { + return ((value & 0x8080808080808080UL) == 0UL); + } + } +} diff --git a/src/Core/Core/Utf8/Utf8Util.Validation.cs b/src/Core/Core/Utf8/Utf8Util.Validation.cs new file mode 100644 index 0000000..020b4da --- /dev/null +++ b/src/Core/Core/Utf8/Utf8Util.Validation.cs @@ -0,0 +1,752 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable SA1512 // Single-line comments should not be followed by blank line +#pragma warning disable SA1601 // Partial elements should be documented + +// +// This file contains workhorse methods for performing validation of UTF-8 byte sequences. +namespace Microsoft.Azure.Cosmos.Core.Utf8 +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + internal static partial class Utf8Util + { + /// + /// Returns the offset in of where the first invalid UTF-8 sequence + /// appears, or -1 if the input is valid UTF-8 text. (Empty inputs are considered valid.) On method + /// return the parameter will contain the total number of Unicode + /// scalar values seen up to (but not including) the first invalid sequence, and + /// will contain the number of surrogate pairs present if this + /// text up to (but not including) the first invalid sequence were represented as UTF-16. To get the + /// total UTF-16 code unit count, add to + /// . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GetIndexOfFirstInvalidUtf8Sequence(ReadOnlySpan input, out int scalarCount, out int surrogatePairCount) + { + return Utf8.Utf8Util.GetIndexOfFirstInvalidUtf8Sequence( + ref MemoryMarshal.GetReference(input), + input.Length, + out scalarCount, + out surrogatePairCount); + } + + // This method will consume as many ASCII bytes as it can using fast vectorized processing, returning the number of + // consumed (ASCII) bytes. It's possible that the method exits early, perhaps because there is some non-ASCII byte + // later in the sequence or because we're running out of input to search. The intent is that the caller *skips over* + // the number of bytes returned by this method, then it continues data processing from the next byte. + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe IntPtr ConsumeAsciiBytesVectorized(ref byte buffer, int length) + { + // Only allow vectorization if vectors are hardware-accelerated and we have enough + // data to allow a vectorized search. + + if (!Vector.IsHardwareAccelerated || length < 3 * Vector.Count) + { + return IntPtr.Zero; + } + + // JITter will generate VMOVUPD instructions, which performs better when the memory to read + // is aligned. The GC may move the buffer around in memory, and while it will never cause + // moved data to be misaligned with respect to the natural word size, no such guarantee is + // made with respect to SIMD vector alignment. We'll pin the buffer so that we can enforce + // alignment manually. + + fixed (byte* pbBuffer = &buffer) + { + // [Potentially unaligned] single SIMD read and comparison, quick check for non-ASCII data. + + Vector mask = new Vector((byte)0x80); + if ((Unsafe.ReadUnaligned>(pbBuffer) & mask) != Vector.Zero) + { + return IntPtr.Zero; + } + + // Round 'pbBuffer' up to the *next* aligned address. If 'pbBuffer' was already aligned, this + // just bumps the address up to the next vector. The read above guaranteed that we read all + // data between 'pbBuffer' and 'pbAlignedBuffer' and checked it for non-ASCII bytes. It's + // possible we'll duplicate a little bit of work if 'pbBuffer' wasn't already aligned since + // its tail end may overlap with the immediate upcoming aligned read, but it's faster just to + // perform the extra work and not worry about checking for this condition. + + // 'pbAlignedBuffer' will be somewhere between 1 and Vector.Count bytes ahead of 'pbBuffer', + // hence the check for a length of >= 3 * Vector.Count at the beginning of this method. + + byte* pbAlignedBuffer; + if (IntPtr.Size >= 8) + { + pbAlignedBuffer = (byte*)(((long)pbBuffer + Vector.Count) & ~((long)Vector.Count - 1)); + } + else + { + pbAlignedBuffer = (byte*)(((int)pbBuffer + Vector.Count) & ~((int)Vector.Count - 1)); + } + + // Now iterate and read two aligned SIMD vectors at a time. We can skip the first length check on the + // first iteration of the loop since we already performed a length check at the very beginning of this + // method. + + byte* pbFinalPosAtWhichCanReadTwoVectors = &pbBuffer[length - (2 * Vector.Count)]; + Contract.Assert(pbAlignedBuffer <= pbFinalPosAtWhichCanReadTwoVectors); + + do + { + if (((Unsafe.Read>(pbAlignedBuffer) | Unsafe.Read>(pbAlignedBuffer + Vector.Count)) & mask) != + Vector.Zero) + { + break; // non-ASCII data incoming + } + } + while ((pbAlignedBuffer += 2 * Vector.Count) <= pbFinalPosAtWhichCanReadTwoVectors); + + // We consumed all data up to 'pbAlignedBuffer' and know it to be non-ASCII. + return (IntPtr)(pbAlignedBuffer - pbBuffer); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int GetIndexOfFirstInvalidUtf8Sequence(ref byte inputBuffer, int inputLength, out int scalarCount, out int surrogatePairCount) + { + // The fields below control where we read from the buffer. + + IntPtr inputBufferCurrentOffset = IntPtr.Zero; + int tempScalarCount = inputLength; + int tempSurrogatePairCount = 0; + + // If the sequence is long enough, try running vectorized "is this sequence ASCII?" + // logic. We perform a small test of the first few bytes to make sure they're all + // ASCII before we incur the cost of invoking the vectorized code path. + + if (Vector.IsHardwareAccelerated) + { + if (IntPtr.Size >= 8) + { + // Test first 16 bytes and check for all-ASCII. + if ((inputLength >= (2 * sizeof(ulong)) + (3 * Vector.Count)) && + Utf8.Utf8Util.QWordAllBytesAreAscii(Utf8.Utf8Util.ReadAndFoldTwoQWordsUnaligned(ref inputBuffer))) + { + inputBufferCurrentOffset = Utf8.Utf8Util.ConsumeAsciiBytesVectorized( + ref Unsafe.Add(ref inputBuffer, 2 * sizeof(ulong)), + inputLength - (2 * sizeof(ulong))) + + (2 * sizeof(ulong)); + } + } + else + { + // Test first 8 bytes and check for all-ASCII. + if ((inputLength >= (2 * sizeof(uint)) + (3 * Vector.Count)) && + Utf8.Utf8Util.DWordAllBytesAreAscii(Utf8.Utf8Util.ReadAndFoldTwoDWordsUnaligned(ref inputBuffer))) + { + inputBufferCurrentOffset = Utf8.Utf8Util.ConsumeAsciiBytesVectorized( + ref Unsafe.Add(ref inputBuffer, 2 * sizeof(uint)), + inputLength - (2 * sizeof(uint))) + + (2 * sizeof(uint)); + } + } + } + + int inputBufferRemainingBytes = inputLength - Utf8.Utf8Util.ConvertIntPtrToInt32WithoutOverflowCheck(inputBufferCurrentOffset); + + // Begin the main loop. + +#if DEBUG + long lastOffsetProcessed = -1; // used for invariant checking in debug builds +#endif + + while (inputBufferRemainingBytes >= sizeof(uint)) + { + // Read 32 bits at a time. This is enough to hold any possible UTF8-encoded scalar. + + Contract.Assert(inputLength - (int)inputBufferCurrentOffset >= sizeof(uint)); + uint thisDWord = Unsafe.ReadUnaligned(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + + AfterReadDWord: + +#if DEBUG + Contract.Assert(lastOffsetProcessed < (long)inputBufferCurrentOffset, "Algorithm should've made forward progress since last read."); + lastOffsetProcessed = (long)inputBufferCurrentOffset; +#endif + + // First, check for the common case of all-ASCII bytes. + + if (Utf8.Utf8Util.DWordAllBytesAreAscii(thisDWord)) + { + // We read an all-ASCII sequence. + + inputBufferCurrentOffset += 4; + inputBufferRemainingBytes -= 4; + + // If we saw a sequence of all ASCII, there's a good chance a significant amount of following data is also ASCII. + // Below is basically unrolled loops with poor man's vectorization. + + if (inputBufferRemainingBytes >= 5 * sizeof(uint)) + { + // The JIT produces better codegen for aligned reads than it does for + // unaligned reads, and we want the processor to operate at maximum + // efficiency in the loop that follows, so we'll align the references + // now. It's OK to do this without pinning because the GC will never + // move a heap-allocated object in a manner that messes with its + // alignment. + { + ref byte refToCurrentDWord = ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset); + thisDWord = Unsafe.ReadUnaligned(ref refToCurrentDWord); + if (!Utf8.Utf8Util.DWordAllBytesAreAscii(thisDWord)) + { + goto AfterReadDWordSkipAllBytesAsciiCheck; + } + + int adjustment = Utf8.Utf8Util.GetNumberOfBytesToNextDWordAlignment(ref refToCurrentDWord); + inputBufferCurrentOffset += adjustment; + + // will adjust 'bytes remaining' value after below loop + } + + // At this point, the input buffer offset points to an aligned DWORD. + // We also know that there's enough room to read at least four DWORD's from the stream. + + IntPtr inputBufferFinalOffsetAtWhichCanSafelyLoop = (IntPtr)(inputLength - (4 * sizeof(uint))); + do + { + ref uint currentReadPosition = ref Unsafe.As(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + + if (!Utf8.Utf8Util.DWordAllBytesAreAscii(currentReadPosition | Unsafe.Add(ref currentReadPosition, 1))) + { + goto LoopTerminatedEarlyDueToNonAsciiData; + } + + if (!Utf8.Utf8Util.DWordAllBytesAreAscii(Unsafe.Add(ref currentReadPosition, 2) | Unsafe.Add(ref currentReadPosition, 3))) + { + inputBufferCurrentOffset += 2 * sizeof(uint); + goto LoopTerminatedEarlyDueToNonAsciiData; + } + + inputBufferCurrentOffset += 4 * sizeof(uint); + } + while (Utf8.Utf8Util.IntPtrIsLessThanOrEqualTo(inputBufferCurrentOffset, inputBufferFinalOffsetAtWhichCanSafelyLoop)); + + inputBufferRemainingBytes = inputLength - Utf8.Utf8Util.ConvertIntPtrToInt32WithoutOverflowCheck(inputBufferCurrentOffset); + continue; // need to perform a bounds check because we might be running out of data + + LoopTerminatedEarlyDueToNonAsciiData: + + // We know that there's *at least* two DWORD's of data remaining in the buffer. + // We also know that one of them (or both of them) contains non-ASCII data somewhere. + // Let's perform a quick check here to bypass the logic at the beginning of the main loop. + + thisDWord = Unsafe.As(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + if (Utf8.Utf8Util.DWordAllBytesAreAscii(thisDWord)) + { + inputBufferCurrentOffset += 4; + thisDWord = Unsafe.As(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + } + + inputBufferRemainingBytes = inputLength - Utf8.Utf8Util.ConvertIntPtrToInt32WithoutOverflowCheck(inputBufferCurrentOffset); + goto AfterReadDWordSkipAllBytesAsciiCheck; + } + + continue; + } + + AfterReadDWordSkipAllBytesAsciiCheck: + + Contract.Assert(!Utf8.Utf8Util.DWordAllBytesAreAscii(thisDWord)); // this should have been handled earlier + + // Next, try stripping off ASCII bytes one at a time. + // We only handle up to three ASCII bytes here since we handled the four ASCII byte case above. + { + uint numLeadingAsciiBytes = Utf8.Utf8Util.CountNumberOfLeadingAsciiBytesFrom24BitInteger(thisDWord); + inputBufferCurrentOffset += (int)numLeadingAsciiBytes; + inputBufferRemainingBytes -= (int)numLeadingAsciiBytes; + + if (inputBufferRemainingBytes < sizeof(uint)) + { + goto ProcessRemainingBytesSlow; // Input buffer doesn't contain enough data to read a DWORD + } + else + { + // The input buffer at the current offset contains a non-ASCII byte. + // Read an entire DWORD and fall through to multi-byte consumption logic. + thisDWord = Unsafe.ReadUnaligned(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + } + } + + // At this point, we know we're working with a multi-byte code unit, + // but we haven't yet validated it. + + // The masks and comparands are derived from the Unicode Standard, Table 3-6. + // Additionally, we need to check for valid byte sequences per Table 3-7. + + // Check the 2-byte case. + + BeforeProcessTwoByteSequence: + + if (Utf8.Utf8Util.DWordBeginsWithUtf8TwoByteMask(thisDWord)) + { + // Per Table 3-7, valid sequences are: + // [ C2..DF ] [ 80..BF ] + + if (Utf8.Utf8Util.DWordBeginsWithOverlongUtf8TwoByteSequence(thisDWord)) + { + goto Error; + } + + ProcessTwoByteSequenceSkipOverlongFormCheck: + + // Optimization: If this is a two-byte-per-character language like Cyrillic or Hebrew, + // there's a good chance that if we see one two-byte run then there's another two-byte + // run immediately after. Let's check that now. + + // On little-endian platforms, we can check for the two-byte UTF8 mask *and* validate that + // the value isn't overlong using a single comparison. On big-endian platforms, we'll need + // to validate the mask and validate that the sequence isn't overlong as two separate comparisons. + + if ((BitConverter.IsLittleEndian && Utf8.Utf8Util.DWordEndsWithValidUtf8TwoByteSequenceLittleEndian(thisDWord)) || + (!BitConverter.IsLittleEndian && + (Utf8.Utf8Util.DWordEndsWithUtf8TwoByteMask(thisDWord) && !Utf8.Utf8Util.DWordEndsWithOverlongUtf8TwoByteSequence(thisDWord)))) + { + ConsumeTwoAdjacentKnownGoodTwoByteSequences: + + // We have two runs of two bytes each. + inputBufferCurrentOffset += 4; + inputBufferRemainingBytes -= 4; + tempScalarCount -= 2; // 4 bytes -> 2 scalars + + if (inputBufferRemainingBytes >= sizeof(uint)) + { + // Optimization: If we read a long run of two-byte sequences, the next sequence is probably + // also two bytes. Check for that first before going back to the beginning of the loop. + + thisDWord = Unsafe.ReadUnaligned(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + + if (BitConverter.IsLittleEndian) + { + if (Utf8.Utf8Util.DWordBeginsWithValidUtf8TwoByteSequenceLittleEndian(thisDWord)) + { + // The next sequence is a valid two-byte sequence. + goto ProcessTwoByteSequenceSkipOverlongFormCheck; + } + } + else + { + if (Utf8.Utf8Util.DWordBeginsAndEndsWithUtf8TwoByteMask(thisDWord)) + { + if (Utf8.Utf8Util.DWordBeginsWithOverlongUtf8TwoByteSequence(thisDWord) || + Utf8.Utf8Util.DWordEndsWithOverlongUtf8TwoByteSequence(thisDWord)) + { + // Mask said it was 2x 2-byte sequences but validation failed, go to beginning of loop for error handling + goto AfterReadDWord; + } + else + { + // Validated next bytes are 2x 2-byte sequences + goto ConsumeTwoAdjacentKnownGoodTwoByteSequences; + } + } + else if (Utf8.Utf8Util.DWordBeginsWithUtf8TwoByteMask(thisDWord)) + { + if (Utf8.Utf8Util.DWordBeginsWithOverlongUtf8TwoByteSequence(thisDWord)) + { + // Mask said it was a 2-byte sequence but validation failed + goto Error; + } + else + { + // Validated next bytes are a single 2-byte sequence with no valid 2-byte sequence following + goto ConsumeSingleKnownGoodTwoByteSequence; + } + } + } + + // If we reached this point, the next sequence is something other than a valid + // two-byte sequence, so go back to the beginning of the loop. + goto AfterReadDWord; + } + else + { + goto ProcessRemainingBytesSlow; // Running out of data - go down slow path + } + } + + ConsumeSingleKnownGoodTwoByteSequence: + + // The buffer contains a 2-byte sequence followed by 2 bytes that aren't a 2-byte sequence. + // Unlikely that a 3-byte sequence would follow a 2-byte sequence, so perhaps remaining + // bytes are ASCII? + + if (Utf8.Utf8Util.DWordThirdByteIsAscii(thisDWord)) + { + if (Utf8.Utf8Util.DWordFourthByteIsAscii(thisDWord)) + { + inputBufferCurrentOffset += 4; // a 2-byte sequence + 2 ASCII bytes + inputBufferRemainingBytes -= 4; // a 2-byte sequence + 2 ASCII bytes + tempScalarCount--; // 2-byte sequence + 2 ASCII bytes -> 3 scalars + } + else + { + inputBufferCurrentOffset += 3; // a 2-byte sequence + 1 ASCII byte + inputBufferRemainingBytes -= 3; // a 2-byte sequence + 1 ASCII byte + tempScalarCount--; // 2-byte sequence + 1 ASCII bytes -> 2 scalars + + // A two-byte sequence followed by an ASCII byte followed by a non-ASCII byte. + // Read in the next DWORD and jump directly to the start of the multi-byte processing block. + + if (inputBufferRemainingBytes >= sizeof(uint)) + { + thisDWord = Unsafe.ReadUnaligned(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + goto BeforeProcessTwoByteSequence; + } + } + } + else + { + inputBufferCurrentOffset += 2; + inputBufferRemainingBytes -= 2; + tempScalarCount--; // 2-byte sequence -> 1 scalar + } + + continue; + } + + // Check the 3-byte case. + + if (Utf8.Utf8Util.DWordBeginsWithUtf8ThreeByteMask(thisDWord)) + { + ProcessThreeByteSequenceWithCheck: + + // We need to check for overlong or surrogate three-byte sequences. + // + // Per Table 3-7, valid sequences are: + // [ E0 ] [ A0..BF ] [ 80..BF ] + // [ E1..EC ] [ 80..BF ] [ 80..BF ] + // [ ED ] [ 80..9F ] [ 80..BF ] + // [ EE..EF ] [ 80..BF ] [ 80..BF ] + // + // Big-endian examples of using the above validation table: + // E0A0 = 1110 0000 1010 0000 => invalid (overlong ) patterns are 1110 0000 100# #### + // ED9F = 1110 1101 1001 1111 => invalid (surrogate) patterns are 1110 1101 101# #### + // If using the bitmask ......................................... 0000 1111 0010 0000 (=0F20), + // Then invalid (overlong) patterns match the comparand ......... 0000 0000 0000 0000 (=0000), + // And invalid (surrogate) patterns match the comparand ......... 0000 1101 0010 0000 (=0D20). + + if (BitConverter.IsLittleEndian) + { + // The "overlong or surrogate" check can be implemented using a single jump, but there's + // some overhead to moving the bits into the correct locations in order to perform the + // correct comparison, and in practice the processor's branch prediction capability is + // good enough that we shouldn't bother. So we'll use two jumps instead. + + // Can't extract this check into its own helper method because JIT produces suboptimal + // assembly, even with aggressive inlining. + + uint comparand = thisDWord & 0x0000200FU; + if ((comparand == 0U) || (comparand == 0x0000200DU)) + { + goto Error; + } + } + else + { + uint comparand = thisDWord & 0x0F200000U; + if ((comparand == 0U) || (comparand == 0x0D200000U)) + { + goto Error; + } + } + + ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks: + + inputBufferCurrentOffset += 3; + inputBufferRemainingBytes -= 3; + tempScalarCount -= 2; // 3 bytes -> 1 scalar + + // Occasionally one-off ASCII characters like spaces, periods, or newlines will make their way + // in to the text. If this happens strip it off now before seeing if the next character + // consists of three code units. + + if (Utf8.Utf8Util.DWordFourthByteIsAscii(thisDWord)) + { + inputBufferCurrentOffset += 1; + inputBufferRemainingBytes--; + } + + SuccessfullyProcessedThreeByteSequence: + + // Optimization: A three-byte character could indicate CJK text, which makes it likely + // that the character following this one is also CJK. We'll try to process several + // three-byte sequences at a time. + + if (IntPtr.Size >= 8 && BitConverter.IsLittleEndian && inputBufferRemainingBytes >= (sizeof(ulong) + 1)) + { + ulong thisQWord = Unsafe.ReadUnaligned(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + + // Is this three 3-byte sequences in a row? + // thisQWord = [ 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz ] [ 10xxxxxx ] + // ---- CHAR 3 ---- --------- CHAR 2 --------- --------- CHAR 1 --------- -CHAR 3- + if ((thisQWord & 0xC0F0C0C0F0C0C0F0UL) == 0x80E08080E08080E0UL && + Utf8.Utf8Util.IsUtf8ContinuationByte(Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset + sizeof(ulong)))) + { + // Saw a proper bitmask for three incoming 3-byte sequences, perform the + // overlong and surrogate sequence checking now. + + // Check the first character. + // If the first character is overlong or a surrogate, fail immediately. + + uint comparand = unchecked((uint)thisQWord) & 0x200FU; + if ((comparand == 0UL) || (comparand == 0x200DU)) + { + goto Error; + } + + // Check the second character. + // If this character is overlong or a surrogate, process the first character (which we + // know to be good because the first check passed) before reporting an error. + + comparand = unchecked((uint)(thisQWord >> 24)) & 0x200FU; + if ((comparand == 0U) || (comparand == 0x200DU)) + { + thisDWord = unchecked((uint)thisQWord); + goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; + } + + // Check the third character (we already checked that it's followed by a continuation byte). + // If this character is overlong or a surrogate, process the first character (which we + // know to be good because the first check passed) before reporting an error. + + comparand = unchecked((uint)(thisQWord >> 48)) & 0x200FU; + if ((comparand == 0U) || (comparand == 0x200DU)) + { + thisDWord = unchecked((uint)thisQWord); + goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; + } + + inputBufferCurrentOffset += 9; + inputBufferRemainingBytes -= 9; + tempScalarCount -= 6; // 9 bytes -> 3 scalars + goto SuccessfullyProcessedThreeByteSequence; + } + + // Is this two 3-byte sequences in a row? + // thisQWord = [ ######## ######## | 10xxxxxx 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz ] + // --------- CHAR 2 --------- --------- CHAR 1 --------- + if ((thisQWord & 0xC0C0F0C0C0F0UL) == 0x8080E08080E0UL) + { + // Saw a proper bitmask for two incoming 3-byte sequences, perform the + // overlong and surrogate sequence checking now. + + // Check the first character. + // If the first character is overlong or a surrogate, fail immediately. + + uint comparand = unchecked((uint)thisQWord) & 0x200FU; + if ((comparand == 0UL) || (comparand == 0x200DU)) + { + goto Error; + } + + // Check the second character. + // If this character is overlong or a surrogate, process the first character (which we + // know to be good because the first check passed) before reporting an error. + + comparand = unchecked((uint)(thisQWord >> 24)) & 0x200FU; + if ((comparand == 0U) || (comparand == 0x200DU)) + { + thisDWord = unchecked((uint)thisQWord); + goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; + } + + inputBufferCurrentOffset += 6; + inputBufferRemainingBytes -= 6; + tempScalarCount -= 4; // 6 bytes -> 2 scalars + + // The next char in the sequence didn't have a 3-byte marker, so it's probably + // an ASCII character. Jump back to the beginning of loop processing. + continue; + } + + thisDWord = unchecked((uint)thisQWord); + if (Utf8.Utf8Util.DWordBeginsWithUtf8ThreeByteMask(thisDWord)) + { + // A single three-byte sequence. + goto ProcessThreeByteSequenceWithCheck; + } + else + { + // Not a three-byte sequence; perhaps ASCII? + goto AfterReadDWord; + } + } + + if (inputBufferRemainingBytes >= sizeof(uint)) + { + thisDWord = Unsafe.ReadUnaligned(ref Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset)); + + // Optimization: A three-byte character could indicate CJK text, which makes it likely + // that the character following this one is also CJK. We'll check for a three-byte sequence + // marker now and jump directly to three-byte sequence processing if we see one, skipping + // all of the logic at the beginning of the loop. + + if (Utf8.Utf8Util.DWordBeginsWithUtf8ThreeByteMask(thisDWord)) + { + goto ProcessThreeByteSequenceWithCheck; // Found another [not yet validated] three-byte sequence; process + } + else + { + goto AfterReadDWord; // Probably ASCII punctuation or whitespace; go back to start of loop + } + } + else + { + goto ProcessRemainingBytesSlow; // Running out of data + } + } + + // Assume the 4-byte case, but we need to validate. + + { + // We need to check for overlong or invalid (over U+10FFFF) four-byte sequences. + // + // Per Table 3-7, valid sequences are: + // [ F0 ] [ 90..BF ] [ 80..BF ] [ 80..BF ] + // [ F1..F3 ] [ 80..BF ] [ 80..BF ] [ 80..BF ] + // [ F4 ] [ 80..8F ] [ 80..BF ] [ 80..BF ] + + if (!Utf8.Utf8Util.DWordBeginsWithUtf8FourByteMask(thisDWord)) + { + goto Error; + } + + // Now check for overlong / out-of-range sequences. + + if (BitConverter.IsLittleEndian) + { + // The DWORD we read is [ 10xxxxxx 10yyyyyy 10zzzzzz 11110www ]. + // We want to get the 'w' byte in front of the 'z' byte so that we can perform + // a single range comparison. We'll take advantage of the fact that the JITter + // can detect a ROR / ROL operation, then we'll just zero out the bytes that + // aren't involved in the range check. + + uint toCheck = unchecked((ushort)thisDWord); + + // At this point, toCheck = [ 00000000 00000000 10zzzzzz 11110www ]. + + toCheck = (toCheck << 24) | (toCheck >> 8); // ROR 8 / ROL 24 + + // At this point, toCheck = [ 11110www 00000000 00000000 10zzzzzz ]. + + if (!Utf8.Utf8Util.IsInRangeInclusive(toCheck, 0xF0000090U, 0xF400008FU)) + { + goto Error; + } + } + else + { + if (!Utf8.Utf8Util.IsInRangeInclusive(thisDWord, 0xF0900000U, 0xF48FFFFFU)) + { + goto Error; + } + } + + // Validation complete. + + inputBufferCurrentOffset += 4; + inputBufferRemainingBytes -= 4; + tempScalarCount -= 3; // 4 bytes -> 1 scalar + tempSurrogatePairCount++; // 4 bytes implies UTF16 surrogate pair + + continue; // go back to beginning of loop for processing + } + } + + ProcessRemainingBytesSlow: + + Contract.Assert(inputBufferRemainingBytes < 4); + while (inputBufferRemainingBytes > 0) + { + uint firstByte = Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset); + + if (firstByte < 0x80U) + { + // 1-byte (ASCII) case + inputBufferCurrentOffset += 1; + inputBufferRemainingBytes -= 1; + continue; + } + else if (inputBufferRemainingBytes >= 2) + { + uint secondByte = Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset + 1); + if (firstByte < 0xE0U) + { + // 2-byte case + if (firstByte >= 0xC2U && Utf8.Utf8Util.IsUtf8ContinuationByte(secondByte)) + { + inputBufferCurrentOffset += 2; + inputBufferRemainingBytes -= 2; + tempScalarCount--; // 2 bytes -> 1 scalar + continue; + } + } + else if (inputBufferRemainingBytes >= 3) + { + uint thirdByte = Unsafe.Add(ref inputBuffer, inputBufferCurrentOffset + 2); + if (firstByte < 0xF0U) + { + if (firstByte == 0xE0U) + { + if (!Utf8.Utf8Util.IsInRangeInclusive(secondByte, 0xA0U, 0xBFU)) + { + goto Error; + } + } + else if (firstByte == 0xEDU) + { + if (!Utf8.Utf8Util.IsInRangeInclusive(secondByte, 0x80U, 0x9FU)) + { + goto Error; + } + } + else + { + if (!Utf8.Utf8Util.IsUtf8ContinuationByte(secondByte)) + { + goto Error; + } + } + + if (Utf8.Utf8Util.IsUtf8ContinuationByte(thirdByte)) + { + inputBufferCurrentOffset += 3; + inputBufferRemainingBytes -= 3; + tempScalarCount -= 2; // 3 bytes -> 1 scalar + continue; + } + } + } + } + + // Error - no match. + + goto Error; + } + + // If we reached this point, we're out of data, and we saw no bad UTF8 sequence. + + scalarCount = tempScalarCount; + surrogatePairCount = tempSurrogatePairCount; + return -1; + + // Error handling logic. + + Error: + + scalarCount = tempScalarCount - + inputBufferRemainingBytes; // we assumed earlier each byte corresponded to a single scalar, perform fixup now to account for unread bytes + + surrogatePairCount = tempSurrogatePairCount; + return Utf8.Utf8Util.ConvertIntPtrToInt32WithoutOverflowCheck(inputBufferCurrentOffset); + } + } +} diff --git a/dotnet/src/Core/UtfAnyString.cs b/src/Core/Core/Utf8/UtfAnyString.cs similarity index 100% rename from dotnet/src/Core/UtfAnyString.cs rename to src/Core/Core/Utf8/UtfAnyString.cs diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..878f332 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,52 @@ + + + + 8.0 + true + $(MSBuildThisFileDirectory)\CodeAnalysis.ruleset + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + v142 + Unicode + + + true + + + + + 4706;4189;4701;4244;%(TreatSpecificWarningsAsErrors) + false + stdcpplatest + + true + true + true + + + DebugFull + + + + + + ProgramDatabase + + + true + true + + + diff --git a/src/HybridRow.sln b/src/HybridRow.sln new file mode 100644 index 0000000..8b74d7b --- /dev/null +++ b/src/HybridRow.sln @@ -0,0 +1,181 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29926.136 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Serialization.HybridRow", "Serialization\HybridRow\Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj", "{490D42EE-1FEF-47CC-97E4-782A353B4D58}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Core", "Core\Core\Microsoft.Azure.Cosmos.Core.csproj", "{4955C705-BA27-4813-83EF-A55A6B6A49CD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator", "Serialization\HybridRowGenerator\Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj", "{A1522622-82DF-4ADF-A6BC-7E2992C97BBF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit", "Serialization\HybridRow.Tests.Unit\Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.csproj", "{61A97067-8426-4EA3-A176-91E1C50BC279}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Serialization.HybridRow.Json", "Serialization\HybridRow.Json\Microsoft.Azure.Cosmos.Serialization.HybridRow.Json.csproj", "{6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf", "Serialization\HybridRow.Tests.Perf\Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.csproj", "{0FC8DEDB-1DEF-477B-A669-029A60109FA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Serialization.HybridRowCLI", "Serialization\HybridRowCLI\Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.csproj", "{AFAA0AAB-F62C-400F-AFB4-22D902732721}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Serialization.HybridRowStress", "Serialization\HybridRowStress\Microsoft.Azure.Cosmos.Serialization.HybridRowStress.csproj", "{0E1D663C-398A-4CD9-A1B6-C273FEDC801B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HybridRow.Native", "Serialization\HybridRow.Native\HybridRow.Native.vcxproj", "{D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Azure.Cosmos.Core.Native", "Core\Core.Native\Microsoft.Azure.Cosmos.Core.Native.vcxproj", "{EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Azure.Cosmos.Core.Native.Tests.Unit", "Core\Core.Native.Tests.Unit\Microsoft.Azure.Cosmos.Core.Native.Tests.Unit.vcxproj", "{BE99633B-5D7D-41DA-8550-035E6689B243}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HybridRow.Native.Tests.Unit", "Serialization\HybridRow.Native.Tests.Unit\HybridRow.Native.Tests.Unit.vcxproj", "{209D2BFA-982F-4BB7-ADEA-C63B926152B3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Debug|x64.ActiveCfg = Debug|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Debug|x64.Build.0 = Debug|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Debug|x86.ActiveCfg = Debug|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Debug|x86.Build.0 = Debug|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Release|Any CPU.Build.0 = Release|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Release|x64.ActiveCfg = Release|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Release|x64.Build.0 = Release|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Release|x86.ActiveCfg = Release|Any CPU + {490D42EE-1FEF-47CC-97E4-782A353B4D58}.Release|x86.Build.0 = Release|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Debug|x64.ActiveCfg = Debug|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Debug|x64.Build.0 = Debug|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Debug|x86.ActiveCfg = Debug|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Debug|x86.Build.0 = Debug|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Release|Any CPU.Build.0 = Release|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Release|x64.ActiveCfg = Release|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Release|x64.Build.0 = Release|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Release|x86.ActiveCfg = Release|Any CPU + {4955C705-BA27-4813-83EF-A55A6B6A49CD}.Release|x86.Build.0 = Release|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Debug|x64.Build.0 = Debug|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Debug|x86.Build.0 = Debug|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Release|Any CPU.Build.0 = Release|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Release|x64.ActiveCfg = Release|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Release|x64.Build.0 = Release|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Release|x86.ActiveCfg = Release|Any CPU + {A1522622-82DF-4ADF-A6BC-7E2992C97BBF}.Release|x86.Build.0 = Release|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Debug|x64.ActiveCfg = Debug|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Debug|x64.Build.0 = Debug|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Debug|x86.ActiveCfg = Debug|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Debug|x86.Build.0 = Debug|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Release|Any CPU.Build.0 = Release|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Release|x64.ActiveCfg = Release|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Release|x64.Build.0 = Release|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Release|x86.ActiveCfg = Release|Any CPU + {61A97067-8426-4EA3-A176-91E1C50BC279}.Release|x86.Build.0 = Release|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Debug|x64.Build.0 = Debug|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Debug|x86.Build.0 = Debug|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Release|Any CPU.Build.0 = Release|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Release|x64.ActiveCfg = Release|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Release|x64.Build.0 = Release|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Release|x86.ActiveCfg = Release|Any CPU + {6CEE8A59-74F2-40A9-AA66-E6A6EEFDEEEE}.Release|x86.Build.0 = Release|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Debug|x64.ActiveCfg = Debug|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Debug|x64.Build.0 = Debug|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Debug|x86.ActiveCfg = Debug|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Debug|x86.Build.0 = Debug|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Release|Any CPU.Build.0 = Release|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Release|x64.ActiveCfg = Release|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Release|x64.Build.0 = Release|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Release|x86.ActiveCfg = Release|Any CPU + {0FC8DEDB-1DEF-477B-A669-029A60109FA0}.Release|x86.Build.0 = Release|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Debug|x64.ActiveCfg = Debug|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Debug|x64.Build.0 = Debug|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Debug|x86.ActiveCfg = Debug|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Debug|x86.Build.0 = Debug|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Release|Any CPU.Build.0 = Release|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Release|x64.ActiveCfg = Release|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Release|x64.Build.0 = Release|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Release|x86.ActiveCfg = Release|Any CPU + {AFAA0AAB-F62C-400F-AFB4-22D902732721}.Release|x86.Build.0 = Release|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Debug|x64.ActiveCfg = Debug|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Debug|x64.Build.0 = Debug|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Debug|x86.Build.0 = Debug|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Release|Any CPU.Build.0 = Release|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Release|x64.ActiveCfg = Release|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Release|x64.Build.0 = Release|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Release|x86.ActiveCfg = Release|Any CPU + {0E1D663C-398A-4CD9-A1B6-C273FEDC801B}.Release|x86.Build.0 = Release|Any CPU + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Debug|Any CPU.ActiveCfg = Debug|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Debug|Any CPU.Build.0 = Debug|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Debug|x64.ActiveCfg = Debug|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Debug|x64.Build.0 = Debug|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Debug|x86.ActiveCfg = Debug|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Release|Any CPU.ActiveCfg = Release|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Release|Any CPU.Build.0 = Release|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Release|x64.ActiveCfg = Release|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Release|x64.Build.0 = Release|x64 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894}.Release|x86.ActiveCfg = Release|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Debug|Any CPU.Build.0 = Debug|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Debug|x64.ActiveCfg = Debug|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Debug|x64.Build.0 = Debug|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Debug|x86.ActiveCfg = Debug|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Release|Any CPU.ActiveCfg = Release|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Release|Any CPU.Build.0 = Release|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Release|x64.ActiveCfg = Release|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Release|x64.Build.0 = Release|x64 + {EAED7D41-3DE6-4C41-A0E4-40D53EA3DABA}.Release|x86.ActiveCfg = Release|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Debug|Any CPU.ActiveCfg = Debug|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Debug|Any CPU.Build.0 = Debug|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Debug|x64.ActiveCfg = Debug|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Debug|x64.Build.0 = Debug|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Debug|x86.ActiveCfg = Debug|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Release|Any CPU.ActiveCfg = Release|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Release|Any CPU.Build.0 = Release|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Release|x64.ActiveCfg = Release|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Release|x64.Build.0 = Release|x64 + {BE99633B-5D7D-41DA-8550-035E6689B243}.Release|x86.ActiveCfg = Release|x64 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3}.Debug|Any CPU.ActiveCfg = Debug|x64 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3}.Debug|x64.ActiveCfg = Debug|x64 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3}.Debug|x64.Build.0 = Debug|x64 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3}.Debug|x86.ActiveCfg = Debug|x64 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3}.Release|Any CPU.ActiveCfg = Release|x64 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3}.Release|x64.ActiveCfg = Release|x64 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3}.Release|x64.Build.0 = Release|x64 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3}.Release|x86.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {282F987F-245A-48B9-8115-F2503FCEAF4F} + EndGlobalSection +EndGlobal diff --git a/dotnet/src/HybridRow.Json/Microsoft.Azure.Cosmos.Serialization.HybridRow.Json.csproj b/src/Serialization/HybridRow.Json/Microsoft.Azure.Cosmos.Serialization.HybridRow.Json.csproj similarity index 66% rename from dotnet/src/HybridRow.Json/Microsoft.Azure.Cosmos.Serialization.HybridRow.Json.csproj rename to src/Serialization/HybridRow.Json/Microsoft.Azure.Cosmos.Serialization.HybridRow.Json.csproj index d4b4d50..9468625 100644 --- a/dotnet/src/HybridRow.Json/Microsoft.Azure.Cosmos.Serialization.HybridRow.Json.csproj +++ b/src/Serialization/HybridRow.Json/Microsoft.Azure.Cosmos.Serialization.HybridRow.Json.csproj @@ -2,17 +2,14 @@ true - {CE1C4987-FC19-4887-9EB6-13508F8DA644} Library Microsoft.Azure.Cosmos.Serialization.HybridRow.Json Microsoft.Azure.Cosmos.Serialization.HybridRow.Json - netstandard2.0 + netstandard2.1 AnyCPU - - - + diff --git a/dotnet/src/HybridRow.Json/RowReaderJsonExtensions.cs b/src/Serialization/HybridRow.Json/RowReaderJsonExtensions.cs similarity index 100% rename from dotnet/src/HybridRow.Json/RowReaderJsonExtensions.cs rename to src/Serialization/HybridRow.Json/RowReaderJsonExtensions.cs diff --git a/dotnet/src/HybridRow.Json/RowReaderJsonSettings.cs b/src/Serialization/HybridRow.Json/RowReaderJsonSettings.cs similarity index 100% rename from dotnet/src/HybridRow.Json/RowReaderJsonSettings.cs rename to src/Serialization/HybridRow.Json/RowReaderJsonSettings.cs diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/CollectionAssert.h b/src/Serialization/HybridRow.Native.Tests.Unit/CollectionAssert.h new file mode 100644 index 0000000..98a50ac --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/CollectionAssert.h @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "CppUnitTest.h" + +namespace cdb_hr_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + struct CollectionAssert final + { + template + static void AreEqual(cdb_core::ReadOnlySpan expected, cdb_core::ReadOnlySpan value) + { + CollectionAssert::AreEqual(expected, value, ""sv); + } + + template + static void AreEqual(cdb_core::ReadOnlySpan expected, cdb_core::ReadOnlySpan value, std::wstring_view message) + { + Assert::AreEqual(expected.Length(), value.Length(), message.data()); + for (uint32_t i = 0; i < expected.Length(); i++) + { + Assert::AreEqual(expected[i], value[i], message.data()); + } + } + }; +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/CppUnitTestFramework.inl b/src/Serialization/HybridRow.Native.Tests.Unit/CppUnitTestFramework.inl new file mode 100644 index 0000000..77bde07 --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/CppUnitTestFramework.inl @@ -0,0 +1,203 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "CppUnitTest.h" +// ReSharper disable CppClangTidyCppcoreguidelinesMacroUsage + +// Additional MACROS to simplify definitions. +#define TEST_METHOD_WITH_OWNER(methodName, ownerAlias)\ + BEGIN_TEST_METHOD_ATTRIBUTE(methodName)\ + TEST_OWNER(L ## ownerAlias)\ + END_TEST_METHOD_ATTRIBUTE()\ + TEST_METHOD(methodName) + +// Additional string function for test diagnostics. +namespace Microsoft::VisualStudio::CppUnitTestFramework +{ + template<> + inline std::wstring ToString>( + const cdb_core::ReadOnlySpan& q) + { + return cdb_core::make_string(L"{p:%p l:%u}", &q[0], q.Length()); + } + + template<> + inline std::wstring ToString( + const cdb_hr::Result& q) + { + return cdb_core::make_string(L"{%d}", q); + } + + template<> + inline std::wstring ToString(const std::byte& q) + { + return cdb_core::make_string(L"{%u}", q); + } + + template<> + inline std::wstring ToString(const uint16_t& q) + { + return cdb_core::make_string(L"{%u}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::LayoutType* q) + { + return cdb_core::make_string(L"{%p}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::Layout* q) + { + return cdb_core::make_string(L"{%p}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::Layout& q) + { + return cdb_core::make_string(L"{%p}", &q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::LayoutScope* q) + { + return cdb_core::make_string(L"{%p}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::LayoutUDT* q) + { + return cdb_core::make_string(L"{%p}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::LayoutColumn& q) + { + return cdb_core::make_string(L"{%p}", &q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::LayoutCode& q) + { + return cdb_core::make_string(L"{%u}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::TypeKind& q) + { + std::string_view v = cdb_hr::ToStringView(q); + return cdb_core::make_string(L"{%.*S}", v.size(), v.data()); + } + + template<> + inline std::wstring ToString( + const cdb_hr::SortDirection& q) + { + return cdb_core::make_string(L"{%u}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::AllowEmptyKind& q) + { + return cdb_core::make_string(L"{%u}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::SchemaLanguageVersion& q) + { + return cdb_core::make_string(L"{%u}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::StorageKind& q) + { + switch (q) + { + case cdb_hr::StorageKind::Sparse: + return cdb_core::make_string(L"Sparse{%u}", q); + case cdb_hr::StorageKind::Fixed: + return cdb_core::make_string(L"Fixed{%u}", q); + case cdb_hr::StorageKind::Variable: + return cdb_core::make_string(L"Variable{%u}", q); + default: + return cdb_core::make_string(L"{%u}", q); + } + } + + template<> + inline std::wstring ToString( + const cdb_hr::HybridRowVersion& q) + { + return cdb_core::make_string(L"{%u}", q); + } + + template<> + inline std::wstring ToString( + const cdb_hr::SchemaId& q) + { + return cdb_core::make_string(L"{%d}", q.Id()); + } + + template<> + inline std::wstring ToString( + const cdb_hr::MongoDbObjectId& q) + { + return L""; + } + + template<> + inline std::wstring ToString( + const cdb_hr::Guid& q) + { + return L""; + } + + template<> + inline std::wstring ToString( + const cdb_hr::DateTime& q) + { + return cdb_core::make_string(L"{%ld}", q.Ticks()); + } + + template<> + inline std::wstring ToString( + const cdb_hr::UnixDateTime& q) + { + return cdb_core::make_string(L"{%ld}", q.GetMilliseconds()); + } + + template<> + inline std::wstring ToString( + const cdb_hr::Decimal& q) + { + return L""; + } + + template<> + inline std::wstring ToString( + const cdb_hr::Float128& q) + { + return cdb_core::make_string(L"Low: {%ld}, High: {%ld}", q.Low, q.High); + } + + template<> + inline std::wstring ToString( + const cdb_hr::NullValue& q) + { + return L""; + } +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/HybridRow.Native.Tests.Unit.vcxproj b/src/Serialization/HybridRow.Native.Tests.Unit/HybridRow.Native.Tests.Unit.vcxproj new file mode 100644 index 0000000..217224a --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/HybridRow.Native.Tests.Unit.vcxproj @@ -0,0 +1,102 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {209D2BFA-982F-4BB7-ADEA-C63B926152B3} + Win32Proj + HybridRowNativeTestsUnit + 10.0 + NativeUnitTestProject + + + + DynamicLibrary + true + false + + + DynamicLibrary + false + false + + + + true + + + false + + + + Use + Level3 + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + _DEBUG;%(PreprocessorDefinitions) + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + Use + Level3 + true + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + Create + + + + + + + + {eaed7d41-3de6-4c41-a0e4-40d53ea3daba} + + + {d02fd209-eff3-4d3f-b98e-e1bf9a1dd894} + + + + + + + \ No newline at end of file diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/HybridRow.Native.Tests.Unit.vcxproj.filters b/src/Serialization/HybridRow.Native.Tests.Unit/HybridRow.Native.Tests.Unit.vcxproj.filters new file mode 100644 index 0000000..022f04c --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/HybridRow.Native.Tests.Unit.vcxproj.filters @@ -0,0 +1,32 @@ + + + + + + + + + + Generated + + + + + + + + + + + Generated + + + + + + + + {6bb289c5-5c7e-4d07-b35f-521c7671671a} + + + \ No newline at end of file diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/HybridRowSerializerUnitTests.cpp b/src/Serialization/HybridRow.Native.Tests.Unit/HybridRowSerializerUnitTests.cpp new file mode 100644 index 0000000..eebea3c --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/HybridRowSerializerUnitTests.cpp @@ -0,0 +1,119 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" +#include "CppUnitTestFramework.inl" +#include "ResultAssert.h" + +namespace cdb_hr_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + TEST_CLASS(HybridRowSerializerUnitTests) + { + public: + + TEST_METHOD_WITH_OWNER(TypedArraySerializerTest, "jthunter") + { + using TS1 = cdb_hr::TypedArrayHybridRowSerializer; + cdb_hr::TypeArgumentList typeArgs1{cdb_hr::TypeArgument{&cdb_hr::LayoutLiteral::Utf8}}; + TestSerializer(typeArgs1, {"abc"s, "xyz"s}, {"abc"s, "ghk"s}, {"abc"s}); + + using TS2 = cdb_hr::TypedArrayHybridRowSerializer; + cdb_hr::TypeArgumentList typeArgs2{{cdb_hr::TypeArgument{&cdb_hr::LayoutLiteral::Int32}}}; + TestSerializer(typeArgs2, {123, 456}, {123, 789}, {4733584}); + } + + TEST_METHOD_WITH_OWNER(ArraySerializerTest, "jthunter") + { + using TS1 = cdb_hr::ArrayHybridRowSerializer; + cdb_hr::TypeArgumentList typeArgs1{cdb_hr::TypeArgument{&cdb_hr::LayoutLiteral::Utf8}}; + TestSerializer(typeArgs1, {"abc"s, "xyz"s}, {"abc"s, "ghk"s}, {"abc"s}); + + using TS2 = cdb_hr::ArrayHybridRowSerializer; + cdb_hr::TypeArgumentList typeArgs2{{cdb_hr::TypeArgument{&cdb_hr::LayoutLiteral::Int32}}}; + TestSerializer(typeArgs2, {123, 456}, {123, 789}, {4733584}); + } + + TEST_METHOD_WITH_OWNER(TypedTupleSerializerTest, "jthunter") + { + using TS1 = cdb_hr::TypedTupleHybridRowSerializer< + cdb_hr::Utf8HybridRowSerializer, + cdb_hr::Int32HybridRowSerializer>; + cdb_hr::TypeArgumentList typeArgs1{ + {&cdb_hr::LayoutLiteral::Utf8, &cdb_hr::LayoutLiteral::Int32} + }; + TestSerializer(typeArgs1, {"abc"s, 123}, {"xyz"s, 123}, {"abc"s, 456}); + + using TS2 = cdb_hr::TypedTupleHybridRowSerializer< + cdb_hr::Int64HybridRowSerializer, + cdb_hr::Int32HybridRowSerializer, + cdb_hr::DateTimeHybridRowSerializer>; + cdb_hr::TypeArgumentList typeArgs2{ + { + &cdb_hr::LayoutLiteral::Int64, + &cdb_hr::LayoutLiteral::Int32, + &cdb_hr::LayoutLiteral::DateTime + } + }; + TestSerializer(typeArgs2, {789, 123, {456}}, {654, 123, {456}}, {789, 123, {789}}); + } + + TEST_METHOD_WITH_OWNER(NullableSerializerTest, "jthunter") + { + using TS1 = cdb_hr::NullableHybridRowSerializer< + std::optional, int32_t, cdb_hr::Int32HybridRowSerializer>; + cdb_hr::TypeArgumentList typeArgs1{ + {cdb_hr::TypeArgument{&cdb_hr::LayoutLiteral::Int32}} + }; + TestSerializer(typeArgs1, 123, 456, std::nullopt); + } + + TEST_METHOD_WITH_OWNER(MapSerializerTest, "jthunter") + { + using TS1 = cdb_hr::TypedMapHybridRowSerializer< + cdb_hr::Utf8HybridRowSerializer, + cdb_hr::Int32HybridRowSerializer>; + cdb_hr::TypeArgumentList typeArgs1{ + {&cdb_hr::LayoutLiteral::Utf8, &cdb_hr::LayoutLiteral::Int32} + }; + + TestSerializer(typeArgs1, {{"abc"s, 123}}, {{"xyz"s, 123}}, {{"abc"s, 456}}); + } + + private: + template + static void TestSerializer( + const cdb_hr::TypeArgumentList& typeArgs, + typename TS::const_reference t1, + typename TS::const_reference t2, + typename TS::const_reference t3) + { + const cdb_hr::LayoutResolver& resolver = cdb_hr::SchemasHrSchema::GetLayoutResolver(); + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SystemSchemaLiteral::EmptySchemaId); + + cdb_hr::MemorySpanResizer resizer{0}; + cdb_hr::RowBuffer row{0, &resizer}; + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + ResultAssert::IsSuccess(TS::Write(row, root.Clone().Find(row, "a"), false, typeArgs, t1)); + auto [r2, v1] = TS::Read(row, root.Clone().Find(row, "a"), false); + ResultAssert::IsSuccess(r2); + + using comparer_type = typename TS::comparer_type; + Assert::IsTrue(comparer_type{}.operator()(t1, v1)); + Assert::AreEqual(comparer_type{}.operator()(t1), comparer_type{}.operator()(v1)); + Assert::AreNotEqual(comparer_type{}.operator()(t1), size_t{0}); + + Assert::IsFalse(comparer_type{}.operator()(t1, t2)); + Assert::AreNotEqual(comparer_type{}.operator()(t1), comparer_type{}.operator()(t2)); + + Assert::IsFalse(comparer_type{}.operator()(t1, t3)); + Assert::AreNotEqual(comparer_type{}.operator()(t1), comparer_type{}.operator()(t3)); + } + }; +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/LayoutCompilerUnitTests.cpp b/src/Serialization/HybridRow.Native.Tests.Unit/LayoutCompilerUnitTests.cpp new file mode 100644 index 0000000..e17ccfe --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/LayoutCompilerUnitTests.cpp @@ -0,0 +1,2765 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" +#include "CppUnitTestFramework.inl" +#include "ResultAssert.h" +#include "CollectionAssert.h" + +// ReSharper disable CppImplicitDefaultConstructorNotAvailable +// ReSharper disable CppClangTidyCppcoreguidelinesProTypeMemberInit +namespace cdb_hr_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + using Logger = Microsoft::VisualStudio::CppUnitTestFramework::Logger; + + TEST_CLASS(LayoutCompilerUnitTests) + { + constexpr static uint32_t InitialRowSize = 2 * 1024 * 1024; + + public: + TEST_METHOD_WITH_OWNER(PackNullAndBoolBits, "jthunter") + { + // Test that null bits and bool bits are packed tightly in the layout. + cdb_hr::Namespace ns{}; + ns.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("TestSchema"); + s.SetSchemaId(cdb_hr::SchemaId{1}); + })); + cdb_hr::Schema& s = *ns.GetSchemas().back(); + + for (int32_t i = 0; i < 32; i++) + { + s.GetProperties().emplace_back(cdb_core::make_unique_with([i](cdb_hr::Property& p) + { + p.SetPath(cdb_core::make_string("%d", i)); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + })); + })); + + auto layout = s.Compile(ns); + Assert::IsTrue(layout->GetSize() == cdb_hr::LayoutBit::DivCeiling((i + 1) * 2, cdb_hr::LayoutType::BitsPerByte), + cdb_core::make_string(L"Size: %u, i: %d", layout->GetSize(), i).c_str()); + } + } + + template + // where TLayout : LayoutType; + struct TestActionDispatcher + { + TestActionDispatcher() = default; + virtual ~TestActionDispatcher() = default; + TestActionDispatcher(const TestActionDispatcher& other) = delete; + TestActionDispatcher(TestActionDispatcher&& other) noexcept = delete; + TestActionDispatcher& operator=(const TestActionDispatcher& other) = delete; + TestActionDispatcher& operator=(TestActionDispatcher&& other) noexcept = delete; + virtual void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, TClosure& closure) = 0; + }; + + template + struct TestActionObjectDispatcher + { + TestActionObjectDispatcher() = default; + virtual ~TestActionObjectDispatcher() = default; + TestActionObjectDispatcher(const TestActionObjectDispatcher& other) = delete; + TestActionObjectDispatcher(TestActionObjectDispatcher&& other) noexcept = delete; + TestActionObjectDispatcher& operator=(const TestActionObjectDispatcher& other) = delete; + TestActionObjectDispatcher& operator=(TestActionObjectDispatcher&& other) noexcept = delete; + virtual void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, TClosure& closure) = 0; + }; + + template typename TDispatcher = TTestCase::Dispatcher, + typename TObjectDispatcher = typename TTestCase::ObjectDispatcher, + template typename TClosure = TTestCase::Closure> + static void LayoutCodeSwitch(cdb_hr::LayoutCode code, cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, void* closure) + { + switch (code) + { + case cdb_hr::LayoutCode::Null: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Boolean: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Int8: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Int16: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Int32: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Int64: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::UInt8: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::UInt16: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::UInt32: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::UInt64: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::VarInt: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::VarUInt: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Float32: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Float64: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Float128: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Decimal: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::DateTime: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::UnixDateTime: + TDispatcher{}.Dispatch(row, scope, + *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Guid: + TDispatcher{}.Dispatch(row, scope, *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::MongoDbObjectId: + TDispatcher{}.Dispatch(row, scope, + *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Utf8: + TDispatcher{}.Dispatch(row, scope, + *static_cast*>(closure)); + break; + case cdb_hr::LayoutCode::Binary: + TDispatcher>{}.Dispatch(row, scope, + *static_cast>*>(closure)); + break; + case cdb_hr::LayoutCode::ObjectScope: + TObjectDispatcher{}.DispatchObject(row, scope, *static_cast*>(closure)); + break; + default: + cdb_core::Contract::Assert(false, cdb_core::make_string("Unknown type will be ignored: %u", code).c_str()); + break; + } + } + + struct RoundTripFixed final + { + template + struct Expected final + { + cdb_hr::TypeKind TypeKind; + TValue Default; + TValue Value; + uint32_t Length = 0; + std::wstring Tag = L""s; + }; + + template + struct Closure final + { + const cdb_hr::LayoutColumn& Col; + Expected& Expected; + }; + + template + struct Dispatcher final : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& col = closure.Col; + Expected& expected = closure.Expected; + const TLayout& t = *static_cast(col.GetType()); + if (col.GetNullBit() != cdb_hr::LayoutBit::Invalid()) + { + auto [r, value] = t.ReadFixed(row, root, col); + ResultAssert::NotFound(r, expected.Tag); + } + else + { + auto [r, value] = t.ReadFixed(row, root, col); + ResultAssert::IsSuccess(r, expected.Tag); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(expected.Default, value, expected.Tag); + } + else + { + Assert::AreEqual(expected.Default, value, expected.Tag.data()); + } + } + + ResultAssert::IsSuccess(t.WriteFixed(row, root, col, expected.Value), expected.Tag.data()); + auto [r, value] = t.ReadFixed(row, root, col); + ResultAssert::IsSuccess(r, expected.Tag.data()); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(expected.Value, value, expected.Tag); + } + else + { + Assert::AreEqual(expected.Value, value, expected.Tag.data()); + } + + cdb_hr::RowCursor roRoot = root.AsReadOnly(); + ResultAssert::InsufficientPermissions(t.WriteFixed(row, roRoot, col, expected.Value)); + ResultAssert::InsufficientPermissions(t.DeleteFixed(row, roRoot, col)); + + if (col.GetNullBit() != cdb_hr::LayoutBit::Invalid()) + { + ResultAssert::IsSuccess(t.DeleteFixed(row, root, col)); + } + else + { + ResultAssert::TypeMismatch(t.DeleteFixed(row, root, col)); + } + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Closure& closure) override + { + Assert::Fail(L"not implemented"); + } + }; + }; + + template + void ParseSchemaFixedCase(RoundTripFixed::Expected expected) + { + for (bool nullable : {true, false}) + { + expected.Tag = cdb_core::make_string(L"{{'%S', 'length': %d, 'nullable': %d}}\n", + ToStringView(expected.TypeKind).data(), expected.Length, nullable); + Logger::WriteMessage(expected.Tag.data()); + + // Build a schema and namespace. + auto schema = cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([&](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(expected.TypeKind); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetLength(expected.Length); + pt.SetNullable(nullable); + })); + })); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.GetSchemas().emplace_back(std::move(schema)); + }); + + // Verify schema compilation failures for invalid configurations. + if ((expected.TypeKind == cdb_hr::TypeKind::Null) && (!nullable)) + { + try + { + auto& s = ns->GetSchemas()[0]; + auto layout = s->Compile(*ns); + Assert::IsNull(layout.get(), expected.Tag.data()); // Should never get here. + } + catch (cdb_hr::LayoutCompiler::LayoutCompilationException&) + { + return; // Schema compile failed as expected. + } + } + + // Round-trip a value of the indicated type. + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + Assert::AreEqual(size_t{1}, layout.GetColumns().size(), expected.Tag.data()); + Assert::AreEqual("table", layout.GetName().data(), expected.Tag.data()); + std::tuple found = layout.TryFind("a"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& col = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Fixed, col.GetStorage(), expected.Tag.data()); + Assert::AreEqual(expected.Length == 0, col.GetType()->IsFixed(), expected.Tag.data()); + + // Try writing a row using the layout. + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + + RoundTripFixed::Closure closure{col, expected}; + LayoutCompilerUnitTests::LayoutCodeSwitch(col.GetType()->GetLayoutCode(), row, root, &closure); + } + } + + TEST_METHOD_WITH_OWNER(ParseSchemaFixed, "jthunter") + { + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Null, cdb_hr::NullValue{}, cdb_hr::NullValue{}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Boolean, false, false}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Int8, int8_t{0}, int8_t{42}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Int16, int16_t{0}, int16_t{42}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Int32, int32_t{0}, int32_t{42}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Int64, int64_t{0}, int64_t{42L}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::UInt8, uint8_t{0}, uint8_t{42}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::UInt16, uint16_t{0}, uint16_t{42}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::UInt32, uint32_t{0}, uint32_t{42U}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::UInt64, uint64_t{0}, uint64_t{42UL}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Float32, float{0}, float{4.2F}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Float64, double{0}, double{4.2}}); + ParseSchemaFixedCase( + RoundTripFixed::Expected{cdb_hr::TypeKind::Float128, float128_t{0, 0}, float128_t{0, 42}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Decimal, cdb_hr::Decimal{0}, cdb_hr::Decimal{42}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::DateTime, cdb_hr::DateTime{0}, cdb_hr::DateTime{42}}); + ParseSchemaFixedCase( + RoundTripFixed::Expected{cdb_hr::TypeKind::UnixDateTime, cdb_hr::UnixDateTime{0}, cdb_hr::UnixDateTime{42}}); + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Guid, cdb_hr::Guid{}, cdb_hr::Guid::NewGuid()}); + ParseSchemaFixedCase(RoundTripFixed::Expected{ + cdb_hr::TypeKind::MongoDbObjectId, + cdb_hr::MongoDbObjectId{}, + cdb_hr::MongoDbObjectId{0, 42} + }); + + ParseSchemaFixedCase(RoundTripFixed::Expected{cdb_hr::TypeKind::Utf8, "\0\0"sv, "AB"sv, 2}); + std::array defaultBinary{{byte{0x00}, byte{0x00}}}; + std::array valueBinary{{byte{0x01}, byte{0x02}}}; + ParseSchemaFixedCase(RoundTripFixed::Expected>{ + cdb_hr::TypeKind::Binary, + defaultBinary, + valueBinary, + static_cast(valueBinary.size()) + }); + } + + struct RoundTripVariable + { + template + struct Expected final + { + cdb_hr::TypeKind TypeKind; + uint32_t Length; + std::wstring Tag; + TValue Short; + TValue Value; + TValue Long; + TValue TooBig; + }; + + template + struct Closure final + { + const cdb_hr::LayoutColumn& Col; + const cdb_hr::Layout& Layout; + Expected& Expected; + }; + + template + struct Dispatcher : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& col = closure.Col; + Expected& expected = closure.Expected; + Logger::WriteMessage(expected.Tag.data()); + RoundTrip(row, root, col, expected.Value, expected); + } + + protected: + static void RoundTrip(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, const cdb_hr::LayoutColumn& col, TValue exValue, + Expected& expected) + // where TLayout : LayoutType + { + const TLayout& t = *static_cast(col.GetType()); + cdb_hr::Result r = t.WriteVariable(row, root, col, exValue); + ResultAssert::IsSuccess(r, expected.Tag.data()); + Compare(row, root, col, exValue, expected); + + cdb_hr::RowCursor roRoot = root.AsReadOnly(); + ResultAssert::InsufficientPermissions(t.WriteVariable(row, roRoot, col, expected.Value)); + } + + static void Compare(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, const cdb_hr::LayoutColumn& col, TValue exValue, + Expected& expected) + // where TLayout : LayoutType + { + const TLayout& t = *static_cast(col.GetType()); + auto [r, value] = t.ReadVariable(row, root, col); + ResultAssert::IsSuccess(r, expected.Tag.data()); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(exValue, value, expected.Tag); + } + else + { + Assert::AreEqual(exValue, value, expected.Tag.data()); + } + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Closure& closure) override + { + Assert::Fail(L"not implemented"); + } + }; + }; + + struct VariableInterleaving : RoundTripVariable + { + template + struct Dispatcher final : RoundTripVariable::Dispatcher + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::Layout& layout = closure.Layout; + Expected& expected = closure.Expected; + + Logger::WriteMessage(expected.Tag.data()); + + const cdb_hr::LayoutColumn& a = Verify(row, root, layout, "a", expected); + const cdb_hr::LayoutColumn& b = Verify(row, root, layout, "b", expected); + const cdb_hr::LayoutColumn& c = Verify(row, root, layout, "c", expected); + + RoundTripVariable::Dispatcher::RoundTrip(row, root, b, expected.Value, expected); + RoundTripVariable::Dispatcher::RoundTrip(row, root, a, expected.Value, expected); + RoundTripVariable::Dispatcher::RoundTrip(row, root, c, expected.Value, expected); + + // Make the var column shorter. + uint32_t rowSizeBeforeShrink = row.GetLength(); + RoundTripVariable::Dispatcher::RoundTrip(row, root, a, expected.Short, expected); + RoundTripVariable::Dispatcher::Compare(row, root, c, expected.Value, expected); + uint32_t rowSizeAfterShrink = row.GetLength(); + Assert::IsTrue(rowSizeAfterShrink < rowSizeBeforeShrink, expected.Tag.data()); + + // Make the var column longer. + RoundTripVariable::Dispatcher::RoundTrip(row, root, a, expected.Long, expected); + RoundTripVariable::Dispatcher::Compare(row, root, c, expected.Value, expected); + uint32_t rowSizeAfterGrow = row.GetLength(); + Assert::IsTrue(rowSizeAfterGrow > rowSizeAfterShrink, expected.Tag.data()); + Assert::IsTrue(rowSizeAfterGrow > rowSizeBeforeShrink, expected.Tag.data()); + + // Check for size overflow errors. + if (a.GetSize() > 0) + { + TooBig(row, root, a, expected); + } + + // Delete the var column. + Delete(row, root, b, expected); + Delete(row, root, c, expected); + Delete(row, root, a, expected); + } + + static const cdb_hr::LayoutColumn& Verify(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, const cdb_hr::Layout& layout, std::string_view path, + Expected& expected) + { + std::tuple found = layout.TryFind(path); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& col = *std::get<1>(found); + + Assert::IsTrue(col.GetType()->AllowVariable()); + const TLayout& t = *static_cast(col.GetType()); + auto [r, value] = t.ReadVariable(row, root, col); + ResultAssert::NotFound(r, expected.Tag.data()); + return col; + } + + static void TooBig(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, const cdb_hr::LayoutColumn& col, Expected& expected) + { + const TLayout& t = *static_cast(col.GetType()); + cdb_hr::Result r = t.WriteVariable(row, root, col, expected.TooBig); + Assert::AreEqual(cdb_hr::Result::TooBig, r, expected.Tag.data()); + } + + static void Delete(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, const cdb_hr::LayoutColumn& col, Expected& expected) + { + const TLayout& t = *static_cast(col.GetType()); + cdb_hr::RowCursor roRoot = root.AsReadOnly(); + ResultAssert::InsufficientPermissions(t.DeleteVariable(row, roRoot, col)); + cdb_hr::Result r = t.DeleteVariable(row, root, col); + ResultAssert::IsSuccess(r, expected.Tag.data()); + auto [r2, _] = t.ReadVariable(row, root, col); + ResultAssert::NotFound(r2, expected.Tag.data()); + } + }; + }; + + template + void ParseSchemaVariableCase(RoundTripVariable::Expected expected) + { + auto schema = cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([&](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(expected.TypeKind); + pt.SetStorage(cdb_hr::StorageKind::Variable); + pt.SetLength(expected.Length); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(cdb_core::make_unique_with([&](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(expected.TypeKind); + pt.SetStorage(cdb_hr::StorageKind::Variable); + pt.SetLength(expected.Length); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("c"s); + p.SetPropertyType(cdb_core::make_unique_with([&](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(expected.TypeKind); + pt.SetStorage(cdb_hr::StorageKind::Variable); + pt.SetLength(expected.Length); + })); + })); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.GetSchemas().emplace_back(std::move(schema)); + }); + + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + + std::tuple found = layout.TryFind("a"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& col = *std::get<1>(found); + + Assert::IsTrue(col.GetType()->AllowVariable()); + Assert::AreEqual(cdb_hr::StorageKind::Variable, col.GetStorage(), expected.Tag.data()); + + // Try writing a row using the layout. + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + RoundTripVariable::Closure closure{col, layout, expected}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + col.GetType()->GetLayoutCode(), row, root, &closure); + } + + TEST_METHOD_WITH_OWNER(ParseSchemaVariable, "jthunter") + { + struct Make + { + // Helper functions to create sample arrays. + static std::string S(size_t size) + { + std::string ret(size, 'a'); + for (size_t i = 0; i < size; i++) + { + ret[i] = static_cast('a' + (i % 26)); + } + + return ret; + } + + static std::vector B(size_t size) + { + std::vector ret{size}; + for (size_t i = 0; i < size; i++) + { + ret[i] = static_cast(i + 1); + } + + return ret; + } + }; + + ParseSchemaVariableCase(RoundTripVariable::Expected{ + cdb_hr::TypeKind::Utf8, + 100, + L"utf8"s, + Make::S(2), + Make::S(20), + Make::S(100), + Make::S(200) + }); + + ParseSchemaVariableCase(RoundTripVariable::Expected>{ + cdb_hr::TypeKind::Binary, + 100, + L"binary"s, + Make::B(2), + Make::B(20), + Make::B(100), + Make::B(200) + }); + + ParseSchemaVariableCase(RoundTripVariable::Expected{ + cdb_hr::TypeKind::VarInt, + 0, + L"varint"s, + 1i64, + 255i64, + INT64_MAX, + 0i64 + }); + + ParseSchemaVariableCase(RoundTripVariable::Expected{ + cdb_hr::TypeKind::VarUInt, + 0, + L"varuint"s, + 1ui64, + 255ui64, + UINT64_MAX, + 0ui64 + }); + } + + struct RoundTripSparseOrdering final + { + template + struct Expected final + { + std::string_view Path; + const cdb_hr::LayoutType& Type; + TValue Value; + }; + + template + struct Closure final + { + std::wstring_view Tag; + Expected& Expected; + }; + + // Class Template Argument Deduction (CTAD) hint + template Closure(std::wstring_view tag, Expected& expected) -> Closure; + + template + struct Dispatcher : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutType& type = closure.Expected.Type; + std::string_view path = closure.Expected.Path; + TValue exValue = closure.Expected.Value; + std::wstring_view tag = closure.Tag; + + const TLayout& t = static_cast(type); + TValue value = exValue; + cdb_hr::RowCursor field = root.Clone().Find(row, path); + cdb_hr::Result r = t.WriteSparse(row, field, value); + ResultAssert::IsSuccess(r, tag); + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::IsSuccess(r, tag); + + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(exValue, value, tag); + } + else + { + Assert::AreEqual(exValue, value, tag.data()); + } + + if (t.IsNull()) + { + r = cdb_hr::LayoutLiteral::Boolean.WriteSparse(row, field, false); + ResultAssert::IsSuccess(r, tag); + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::TypeMismatch(r, tag); + } + else + { + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, field, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, tag); + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::TypeMismatch(r, tag); + } + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Closure& closure) override + { + Assert::Fail(L"not implemented"); + } + }; + }; + + template + void SparseOrderingCase(std::tuple expected) + { + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + auto schema = cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) { n.GetSchemas().emplace_back(std::move(schema)); }); + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + + constexpr size_t N = std::tuple_size::value; + std::array permutation{}; + for (size_t i = 0; i < N; i++) + { + permutation[i] = i; + } + + do + { + // Make a tag from the permutation. + std::wstring tag{}; + for (size_t i : permutation) + { + if (!tag.empty()) + { + tag += L", "; + } + + if (i == 0) + { + auto& field = std::get<0>(expected); + tag += cdb_core::make_string(L"%S: %S", field.Path.data(), field.Type.GetName().data()); + } + else if (i == 1) + { + auto& field = std::get<1>(expected); + tag += cdb_core::make_string(L"%S: %S", field.Path.data(), field.Type.GetName().data()); + } + if constexpr (N > 2) + { + if (i == 2) + { + auto& field = std::get<2>(expected); + tag += cdb_core::make_string(L"%S: %S", field.Path.data(), field.Type.GetName().data()); + } + else if (i == 3) + { + auto& field = std::get<3>(expected); + tag += cdb_core::make_string(L"%S: %S", field.Path.data(), field.Type.GetName().data()); + } + } + } + tag += L"\n"; + Logger::WriteMessage(tag.data()); + + // Run test on the permutation. + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + for (size_t i : permutation) + { + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + if (i == 0) + { + RoundTripSparseOrdering::Closure closure{tag, std::get<0>(expected)}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + closure.Expected.Type.GetLayoutCode(), row, root, &closure); + } + else if (i == 1) + { + RoundTripSparseOrdering::Closure closure{tag, std::get<1>(expected)}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + closure.Expected.Type.GetLayoutCode(), row, root, &closure); + } + if constexpr (N > 2) + { + if (i == 2) + { + RoundTripSparseOrdering::Closure closure{tag, std::get<2>(expected)}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + closure.Expected.Type.GetLayoutCode(), row, root, &closure); + } + else if (i == 3) + { + RoundTripSparseOrdering::Closure closure{tag, std::get<3>(expected)}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + closure.Expected.Type.GetLayoutCode(), row, root, &closure); + } + } + } + } while (std::next_permutation(permutation.begin(), permutation.end())); + } + + TEST_METHOD_WITH_OWNER(SparseOrdering, "jthunter") + { + // Test various orderings of multiple sparse column types. + SparseOrderingCase(std::make_tuple( + RoundTripSparseOrdering::Expected{"a"sv, cdb_hr::LayoutLiteral::Utf8, "aa"sv}, + RoundTripSparseOrdering::Expected{"b"sv, cdb_hr::LayoutLiteral::Utf8, "bb"sv} + )); + + SparseOrderingCase(std::make_tuple( + RoundTripSparseOrdering::Expected{"a"sv, cdb_hr::LayoutLiteral::VarInt, 42i64}, + RoundTripSparseOrdering::Expected{"b"sv, cdb_hr::LayoutLiteral::Int64, 43i64} + )); + + SparseOrderingCase(std::make_tuple( + RoundTripSparseOrdering::Expected{"a"sv, cdb_hr::LayoutLiteral::VarInt, 42i64}, + RoundTripSparseOrdering::Expected{"b"sv, cdb_hr::LayoutLiteral::Utf8, "aa"sv}, + RoundTripSparseOrdering::Expected{"b"sv, cdb_hr::LayoutLiteral::Null, {}}, + RoundTripSparseOrdering::Expected{"b"sv, cdb_hr::LayoutLiteral::Boolean, true} + )); + } + + struct RoundTripSparseSimple final + { + template + struct Expected final + { + cdb_hr::TypeKind TypeKind; + std::wstring_view Tag; + TValue Value; + }; + + template + struct Closure final + { + const cdb_hr::LayoutColumn& Col; + Expected& Expected; + }; + + template + struct Dispatcher : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& col = closure.Col; + Expected& expected = closure.Expected; + + Logger::WriteMessage(col.GetType()->GetName().data()); + const TLayout& t = *static_cast(col.GetType()); + cdb_hr::RowCursor field = root.Clone().Find(row, col.GetPath()); + auto [r, value] = t.ReadSparse(row, field); + ResultAssert::NotFound(r, expected.Tag.data()); + r = t.WriteSparse(row, field, expected.Value, cdb_hr::UpdateOptions::Update); + ResultAssert::NotFound(r, expected.Tag.data()); + r = t.WriteSparse(row, field, expected.Value); + ResultAssert::IsSuccess(r, expected.Tag.data()); + r = t.WriteSparse(row, field, expected.Value, cdb_hr::UpdateOptions::Insert); + ResultAssert::Exists(r, expected.Tag.data()); + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::IsSuccess(r, expected.Tag.data()); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(expected.Value, value, expected.Tag.data()); + } + else + { + Assert::AreEqual(expected.Value, value, expected.Tag.data()); + } + + cdb_hr::RowCursor roRoot = root.AsReadOnly().Find(row, col.GetPath()); + ResultAssert::InsufficientPermissions(t.DeleteSparse(row, roRoot)); + ResultAssert::InsufficientPermissions(t.WriteSparse(row, roRoot, expected.Value, cdb_hr::UpdateOptions::Update)); + + if (t.IsNull()) + { + r = cdb_hr::LayoutLiteral::Boolean.WriteSparse(row, field, false); + ResultAssert::IsSuccess(r, expected.Tag.data()); + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::TypeMismatch(r, expected.Tag.data()); + } + else + { + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, field, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, expected.Tag.data()); + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::TypeMismatch(r, expected.Tag.data()); + } + + r = t.DeleteSparse(row, field); + ResultAssert::TypeMismatch(r, expected.Tag.data()); + + // Overwrite it again, then delete it. + r = t.WriteSparse(row, field, expected.Value, cdb_hr::UpdateOptions::Update); + ResultAssert::IsSuccess(r, expected.Tag.data()); + r = t.DeleteSparse(row, field); + ResultAssert::IsSuccess(r, expected.Tag.data()); + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::NotFound(r, expected.Tag.data()); + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Closure& closure) override + { + Assert::Fail(L"not implemented"); + } + }; + }; + + template + void ParseSchemaSparseSimpleCase(cdb_hr::RowBuffer& row, RoundTripSparseSimple::Expected expected) + { + auto schema = cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([&](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(expected.TypeKind); + pt.SetStorage(cdb_hr::StorageKind::Sparse); + })); + })); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.GetSchemas().emplace_back(std::move(schema)); + }); + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + + Assert::AreEqual(size_t{1}, layout.GetColumns().size(), expected.Tag.data()); + std::tuple found = layout.TryFind("a"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& col = *std::get<1>(found); + + Assert::AreEqual(cdb_hr::StorageKind::Sparse, col.GetStorage(), expected.Tag.data()); + + // Try writing a row using the layout. + row.Reset(); + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + RoundTripSparseSimple::Closure closure{col, expected}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + col.GetType()->GetLayoutCode(), row, root, &closure); + } + + TEST_METHOD_WITH_OWNER(ParseSchemaSparseSimple, "jthunter") + { + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + + // Test all sparse column types. + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Null, L"null"sv, {}}); + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Boolean, L"bool"sv, true}); + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Boolean, L"bool"sv, false}); + + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Int8, L"int8"sv, int8_t{42}}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Int16, L"int16"sv, int16_t{42}}); + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Int32, L"int32"sv, 42}); + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Int64, L"int64"sv, 42i64}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::UInt8, L"uint8"sv, uint8_t{42}}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::UInt16, L"uint16"sv, uint16_t{42}}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::UInt32, L"uint32"sv, 42ui32}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::UInt64, L"uint64"sv, 42ui64}); + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected{cdb_hr::TypeKind::VarInt, L"varint"sv, 42i64}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::VarUInt, L"varuint"sv, 42ui64}); + + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Float32, L"float32"sv, 4.2F}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Float64, L"float64"sv, 4.2}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Float128, L"float128"sv, {0, 42}}); + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Decimal, L"decimal"sv, {42}}); + + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::DateTime, L"datetime"sv, {42}}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::UnixDateTime, L"unixdatetime"sv, {42}}); + + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Guid, L"guid"sv, cdb_hr::Guid::NewGuid()}); + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::MongoDbObjectId, L"mongodbobjectid"sv, {0, 42}}); + + ParseSchemaSparseSimpleCase(row, + RoundTripSparseSimple::Expected{cdb_hr::TypeKind::Utf8, L"utf8"sv, "AB"}); + ParseSchemaSparseSimpleCase(row, RoundTripSparseSimple::Expected>{ + cdb_hr::TypeKind::Binary, + L"binary"sv, + std::array{{byte{0x01}, byte{0x02}}} + }); + } + + template typename TExpected> + void ParseSchemaUDTCase(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& udtScope1, std::string_view path, TExpected expected) + { + const cdb_hr::Layout& layout = udtScope1.GetLayout(); + std::tuple found = layout.TryFind(path); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& col = *std::get<1>(found); + + if constexpr (std::is_same_v, RoundTripFixed::Expected>) + { + RoundTripFixed::Closure closure{col, expected}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + col.GetType()->GetLayoutCode(), + row, + udtScope1, + &closure); + } + else + { + RoundTripVariable::Closure closure{col, layout, expected}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + col.GetType()->GetLayoutCode(), + row, + udtScope1, + &closure); + } + } + + TEST_METHOD_WITH_OWNER(ParseSchemaUDT, "jthunter") + { + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.SetName("myNamespace"); + n.GetSchemas().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("udtA"s); + s.SetSchemaId(cdb_hr::SchemaId{1}); + s.SetOptions(cdb_core::make_unique_with([&](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(false); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([&](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int8); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(cdb_core::make_unique_with([&](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + pt.SetLength(100); + })); + })); + })); + n.GetSchemas().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("udtB"s); + s.SetSchemaId(cdb_hr::SchemaId{2}); + })); + n.GetSchemas().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("udtB"s); + s.SetSchemaId(cdb_hr::SchemaId{3}); + })); + n.GetSchemas().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("udtB"s); + s.SetSchemaId(cdb_hr::SchemaId{4}); + })); + n.GetSchemas().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.SetOptions(cdb_core::make_unique_with([&](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(false); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("u"s); + p.SetPropertyType(std::make_unique("udtA")); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&](cdb_hr::Property& p) + { + p.SetPath("v"s); + p.SetPropertyType(std::make_unique("udtB", cdb_hr::SchemaId{3})); + })); + })); + }); + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + + std::wstring_view tag = L"Tag: myNamespace"sv; + + std::tuple found = layout.TryFind("u"sv); + Assert::IsTrue(std::get<0>(found), tag.data()); + const cdb_hr::LayoutColumn& udtACol = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, udtACol.GetStorage(), tag.data()); + + // Verify that UDT versioning works through schema references. + found = layout.TryFind("v"sv); + Assert::IsTrue(std::get<0>(found), tag.data()); + const cdb_hr::LayoutColumn& udtBCol = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, udtBCol.GetStorage(), tag.data()); + Assert::AreEqual(static_cast(&cdb_hr::LayoutLiteral::UDT), udtBCol.GetType()); + Assert::AreEqual(cdb_hr::SchemaId{3}, udtBCol.GetTypeArgs().GetSchemaId()); + + const cdb_hr::Layout& udtLayout = resolver.Resolve(cdb_hr::SchemaId{1}); + row.Reset(); + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + + // Verify the udt doesn't yet exist. + cdb_hr::RowCursor scope = cdb_hr::RowCursor::Create(row).Find(row, udtACol.GetPath()); + auto [r, scope2] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + ResultAssert::NotFound(r, tag); + cdb_hr::RowCursor udtScope1; + std::tie(r, udtScope1) = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, udtACol.GetTypeArgs()); + ResultAssert::IsSuccess(r, tag); + cdb_hr::RowCursor udtScope2; + std::tie(r, udtScope2) = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + ResultAssert::IsSuccess(r, tag); + Assert::AreSame(udtLayout, udtScope2.GetLayout(), tag.data()); + Assert::AreEqual(udtScope1.GetScopeType(), udtScope2.GetScopeType(), tag.data()); + Assert::AreEqual(udtScope1.m_start, udtScope2.m_start, tag.data()); + Assert::AreEqual(udtScope1.GetImmutable(), udtScope2.GetImmutable(), tag.data()); + + ParseSchemaUDTCase(row, udtScope1, "a", RoundTripFixed::Expected{ + cdb_hr::TypeKind::Int8, + int8_t{0}, + int8_t{42}, + 0, + L"{ 'type': 'int8', 'storage': 'fixed' }"s + }); + ParseSchemaUDTCase(row, udtScope1, "b", RoundTripVariable::Expected{ + cdb_hr::TypeKind::Utf8, + 2, + L"{ 'type': 'utf8', 'storage': 'variable' }"s, + "", + "AB", + "", + "" + }); + + cdb_hr::RowCursor roRoot = cdb_hr::RowCursor::Create(row).AsReadOnly().Find(row, udtACol.GetPath()); + ResultAssert::InsufficientPermissions(udtACol.TypeAs().DeleteScope(row, roRoot)); + std::tie(r, udtScope2) = udtACol.TypeAs().WriteScope(row, roRoot, udtACol.GetTypeArgs()); + ResultAssert::InsufficientPermissions(r); + + // Overwrite the whole scope. + scope = std::move(cdb_hr::RowCursor::Create(row).Find(row, udtACol.GetPath())); + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, scope, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, tag); + std::tie(r, std::ignore) = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + ResultAssert::TypeMismatch(r, tag); + r = cdb_hr::LayoutLiteral::UDT.DeleteScope(row, scope); + ResultAssert::TypeMismatch(r, tag); + + // Overwrite it again, then delete it. + scope = std::move(cdb_hr::RowCursor::Create(row).Find(row, udtACol.GetPath())); + std::tie(r, std::ignore) = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, udtACol.GetTypeArgs()); + ResultAssert::IsSuccess(r, tag); + r = cdb_hr::LayoutLiteral::UDT.DeleteScope(row, scope); + ResultAssert::IsSuccess(r, tag); + scope = std::move(cdb_hr::RowCursor::Create(row).Find(row, udtACol.GetPath())); + std::tie(r, std::ignore) = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + ResultAssert::NotFound(r, tag); + } + + struct RoundTripSparseObject final + { + template + struct Expected final + { + cdb_hr::TypeKind Type; + std::wstring_view Tag; + TValue Value; + }; + + template + struct Closure + { + const cdb_hr::LayoutColumn& ObjCol; + const cdb_hr::LayoutColumn& Col; + Expected& Expected; + }; + + template + struct Dispatcher : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& objCol = closure.ObjCol; + const cdb_hr::LayoutObject& objT = objCol.TypeAs(); + const cdb_hr::LayoutColumn& col = closure.Col; + Expected& expected = closure.Expected; + + Logger::WriteMessage(col.GetType()->GetName().data()); + Assert::AreSame(objCol, *col.GetParent(), expected.Tag.data()); + + const TLayout& t = col.GetType()->TypeAs(); + + // Attempt to read the object and the nested column. + cdb_hr::RowCursor field = root.Clone().Find(row, objCol.GetPath()); + auto [r, scope] = objT.ReadScope(row, field); + ResultAssert::NotFound(r, expected.Tag.data()); + + // Write the object and the nested column. + std::tie(r, scope) = objT.WriteScope(row, field, objCol.GetTypeArgs()); + ResultAssert::IsSuccess(r, expected.Tag.data()); + + // Verify the nested field doesn't yet appear within the new scope. + cdb_hr::RowCursor nestedField = scope.Clone().Find(row, col.GetPath()); + TValue value; + std::tie(r, value) = t.ReadSparse(row, nestedField); + ResultAssert::NotFound(r, expected.Tag.data()); + + // Write the nested field. + r = t.WriteSparse(row, nestedField, expected.Value); + ResultAssert::IsSuccess(r, expected.Tag.data()); + + // Read the object and the nested column, validate the nested column has the proper value. + cdb_hr::RowCursor scope2; + std::tie(r, scope2) = objT.ReadScope(row, field); + ResultAssert::IsSuccess(r, expected.Tag.data()); + Assert::AreEqual(scope.GetScopeType(), scope2.GetScopeType(), expected.Tag.data()); + Assert::AreEqual(scope.m_start, scope2.m_start, expected.Tag.data()); + Assert::AreEqual(scope.GetImmutable(), scope2.GetImmutable(), expected.Tag.data()); + + // Read the nested field + nestedField = scope2.Clone().Find(row, col.GetPath()); + std::tie(r, value) = t.ReadSparse(row, nestedField); + ResultAssert::IsSuccess(r, expected.Tag.data()); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(expected.Value, value, expected.Tag.data()); + } + else + { + Assert::AreEqual(expected.Value, value, expected.Tag.data()); + } + + cdb_hr::RowCursor roRoot = root.AsReadOnly().Find(row, objCol.GetPath()); + ResultAssert::InsufficientPermissions(objT.DeleteScope(row, roRoot)); + std::tie(r, scope2) = objT.WriteScope(row, roRoot, objCol.GetTypeArgs()); + ResultAssert::InsufficientPermissions(r); + + // Overwrite the whole scope. + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, field, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, expected.Tag.data()); + std::tie(r, std::ignore) = objT.ReadScope(row, field); + ResultAssert::TypeMismatch(r, expected.Tag.data()); + r = objT.DeleteScope(row, field); + ResultAssert::TypeMismatch(r, expected.Tag.data()); + + // Overwrite it again, then delete it. + std::tie(r, std::ignore) = objT.WriteScope(row, field, objCol.GetTypeArgs(), cdb_hr::UpdateOptions::Update); + ResultAssert::IsSuccess(r, expected.Tag.data()); + r = objT.DeleteScope(row, field); + ResultAssert::IsSuccess(r, expected.Tag.data()); + std::tie(r, std::ignore) = objT.ReadScope(row, field); + ResultAssert::NotFound(r, expected.Tag.data()); + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Closure& closure) override + { + Assert::Fail(L"not implemented"); + } + }; + }; + + TEST_METHOD_WITH_OWNER(ParseSchemaSparseObject, "jthunter") + { + RoundTripSparseObject::Expected expected{cdb_hr::TypeKind::Int8, L"int8"sv, int8_t{42}}; + auto schema = cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::ObjectPropertyType& pt) + { + pt.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& innerP) + { + innerP.SetPath("b"s); + innerP.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& innerPT) + { + innerPT.SetType(cdb_hr::TypeKind::Int8); + })); + })); + })); + })); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.GetSchemas().emplace_back(std::move(schema)); + }); + + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + + Assert::AreEqual(size_t{1}, layout.GetColumns().size(), expected.Tag.data()); + std::tuple found = layout.TryFind("a"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& objCol = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, objCol.GetStorage(), expected.Tag.data()); + + found = layout.TryFind("a.b"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& col = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, objCol.GetStorage(), expected.Tag.data()); + + // Try writing a row using the layout. + row.Reset(); + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + RoundTripSparseObject::Closure closure{objCol, col, expected}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + col.GetType()->GetLayoutCode(), + row, + root, + &closure); + } + + struct RoundTripSparseObjectMulti final + { + template + struct ExpectedProperty final + { + std::string_view Path; + TValue Value; + }; + + template + struct Expected final + { + std::wstring_view Tag; + std::vector> Props; + std::tuple...> ExpectedProps; + }; + + template + struct Closure final + { + const cdb_hr::LayoutColumn& Col; + ExpectedProperty& Prop; + std::wstring_view Tag; + }; + + // Class Template Argument Deduction (CTAD) hint + template Closure( + const cdb_hr::LayoutColumn& col, ExpectedProperty& prop, std::wstring_view tag) -> Closure; + + template + struct Dispatcher : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& col = closure.Col; + ExpectedProperty& prop = closure.Prop; + std::wstring tag = cdb_core::make_string(L"Prop: %S: Tag: %s", prop.Path.data(), closure.Tag.data()); + + Logger::WriteMessage(tag.data()); + + const TLayout& t = col.TypeAs(); + + // Verify the nested field doesn't yet appear within the new scope. + cdb_hr::RowCursor nestedField = root.Clone().Find(row, col.GetPath()); + auto [r, value] = t.ReadSparse(row, nestedField); + Assert::IsTrue(r == cdb_hr::Result::NotFound || r == cdb_hr::Result::TypeMismatch, tag.data()); + + // Write the nested field. + r = t.WriteSparse(row, nestedField, prop.Value); + ResultAssert::IsSuccess(r, tag); + + // Read the nested field + std::tie(r, value) = t.ReadSparse(row, nestedField); + ResultAssert::IsSuccess(r, tag); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(prop.Value, value, tag); + } + else + { + Assert::AreEqual(prop.Value, value, tag.data()); + } + + // Overwrite the nested field. + if (t.IsNull()) + { + r = cdb_hr::LayoutLiteral::Boolean.WriteSparse(row, nestedField, false); + ResultAssert::IsSuccess(r, tag); + } + else + { + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, nestedField, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, tag); + } + + // Verify nested field no longer there. + std::tie(r, value) = t.ReadSparse(row, nestedField); + ResultAssert::TypeMismatch(r, tag); + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Closure& closure) override + { + const cdb_hr::LayoutColumn& col = closure.Col; + ExpectedProperty& prop = closure.Prop; + std::wstring tag = cdb_core::make_string(L"Prop: %S: Tag: %s", prop.Path.data(), closure.Tag.data()); + + Logger::WriteMessage(tag.data()); + + const cdb_hr::LayoutObject& t = col.TypeAs(); + + // Verify the nested field doesn't yet appear within the new scope. + cdb_hr::RowCursor nestedField = scope.Clone().Find(row, col.GetPath()); + auto [r, scope2] = t.ReadScope(row, nestedField); + ResultAssert::NotFound(r, tag); + + // Write the nested field. + std::tie(r, scope2) = t.WriteScope(row, nestedField, col.GetTypeArgs()); + ResultAssert::IsSuccess(r, tag); + + // Read the nested field + cdb_hr::RowCursor scope3; + std::tie(r, scope3) = t.ReadScope(row, nestedField); + ResultAssert::IsSuccess(r, tag); + Assert::AreEqual(scope2.AsReadOnly().GetScopeType(), scope3.GetScopeType(), tag.data()); + Assert::AreEqual(scope2.AsReadOnly().m_start, scope3.m_start, tag.data()); + + // Overwrite the nested field. + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, nestedField, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, tag); + + // Verify nested field no longer there. + std::tie(r, scope3) = t.ReadScope(row, nestedField); + ResultAssert::TypeMismatch(r, tag); + } + }; + }; + + template + static void ParseSchemaSparseObjectMultiValue( + cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const cdb_hr::Layout& layout, + RoundTripSparseObjectMulti::ExpectedProperty& prop, + std::wstring_view tag) + { + std::tuple found = layout.TryFind(prop.Path); + Assert::IsTrue(std::get<0>(found), tag.data()); + const cdb_hr::LayoutColumn& col = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, col.GetStorage(), tag.data()); + + RoundTripSparseObjectMulti::Closure closure{col, prop, tag}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + col.GetType()->GetLayoutCode(), row, scope, &closure); + } + + template + static void ParseSchemaSparseObjectMultiCase(cdb_hr::RowBuffer& row, + RoundTripSparseObjectMulti::Expected expected) + { + auto schema = cdb_core::make_unique_with([&expected](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&expected](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([&expected](cdb_hr::ObjectPropertyType& pt) + { + for (auto& pp : expected.Props) + { + pt.GetProperties().emplace_back(std::move(pp)); + } + })); + })); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.GetSchemas().emplace_back(std::move(schema)); + }); + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + + Assert::AreEqual(size_t{1}, layout.GetColumns().size(), expected.Tag.data()); + std::tuple found = layout.TryFind("a"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& objCol = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, objCol.GetStorage(), expected.Tag.data()); + + // Try writing a row using the layout. + row.Reset(); + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + + // Verify the object doesn't exist. + const cdb_hr::LayoutObject& objT = objCol.TypeAs(); + cdb_hr::RowCursor field = cdb_hr::RowCursor::Create(row).Find(row, objCol.GetPath()); + auto [r, scope] = objT.ReadScope(row, field); + ResultAssert::NotFound(r, expected.Tag.data()); + + // Write the object and the nested column. + std::tie(r, scope) = objT.WriteScope(row, field, objCol.GetTypeArgs()); + ResultAssert::IsSuccess(r, expected.Tag.data()); + + constexpr size_t N = std::tuple_size>::value; + std::array permutation{}; + for (size_t i = 0; i < N; i++) { permutation[i] = i; } + + do + { + for (size_t i : permutation) + { + if (i == 0) + { + LayoutCompilerUnitTests::ParseSchemaSparseObjectMultiValue(row, scope, layout, + std::get<0>(expected.ExpectedProps), expected.Tag); + } + + if constexpr (N > 1) + { + if (i == 1) + { + LayoutCompilerUnitTests::ParseSchemaSparseObjectMultiValue(row, scope, layout, + std::get<1>(expected.ExpectedProps), expected.Tag); + } + } + + if constexpr (N > 2) // NOLINT(readability-misleading-indentation) + { + if (i == 2) + { + LayoutCompilerUnitTests::ParseSchemaSparseObjectMultiValue(row, scope, layout, + std::get<2>(expected.ExpectedProps), expected.Tag); + } + } + + if constexpr (N > 3) // NOLINT(readability-misleading-indentation) + { + if (i == 3) + { + LayoutCompilerUnitTests::ParseSchemaSparseObjectMultiValue(row, scope, layout, + std::get<3>(expected.ExpectedProps), expected.Tag); + } + } + } + } while (std::next_permutation(permutation.begin(), permutation.end())); + + // Write something after the scope. + std::string otherColumnPath = cdb_core::make_string("not-%s", objCol.GetPath()); + cdb_hr::RowCursor otherColumn = field.Clone().Find(row, otherColumnPath); + r = cdb_hr::LayoutLiteral::Boolean.WriteSparse(row, otherColumn, true); + ResultAssert::IsSuccess(r, expected.Tag.data()); + + // Overwrite the whole scope. + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, field, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, expected.Tag.data()); + std::tie(r, std::ignore) = objT.ReadScope(row, field); + ResultAssert::TypeMismatch(r, expected.Tag.data()); + + // Read the thing after the scope and verify it is still there. + otherColumn = field.Clone().Find(row, otherColumnPath); + bool notScope; + std::tie(r, notScope) = cdb_hr::LayoutLiteral::Boolean.ReadSparse(row, otherColumn); + ResultAssert::IsSuccess(r, expected.Tag.data()); + Assert::IsTrue(notScope, expected.Tag.data()); + } + + TEST_METHOD_WITH_OWNER(ParseSchemaSparseObjectMulti, "jthunter") + { + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + + ParseSchemaSparseObjectMultiCase(row, RoundTripSparseObjectMulti::Expected + { + L"{'path': 'b', 'type': {'type': 'int8'}}"sv, + cdb_core::make_with([](std::vector>& v) + { + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int8); + })); + })); + }), + std::make_tuple( + RoundTripSparseObjectMulti::ExpectedProperty{"a.b"sv, int8_t{42}} + ) + }); + + ParseSchemaSparseObjectMultiCase(row, RoundTripSparseObjectMulti::Expected + { + L"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'utf8'}}"sv, + cdb_core::make_with([](std::vector>& v) + { + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int8); + })); + })); + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("c"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + })); + }), + std::make_tuple( + RoundTripSparseObjectMulti::ExpectedProperty{"a.b"sv, int8_t{42}}, + RoundTripSparseObjectMulti::ExpectedProperty{"a.c"sv, "abc"sv} + ) + }); + + ParseSchemaSparseObjectMultiCase(row, + RoundTripSparseObjectMulti::Expected, cdb_hr::NullValue> + { + L"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'bool'}}, {'path': 'd', 'type': {'type': 'binary'}}, {'path': 'e', 'type': {'type': 'null'}}"sv, + cdb_core::make_with([](std::vector>& v) + { + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int8); + })); + })); + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("c"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + })); + })); + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("d"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Binary); + })); + })); + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("e"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Null); + })); + })); + }), + std::make_tuple( + RoundTripSparseObjectMulti::ExpectedProperty{"a.b"sv, int8_t{42}}, + RoundTripSparseObjectMulti::ExpectedProperty{"a.c"sv, true}, + RoundTripSparseObjectMulti::ExpectedProperty>{ + "a.d"sv, + std::array{{byte{0x01}, byte{0x02}, byte{0x03}}} + }, + RoundTripSparseObjectMulti::ExpectedProperty{"a.e"sv, {}} + ) + }); + + ParseSchemaSparseObjectMultiCase(row, RoundTripSparseObjectMulti::Expected + { + L"{'path': 'b', 'type': {'type': 'object'}}"sv, + cdb_core::make_with([](std::vector>& v) + { + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(std::make_unique()); + })); + }), + std::make_tuple( + RoundTripSparseObjectMulti::ExpectedProperty{"a.b"sv, nullptr} + ) + }); + } + + struct RoundTripSparseObjectNested final + { + template + struct ExpectedProperty + { + std::string_view Path; + TValue Value; + }; + + template + struct Expected final + { + std::wstring_view Tag; + std::vector> Props; + std::tuple...> ExpectedProps; + }; + + template + struct Closure final + { + const cdb_hr::LayoutColumn& Col; + ExpectedProperty& Prop; + std::wstring_view Tag; + }; + + // Class Template Argument Deduction (CTAD) hint + template Closure( + const cdb_hr::LayoutColumn& col, ExpectedProperty& prop, std::wstring_view tag) -> Closure; + + /// Ensure that a parent scope exists in the row. + /// The row to create the desired scope. + /// The root scope. + /// The scope to create. + /// A string to tag errors with. + /// The enclosing scope. + static cdb_hr::RowCursor EnsureScope(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, const cdb_hr::LayoutColumn* col, std::wstring_view tag) + { + if (col == nullptr) + { + return root; + } + + cdb_hr::RowCursor parentScope = RoundTripSparseObjectNested::EnsureScope(row, root, col->GetParent(), tag); + + const cdb_hr::LayoutObject& pT = col->GetType()->TypeAs(); + cdb_hr::RowCursor field = parentScope.Clone().Find(row, col->GetPath()); + auto [r, scope] = pT.ReadScope(row, field); + if (r == cdb_hr::Result::NotFound) + { + std::tie(r, scope) = pT.WriteScope(row, field, col->GetTypeArgs()); + } + + ResultAssert::IsSuccess(r, tag); + return scope; + } + + template + struct Dispatcher : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& col = closure.Col; + ExpectedProperty& prop = closure.Prop; + std::wstring tag = cdb_core::make_string(L"Prop: %S: Tag: %s", prop.Path.data(), closure.Tag.data()); + Logger::WriteMessage(tag.data()); + + const TLayout& t = col.GetType()->TypeAs(); + + // Ensure scope exists. + cdb_hr::RowCursor scope = RoundTripSparseObjectNested::EnsureScope(row, root, col.GetParent(), tag); + + // Write the nested field. + cdb_hr::RowCursor field = scope.Clone().Find(row, col.GetPath()); + cdb_hr::Result r = t.WriteSparse(row, field, prop.Value); + ResultAssert::IsSuccess(r, tag); + + // Read the nested field + TValue value; + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::IsSuccess(r, tag); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(prop.Value, value, tag); + } + else + { + Assert::AreEqual(prop.Value, value, tag.data()); + } + + // Overwrite the nested field. + if (t.IsNull()) + { + r = cdb_hr::LayoutLiteral::Boolean.WriteSparse(row, field, false); + ResultAssert::IsSuccess(r, tag); + } + else + { + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, field, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, tag); + } + + // Verify nested field no longer there. + std::tie(r, value) = t.ReadSparse(row, field); + ResultAssert::TypeMismatch(r, tag); + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& col = closure.Col; + ExpectedProperty& prop = closure.Prop; + std::wstring tag = cdb_core::make_string(L"Prop: %S: Tag: %s", prop.Path.data(), closure.Tag.data()); + Logger::WriteMessage(tag.data()); + + // Ensure scope exists. + cdb_hr::RowCursor scope = RoundTripSparseObjectNested::EnsureScope(row, root, &col, tag); + Assert::AreNotEqual(root.m_start, scope.m_start, tag.data()); + } + }; + }; + + template + static void ParseSchemaSparseObjectNestedValue( + cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const cdb_hr::Layout& layout, + RoundTripSparseObjectNested::ExpectedProperty& prop, + std::wstring_view tag) + { + std::tuple found = layout.TryFind(prop.Path); + Assert::IsTrue(std::get<0>(found), tag.data()); + const cdb_hr::LayoutColumn& col = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, col.GetStorage(), tag.data()); + + RoundTripSparseObjectNested::Closure closure{col, prop, tag}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + col.GetType()->GetLayoutCode(), row, scope, &closure); + } + + template + static void ParseSchemaSparseObjectNestedCase(cdb_hr::RowBuffer& row, + RoundTripSparseObjectNested::Expected expected) + { + auto schema = cdb_core::make_unique_with([&expected](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([&expected](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([&expected](cdb_hr::ObjectPropertyType& pt) + { + pt.GetProperties().emplace_back(cdb_core::make_unique_with([&expected](cdb_hr::Property& p2) + { + p2.SetPath("b"s); + p2.SetPropertyType(cdb_core::make_unique_with([&expected](cdb_hr::ObjectPropertyType& pt2) + { + for (auto& pp : expected.Props) + { + pt2.GetProperties().emplace_back(std::move(pp)); + } + })); + })); + })); + })); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.GetSchemas().emplace_back(std::move(schema)); + }); + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + + std::tuple found = layout.TryFind("a"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& objCol = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, objCol.GetStorage(), expected.Tag.data()); + found = layout.TryFind("a.b"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& objCol2 = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, objCol2.GetStorage(), expected.Tag.data()); + + // Try writing a row using the layout. + row.Reset(); + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + + // Write the object. + const cdb_hr::LayoutObject& objT = objCol.TypeAs(); + cdb_hr::RowCursor field = root.Clone().Find(row, objCol.GetPath()); + auto [r, _] = objT.WriteScope(row, field, objCol.GetTypeArgs()); + ResultAssert::IsSuccess(r, expected.Tag.data()); + + constexpr size_t N = std::tuple_size>::value; + std::array permutation{}; + for (size_t i = 0; i < N; i++) { permutation[i] = i; } + + do + { + for (size_t i : permutation) + { + if (i == 0) + { + LayoutCompilerUnitTests::ParseSchemaSparseObjectNestedValue(row, root, layout, + std::get<0>(expected.ExpectedProps), expected.Tag); + } + + if constexpr (N > 1) + { + if (i == 1) + { + LayoutCompilerUnitTests::ParseSchemaSparseObjectNestedValue(row, root, layout, + std::get<1>(expected.ExpectedProps), expected.Tag); + } + } + + if constexpr (N > 2) // NOLINT(readability-misleading-indentation) + { + if (i == 2) + { + LayoutCompilerUnitTests::ParseSchemaSparseObjectNestedValue(row, root, layout, + std::get<2>(expected.ExpectedProps), expected.Tag); + } + } + + if constexpr (N > 3) // NOLINT(readability-misleading-indentation) + { + if (i == 3) + { + LayoutCompilerUnitTests::ParseSchemaSparseObjectNestedValue(row, root, layout, + std::get<3>(expected.ExpectedProps), expected.Tag); + } + } + } + } while (std::next_permutation(permutation.begin(), permutation.end())); + } + + TEST_METHOD_WITH_OWNER(ParseSchemaSparseObjectNested, "jthunter") + { + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + + ParseSchemaSparseObjectNestedCase(row, RoundTripSparseObjectNested::Expected + { + L"{'path': 'c', 'type': {'type': 'int8'}}"sv, + cdb_core::make_with([](std::vector>& v) + { + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("c"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int8); + })); + })); + }), + std::make_tuple( + RoundTripSparseObjectNested::ExpectedProperty{"a.b.c"sv, int8_t{42}} + ) + }); + + ParseSchemaSparseObjectNestedCase(row, RoundTripSparseObjectNested::Expected + { + L"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'utf8'}}"sv, + cdb_core::make_with([](std::vector>& v) + { + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int8); + })); + })); + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("c"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + })); + }), + std::make_tuple( + RoundTripSparseObjectNested::ExpectedProperty{"a.b.b"sv, int8_t{42}}, + RoundTripSparseObjectNested::ExpectedProperty{"a.b.c"sv, "abc"sv} + ) + }); + + ParseSchemaSparseObjectNestedCase(row, + RoundTripSparseObjectNested::Expected, cdb_hr::NullValue> + { + L"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'bool'}}, {'path': 'd', 'type': {'type': 'binary'}}, {'path': 'e', 'type': {'type': 'null'}}"sv, + cdb_core::make_with([](std::vector>& v) + { + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int8); + })); + })); + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("c"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + })); + })); + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("d"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Binary); + })); + })); + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("e"s); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Null); + })); + })); + }), + std::make_tuple( + RoundTripSparseObjectNested::ExpectedProperty{"a.b.b"sv, int8_t{42}}, + RoundTripSparseObjectNested::ExpectedProperty{"a.b.c"sv, true}, + RoundTripSparseObjectNested::ExpectedProperty>{ + "a.b.d"sv, + std::array{{byte{0x01}, byte{0x02}, byte{0x03}}} + }, + RoundTripSparseObjectNested::ExpectedProperty{"a.b.e"sv, {}} + ) + }); + + ParseSchemaSparseObjectNestedCase(row, RoundTripSparseObjectNested::Expected + { + L"{'path': 'b', 'type': {'type': 'object'}}"sv, + cdb_core::make_with([](std::vector>& v) + { + v.emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("b"s); + p.SetPropertyType(std::make_unique()); + })); + }), + std::make_tuple( + RoundTripSparseObjectNested::ExpectedProperty{"a.b.b"sv, nullptr} + ) + }); + } + + struct RoundTripSparseArray final + { + template + struct Expected final + { + std::wstring_view Tag; + cdb_hr::TypeKind Type; + const cdb_hr::LayoutType& LayoutType; + std::vector Value; + }; + + template + struct Closure final + { + const cdb_hr::LayoutColumn& ArrCol; + Expected& Expected; + }; + + template + struct Dispatcher : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& arrCol = closure.ArrCol; + const cdb_hr::LayoutIndexedScope& arrT = arrCol.TypeAs(); + Expected& expected = closure.Expected; + std::wstring tag = cdb_core::make_string(L"Tag: %s, Array: %S", expected.Tag.data(), + arrCol.GetType()->GetName().data()); + + Logger::WriteMessage(tag.data()); + + const TLayout& t = expected.LayoutType.template TypeAs(); + + // Verify the array doesn't yet exist. + cdb_hr::RowCursor field = root.Clone().Find(row, arrCol.GetPath()); + auto [r, scope] = arrT.ReadScope(row, field); + ResultAssert::NotFound(r, tag); + + // Write the array. + std::tie(r, scope) = arrT.WriteScope(row, field, arrCol.GetTypeArgs()); + ResultAssert::IsSuccess(r, tag); + + // Verify the nested field doesn't yet appear within the new scope. + Assert::IsFalse(scope.MoveNext(row), tag.data()); + TValue value; + std::tie(r, value) = t.ReadSparse(row, scope); + ResultAssert::NotFound(r, tag); + + // Write the nested fields. + cdb_hr::RowCursor elm = scope.Clone(); + for (TValue item : expected.Value) + { + // Write the ith index. + r = t.WriteSparse(row, elm, item); + ResultAssert::IsSuccess(r, tag); + + // Move cursor to the ith+1 index. + Assert::IsFalse(elm.MoveNext(row), tag.data()); + } + + // Read the array and the nested column, validate the nested column has the proper value. + cdb_hr::RowCursor scope2; + std::tie(r, scope2) = arrT.ReadScope(row, field); + ResultAssert::IsSuccess(r, tag); + Assert::AreEqual(scope.GetScopeType(), scope2.GetScopeType(), tag.data()); + Assert::AreEqual(scope.m_start, scope2.m_start, tag.data()); + Assert::AreEqual(scope.GetImmutable(), scope2.GetImmutable(), tag.data()); + + // Read the nested fields + elm = scope2.Clone(); + for (TValue item : expected.Value) + { + Assert::IsTrue(elm.MoveNext(row)); + std::tie(r, value) = t.ReadSparse(row, elm); + ResultAssert::IsSuccess(r, tag); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(item, value, tag); + } + else + { + Assert::AreEqual(item, value, tag.data()); + } + } + + // Delete an item. + int indexToDelete = 1; + elm = scope2.Clone(); + Assert::IsTrue(elm.MoveTo(row, indexToDelete), tag.data()); + r = t.DeleteSparse(row, elm); + ResultAssert::IsSuccess(r, tag); + std::vector remainingValues = std::vector(expected.Value); + remainingValues.erase(remainingValues.begin() + indexToDelete); + elm = scope2.Clone(); + for (TValue item : remainingValues) + { + Assert::IsTrue(elm.MoveNext(row), tag.data()); + std::tie(r, value) = t.ReadSparse(row, elm); + ResultAssert::IsSuccess(r, tag); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(item, value, tag); + } + else + { + Assert::AreEqual(item, value, tag.data()); + } + } + + elm = scope2.Clone(); + Assert::IsFalse(elm.MoveTo(row, static_cast(remainingValues.size())), tag.data()); + + cdb_hr::RowCursor roRoot = root.AsReadOnly().Find(row, arrCol.GetPath()); + ResultAssert::InsufficientPermissions(arrT.DeleteScope(row, roRoot)); + std::tie(r, scope2) = arrT.WriteScope(row, roRoot, arrCol.GetTypeArgs()); + ResultAssert::InsufficientPermissions(r); + + // Overwrite the whole scope. + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, field, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, tag); + std::tie(r, std::ignore) = arrT.ReadScope(row, field); + ResultAssert::TypeMismatch(r, tag); + r = arrT.DeleteScope(row, field); + ResultAssert::TypeMismatch(r, expected.Tag.data()); + + // Overwrite it again, then delete it. + std::tie(r, std::ignore) = arrT.WriteScope(row, field, arrCol.GetTypeArgs(), cdb_hr::UpdateOptions::Update); + ResultAssert::IsSuccess(r, expected.Tag.data()); + r = arrT.DeleteScope(row, field); + ResultAssert::IsSuccess(r, expected.Tag.data()); + std::tie(r, std::ignore) = arrT.ReadScope(row, field); + ResultAssert::NotFound(r, expected.Tag.data()); + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Closure& closure) override + { + Assert::Fail(L"not implemented"); + } + }; + }; + + template + void ParseSchemaSparseArrayCase(cdb_hr::RowBuffer& row, RoundTripSparseArray::Expected expected) + { + for (cdb_hr::LayoutCode arrT : std::array{cdb_hr::LayoutCode::TypedArrayScope, cdb_hr::LayoutCode::ArrayScope}) + { + auto schema = cdb_core::make_unique_with([arrT, itemType = expected.Type](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([arrT, itemType](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([arrT, itemType](cdb_hr::ArrayPropertyType& pt) + { + if (arrT == cdb_hr::LayoutCode::TypedArrayScope) + { + pt.SetItems(cdb_core::make_unique_with([arrT, itemType](cdb_hr::PrimitivePropertyType& pt2) + { + pt2.SetType(itemType); + pt2.SetNullable(false); + })); + } + })); + })); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.GetSchemas().emplace_back(std::move(schema)); + }); + + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + + std::tuple found = layout.TryFind("a"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& arrCol = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, arrCol.GetStorage(), expected.Tag.data()); + + // Try writing a row using the layout. + row.Reset(); + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + RoundTripSparseArray::Closure closure{arrCol, expected}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + expected.LayoutType.GetLayoutCode(), + row, + root, + &closure); + } + } + + TEST_METHOD_WITH_OWNER(ParseSchemaSparseArray, "jthunter") + { + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[null]"sv, cdb_hr::TypeKind::Null, cdb_hr::LayoutLiteral::Null, {cdb_hr::NullValue{}, cdb_hr::NullValue{}, cdb_hr::NullValue{}}}); + + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[bool]"sv, cdb_hr::TypeKind::Boolean, cdb_hr::LayoutLiteral::Boolean, {true, false, true}}); + + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[int8]"sv, cdb_hr::TypeKind::Int8, cdb_hr::LayoutLiteral::Int8, {int8_t{42}, int8_t{43}, int8_t{44}}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[int16]"sv, cdb_hr::TypeKind::Int16, cdb_hr::LayoutLiteral::Int16, {int16_t{42}, int16_t{43}, int16_t{44}}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[int32]"sv, cdb_hr::TypeKind::Int32, cdb_hr::LayoutLiteral::Int32, {42, 43, 44}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[int64]"sv, cdb_hr::TypeKind::Int64, cdb_hr::LayoutLiteral::Int64, {42i64, 43i64, 44i64}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[uint8]"sv, cdb_hr::TypeKind::UInt8, cdb_hr::LayoutLiteral::UInt8, {uint8_t{42}, uint8_t{43}, uint8_t{44}}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[uint16]"sv, cdb_hr::TypeKind::UInt16, cdb_hr::LayoutLiteral::UInt16, {uint16_t{42}, uint16_t{43}, uint16_t{44}}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[uint32]"sv, cdb_hr::TypeKind::UInt32, cdb_hr::LayoutLiteral::UInt32, {42ui32, 43ui32, 44ui32}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[uint64]"sv, cdb_hr::TypeKind::UInt64, cdb_hr::LayoutLiteral::UInt64, {42ui64, 43ui64, 44ui64}}); + + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[varint]"sv, cdb_hr::TypeKind::VarInt, cdb_hr::LayoutLiteral::VarInt, {42i64, 43i64, 44i64}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[varuint]"sv, cdb_hr::TypeKind::VarUInt, cdb_hr::LayoutLiteral::VarUInt, {42ui64, 43ui64, 44ui64}}); + + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[float32]"sv, cdb_hr::TypeKind::Float32, cdb_hr::LayoutLiteral::Float32, {4.2F, 4.3F, 4.4F}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + {L"array[float64]"sv, cdb_hr::TypeKind::Float64, cdb_hr::LayoutLiteral::Float64, {4.2, 4.3, 4.4}}); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + { + L"array[float128]"sv, + cdb_hr::TypeKind::Float128, + cdb_hr::LayoutLiteral::Float128, + {cdb_hr::Float128{0, 42}, cdb_hr::Float128{0, 43}, cdb_hr::Float128{0, 44}} + }); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + { + L"array[decimal]"sv, + cdb_hr::TypeKind::Decimal, + cdb_hr::LayoutLiteral::Decimal, + {cdb_hr::Decimal{42}, cdb_hr::Decimal{43}, cdb_hr::Decimal{44}} + }); + + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + { + L"array[datetime]"sv, + cdb_hr::TypeKind::DateTime, + cdb_hr::LayoutLiteral::DateTime, + {cdb_hr::DateTime{0}, cdb_hr::DateTime{1}, cdb_hr::DateTime{2}} + }); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + { + L"array[unixdatetime]"sv, + cdb_hr::TypeKind::UnixDateTime, + cdb_hr::LayoutLiteral::UnixDateTime, + {cdb_hr::UnixDateTime{1}, cdb_hr::UnixDateTime{2}, cdb_hr::UnixDateTime{3}} + }); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + { + L"array[guid]"sv, + cdb_hr::TypeKind::Guid, + cdb_hr::LayoutLiteral::Guid, + { + cdb_hr::Guid{0x8ddde8c5, 0xbba1, 0x43dc, 0xa4, 0xcc, 0x3e, 0x7f, 0xb8, 0x4e, 0x96, 0xc0}, + cdb_hr::Guid{0x20825fb2, 0xc1b0, 0x400f, 0x91, 0x67, 0xce, 0x34, 0x4f, 0xbd, 0xb0, 0x91}, + cdb_hr::Guid{0xbc722d4e, 0x4e31, 0x42e0, 0xac, 0xe7, 0xd, 0xe7, 0x9b, 0xbb, 0xc8, 0x19} + } + }); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + { + L"array[mongodbobjectid]"sv, + cdb_hr::TypeKind::MongoDbObjectId, + cdb_hr::LayoutLiteral::MongoDbObjectId, + {cdb_hr::MongoDbObjectId{0, 1}, cdb_hr::MongoDbObjectId{0, 2}, cdb_hr::MongoDbObjectId{0, 3}} + }); + + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected + { + L"array[utf8]"sv, + cdb_hr::TypeKind::Utf8, + cdb_hr::LayoutLiteral::Utf8, + {"abc"sv, "def"sv, "xyz"sv} + }); + LayoutCompilerUnitTests::ParseSchemaSparseArrayCase(row, RoundTripSparseArray::Expected> + { + L"array[binary]"sv, + cdb_hr::TypeKind::Binary, + cdb_hr::LayoutLiteral::Binary, + { + std::array{{byte{0x01}, byte{0x02}}}, + std::array{{byte{0x03}, byte{0x04}}}, + std::array{{byte{0x05}, byte{0x06}}}, + }, + }); + } + + struct RoundTripSparseSet final + { + template + struct Expected final + { + std::wstring_view Tag; + cdb_hr::TypeKind Type; + const cdb_hr::LayoutType& LayoutType; + std::vector Value; + }; + + template + struct Closure final + { + const cdb_hr::LayoutColumn& SetCol; + Expected& Expected; + }; + + template + struct Dispatcher : TestActionDispatcher, TLayout, TValue> + { + void Dispatch(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& root, Closure& closure) override + { + const cdb_hr::LayoutColumn& setCol = closure.SetCol; + const cdb_hr::LayoutUniqueScope& setT = setCol.TypeAs(); + Expected& expected = closure.Expected; + std::wstring tag = cdb_core::make_string(L"Tag: %s, Set: %S", expected.Tag.data(), + setCol.GetType()->GetName().data()); + + Logger::WriteMessage(tag.data()); + const TLayout& t = expected.LayoutType.template TypeAs(); + + // Verify the Set doesn't yet exist. + cdb_hr::RowCursor field = root.Clone().Find(row, setCol.GetPath()); + auto [r, scope] = setT.ReadScope(row, field); + ResultAssert::NotFound(r, tag); + + // Write the Set. + std::tie(r, scope) = setT.WriteScope(row, field, setCol.GetTypeArgs()); + ResultAssert::IsSuccess(r, tag); + + // Verify the nested field doesn't yet appear within the new scope. + Assert::IsFalse(scope.MoveNext(row), tag.data()); + TValue value; + std::tie(r, value) = t.ReadSparse(row, scope); + ResultAssert::NotFound(r, tag); + + // Write the nested fields. + for (TValue v1 : expected.Value) + { + // Write the ith item into staging storage. + cdb_hr::RowCursor tempCursor = root.Clone().Find(row, ""sv); + r = t.WriteSparse(row, tempCursor, v1); + ResultAssert::IsSuccess(r, tag); + + // Move item into the set. + r = setT.MoveField(row, scope, tempCursor); + ResultAssert::IsSuccess(r, tag); + } + + // Attempts to insert the same items into the set again will fail. + for (TValue v2 : expected.Value) + { + // Write the ith item into staging storage. + cdb_hr::RowCursor tempCursor = root.Clone().Find(row, ""sv); + r = t.WriteSparse(row, tempCursor, v2); + ResultAssert::IsSuccess(r, tag); + + // Move item into the set. + r = setT.MoveField(row, scope, tempCursor, cdb_hr::UpdateOptions::Insert); + ResultAssert::Exists(r, tag); + } + + // Read the Set and the nested column, validate the nested column has the proper value. + cdb_hr::RowCursor scope2; + std::tie(r, scope2) = setT.ReadScope(row, field); + ResultAssert::IsSuccess(r, tag); + Assert::AreEqual(scope.GetScopeType(), scope2.GetScopeType(), tag.data()); + Assert::AreEqual(scope.m_start, scope2.m_start, tag.data()); + Assert::AreEqual(scope.GetImmutable(), scope2.GetImmutable(), tag.data()); + + // Read the nested fields + std::tie(r, scope) = setT.ReadScope(row, field); + ResultAssert::IsSuccess(r); + for (TValue item : expected.Value) + { + Assert::IsTrue(scope.MoveNext(row), tag.data()); + std::tie(r, value) = t.ReadSparse(row, scope); + ResultAssert::IsSuccess(r, tag); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(item, value, tag); + } + else + { + Assert::AreEqual(item, value, tag.data()); + } + } + + // Delete all of the items and then insert them again in the opposite order. + std::tie(r, scope) = setT.ReadScope(row, field); + ResultAssert::IsSuccess(r); + for (size_t i = 0; i < expected.Value.size(); i++) + { + Assert::IsTrue(scope.MoveNext(row), tag.data()); + r = t.DeleteSparse(row, scope); + ResultAssert::IsSuccess(r, tag); + } + + std::tie(r, scope) = setT.ReadScope(row, field); + ResultAssert::IsSuccess(r); + for (int i = static_cast(expected.Value.size()) - 1; i >= 0; --i) + { + // Write the ith item into staging storage. + cdb_hr::RowCursor tempCursor = root.Clone().Find(row, ""sv); + r = t.WriteSparse(row, tempCursor, expected.Value[i]); + ResultAssert::IsSuccess(r, tag); + + // Move item into the set. + r = setT.MoveField(row, scope, tempCursor); + ResultAssert::IsSuccess(r, tag); + } + + // Verify they still enumerate in sorted order. + std::tie(r, scope) = setT.ReadScope(row, field); + ResultAssert::IsSuccess(r); + for (TValue item : expected.Value) + { + Assert::IsTrue(scope.MoveNext(row), tag.data()); + std::tie(r, value) = t.ReadSparse(row, scope); + ResultAssert::IsSuccess(r, tag); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(item, value, tag); + } + else + { + Assert::AreEqual(item, value, tag.data()); + } + } + + // Delete one item. + if (expected.Value.size() > 1) + { + int indexToDelete = 1; + std::tie(r, scope) = setT.ReadScope(row, field); + ResultAssert::IsSuccess(r); + Assert::IsTrue(scope.MoveTo(row, indexToDelete), tag.data()); + r = t.DeleteSparse(row, scope); + ResultAssert::IsSuccess(r, tag); + std::vector remainingValues(expected.Value); + remainingValues.erase(remainingValues.begin() + indexToDelete); + + std::tie(r, scope) = setT.ReadScope(row, field); + ResultAssert::IsSuccess(r); + for (TValue item : remainingValues) + { + Assert::IsTrue(scope.MoveNext(row), tag.data()); + std::tie(r, value) = t.ReadSparse(row, scope); + ResultAssert::IsSuccess(r, tag); + if constexpr (std::is_same_v>) + { + CollectionAssert::AreEqual(item, value, tag); + } + else + { + Assert::AreEqual(item, value, tag.data()); + } + } + + Assert::IsFalse(scope.MoveTo(row, static_cast(remainingValues.size())), tag.data()); + } + + cdb_hr::RowCursor roRoot = root.AsReadOnly().Find(row, setCol.GetPath()); + ResultAssert::InsufficientPermissions(setT.DeleteScope(row, roRoot)); + std::tie(r, std::ignore) = setT.WriteScope(row, roRoot, setCol.GetTypeArgs()); + ResultAssert::InsufficientPermissions(r); + + // Overwrite the whole scope. + r = cdb_hr::LayoutLiteral::Null.WriteSparse(row, field, cdb_hr::NullValue{}); + ResultAssert::IsSuccess(r, tag); + std::tie(r, std::ignore) = setT.ReadScope(row, field); + ResultAssert::TypeMismatch(r, tag); + r = setT.DeleteScope(row, field); + ResultAssert::TypeMismatch(r, expected.Tag.data()); + + // Overwrite it again, then delete it. + std::tie(r, std::ignore) = setT.WriteScope(row, field, setCol.GetTypeArgs(), cdb_hr::UpdateOptions::Update); + ResultAssert::IsSuccess(r, expected.Tag.data()); + r = setT.DeleteScope(row, field); + ResultAssert::IsSuccess(r, expected.Tag.data()); + std::tie(r, std::ignore) = setT.ReadScope(row, field); + ResultAssert::NotFound(r, expected.Tag.data()); + } + }; + + struct ObjectDispatcher final : TestActionObjectDispatcher> + { + void DispatchObject(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Closure& closure) override + { + Assert::Fail(L"not implemented"); + } + }; + }; + + template + void ParseSchemaSparseSetCase(cdb_hr::RowBuffer& row, RoundTripSparseSet::Expected expected) + { + for (cdb_hr::LayoutCode arrT : std::array{cdb_hr::LayoutCode::TypedSetScope /*, LayoutCode::SetScope*/}) + { + auto schema = cdb_core::make_unique_with([arrT, itemType = expected.Type](cdb_hr::Schema& s) + { + s.SetName("table"s); + s.SetSchemaId(cdb_hr::SchemaId{-1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([arrT, itemType](cdb_hr::Property& p) + { + p.SetPath("a"s); + p.SetPropertyType(cdb_core::make_unique_with([arrT, itemType](cdb_hr::SetPropertyType& pt) + { + if (arrT == cdb_hr::LayoutCode::TypedSetScope) + { + pt.SetItems(cdb_core::make_unique_with([arrT, itemType](cdb_hr::PrimitivePropertyType& pt2) + { + pt2.SetType(itemType); + pt2.SetNullable(false); + })); + } + })); + })); + }); + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.GetSchemas().emplace_back(std::move(schema)); + }); + + cdb_hr::LayoutResolverNamespace resolver{std::move(ns)}; + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::SchemaId{-1}); + + std::tuple found = layout.TryFind("a"sv); + Assert::IsTrue(std::get<0>(found), expected.Tag.data()); + const cdb_hr::LayoutColumn& setCol = *std::get<1>(found); + Assert::AreEqual(cdb_hr::StorageKind::Sparse, setCol.GetStorage(), expected.Tag.data()); + + // Try writing a row using the layout. + row.Reset(); + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + cdb_hr::HybridRowHeader header = row.GetHeader(); + Assert::AreEqual(cdb_hr::HybridRowVersion::V1, header.GetVersion()); + Assert::AreEqual(layout.GetSchemaId(), header.GetSchemaId()); + + cdb_hr::RowCursor root = cdb_hr::RowCursor::Create(row); + RoundTripSparseSet::Closure closure{setCol, expected}; + LayoutCompilerUnitTests::LayoutCodeSwitch( + expected.LayoutType.GetLayoutCode(), + row, + root, + &closure); + } + } + + TEST_METHOD_WITH_OWNER(ParseSchemaSparseSet, "jthunter") + { + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[null]"sv, cdb_hr::TypeKind::Null, cdb_hr::LayoutLiteral::Null, {cdb_hr::NullValue{}}}); + + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[bool]"sv, cdb_hr::TypeKind::Boolean, cdb_hr::LayoutLiteral::Boolean, {false, true}}); + + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[int8]"sv, cdb_hr::TypeKind::Int8, cdb_hr::LayoutLiteral::Int8, {int8_t{42}, int8_t{43}, int8_t{44}}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[int16]"sv, cdb_hr::TypeKind::Int16, cdb_hr::LayoutLiteral::Int16, {int16_t{42}, int16_t{43}, int16_t{44}}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[int32]"sv, cdb_hr::TypeKind::Int32, cdb_hr::LayoutLiteral::Int32, {42, 43, 44}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[int64]"sv, cdb_hr::TypeKind::Int64, cdb_hr::LayoutLiteral::Int64, {42i64, 43i64, 44i64}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[uint8]"sv, cdb_hr::TypeKind::UInt8, cdb_hr::LayoutLiteral::UInt8, {uint8_t{42}, uint8_t{43}, uint8_t{44}}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[uint16]"sv, cdb_hr::TypeKind::UInt16, cdb_hr::LayoutLiteral::UInt16, {uint16_t{42}, uint16_t{43}, uint16_t{44}}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[uint32]"sv, cdb_hr::TypeKind::UInt32, cdb_hr::LayoutLiteral::UInt32, {42ui32, 43ui32, 44ui32}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[uint64]"sv, cdb_hr::TypeKind::UInt64, cdb_hr::LayoutLiteral::UInt64, {42ui64, 43ui64, 44ui64}}); + + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[varint]"sv, cdb_hr::TypeKind::VarInt, cdb_hr::LayoutLiteral::VarInt, {42i64, 43i64, 44i64}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[varuint]"sv, cdb_hr::TypeKind::VarUInt, cdb_hr::LayoutLiteral::VarUInt, {42ui64, 43ui64, 44ui64}}); + + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + {L"set[float32]"sv, cdb_hr::TypeKind::Float32, cdb_hr::LayoutLiteral::Float32, {4.2F, 4.3F, 4.4F}}); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + { + L"set[float64]"sv, + cdb_hr::TypeKind::Float64, + cdb_hr::LayoutLiteral::Float64, + { + static_cast(0xAAAAAAAAAAAAAAAA), + static_cast(0xBBBBBBBBBBBBBBBB), + static_cast(0xCCCCCCCCCCCCCCCC), + } + }); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + { + L"set[float128]"sv, + cdb_hr::TypeKind::Float128, + cdb_hr::LayoutLiteral::Float128, + {cdb_hr::Float128{0, 42}, cdb_hr::Float128{0, 43}, cdb_hr::Float128{0, 44}} + }); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + { + L"set[decimal]"sv, + cdb_hr::TypeKind::Decimal, + cdb_hr::LayoutLiteral::Decimal, + {cdb_hr::Decimal{42}, cdb_hr::Decimal{43}, cdb_hr::Decimal{44}} + }); + + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + { + L"set[datetime]"sv, + cdb_hr::TypeKind::DateTime, + cdb_hr::LayoutLiteral::DateTime, + {cdb_hr::DateTime{1}, cdb_hr::DateTime{2}, cdb_hr::DateTime{3}} + }); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + { + L"set[unixdatetime]"sv, + cdb_hr::TypeKind::UnixDateTime, + cdb_hr::LayoutLiteral::UnixDateTime, + {cdb_hr::UnixDateTime{1}, cdb_hr::UnixDateTime{2}, cdb_hr::UnixDateTime{3}} + }); + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected + { + L"set[guid]"sv, + cdb_hr::TypeKind::Guid, + cdb_hr::LayoutLiteral::Guid, + { + cdb_hr::Guid{0xaaaaaaaa, 0xaaaa, 0xaaaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}, + cdb_hr::Guid{0xbbbbbbbb, 0xbbbb, 0xbbbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb}, + cdb_hr::Guid{0xcccccccc, 0xcccc, 0xcccc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc} + } + }); + + LayoutCompilerUnitTests::ParseSchemaSparseSetCase(row, RoundTripSparseSet::Expected> + { + L"set[binary]"sv, + cdb_hr::TypeKind::Binary, + cdb_hr::LayoutLiteral::Binary, + { + std::array{{byte{0x01}, byte{0x02}}}, + std::array{{byte{0x03}, byte{0x04}}}, + std::array{{byte{0x05}, byte{0x06}}}, + }, + }); + } + }; +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/LayoutLiteralUnitTests.cpp b/src/Serialization/HybridRow.Native.Tests.Unit/LayoutLiteralUnitTests.cpp new file mode 100644 index 0000000..348f076 --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/LayoutLiteralUnitTests.cpp @@ -0,0 +1,104 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" +#include "CppUnitTestFramework.inl" + +namespace cdb_hr_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + TEST_CLASS(LayoutLiteralUnitTests) + { + public: + + template>> + struct LayoutTypeCheck + { + static void Check(const cdb_hr::LayoutType& literal) noexcept + { + const cdb_hr::LayoutType* q = static_cast(&literal); + Assert::AreEqual(q, cdb_hr::LayoutLiteral::FromCode()); + Assert::AreEqual(code, literal.GetLayoutCode()); + const T& r = q->TypeAs(); + Assert::AreEqual(q, static_cast(&r)); + } + }; + + TEST_METHOD_WITH_OWNER(TypeAsTest, "jthunter") + { + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Int8); + + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Int8); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Int16); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Int32); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Int64); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::UInt8); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::UInt16); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::UInt32); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::UInt64); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::VarInt); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::VarUInt); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Float32); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Float64); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Float128); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Decimal); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::DateTime); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::UnixDateTime); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Guid); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::MongoDbObjectId); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Null); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Boolean); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::BooleanFalse); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Utf8); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Binary); + + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Object); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableObject); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Array); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableArray); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::TypedArray); + LayoutTypeCheck + ::Check(cdb_hr::LayoutLiteral::ImmutableTypedArray); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::TypedSet); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableTypedSet); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::TypedMap); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableTypedMap); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Tuple); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableTuple); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::TypedTuple); + LayoutTypeCheck + ::Check(cdb_hr::LayoutLiteral::ImmutableTypedTuple); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Tagged); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableTagged); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Tagged2); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableTagged2); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::Nullable); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableNullable); + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::UDT); + LayoutTypeCheck::Check( + cdb_hr::LayoutLiteral::ImmutableUDT); + + LayoutTypeCheck::Check(cdb_hr::LayoutLiteral::EndScope); + } + }; +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/ResultAssert.h b/src/Serialization/HybridRow.Native.Tests.Unit/ResultAssert.h new file mode 100644 index 0000000..c0321f9 --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/ResultAssert.h @@ -0,0 +1,74 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "CppUnitTest.h" + +namespace cdb_hr_test +{ + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + struct ResultAssert final + { + static void IsSuccess(cdb_hr::Result actual) + { + Assert::AreEqual(cdb_hr::Result::Success, actual); + } + + static void IsSuccess(cdb_hr::Result actual, std::wstring_view message) + { + Assert::AreEqual(cdb_hr::Result::Success, actual, message.data()); + } + + static void NotFound(cdb_hr::Result actual) + { + Assert::AreEqual(cdb_hr::Result::NotFound, actual); + } + + static void NotFound(cdb_hr::Result actual, std::wstring_view message) + { + Assert::AreEqual(cdb_hr::Result::NotFound, actual, message.data()); + } + + static void Exists(cdb_hr::Result actual) + { + Assert::AreEqual(cdb_hr::Result::Exists, actual); + } + + static void Exists(cdb_hr::Result actual, std::wstring_view message) + { + Assert::AreEqual(cdb_hr::Result::Exists, actual, message.data()); + } + + static void TypeMismatch(cdb_hr::Result actual) + { + Assert::AreEqual(cdb_hr::Result::TypeMismatch, actual); + } + + static void TypeMismatch(cdb_hr::Result actual, std::wstring_view message) + { + Assert::AreEqual(cdb_hr::Result::TypeMismatch, actual, message.data()); + } + + static void InsufficientPermissions(cdb_hr::Result actual) + { + Assert::AreEqual(cdb_hr::Result::InsufficientPermissions, actual); + } + + static void InsufficientPermissions(cdb_hr::Result actual, std::wstring_view message) + { + Assert::AreEqual(cdb_hr::Result::InsufficientPermissions, actual, message.data()); + } + + static void TypeConstraint(cdb_hr::Result actual) + { + Assert::AreEqual(cdb_hr::Result::TypeConstraint, actual); + } + + static void TypeConstraint(cdb_hr::Result actual, std::wstring_view message) + { + Assert::AreEqual(cdb_hr::Result::TypeConstraint, actual, message.data()); + } + }; +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/StringTokenizerUnitTests.cpp b/src/Serialization/HybridRow.Native.Tests.Unit/StringTokenizerUnitTests.cpp new file mode 100644 index 0000000..5be9dfe --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/StringTokenizerUnitTests.cpp @@ -0,0 +1,43 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" +#include "CppUnitTestFramework.inl" + +namespace cdb_hr_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + TEST_CLASS(StringTokenizerUnitTests) + { + public: + + TEST_METHOD_WITH_OWNER(RoundTripTest, "jthunter") + { + cdb_hr::StringTokenizer tokenizer{}; + + const auto& fooToken = tokenizer.Add("foo"sv); + Assert::IsFalse(fooToken.IsNull()); + Assert::AreEqual("foo"sv, fooToken.GetPath()); + Assert::AreNotEqual(static_cast(0), fooToken.GetVarint().Length()); + const auto& [fooSuccess, fooString] = tokenizer.TryFindString(fooToken.GetId()); + Assert::IsTrue(fooSuccess); + Assert::AreEqual("foo"sv, fooString); + + const auto& barToken = tokenizer.Add("bar"sv); + Assert::IsFalse(barToken.IsNull()); + Assert::AreEqual("bar"sv, barToken.GetPath()); + Assert::AreNotEqual(static_cast(0), barToken.GetVarint().Length()); + const auto& [barSuccess, barString] = tokenizer.TryFindString(barToken.GetId()); + Assert::IsTrue(barSuccess); + Assert::AreEqual("bar"sv, barString); + + Assert::AreNotEqual(barToken.GetId(), fooToken.GetId()); + Assert::AreNotEqual(barToken.GetPath(), fooToken.GetPath()); + Assert::AreNotEqual(barToken.GetVarint(), fooToken.GetVarint()); + } + }; +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/SystemSchemaUnitTests.cpp b/src/Serialization/HybridRow.Native.Tests.Unit/SystemSchemaUnitTests.cpp new file mode 100644 index 0000000..3c9ce7d --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/SystemSchemaUnitTests.cpp @@ -0,0 +1,345 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" +#include "CppUnitTestFramework.inl" +#include "ResultAssert.h" + +// ReSharper disable CppClangTidyCppcoreguidelinesProTypeStaticCastDowncast +namespace cdb_hr_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + TEST_CLASS(SystemSchemaUnitTests) + { + public: + + TEST_METHOD_WITH_OWNER(LoadGeneratedHrSchema, "jthunter") + { + const cdb_hr::Layout& layout = cdb_hr::SchemasHrSchema::GetLayoutResolver().Resolve(cdb_hr::SchemaOptionsHybridRowSerializer::Id); + Assert::AreEqual(cdb_hr::SchemaOptionsHybridRowSerializer::Id, layout.GetSchemaId()); + } + + TEST_METHOD_WITH_OWNER(LoadTest, "jthunter") + { + const cdb_hr::LayoutResolver& resolver = cdb_hr::SchemasHrSchema::GetLayoutResolver(); + + std::array systemSchemas{ + cdb_hr::SystemSchemaLiteral::EmptySchemaId, + cdb_hr::SegmentHybridRowSerializer::Id, + cdb_hr::RecordHybridRowSerializer::Id, + cdb_hr::NamespaceHybridRowSerializer::Id, + cdb_hr::SchemaHybridRowSerializer::Id, + cdb_hr::SchemaOptionsHybridRowSerializer::Id, + cdb_hr::PartitionKeyHybridRowSerializer::Id, + cdb_hr::PrimarySortKeyHybridRowSerializer::Id, + cdb_hr::StaticKeyHybridRowSerializer::Id, + cdb_hr::PropertyHybridRowSerializer::Id, + cdb_hr::PropertyTypeHybridRowSerializer::Id, + cdb_hr::PrimitivePropertyTypeHybridRowSerializer::Id, + cdb_hr::ScopePropertyTypeHybridRowSerializer::Id, + cdb_hr::ArrayPropertyTypeHybridRowSerializer::Id, + cdb_hr::ObjectPropertyTypeHybridRowSerializer::Id, + cdb_hr::SetPropertyTypeHybridRowSerializer::Id, + cdb_hr::MapPropertyTypeHybridRowSerializer::Id, + cdb_hr::TuplePropertyTypeHybridRowSerializer::Id, + cdb_hr::TaggedPropertyTypeHybridRowSerializer::Id, + }; + + // Make sure all system schemas are loadable. + for (cdb_hr::SchemaId id : systemSchemas) + { + const cdb_hr::Layout& l = resolver.Resolve(id); + Assert::AreEqual(id, l.GetSchemaId()); + } + + // Make sure all system schema ids are unique. + for (cdb_hr::SchemaId id : systemSchemas) + { + int count = 0; + for (cdb_hr::SchemaId other : systemSchemas) + { + if (other == id) + { + count++; + } + } + + Assert::AreEqual(1, count); + } + } + + static void AssertEqual(const cdb_hr::PartitionKey& i1, const cdb_hr::PartitionKey& i2) + { + Assert::AreEqual(i1.GetPath(), i2.GetPath()); + } + + static void AssertEqual(const cdb_hr::PrimarySortKey& i1, const cdb_hr::PrimarySortKey& i2) + { + Assert::AreEqual(i1.GetPath(), i2.GetPath()); + Assert::AreEqual(i1.GetDirection(), i2.GetDirection()); + } + + static void AssertEqual(const cdb_hr::StaticKey& i1, const cdb_hr::StaticKey& i2) + { + Assert::AreEqual(i1.GetPath(), i2.GetPath()); + } + + static void AssertEqual(const cdb_hr::Property& i1, const cdb_hr::Property& i2) + { + Assert::AreEqual(i1.GetComment(), i2.GetComment()); + Assert::AreEqual(i1.GetPath(), i2.GetPath()); + Assert::AreEqual(i1.GetApiName(), i2.GetApiName()); + Assert::AreEqual(i1.GetAllowEmpty(), i2.GetAllowEmpty()); + if (i1.GetPropertyType().has_value()) + { + Assert::IsTrue(i2.GetPropertyType().has_value()); + AssertEqual(i1.GetPropertyType().value(), i2.GetPropertyType().value()); + } + else + { + Assert::IsFalse(i2.GetPropertyType().has_value()); + } + } + + static void AssertEqual(const cdb_hr::PropertyType& i1, const cdb_hr::PropertyType& i2) + { + Assert::AreEqual(i1.GetApiType(), i2.GetApiType()); + Assert::AreEqual(i1.GetType(), i2.GetType()); + Assert::AreEqual(i1.GetNullable(), i2.GetNullable()); + + Assert::AreEqual(i1.GetRuntimeSchemaId(), i2.GetRuntimeSchemaId()); + switch (i1.GetRuntimeSchemaId().Id()) + { + case cdb_hr::PrimitivePropertyTypeHybridRowSerializer::Id.Id(): + { + AssertEqual(static_cast(i1), static_cast(i2)); + return; + } + + case cdb_hr::ArrayPropertyTypeHybridRowSerializer::Id.Id(): + case cdb_hr::ObjectPropertyTypeHybridRowSerializer::Id.Id(): + case cdb_hr::UdtPropertyTypeHybridRowSerializer::Id.Id(): + case cdb_hr::SetPropertyTypeHybridRowSerializer::Id.Id(): + case cdb_hr::MapPropertyTypeHybridRowSerializer::Id.Id(): + case cdb_hr::TuplePropertyTypeHybridRowSerializer::Id.Id(): + case cdb_hr::TaggedPropertyTypeHybridRowSerializer::Id.Id(): + case cdb_hr::ScopePropertyTypeHybridRowSerializer::Id.Id(): + { + AssertEqual(static_cast(i1), static_cast(i2)); + return; + } + + default: + Assert::Fail(L"Type is abstract."); + } + } + + static void AssertEqual(const cdb_hr::PrimitivePropertyType& i1, const cdb_hr::PrimitivePropertyType& i2) + { + Assert::AreEqual(i1.GetLength(), i2.GetLength()); + Assert::AreEqual(i1.GetStorage(), i2.GetStorage()); + } + + static void AssertEqual(const cdb_hr::ScopePropertyType& i1, const cdb_hr::ScopePropertyType& i2) + { + Assert::AreEqual(i1.GetImmutable(), i2.GetImmutable()); + + switch (i1.GetRuntimeSchemaId().Id()) + { + case cdb_hr::ArrayPropertyTypeHybridRowSerializer::Id.Id(): + { + const cdb_hr::ArrayPropertyType& p = static_cast(i1); + const cdb_hr::ArrayPropertyType& q = static_cast(i2); + Assert::AreEqual(p.GetItems().has_value(), q.GetItems().has_value()); + AssertEqual(p.GetItems().value(), q.GetItems().value()); + break; + } + case cdb_hr::ObjectPropertyTypeHybridRowSerializer::Id.Id(): + { + const cdb_hr::ObjectPropertyType& p = static_cast(i1); + const cdb_hr::ObjectPropertyType& q = static_cast(i2); + Assert::AreEqual(p.GetProperties().size(), q.GetProperties().size()); + for (size_t i = 0; i < p.GetProperties().size(); i++) + { + AssertEqual(*p.GetProperties()[i], *q.GetProperties()[i]); + } + + break; + } + case cdb_hr::SetPropertyTypeHybridRowSerializer::Id.Id(): + { + const cdb_hr::SetPropertyType& p = static_cast(i1); + const cdb_hr::SetPropertyType& q = static_cast(i2); + Assert::AreEqual(p.GetItems().has_value(), q.GetItems().has_value()); + AssertEqual(p.GetItems().value(), q.GetItems().value()); + break; + } + case cdb_hr::MapPropertyTypeHybridRowSerializer::Id.Id(): + { + const cdb_hr::MapPropertyType& p = static_cast(i1); + const cdb_hr::MapPropertyType& q = static_cast(i2); + Assert::AreEqual(p.GetKeys().has_value(), q.GetKeys().has_value()); + Assert::AreEqual(p.GetValues().has_value(), q.GetValues().has_value()); + AssertEqual(p.GetKeys().value(), q.GetKeys().value()); + AssertEqual(p.GetValues().value(), q.GetValues().value()); + break; + } + case cdb_hr::TuplePropertyTypeHybridRowSerializer::Id.Id(): + { + const cdb_hr::TuplePropertyType& p = static_cast(i1); + const cdb_hr::TuplePropertyType& q = static_cast(i2); + Assert::AreEqual(p.GetItems().size(), q.GetItems().size()); + for (size_t i = 0; i < p.GetItems().size(); i++) + { + AssertEqual(*p.GetItems()[i], *q.GetItems()[i]); + } + + break; + } + case cdb_hr::TaggedPropertyTypeHybridRowSerializer::Id.Id(): + { + const cdb_hr::TaggedPropertyType& p = static_cast(i1); + const cdb_hr::TaggedPropertyType& q = static_cast(i2); + Assert::AreEqual(p.GetItems().size(), q.GetItems().size()); + for (size_t i = 0; i < p.GetItems().size(); i++) + { + AssertEqual(*p.GetItems()[i], *q.GetItems()[i]); + } + + break; + } + case cdb_hr::UdtPropertyTypeHybridRowSerializer::Id.Id(): + { + const cdb_hr::UdtPropertyType& p = static_cast(i1); + const cdb_hr::UdtPropertyType& q = static_cast(i2); + Assert::AreEqual(p.GetName(), q.GetName()); + Assert::AreEqual(p.GetSchemaId(), q.GetSchemaId()); + break; + } + default: + Assert::Fail(L"Type is abstract."); + } + } + + static void AssertEqual(const cdb_hr::EnumSchema& s1, const cdb_hr::EnumSchema& s2) + { + Assert::AreEqual(s1.GetType(), s2.GetType()); + Assert::AreEqual(s1.GetApiType(), s2.GetApiType()); + Assert::AreEqual(s1.GetName(), s2.GetName()); + Assert::AreEqual(s1.GetComment(), s2.GetComment()); + + Assert::AreEqual(s1.GetValues().size(), s2.GetValues().size()); + for (size_t i = 0; i < s1.GetValues().size(); i++) + { + AssertEqual(*s1.GetValues()[i], *s2.GetValues()[i]); + } + } + + static void AssertEqual(const cdb_hr::EnumValue& s1, const cdb_hr::EnumValue& s2) + { + Assert::AreEqual(s1.GetValue(), s2.GetValue()); + Assert::AreEqual(s1.GetName(), s2.GetName()); + Assert::AreEqual(s1.GetComment(), s2.GetComment()); + } + + static void AssertEqual(const cdb_hr::SchemaOptions& s1, const cdb_hr::SchemaOptions& s2) + { + Assert::AreEqual(s1.GetDisallowUnschematized(), s2.GetDisallowUnschematized()); + Assert::AreEqual(s1.GetEnablePropertyLevelTimestamp(), s2.GetEnablePropertyLevelTimestamp()); + Assert::AreEqual(s1.GetDisableSystemPrefix(), s2.GetDisableSystemPrefix()); + Assert::AreEqual(s1.GetAbstract(), s2.GetAbstract()); + } + + static void AssertEqual(const cdb_hr::Schema& s1, const cdb_hr::Schema& s2) + { + Assert::AreEqual(s1.GetVersion(), s2.GetVersion()); + Assert::AreEqual(s1.GetType(), s2.GetType()); + Assert::AreEqual(s1.GetSchemaId(), s2.GetSchemaId()); + Assert::AreEqual(s1.GetName(), s2.GetName()); + Assert::AreEqual(s1.GetComment(), s2.GetComment()); + + if (!s1.GetOptions().has_value()) + { + Assert::IsFalse(s2.GetOptions().has_value()); + } + else + { + Assert::IsTrue(s2.GetOptions().has_value()); + AssertEqual(s1.GetOptions().value(), s2.GetOptions().value()); + } + + Assert::AreEqual(s1.GetPartitionKeys().size(), s2.GetPartitionKeys().size()); + for (size_t i = 0; i < s1.GetPartitionKeys().size(); i++) + { + AssertEqual(*s1.GetPartitionKeys()[i], *s2.GetPartitionKeys()[i]); + } + + Assert::AreEqual(s1.GetPrimaryKeys().size(), s2.GetPrimaryKeys().size()); + for (size_t i = 0; i < s1.GetPrimaryKeys().size(); i++) + { + AssertEqual(*s1.GetPrimaryKeys()[i], *s2.GetPrimaryKeys()[i]); + } + + Assert::AreEqual(s1.GetStaticKeys().size(), s2.GetStaticKeys().size()); + for (size_t i = 0; i < s1.GetStaticKeys().size(); i++) + { + AssertEqual(*s1.GetStaticKeys()[i], *s2.GetStaticKeys()[i]); + } + + Assert::AreEqual(s1.GetProperties().size(), s2.GetProperties().size()); + for (size_t i = 0; i < s1.GetProperties().size(); i++) + { + AssertEqual(*s1.GetProperties()[i], *s2.GetProperties()[i]); + } + } + + static void SerializerRoundtripNamespace(const cdb_hr::Namespace& ns1) + { + const cdb_hr::LayoutResolver& resolver = cdb_hr::SystemSchemaLiteral::GetLayoutResolver(); + const cdb_hr::Layout& layout = resolver.Resolve(cdb_hr::NamespaceHybridRowSerializer::Id); + + cdb_hr::MemorySpanResizer resizer{0}; + cdb_hr::RowBuffer row{0, &resizer}; + row.InitLayout(cdb_hr::HybridRowVersion::V1, layout, &resolver); + + // Write the whole namespace to a row. + cdb_hr::Result r = ns1.Write(row); + ResultAssert::IsSuccess(r); + + // Read the namespace back. + std::unique_ptr pns2; + std::tie(r, pns2) = cdb_hr::Namespace::Read(row); + ResultAssert::IsSuccess(r); + const cdb_hr::Namespace& ns2 = *pns2; + + // Compare the materialized row with the original in-memory object model. + Assert::AreEqual(ns1.GetVersion(), ns2.GetVersion()); + Assert::AreEqual(ns1.GetName(), ns2.GetName()); + Assert::AreEqual(ns1.GetComment(), ns2.GetComment()); + Assert::AreEqual(ns1.GetSchemas().size(), ns2.GetSchemas().size()); + for (size_t i = 0; i < ns1.GetSchemas().size(); i++) + { + const cdb_hr::Schema& s1 = *ns1.GetSchemas()[i]; + const cdb_hr::Schema& s2 = *ns2.GetSchemas()[i]; + AssertEqual(s1, s2); + } + Assert::AreEqual(ns1.GetEnums().size(), ns2.GetEnums().size()); + for (size_t i = 0; i < ns1.GetEnums().size(); i++) + { + const cdb_hr::EnumSchema& s1 = *ns1.GetEnums()[i]; + const cdb_hr::EnumSchema& s2 = *ns2.GetEnums()[i]; + AssertEqual(s1, s2); + } + } + + TEST_METHOD_WITH_OWNER(SerializeSystemNamespaceTest, "jthunter") + { + const cdb_hr::Namespace& ns1 = cdb_hr::SystemSchemaLiteral::GetNamespace(); + SystemSchemaUnitTests::SerializerRoundtripNamespace(ns1); + } + }; +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/TaggedSchema.generated.cpp b/src/Serialization/HybridRow.Native.Tests.Unit/TaggedSchema.generated.cpp new file mode 100644 index 0000000..6c1b003 --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/TaggedSchema.generated.cpp @@ -0,0 +1,546 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "TaggedSchema.generated.h" + +// ------------------------------------------------------------ +// This file was generated by: +// Microsoft.Azure.Cosmos.Serialization.HybridRowCLI: 1.0.0.0 +// +// This file should not be modified directly. +// ------------------------------------------------------------ + +// ReSharper disable CppClangTidyCppcoreguidelinesProTypeStaticCastDowncast +// ReSharper disable CppClangTidyPerformanceMoveConstArg +// ReSharper disable CppRedundantControlFlowJump +// ReSharper disable CppClangTidyClangDiagnosticExitTimeDestructors +namespace cdb_hr_test::typed_array +{ + using namespace std::literals; + + class TypedArrayHrSchema::Literal final + { + friend struct TypedArrayHrSchema; + + + static const cdb_hr::Namespace& GetNamespace() noexcept + { + return *s_namespace; + } + + static std::unique_ptr LoadSchema() + { + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.SetName("Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedArray"); + n.SetVersion(cdb_hr::SchemaLanguageVersion::V2); + n.SetCppNamespace("cdb_hr_test::typed_array"); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("Tagged"); + s.SetSchemaId(cdb_hr::SchemaId{1}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("title"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("tags"); + p.SetPropertyType( + std::make_unique(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetNullable(false); + }))); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("options"); + p.SetPropertyType( + std::make_unique(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int32); + }))); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("ratings"); + p.SetPropertyType( + std::make_unique( + std::make_unique(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Float64); + pt.SetNullable(false); + }), false))); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("similars"); + p.SetPropertyType( + std::make_unique( + std::make_unique("SimilarMatch", cdb_hr::SchemaId{0}, false))); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("priority"); + p.SetPropertyType( + std::make_unique( + std::make_unique( + cdb_hr::IHybridRowSerializer::make_unique_vector( + cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetNullable(false); + }), + cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int64); + pt.SetNullable(false); + }) + ), false))); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("SimilarMatch"); + s.SetSchemaId(cdb_hr::SchemaId{2}); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("thumbprint"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetLength(18); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("score"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Float64); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + })); + })); + })); + }); + + s_namespace = ns.get(); + return std::make_unique(std::move(ns)); + } + + inline static cdb_hr::Namespace* s_namespace{nullptr}; + inline static std::unique_ptr s_layoutResolver{LoadSchema()}; + }; + + const cdb_hr::Namespace& TypedArrayHrSchema::GetNamespace() noexcept + { + return *Literal::s_namespace; + } + + const cdb_hr::LayoutResolver& TypedArrayHrSchema::GetLayoutResolver() noexcept + { + return *Literal::s_layoutResolver; + } + + class TaggedHybridRowSerializer::Literal final + { + friend struct TaggedHybridRowSerializer; + + constexpr static std::string_view TitleName{"title"sv}; + constexpr static std::string_view TagsName{"tags"sv}; + constexpr static std::string_view OptionsName{"options"sv}; + constexpr static std::string_view RatingsName{"ratings"sv}; + constexpr static std::string_view SimilarsName{"similars"sv}; + constexpr static std::string_view PriorityName{"priority"sv}; + + inline static const cdb_hr::Layout& Layout{TypedArrayHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& TitleColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, TitleName)}; + inline static const cdb_hr::LayoutColumn& TagsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, TagsName)}; + inline static const cdb_hr::LayoutColumn& OptionsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, OptionsName)}; + inline static const cdb_hr::LayoutColumn& RatingsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, RatingsName)}; + inline static const cdb_hr::LayoutColumn& SimilarsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, SimilarsName)}; + inline static const cdb_hr::LayoutColumn& PriorityColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PriorityName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& TagsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, TagsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& OptionsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, OptionsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& RatingsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, RatingsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& SimilarsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, SimilarsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& PriorityToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, PriorityColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Tagged& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Tagged& value); + }; + + cdb_hr::Result TaggedHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const Tagged& value) noexcept + { + if (isRoot) + { + return TaggedHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = TaggedHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result TaggedHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Tagged& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetTitle())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, TitleColumn, cdb_hr::IHybridRowSerializer::get(value.GetTitle())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetTags())) + { + scope.Find(row, TagsColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + TagsColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetTags())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetOptions())) + { + scope.Find(row, OptionsColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer, cdb_hr::NullableHybridRowSerializer, int32_t, cdb_hr::Int32HybridRowSerializer>>::Write( + row, + scope, + false, + OptionsColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetOptions())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetRatings())) + { + scope.Find(row, RatingsColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer, cdb_hr::TypedArrayHybridRowSerializer>::Write( + row, + scope, + false, + RatingsColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetRatings())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetSimilars())) + { + scope.Find(row, SimilarsColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + SimilarsColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetSimilars())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetPriority())) + { + scope.Find(row, PriorityColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer, cdb_hr::TypedTupleHybridRowSerializer>::Write( + row, + scope, + false, + PriorityColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetPriority())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> TaggedHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = TaggedHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = TaggedHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result TaggedHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Tagged& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, TitleColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetTitle(std::string(fieldValue)); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == TagsToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetTags(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == OptionsToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer, cdb_hr::NullableHybridRowSerializer, int32_t, cdb_hr::Int32HybridRowSerializer>>::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetOptions(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == RatingsToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer, cdb_hr::TypedArrayHybridRowSerializer>::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetRatings(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == SimilarsToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetSimilars(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == PriorityToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer, cdb_hr::TypedTupleHybridRowSerializer>::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetPriority(std::move(fieldValue)); + continue; + } + } + + return cdb_hr::Result::Success; + } + + class SimilarMatchHybridRowSerializer::Literal final + { + friend struct SimilarMatchHybridRowSerializer; + + constexpr static std::string_view ThumbprintName{"thumbprint"sv}; + constexpr static std::string_view ScoreName{"score"sv}; + + inline static const cdb_hr::Layout& Layout{TypedArrayHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& ThumbprintColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ThumbprintName)}; + inline static const cdb_hr::LayoutColumn& ScoreColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ScoreName)}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const SimilarMatch& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, SimilarMatch& value); + }; + + cdb_hr::Result SimilarMatchHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const SimilarMatch& value) noexcept + { + if (isRoot) + { + return SimilarMatchHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = SimilarMatchHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result SimilarMatchHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const SimilarMatch& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetThumbprint())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteFixed( + row, scope, ThumbprintColumn, cdb_hr::IHybridRowSerializer::get(value.GetThumbprint())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetScore())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Float64.WriteFixed( + row, scope, ScoreColumn, cdb_hr::IHybridRowSerializer::get(value.GetScore())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> SimilarMatchHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = SimilarMatchHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = SimilarMatchHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result SimilarMatchHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, SimilarMatch& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadFixed(row, scope, ThumbprintColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetThumbprint(std::string(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Float64.ReadFixed(row, scope, ScoreColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetScore(fieldValue); + break; + default: + return r; + } + } + + return cdb_hr::Result::Success; + } +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/TaggedSchema.generated.h b/src/Serialization/HybridRow.Native.Tests.Unit/TaggedSchema.generated.h new file mode 100644 index 0000000..b38c84f --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/TaggedSchema.generated.h @@ -0,0 +1,97 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +// ------------------------------------------------------------ +// This file was generated by: +// Microsoft.Azure.Cosmos.Serialization.HybridRowCLI: 1.0.0.0 +// +// This file should not be modified directly. +// ------------------------------------------------------------ + +namespace cdb_hr_test::typed_array +{ + class Tagged; + class SimilarMatch; + + struct TypedArrayHrSchema final + { + static const cdb_hr::Namespace& GetNamespace() noexcept; + static const cdb_hr::LayoutResolver& GetLayoutResolver() noexcept; + + private: + class Literal; + }; + + class Tagged final + { + public: + [[nodiscard]] const std::string& GetTitle() const noexcept { return m_title; } + void SetTitle(std::string value) noexcept { m_title = std::move(value);} + [[nodiscard]] const std::vector& GetTags() const noexcept { return m_tags; } + void SetTags(std::vector value) noexcept { m_tags = std::move(value);} + [[nodiscard]] const std::vector>& GetOptions() const noexcept { return m_options; } + void SetOptions(std::vector> value) noexcept { m_options = std::move(value);} + [[nodiscard]] const std::vector>& GetRatings() const noexcept { return m_ratings; } + void SetRatings(std::vector> value) noexcept { m_ratings = std::move(value);} + [[nodiscard]] const std::vector>& GetSimilars() const noexcept { return m_similars; } + void SetSimilars(std::vector> value) noexcept { m_similars = std::move(value);} + [[nodiscard]] const std::vector>& GetPriority() const noexcept { return m_priority; } + void SetPriority(std::vector> value) noexcept { m_priority = std::move(value);} + private: + std::string m_title{}; + std::vector m_tags{}; + std::vector> m_options{}; + std::vector> m_ratings{}; + std::vector> m_similars{}; + std::vector> m_priority{}; + }; + + class SimilarMatch final + { + public: + [[nodiscard]] const std::string& GetThumbprint() const noexcept { return m_thumbprint; } + void SetThumbprint(std::string value) noexcept { m_thumbprint = std::move(value);} + [[nodiscard]] float64_t GetScore() const noexcept { return m_score; } + void SetScore(float64_t value) noexcept { m_score = std::move(value);} + private: + std::string m_thumbprint{}; + float64_t m_score{}; + }; + + struct TaggedHybridRowSerializer final + { + using value_type = Tagged; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{1}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const Tagged& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct SimilarMatchHybridRowSerializer final + { + using value_type = SimilarMatch; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const SimilarMatch& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/TypedArrayUnitTests.cpp b/src/Serialization/HybridRow.Native.Tests.Unit/TypedArrayUnitTests.cpp new file mode 100644 index 0000000..2b88f8a --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/TypedArrayUnitTests.cpp @@ -0,0 +1,134 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "CppUnitTest.h" +#include "CppUnitTestFramework.inl" +#include "TaggedSchema.generated.h" +#include "ResultAssert.h" + +namespace cdb_hr_test +{ + using namespace std::literals; + using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert; + + TEST_CLASS(TypedArrayUnitTests) + { + public: + TypedArrayUnitTests() : + m_resolver{typed_array::TypedArrayHrSchema::GetLayoutResolver()}, + m_layout{m_resolver.Resolve(typed_array::TaggedHybridRowSerializer::Id)} { } + + TEST_METHOD_WITH_OWNER(CreateTags, "jthunter") + { + cdb_hr::MemorySpanResizer resizer{InitialRowSize}; + cdb_hr::RowBuffer row{InitialRowSize, &resizer}; + row.InitLayout(cdb_hr::HybridRowVersion::V1, m_layout, &m_resolver); + + std::unique_ptr t1 = cdb_core::make_unique_with([&](typed_array::Tagged& o) + { + o.SetTitle("Thriller"); + o.SetTags(std::vector{"classic", "Post-disco", "funk"}); + o.SetOptions(std::vector>{8, {}, 9}); + o.SetRatings(std::vector> + { + std::vector{1.2, 3.0}, + std::vector{4.1, 5.7}, + std::vector{7.3, 8.12, 9.14}, + }); + o.SetSimilars( + cdb_core::make_with([](std::vector>& v) + { + // ReSharper disable three StringLiteralTypo + v.emplace_back(cdb_core::make_unique_with([&](typed_array::SimilarMatch& m) + { + m.SetThumbprint("TRABACN128F425B784"); + m.SetScore(0.87173699999999998); + })); + v.emplace_back(cdb_core::make_unique_with([&](typed_array::SimilarMatch& m) + { + m.SetThumbprint("TRJYGLF12903CB4952"); + m.SetScore(0.75105200000000005); + })); + v.emplace_back(cdb_core::make_unique_with([&](typed_array::SimilarMatch& m) + { + m.SetThumbprint("TRWJMMB128F429D550"); + m.SetScore(0.50866100000000003); + })); + })); + o.SetPriority(std::vector> + { + std::make_tuple("80's", 100L), + std::make_tuple("classics", 100L), + std::make_tuple("pop", 50L), + }); + }); + + cdb_hr::RowCursor scope = cdb_hr::RowCursor::Create(row); + ResultAssert::IsSuccess(typed_array::TaggedHybridRowSerializer::Write(row, scope, true, {}, *t1)); + scope = cdb_hr::RowCursor::Create(row); + auto [r, t2] = typed_array::TaggedHybridRowSerializer::Read(row, scope, true); + ResultAssert::IsSuccess(r); + + t1->SetSimilars( + cdb_core::make_with([](std::vector>& v) + { + // ReSharper disable three StringLiteralTypo + v.emplace_back(cdb_core::make_unique_with([&](typed_array::SimilarMatch& m) + { + m.SetThumbprint("TRABACN128F425B784"); + m.SetScore(0.87173699999999998); + })); + v.emplace_back(cdb_core::make_unique_with([&](typed_array::SimilarMatch& m) + { + m.SetThumbprint("TRJYGLF12903CB4952"); + m.SetScore(0.75105200000000005); + })); + v.emplace_back(cdb_core::make_unique_with([&](typed_array::SimilarMatch& m) + { + m.SetThumbprint("TRWJMMB128F429D550"); + m.SetScore(0.50866100000000003); + })); + })); + Assert::IsTrue(cdb_core::DeepCompare(t1, t2)); + } + + private: + constexpr static int InitialRowSize = 2 * 1024 * 1024; + const cdb_hr::LayoutResolver& m_resolver; + const cdb_hr::Layout& m_layout; + }; +} + +namespace cdb_core +{ + template<> + struct DeepComparer + { + using value_type = cdb_hr_test::typed_array::Tagged; + + bool operator()(const value_type& x, const value_type& y) const noexcept + { + return cdb_core::DeepCompare(x.GetTitle(), y.GetTitle()) && + cdb_core::DeepCompare(x.GetTags(), y.GetTags()) && + cdb_core::DeepCompare(x.GetOptions(), y.GetOptions()) && + cdb_core::DeepCompare(x.GetRatings(), y.GetRatings()) && + cdb_core::DeepCompare(x.GetSimilars(), y.GetSimilars()) && + cdb_core::DeepCompare(x.GetPriority(), y.GetPriority()); + } + }; + + template<> + struct DeepComparer + { + using value_type = cdb_hr_test::typed_array::SimilarMatch; + + bool operator()(const value_type& x, const value_type& y) const noexcept + { + return + cdb_core::DeepCompare(x.GetThumbprint(), y.GetThumbprint()) && + cdb_core::DeepCompare(x.GetScore(), y.GetScore()); + } + }; +} diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/cpp.hint b/src/Serialization/HybridRow.Native.Tests.Unit/cpp.hint new file mode 100644 index 0000000..a7cf4e7 --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/cpp.hint @@ -0,0 +1,4 @@ +// Hint files help the Visual Studio IDE interpret Visual C++ identifiers +// such as names of functions and macros. +// For more information see https://go.microsoft.com/fwlink/?linkid=865984 +#define TEST_METHOD_WITH_OWNER(methodName, ownerAlias) BEGIN_TEST_METHOD_ATTRIBUTE(methodName) TEST_OWNER(L##ownerAlias) END_TEST_METHOD_ATTRIBUTE() TEST_METHOD(methodName) diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/pch.cpp b/src/Serialization/HybridRow.Native.Tests.Unit/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/Serialization/HybridRow.Native.Tests.Unit/pch.h b/src/Serialization/HybridRow.Native.Tests.Unit/pch.h new file mode 100644 index 0000000..1c3a16c --- /dev/null +++ b/src/Serialization/HybridRow.Native.Tests.Unit/pch.h @@ -0,0 +1,7 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "../HybridRow.Native/HybridRow.Native.h" diff --git a/src/Serialization/HybridRow.Native/AllowEmptyKind.h b/src/Serialization/HybridRow.Native/AllowEmptyKind.h new file mode 100644 index 0000000..ec683ac --- /dev/null +++ b/src/Serialization/HybridRow.Native/AllowEmptyKind.h @@ -0,0 +1,27 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// Describes the empty canonicalization for properties. + enum class AllowEmptyKind : unsigned char + { + /// Empty and null are treated as distinct. + None = 0, + + /// Empty values are converted to null when written. + EmptyAsNull = 1, + + /// Null values are converted to empty when read. + NullAsEmpty = 2, + + /// + /// Empty values are converted to null when written, and null values are converted to empty + /// when read. + /// + Both = EmptyAsNull | NullAsEmpty, + }; +} diff --git a/src/Serialization/HybridRow.Native/ArrayHybridRowSerializer.h b/src/Serialization/HybridRow.Native/ArrayHybridRowSerializer.h new file mode 100644 index 0000000..5d5d954 --- /dev/null +++ b/src/Serialization/HybridRow.Native/ArrayHybridRowSerializer.h @@ -0,0 +1,105 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "IHybridRowSerializer.h" +#include "LayoutType.h" +#include "RowCursor.h" + +namespace cdb_hr +{ + template>> + struct ArrayHybridRowSerializer final + { + struct ArrayComparer; + using value_type = std::vector; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = ArrayComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const_reference value) noexcept + { + auto [r, childScope] = LayoutLiteral::Array.WriteScope(row, scope, TypeArgumentList{}); + if (r != Result::Success) + { + return r; + } + + for (auto& item : value) + { + r = TSerializer::Write(row, childScope, false, typeArgs[0].GetTypeArgs(), IHybridRowSerializer::get(item)); + if (r != Result::Success) + { + return r; + } + + childScope.MoveNext(row); + } + + scope.Skip(row, childScope); + return Result::Success; + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + auto [r, childScope] = LayoutLiteral::Array.ReadScope(row, scope); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + value_type items{}; + while (childScope.MoveNext(row)) + { + auto [r, item] = TSerializer::Read(row, childScope, isRoot); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + items.emplace_back(std::move(item)); + } + + scope.Skip(row, childScope); + return {Result::Success, std::move(items)}; + } + + struct ArrayComparer final + { + constexpr bool operator()(const_reference x, const_reference y) const + { + if (&x == &y) + { + return true; + } + if (x.size() != y.size()) + { + return false; + } + + for (size_t i = 0; i < x.size(); i++) + { + if (!typename TSerializer::comparer_type{}.operator()(x[i], y[i])) + { + return false; + } + } + + return true; + } + + constexpr std::size_t operator()(const_reference s) const noexcept + { + cdb_core::HashCode hash{}; + for (size_t i = 0; i < s.size(); i++) + { + hash.Add(s[i]); + } + return hash.ToHashCode(); + } + }; + }; +} diff --git a/src/Serialization/HybridRow.Native/ArrayPropertyType.h b/src/Serialization/HybridRow.Native/ArrayPropertyType.h new file mode 100644 index 0000000..387a740 --- /dev/null +++ b/src/Serialization/HybridRow.Native/ArrayPropertyType.h @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include "ScopePropertyType.h" + +namespace cdb_hr +{ + /// Array properties represent an unbounded set of zero or more items. + /// + /// Arrays may be typed or untyped. Within typed arrays, all items MUST be the same type. The + /// type of items is specified via . Typed arrays may be stored more efficiently + /// than untyped arrays. When is unspecified, the array is untyped and its items + /// may be heterogeneous. + /// + class ArrayPropertyType final : public ScopePropertyType + { + public: + ArrayPropertyType() noexcept : ScopePropertyType{TypeKind::Array}, m_items{} {} + + explicit ArrayPropertyType(std::unique_ptr items, bool nullable = true, bool immutable = false) noexcept : + ScopePropertyType{TypeKind::Array, nullable, immutable}, + m_items{std::move(items)} {} + + ~ArrayPropertyType() noexcept override = default; + ArrayPropertyType(ArrayPropertyType&) = delete; + ArrayPropertyType(ArrayPropertyType&&) = delete; + ArrayPropertyType& operator=(const ArrayPropertyType&) = delete; + ArrayPropertyType& operator=(ArrayPropertyType&&) = delete; + + [[nodiscard]] SchemaId GetRuntimeSchemaId() const noexcept override { return SchemaId{2147473661}; } + [[nodiscard]] PropertyKind GetKind() const noexcept override { return PropertyKind::Array; } + + /// (Optional) type of the elements of the array, if a typed array, otherwise null. + [[nodiscard]] std::optional> GetItems() const noexcept + { + return m_items ? std::optional>{*m_items} : std::nullopt; + } + + void SetItems(std::unique_ptr value) noexcept { m_items = std::move(value); } + + private: + std::unique_ptr m_items; + }; +} diff --git a/src/Serialization/HybridRow.Native/DateTime.h b/src/Serialization/HybridRow.Native/DateTime.h new file mode 100644 index 0000000..7ec7309 --- /dev/null +++ b/src/Serialization/HybridRow.Native/DateTime.h @@ -0,0 +1,55 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include + +namespace cdb_hr +{ + /// C# DateTime type. + /// CoreCLR DateTime definition: https://referencesource.microsoft.com/#mscorlib/system/datetime.cs + #pragma pack(push, 1) + struct DateTime final + { + constexpr DateTime() noexcept : m_data(0) { } + ~DateTime() noexcept = default; + constexpr DateTime(int64_t ticks) noexcept : m_data(static_cast(ticks)) { } + DateTime(const DateTime& other) = default; + DateTime(DateTime&& other) noexcept = default; + DateTime& operator=(const DateTime& other) = default; + DateTime& operator=(DateTime&& other) noexcept = default; + + [[nodiscard]] int64_t Ticks() const noexcept { return static_cast(m_data); } + + friend bool operator==(const DateTime& lhs, const DateTime& rhs) { return lhs.m_data == rhs.m_data; } + friend bool operator!=(const DateTime& lhs, const DateTime& rhs) { return !(lhs == rhs); } + + private: + friend std::hash; + + // The data is stored as an unsigned 64-bit integer + // Bits 01-62: The value of 100-nanosecond ticks where 0 represents 1/1/0001 12:00am, up until the value + // 12/31/9999 23:59:59.9999999 + // Bits 63-64: A four-state value that describes the DateTimeKind value of the date time, with a 2nd + // value for the rare case where the date time is local, but is in an overlapped daylight + // savings time hour and it is in daylight savings time. This allows distinction of these + // otherwise ambiguous local times and prevents data loss when round tripping from Local to + // UTC time. + uint64_t m_data; + }; + #pragma pack(pop) +} + +namespace std +{ + template<> + struct hash + { + constexpr std::size_t operator()(cdb_hr::DateTime const& s) const noexcept + { + return cdb_core::HashCode::Combine(s.m_data); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/Decimal.h b/src/Serialization/HybridRow.Native/Decimal.h new file mode 100644 index 0000000..1a3eca1 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Decimal.h @@ -0,0 +1,81 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include +#include + +namespace cdb_hr +{ + /// C# decimal type. + /// CoreCLR Decimal definition: https://referencesource.microsoft.com/#mscorlib/system/decimal.cs + #pragma pack(push, 1) + struct Decimal final + { + constexpr Decimal() noexcept : m_flags(0), m_hi(0), m_lo(0), m_mid(0) { } + ~Decimal() noexcept = default; + + constexpr Decimal(int32_t lo, int32_t mid, int32_t hi, bool isNegative, std::byte byteScale) noexcept : + m_flags((static_cast(byteScale) << 16) | (isNegative ? Decimal::SignMask : 0)), + m_hi(hi), + m_lo(lo), + m_mid(mid) { } + + constexpr Decimal(int64_t value) noexcept : + m_flags((value >= 0) ? 0 : SignMask), + m_hi(0), + m_lo(static_cast(value >= 0 ? value : -value)), + m_mid(static_cast((value >= 0 ? value : -value) >> 32)) { } + + Decimal(const Decimal& other) = default; + Decimal(Decimal&& other) noexcept = default; + Decimal& operator=(const Decimal& other) = default; + Decimal& operator=(Decimal&& other) noexcept = default; + + friend bool operator==(const Decimal& lhs, const Decimal& rhs) + { + return lhs.m_flags == rhs.m_flags + && lhs.m_hi == rhs.m_hi + && lhs.m_lo == rhs.m_lo + && lhs.m_mid == rhs.m_mid; + } + + friend bool operator!=(const Decimal& lhs, const Decimal& rhs) { return !(lhs == rhs); } + + private: + friend std::hash; + + // The lo, mid, hi, and flags fields contain the representation of the + // Decimal value. The lo, mid, and hi fields contain the 96-bit integer + // part of the Decimal. Bits 0-15 (the lower word) of the flags field are + // unused and must be zero; bits 16-23 contain must contain a value between + // 0 and 28, indicating the power of 10 to divide the 96-bit integer part + // by to produce the Decimal value; bits 24-30 are unused and must be zero; + // and finally bit 31 indicates the sign of the Decimal value, 0 meaning + // positive and 1 meaning negative. + int32_t m_flags; + int32_t m_hi; + int32_t m_lo; + int32_t m_mid; + + // Sign mask for the flags field. A value of zero in this bit indicates a + // positive Decimal value, and a value of one in this bit indicates a + // negative Decimal value. + constexpr static int32_t SignMask = static_cast(0x80000000); + }; + #pragma pack(pop) +} + +namespace std +{ + template<> + struct hash + { + constexpr std::size_t operator()(cdb_hr::Decimal const& s) const noexcept + { + return cdb_core::HashCode::Combine(s.m_flags, s.m_hi, s.m_lo, s.m_mid); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/EnumSchema.h b/src/Serialization/HybridRow.Native/EnumSchema.h new file mode 100644 index 0000000..01652fe --- /dev/null +++ b/src/Serialization/HybridRow.Native/EnumSchema.h @@ -0,0 +1,65 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include +#include +#include "TypeKind.h" +#include "EnumValue.h" + +namespace cdb_hr +{ + /// An enum schema describes a set of constrained integer values. + class EnumSchema final + { + public: + /// Initializes a new instance of the class. + EnumSchema() noexcept : + m_comment{}, + m_name{}, + m_apitype{}, + m_type{TypeKind::Int32}, + m_values{} {} + + /// An (optional) comment describing the purpose of this enum. + /// Comments are for documentary purpose only and do not affect the enum at runtime. + [[nodiscard]] std::string_view GetComment() const noexcept { return m_comment; } + void SetComment(std::string_view value) noexcept { m_comment = value; } + + /// The name of the enum. + /// + /// The name of a enum MUST be unique within its namespace. + /// + /// Names must begin with an alpha-numeric character and can only contain alpha-numeric characters and + /// underscores. + /// + [[nodiscard]] std::string_view GetName() const noexcept { return m_name; } + void SetName(std::string_view value) noexcept { m_name = value; } + + /// Api-specific type annotations for the property. + [[nodiscard]] std::string_view GetApiType() const noexcept { return m_apitype; } + void SetApiType(std::string_view value) noexcept { m_apitype = value; } + + /// The logical base type of the enum. + /// This must be a primitive. + [[nodiscard]] TypeKind GetType() const noexcept { return m_type; } + void SetType(TypeKind value) noexcept { m_type = value; } + + /// A list of zero or more value definitions. + /// This field is never null. + [[nodiscard]] const std::vector>& GetValues() const noexcept { return m_values; } + std::vector>& GetValues() noexcept { return m_values; } + void SetValues(std::vector> value) noexcept { m_values = std::move(value); } + + private: + tla::string m_comment; + tla::string m_name; + tla::string m_apitype; + TypeKind m_type; + + /// A list of zero or more value definitions. + std::vector> m_values; + }; +} diff --git a/src/Serialization/HybridRow.Native/EnumValue.h b/src/Serialization/HybridRow.Native/EnumValue.h new file mode 100644 index 0000000..9e55870 --- /dev/null +++ b/src/Serialization/HybridRow.Native/EnumValue.h @@ -0,0 +1,41 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_hr +{ + /// An enum schema describes a set of constrained integer values. + class EnumValue final + { + public: + /// Initializes a new instance of the class. + EnumValue() noexcept : m_comment{}, m_name{}, m_value{} {} + + /// An (optional) comment describing the purpose of this value. + /// Comments are for documentary purpose only and do not affect the enum at runtime. + [[nodiscard]] std::string_view GetComment() const noexcept { return m_comment; } + void SetComment(std::string_view value) noexcept { m_comment = value; } + + /// The name of the enum value. + /// + /// The name of a value MUST be unique within its EnumSchema. + /// + /// Names must begin with an alpha-numeric character and can only contain alpha-numeric characters and + /// underscores. + /// + [[nodiscard]] std::string_view GetName() const noexcept { return m_name; } + void SetName(std::string_view value) noexcept { m_name = value; } + + /// The numerical value of the enum value. + [[nodiscard]] int64_t GetValue() const noexcept { return m_value; } + void SetValue(int64_t value) noexcept { m_value = value; } + + private: + tla::string m_comment; + tla::string m_name; + int64_t m_value; + }; +} diff --git a/src/Serialization/HybridRow.Native/Float128.h b/src/Serialization/HybridRow.Native/Float128.h new file mode 100644 index 0000000..899e503 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Float128.h @@ -0,0 +1,78 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include + +namespace cdb_hr +{ + /// An IEEE 128-bit floating point value. + /// + /// 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. + /// + /// + /// Source Link + /// + /// Wikipedia: + /// https://en.wikipedia.org/wiki/Decimal128_floating-point_format + /// + /// The spec: https://ieeexplore.ieee.org/document/4610935 + /// + /// Decimal Encodings: http://speleotrove.com/decimal/decbits.html + /// + /// + /// + #pragma pack(push, 1) + struct Float128 final + { + /// The size (in bytes) of a . + constexpr static int Size = sizeof(long) + sizeof(long); + + /// + /// The low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point, using the BID + /// encoding scheme. + /// + int64_t Low; + + /// + /// The high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point, using the BID + /// encoding scheme. + /// + int64_t High; + + /// Initializes a new instance of the struct. + /// the high-order 64 bits. + /// the low-order 64 bits. + constexpr Float128(long high, long low) noexcept : Low(low), High(high) { } + constexpr Float128() noexcept : Low(0), High(0) { } + ~Float128() = default; + Float128(const Float128& other) noexcept = default; + Float128(Float128&& other) noexcept = default; + Float128& operator=(const Float128& other) noexcept = default; + Float128& operator=(Float128&& other) noexcept = default; + + friend bool operator==(const Float128& lhs, const Float128& rhs) + { + return lhs.Low == rhs.Low + && lhs.High == rhs.High; + } + + friend bool operator!=(const Float128& lhs, const Float128& rhs) { return !(lhs == rhs); } + }; + #pragma pack(pop) +} + +namespace std +{ + template<> + struct hash + { + constexpr std::size_t operator()(cdb_hr::Float128 const& s) const noexcept + { + return cdb_core::HashCode::Combine(s.Low, s.High); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/Guid.cpp b/src/Serialization/HybridRow.Native/Guid.cpp new file mode 100644 index 0000000..a3670f1 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Guid.cpp @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "Guid.h" +#include "winerror.h" + +namespace rpc +{ + #include +} + +namespace cdb_hr +{ + Guid Guid::NewGuid() noexcept + { + Guid retval; + rpc::RPC_STATUS status = rpc::UuidCreate(&retval.m_data); + cdb_core::Contract::Requires(status == S_OK); + return retval; + } +} diff --git a/src/Serialization/HybridRow.Native/Guid.h b/src/Serialization/HybridRow.Native/Guid.h new file mode 100644 index 0000000..77600bd --- /dev/null +++ b/src/Serialization/HybridRow.Native/Guid.h @@ -0,0 +1,65 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +// ReSharper disable once CppUnusedIncludeDirective +#include "guiddef.h" + +namespace cdb_hr +{ + /// C# Guid type. + /// CoreCLR Guid definition: https://referencesource.microsoft.com/#mscorlib/system/guid.cs + #pragma pack(push, 1) + struct Guid final + { + constexpr static Guid Empty() noexcept { return Guid{}; } + static Guid NewGuid() noexcept; + + constexpr Guid() noexcept : m_data{0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}} { } + ~Guid() noexcept = default; + constexpr Guid(uint32_t a, uint16_t b, uint16_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g, uint8_t h, uint8_t i, + uint8_t j, uint8_t k) noexcept; + Guid(const Guid& other) = default; + Guid(Guid&& other) noexcept = default; + Guid& operator=(const Guid& other) = default; + Guid& operator=(Guid&& other) noexcept = default; + + friend bool operator==(const Guid& lhs, const Guid& rhs) noexcept; + friend bool operator!=(const Guid& lhs, const Guid& rhs) noexcept; + + private: + friend std::hash; + + GUID m_data; + }; + #pragma pack(pop) + + constexpr Guid::Guid(uint32_t a, uint16_t b, uint16_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g, uint8_t h, + uint8_t i, uint8_t j, uint8_t k) noexcept : + m_data{a, b, c, {d, e, f, g, h, i, j, k}} { } + + inline bool operator==(const Guid& lhs, const Guid& rhs) noexcept + { + return IsEqualGUID(lhs.m_data, rhs.m_data) != 0; + } + + inline bool operator!=(const Guid& lhs, const Guid& rhs) noexcept + { + return !(lhs == rhs); + } +} + +namespace std +{ + template<> + struct hash + { + constexpr std::size_t operator()(cdb_hr::Guid const& s) const noexcept + { + return cdb_core::HashCode::Combine( + s.m_data.Data1, s.m_data.Data2, s.m_data.Data3, s.m_data.Data4[2], s.m_data.Data4[7]); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/HybridRow.Native.h b/src/Serialization/HybridRow.Native/HybridRow.Native.h new file mode 100644 index 0000000..33211c8 --- /dev/null +++ b/src/Serialization/HybridRow.Native/HybridRow.Native.h @@ -0,0 +1,59 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "framework.h" + +// Scalar Types +#include "Result.h" +#include "MemorySpanResizer.h" + +// Layout Types +#include "StringTokenizer.h" +#include "LayoutType.h" +#include "LayoutType.inl" +#include "LayoutCompiler.h" +#include "LayoutResolver.h" +#include "LayoutResolverNamespace.h" + +// Schema Types +#include "StorageKind.h" +#include "TypeKind.h" +#include "SchemaLanguageVersion.h" +#include "Namespace.h" +#include "Schema.h" +#include "SchemaId.h" +#include "SchemaOptions.h" +#include "Property.h" +#include "PrimarySortKey.h" +#include "PartitionKey.h" +#include "SortDirection.h" +#include "StaticKey.h" +#include "PropertyType.h" +#include "UdtPropertyType.h" +#include "TuplePropertyType.h" +#include "TaggedPropertyType.h" +#include "SetPropertyType.h" +#include "ScopePropertyType.h" +#include "PrimitivePropertyType.h" +#include "ObjectPropertyType.h" +#include "MapPropertyType.h" +#include "ArrayPropertyType.h" +#include "Segment.h" + +// System Schema +#include "IHybridRowSerializer.h" +#include "SystemSchemaLiteral.h" +#include "ArrayHybridRowSerializer.h" +#include "TypedArrayHybridRowSerializer.h" +#include "TypedMapHybridRowSerializer.h" +#include "TypedTupleHybridRowSerializer.h" +#include "NullableHybridRowSerializer.h" +#include "PrimitiveHybridRowSerializer.h" +#include "SystemSchema.h" + +// RecordIO +#include "RecordIOFormatter.h" +#include "RecordIOParser.h" diff --git a/src/Serialization/HybridRow.Native/HybridRow.Native.natvis b/src/Serialization/HybridRow.Native/HybridRow.Native.natvis new file mode 100644 index 0000000..c0fe15a --- /dev/null +++ b/src/Serialization/HybridRow.Native/HybridRow.Native.natvis @@ -0,0 +1,6 @@ + + + + {{{m_id}}} + + \ No newline at end of file diff --git a/src/Serialization/HybridRow.Native/HybridRow.Native.vcxproj b/src/Serialization/HybridRow.Native/HybridRow.Native.vcxproj new file mode 100644 index 0000000..082639c --- /dev/null +++ b/src/Serialization/HybridRow.Native/HybridRow.Native.vcxproj @@ -0,0 +1,177 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {D02FD209-EFF3-4D3F-B98E-E1BF9A1DD894} + Win32Proj + HybridRowNative + 10.0 + + + + StaticLibrary + true + + + StaticLibrary + false + + + + true + + + false + + + + Use + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + pch.h + false + + + Windows + + + + + Use + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + pch.h + + + Windows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + {eaed7d41-3de6-4c41-a0e4-40d53ea3daba} + + + + + + + + + + \ No newline at end of file diff --git a/src/Serialization/HybridRow.Native/HybridRow.Native.vcxproj.filters b/src/Serialization/HybridRow.Native/HybridRow.Native.vcxproj.filters new file mode 100644 index 0000000..81bed67 --- /dev/null +++ b/src/Serialization/HybridRow.Native/HybridRow.Native.vcxproj.filters @@ -0,0 +1,291 @@ + + + + + + Layouts + + + Layouts + + + Layouts + + + Layouts + + + Layouts + + + Layouts + + + + Scalars + + + Scalars + + + Layouts + + + Layouts + + + Layouts + + + IO + + + IO + + + Schemas + + + Schemas + + + Layouts + + + Generated + + + RecordIO + + + RecordIO + + + + + + + + Layouts + + + + + Layouts + + + Layouts + + + Layouts + + + Schemas + + + Layouts + + + Layouts + + + Layouts + + + Layouts + + + Layouts + + + Layouts + + + Layouts + + + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + + + + Scalars + + + Scalars + + + Scalars + + + Scalars + + + Scalars + + + Scalars + + + Scalars + + + Layouts + + + Layouts + + + Layouts + + + Schemas + + + IO + + + IO + + + IO + + + Schemas + + + Schemas + + + Schemas + + + + Layouts + + + Generated + + + Layouts + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + Schemas + + + RecordIO + + + RecordIO + + + RecordIO + + + Schemas + + + + + {76203f3c-5cd4-446f-96ec-15092eaec523} + + + {8c7a2985-33b9-41f9-8e05-94b996ccec0f} + + + {39967b35-79dc-4b38-b32f-63b700f28630} + + + {fa535215-bc4c-447a-a95a-cb99c42bee8a} + + + {2a643526-2db3-45dc-aba8-230b152dc26a} + + + {27651037-e785-4e32-b0ba-3c5617a2d9f1} + + + + + Layouts + + + + + + \ No newline at end of file diff --git a/src/Serialization/HybridRow.Native/HybridRowHeader.h b/src/Serialization/HybridRow.Native/HybridRowHeader.h new file mode 100644 index 0000000..9041343 --- /dev/null +++ b/src/Serialization/HybridRow.Native/HybridRowHeader.h @@ -0,0 +1,53 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "HybridRowVersion.h" +#include "SchemaId.h" + +namespace cdb_hr +{ + /// Describes the header the precedes all valid Hybrid Rows. + #pragma pack(push, 1) + struct HybridRowHeader final + { + /// Size (in bytes) of a serialized header. + constexpr static uint32_t Size = sizeof(HybridRowVersion) + SchemaId::Size; + + /// Initializes a new instance of the struct. + constexpr HybridRowHeader() noexcept : m_version{HybridRowVersion::Invalid}, m_schemaId{} {} + ~HybridRowHeader() noexcept = default; + HybridRowHeader(const HybridRowHeader& other) noexcept = default; + HybridRowHeader(HybridRowHeader&& other) noexcept = default; + HybridRowHeader& operator=(const HybridRowHeader& other) noexcept = default; + HybridRowHeader& operator=(HybridRowHeader&& other) noexcept = default; + + /// Initializes a new instance of the struct. + /// The version of the HybridRow library used to write this row. + /// The unique identifier of the schema whose layout was used to write this row. + constexpr HybridRowHeader(HybridRowVersion version, SchemaId schemaId); + + /// The version of the HybridRow library used to write this row. + [[nodiscard]] + HybridRowVersion GetVersion() const noexcept; + + /// The unique identifier of the schema whose layout was used to write this row. + [[nodiscard]] + SchemaId GetSchemaId() const noexcept; + + private: + HybridRowVersion m_version; + SchemaId m_schemaId; + }; + #pragma pack(pop) + + static_assert(cdb_core::is_blittable_v); + + constexpr HybridRowHeader::HybridRowHeader(HybridRowVersion version, SchemaId schemaId) : + m_version(version), + m_schemaId(schemaId) {} + + inline HybridRowVersion HybridRowHeader::GetVersion() const noexcept { return m_version; } + inline SchemaId HybridRowHeader::GetSchemaId() const noexcept { return m_schemaId; } +} diff --git a/src/Serialization/HybridRow.Native/HybridRowVersion.h b/src/Serialization/HybridRow.Native/HybridRowVersion.h new file mode 100644 index 0000000..d2a37b7 --- /dev/null +++ b/src/Serialization/HybridRow.Native/HybridRowVersion.h @@ -0,0 +1,18 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// Versions of HybridRow. + /// A version from this list MUST be inserted in the version BOM at the beginning of all rows. + enum class HybridRowVersion : unsigned char + { + Invalid = 0, + + /// Initial version of the HybridRow format. + V1 = 0x81, + }; +} diff --git a/src/Serialization/HybridRow.Native/IHybridRowSerializer.h b/src/Serialization/HybridRow.Native/IHybridRowSerializer.h new file mode 100644 index 0000000..09f84bf --- /dev/null +++ b/src/Serialization/HybridRow.Native/IHybridRowSerializer.h @@ -0,0 +1,196 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "Result.h" +#include "RowBuffer.h" + +namespace cdb_hr +{ + template + struct is_write_serializable; + + /// Write the object to a row. + /// The row to write into. + /// The position in the row at which to write. + /// + /// True if this object is the top-most element within the row such that the row's + /// layout is the object, or false if object is a nested UDT within the column of some other object. + /// + /// Type arguments if the object is a generic collection. + /// The object to write. + /// Success if the write is successful, an error code otherwise. + template + struct is_write_serializable< + Result(TSerializer::*)(RowBuffer&, RowCursor&, bool, const TypeArgumentList&, const T&) const noexcept> + { + constexpr static bool value = true; + }; + + template + struct is_write_serializable + { + constexpr static bool value = true; + }; + + template + struct is_write_serializable + { + constexpr static bool value = is_write_serializable::value; + }; + + template + struct is_read_serializable; + + /// Read and materialize and object from a row. + /// The row to read from. + /// The position in the row to read at. + /// + /// True if this object is the top-most element within the row such that the row's + /// layout is the object, or false if object is a nested UDT within the column of some other object. + /// + /// Success if the write is successful, an error code otherwise. + template + struct is_read_serializable< + std::tuple>(TSerializer::*)(const RowBuffer&, RowCursor&, bool) const> + { + constexpr static bool value = true; + }; + + template + struct is_read_serializable>(*)(const RowBuffer&, RowCursor&, bool)> + { + constexpr static bool value = true; + }; + + template + struct is_read_serializable(*)(const RowBuffer&, RowCursor&, bool)> + { + constexpr static bool value = true; + }; + + template + struct is_read_serializable + { + constexpr static bool value = is_read_serializable::value; + }; + + // ReSharper disable once CppInconsistentNaming + template + struct is_hybridrow_serializer + { + constexpr static bool value = + std::is_nothrow_default_constructible_v + && is_write_serializable::value + && is_read_serializable::value; + }; + + // ReSharper disable once CppInconsistentNaming + template + inline constexpr bool is_hybridrow_serializer_v = is_hybridrow_serializer::value; + + struct IHybridRowSerializer final + { + static const LayoutColumn& InitLayoutColumn(const Layout& layout, const std::string_view& name) noexcept + { + auto [found, col] = layout.TryFind(name); + cdb_core::Contract::Invariant(found); + cdb_core::Contract::Invariant(col != nullptr); + return *col; + } + + static const StringTokenizer::StringToken& InitStringToken(const Layout& layout, + const std::string_view& name) noexcept + { + auto [found, tok] = layout.GetTokenizer().TryFindToken(name); + cdb_core::Contract::Invariant(found); + return tok; + } + + template + constexpr static bool is_default(const T* value) { return value == nullptr; } + + template + constexpr static bool is_default(const std::unique_ptr& value) { return value == nullptr; } + + template + constexpr static bool is_default(const std::optional value) { return !value.has_value(); } + + template, + std::disjunction, std::is_enum>>>> + constexpr static bool is_default(const T& value) { return value == T{}; } + + template + constexpr static bool is_default(const std::basic_string_view value) { return false; } + + template + constexpr static bool is_default(const std::basic_string value) { return false; } + + template + constexpr static bool is_default(const std::vector& value) { return false; } + + template, + std::disjunction, std::is_enum>>>> + constexpr static bool is_default_or_empty(T value) { return value == T{}; } + + template + constexpr static bool is_default_or_empty(const std::vector& value) { return value.empty(); } + + template + constexpr static bool is_default_or_empty(const std::basic_string_view value) + { + return value.empty(); + } + + template + constexpr static bool is_default_or_empty(const std::basic_string value) + { + return value.empty(); + } + + template + constexpr static bool is_default_or_empty(const std::optional value) + { + return !value.has_value() || IHybridRowSerializer::is_default_or_empty(*value); + } + + template + constexpr static const T& get(const T* value) + { + cdb_core::Contract::Requires(value != nullptr); + return *value; + } + + template + constexpr static const T& get(const std::unique_ptr& value) + { + cdb_core::Contract::Requires(value != nullptr); + return *value; + } + + template + constexpr static const T& get(std::optional> value) + { + cdb_core::Contract::Requires(value.has_value()); + return *value; + } + + template + constexpr static const T& get(const T& value) + { + return value; + } + + template + constexpr static std::vector> make_unique_vector(std::unique_ptr... args) + { + std::vector> retval{}; + retval.reserve(std::tuple_size_v>); + (retval.emplace_back(std::move(args)), ...); + return std::move(retval); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/ISpanResizer.h b/src/Serialization/HybridRow.Native/ISpanResizer.h new file mode 100644 index 0000000..99efd3b --- /dev/null +++ b/src/Serialization/HybridRow.Native/ISpanResizer.h @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +namespace cdb_hr +{ + template + struct ISpanResizer + { + ISpanResizer() noexcept = default; + ISpanResizer(const ISpanResizer& other) noexcept = default; + ISpanResizer(ISpanResizer&& other) noexcept = default; + ISpanResizer& operator=(const ISpanResizer& other) noexcept = default; + ISpanResizer& operator=(ISpanResizer&& other) noexcept = default; + virtual ~ISpanResizer() = default; + + /// Resizes an existing a buffer. + /// The type of the elements of the memory. + /// The minimum required length (in elements) of the memory. + /// + /// Optional existing memory to be copied to the new buffer. Ownership of is + /// transferred as part of this call and it should not be used by the caller after this call completes. + /// + /// + /// A new memory whose size is at least as big as + /// and containing the content of . + /// + virtual cdb_core::Span Resize(uint32_t minimumLength, cdb_core::Span buffer = cdb_core::Span()) = 0; + }; +} diff --git a/src/Serialization/HybridRow.Native/Layout.cpp b/src/Serialization/HybridRow.Native/Layout.cpp new file mode 100644 index 0000000..fd6c933 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Layout.cpp @@ -0,0 +1,55 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "Layout.h" +#include "SchemaId.h" + +namespace cdb_hr +{ + Layout::Layout(tla::string_view name, SchemaId schemaId, uint32_t numBitmaskBytes, uint32_t minRequiredSize, + tla::vector> columns) noexcept : + m_name(name), + m_schemaId(schemaId), + m_size(minRequiredSize), + m_numBitmaskBytes(numBitmaskBytes), + m_numFixed(0), + m_numVariable(0), + m_tokenizer(), + m_columns(std::move(columns)), + m_topColumns(), + m_pathMap() + { + m_topColumns.reserve(m_columns.size()); + m_pathMap.reserve(m_columns.size()); + for (const auto& c : m_columns) + { + m_tokenizer.Add(c->GetPath()); + m_pathMap.insert({c->GetFullPath(), c.get()}); + if (c->GetStorage() == StorageKind::Fixed) + { + m_numFixed++; + } + else if (c->GetStorage() == StorageKind::Variable) + { + m_numVariable++; + } + + if (c->GetParent() == nullptr) + { + m_topColumns.push_back(c.get()); + } + } + } + + std::tuple Layout::TryFind(std::string_view path) const noexcept + { + const auto got = m_pathMap.find(path); + if (got == m_pathMap.end()) + { + return {false, nullptr}; + } + return {true, got->second}; + } +} diff --git a/src/Serialization/HybridRow.Native/Layout.h b/src/Serialization/HybridRow.Native/Layout.h new file mode 100644 index 0000000..89c2ba2 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Layout.h @@ -0,0 +1,103 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#pragma warning(disable : 4638) +#include "SchemaId.h" +#include "LayoutColumn.h" +#include "SamplingUtf8StringComparer.h" +#include "StringTokenizer.h" + +namespace cdb_hr +{ + // Forward declaration. + class Schema; +} + +namespace cdb_hr +{ + using namespace std::literals; + + /// A Layout describes the structure of a Hybrid Row. + /// + /// A layout indicates the number, order, and type of all schematized columns to be stored + /// within a hybrid row. The order and type of columns defines the physical ordering of bytes used to + /// encode the row and impacts the cost of updating the row. + /// + /// A layout is created by compiling a or through a . + /// + /// is immutable. + /// + class Layout final + { + public: + /// Name of the layout. + /// + /// Usually this is the name of the from which this + /// was generated. + /// + [[nodiscard]] std::string_view GetName() const noexcept; + + /// Unique identifier of the schema from which this was generated. + [[nodiscard]] SchemaId GetSchemaId() const noexcept; + + /// The set of top level columns defined in the layout (in left-to-right order). + [[nodiscard]] const tla::vector& GetColumns() const noexcept; + + /// Minimum required size of a row of this layout. + /// + /// This size excludes all sparse columns, and assumes all columns (including variable) are + /// null. + /// + [[nodiscard]] uint32_t GetSize() const noexcept; + + /// The number of bitmask bytes allocated within the layout. + /// + /// A presence bit is allocated for each fixed and variable-length field. Sparse columns + /// never have presence bits. Fixed boolean allocate an additional bit from the bitmask to store their + /// value. + /// + [[nodiscard]] uint32_t GetNumBitmaskBytes() const noexcept; + + /// The number of fixed columns. + [[nodiscard]] uint32_t GetNumFixed() const noexcept; + + /// The number of variable-length columns. + [[nodiscard]] uint32_t GetNumVariable() const noexcept; + + /// A tokenizer for path strings. + [[nodiscard]] const StringTokenizer& GetTokenizer() const noexcept; + + /// Finds a column specification for a column with a matching path. + /// The path of the column to find. + /// (True and the column) if a column with the path is found, otherwise (false, nullptr). + [[nodiscard]] std::tuple TryFind(std::string_view path) const noexcept; + + private: + friend class LayoutBuilder; + friend struct LayoutConstants; + Layout(std::string_view name, SchemaId schemaId, uint32_t numBitmaskBytes, uint32_t minRequiredSize, + tla::vector> columns) noexcept; + + tla::string m_name; + SchemaId m_schemaId; + uint32_t m_size; + uint32_t m_numBitmaskBytes; + uint32_t m_numFixed; + uint32_t m_numVariable; + StringTokenizer m_tokenizer; + tla::vector> m_columns; + tla::vector m_topColumns; + tla::unordered_map m_pathMap; + }; + + inline std::string_view Layout::GetName() const noexcept { return m_name; } + inline SchemaId Layout::GetSchemaId() const noexcept { return m_schemaId; } + inline const tla::vector& Layout::GetColumns() const noexcept { return m_topColumns; } + inline uint32_t Layout::GetSize() const noexcept { return m_size; } + inline uint32_t Layout::GetNumBitmaskBytes() const noexcept { return m_numBitmaskBytes; } + inline uint32_t Layout::GetNumFixed() const noexcept { return m_numFixed; } + inline uint32_t Layout::GetNumVariable() const noexcept { return m_numVariable; } + inline const StringTokenizer& Layout::GetTokenizer() const noexcept { return m_tokenizer; } +} diff --git a/src/Serialization/HybridRow.Native/LayoutBit.h b/src/Serialization/HybridRow.Native/LayoutBit.h new file mode 100644 index 0000000..ce467e3 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutBit.h @@ -0,0 +1,126 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr_test +{ + class LayoutCompilerUnitTests; +} + +namespace cdb_hr +{ + class LayoutBuilder; + + struct LayoutBit final + { + /// The number of bits in a single byte on the current architecture. + constexpr static uint32_t BitsPerByte = 8; + + /// The empty bit. + constexpr static LayoutBit Invalid(); + + ~LayoutBit() noexcept = default; + LayoutBit(const LayoutBit& other) noexcept = default; + LayoutBit(LayoutBit&& other) noexcept = default; + LayoutBit& operator=(const LayoutBit& other) noexcept = default; + LayoutBit& operator=(LayoutBit&& other) noexcept = default; + bool operator==(const LayoutBit& rhs) const noexcept; + bool operator!=(const LayoutBit& rhs) const noexcept; + + /// True if the default empty value, false otherwise. + [[nodiscard]] bool IsInvalid() const noexcept; + + /// The 0-based offset into the layout bitmask. + [[nodiscard]] uint32_t GetIndex() const noexcept; + + /// + /// Returns the 0-based byte offset from the beginning of the row or scope that contains the + /// bit from the bitmask. + /// + /// Also see to identify. + /// The byte offset from the beginning of the row where the scope begins. + /// The byte offset containing this bit. + [[nodiscard]] uint32_t GetOffset(uint32_t offset) const noexcept; + + /// Returns the 0-based bit from the beginning of the byte that contains this bit. + /// Also see to identify relevant byte. + /// The bit of the byte within the bitmask. + [[nodiscard]] uint32_t GetBit() const noexcept; + + [[nodiscard]] size_t GetHashCode() const noexcept; + + private: + friend class LayoutBuilder; + friend class cdb_hr_test::LayoutCompilerUnitTests; + + /// Compute the division rounding up to the next whole number. + /// The numerator to divide. + /// The divisor to divide by. + /// The ceiling(numerator/divisor). + constexpr static uint32_t DivCeiling(uint32_t numerator, uint32_t divisor); + + /// Allocates layout bits from a bitmask. + struct Allocator final + { + /// Initializes a new instance of the class. + Allocator() noexcept : m_next{0} { } + ~Allocator() noexcept = default; + Allocator(const Allocator& other) noexcept = default; + Allocator(Allocator&& other) noexcept = default; + Allocator& operator=(const Allocator& other) noexcept = default; + Allocator& operator=(Allocator&& other) noexcept = default; + + /// The number of bytes needed to hold all bits so far allocated. + [[nodiscard]] uint32_t GetNumBytes() const noexcept; + + /// Allocates a new bit from the bitmask. + /// The allocated bit. + LayoutBit Allocate() noexcept; + + private: + /// The next bit to allocate. + uint32_t m_next; + }; + + /// Initializes a new instance of the struct. + /// The 0-based offset into the layout bitmask. + explicit constexpr LayoutBit(uint32_t index) noexcept : m_index{index} { } + + /// The 0-based offset into the layout bitmask. + uint32_t m_index; + }; + + constexpr LayoutBit LayoutBit::Invalid() { return LayoutBit(static_cast(-1)); } + inline bool LayoutBit::operator==(const LayoutBit& rhs) const noexcept { return m_index == rhs.m_index; } + inline bool LayoutBit::operator!=(const LayoutBit& rhs) const noexcept { return !operator==(rhs); } + inline bool LayoutBit::IsInvalid() const noexcept { return m_index == static_cast(-1); } + inline uint32_t LayoutBit::GetIndex() const noexcept { return m_index; } + + inline uint32_t LayoutBit::GetOffset(uint32_t offset) const noexcept + { + return offset + (m_index / BitsPerByte); + } + + inline uint32_t LayoutBit::GetBit() const noexcept { return m_index % BitsPerByte; } + + inline size_t LayoutBit::GetHashCode() const noexcept + { + static_assert(cdb_core::is_hashable_v); + + return std::hash{}(static_cast(m_index)); + } + + inline uint32_t LayoutBit::Allocator::GetNumBytes() const noexcept + { + return DivCeiling(m_next, BitsPerByte); + } + + inline LayoutBit LayoutBit::Allocator::Allocate() noexcept { return LayoutBit(m_next++); } + + constexpr uint32_t LayoutBit::DivCeiling(uint32_t numerator, uint32_t divisor) + { + return (numerator + (divisor - 1)) / divisor; + } +} diff --git a/src/Serialization/HybridRow.Native/LayoutBuilder.cpp b/src/Serialization/HybridRow.Native/LayoutBuilder.cpp new file mode 100644 index 0000000..d56c245 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutBuilder.cpp @@ -0,0 +1,242 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "LayoutBuilder.h" + +#include "LayoutType.h" + +// ReSharper disable IdentifierTypo +namespace cdb_hr +{ + LayoutBuilder::LayoutBuilder(std::string_view name, SchemaId schemaId) noexcept : + m_name{name}, + m_schemaId{schemaId}, + m_fixedSize{}, + m_fixedCount{}, + m_varCount{}, + m_sparseCount{}, + m_bitAllocator{}, + m_fixedColumns{}, + m_varColumns{}, + m_sparseColumns{}, + m_scope{} + { + // [ + // + // ... + // ... + // ... + // ] + } + + LayoutColumn* LayoutBuilder::GetParent() noexcept + { + if (m_scope.empty()) + { + return nullptr; + } + + return m_scope.back(); + } + + void LayoutBuilder::AddFixedColumn(std::string_view path, const LayoutType* type, bool nullable, + uint32_t length) noexcept + { + cdb_core::Contract::Requires(!type->IsVarint()); + + std::unique_ptr col; + if (type->IsNull()) + { + cdb_core::Contract::Requires(nullable); + LayoutBit nullbit = m_bitAllocator.Allocate(); + col.reset(new LayoutColumn( + path, + type, + {}, + StorageKind::Fixed, + GetParent(), + m_fixedCount, + 0, + nullbit, + LayoutBit::Invalid() // boolBit + )); + } + else if (type->IsBool()) + { + LayoutBit nullbit = nullable ? m_bitAllocator.Allocate() : LayoutBit::Invalid(); + LayoutBit boolbit = m_bitAllocator.Allocate(); + col.reset(new LayoutColumn( + path, + type, + {}, + StorageKind::Fixed, + GetParent(), + m_fixedCount, + 0, + nullbit, + boolbit // boolBit + )); + } + else + { + LayoutBit nullbit = nullable ? m_bitAllocator.Allocate() : LayoutBit::Invalid(); + col.reset(new LayoutColumn( + path, + type, + {}, + StorageKind::Fixed, + GetParent(), + m_fixedCount, + m_fixedSize, + nullbit, + LayoutBit::Invalid(), // boolBit + length)); + + m_fixedSize += type->IsFixed() ? type->GetSize() : length; + } + + m_fixedCount++; + cdb_core::Contract::Assert(col != nullptr); + m_fixedColumns.push_back(std::move(col)); + } + + void LayoutBuilder::AddVariableColumn(std::string_view path, const LayoutType* type, uint32_t length) noexcept + { + cdb_core::Contract::Requires(type->AllowVariable()); + + std::unique_ptr col{ + new LayoutColumn{ + path, + type, + {}, + StorageKind::Variable, + GetParent(), + m_varCount, + m_varCount, + m_bitAllocator.Allocate(), // nullBit + LayoutBit::Invalid(), // boolBit + length + } + }; + + m_varCount++; + m_varColumns.push_back(std::move(col)); + } + + void LayoutBuilder::AddSparseColumn(std::string_view path, const LayoutType* type) noexcept + { + std::unique_ptr col{ + new LayoutColumn{ + path, + type, + {}, + StorageKind::Sparse, + GetParent(), + m_sparseCount, + UINT_MAX, + LayoutBit::Invalid(), // nullBit + LayoutBit::Invalid() // boolBit + } + }; + + m_sparseCount++; + m_sparseColumns.push_back(std::move(col)); + } + + void LayoutBuilder::AddObjectScope(std::string_view path, const LayoutType* type) noexcept + { + std::unique_ptr col{ + new LayoutColumn{ + path, + type, + {}, + StorageKind::Sparse, + GetParent(), + m_sparseCount, + UINT_MAX, + LayoutBit::Invalid(), // nullBit + LayoutBit::Invalid() // boolBit + } + }; + + m_sparseCount++; + m_scope.push_back(col.get()); + m_sparseColumns.push_back(std::move(col)); + } + + void LayoutBuilder::EndObjectScope() noexcept + { + cdb_core::Contract::Requires(!m_scope.empty()); + m_scope.pop_back(); + } + + void LayoutBuilder::AddTypedScope(std::string_view path, const LayoutType* type, TypeArgumentList typeArgs) noexcept + { + std::unique_ptr col{ + new LayoutColumn{ + path, + type, + std::move(typeArgs), + StorageKind::Sparse, + GetParent(), + m_sparseCount, + UINT_MAX, + LayoutBit::Invalid(), // nullBit + LayoutBit::Invalid() // boolBit + } + }; + + m_sparseCount++; + m_sparseColumns.push_back(std::move(col)); + } + + std::unique_ptr LayoutBuilder::Build() noexcept + { + // Compute offset deltas. Offset bools by the present byte count, and fixed fields by the sum of the present and bool count. + uint32_t fixedDelta = m_bitAllocator.GetNumBytes(); + uint32_t varIndexDelta = m_fixedCount; + + // Update the fixedColumns with the delta before freezing them. + tla::vector> updatedColumns{}; + updatedColumns.reserve(m_fixedColumns.size() + m_varColumns.size()); + + for (auto& c : m_fixedColumns) + { + c->SetOffset(c->GetOffset() + fixedDelta); + updatedColumns.push_back(std::move(c)); + } + + for (auto& c : m_varColumns) + { + // Adjust variable column indexes such that they begin immediately following the last fixed column. + c->SetIndex(c->GetIndex() + varIndexDelta); + updatedColumns.push_back(std::move(c)); + } + + for (auto& c : m_sparseColumns) + { + updatedColumns.push_back(std::move(c)); + } + + std::unique_ptr layout = std::unique_ptr{ + new Layout(m_name, m_schemaId, m_bitAllocator.GetNumBytes(), m_fixedSize + fixedDelta, std::move(updatedColumns)) + }; + Reset(); + return std::move(layout); + } + + void LayoutBuilder::Reset() noexcept + { + m_bitAllocator = LayoutBit::Allocator(); + m_fixedSize = 0; + m_fixedCount = 0; + m_fixedColumns.clear(); + m_varCount = 0; + m_varColumns.clear(); + m_sparseCount = 0; + m_sparseColumns.clear(); + m_scope.clear(); + } +} diff --git a/src/Serialization/HybridRow.Native/LayoutBuilder.h b/src/Serialization/HybridRow.Native/LayoutBuilder.h new file mode 100644 index 0000000..5830d0c --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutBuilder.h @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "Layout.h" +#include "LayoutBit.h" +#include "LayoutColumn.h" +#include "SchemaId.h" + +namespace cdb_hr +{ + class LayoutBuilder final + { + public: + LayoutBuilder(std::string_view name, SchemaId schemaId) noexcept; + ~LayoutBuilder() noexcept = default; + LayoutBuilder() = delete; + LayoutBuilder(const LayoutBuilder& other) = delete; + LayoutBuilder(LayoutBuilder&& other) noexcept = delete; + LayoutBuilder& operator=(const LayoutBuilder& other) = delete; + LayoutBuilder& operator=(LayoutBuilder&& other) noexcept = delete; + + void AddFixedColumn(std::string_view path, const LayoutType* type, bool nullable, uint32_t length = 0) noexcept; + void AddVariableColumn(std::string_view path, const LayoutType* type, uint32_t length = 0) noexcept; + void AddSparseColumn(std::string_view path, const LayoutType* type) noexcept; + void AddObjectScope(std::string_view path, const LayoutType* type) noexcept; + void EndObjectScope() noexcept; + void AddTypedScope(std::string_view path, const LayoutType* type, TypeArgumentList typeArgs) noexcept; + std::unique_ptr Build() noexcept; + + private: + LayoutColumn* GetParent() noexcept; + void Reset() noexcept; + + tla::string m_name; + SchemaId m_schemaId; + uint32_t m_fixedSize; + uint32_t m_fixedCount; + uint32_t m_varCount; + uint32_t m_sparseCount; + LayoutBit::Allocator m_bitAllocator; + tla::vector> m_fixedColumns; + tla::vector> m_varColumns; + tla::vector> m_sparseColumns; + tla::vector m_scope; + }; +} diff --git a/src/Serialization/HybridRow.Native/LayoutCode.h b/src/Serialization/HybridRow.Native/LayoutCode.h new file mode 100644 index 0000000..fa04cbf --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutCode.h @@ -0,0 +1,73 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ +#pragma once + +namespace cdb_hr +{ +/// Type coded used in the binary encoding to indicate the formatting of succeeding bytes. +enum class LayoutCode : unsigned char +{ + Invalid = 0, + + Null = 1, + BooleanFalse = 2, + Boolean = 3, + + Int8 = 5, + Int16 = 6, + Int32 = 7, + Int64 = 8, + UInt8 = 9, + UInt16 = 10, + UInt32 = 11, + UInt64 = 12, + VarInt = 13, + VarUInt = 14, + + Float32 = 15, + Float64 = 16, + Decimal = 17, + + DateTime = 18, + Guid = 19, + + Utf8 = 20, + Binary = 21, + + Float128 = 22, + UnixDateTime = 23, + MongoDbObjectId = 24, + + ObjectScope = 30, + ImmutableObjectScope = 31, + ArrayScope = 32, + ImmutableArrayScope = 33, + TypedArrayScope = 34, + ImmutableTypedArrayScope = 35, + TupleScope = 36, + ImmutableTupleScope = 37, + TypedTupleScope = 38, + ImmutableTypedTupleScope = 39, + MapScope = 40, + ImmutableMapScope = 41, + TypedMapScope = 42, + ImmutableTypedMapScope = 43, + SetScope = 44, + ImmutableSetScope = 45, + TypedSetScope = 46, + ImmutableTypedSetScope = 47, + NullableScope = 48, + ImmutableNullableScope = 49, + TaggedScope = 50, + ImmutableTaggedScope = 51, + Tagged2Scope = 52, + ImmutableTagged2Scope = 53, + + /// Nested row. + Schema = 68, + ImmutableSchema = 69, + + EndScope = 70, +}; +} diff --git a/src/Serialization/HybridRow.Native/LayoutCodeTraits.h b/src/Serialization/HybridRow.Native/LayoutCodeTraits.h new file mode 100644 index 0000000..fa36dbf --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutCodeTraits.h @@ -0,0 +1,43 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "LayoutCode.h" + +namespace cdb_hr +{ +struct LayoutCodeTraits final +{ + /// Returns the same scope code without the immutable bit set. + /// The scope type code. + static LayoutCode ClearImmutableBit(LayoutCode code) noexcept + { + return static_cast(static_cast(code) & 0xFE); + } + + /// + /// Returns true if the type code indicates that, even within a typed scope, this element type + /// always requires a type code (because the value itself is in the type code). + /// + /// The element type code. + static bool AlwaysRequiresTypeCode(LayoutCode code) noexcept + { + return (code == LayoutCode::Boolean) || + (code == LayoutCode::BooleanFalse) || + (code == LayoutCode::Null); + } + + /// Returns a canonicalized version of the layout code. + /// + /// Some codes (e.g. use multiple type codes to also encode + /// values. This function converts actual value based code into the canonicalized type code for schema + /// comparisons. + /// + /// The code to canonicalize. + static LayoutCode Canonicalize(LayoutCode code) noexcept + { + return (code == LayoutCode::BooleanFalse) ? LayoutCode::Boolean : code; + } +}; +} diff --git a/src/Serialization/HybridRow.Native/LayoutColumn.cpp b/src/Serialization/HybridRow.Native/LayoutColumn.cpp new file mode 100644 index 0000000..82a9d1a --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutColumn.cpp @@ -0,0 +1,65 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include +#include "LayoutType.h" +#include "LayoutColumn.h" +#include "LayoutCode.h" +#include "LayoutCodeTraits.h" + +namespace cdb_hr +{ + LayoutColumn::LayoutColumn(std::string_view path, const LayoutType* type, TypeArgumentList typeArgs, + StorageKind storage, const LayoutColumn* parent, + uint32_t index, uint32_t offset, LayoutBit nullBit, LayoutBit boolBit, + uint32_t length) noexcept : + m_size(type->IsFixed() ? type->GetSize() : length), + m_path(path), + m_fullPath(GetFullPath(parent, m_path)), + m_typeArg(type, std::move(typeArgs)), + m_storage(storage), + m_parent(parent), + m_index(index), + m_offset(offset), + m_nullBit(nullBit), + m_boolBit(boolBit) { } + + tla::string LayoutColumn::GetFullPath(const LayoutColumn* parent, std::string_view path) noexcept + { + if (parent != nullptr) + { + switch (LayoutCodeTraits::ClearImmutableBit(parent->m_typeArg.GetType()->GetLayoutCode())) + { + case LayoutCode::ObjectScope: + case LayoutCode::Schema: + try + { + return tla::string((std::ostringstream() << parent->GetFullPath() << "."sv << path).str()); + } + catch (std::bad_alloc&) + { + cdb_core::Contract::Fail("Unable to allocate memory."); + } + case LayoutCode::ArrayScope: + case LayoutCode::TypedArrayScope: + case LayoutCode::TypedSetScope: + case LayoutCode::TypedMapScope: + try + { + return tla::string((std::ostringstream() << parent->GetFullPath() << "[]"sv << path).str()); + } + catch (std::bad_alloc&) + { + cdb_core::Contract::Fail("Unable to allocate memory."); + } + default: + cdb_core::Contract::Fail(cdb_core::make_string("Parent scope type not supported: %d", + parent->m_typeArg.GetType()->GetLayoutCode())); + } + } + + return tla::string(path); + } +} diff --git a/src/Serialization/HybridRow.Native/LayoutColumn.h b/src/Serialization/HybridRow.Native/LayoutColumn.h new file mode 100644 index 0000000..e3dc60a --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutColumn.h @@ -0,0 +1,165 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "StorageKind.h" +#include "TypeArgument.h" +#include "TypeArgumentList.h" +#include "LayoutBit.h" + +namespace cdb_hr +{ + class LayoutColumn final + { + public: + ~LayoutColumn() noexcept = default; + LayoutColumn(const LayoutColumn& other) = delete; + LayoutColumn(LayoutColumn&& other) noexcept = delete; + LayoutColumn& operator=(const LayoutColumn& other) = delete; + LayoutColumn& operator=(LayoutColumn&& other) noexcept = delete; + + /// The relative path of the field within its parent scope. + /// + /// Paths are expressed in dotted notation: e.g. a relative of 'b.c' + /// within the scope 'a' yields a of 'a.b.c'. + /// + [[nodiscard]] std::string_view GetPath() const noexcept { return m_path; } + + /// The full logical path of the field within the row. + /// + /// Paths are expressed in dotted notation: e.g. a relative of 'b.c' + /// within the scope 'a' yields a of 'a.b.c'. + /// + [[nodiscard]] std::string_view GetFullPath() const noexcept { return m_fullPath; } + + /// The physical layout type of the field. + [[nodiscard]] const LayoutType* GetType() const noexcept { return m_typeArg.GetType(); } + + /// The storage kind of the field. + [[nodiscard]] StorageKind GetStorage() const noexcept { return m_storage; } + + /// The layout of the parent scope, if a nested column, otherwise null. + [[nodiscard]] const LayoutColumn* GetParent() const noexcept { return m_parent; } + + /// The full logical type. + [[nodiscard]] const TypeArgument& GetTypeArg() const noexcept { return m_typeArg; } + + /// For types with generic parameters (e.g. , the type parameters. + [[nodiscard]] const TypeArgumentList& GetTypeArgs() const noexcept { return m_typeArg.GetTypeArgs(); } + + /// + /// 0-based index of the column within the structure. Also indicates which presence bit + /// controls this column. + /// + [[nodiscard]] uint32_t GetIndex() const noexcept { return m_index; } + + /// + /// If equals then the byte offset to + /// the field location. + /// + /// If equals then the 0-based index of the + /// field from the beginning of the variable length segment. + /// + /// For all other values of , is ignored. + /// + [[nodiscard]] uint32_t GetOffset() const noexcept { return m_offset; } + + /// For nullable fields, the the bit in the layout bitmask for the null bit. + [[nodiscard]] LayoutBit GetNullBit() const noexcept { return m_nullBit; } + + /// For bool fields, 0-based index into the bit mask for the bool value. + [[nodiscard]] LayoutBit GetBoolBit() const noexcept { return m_boolBit; } + + /// + /// If equals then the fixed number of + /// bytes reserved for the value. + /// + /// If equals then the maximum number of + /// bytes allowed for the value. + /// + [[nodiscard]] uint32_t GetSize() const noexcept { return m_size; } + + /// The physical layout type of the field cast to the specified type. + template>> + const T& TypeAs() const; + + private: + friend class LayoutBuilder; + + /// Initializes a new instance of the class. + /// The path to the field relative to parent scope. + /// Type of the field. + /// Storage encoding of the field. + /// The layout of the parent scope, if a nested column. + /// 0-based column index. + /// 0-based Offset from beginning of serialization. + /// 0-based index into the bit mask for the null bit. + /// For bool fields, 0-based index into the bit mask for the bool value. + /// For variable length types the length, otherwise 0. + /// + /// For types with generic parameters (e.g. , the type + /// parameters. + /// + LayoutColumn(std::string_view path, const LayoutType* type, TypeArgumentList typeArgs, + StorageKind storage, const LayoutColumn* parent, + uint32_t index, uint32_t offset, LayoutBit nullBit, LayoutBit boolBit, uint32_t length = 0) noexcept; + + void SetIndex(uint32_t index) { m_index = index; } + void SetOffset(uint32_t offset) { m_offset = offset; } + + /// Computes the full logical path to the column. + /// The layout of the parent scope, if a nested column, otherwise null. + /// The path to the field relative to parent scope. + /// The full logical path. + [[nodiscard]] + static tla::string GetFullPath(const LayoutColumn* parent, std::string_view path) noexcept; + + /// + /// If equals then the fixed number of + /// bytes reserved for the value. + /// + /// If equals then the maximum number of + /// bytes allowed for the value. + /// + uint32_t m_size; + + /// The relative path of the field within its parent scope. + std::string m_path; + + /// The full logical path of the field within the row. + std::string m_fullPath; + + /// The physical layout type of the field. + TypeArgument m_typeArg; + + /// The storage kind of the field. + StorageKind m_storage; + + /// The layout of the parent scope, if a nested column, otherwise null. + const LayoutColumn* m_parent; + + /// + /// 0-based index of the column within the structure. Also indicates which presence bit + /// controls this column. + /// + uint32_t m_index; + + /// + /// If equals then the byte offset to + /// the field location. + /// + /// If equals then the 0-based index of the + /// field from the beginning of the variable length segment. + /// + /// For all other values of , is ignored. + /// + uint32_t m_offset; + + /// For nullable fields, the 0-based index into the bit mask for the null bit. + LayoutBit m_nullBit; + + /// For bool fields, 0-based index into the bit mask for the bool value. + LayoutBit m_boolBit; + }; +} diff --git a/src/Serialization/HybridRow.Native/LayoutCompiler.cpp b/src/Serialization/HybridRow.Native/LayoutCompiler.cpp new file mode 100644 index 0000000..a59b387 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutCompiler.cpp @@ -0,0 +1,502 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "Schema.h" +#include "Namespace.h" +#include "LayoutCode.h" +#include "LayoutType.h" +#include "LayoutBuilder.h" +#include "LayoutCompiler.h" +#include "ArrayPropertyType.h" +#include "LayoutCodeTraits.h" +#include "MapPropertyType.h" +#include "ObjectPropertyType.h" +#include "PrimitivePropertyType.h" +#include "SetPropertyType.h" +#include "TaggedPropertyType.h" +#include "TuplePropertyType.h" +#include "UdtPropertyType.h" + +// ReSharper disable CppClangTidyCppcoreguidelinesProTypeStaticCastDowncast +namespace cdb_hr +{ + class LayoutBuilder; + + // Forward declaration. + static void AddBase(LayoutBuilder& builder, const Namespace& ns, const Schema& s); + static void AddProperties(LayoutBuilder& builder, SchemaLanguageVersion v, const Namespace& ns, LayoutCode scope, + const std::vector>& properties); + static std::tuple LogicalToPhysicalType( + SchemaLanguageVersion v, const Namespace& ns, const PropertyType& logicalType); + static const LayoutType* PrimitiveToPhysicalType(TypeKind type); + constexpr static std::string_view BasePropertyName = "__base"sv; + + std::unique_ptr LayoutCompiler::Compile(const Namespace& ns, const Schema& schema) noexcept(false) + { + cdb_core::Contract::Requires(schema.GetType() == TypeKind::Schema); + cdb_core::Contract::Requires(!schema.GetName().empty()); + cdb_core::Contract::Requires(std::find_if(ns.GetSchemas().begin(), ns.GetSchemas().end(), + [&schema](const std::unique_ptr& p) { return p.get() == &schema; }) != ns.GetSchemas().end()); + + SchemaLanguageVersion v = schema.GetEffectiveSdlVersion(ns); + LayoutBuilder builder{schema.GetName(), schema.GetSchemaId()}; + if (!schema.GetBaseName().empty()) + { + AddBase(builder, ns, schema); + } + AddProperties(builder, v, ns, LayoutCode::Schema, schema.GetProperties()); + + return builder.Build(); + } + + void AddBase(LayoutBuilder& builder, const Namespace& ns, const Schema& s) + { + const auto& schemas = ns.GetSchemas(); + decltype(schemas.begin()) iter; + if (s.GetBaseSchemaId() == SchemaId::Invalid()) + { + iter = std::find_if(schemas.begin(), schemas.end(), + [&s](const std::unique_ptr& q) { return q->GetName() == s.GetBaseName(); }); + } + else + { + iter = std::find_if(schemas.begin(), schemas.end(), + [&s](const std::unique_ptr& q) { return q->GetSchemaId() == s.GetBaseSchemaId(); }); + } + + if (iter == schemas.end()) + { + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Cannot resolve schema reference: '%s:%d'", + s.GetBaseName().data(), s.GetBaseSchemaId().Id())); + } + + const Schema& bs = **iter; + if (bs.GetName() != s.GetBaseName()) + { + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Ambiguous schema reference: '%s:%d'", + s.GetBaseName().data(), s.GetBaseSchemaId().Id())); + } + + builder.AddTypedScope(BasePropertyName, &LayoutLiteral::UDT, bs.GetSchemaId()); + } + + void AddProperties( + LayoutBuilder& builder, + SchemaLanguageVersion v, + const Namespace& ns, + LayoutCode scope, + const std::vector>& properties) + { + for (const auto& ptr : properties) + { + const Property& p = *ptr; + if (!p.GetPropertyType().has_value()) + { + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Property missing type")); + } + const PropertyType& pt = p.GetPropertyType().value(); + auto [type, typeArgs] = LogicalToPhysicalType(v, ns, pt); + switch (LayoutCodeTraits::ClearImmutableBit(type->GetLayoutCode())) + { + case LayoutCode::ObjectScope: + { + if (!pt.GetNullable()) + { + throw LayoutCompiler::LayoutCompilationException("Non-nullable sparse column are not supported."); + } + + cdb_core::Contract::Invariant(pt.GetKind() == PropertyKind::Object); + const ObjectPropertyType& op = static_cast(pt); + builder.AddObjectScope(p.GetPath(), type); + AddProperties(builder, v, ns, type->GetLayoutCode(), op.GetProperties()); + builder.EndObjectScope(); + break; + } + + case LayoutCode::ArrayScope: + case LayoutCode::TypedArrayScope: + case LayoutCode::SetScope: + case LayoutCode::TypedSetScope: + case LayoutCode::MapScope: + case LayoutCode::TypedMapScope: + case LayoutCode::TupleScope: + case LayoutCode::TypedTupleScope: + case LayoutCode::TaggedScope: + case LayoutCode::Tagged2Scope: + case LayoutCode::Schema: + { + if (!pt.GetNullable()) + { + throw LayoutCompiler::LayoutCompilationException("Non-nullable sparse column are not supported."); + } + + builder.AddTypedScope(p.GetPath(), type, typeArgs); + break; + } + + case LayoutCode::NullableScope: + { + throw LayoutCompiler::LayoutCompilationException("Nullables cannot be explicitly declared as columns."); + } + + default: + { + if (pt.GetKind() == PropertyKind::Primitive) + { + const PrimitivePropertyType& pp = static_cast(pt); + if ((pp.GetType() == TypeKind::Enum) && (v < SchemaLanguageVersion::V2)) + { + throw LayoutCompiler::LayoutCompilationException("Enums require SDL v2 or higher."); + } + + switch (pp.GetStorage()) + { + case StorageKind::Fixed: + if (LayoutCodeTraits::ClearImmutableBit(scope) != LayoutCode::Schema) + { + throw LayoutCompiler::LayoutCompilationException("Cannot have fixed storage within a sparse scope."); + } + + if (type->IsNull() && !pp.GetNullable()) + { + throw LayoutCompiler::LayoutCompilationException("Non-nullable null columns are not supported."); + } + + builder.AddFixedColumn(p.GetPath(), type, pp.GetNullable(), pp.GetLength()); + break; + case StorageKind::Variable: + if (pp.GetType() == TypeKind::Enum) + { + throw LayoutCompiler::LayoutCompilationException("Enums cannot have storage specification: Variable"); + } + + if (LayoutCodeTraits::ClearImmutableBit(scope) != LayoutCode::Schema) + { + throw LayoutCompiler::LayoutCompilationException("Cannot have variable storage within a sparse scope."); + } + + if (!pp.GetNullable()) + { + throw LayoutCompiler::LayoutCompilationException("Non-nullable variable columns are not supported."); + } + + builder.AddVariableColumn(p.GetPath(), type, pp.GetLength()); + break; + case StorageKind::Sparse: + if (!pp.GetNullable()) + { + throw LayoutCompiler::LayoutCompilationException("Non-nullable sparse columns are not supported."); + } + + builder.AddSparseColumn(p.GetPath(), type); + break; + default: + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Unknown storage specification: %u", + pp.GetStorage())); + } + } + else + { + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Unknown property type: %s", + type->GetName().data())); + } + + break; + } + } + } + } + + static std::tuple LogicalToPhysicalType( + SchemaLanguageVersion v, const Namespace& ns, const PropertyType& logicalType) + { + bool immutable = (logicalType.GetKind() == PropertyKind::Primitive) + ? false + : static_cast(logicalType).GetImmutable(); + + switch (logicalType.GetType()) + { + case TypeKind::Null: + case TypeKind::Boolean: + case TypeKind::Int8: + case TypeKind::Int16: + case TypeKind::Int32: + case TypeKind::Int64: + case TypeKind::UInt8: + case TypeKind::UInt16: + case TypeKind::UInt32: + case TypeKind::UInt64: + case TypeKind::Float32: + case TypeKind::Float64: + case TypeKind::Float128: + case TypeKind::Decimal: + case TypeKind::DateTime: + case TypeKind::UnixDateTime: + case TypeKind::Guid: + case TypeKind::MongoDbObjectId: + case TypeKind::Utf8: + case TypeKind::Binary: + case TypeKind::VarInt: + case TypeKind::VarUInt: + return {PrimitiveToPhysicalType(logicalType.GetType()), {}}; + + case TypeKind::Object: + return {immutable ? &LayoutLiteral::ImmutableObject : &LayoutLiteral::Object, {}}; + case TypeKind::Array: + { + cdb_core::Contract::Invariant(logicalType.GetKind() == PropertyKind::Array); + const ArrayPropertyType& ap = static_cast(logicalType); + if (ap.GetItems().has_value()) + { + const PropertyType& pt = *ap.GetItems(); + if (pt.GetType() != TypeKind::Any) + { + auto [itemType, itemTypeArgs] = LogicalToPhysicalType(v, ns, pt); + if (pt.GetNullable()) + { + itemTypeArgs = {{itemType, itemTypeArgs}}; + itemType = itemType->IsImmutable() ? &LayoutLiteral::ImmutableNullable : &LayoutLiteral::Nullable; + } + + TypeArgumentList typeArgs{{itemType, itemTypeArgs}}; + return {immutable ? &LayoutLiteral::ImmutableTypedArray : &LayoutLiteral::TypedArray, typeArgs}; + } + } + + return {immutable ? &LayoutLiteral::ImmutableArray : &LayoutLiteral::Array, {}}; + } + case TypeKind::Set: + { + cdb_core::Contract::Invariant(logicalType.GetKind() == PropertyKind::Set); + const SetPropertyType& sp = static_cast(logicalType); + if (sp.GetItems().has_value()) + { + const PropertyType& pt = *sp.GetItems(); + if (pt.GetType() != TypeKind::Any) + { + auto [itemType, itemTypeArgs] = LogicalToPhysicalType(v, ns, pt); + if (pt.GetNullable()) + { + itemTypeArgs = {{itemType, itemTypeArgs}}; + itemType = itemType->IsImmutable() ? &LayoutLiteral::ImmutableNullable : &LayoutLiteral::Nullable; + } + + TypeArgumentList typeArgs{{itemType, itemTypeArgs}}; + return {immutable ? &LayoutLiteral::ImmutableTypedSet : &LayoutLiteral::TypedSet, typeArgs}; + } + } + + // TODO(283638): implement sparse set. + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Unknown property type: %u", + logicalType.GetType())); + } + case TypeKind::Map: + { + cdb_core::Contract::Invariant(logicalType.GetKind() == PropertyKind::Map); + const MapPropertyType& mp = static_cast(logicalType); + if (mp.GetKeys().has_value() && mp.GetValues().has_value()) + { + const PropertyType& kpt = *mp.GetKeys(); + const PropertyType& vpt = *mp.GetValues(); + if ((kpt.GetType() != TypeKind::Any) && (vpt.GetType() != TypeKind::Any)) + { + auto [keyType, keyTypeArgs] = LogicalToPhysicalType(v, ns, kpt); + if (kpt.GetNullable()) + { + keyTypeArgs = {{keyType, keyTypeArgs}}; + keyType = keyType->IsImmutable() ? &LayoutLiteral::ImmutableNullable : &LayoutLiteral::Nullable; + } + + auto [valueType, valueTypeArgs] = LogicalToPhysicalType(v, ns, vpt); + if (vpt.GetNullable()) + { + valueTypeArgs = {{valueType, valueTypeArgs}}; + valueType = valueType->IsImmutable() ? &LayoutLiteral::ImmutableNullable : &LayoutLiteral::Nullable; + } + + TypeArgumentList typeArgs{{{keyType, keyTypeArgs}, {valueType, valueTypeArgs}}}; + return {immutable ? &LayoutLiteral::ImmutableTypedMap : &LayoutLiteral::TypedMap, typeArgs}; + } + } + + // TODO(283638): implement sparse map. + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Unknown property type: %u", + logicalType.GetType())); + } + case TypeKind::Tuple: + { + cdb_core::Contract::Invariant(logicalType.GetKind() == PropertyKind::Tuple); + const TuplePropertyType& tp = static_cast(logicalType); + tla::vector args{}; + args.reserve(tp.GetItems().size()); + for (const auto& item : tp.GetItems()) + { + auto [itemType, itemTypeArgs] = LogicalToPhysicalType(v, ns, *item); + if (item->GetNullable()) + { + itemTypeArgs = {{itemType, itemTypeArgs}}; + itemType = itemType->IsImmutable() ? &LayoutLiteral::ImmutableNullable : &LayoutLiteral::Nullable; + } + + args.emplace_back(itemType, itemTypeArgs); + } + + TypeArgumentList typeArgs{args}; + return {immutable ? &LayoutLiteral::ImmutableTypedTuple : &LayoutLiteral::TypedTuple, typeArgs}; + } + case TypeKind::Tagged: + { + cdb_core::Contract::Invariant(logicalType.GetKind() == PropertyKind::Tagged); + const TaggedPropertyType& tg = static_cast(logicalType); + if ((tg.GetItems().size() < TaggedPropertyType::MinTaggedArguments) || + (tg.GetItems().size() > TaggedPropertyType::MaxTaggedArguments)) + { + throw LayoutCompiler::LayoutCompilationException( + cdb_core::make_string("Invalid number of arguments in Tagged: %u <= %u <= %u", + TaggedPropertyType::MinTaggedArguments, + tg.GetItems().size(), + TaggedPropertyType::MaxTaggedArguments)); + } + + tla::vector tgArgs{}; + tgArgs.reserve(tg.GetItems().size() + 1); + tgArgs.emplace_back(&LayoutLiteral::UInt8); + for (const auto& item : tg.GetItems()) + { + auto [itemType, itemTypeArgs] = LogicalToPhysicalType(v, ns, *item); + if (item->GetNullable()) + { + itemTypeArgs = {{itemType, itemTypeArgs}}; + itemType = itemType->IsImmutable() ? &LayoutLiteral::ImmutableNullable : &LayoutLiteral::Nullable; + } + + tgArgs.emplace_back(itemType, itemTypeArgs); + } + + TypeArgumentList typeArgs{tgArgs}; + switch (tg.GetItems().size()) + { + case 1: + return {immutable ? &LayoutLiteral::ImmutableTagged : &LayoutLiteral::Tagged, typeArgs}; + case 2: + return {immutable ? &LayoutLiteral::ImmutableTagged2 : &LayoutLiteral::Tagged2, typeArgs}; + default: + throw LayoutCompiler::LayoutCompilationException("Unexpected tagged arity"); + } + } + case TypeKind::Schema: + { + cdb_core::Contract::Invariant(logicalType.GetKind() == PropertyKind::Udt); + const UdtPropertyType& up = static_cast(logicalType); + const auto& schemas = ns.GetSchemas(); + decltype(schemas.begin()) iter; + if (up.GetSchemaId() == SchemaId::Invalid()) + { + iter = std::find_if(schemas.begin(), schemas.end(), + [&up](const std::unique_ptr& s) { return s->GetName() == up.GetName(); }); + } + else + { + iter = std::find_if(schemas.begin(), schemas.end(), + [&up](const std::unique_ptr& s) { return s->GetSchemaId() == up.GetSchemaId(); }); + } + if (iter == schemas.end()) + { + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string( + "Cannot resolve schema reference: '%s:%d'", + up.GetName().data(), up.GetSchemaId().Id())); + } + + const Schema& udtSchema = **iter; + if (udtSchema.GetName() != up.GetName()) + { + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Ambiguous schema reference: '%s:%d'", + up.GetName().data(), up.GetSchemaId().Id())); + } + + TypeArgumentList typeArgs{udtSchema.GetSchemaId()}; + return {immutable ? &LayoutLiteral::ImmutableUDT : &LayoutLiteral::UDT, typeArgs}; + } + case TypeKind::Enum: + { + if (v < SchemaLanguageVersion::V2) + { + throw LayoutCompiler::LayoutCompilationException("Enums require SDL v2 or higher."); + } + + cdb_core::Contract::Invariant(logicalType.GetKind() == PropertyKind::Primitive); + const PrimitivePropertyType& ep = static_cast(logicalType); + const auto& enums = ns.GetEnums(); + decltype(enums.begin()) iter = std::find_if(enums.begin(), enums.end(), + [&ep](const std::unique_ptr& es) { return es->GetName() == ep.GetEnum(); }); + if (iter == enums.end()) + { + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string( + "Cannot resolve enum schema reference: '%s'", + ep.GetEnum().data())); + } + + const EnumSchema& enumSchema = **iter; + return {PrimitiveToPhysicalType(enumSchema.GetType()), {}}; + } + default: + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Unknown property type: %u", + logicalType.GetType())); + } + } + + static const LayoutType* PrimitiveToPhysicalType(TypeKind type) + { + switch (type) + { + case TypeKind::Null: + return &LayoutLiteral::Null; + case TypeKind::Boolean: + return &LayoutLiteral::Boolean; + case TypeKind::Int8: + return &LayoutLiteral::Int8; + case TypeKind::Int16: + return &LayoutLiteral::Int16; + case TypeKind::Int32: + return &LayoutLiteral::Int32; + case TypeKind::Int64: + return &LayoutLiteral::Int64; + case TypeKind::UInt8: + return &LayoutLiteral::UInt8; + case TypeKind::UInt16: + return &LayoutLiteral::UInt16; + case TypeKind::UInt32: + return &LayoutLiteral::UInt32; + case TypeKind::UInt64: + return &LayoutLiteral::UInt64; + case TypeKind::Float32: + return &LayoutLiteral::Float32; + case TypeKind::Float64: + return &LayoutLiteral::Float64; + case TypeKind::Float128: + return &LayoutLiteral::Float128; + case TypeKind::Decimal: + return &LayoutLiteral::Decimal; + case TypeKind::DateTime: + return &LayoutLiteral::DateTime; + case TypeKind::UnixDateTime: + return &LayoutLiteral::UnixDateTime; + case TypeKind::Guid: + return &LayoutLiteral::Guid; + case TypeKind::MongoDbObjectId: + return &LayoutLiteral::MongoDbObjectId; + case TypeKind::Utf8: + return &LayoutLiteral::Utf8; + case TypeKind::Binary: + return &LayoutLiteral::Binary; + case TypeKind::VarInt: + return &LayoutLiteral::VarInt; + case TypeKind::VarUInt: + return &LayoutLiteral::VarUInt; + default: + throw LayoutCompiler::LayoutCompilationException(cdb_core::make_string("Unknown property type: %u", type)); + } + } +} diff --git a/src/Serialization/HybridRow.Native/LayoutCompiler.h b/src/Serialization/HybridRow.Native/LayoutCompiler.h new file mode 100644 index 0000000..c66969d --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutCompiler.h @@ -0,0 +1,32 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_hr +{ + class Schema; + class Namespace; + class Layout; + + /// Converts a logical schema into a physical layout. + class LayoutCompiler final + { + public: + LayoutCompiler() = delete; // static class + + /// Compiles a logical schema into a physical layout that can be used to read and write rows. + /// The namespace within which is defined. + /// The logical schema to produce a layout for. + /// The layout for the schema. + static std::unique_ptr Compile(const Namespace& ns, const Schema& schema) noexcept(false); + + struct LayoutCompilationException : std::runtime_error + { + LayoutCompilationException(const std::string& message) noexcept : std::runtime_error(message) { } + LayoutCompilationException(const char* message) noexcept : std::runtime_error(message) { } + }; + }; +} diff --git a/src/Serialization/HybridRow.Native/LayoutResolver.h b/src/Serialization/HybridRow.Native/LayoutResolver.h new file mode 100644 index 0000000..30fb380 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutResolver.h @@ -0,0 +1,25 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "SchemaId.h" +#include "Layout.h" + +namespace cdb_hr +{ +struct LayoutResolver +{ + virtual ~LayoutResolver() = default; + LayoutResolver(const LayoutResolver& other) noexcept = delete; + LayoutResolver(LayoutResolver&& other) noexcept = delete; + LayoutResolver& operator=(const LayoutResolver& other) noexcept = delete; + LayoutResolver& operator=(LayoutResolver&& other) noexcept = delete; + + [[nodiscard]] + virtual const Layout& Resolve(SchemaId schemaId) const noexcept = 0; + +protected: + LayoutResolver() noexcept = default; +}; +} diff --git a/src/Serialization/HybridRow.Native/LayoutResolverNamespace.cpp b/src/Serialization/HybridRow.Native/LayoutResolverNamespace.cpp new file mode 100644 index 0000000..78b1b90 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutResolverNamespace.cpp @@ -0,0 +1,67 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "LayoutResolverNamespace.h" + +// ReSharper disable CppClangTidyCppcoreguidelinesProTypeStaticCastDowncast +namespace cdb_hr +{ + class LayoutBuilder; + + LayoutResolverNamespace::LayoutResolverNamespace( + std::unique_ptr schemaNamespace, + std::unique_ptr parent) noexcept : + m_schemaNamespace{std::move(schemaNamespace)}, + m_parent{std::move(parent)}, + m_lock{}, + m_layouts{}, + m_layoutCache{} { } + + const Layout& LayoutResolverNamespace::Resolve(SchemaId schemaId) const noexcept + { + try + { + { + std::shared_lock lock(m_lock); + auto iter = m_layoutCache.find(schemaId.Id()); + if (iter != m_layoutCache.end()) + { + return iter->second; + } + } + + for (auto& p : m_schemaNamespace->GetSchemas()) + { + Schema& s = *p; + if (s.GetSchemaId() == schemaId) + { + auto layout = s.Compile(*m_schemaNamespace); + std::unique_lock lock(m_lock); + auto [iter, added] = m_layoutCache.try_emplace(schemaId.Id(), *layout); + if (added) + { + m_layouts.emplace_back(std::move(layout)); + } + return iter->second; + } + } + + if (m_parent != nullptr) + { + const Layout& layout = m_parent->Resolve(schemaId); + std::unique_lock lock(m_lock); + auto [iter, _] = m_layoutCache.try_emplace(schemaId.Id(), layout); + return iter->second; + } + + cdb_core::Contract::Fail(cdb_core::make_string("Failed to resolve schema %d", schemaId.Id())); + } + catch (...) + { + cdb_core::Contract::Fail(cdb_core::make_string("Failed to acquire mutex while resolving schema %d", + schemaId.Id())); + } + } +} diff --git a/src/Serialization/HybridRow.Native/LayoutResolverNamespace.h b/src/Serialization/HybridRow.Native/LayoutResolverNamespace.h new file mode 100644 index 0000000..ace4913 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutResolverNamespace.h @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "SchemaId.h" +#include "Layout.h" +#include "LayoutResolver.h" +#include "Namespace.h" + +namespace cdb_hr +{ + /// + /// An implementation of which dynamically compiles schema from + /// a . + /// + /// + /// This resolver assumes that within the have + /// their properly populated. The resolver caches compiled schema. + /// + /// All members of this class are multi-thread safe. + class LayoutResolverNamespace final : public LayoutResolver + { + public: + LayoutResolverNamespace(std::unique_ptr schemaNamespace, + std::unique_ptr parent = {}) noexcept; + ~LayoutResolverNamespace() override = default; + LayoutResolverNamespace(const LayoutResolverNamespace& other) noexcept = delete; + LayoutResolverNamespace(LayoutResolverNamespace&& other) noexcept = delete; + LayoutResolverNamespace& operator=(const LayoutResolverNamespace& other) noexcept = delete; + LayoutResolverNamespace& operator=(LayoutResolverNamespace&& other) noexcept = delete; + + /// Returns a borrowed pointer to the namespace managed by this instance. + [[nodiscard]] const Namespace* GetNamespace() const noexcept { return m_schemaNamespace.get(); }; + /// Returns a borrowed pointer to the compiled layout matching the given schema id. + [[nodiscard]] virtual const Layout& Resolve(SchemaId schemaId) const noexcept override; + + private: + std::unique_ptr m_schemaNamespace; + std::unique_ptr m_parent; + mutable std::shared_mutex m_lock; + mutable std::vector> m_layouts; + mutable std::map m_layoutCache; + }; +} diff --git a/src/Serialization/HybridRow.Native/LayoutType.cpp b/src/Serialization/HybridRow.Native/LayoutType.cpp new file mode 100644 index 0000000..7cce6c2 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutType.cpp @@ -0,0 +1,2133 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "TypeArgument.h" +#include "TypeArgumentList.h" +#include "LayoutType.h" + +#include "LayoutCodeTraits.h" +#include "RowBuffer.h" +#include "RowCursor.h" + +namespace cdb_hr +{ + size_t LayoutType::GetHashCode() const noexcept + { + static_assert(cdb_core::is_hashable_v); + + return std::hash{}(this); + } + + /// Helper for preparing the delete of a sparse field. + /// The row to delete from. + /// The parent edit containing the field to delete. + /// The expected type of the field. + /// Success if the delete is permitted, the error code otherwise. + Result LayoutType::PrepareSparseDelete(const RowBuffer& b, const RowCursor& edit, LayoutCode code) noexcept + { + if (edit.m_scopeType->IsFixedArity()) + { + return Result::TypeConstraint; + } + + if (edit.m_immutable) + { + return Result::InsufficientPermissions; + } + + if (edit.m_exists && LayoutCodeTraits::Canonicalize(edit.m_cellType->GetLayoutCode()) != code) + { + return Result::TypeMismatch; + } + + return Result::Success; + } + + Result LayoutType::PrepareSparseWrite(RowBuffer& b, RowCursor& edit, const TypeArgument& typeArg, + UpdateOptions options) noexcept + { + if (edit.m_immutable || (edit.m_scopeType->IsUniqueScope() && !edit.m_deferUniqueIndex)) + { + return Result::InsufficientPermissions; + } + + if (edit.m_scopeType->IsFixedArity() && !(edit.m_scopeType->IsLayoutNullable())) + { + if ((edit.m_index < edit.m_scopeTypeArgs.GetCount()) && typeArg != edit.m_scopeTypeArgs[edit.m_index]) + { + return Result::TypeConstraint; + } + } + else if (edit.m_scopeType->IsLayoutTypedMap()) + { + if (!((typeArg.GetType()->IsLayoutTypedTuple()) && typeArg.GetTypeArgs() == edit.m_scopeTypeArgs)) + { + return Result::TypeConstraint; + } + } + else if (edit.m_scopeType->IsTypedScope() && typeArg != edit.m_scopeTypeArgs[0]) + { + return Result::TypeConstraint; + } + + if ((options == UpdateOptions::InsertAt) && edit.m_scopeType->IsFixedArity()) + { + return Result::TypeConstraint; + } + + if ((options == UpdateOptions::InsertAt) && !edit.m_scopeType->IsFixedArity()) + { + edit.m_exists = false; // InsertAt never overwrites an existing item. + } + + if ((options == UpdateOptions::Update) && (!edit.m_exists)) + { + return Result::NotFound; + } + + if ((options == UpdateOptions::Insert) && edit.m_exists) + { + return Result::Exists; + } + + return Result::Success; + } + + Result LayoutType::PrepareSparseRead(const RowBuffer& b, const RowCursor& edit, LayoutCode code) noexcept + { + if (!edit.m_exists) + { + return Result::NotFound; + } + + if (LayoutCodeTraits::Canonicalize(edit.m_cellType->GetLayoutCode()) != code) + { + return Result::TypeMismatch; + } + + return Result::Success; + } + + std::tuple LayoutType::PrepareSparseMove(RowBuffer& b, const RowCursor& destinationScope, + const LayoutScope* destinationCode, + const TypeArgument& elementType, RowCursor& srcEdit, + UpdateOptions options) noexcept + { + cdb_core::Contract::Requires(destinationScope.m_scopeType == destinationCode); + cdb_core::Contract::Requires(destinationScope.m_index == 0, "Can only insert into a edit at the root"); + + // Prepare the delete of the source. + Result result = PrepareSparseDelete(b, srcEdit, elementType.GetType()->GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + if (!srcEdit.m_exists) + { + return {Result::NotFound, {}}; + } + + if (destinationScope.m_immutable) + { + b.DeleteSparse(srcEdit); + return {Result::InsufficientPermissions, {}}; + } + + if (srcEdit.m_cellTypeArgs != elementType.GetTypeArgs()) + { + b.DeleteSparse(srcEdit); + return {Result::TypeConstraint, {}}; + } + + if (options == UpdateOptions::InsertAt) + { + b.DeleteSparse(srcEdit); + return {Result::TypeConstraint, {}}; + } + + // Prepare the insertion at the destination. + RowCursor dstEdit = b.PrepareSparseMove(destinationScope, srcEdit); + if ((options == UpdateOptions::Update) && (!dstEdit.m_exists)) + { + b.DeleteSparse(srcEdit); + return {Result::NotFound, {}}; + } + + if ((options == UpdateOptions::Insert) && dstEdit.m_exists) + { + b.DeleteSparse(srcEdit); + return {Result::Exists, {}}; + } + + return {Result::Success, dstEdit}; + } + + uint32_t LayoutType::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + return sizeof(LayoutCode); + } + + uint32_t LayoutType::WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept + { + row.WriteSparseTypeCode(offset, GetLayoutCode()); + return sizeof(LayoutCode); + } + + std::tuple LayoutType::ReadTypeArgument(const RowBuffer& row, uint32_t offset) noexcept + { + const LayoutType* itemCode = row.ReadSparseTypeCode(offset); + auto [itemTypeArgs, argsLenInBytes] = itemCode->ReadTypeArgumentList(row, offset + sizeof(LayoutCode)); + uint32_t lenInBytes = sizeof(LayoutCode) + argsLenInBytes; + return {TypeArgument(itemCode, itemTypeArgs), lenInBytes}; + } + + std::tuple LayoutType::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + return {{}, {}}; + } + + Result ScalarLayoutTypeBase::HasValue(const RowBuffer& b, const RowCursor& scope, const LayoutColumn& col) noexcept + { + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return Result::NotFound; + } + + return Result::Success; + } + + Result ScalarLayoutTypeBase::DeleteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + if (col.GetNullBit().IsInvalid()) + { + // Cannot delete a non-nullable fixed column. + return Result::TypeMismatch; + } + + b.UnsetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + /// Delete an existing value. + /// + /// If a value exists, then it is removed. The remainder of the row is resized to accomodate + /// a decrease in required space. If no value exists this operation is a no-op. + /// + Result ScalarLayoutTypeBase::DeleteVariable(RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + bool exists = b.ReadBit(scope.m_start, col.GetNullBit()); + if (exists) + { + int varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + b.DeleteVariable(varOffset, IsVarint()); + b.UnsetBit(scope.m_start, col.GetNullBit()); + } + + return Result::Success; + } + + /// Delete an existing value. + /// + /// If a value exists, then it is removed. The remainder of the row is resized to accomodate + /// a decrease in required space. If no value exists this operation is a no-op. + /// + Result ScalarLayoutTypeBase::DeleteSparse(RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = LayoutType::PrepareSparseDelete(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return result; + } + + b.DeleteSparse(edit); + return Result::Success; + } + + Result LayoutInt8::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteInt8(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutInt8::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadInt8(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutInt8::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseInt8(edit, value, options); + return Result::Success; + } + + std::tuple LayoutInt8::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseInt8(edit); + return {Result::Success, value}; + } + + Result LayoutInt16::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteInt16(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutInt16::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadInt16(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutInt16::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseInt16(edit, value, options); + return Result::Success; + } + + std::tuple LayoutInt16::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseInt16(edit); + return {Result::Success, value}; + } + + Result LayoutInt32::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteInt32(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutInt32::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadInt32(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutInt32::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseInt32(edit, value, options); + return Result::Success; + } + + std::tuple LayoutInt32::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseInt32(edit); + return {Result::Success, value}; + } + + Result LayoutInt64::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteInt64(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutInt64::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadInt64(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutInt64::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseInt64(edit, value, options); + return Result::Success; + } + + std::tuple LayoutInt64::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseInt64(edit); + return {Result::Success, value}; + } + + Result LayoutUInt8::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteUInt8(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutUInt8::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadUInt8(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutUInt8::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseUInt8(edit, value, options); + return Result::Success; + } + + std::tuple LayoutUInt8::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseUInt8(edit); + return {Result::Success, value}; + } + + Result LayoutUInt16::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteUInt16(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutUInt16::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadUInt16(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutUInt16::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseUInt16(edit, value, options); + return Result::Success; + } + + std::tuple LayoutUInt16::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseUInt16(edit); + return {Result::Success, value}; + } + + Result LayoutUInt32::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteUInt32(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutUInt32::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadUInt32(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutUInt32::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseUInt32(edit, value, options); + return Result::Success; + } + + std::tuple LayoutUInt32::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseUInt32(edit); + return {Result::Success, value}; + } + + Result LayoutUInt64::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteUInt64(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutUInt64::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadUInt64(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutUInt64::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseUInt64(edit, value, options); + return Result::Success; + } + + std::tuple LayoutUInt64::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseUInt64(edit); + return {Result::Success, value}; + } + + Result LayoutFloat32::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteFloat32(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutFloat32::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadFloat32(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutFloat32::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseFloat32(edit, value, options); + return Result::Success; + } + + std::tuple LayoutFloat32::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseFloat32(edit); + return {Result::Success, value}; + } + + Result LayoutFloat64::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteFloat64(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutFloat64::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadFloat64(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutFloat64::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseFloat64(edit, value, options); + return Result::Success; + } + + std::tuple LayoutFloat64::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseFloat64(edit); + return {Result::Success, value}; + } + + Result LayoutFloat128::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteFloat128(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutFloat128::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadFloat128(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutFloat128::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseFloat128(edit, value, options); + return Result::Success; + } + + std::tuple LayoutFloat128::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseFloat128(edit); + return {Result::Success, value}; + } + + Result LayoutDecimal::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteDecimal(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutDecimal::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadDecimal(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutDecimal::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseDecimal(edit, value, options); + return Result::Success; + } + + std::tuple LayoutDecimal::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseDecimal(edit); + return {Result::Success, value}; + } + + Result LayoutBoolean::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const bool& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + if (value) + { + b.SetBit(scope.m_start, col.GetBoolBit()); + } + else + { + b.UnsetBit(scope.m_start, col.GetBoolBit()); + } + + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutBoolean::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, false}; + } + + bool value = b.ReadBit(scope.m_start, col.GetBoolBit()); + return {Result::Success, value}; + } + + Result LayoutBoolean::WriteSparse(RowBuffer& b, RowCursor& edit, const bool& value, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseBool(edit, value, options); + return Result::Success; + } + + std::tuple LayoutBoolean::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, false}; + } + + bool value = b.ReadSparseBool(edit); + return {Result::Success, value}; + } + + Result LayoutNull::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutNull::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + return {Result::Success, {}}; + } + + Result LayoutNull::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseNull(edit, value, options); + return Result::Success; + } + + std::tuple LayoutNull::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseNull(edit); + return {Result::Success, value}; + } + + Result LayoutDateTime::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteDateTime(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutDateTime::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadDateTime(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutDateTime::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseDateTime(edit, value, options); + return Result::Success; + } + + std::tuple LayoutDateTime::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseDateTime(edit); + return {Result::Success, value}; + } + + Result LayoutUnixDateTime::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteUnixDateTime(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutUnixDateTime::ReadFixed( + const RowBuffer& b, const RowCursor& scope, const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadUnixDateTime(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutUnixDateTime::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseUnixDateTime(edit, value, options); + return Result::Success; + } + + std::tuple LayoutUnixDateTime::ReadSparse( + const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseUnixDateTime(edit); + return {Result::Success, value}; + } + + Result LayoutGuid::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteGuid(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutGuid::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadGuid(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutGuid::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseGuid(edit, value, options); + return Result::Success; + } + + std::tuple LayoutGuid::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseGuid(edit); + return {Result::Success, value}; + } + + Result LayoutMongoDbObjectId::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteMongoDbObjectId(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutMongoDbObjectId::ReadFixed( + const RowBuffer& b, const RowCursor& scope, const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadMongoDbObjectId(scope.m_start + col.GetOffset()); + return {Result::Success, value}; + } + + Result LayoutMongoDbObjectId::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseMongoDbObjectId(edit, value, options); + return Result::Success; + } + + std::tuple LayoutMongoDbObjectId::ReadSparse( + const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseMongoDbObjectId(edit); + return {Result::Success, value}; + } + + Result LayoutUtf8::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + cdb_core::Contract::Requires(static_cast(value.size()) == col.GetSize()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteFixedString(scope.m_start + col.GetOffset(), value); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutUtf8::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadFixedString(scope.m_start + col.GetOffset(), col.GetSize()); + return {Result::Success, value}; + } + + Result LayoutUtf8::WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const LayoutUtf8::T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + uint32_t length = static_cast(value.size()); + if ((col.GetSize() > 0) && (length > col.GetSize())) + { + return Result::TooBig; + } + + bool exists = b.ReadBit(scope.m_start, col.GetNullBit()); + int varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + int32_t shift = b.WriteVariableString(varOffset, value, exists); + b.SetBit(scope.m_start, col.GetNullBit()); + scope.m_metaOffset += shift; + scope.m_valueOffset += shift; + return Result::Success; + } + + std::tuple LayoutUtf8::ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + int varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + T value = b.ReadVariableString(varOffset); + return {Result::Success, value}; + } + + Result LayoutUtf8::WriteSparse(RowBuffer& b, RowCursor& edit, const LayoutUtf8::T& value, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseString(edit, value, options); + return Result::Success; + } + + std::tuple LayoutUtf8::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseString(edit); + return {Result::Success, value}; + } + + Result LayoutBinary::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + cdb_core::Contract::Requires(value.Length() == col.GetSize()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + b.WriteFixedBinary(scope.m_start + col.GetOffset(), value, col.GetSize()); + b.SetBit(scope.m_start, col.GetNullBit()); + return Result::Success; + } + + std::tuple LayoutBinary::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + T value = b.ReadFixedBinary(scope.m_start + col.GetOffset(), col.GetSize()); + return {Result::Success, value}; + } + + Result LayoutBinary::WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + uint32_t length = value.Length(); + if ((col.GetSize() > 0) && (length > col.GetSize())) + { + return Result::TooBig; + } + + bool exists = b.ReadBit(scope.m_start, col.GetNullBit()); + uint32_t varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + int32_t shift = b.WriteVariableBinary(varOffset, value, exists); + b.SetBit(scope.m_start, col.GetNullBit()); + scope.m_metaOffset += shift; + scope.m_valueOffset += shift; + return Result::Success; + } + + std::tuple LayoutBinary::ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + uint32_t varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + T value = b.ReadVariableBinary(varOffset); + return {Result::Success, value}; + } + + Result LayoutBinary::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseBinary(edit, value, options); + return Result::Success; + } + + std::tuple LayoutBinary::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseBinary(edit); + return {Result::Success, value}; + } + + Result LayoutVarInt::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Fail("Not Implemented"); + } + + std::tuple LayoutVarInt::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Fail("Not Implemented"); + } + + Result LayoutVarInt::WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + bool exists = b.ReadBit(scope.m_start, col.GetNullBit()); + int varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + int32_t shift = b.WriteVariableInt(varOffset, value, exists); + b.SetBit(scope.m_start, col.GetNullBit()); + scope.m_metaOffset += shift; + scope.m_valueOffset += shift; + return Result::Success; + } + + std::tuple LayoutVarInt::ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + int varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + T value = b.ReadVariableInt(varOffset); + return {Result::Success, value}; + } + + Result LayoutVarInt::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseVarInt(edit, value, options); + return Result::Success; + } + + std::tuple LayoutVarInt::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseVarInt(edit); + return {Result::Success, value}; + } + + Result LayoutVarUInt::WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Fail("Not Implemented"); + } + + std::tuple LayoutVarUInt::ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Fail("Not Implemented"); + } + + Result LayoutVarUInt::WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (scope.m_immutable) + { + return Result::InsufficientPermissions; + } + + bool exists = b.ReadBit(scope.m_start, col.GetNullBit()); + int varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + int32_t shift = b.WriteVariableUInt(varOffset, value, exists); + b.SetBit(scope.m_start, col.GetNullBit()); + scope.m_metaOffset += shift; + scope.m_valueOffset += shift; + return Result::Success; + } + + std::tuple LayoutVarUInt::ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUDT()); + if (!b.ReadBit(scope.m_start, col.GetNullBit())) + { + return {Result::NotFound, {}}; + } + + int varOffset = b.ComputeVariableValueOffset(*scope.m_layout, scope.m_start, col.GetOffset()); + T value = b.ReadVariableUInt(varOffset); + return {Result::Success, value}; + } + + Result LayoutVarUInt::WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, edit, GetTypeArg(), options); + if (result != Result::Success) + { + return result; + } + + b.WriteSparseVarUInt(edit, value, options); + return Result::Success; + } + + std::tuple LayoutVarUInt::ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, {}}; + } + + T value = b.ReadSparseVarUInt(edit); + return {Result::Success, value}; + } + + std::tuple LayoutScope::ReadScope(const RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = LayoutType::PrepareSparseRead(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return {result, RowCursor{}}; + } + + RowCursor value = b.SparseIteratorReadScope(edit, + IsImmutable() || edit.m_immutable || edit.m_scopeType->IsUniqueScope()); + return {Result::Success, std::move(value)}; + } + + Result LayoutScope::DeleteScope(RowBuffer& b, RowCursor& edit) const noexcept + { + Result result = LayoutType::PrepareSparseDelete(b, edit, GetLayoutCode()); + if (result != Result::Success) + { + return result; + } + + b.DeleteSparse(edit); + return Result::Success; + } + + void LayoutScope::ReadSparsePath(const RowBuffer& row, RowCursor& edit) const noexcept + { + auto [token, pathLenInBytes, pathOffset] = row.ReadSparsePathLen(*edit.m_layout, edit.m_valueOffset); + edit.m_pathToken = token; + edit.m_pathOffset = pathOffset; + edit.m_valueOffset += pathLenInBytes; + } + + std::tuple LayoutEndScope::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + cdb_core::Contract::Fail("Cannot write an EndScope directly"); + } + + std::tuple LayoutObject::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteSparseObject(scope, this, options)}; + } + + std::tuple LayoutUDT::WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + const Layout& udt = b.GetResolver()->Resolve(typeArgs.GetSchemaId()); + Result result = LayoutType::PrepareSparseWrite(b, scope, TypeArgument{this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteSparseUDT(scope, this, udt, options)}; + } + + uint32_t LayoutUDT::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + return sizeof(LayoutCode) + sizeof(SchemaId); + } + + uint32_t LayoutUDT::WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept + { + row.WriteSparseTypeCode(offset, GetLayoutCode()); + row.WriteSchemaId(offset + sizeof(LayoutCode), value.GetSchemaId()); + return sizeof(LayoutCode) + sizeof(SchemaId); + } + + /// [TypeArgumentList typeArgs, uint32_t lenInBytes] + std::tuple LayoutUDT::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + SchemaId schemaId = row.ReadSchemaId(offset); + return {TypeArgumentList(schemaId), static_cast(sizeof(SchemaId))}; + } + + void LayoutIndexedScope::ReadSparsePath(const RowBuffer& row, RowCursor& edit) const noexcept + { + edit.m_pathToken = 0; + edit.m_pathOffset = 0; + } + + std::tuple LayoutArray::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteSparseArray(scope, this, options)}; + } + + std::tuple LayoutTypedArray::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteTypedArray(scope, this, typeArgs, options)}; + } + + bool LayoutTypedArray::HasImplicitTypeCode(const RowCursor& edit) const noexcept + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() == 1); + return !LayoutCodeTraits::AlwaysRequiresTypeCode(edit.m_scopeTypeArgs[0].GetType()->GetLayoutCode()); + } + + void LayoutTypedArray::SetImplicitTypeCode(RowCursor& edit) const noexcept + { + edit.m_cellType = edit.m_scopeTypeArgs[0].GetType(); + edit.m_cellTypeArgs = edit.m_scopeTypeArgs[0].GetTypeArgs(); + } + + uint32_t LayoutTypedArray::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 1); + return sizeof(LayoutCode) + value[0].GetType()->CountTypeArgument(value[0].GetTypeArgs()); + } + + uint32_t LayoutTypedArray::WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 1); + row.WriteSparseTypeCode(offset, GetLayoutCode()); + uint32_t lenInBytes = sizeof(LayoutCode); + lenInBytes += value[0].GetType()->WriteTypeArgument(row, offset + lenInBytes, value[0].GetTypeArgs()); + return lenInBytes; + } + + std::tuple LayoutTypedArray::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + auto [typeArg, lenInBytes] = ReadTypeArgument(row, offset); + return {TypeArgumentList{{typeArg}}, lenInBytes}; + } + + std::tuple LayoutTuple::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteSparseTuple(scope, this, typeArgs, options)}; + } + + uint32_t LayoutTuple::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + uint32_t lenInBytes = sizeof(LayoutCode); + lenInBytes += RowBuffer::Count7BitEncodedUInt(static_cast(value.GetCount())); + for (const TypeArgument& arg : value) + { + lenInBytes += arg.GetType()->CountTypeArgument(arg.GetTypeArgs()); + } + + return lenInBytes; + } + + uint32_t LayoutTuple::WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept + { + row.WriteSparseTypeCode(offset, GetLayoutCode()); + uint32_t lenInBytes = sizeof(LayoutCode); + lenInBytes += row.Write7BitEncodedUInt(offset + lenInBytes, static_cast(value.GetCount())); + for (const TypeArgument& arg : value) + { + lenInBytes += arg.GetType()->WriteTypeArgument(row, offset + lenInBytes, arg.GetTypeArgs()); + } + + return lenInBytes; + } + + std::tuple LayoutTuple::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + auto [numTypeArgs, lenInBytes] = row.Read7BitEncodedUInt(offset); + tla::vector retval; + retval.resize(numTypeArgs); + for (uint64_t i = 0; i < numTypeArgs; i++) + { + uint32_t itemLenInBytes; + std::tie(retval[i], itemLenInBytes) = ReadTypeArgument(row, offset + lenInBytes); + lenInBytes += itemLenInBytes; + } + return {std::move(retval), lenInBytes}; + } + + std::tuple LayoutTypedTuple::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteTypedTuple(scope, this, typeArgs, options)}; + } + + bool LayoutTypedTuple::HasImplicitTypeCode(const RowCursor& edit) const noexcept + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() > edit.m_index); + return !LayoutCodeTraits::AlwaysRequiresTypeCode(edit.m_scopeTypeArgs[edit.m_index].GetType()->GetLayoutCode()); + } + + void LayoutTypedTuple::SetImplicitTypeCode(RowCursor& edit) const noexcept + { + edit.m_cellType = edit.m_scopeTypeArgs[edit.m_index].GetType(); + edit.m_cellTypeArgs = edit.m_scopeTypeArgs[edit.m_index].GetTypeArgs(); + } + + uint32_t LayoutTypedTuple::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + uint32_t lenInBytes = sizeof(LayoutCode); + lenInBytes += RowBuffer::Count7BitEncodedUInt(static_cast(value.GetCount())); + for (const TypeArgument& arg : value) + { + lenInBytes += arg.GetType()->CountTypeArgument(arg.GetTypeArgs()); + } + + return lenInBytes; + } + + uint32_t LayoutTypedTuple::WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept + { + row.WriteSparseTypeCode(offset, GetLayoutCode()); + uint32_t lenInBytes = sizeof(LayoutCode); + lenInBytes += row.Write7BitEncodedUInt(offset + lenInBytes, static_cast(value.GetCount())); + for (const TypeArgument& arg : value) + { + lenInBytes += arg.GetType()->WriteTypeArgument(row, offset + lenInBytes, arg.GetTypeArgs()); + } + + return lenInBytes; + } + + std::tuple LayoutTypedTuple::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + auto [numTypeArgs, lenInBytes] = row.Read7BitEncodedUInt(offset); + tla::vector retval; + retval.resize(numTypeArgs); + for (uint64_t i = 0; i < numTypeArgs; i++) + { + uint32_t itemLenInBytes; + std::tie(retval[i], itemLenInBytes) = ReadTypeArgument(row, offset + lenInBytes); + lenInBytes += itemLenInBytes; + } + return {std::move(retval), lenInBytes}; + } + + std::tuple LayoutTagged::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteTypedTuple(scope, this, typeArgs, options)}; + } + + bool LayoutTagged::HasImplicitTypeCode(const RowCursor& edit) const noexcept + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() > edit.m_index); + return !LayoutCodeTraits::AlwaysRequiresTypeCode(edit.m_scopeTypeArgs[edit.m_index].GetType()->GetLayoutCode()); + } + + void LayoutTagged::SetImplicitTypeCode(RowCursor& edit) const noexcept + { + edit.m_cellType = edit.m_scopeTypeArgs[edit.m_index].GetType(); + edit.m_cellTypeArgs = edit.m_scopeTypeArgs[edit.m_index].GetTypeArgs(); + } + + uint32_t LayoutTagged::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 2); + return sizeof(LayoutCode) + value[1].GetType()->CountTypeArgument(value[1].GetTypeArgs()); + } + + uint32_t LayoutTagged::WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 2); + row.WriteSparseTypeCode(offset, GetLayoutCode()); + uint32_t lenInBytes = sizeof(LayoutCode); + lenInBytes += value[1].GetType()->WriteTypeArgument(row, offset + lenInBytes, value[1].GetTypeArgs()); + return lenInBytes; + } + + std::tuple LayoutTagged::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + tla::vector retval; + retval.resize(2); + retval[0] = TypeArgument{&LayoutLiteral::UInt8, {}}; + uint32_t lenInBytes; + std::tie(retval[1], lenInBytes) = ReadTypeArgument(row, offset); + return {std::move(retval), lenInBytes}; + } + + std::tuple LayoutTagged2::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteTypedTuple(scope, this, typeArgs, options)}; + } + + bool LayoutTagged2::HasImplicitTypeCode(const RowCursor& edit) const noexcept + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() > edit.m_index); + return !LayoutCodeTraits::AlwaysRequiresTypeCode(edit.m_scopeTypeArgs[edit.m_index].GetType()->GetLayoutCode()); + } + + void LayoutTagged2::SetImplicitTypeCode(RowCursor& edit) const noexcept + { + edit.m_cellType = edit.m_scopeTypeArgs[edit.m_index].GetType(); + edit.m_cellTypeArgs = edit.m_scopeTypeArgs[edit.m_index].GetTypeArgs(); + } + + uint32_t LayoutTagged2::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 3); + uint32_t lenInBytes = sizeof(LayoutCode); + for (size_t i = 1; i < value.GetCount(); i++) + { + TypeArgument arg = value[i]; + lenInBytes += arg.GetType()->CountTypeArgument(arg.GetTypeArgs()); + } + + return lenInBytes; + } + + uint32_t LayoutTagged2::WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 3); + row.WriteSparseTypeCode(offset, GetLayoutCode()); + uint32_t lenInBytes = sizeof(LayoutCode); + for (size_t i = 1; i < value.GetCount(); i++) + { + TypeArgument arg = value[i]; + lenInBytes += arg.GetType()->WriteTypeArgument(row, offset + lenInBytes, arg.GetTypeArgs()); + } + + return lenInBytes; + } + + std::tuple LayoutTagged2::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + uint32_t lenInBytes = 0; + tla::vector retval; + retval.resize(3); + retval[0] = TypeArgument{&LayoutLiteral::UInt8, {}}; + for (size_t i = 1; i < 3; i++) + { + uint32_t itemLenInBytes; + std::tie(retval[i], itemLenInBytes) = ReadTypeArgument(row, offset + lenInBytes); + lenInBytes += itemLenInBytes; + } + + return {retval, lenInBytes}; + } + + std::tuple LayoutNullable::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + bool hasValue, UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteNullable(scope, this, typeArgs, options, hasValue)}; + } + + std::tuple LayoutNullable::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + return WriteScope(b, scope, typeArgs, true, options); + } + + Result LayoutNullable::HasValue(const RowBuffer& b, const RowCursor& scope) noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsLayoutNullable()); + cdb_core::Contract::Assert(scope.m_index == 1 || scope.m_index == 2, "Nullable scopes always point at the value"); + cdb_core::Contract::Assert(scope.m_scopeTypeArgs.GetCount() == 1); + bool hasValue = b.ReadInt8(scope.m_start) != 0; + return hasValue ? Result::Success : Result::NotFound; + } + + bool LayoutNullable::HasImplicitTypeCode(const RowCursor& edit) const noexcept + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() == 1); + cdb_core::Contract::Assert(edit.m_index == 1); + return !LayoutCodeTraits::AlwaysRequiresTypeCode(edit.m_scopeTypeArgs[0].GetType()->GetLayoutCode()); + } + + void LayoutNullable::SetImplicitTypeCode(RowCursor& edit) const noexcept + { + cdb_core::Contract::Assert(edit.m_index == 1); + edit.m_cellType = edit.m_scopeTypeArgs[0].GetType(); + edit.m_cellTypeArgs = edit.m_scopeTypeArgs[0].GetTypeArgs(); + } + + uint32_t LayoutNullable::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 1); + return sizeof(LayoutCode) + value[0].GetType()->CountTypeArgument(value[0].GetTypeArgs()); + } + + uint32_t LayoutNullable::WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 1); + row.WriteSparseTypeCode(offset, GetLayoutCode()); + uint32_t lenInBytes = sizeof(LayoutCode); + lenInBytes += value[0].GetType()->WriteTypeArgument(row, offset + lenInBytes, value[0].GetTypeArgs()); + return lenInBytes; + } + + std::tuple LayoutNullable::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + auto [typeArg, lenInBytes] = ReadTypeArgument(row, offset); + return {TypeArgumentList{{typeArg}}, lenInBytes}; + } + + Result LayoutUniqueScope::MoveField(RowBuffer& b, RowCursor& destinationScope, RowCursor& sourceEdit, + UpdateOptions options) const noexcept + { + auto [result, dstEdit] = PrepareSparseMove(b, destinationScope, this, FieldType(destinationScope), sourceEdit, + options); + if (result != Result::Success) + { + return result; + } + + // Perform the move. + b.TypedCollectionMoveField(dstEdit, sourceEdit, static_cast(options)); + + // TODO: it would be "better" if the destinationScope were updated to point to the + // highest item seen. Then we would avoid the maximum reparse. + destinationScope.m_count = dstEdit.m_count; + return Result::Success; + } + + std::tuple LayoutUniqueScope::Find(RowBuffer& b, const RowCursor& scope, + RowCursor& patternScope) const noexcept + { + auto [result, value] = PrepareSparseMove(b, scope, this, FieldType(scope), patternScope, UpdateOptions::Update); + if (result != Result::Success) + { + return {result, {}}; + } + + // Check if the search found the result. + b.DeleteSparse(patternScope); + + return {Result::Success, value}; + } + + TypeArgument LayoutTypedSet::FieldType(const RowCursor& scope) const noexcept + { + return scope.m_scopeTypeArgs[0]; + } + + std::tuple LayoutTypedSet::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteTypedSet(scope, this, typeArgs, options)}; + } + + bool LayoutTypedSet::HasImplicitTypeCode(const RowCursor& edit) const noexcept + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() == 1); + return !LayoutCodeTraits::AlwaysRequiresTypeCode(edit.m_scopeTypeArgs[0].GetType()->GetLayoutCode()); + } + + void LayoutTypedSet::SetImplicitTypeCode(RowCursor& edit) const noexcept + { + edit.m_cellType = edit.m_scopeTypeArgs[0].GetType(); + edit.m_cellTypeArgs = edit.m_scopeTypeArgs[0].GetTypeArgs(); + } + + uint32_t LayoutTypedSet::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 1); + return sizeof(LayoutCode) + value[0].GetType()->CountTypeArgument(value[0].GetTypeArgs()); + } + + uint32_t LayoutTypedSet::WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 1); + row.WriteSparseTypeCode(offset, GetLayoutCode()); + uint32_t lenInBytes = sizeof(LayoutCode); + lenInBytes += value[0].GetType()->WriteTypeArgument(row, offset + lenInBytes, value[0].GetTypeArgs()); + return lenInBytes; + } + + std::tuple LayoutTypedSet::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + auto [typeArg, lenInBytes] = ReadTypeArgument(row, offset); + return {TypeArgumentList{{typeArg}}, lenInBytes}; + } + + TypeArgument LayoutTypedMap::FieldType(const RowCursor& scope) const noexcept + { + return TypeArgument{ + scope.m_scopeType->IsImmutable() ? &LayoutLiteral::ImmutableTypedTuple : &LayoutLiteral::TypedTuple, + scope.m_scopeTypeArgs + }; + } + + std::tuple LayoutTypedMap::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options) const noexcept + { + Result result = PrepareSparseWrite(b, scope, {this, typeArgs}, options); + if (result != Result::Success) + { + return {result, {}}; + } + + return {Result::Success, b.WriteTypedMap(scope, this, typeArgs, options)}; + } + + bool LayoutTypedMap::HasImplicitTypeCode(const RowCursor& edit) const noexcept + { + return true; + } + + void LayoutTypedMap::SetImplicitTypeCode(RowCursor& edit) const noexcept + { + edit.m_cellType = edit.m_scopeType->IsImmutable() + ? &LayoutLiteral::ImmutableTypedTuple + : &LayoutLiteral::TypedTuple; + edit.m_cellTypeArgs = edit.m_scopeTypeArgs; + } + + uint32_t LayoutTypedMap::CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 2); + uint32_t lenInBytes = sizeof(LayoutCode); + for (const TypeArgument& arg : value) + { + lenInBytes += arg.GetType()->CountTypeArgument(arg.GetTypeArgs()); + } + + return lenInBytes; + } + + uint32_t LayoutTypedMap::WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept + { + cdb_core::Contract::Assert(value.GetCount() == 2); + row.WriteSparseTypeCode(offset, GetLayoutCode()); + uint32_t lenInBytes = sizeof(LayoutCode); + for (const TypeArgument& arg : value) + { + lenInBytes += arg.GetType()->WriteTypeArgument(row, offset + lenInBytes, arg.GetTypeArgs()); + } + + return lenInBytes; + } + + std::tuple LayoutTypedMap::ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept + { + uint32_t lenInBytes = 0; + tla::vector retval; + retval.resize(2); + for (int i = 0; i < 2; i++) + { + uint32_t itemLenInBytes; + std::tie(retval[i], itemLenInBytes) = ReadTypeArgument(row, offset + lenInBytes); + lenInBytes += itemLenInBytes; + } + + return {retval, lenInBytes}; + } +} diff --git a/src/Serialization/HybridRow.Native/LayoutType.h b/src/Serialization/HybridRow.Native/LayoutType.h new file mode 100644 index 0000000..420b3a1 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutType.h @@ -0,0 +1,1628 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "LayoutCode.h" +#include "LayoutBit.h" +#include "Result.h" +#include "RowBuffer.h" +#include "TypeArgument.h" +#include "UpdateOptions.h" + +// ReSharper disable CppHiddenFunction +// ReSharper disable CppPolymorphicClassWithNonVirtualPublicDestructor +namespace cdb_hr +{ + using namespace std::literals; + struct RowCursor; + struct RowWriter; + class LayoutEndScope; + class LayoutInt8; + class LayoutInt16; + class LayoutInt32; + class LayoutInt64; + class LayoutUInt8; + class LayoutUInt16; + class LayoutUInt32; + class LayoutUInt64; + class LayoutVarInt; + class LayoutVarUInt; + class LayoutFloat32; + class LayoutFloat64; + class LayoutFloat128; + class LayoutDecimal; + class LayoutDateTime; + class LayoutUnixDateTime; + class LayoutGuid; + class LayoutMongoDbObjectId; + class LayoutNull; + class LayoutBoolean; + class LayoutUtf8; + class LayoutBinary; + class LayoutObject; + class LayoutArray; + class LayoutTypedArray; + class LayoutTypedSet; + class LayoutTypedMap; + class LayoutTuple; + class LayoutTypedTuple; + class LayoutTagged; + class LayoutTagged2; + class LayoutNullable; + struct LayoutLiteral; + + /// The abstract base class for typed hybrid row field descriptors. + /// is immutable. + class LayoutType + { + public: + /// The number of bits in a single byte on the current architecture. + constexpr static uint32_t BitsPerByte = LayoutBit::BitsPerByte; + + protected: + ~LayoutType() noexcept = default; + public: + LayoutType(const LayoutType& other) = delete; + LayoutType(LayoutType&& other) = delete; + LayoutType& operator=(const LayoutType& other) = delete; + LayoutType& operator=(LayoutType&& other) = delete; + + /// The physical layout type of the field cast to the specified type. + template>> + const T& TypeAs() const; + + [[nodiscard]] size_t GetHashCode() const noexcept; + + /// Human readable name of the type. + [[nodiscard]] virtual std::string_view GetName() const noexcept = 0; + + /// True if this type is always fixed length. + [[nodiscard]] virtual bool IsFixed() const noexcept = 0; + + /// True if this type is a literal null. + [[nodiscard]] virtual bool IsNull() const noexcept { return false; } + + /// True if this type can be used in the variable-length segment. + [[nodiscard]] bool AllowVariable() const noexcept { return !IsFixed(); } + + /// If true, this edit's nested fields cannot be updated individually. + /// The entire edit can still be replaced. + [[nodiscard]] bool IsImmutable() const noexcept { return m_immutable; } + + /// If fixed, the fixed size of the type's serialization in bytes, otherwise undefined. + [[nodiscard]] uint32_t GetSize() const noexcept { return m_size; } + + /// True if this type is a boolean. + [[nodiscard]] virtual bool IsBool() const noexcept { return false; } + + /// True if this type is a variable-length encoded integer type (either signed or unsigned). + [[nodiscard]] virtual bool IsVarint() const noexcept { return false; } + + /// True if this type is a scope. + [[nodiscard]] virtual bool IsLayoutScope() const noexcept { return false; } + + /// True if this type is a UDT. + [[nodiscard]] virtual bool IsUDT() const noexcept { return false; } + + /// Returns true if this is a nullable scope. + [[nodiscard]] virtual bool IsLayoutNullable() const noexcept { return false; } + + /// Returns true if this is a typed tuple scope. + [[nodiscard]] virtual bool IsLayoutTypedTuple() const noexcept { return false; } + + /// Returns true if this is a unique indexed scope. + [[nodiscard]] virtual bool IsLayoutUniqueScope() const noexcept { return false; } + + /// Returns true if this is a end scope. + [[nodiscard]] bool IsLayoutEndScope() const noexcept { return GetLayoutCode() == LayoutCode::EndScope; } + + /// The physical layout code used to represent the type within the serialization. + [[nodiscard]] constexpr LayoutCode GetLayoutCode() const noexcept { return m_code; } + + protected: + constexpr LayoutType(LayoutCode code, bool immutable, uint32_t size) noexcept : + m_code(code), + m_immutable(immutable), + m_size(size) { } + + /// Helper for preparing the delete of a sparse field. + /// The row to delete from. + /// The parent edit containing the field to delete. + /// The expected type of the field. + /// Success if the delete is permitted, the error code otherwise. + static Result PrepareSparseDelete(const RowBuffer& b, const RowCursor& edit, LayoutCode code) noexcept; + + /// Helper for preparing the write of a sparse field. + /// The row to write to. + /// The cursor for the field to write. + /// The (optional) type constraints. + /// The write options. + /// Success if the write is permitted, the error code otherwise. + static Result PrepareSparseWrite(RowBuffer& b, RowCursor& edit, const TypeArgument& typeArg, + UpdateOptions options) noexcept; + + /// Helper for preparing the read of a sparse field. + /// The row to read from. + /// The parent edit containing the field to read. + /// The expected type of the field. + /// Success if the read is permitted, the error code otherwise. + static Result PrepareSparseRead(const RowBuffer& b, const RowCursor& edit, LayoutCode code) noexcept; + + /// Helper for preparing the move of a sparse field into an existing restricted edit. + /// The row to read from. + /// The parent set edit into which the field should be moved. + /// The expected type of the edit moving within. + /// The expected type of the elements within the edit. + /// The field to be moved. + /// The move options. + /// [Result result, RowCursor dstEdit] + /// Result: Success if the move is permitted, the error code otherwise. + /// RowCursor: If successful, a prepared insertion cursor for the destination. + /// + /// The source field is delete if the move prepare fails with a destination error. + static std::tuple PrepareSparseMove(RowBuffer& b, const RowCursor& destinationScope, + const LayoutScope* destinationCode, + const TypeArgument& elementType, RowCursor& srcEdit, + UpdateOptions options) noexcept; + + [[nodiscard]] virtual uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept; + [[nodiscard]] virtual uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept; + /// [TypeArgument typeArg, uint32_t lenInBytes] + [[nodiscard]] static std::tuple ReadTypeArgument( + const RowBuffer& row, uint32_t offset) noexcept; + /// [TypeArgumentList typeArgs, uint32_t lenInBytes] + [[nodiscard]] virtual std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept; + + private: + friend class RowBuffer; + friend class LayoutInt8; + friend class LayoutInt16; + friend class LayoutInt32; + friend class LayoutInt64; + friend class LayoutUInt8; + friend class LayoutUInt16; + friend class LayoutUInt32; + friend class LayoutUInt64; + friend class LayoutVarInt; + friend class LayoutVarUInt; + friend class LayoutFloat32; + friend class LayoutFloat64; + friend class LayoutFloat128; + friend class LayoutDecimal; + friend class LayoutDateTime; + friend class LayoutUnixDateTime; + friend class LayoutGuid; + friend class LayoutMongoDbObjectId; + friend class LayoutNull; + friend class LayoutBoolean; + friend class LayoutUtf8; + friend class LayoutBinary; + friend class LayoutObject; + friend class LayoutArray; + friend class LayoutTypedArray; + friend class LayoutTypedSet; + friend class LayoutTypedMap; + friend class LayoutTuple; + friend class LayoutTypedTuple; + friend class LayoutTagged; + friend class LayoutTagged2; + friend class LayoutNullable; + friend class LayoutUDT; + + const LayoutCode m_code; + const bool m_immutable; + const uint32_t m_size; + }; + + /// Base class for scalar layout types. + class ScalarLayoutTypeBase : public LayoutType + { + public: + ~ScalarLayoutTypeBase() noexcept = default; // NOLINT(clang-diagnostic-non-virtual-dtor) + ScalarLayoutTypeBase(const ScalarLayoutTypeBase& other) = delete; + ScalarLayoutTypeBase(ScalarLayoutTypeBase&& other) = delete; + ScalarLayoutTypeBase& operator=(const ScalarLayoutTypeBase& other) = delete; + ScalarLayoutTypeBase& operator=(ScalarLayoutTypeBase&& other) = delete; + + static Result HasValue(const RowBuffer& b, const RowCursor& scope, const LayoutColumn& col) noexcept; + Result DeleteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col) const noexcept; + + /// Delete an existing value. + /// + /// If a value exists, then it is removed. The remainder of the row is resized to accomodate + /// a decrease in required space. If no value exists this operation is a no-op. + /// + Result DeleteVariable(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col) const noexcept; + + /// Delete an existing value. + /// + /// If a value exists, then it is removed. The remainder of the row is resized to accomodate + /// a decrease in required space. If no value exists this operation is a no-op. + /// + Result DeleteSparse(RowBuffer& b, RowCursor& edit) const noexcept; + + protected: + constexpr ScalarLayoutTypeBase(LayoutCode code, int size) noexcept : LayoutType(code, false, size) {} + + [[nodiscard]] TypeArgument GetTypeArg() const noexcept { return {this}; } + + private: + friend struct RowWriter; + }; + + /// + /// Describes the physical byte layout of a hybrid row field of a specific physical scalar type + /// . + /// + /// + /// is an immutable, stateless, helper class. It provides + /// methods for manipulating hybrid row fields of a particular scalar type, and properties that describe the + /// layout of fields of that type. + /// + /// is immutable. + /// + template + class ScalarLayoutType : public ScalarLayoutTypeBase + { + public: + // ReSharper disable once CppHidingFunction + ~ScalarLayoutType() noexcept = default; // NOLINT(clang-diagnostic-non-virtual-dtor) + ScalarLayoutType(const ScalarLayoutType& other) = delete; + ScalarLayoutType(ScalarLayoutType&& other) = delete; + ScalarLayoutType& operator=(const ScalarLayoutType& other) = delete; + ScalarLayoutType& operator=(ScalarLayoutType&& other) = delete; + + virtual Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept = 0; + [[nodiscard]] virtual std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept = 0; + + virtual Result WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept + { + return Result::Failure; + } + + [[nodiscard]] virtual std::tuple ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept + { + return {Result::Failure, T()}; + } + + virtual Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept = 0; + virtual std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept = 0; + + protected: + constexpr ScalarLayoutType(LayoutCode code, int size) noexcept : ScalarLayoutTypeBase(code, size) { } + }; + + class LayoutInt8 final : public ScalarLayoutType + { + using T = int8_t; + public: + ~LayoutInt8() noexcept = default; + LayoutInt8(const LayoutInt8& other) = delete; + LayoutInt8(LayoutInt8&& other) = delete; + LayoutInt8& operator=(const LayoutInt8& other) = delete; + LayoutInt8& operator=(LayoutInt8&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "int8"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutInt8() : ScalarLayoutType(LayoutCode::Int8, sizeof(T)) { } + }; + + class LayoutInt16 final : public ScalarLayoutType + { + using T = int16_t; + public: + ~LayoutInt16() noexcept = default; + LayoutInt16(const LayoutInt16& other) = delete; + LayoutInt16(LayoutInt16&& other) = delete; + LayoutInt16& operator=(const LayoutInt16& other) = delete; + LayoutInt16& operator=(LayoutInt16&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "int16"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutInt16() : ScalarLayoutType(LayoutCode::Int16, sizeof(T)) { } + }; + + class LayoutInt32 final : public ScalarLayoutType + { + using T = int32_t; + public: + ~LayoutInt32() noexcept = default; + LayoutInt32(const LayoutInt32& other) = delete; + LayoutInt32(LayoutInt32&& other) = delete; + LayoutInt32& operator=(const LayoutInt32& other) = delete; + LayoutInt32& operator=(LayoutInt32&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "int32"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutInt32() : ScalarLayoutType(LayoutCode::Int32, sizeof(T)) { } + }; + + class LayoutInt64 final : public ScalarLayoutType + { + using T = int64_t; + public: + ~LayoutInt64() noexcept = default; + LayoutInt64(const LayoutInt64& other) = delete; + LayoutInt64(LayoutInt64&& other) = delete; + LayoutInt64& operator=(const LayoutInt64& other) = delete; + LayoutInt64& operator=(LayoutInt64&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "int64"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutInt64() : ScalarLayoutType(LayoutCode::Int64, sizeof(T)) { } + }; + + class LayoutUInt8 final : public ScalarLayoutType + { + using T = uint8_t; + public: + ~LayoutUInt8() noexcept = default; + LayoutUInt8(const LayoutUInt8& other) = delete; + LayoutUInt8(LayoutUInt8&& other) = delete; + LayoutUInt8& operator=(const LayoutUInt8& other) = delete; + LayoutUInt8& operator=(LayoutUInt8&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "uint8"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutUInt8() : ScalarLayoutType(LayoutCode::UInt8, sizeof(T)) { } + }; + + class LayoutUInt16 final : public ScalarLayoutType + { + using T = uint16_t; + public: + ~LayoutUInt16() noexcept = default; + LayoutUInt16(const LayoutUInt16& other) = delete; + LayoutUInt16(LayoutUInt16&& other) = delete; + LayoutUInt16& operator=(const LayoutUInt16& other) = delete; + LayoutUInt16& operator=(LayoutUInt16&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "uint16"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutUInt16() : ScalarLayoutType(LayoutCode::UInt16, sizeof(T)) { } + }; + + class LayoutUInt32 final : public ScalarLayoutType + { + using T = uint32_t; + public: + ~LayoutUInt32() noexcept = default; + LayoutUInt32(const LayoutUInt32& other) = delete; + LayoutUInt32(LayoutUInt32&& other) = delete; + LayoutUInt32& operator=(const LayoutUInt32& other) = delete; + LayoutUInt32& operator=(LayoutUInt32&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "uint32"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutUInt32() : ScalarLayoutType(LayoutCode::UInt32, sizeof(T)) { } + }; + + class LayoutUInt64 final : public ScalarLayoutType + { + using T = uint64_t; + public: + ~LayoutUInt64() noexcept = default; + LayoutUInt64(const LayoutUInt64& other) = delete; + LayoutUInt64(LayoutUInt64&& other) = delete; + LayoutUInt64& operator=(const LayoutUInt64& other) = delete; + LayoutUInt64& operator=(LayoutUInt64&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "uint64"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutUInt64() : ScalarLayoutType(LayoutCode::UInt64, sizeof(T)) { } + }; + + class LayoutVarInt final : public ScalarLayoutType + { + using T = int64_t; + public: + ~LayoutVarInt() noexcept = default; + LayoutVarInt(const LayoutVarInt& other) = delete; + LayoutVarInt(LayoutVarInt&& other) = delete; + LayoutVarInt& operator=(const LayoutVarInt& other) = delete; + LayoutVarInt& operator=(LayoutVarInt&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "varint"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return false; } + [[nodiscard]] bool IsVarint() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutVarInt() : ScalarLayoutType(LayoutCode::VarInt, 0) { } + }; + + class LayoutVarUInt final : public ScalarLayoutType + { + using T = uint64_t; + public: + ~LayoutVarUInt() noexcept = default; + LayoutVarUInt(const LayoutVarUInt& other) = delete; + LayoutVarUInt(LayoutVarUInt&& other) = delete; + LayoutVarUInt& operator=(const LayoutVarUInt& other) = delete; + LayoutVarUInt& operator=(LayoutVarUInt&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "varuint"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return false; } + [[nodiscard]] bool IsVarint() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutVarUInt() : ScalarLayoutType(LayoutCode::VarUInt, 0) { } + }; + + class LayoutFloat32 final : public ScalarLayoutType + { + using T = float32_t; + public: + ~LayoutFloat32() noexcept = default; + LayoutFloat32(const LayoutFloat32& other) = delete; + LayoutFloat32(LayoutFloat32&& other) = delete; + LayoutFloat32& operator=(const LayoutFloat32& other) = delete; + LayoutFloat32& operator=(LayoutFloat32&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "float32"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutFloat32() : ScalarLayoutType(LayoutCode::Float32, sizeof(T)) { } + }; + + class LayoutFloat64 final : public ScalarLayoutType + { + using T = float64_t; + public: + ~LayoutFloat64() noexcept = default; + LayoutFloat64(const LayoutFloat64& other) = delete; + LayoutFloat64(LayoutFloat64&& other) = delete; + LayoutFloat64& operator=(const LayoutFloat64& other) = delete; + LayoutFloat64& operator=(LayoutFloat64&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "float64"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutFloat64() : ScalarLayoutType(LayoutCode::Float64, sizeof(T)) { } + }; + + class LayoutFloat128 final : public ScalarLayoutType + { + using T = float128_t; + public: + ~LayoutFloat128() noexcept = default; + LayoutFloat128(const LayoutFloat128& other) = delete; + LayoutFloat128(LayoutFloat128&& other) = delete; + LayoutFloat128& operator=(const LayoutFloat128& other) = delete; + LayoutFloat128& operator=(LayoutFloat128&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "float128"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutFloat128() : ScalarLayoutType(LayoutCode::Float128, sizeof(T)) { } + }; + + class LayoutDecimal final : public ScalarLayoutType + { + using T = decimal_t; + public: + ~LayoutDecimal() noexcept = default; + LayoutDecimal(const LayoutDecimal& other) = delete; + LayoutDecimal(LayoutDecimal&& other) = delete; + LayoutDecimal& operator=(const LayoutDecimal& other) = delete; + LayoutDecimal& operator=(LayoutDecimal&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "decimal"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutDecimal() : ScalarLayoutType(LayoutCode::Decimal, sizeof(T)) { } + }; + + class LayoutDateTime final : public ScalarLayoutType + { + using T = cdb_hr::DateTime; + public: + ~LayoutDateTime() noexcept = default; + LayoutDateTime(const LayoutDateTime& other) = delete; + LayoutDateTime(LayoutDateTime&& other) = delete; + LayoutDateTime& operator=(const LayoutDateTime& other) = delete; + LayoutDateTime& operator=(LayoutDateTime&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "datetime"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutDateTime() : ScalarLayoutType(LayoutCode::DateTime, sizeof(T)) { } + }; + + class LayoutUnixDateTime final : public ScalarLayoutType + { + using T = cdb_hr::UnixDateTime; + public: + ~LayoutUnixDateTime() noexcept = default; + LayoutUnixDateTime(const LayoutUnixDateTime& other) = delete; + LayoutUnixDateTime(LayoutUnixDateTime&& other) = delete; + LayoutUnixDateTime& operator=(const LayoutUnixDateTime& other) = delete; + LayoutUnixDateTime& operator=(LayoutUnixDateTime&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "unixdatetime"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutUnixDateTime() : ScalarLayoutType(LayoutCode::UnixDateTime, sizeof(T)) { } + }; + + class LayoutGuid final : public ScalarLayoutType + { + using T = cdb_hr::Guid; + public: + ~LayoutGuid() noexcept = default; + LayoutGuid(const LayoutGuid& other) = delete; + LayoutGuid(LayoutGuid&& other) = delete; + LayoutGuid& operator=(const LayoutGuid& other) = delete; + LayoutGuid& operator=(LayoutGuid&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "guid"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutGuid() : ScalarLayoutType(LayoutCode::Guid, sizeof(T)) { } + }; + + class LayoutMongoDbObjectId final : public ScalarLayoutType + { + using T = cdb_hr::MongoDbObjectId; + public: + ~LayoutMongoDbObjectId() noexcept = default; + LayoutMongoDbObjectId(const LayoutMongoDbObjectId& other) = delete; + LayoutMongoDbObjectId(LayoutMongoDbObjectId&& other) = delete; + LayoutMongoDbObjectId& operator=(const LayoutMongoDbObjectId& other) = delete; + LayoutMongoDbObjectId& operator=(LayoutMongoDbObjectId&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "mongodbobjectid"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutMongoDbObjectId() : ScalarLayoutType(LayoutCode::MongoDbObjectId, sizeof(T)) { } + }; + + class LayoutNull final : public ScalarLayoutType + { + using T = NullValue; + public: + ~LayoutNull() noexcept = default; + LayoutNull(const LayoutNull& other) = delete; + LayoutNull(LayoutNull&& other) = delete; + LayoutNull& operator=(const LayoutNull& other) = delete; + LayoutNull& operator=(LayoutNull&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "null"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + [[nodiscard]] bool IsNull() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutNull() : ScalarLayoutType(LayoutCode::Null, 0) { } + }; + + class LayoutBoolean final : public ScalarLayoutType + { + using T = bool; + public: + ~LayoutBoolean() noexcept = default; + LayoutBoolean(const LayoutBoolean& other) = delete; + LayoutBoolean(LayoutBoolean&& other) = delete; + LayoutBoolean& operator=(const LayoutBoolean& other) = delete; + LayoutBoolean& operator=(LayoutBoolean&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "bool"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return true; } + [[nodiscard]] bool IsBool() const noexcept override { return true; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutBoolean(bool value) : ScalarLayoutType(value ? LayoutCode::Boolean : LayoutCode::BooleanFalse, + 0) { } + }; + + class LayoutUtf8 final : public ScalarLayoutType + { + using T = std::string_view; + public: + ~LayoutUtf8() noexcept = default; + LayoutUtf8(const LayoutUtf8& other) = delete; + LayoutUtf8(LayoutUtf8&& other) = delete; + LayoutUtf8& operator=(const LayoutUtf8& other) = delete; + LayoutUtf8& operator=(LayoutUtf8&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "utf8"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return false; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutUtf8() : ScalarLayoutType(LayoutCode::Utf8, 0) { } + }; + + class LayoutBinary final : public ScalarLayoutType> + { + using T = cdb_core::ReadOnlySpan; + public: + ~LayoutBinary() noexcept = default; + LayoutBinary(const LayoutBinary& other) = delete; + LayoutBinary(LayoutBinary&& other) = delete; + LayoutBinary& operator=(const LayoutBinary& other) = delete; + LayoutBinary& operator=(LayoutBinary&& other) = delete; + + [[nodiscard]] std::string_view GetName() const noexcept override { return "binary"sv; } + [[nodiscard]] bool IsFixed() const noexcept override { return false; } + + [[nodiscard]] Result WriteFixed(RowBuffer& b, const RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadFixed(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteVariable(RowBuffer& b, RowCursor& scope, const LayoutColumn& col, + const T& value) const noexcept override; + [[nodiscard]] std::tuple ReadVariable(const RowBuffer& b, const RowCursor& scope, + const LayoutColumn& col) const noexcept override; + [[nodiscard]] Result WriteSparse(RowBuffer& b, RowCursor& edit, const T& value, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + [[nodiscard]] std::tuple ReadSparse(const RowBuffer& b, RowCursor& edit) const noexcept override; + + private: + friend struct LayoutLiteral; + constexpr LayoutBinary() : ScalarLayoutType(LayoutCode::Binary, 0) { } + }; + + class LayoutScope : public LayoutType + { + public: + ~LayoutScope() noexcept = default; // NOLINT(clang-diagnostic-non-virtual-dtor) + LayoutScope(const LayoutScope& other) = delete; + LayoutScope(LayoutScope&& other) = delete; + LayoutScope& operator=(const LayoutScope& other) = delete; + LayoutScope& operator=(LayoutScope&& other) = delete; + + [[nodiscard]] bool IsFixed() const noexcept override { return false; } + + /// True if this type is a scope. + [[nodiscard]] bool IsLayoutScope() const noexcept final { return true; } + + /// True if this type is a typed map scope. + [[nodiscard]] virtual bool IsLayoutTypedMap() const noexcept { return false; } + + /// A function to write content into a . + /// The type of the context value passed by the caller. + /// The row to write to. + /// The type of the scope to write into. + /// A context value provided by the caller. + /// The result. + template + using WriterFunc = Result (*)(RowBuffer& b, const RowCursor& scope, TContext& context); + + [[nodiscard]] std::tuple ReadScope(const RowBuffer& b, RowCursor& edit) const noexcept; + [[nodiscard]] virtual std::tuple WriteScope( + RowBuffer& b, + RowCursor& scope, + const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept = 0; + + // TODO(jthunter): does this need to be virtual? + template + Result WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + TContext& context, WriterFunc func, + UpdateOptions options = UpdateOptions::Upsert) const noexcept; + + Result DeleteScope(RowBuffer& b, RowCursor& edit) const noexcept; + + protected: + constexpr LayoutScope(LayoutCode code, bool immutable, bool isSizedScope, bool isIndexedScope, bool isFixedArity, + bool isUniqueScope, bool isTypedScope) noexcept : + LayoutType(code, immutable, 0), + m_isSizedScope{isSizedScope}, + m_isIndexedScope{isIndexedScope}, + m_isFixedArity{isFixedArity}, + m_isUniqueScope{isUniqueScope}, + m_isTypedScope{isTypedScope} { } + + private: + friend class LayoutType; + friend struct LayoutLiteral; + friend class RowBuffer; + friend struct RowCursor; + friend struct RowWriter; + + /// Returns true if this is a sized scope. + [[nodiscard]] + bool IsSizedScope() const noexcept { return m_isSizedScope; } + + /// Returns true if this is an indexed scope. + [[nodiscard]] + bool IsIndexedScope() const noexcept { return m_isIndexedScope; } + + /// Returns true if this is a fixed arity scope. + [[nodiscard]] + bool IsFixedArity() const noexcept { return m_isFixedArity; } + + /// Returns true if the scope's elements cannot be updated directly. + [[nodiscard]] + bool IsUniqueScope() const noexcept { return m_isUniqueScope; } + + /// Returns true if this is a typed scope. + [[nodiscard]] + bool IsTypedScope() const noexcept { return m_isTypedScope; } + + /// + /// Returns true if writing an item in the specified typed scope would elide the type code + /// because it is implied by the type arguments. + /// + /// + /// True if the type code is implied (not written), false otherwise. + [[nodiscard]] virtual bool HasImplicitTypeCode(const RowCursor& edit) const noexcept + { + return false; + } + + virtual void SetImplicitTypeCode(RowCursor& edit) const noexcept + { + cdb_core::Contract::Fail("No implicit type codes."); + } + + virtual void ReadSparsePath(const RowBuffer& row, RowCursor& edit) const noexcept; + + bool m_isSizedScope; + bool m_isIndexedScope; + bool m_isFixedArity; + bool m_isUniqueScope; + bool m_isTypedScope; + }; + + class LayoutPropertyScope : public LayoutScope + { + public: + // ReSharper disable once CppHidingFunction + ~LayoutPropertyScope() noexcept = default; // NOLINT(clang-diagnostic-non-virtual-dtor) + LayoutPropertyScope(const LayoutPropertyScope& other) = delete; + LayoutPropertyScope(LayoutPropertyScope&& other) = delete; + LayoutPropertyScope& operator=(const LayoutPropertyScope& other) = delete; + LayoutPropertyScope& operator=(LayoutPropertyScope&& other) = delete; + + protected: + constexpr LayoutPropertyScope(LayoutCode code, bool immutable) : LayoutScope( + code, + immutable, + false, // isSizedScope + false, // isIndexedScope + false, // isFixedArity + false, // isUniqueScope + false) // isTypedScope + { } + }; + + class LayoutEndScope final : public LayoutScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override { return "end"sv; } + + private: + friend struct LayoutLiteral; + + constexpr LayoutEndScope() : LayoutScope( + LayoutCode::EndScope, false, + false, // isSizedScope + false, // isIndexedScope + false, // isFixedArity + false, // isUniqueScope + false) // isTypedScope + { } + + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + }; + + class LayoutObject final : public LayoutPropertyScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override + { + return m_immutable ? "im_object"sv : "object"sv; + } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutObject(bool immutable) : + LayoutPropertyScope(immutable ? LayoutCode::ImmutableObjectScope : LayoutCode::ObjectScope, immutable) { } + }; + + class LayoutUDT final : public LayoutPropertyScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override { return m_immutable ? "im_udt"sv : "udt"sv; } + [[nodiscard]] bool IsUDT() const noexcept override { return true; } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutUDT(bool immutable) : + LayoutPropertyScope(immutable ? LayoutCode::ImmutableSchema : LayoutCode::Schema, immutable) { } + + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + [[nodiscard]] uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, + const TypeArgumentList& value) const noexcept override; + + /// [TypeArgumentList typeArgs, uint32_t lenInBytes] + [[nodiscard]] + std::tuple + ReadTypeArgumentList(const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + class LayoutIndexedScope : public LayoutScope + { + public: + // ReSharper disable once CppHidingFunction + ~LayoutIndexedScope() noexcept = default; // NOLINT(clang-diagnostic-non-virtual-dtor) + LayoutIndexedScope(const LayoutIndexedScope& other) = delete; + LayoutIndexedScope(LayoutIndexedScope&& other) = delete; + LayoutIndexedScope& operator=(const LayoutIndexedScope& other) = delete; + LayoutIndexedScope& operator=(LayoutIndexedScope&& other) = delete; + + protected: + constexpr LayoutIndexedScope(LayoutCode code, + bool immutable, + bool isSizedScope, + bool isFixedArity, + bool isUniqueScope, + bool isTypedScope) : + LayoutScope( + code, + immutable, + isSizedScope, + true, // isIndexedScope + isFixedArity, + isUniqueScope, + isTypedScope) { } + + void ReadSparsePath(const RowBuffer& row, RowCursor& edit) const noexcept override; + }; + + class LayoutArray final : public LayoutIndexedScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override { return m_immutable ? "im_array"sv : "array"sv; } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutArray(bool immutable) : + LayoutIndexedScope( + immutable ? LayoutCode::ImmutableArrayScope : LayoutCode::ArrayScope, + immutable, + false, // isSizedScope + false, // isFixedArity + false, // isUniqueScope + false) // isTypedScope + { } + }; + + class LayoutTypedArray final : public LayoutIndexedScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override + { + return m_immutable ? "im_array_t"sv : "array_t"sv; + } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutTypedArray(bool immutable) : + LayoutIndexedScope( + immutable ? LayoutCode::ImmutableTypedArrayScope : LayoutCode::TypedArrayScope, + immutable, + true, // isSizedScope + false, // isFixedArity + false, // isUniqueScope + true) // isTypedScope + { } + + [[nodiscard]] bool HasImplicitTypeCode(const RowCursor& edit) const noexcept override; + void SetImplicitTypeCode(RowCursor& edit) const noexcept override; + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept override; + [[nodiscard]] std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + class LayoutTuple final : public LayoutIndexedScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override + { + return m_immutable ? "im_tuple"sv : "tuple"sv; + } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutTuple(bool immutable) : + LayoutIndexedScope( + immutable ? LayoutCode::ImmutableTupleScope : LayoutCode::TupleScope, + immutable, + false, // isSizedScope + true, // isFixedArity + false, // isUniqueScope + false) // isTypedScope + { } + + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept override; + [[nodiscard]] std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + class LayoutTypedTuple final : public LayoutIndexedScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override + { + return m_immutable ? "im_tuple_t"sv : "tuple_t"sv; + } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutTypedTuple(bool immutable) : + LayoutIndexedScope( + immutable ? LayoutCode::ImmutableTypedTupleScope : LayoutCode::TypedTupleScope, + immutable, + true, // isSizedScope + true, // isFixedArity + false, // isUniqueScope + true) // isTypedScope + { } + + [[nodiscard]] bool IsLayoutTypedTuple() const noexcept override { return true; } + + [[nodiscard]] bool HasImplicitTypeCode(const RowCursor& edit) const noexcept override; + void SetImplicitTypeCode(RowCursor& edit) const noexcept override; + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept override; + [[nodiscard]] std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + class LayoutTagged final : public LayoutIndexedScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override + { + return m_immutable ? "im_tagged_t"sv : "tagged_t"sv; + } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutTagged(bool immutable) : + LayoutIndexedScope( + immutable ? LayoutCode::ImmutableTaggedScope : LayoutCode::TaggedScope, + immutable, + true, // isSizedScope + true, // isFixedArity + false, // isUniqueScope + true) // isTypedScope + { } + + [[nodiscard]] bool HasImplicitTypeCode(const RowCursor& edit) const noexcept override; + void SetImplicitTypeCode(RowCursor& edit) const noexcept override; + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept override; + [[nodiscard]] std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + class LayoutTagged2 final : public LayoutIndexedScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override + { + return m_immutable ? "im_tagged2_t"sv : "tagged2_t"sv; + } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutTagged2(bool immutable) : + LayoutIndexedScope( + immutable ? LayoutCode::ImmutableTagged2Scope : LayoutCode::Tagged2Scope, + immutable, + true, // isSizedScope + true, // isFixedArity + false, // isUniqueScope + true) // isTypedScope + { } + + [[nodiscard]] bool HasImplicitTypeCode(const RowCursor& edit) const noexcept override; + void SetImplicitTypeCode(RowCursor& edit) const noexcept override; + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept override; + [[nodiscard]] std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + class LayoutNullable final : public LayoutIndexedScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override + { + return m_immutable ? "im_nullable"sv : "nullable"sv; + } + + [[nodiscard]] bool IsLayoutNullable() const noexcept override { return true; } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + bool hasValue, + UpdateOptions options = UpdateOptions::Upsert) const noexcept; + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + static Result HasValue(const RowBuffer& b, const RowCursor& scope) noexcept; + + private: + friend struct LayoutLiteral; + + constexpr LayoutNullable(bool immutable) : + LayoutIndexedScope( + immutable ? LayoutCode::ImmutableNullableScope : LayoutCode::NullableScope, + immutable, + true, // isSizedScope + true, // isFixedArity + false, // isUniqueScope + true) // isTypedScope + { } + + [[nodiscard]] bool HasImplicitTypeCode(const RowCursor& edit) const noexcept override; + void SetImplicitTypeCode(RowCursor& edit) const noexcept override; + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept override; + [[nodiscard]] std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + class LayoutUniqueScope : public LayoutIndexedScope + { + public: + // ReSharper disable once CppHidingFunction + ~LayoutUniqueScope() noexcept = default; // NOLINT(clang-diagnostic-non-virtual-dtor) + LayoutUniqueScope(const LayoutUniqueScope& other) = delete; + LayoutUniqueScope(LayoutUniqueScope&& other) = delete; + LayoutUniqueScope& operator=(const LayoutUniqueScope& other) = delete; + LayoutUniqueScope& operator=(LayoutUniqueScope&& other) = delete; + + /// Returns true if this is a unique indexed scope. + [[nodiscard]] bool IsLayoutUniqueScope() const noexcept override { return true; } + + [[nodiscard]] virtual TypeArgument FieldType(const RowCursor& scope) const noexcept = 0; + + [[nodiscard]] Result MoveField(RowBuffer& b, RowCursor& destinationScope, RowCursor& sourceEdit, + UpdateOptions options = UpdateOptions::Upsert) const noexcept; + + // Force inherited overloads to be in scope. + using LayoutIndexedScope::WriteScope; + + // TODO(jthunter): does this need to be virtual? + template + [[nodiscard]] Result WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + TContext& context, WriterFunc func, + UpdateOptions options = UpdateOptions::Upsert) const noexcept; + + /// Search for a matching field within a unique index. + /// The row to search. + /// The parent unique index edit to search. + /// The parent edit from which the match pattern is read. + /// [Result result, RowCursor value] + /// result: Success a matching field exists in the unique index, NotFound if no match is found, the + /// error code otherwise. + /// value: If successful, the updated edit. + /// + /// The pattern field is delete whether the find succeeds or fails. + [[nodiscard]] std::tuple Find(RowBuffer& b, const RowCursor& scope, + RowCursor& patternScope) const noexcept; + + protected: + constexpr LayoutUniqueScope(LayoutCode code, bool immutable, bool isSizedScope, bool isTypedScope) : + LayoutIndexedScope( + code, + immutable, + isSizedScope, + false, // isFixedArity + true, // isUniqueScope + isTypedScope) { } + }; + + class LayoutTypedSet final : public LayoutUniqueScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override { return m_immutable ? "im_set_t"sv : "set_t"sv; } + [[nodiscard]] TypeArgument FieldType(const RowCursor& scope) const noexcept override; + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutTypedSet(bool immutable) : + LayoutUniqueScope( + immutable ? LayoutCode::ImmutableTypedSetScope : LayoutCode::TypedSetScope, + immutable, + true, // isSizedScope + true) // isTypedScope + { } + + [[nodiscard]] bool HasImplicitTypeCode(const RowCursor& edit) const noexcept override; + void SetImplicitTypeCode(RowCursor& edit) const noexcept override; + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept override; + [[nodiscard]] std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + class LayoutTypedMap final : public LayoutUniqueScope + { + public: + [[nodiscard]] std::string_view GetName() const noexcept override { return m_immutable ? "im_map_t"sv : "map_t"sv; } + [[nodiscard]] TypeArgument FieldType(const RowCursor& scope) const noexcept override; + [[nodiscard]] bool IsLayoutTypedMap() const noexcept override { return true; } + + [[nodiscard]] + std::tuple WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + UpdateOptions options = UpdateOptions::Upsert) const noexcept override; + + private: + friend struct LayoutLiteral; + + constexpr LayoutTypedMap(bool immutable) : + LayoutUniqueScope( + immutable ? LayoutCode::ImmutableTypedMapScope : LayoutCode::TypedMapScope, + immutable, + true, // isSizedScope + true) // isTypedScope + { } + + [[nodiscard]] bool HasImplicitTypeCode(const RowCursor& edit) const noexcept override; + void SetImplicitTypeCode(RowCursor& edit) const noexcept override; + [[nodiscard]] uint32_t CountTypeArgument([[maybe_unused]] const TypeArgumentList& value) const noexcept override; + uint32_t WriteTypeArgument(RowBuffer& row, uint32_t offset, const TypeArgumentList& value) const noexcept override; + [[nodiscard]] std::tuple ReadTypeArgumentList( + const RowBuffer& row, uint32_t offset) const noexcept override; + }; + + struct LayoutLiteral final + { + constexpr static LayoutInt8 Int8{}; + constexpr static LayoutInt16 Int16{}; + constexpr static LayoutInt32 Int32{}; + constexpr static LayoutInt64 Int64{}; + constexpr static LayoutUInt8 UInt8{}; + constexpr static LayoutUInt16 UInt16{}; + constexpr static LayoutUInt32 UInt32{}; + constexpr static LayoutUInt64 UInt64{}; + constexpr static LayoutVarInt VarInt{}; + constexpr static LayoutVarUInt VarUInt{}; + constexpr static LayoutFloat32 Float32{}; + constexpr static LayoutFloat64 Float64{}; + constexpr static LayoutFloat128 Float128{}; + constexpr static LayoutDecimal Decimal{}; + constexpr static LayoutDateTime DateTime{}; + constexpr static LayoutUnixDateTime UnixDateTime{}; + constexpr static LayoutGuid Guid{}; + constexpr static LayoutMongoDbObjectId MongoDbObjectId{}; + constexpr static LayoutNull Null{}; + constexpr static LayoutBoolean Boolean{true}; + constexpr static LayoutBoolean BooleanFalse{false}; + constexpr static LayoutUtf8 Utf8{}; + constexpr static LayoutBinary Binary{}; + constexpr static LayoutObject Object{false}; + constexpr static LayoutObject ImmutableObject{true}; + constexpr static LayoutArray Array{false}; + constexpr static LayoutArray ImmutableArray{true}; + constexpr static LayoutTypedArray TypedArray{false}; + constexpr static LayoutTypedArray ImmutableTypedArray{true}; + constexpr static LayoutTypedSet TypedSet{false}; + constexpr static LayoutTypedSet ImmutableTypedSet{true}; + constexpr static LayoutTypedMap TypedMap{false}; + constexpr static LayoutTypedMap ImmutableTypedMap{true}; + constexpr static LayoutTuple Tuple{false}; + constexpr static LayoutTuple ImmutableTuple{true}; + constexpr static LayoutTypedTuple TypedTuple{false}; + constexpr static LayoutTypedTuple ImmutableTypedTuple{true}; + constexpr static LayoutTagged Tagged{false}; + constexpr static LayoutTagged ImmutableTagged{true}; + constexpr static LayoutTagged2 Tagged2{false}; + constexpr static LayoutTagged2 ImmutableTagged2{true}; + constexpr static LayoutNullable Nullable{false}; + constexpr static LayoutNullable ImmutableNullable{true}; + constexpr static LayoutUDT UDT{false}; + constexpr static LayoutUDT ImmutableUDT{true}; + constexpr static LayoutEndScope EndScope{}; + + template constexpr static const LayoutType* FromCode() noexcept; + constexpr static const LayoutType* FromCode(LayoutCode code) noexcept; + + private: + template constexpr static const LayoutType* + FromCode(LayoutCode code) noexcept; // NOLINT(clang-diagnostic-undefined-inline) + }; + + constexpr const LayoutType* LayoutLiteral::FromCode(LayoutCode code) noexcept + { + return FromCode(code); + } + + template constexpr const LayoutType* LayoutLiteral::FromCode() noexcept + { + return FromCode(literal); + } + + template constexpr const LayoutType* LayoutLiteral::FromCode(LayoutCode code) noexcept + { + if (literal != LayoutCode::Invalid) + { + code = literal; + } + + switch (code) + { + case LayoutCode::Int8: + return &LayoutLiteral::Int8; + case LayoutCode::Int16: + return &LayoutLiteral::Int16; + case LayoutCode::Int32: + return &LayoutLiteral::Int32; + case LayoutCode::Int64: + return &LayoutLiteral::Int64; + case LayoutCode::UInt8: + return &LayoutLiteral::UInt8; + case LayoutCode::UInt16: + return &LayoutLiteral::UInt16; + case LayoutCode::UInt32: + return &LayoutLiteral::UInt32; + case LayoutCode::UInt64: + return &LayoutLiteral::UInt64; + case LayoutCode::VarInt: + return &LayoutLiteral::VarInt; + case LayoutCode::VarUInt: + return &LayoutLiteral::VarUInt; + case LayoutCode::Float32: + return &LayoutLiteral::Float32; + case LayoutCode::Float64: + return &LayoutLiteral::Float64; + case LayoutCode::Float128: + return &LayoutLiteral::Float128; + case LayoutCode::Decimal: + return &LayoutLiteral::Decimal; + case LayoutCode::DateTime: + return &LayoutLiteral::DateTime; + case LayoutCode::UnixDateTime: + return &LayoutLiteral::UnixDateTime; + case LayoutCode::Guid: + return &LayoutLiteral::Guid; + case LayoutCode::MongoDbObjectId: + return &LayoutLiteral::MongoDbObjectId; + case LayoutCode::Null: + return &LayoutLiteral::Null; + case LayoutCode::Boolean: + return &LayoutLiteral::Boolean; + case LayoutCode::BooleanFalse: + return &LayoutLiteral::BooleanFalse; + case LayoutCode::Utf8: + return &LayoutLiteral::Utf8; + case LayoutCode::Binary: + return &LayoutLiteral::Binary; + case LayoutCode::ObjectScope: + return &LayoutLiteral::Object; + case LayoutCode::ImmutableObjectScope: + return &LayoutLiteral::ImmutableObject; + case LayoutCode::ArrayScope: + return &LayoutLiteral::Array; + case LayoutCode::ImmutableArrayScope: + return &LayoutLiteral::ImmutableArray; + case LayoutCode::TypedArrayScope: + return &LayoutLiteral::TypedArray; + case LayoutCode::ImmutableTypedArrayScope: + return &LayoutLiteral::ImmutableTypedArray; + case LayoutCode::TypedSetScope: + return &LayoutLiteral::TypedSet; + case LayoutCode::ImmutableTypedSetScope: + return &LayoutLiteral::ImmutableTypedSet; + case LayoutCode::TypedMapScope: + return &LayoutLiteral::TypedMap; + case LayoutCode::ImmutableTypedMapScope: + return &LayoutLiteral::ImmutableTypedMap; + case LayoutCode::TupleScope: + return &LayoutLiteral::Tuple; + case LayoutCode::ImmutableTupleScope: + return &LayoutLiteral::ImmutableTuple; + case LayoutCode::TypedTupleScope: + return &LayoutLiteral::TypedTuple; + case LayoutCode::ImmutableTypedTupleScope: + return &LayoutLiteral::ImmutableTypedTuple; + case LayoutCode::TaggedScope: + return &LayoutLiteral::Tagged; + case LayoutCode::ImmutableTaggedScope: + return &LayoutLiteral::ImmutableTagged; + case LayoutCode::Tagged2Scope: + return &LayoutLiteral::Tagged2; + case LayoutCode::ImmutableTagged2Scope: + return &LayoutLiteral::ImmutableTagged2; + case LayoutCode::NullableScope: + return &LayoutLiteral::Nullable; + case LayoutCode::ImmutableNullableScope: + return &LayoutLiteral::ImmutableNullable; + case LayoutCode::Schema: + return &LayoutLiteral::UDT; + case LayoutCode::ImmutableSchema: + return &LayoutLiteral::ImmutableUDT; + case LayoutCode::EndScope: + return &LayoutLiteral::EndScope; + default: + cdb_core::Contract::Fail(cdb_core::make_string("Not Implemented: %d", static_cast(code))); + } + } +} diff --git a/src/Serialization/HybridRow.Native/LayoutType.inl b/src/Serialization/HybridRow.Native/LayoutType.inl new file mode 100644 index 0000000..257c545 --- /dev/null +++ b/src/Serialization/HybridRow.Native/LayoutType.inl @@ -0,0 +1,167 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "LayoutCodeTraits.h" +#include "LayoutType.h" +#include "RowCursor.h" + +namespace cdb_hr +{ + namespace Internal + { + template + struct LayoutTypeCheck + { + constexpr static const T& TypeAs(const LayoutType& t) noexcept + { + LayoutCode tc = t.GetLayoutCode(); + if (code == LayoutCode::Boolean) + { + tc = LayoutCodeTraits::Canonicalize(tc); + } + else if ((code >= LayoutCode::ObjectScope) && (code < LayoutCode::EndScope)) + { + tc = LayoutCodeTraits::ClearImmutableBit(tc); + } + cdb_core::Contract::Requires(tc == code); + return static_cast(t); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + } + }; + + template + struct LayoutScopeTypeCheck {}; + + template<> + struct LayoutScopeTypeCheck + { + constexpr static const LayoutIndexedScope& TypeAs(const LayoutType& t) noexcept + { + LayoutCode tc = t.GetLayoutCode(); + cdb_core::Contract::Requires((tc >= LayoutCode::ArrayScope) && (tc <= LayoutCode::ImmutableTagged2Scope)); + return static_cast(t); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + } + }; + + template<> + struct LayoutScopeTypeCheck + { + constexpr static const LayoutUniqueScope& TypeAs(const LayoutType& t) noexcept + { + LayoutCode tc = t.GetLayoutCode(); + cdb_core::Contract::Requires((tc >= LayoutCode::MapScope) && (tc <= LayoutCode::ImmutableTypedSetScope)); + return static_cast(t); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + } + }; + + template struct LayoutTypeAs final {}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutScopeTypeCheck{}; + template<> struct LayoutTypeAs final : LayoutScopeTypeCheck{}; + } + + template [[nodiscard]] const T& LayoutType::TypeAs() const + { + return Internal::LayoutTypeAs::TypeAs(*this); + } + + template + Result LayoutScope::WriteScope(RowBuffer& b, RowCursor& scope, + const TypeArgumentList& typeArgs, TContext& context, + WriterFunc func, UpdateOptions options) const noexcept + { + auto [r, childScope] = WriteScope(b, scope, typeArgs, options); + if (r != Result::Success) + { + return r; + } + + r = func != nullptr ? func(b, childScope, context) : Result::Success; + if (r != Result::Success) + { + DeleteScope(b, scope); + return r; + } + + scope.Skip(b, childScope); + return Result::Success; + } + + template const T& TypeArgument::TypeAs() const + { + return m_type->TypeAs(); + } + + template const T& LayoutColumn::TypeAs() const + { + return m_typeArg.GetType()->TypeAs(); + } + + template + Result LayoutUniqueScope::WriteScope(RowBuffer& b, RowCursor& scope, const TypeArgumentList& typeArgs, + TContext& context, WriterFunc func, + UpdateOptions options) const noexcept + { + Result r; + RowCursor uniqueScope; + std::tie(r, uniqueScope) = LayoutIndexedScope::WriteScope(b, scope, typeArgs, options); + if (r != Result::Success) + { + return r; + } + + RowCursor childScope = uniqueScope.Clone(); + childScope.m_deferUniqueIndex = true; + r = func != nullptr ? func(b, childScope, context) : Result::Success; + if (r != Result::Success) + { + DeleteScope(b, scope); + return r; + } + + uniqueScope.m_count = childScope.m_count; + r = b.TypedCollectionUniqueIndexRebuild(uniqueScope); + if (r != Result::Success) + { + DeleteScope(b, scope); + return r; + } + + scope.Skip(b, childScope); + return Result::Success; + } +} diff --git a/src/Serialization/HybridRow.Native/MapPropertyType.h b/src/Serialization/HybridRow.Native/MapPropertyType.h new file mode 100644 index 0000000..18b0881 --- /dev/null +++ b/src/Serialization/HybridRow.Native/MapPropertyType.h @@ -0,0 +1,64 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include "ScopePropertyType.h" + +namespace cdb_hr +{ + /// + /// Map properties represent an unbounded set of zero or more key-value pairs with unique + /// keys. + /// + /// + /// Maps are typed or untyped. Within typed maps, all key MUST be the same type, and all + /// values MUST be the same type. The type of both key and values is specified via + /// and respectively. Typed maps may be stored more efficiently than untyped + /// maps. When or is unspecified or marked + /// , the map is untyped and its key and/or values may be heterogeneous. + /// + class MapPropertyType final : public ScopePropertyType + { + public: + MapPropertyType() noexcept : ScopePropertyType{TypeKind::Map}, m_keys{}, m_values{} {} + + MapPropertyType(std::unique_ptr keys, + std::unique_ptr values, + bool nullable = true, + bool immutable = false) noexcept : + ScopePropertyType{TypeKind::Map, nullable, immutable}, + m_keys{std::move(keys)}, + m_values{std::move(values)} {} + + ~MapPropertyType() noexcept override = default; + MapPropertyType(MapPropertyType&) = delete; + MapPropertyType(MapPropertyType&&) = delete; + MapPropertyType& operator=(const MapPropertyType&) = delete; + MapPropertyType& operator=(MapPropertyType&&) = delete; + + [[nodiscard]] SchemaId GetRuntimeSchemaId() const noexcept override { return SchemaId{2147473665}; } + [[nodiscard]] PropertyKind GetKind() const noexcept override { return PropertyKind::Map; } + + /// (Optional) type of the keys of the map, if a typed map, otherwise null. + [[nodiscard]] std::optional> GetKeys() const noexcept + { + return m_keys ? std::optional>{*m_keys} : std::nullopt; + } + + void SetKeys(std::unique_ptr value) noexcept { m_keys = std::move(value); } + + /// (Optional) type of the values of the map, if a typed map, otherwise null. + [[nodiscard]] std::optional> GetValues() const noexcept + { + return m_values ? std::optional>{*m_values} : std::nullopt; + } + + void SetValues(std::unique_ptr value) noexcept { m_values = std::move(value); } + + private: + std::unique_ptr m_keys; + std::unique_ptr m_values; + }; +} diff --git a/src/Serialization/HybridRow.Native/MemorySpanResizer.h b/src/Serialization/HybridRow.Native/MemorySpanResizer.h new file mode 100644 index 0000000..6075b4c --- /dev/null +++ b/src/Serialization/HybridRow.Native/MemorySpanResizer.h @@ -0,0 +1,43 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "ISpanResizer.h" + +namespace cdb_hr +{ + template + class MemorySpanResizer : public ISpanResizer + { + public: + MemorySpanResizer(uint32_t initialCapacity = 0) : m_memory{initialCapacity} { } + MemorySpanResizer(cdb_core::Memory buffer) : m_memory{std::move(buffer)} { } + + cdb_core::Memory GetMemory() const noexcept { return m_memory; } + + cdb_core::Span Resize(uint32_t minimumLength, cdb_core::Span buffer) override + { + // ReSharper disable once CppEntityAssignedButNoRead + cdb_core::Memory old{}; + if (m_memory.Length() < minimumLength) + { + // Keep the old allocation alive till the end of the method + old = std::move(m_memory); + // Allocate a new block. + m_memory = cdb_core::Memory(std::max(minimumLength, buffer.Length())); + } + + cdb_core::Span next = m_memory.AsSpan(); + if (!buffer.IsEmpty() && next.Slice(0, buffer.Length()) != buffer) + { + buffer.CopyTo(next); + } + + return next; + } + + private: + cdb_core::Memory m_memory; + }; +} diff --git a/src/Serialization/HybridRow.Native/MongoDbObjectId.cpp b/src/Serialization/HybridRow.Native/MongoDbObjectId.cpp new file mode 100644 index 0000000..2587c21 --- /dev/null +++ b/src/Serialization/HybridRow.Native/MongoDbObjectId.cpp @@ -0,0 +1,6 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "MongoDbObjectId.h" diff --git a/src/Serialization/HybridRow.Native/MongoDbObjectId.h b/src/Serialization/HybridRow.Native/MongoDbObjectId.h new file mode 100644 index 0000000..a5219f4 --- /dev/null +++ b/src/Serialization/HybridRow.Native/MongoDbObjectId.h @@ -0,0 +1,168 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// A 12-byte MongoDB Object Identifier (in big-endian byte order). + #pragma pack(push, 1) + struct MongoDbObjectId final + { + /// The size (in bytes) of a MongoObjectId. + constexpr static int Size = 12; + + [[nodiscard]] constexpr MongoDbObjectId() noexcept : m_data{{0, 0}} {} + ~MongoDbObjectId() noexcept = default; + MongoDbObjectId(const MongoDbObjectId& other) noexcept = default; + MongoDbObjectId(MongoDbObjectId&& other) noexcept = default; + MongoDbObjectId& operator=(const MongoDbObjectId& other) noexcept = default; + MongoDbObjectId& operator=(MongoDbObjectId&& other) noexcept = default; + + /// Initializes a new instance of the struct. + /// the high-order 32-bits. + /// the low-order 64-bits. + constexpr MongoDbObjectId(uint32_t high, uint64_t low) noexcept; + + /// Initializes a new instance of the struct. + /// the bytes of the object id in big-endian order. + MongoDbObjectId(const cdb_core::Span& src) noexcept; + + friend bool operator==(const MongoDbObjectId& left, const MongoDbObjectId& right) noexcept; + friend bool operator!=(const MongoDbObjectId& left, const MongoDbObjectId& right) noexcept; + + /// Returns true if this is the same value as . + /// The value to compare against. + /// True if the two values are the same. + [[nodiscard]] + bool Equals(const MongoDbObjectId& other) const noexcept; + + /// Returns a hash code. + [[nodiscard]] + size_t GetHashCode() const noexcept; + + /// Returns the bytes of the object id as a byte array in big-endian order. + [[nodiscard]] + cdb_core::Memory ToByteArray() const noexcept; + + /// Copies the bytes of the object id to the provided buffer. + /// A buffer to receive the bytes in big-endian order. + /// + /// Required: The buffer must be able to accomodate the full object id (see + /// ) at the offset indicated. + /// + void CopyTo(cdb_core::Span dest) const noexcept; + + private: + friend std::hash; + + constexpr static uint32_t SwapByteOrder(uint32_t value); + + // reverse byte order (64-bit) + constexpr static uint64_t SwapByteOrder(uint64_t value); + + /// The object id bytes inlined. + union + { + struct + { + uint32_t High; + uint64_t Low; + }; + + byte Data[MongoDbObjectId::Size]; + } m_data; + }; + #pragma pack(pop) + + constexpr MongoDbObjectId::MongoDbObjectId(uint32_t high, uint64_t low) noexcept : + m_data{{MongoDbObjectId::SwapByteOrder(high), MongoDbObjectId::SwapByteOrder(low)}} + { + static_assert(cdb_core::Endian::IsLittleEndian()); + } + + /// Initializes a new instance of the struct. + /// the bytes of the object id in big-endian order. + inline MongoDbObjectId::MongoDbObjectId(const cdb_core::Span& src) noexcept : m_data{} + { + cdb_core::Contract::Requires(src.Length() == MongoDbObjectId::Size); + + const byte* q = &src[0]; + m_data.High = *reinterpret_cast(&q[0]); + m_data.Low = *reinterpret_cast(&q[4]); + } + + /// Operator == overload. + inline bool operator==(const MongoDbObjectId& left, const MongoDbObjectId& right) noexcept + { + return left.Equals(right); + } + + /// Operator != overload. + inline bool operator!=(const MongoDbObjectId& left, const MongoDbObjectId& right) noexcept + { + return !left.Equals(right); + } + + inline bool MongoDbObjectId::Equals(const MongoDbObjectId& other) const noexcept + { + return m_data.High == other.m_data.High && m_data.Low == other.m_data.Low; + } + + inline size_t MongoDbObjectId::GetHashCode() const noexcept + { + size_t hashCode = 0; + const byte* p = m_data.Data; + hashCode = (hashCode * 397) ^ *reinterpret_cast(&p[0]); + hashCode = (hashCode * 397) ^ *reinterpret_cast(&p[4]); + hashCode = (hashCode * 397) ^ *reinterpret_cast(&p[8]); + return hashCode; + } + + inline cdb_core::Memory MongoDbObjectId::ToByteArray() const noexcept + { + cdb_core::Memory bytes{MongoDbObjectId::Size}; + CopyTo(bytes.AsSpan()); + return bytes; + } + + inline void MongoDbObjectId::CopyTo(cdb_core::Span dest) const noexcept + { + cdb_core::Contract::Requires(dest.Length() == MongoDbObjectId::Size); + const cdb_core::Span source = cdb_core::Span(const_cast(m_data.Data), MongoDbObjectId::Size); + source.CopyTo(dest); + } + + constexpr uint32_t MongoDbObjectId::SwapByteOrder(uint32_t value) + { + return ((value & 0x000000FFU) << 24) | + ((value & 0x0000FF00U) << 8) | + ((value & 0x00FF0000U) >> 8) | + ((value & 0xFF000000U) >> 24); + } + + constexpr uint64_t MongoDbObjectId::SwapByteOrder(uint64_t value) + { + return ((value & 0x00000000000000FFUL) << 56) | + ((value & 0x000000000000FF00UL) << 40) | + ((value & 0x0000000000FF0000UL) << 24) | + ((value & 0x00000000FF000000UL) << 8) | + ((value & 0x000000FF00000000UL) >> 8) | + ((value & 0x0000FF0000000000UL) >> 24) | + ((value & 0x00FF000000000000UL) >> 40) | + ((value & 0xFF00000000000000UL) >> 56); + } +} + +namespace std +{ + template<> + struct hash + { + constexpr std::size_t operator()(cdb_hr::MongoDbObjectId const& s) const noexcept + { + return cdb_core::HashCode::Combine(s.m_data.Low, s.m_data.High); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/Namespace.cpp b/src/Serialization/HybridRow.Native/Namespace.cpp new file mode 100644 index 0000000..59e4908 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Namespace.cpp @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "Namespace.h" +#include "RowCursor.h" +#include "IHybridRowSerializer.h" +#include "SystemSchema.h" + +namespace cdb_hr +{ + std::tuple> Namespace::Read(const RowBuffer& row) noexcept + { + cdb_core::Contract::Requires(row.GetHeader().GetSchemaId() == NamespaceHybridRowSerializer::Id); + RowCursor root = RowCursor::Create(row); + return NamespaceHybridRowSerializer::Read(row, root, true); + } + + Result Namespace::Write(RowBuffer& row) const noexcept + { + cdb_core::Contract::Requires(row.GetHeader().GetSchemaId() == NamespaceHybridRowSerializer::Id); + RowCursor root = RowCursor::Create(row); + return NamespaceHybridRowSerializer::Write(row, root, true, NamespaceHybridRowSerializer::Id, *this); + } + + [[nodiscard]] SchemaLanguageVersion Namespace::GetEffectiveSdlVersion() const noexcept + { + return m_version != SchemaLanguageVersion::Unspecified ? m_version : SchemaLanguageVersion::Latest; + } +} diff --git a/src/Serialization/HybridRow.Native/Namespace.h b/src/Serialization/HybridRow.Native/Namespace.h new file mode 100644 index 0000000..a83cd04 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Namespace.h @@ -0,0 +1,89 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include +#include "Result.h" +#include "EnumSchema.h" +#include "RowBuffer.h" +#include "SchemaLanguageVersion.h" +#include "Schema.h" + +namespace cdb_hr +{ + class Namespace final + { + public : + /// Initializes a new instance of the class. + Namespace() noexcept : m_version{SchemaLanguageVersion::V2}, m_name{}, m_comment{}, m_enums{}, m_schemas{} { } + ~Namespace() noexcept = default; + Namespace(const Namespace& other) noexcept = delete; + Namespace(Namespace&& other) noexcept = delete; + Namespace& operator=(const Namespace& other) noexcept = delete; + Namespace& operator=(Namespace&& other) noexcept = delete; + + /// The version of the HybridRow Schema Definition Language used to encode this namespace. + [[nodiscard]] SchemaLanguageVersion GetVersion() const noexcept { return m_version; } + void SetVersion(SchemaLanguageVersion value) noexcept { m_version = value; } + + /// The fully qualified identifier of the namespace. + [[nodiscard]] std::string_view GetName() const noexcept { return m_name; } + void SetName(std::string_view value) noexcept { m_name = value; } + + /// An (optional) comment describing the purpose of this namespace. + /// Comments are for documentary purpose only and do not affect the namespace at runtime. + [[nodiscard]] std::string_view GetComment() const noexcept { return m_comment; } + void SetComment(std::string_view comment) noexcept { m_comment = comment; } + + /// An (optional) namespace to use when performing C++ codegen. + [[nodiscard]] std::string_view GetCppNamespace() const noexcept { return m_cppNamespace; } + void SetCppNamespace(std::string_view cppNamespace) noexcept { m_cppNamespace = cppNamespace; } + + /// The set of enums defined in . + [[nodiscard]] const std::vector>& GetEnums() const noexcept { return m_enums; } + std::vector>& GetEnums() noexcept { return m_enums; } + void SetEnums(std::vector> value) noexcept { m_enums = std::move(value); } + + /// The set of schemas that make up the . + /// + /// Namespaces may consist of zero or more table schemas along with zero or more UDT schemas. + /// Table schemas can only reference UDT schemas defined in the same namespace. UDT schemas can + /// contain nested UDTs whose schemas are defined within the same namespace. + /// + [[nodiscard]] const std::vector>& GetSchemas() const noexcept { return m_schemas; } + std::vector>& GetSchemas() noexcept { return m_schemas; } + void SetSchemas(std::vector> value) noexcept { m_schemas = std::move(value); } + + /// Read Namespace as a row. + /// The row to read from. + /// Success if the read is successful, an error code otherwise. + static std::tuple> Read(const RowBuffer& row) noexcept; + + /// Write Namespace as a row. + /// The row to write into. + /// Success if the write is successful, an error code otherwise. + Result Write(RowBuffer& row) const noexcept; + + private: + friend class Schema; + + /// + /// Returns the effective SDL language version. + /// + /// The effective SDL language version. + [[nodiscard]] SchemaLanguageVersion GetEffectiveSdlVersion() const noexcept; + + SchemaLanguageVersion m_version; + tla::string m_name; + tla::string m_comment; + tla::string m_cppNamespace; + + /// The set of enums for the . + std::vector> m_enums; + + /// The set of schemas that make up the . + std::vector> m_schemas; + }; +} diff --git a/src/Serialization/HybridRow.Native/NullValue.h b/src/Serialization/HybridRow.Native/NullValue.h new file mode 100644 index 0000000..91e3705 --- /dev/null +++ b/src/Serialization/HybridRow.Native/NullValue.h @@ -0,0 +1,40 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// The literal null value. + /// + /// May be stored hybrid row to indicate the literal null value. Typically this value should + /// not be used and the corresponding column should be absent from the row. + /// + #pragma pack(push, 1) + struct NullValue final + { + constexpr NullValue() noexcept = default; + ~NullValue() = default; + NullValue(const NullValue& other) noexcept = default; + NullValue(NullValue&& other) noexcept = default; + NullValue& operator=(const NullValue& other) noexcept = default; + NullValue& operator=(NullValue&& other) noexcept = default; + + friend bool operator==(const NullValue& lhs, const NullValue& rhs) { return true; } + friend bool operator!=(const NullValue& lhs, const NullValue& rhs) { return false; } + }; + #pragma pack(pop) +} + +namespace std +{ + template<> + struct hash + { + std::size_t operator()(cdb_hr::NullValue const& s) const noexcept + { + return cdb_core::HashCode::Combine(1899077816); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/NullableHybridRowSerializer.h b/src/Serialization/HybridRow.Native/NullableHybridRowSerializer.h new file mode 100644 index 0000000..7c66ac1 --- /dev/null +++ b/src/Serialization/HybridRow.Native/NullableHybridRowSerializer.h @@ -0,0 +1,116 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "IHybridRowSerializer.h" +#include "LayoutType.h" +#include "RowCursor.h" + +namespace cdb_hr +{ + template>> + struct NullableHybridRowSerializer final + { + struct NullableComparerComparer; + using value_type = TNullable; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = NullableComparerComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const TNullable& value) noexcept + { + cdb_core::Contract::Assert(!isRoot); + bool hasValue = HasValue(value); + auto [r, childScope] = LayoutLiteral::Nullable.WriteScope(row, scope, typeArgs, hasValue); + if (r != Result::Success) + { + return r; + } + + if (hasValue) + { + r = TSerializer::Write(row, childScope, false, typeArgs[0].GetTypeArgs(), AsValue(value)); + if (r != Result::Success) + { + return r; + } + } + + scope.Skip(row, childScope); + return Result::Success; + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + cdb_core::Contract::Assert(!isRoot); + auto [r, childScope] = LayoutLiteral::Nullable.ReadScope(row, scope); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + if (childScope.MoveNext(row)) + { + auto [r, item] = TSerializer::Read(row, childScope, isRoot); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + scope.Skip(row, childScope); + return {Result::Success, std::move(AsNullable(std::move(item)))}; + } + + scope.Skip(row, childScope); + return {Result::Success, TNullable{}}; + } + + struct NullableComparerComparer final + { + constexpr bool operator()(const_reference x, const_reference y) const + { + bool xHasValue = HasValue(x); + if (xHasValue != HasValue(y)) + { + return false; + } + if (!xHasValue) + { + return true; + } + + return typename TSerializer::comparer_type{}.operator()(AsValue(x), AsValue(y)); + } + + constexpr std::size_t operator()(const_reference s) const noexcept + { + cdb_core::HashCode hash{}; + if (HasValue(s)) + { + hash.Add(AsValue(s)); + } + return hash.ToHashCode(); + } + }; + + private: + static bool HasValue(const TNullable& value) + { + return value.operator bool(); + } + + static TNullable AsNullable(T value) + { + return TNullable{value}; + } + + static const T& AsValue(const TNullable& value) + { + return *value; + } + }; +} diff --git a/src/Serialization/HybridRow.Native/ObjectPropertyType.h b/src/Serialization/HybridRow.Native/ObjectPropertyType.h new file mode 100644 index 0000000..d3a789b --- /dev/null +++ b/src/Serialization/HybridRow.Native/ObjectPropertyType.h @@ -0,0 +1,49 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include +#include "Property.h" +#include "ScopePropertyType.h" + +namespace cdb_hr +{ + /// Object properties represent nested structures. + /// + /// Object properties map to multiple columns depending on the number of internal properties + /// within the defined object structure. Object properties are provided as a convince in schema + /// design. They are effectively equivalent to defining the same properties explicitly via + /// with nested property paths. + /// + class ObjectPropertyType final : public ScopePropertyType + { + public: + ObjectPropertyType() noexcept : ScopePropertyType{TypeKind::Object}, m_properties{} {} + + explicit ObjectPropertyType(std::vector> props, + bool nullable = true, + bool immutable = false) noexcept : + ScopePropertyType{TypeKind::Object, nullable, immutable}, + m_properties{std::move(props)} {} + + ~ObjectPropertyType() noexcept override = default; + ObjectPropertyType(ObjectPropertyType&) = delete; + ObjectPropertyType(ObjectPropertyType&&) = delete; + ObjectPropertyType& operator=(const ObjectPropertyType&) = delete; + ObjectPropertyType& operator=(ObjectPropertyType&&) = delete; + + [[nodiscard]] SchemaId GetRuntimeSchemaId() const noexcept override { return SchemaId{2147473662}; } + [[nodiscard]] PropertyKind GetKind() const noexcept override { return PropertyKind::Object; } + + /// A list of zero or more property definitions that define the columns within the schema. + [[nodiscard]] const std::vector>& GetProperties() const noexcept { return m_properties; } + std::vector>& GetProperties() noexcept { return m_properties; } + void SetProperties(std::vector> value) noexcept { m_properties = std::move(value); } + + private: + /// A list of zero or more property definitions that define the columns within the schema. + std::vector> m_properties; + }; +} diff --git a/src/Serialization/HybridRow.Native/PartitionKey.h b/src/Serialization/HybridRow.Native/PartitionKey.h new file mode 100644 index 0000000..a3de05f --- /dev/null +++ b/src/Serialization/HybridRow.Native/PartitionKey.h @@ -0,0 +1,30 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_hr +{ + /// Describes a property or set of properties used to partition the data set across machines. + class PartitionKey final + { + public: + PartitionKey() noexcept = default; + explicit PartitionKey(std::string_view path) noexcept : m_path{path} {} + ~PartitionKey() noexcept = default; + PartitionKey(const PartitionKey& other) = default; + PartitionKey(PartitionKey&& other) noexcept = default; + PartitionKey& operator=(const PartitionKey& other) = default; + PartitionKey& operator=(PartitionKey&& other) noexcept = default; + + /// The logical path of the referenced property. + /// Partition keys MUST refer to properties defined within the same schema. + [[nodiscard]] std::string_view GetPath() const noexcept { return m_path; } + void SetPath(std::string_view path) noexcept { m_path = path; } + + private: + tla::string m_path; + }; +} diff --git a/src/Serialization/HybridRow.Native/PrimarySortKey.h b/src/Serialization/HybridRow.Native/PrimarySortKey.h new file mode 100644 index 0000000..a2c45d0 --- /dev/null +++ b/src/Serialization/HybridRow.Native/PrimarySortKey.h @@ -0,0 +1,46 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include "SortDirection.h" + +namespace cdb_hr +{ + /// + /// Describes a property or set of properties used to order the data set within a single + /// partition. + /// + class PrimarySortKey final + { + public: + PrimarySortKey() noexcept : m_path{}, m_direction{SortDirection::Ascending} {} + + explicit PrimarySortKey(std::string_view path, SortDirection dir = SortDirection::Ascending) noexcept : + m_path{path}, + m_direction{dir} {} + + ~PrimarySortKey() noexcept = default; + PrimarySortKey(const PrimarySortKey& other) = default; + PrimarySortKey(PrimarySortKey&& other) noexcept = default; + PrimarySortKey& operator=(const PrimarySortKey& other) = default; + PrimarySortKey& operator=(PrimarySortKey&& other) noexcept = default; + + /// The logical path of the referenced property. + /// Primary keys MUST refer to properties defined within the same schema. + [[nodiscard]] std::string_view GetPath() const noexcept { return m_path; } + void SetPath(std::string_view value) noexcept { m_path = value; } + + /// The logical path of the referenced property. + /// Primary keys MUST refer to properties defined within the same schema. + [[nodiscard]] + SortDirection GetDirection() const noexcept { return m_direction; } + + void SetDirection(SortDirection value) noexcept { m_direction = value; } + + private: + tla::string m_path; + SortDirection m_direction; + }; +} diff --git a/src/Serialization/HybridRow.Native/PrimitiveHybridRowSerializer.h b/src/Serialization/HybridRow.Native/PrimitiveHybridRowSerializer.h new file mode 100644 index 0000000..e9ea2c3 --- /dev/null +++ b/src/Serialization/HybridRow.Native/PrimitiveHybridRowSerializer.h @@ -0,0 +1,482 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "LayoutType.h" +#include "RowCursor.h" + +namespace cdb_hr +{ + template + struct EqualityComparer final + { + constexpr bool operator()(const T& x, const T& y) const { return x == y; } + constexpr std::size_t operator()(const T& s) const noexcept { return std::hash{}.operator()(s); } + }; + + struct Int8HybridRowSerializer final + { + using value_type = int8_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const_reference value) noexcept + { + return LayoutLiteral::Int8.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Int8.ReadSparse(row, scope); + } + }; + + struct Int16HybridRowSerializer final + { + using value_type = int16_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const int16_t& value) noexcept + { + return LayoutLiteral::Int16.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Int16.ReadSparse(row, scope); + } + }; + + struct Int32HybridRowSerializer final + { + using value_type = int32_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const int32_t& value) noexcept + { + return LayoutLiteral::Int32.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Int32.ReadSparse(row, scope); + } + }; + + struct Int64HybridRowSerializer final + { + using value_type = int64_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const int64_t& value) noexcept + { + return LayoutLiteral::Int64.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Int64.ReadSparse(row, scope); + } + }; + + struct UInt8HybridRowSerializer final + { + using value_type = uint8_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const uint8_t& value) noexcept + { + return LayoutLiteral::UInt8.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::UInt8.ReadSparse(row, scope); + } + }; + + struct UInt16HybridRowSerializer final + { + using value_type = uint16_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const uint16_t& value) noexcept + { + return LayoutLiteral::UInt16.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::UInt16.ReadSparse(row, scope); + } + }; + + struct UInt32HybridRowSerializer final + { + using value_type = uint32_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const uint32_t& value) noexcept + { + return LayoutLiteral::UInt32.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::UInt32.ReadSparse(row, scope); + } + }; + + struct UInt64HybridRowSerializer final + { + using value_type = uint64_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const uint64_t& value) noexcept + { + return LayoutLiteral::UInt64.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::UInt64.ReadSparse(row, scope); + } + }; + + struct Float32HybridRowSerializer final + { + using value_type = float32_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const float32_t& value) noexcept + { + return LayoutLiteral::Float32.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Float32.ReadSparse(row, scope); + } + }; + + struct Float64HybridRowSerializer final + { + using value_type = float64_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const float64_t& value) noexcept + { + return LayoutLiteral::Float64.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Float64.ReadSparse(row, scope); + } + }; + + struct Float128HybridRowSerializer final + { + using value_type = float128_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const float128_t& value) noexcept + { + return LayoutLiteral::Float128.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Float128.ReadSparse(row, scope); + } + }; + + struct DecimalHybridRowSerializer final + { + using value_type = Decimal; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const Decimal& value) noexcept + { + return LayoutLiteral::Decimal.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Decimal.ReadSparse(row, scope); + } + }; + + struct BooleanHybridRowSerializer final + { + using value_type = bool; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const bool& value) noexcept + { + return LayoutLiteral::Boolean.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Boolean.ReadSparse(row, scope); + } + }; + + struct NullHybridRowSerializer final + { + using value_type = NullValue; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const NullValue& value) noexcept + { + return LayoutLiteral::Null.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Null.ReadSparse(row, scope); + } + }; + + struct DateTimeHybridRowSerializer final + { + using value_type = DateTime; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const DateTime& value) noexcept + { + return LayoutLiteral::DateTime.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::DateTime.ReadSparse(row, scope); + } + }; + + struct UnixDateTimeHybridRowSerializer final + { + using value_type = UnixDateTime; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const UnixDateTime& value) noexcept + { + return LayoutLiteral::UnixDateTime.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::UnixDateTime.ReadSparse(row, scope); + } + }; + + struct GuidHybridRowSerializer final + { + using value_type = Guid; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const Guid& value) noexcept + { + return LayoutLiteral::Guid.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::Guid.ReadSparse(row, scope); + } + }; + + struct MongoDbObjectIdHybridRowSerializer final + { + using value_type = MongoDbObjectId; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const MongoDbObjectId& value) noexcept + { + return LayoutLiteral::MongoDbObjectId.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::MongoDbObjectId.ReadSparse(row, scope); + } + }; + + struct Utf8HybridRowSerializer final + { + using value_type = std::string; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const value_type& value) noexcept + { + return LayoutLiteral::Utf8.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + auto [r, sv] = LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != Result::Success) + { + return {r, {}}; + } + return {r, std::string(sv)}; + } + }; + + struct BinaryHybridRowSerializer final + { + struct BinaryComparer; + using value_type = cdb_core::Memory; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = BinaryComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const cdb_core::ReadOnlySpan& value) noexcept + { + return LayoutLiteral::Binary.WriteSparse(row, scope, value); + } + + static std::tuple> Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + auto [r, sp] = LayoutLiteral::Binary.ReadSparse(row, scope); + if (r != Result::Success) + { + return {r, {}}; + } + return {r, cdb_core::Memory(sp)}; + } + + struct BinaryComparer final + { + constexpr bool operator()(const_reference x, const_reference y) const + { + return x.AsSpan().SequenceEqual(y.AsSpan()); + } + + std::size_t operator()(const_reference s) const noexcept + { + cdb_core::HashCode hash{}; + + // Add bulk in 8-byte words. + cdb_core::ReadOnlySpan span = cdb_core::MemoryMarshal::Cast(s.AsSpan()); + for (auto i : span) + { + hash.AddHash(i); + } + + // Add any residual as separate bytes. + cdb_core::ReadOnlySpan residual = s.AsSpan().Slice(span.Length() * sizeof(uint64_t)); + for (auto i : residual) + { + hash.AddHash(static_cast(i)); + } + + return hash.ToHashCode(); + } + }; + + }; + + struct VarIntHybridRowSerializer final + { + using value_type = int64_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const int64_t& value) noexcept + { + return LayoutLiteral::VarInt.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::VarInt.ReadSparse(row, scope); + } + }; + + struct VarUIntHybridRowSerializer final + { + using value_type = uint64_t; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = EqualityComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const uint64_t& value) noexcept + { + return LayoutLiteral::VarUInt.WriteSparse(row, scope, value); + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + return LayoutLiteral::VarUInt.ReadSparse(row, scope); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/PrimitivePropertyType.h b/src/Serialization/HybridRow.Native/PrimitivePropertyType.h new file mode 100644 index 0000000..f09333b --- /dev/null +++ b/src/Serialization/HybridRow.Native/PrimitivePropertyType.h @@ -0,0 +1,73 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "PropertyType.h" +#include "StorageKind.h" + +namespace cdb_hr +{ + /// A primitive property. + /// + /// Primitive properties map to columns one-to-one. Primitive properties indicate how the + /// column should be represented within the row. + /// + class PrimitivePropertyType final : public PropertyType + { + public: + PrimitivePropertyType() noexcept : m_length{0}, m_storage{StorageKind::Sparse}, m_enum{}, m_rowBufferSize{} {} + + PrimitivePropertyType(TypeKind type, StorageKind storage = StorageKind::Sparse, + bool nullable = true, std::string_view apiType = ""sv) noexcept : + PropertyType{type, nullable, apiType}, m_length{0}, m_storage{storage}, m_enum{} {} + + ~PrimitivePropertyType() noexcept override = default; + PrimitivePropertyType(PrimitivePropertyType&) = delete; + PrimitivePropertyType(PrimitivePropertyType&&) = delete; + PrimitivePropertyType& operator=(const PrimitivePropertyType&) = delete; + PrimitivePropertyType& operator=(PrimitivePropertyType&&) = delete; + + [[nodiscard]] SchemaId GetRuntimeSchemaId() const noexcept override { return SchemaId{2147473659}; } + [[nodiscard]] PropertyKind GetKind() const noexcept override { return PropertyKind::Primitive; } + + /// The maximum allowable length in bytes. + /// + /// This annotation is only valid for non-fixed length types. A value of 0 means the maximum + /// allowable length. + /// + [[nodiscard]] uint32_t GetLength() const noexcept { return m_length; } + void SetLength(uint32_t value) noexcept { m_length = value; } + + /// Storage requirements of the property. + [[nodiscard]] StorageKind GetStorage() const noexcept { return m_storage; } + void SetStorage(StorageKind value) noexcept { m_storage = value; } + + /// The identifier of the enum defining the values and base type. + /// + /// This annotation is only valid for enum types. The enum MUST be defined within the + /// same as the schema that references it. + /// + [[nodiscard]] std::string_view GetEnum() const noexcept { return m_enum; } + void SetEnum(std::string_view value) noexcept { m_enum = value; } + + /// If true then during serialization this field includes the actual row buffer size. + /// + /// + /// Fields with this annotation MUST be fields with . + /// Only a single field per schema have this annotation. + /// + /// + /// During serialization this field is deferred until the very end and written last. + /// + /// + [[nodiscard]] bool GetRowBufferSize() const noexcept { return m_rowBufferSize; } + void SetRowBufferSize(bool value) noexcept { m_rowBufferSize = value; } + + private: + uint32_t m_length; + StorageKind m_storage; + tla::string m_enum; + bool m_rowBufferSize; + }; +} diff --git a/src/Serialization/HybridRow.Native/Property.h b/src/Serialization/HybridRow.Native/Property.h new file mode 100644 index 0000000..722bde2 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Property.h @@ -0,0 +1,65 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include "AllowEmptyKind.h" +#include "PropertyType.h" + +namespace cdb_hr +{ + /// Describes a single property definition. + class Property final + { + public: + Property() noexcept : m_comment{}, m_path{}, m_propertyType{}, m_apiname{}, m_allowEmpty{} {} + ~Property() = default; + Property(Property&) = delete; + Property(Property&&) = delete; + Property& operator=(const Property&) = delete; + Property& operator=(Property&&) = delete; + + /// An (optional) comment describing the purpose of this property. + /// Comments are for documentary purpose only and do not affect the property at runtime. + [[nodiscard]] std::string_view GetComment() const noexcept { return m_comment; } + void SetComment(std::string_view comment) noexcept { m_comment = comment; } + + /// The logical path of this property. + /// + /// For complex properties (e.g. objects) the logical path forms a prefix to relative paths of + /// properties defined within nested structures. + /// + /// See the logical path specification for full details on both relative and absolute paths. + /// + [[nodiscard]] std::string_view GetPath() const noexcept { return m_path; } + void SetPath(std::string_view path) noexcept { m_path = path; } + + /// Api-specific name annotations for the property. + [[nodiscard]] std::string_view GetApiName() const noexcept { return m_apiname; } + void SetApiName(std::string_view value) noexcept { m_apiname = value; } + + /// The type of the property. + /// + /// Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a + /// single column. Complex types may define one or more columns depending on their structure. + /// + [[nodiscard]] std::optional> GetPropertyType() const noexcept + { + return m_propertyType ? std::optional>{*m_propertyType} : std::nullopt; + } + + void SetPropertyType(std::unique_ptr propType) noexcept { m_propertyType = std::move(propType); } + + /// Empty canonicalization for this property. + [[nodiscard]] AllowEmptyKind GetAllowEmpty() const noexcept { return m_allowEmpty; } + void SetAllowEmpty(AllowEmptyKind value) noexcept { m_allowEmpty = value; } + + private: + tla::string m_comment; + tla::string m_path; + std::unique_ptr m_propertyType; + tla::string m_apiname; + AllowEmptyKind m_allowEmpty; + }; +} diff --git a/src/Serialization/HybridRow.Native/PropertyKind.h b/src/Serialization/HybridRow.Native/PropertyKind.h new file mode 100644 index 0000000..d70deb3 --- /dev/null +++ b/src/Serialization/HybridRow.Native/PropertyKind.h @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// Describes the logical kind of a property. + enum class PropertyKind : unsigned char + { + /// Reserved. + Invalid = 0, + + Primitive, + Object, + Array, + Map, + Set, + Tagged, + Tuple, + Udt, + }; +} diff --git a/src/Serialization/HybridRow.Native/PropertyType.h b/src/Serialization/HybridRow.Native/PropertyType.h new file mode 100644 index 0000000..1595bc0 --- /dev/null +++ b/src/Serialization/HybridRow.Native/PropertyType.h @@ -0,0 +1,62 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +#include "PropertyKind.h" +#include "TypeKind.h" + +namespace cdb_hr +{ + using namespace std::literals; + + /// The base class for property types both primitive and complex. + class PropertyType + { + public: + PropertyType(PropertyType&) = delete; + PropertyType(PropertyType&&) = delete; + PropertyType& operator=(const PropertyType&) = delete; + PropertyType& operator=(PropertyType&&) = delete; + virtual ~PropertyType() noexcept = default; + + /// The logical type of the property. + [[nodiscard]] virtual SchemaId GetRuntimeSchemaId() const noexcept = 0; + + /// The logical type of the property. + [[nodiscard]] virtual PropertyKind GetKind() const noexcept = 0; + + /// Api-specific type annotations for the property. + [[nodiscard]] std::string_view GetApiType() const noexcept { return m_apiType; } + void SetApiType(std::string_view value) noexcept { m_apiType = value; } + + /// The logical type of the property. + [[nodiscard]] TypeKind GetType() const noexcept { return m_type; } + void SetType(TypeKind value) noexcept { m_type = value; } + + /// True if the property can be null. + /// Default: true. + [[nodiscard]] bool GetNullable() const noexcept { return m_nullable; } + void SetNullable(bool value) noexcept { m_nullable = value; } + + private: + friend class PrimitivePropertyType; + friend class ScopePropertyType; + + PropertyType() : + m_type{TypeKind::Invalid}, + m_nullable{true}, + m_apiType{} { } + + explicit PropertyType(TypeKind type, bool nullable = true, std::string_view apiType = ""sv) : + m_type{type}, + m_nullable{nullable}, + m_apiType{apiType} { } + + TypeKind m_type; + bool m_nullable; + tla::string m_apiType; + }; +} diff --git a/src/Serialization/HybridRow.Native/RecordIOFormatter.cpp b/src/Serialization/HybridRow.Native/RecordIOFormatter.cpp new file mode 100644 index 0000000..2ac3881 --- /dev/null +++ b/src/Serialization/HybridRow.Native/RecordIOFormatter.cpp @@ -0,0 +1,59 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "Segment.h" +#include "IHybridRowSerializer.h" +#include "MemorySpanResizer.h" +#include "SystemSchema.h" +#include "RowCursor.h" +#include "RecordIOFormatter.h" + +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace cdb_hr +{ + template>> + static std::tuple FormatObject( + ISpanResizer& resizer, + uint32_t initialCapacity, + const Layout& layout, + const T& obj) noexcept + { + RowBuffer row{initialCapacity, &resizer}; + const LayoutResolver& resolver = SchemasHrSchema::GetLayoutResolver(); + row.InitLayout(HybridRowVersion::V1, layout, &resolver); + RowCursor root = RowCursor::Create(row); + Result r = TSerializer::Write(row, root, true, {}, obj); + if (r != Result::Success) + { + row.Reset(); + } + + return {r, row}; + } + + std::tuple RecordIOFormatter::FormatSegment(const Segment& segment, ISpanResizer& resizer) noexcept + { + const Layout& layout = SchemasHrSchema::GetLayoutResolver().Resolve(SegmentHybridRowSerializer::Id); + uint32_t estimatedSize = HybridRowHeader::Size + layout.GetSize() + + static_cast(segment.GetComment().size()) + + static_cast(segment.GetSDL().size()) + + uint32_t{20}; + + return FormatObject(resizer, estimatedSize, layout, segment); + } + + std::tuple RecordIOFormatter::FormatRecord(const cdb_core::ReadOnlyMemory& body, + ISpanResizer& resizer) noexcept + { + const Layout& layout = SchemasHrSchema::GetLayoutResolver().Resolve(RecordHybridRowSerializer::Id); + uint32_t estimatedSize = HybridRowHeader::Size + layout.GetSize() + body.Length(); + uint32_t crc32 = cdb_core::Crc32::Update(0, body.AsSpan()); + Record record{static_cast(body.Length()), crc32}; + return FormatObject(resizer, estimatedSize, layout, record); + } +} diff --git a/src/Serialization/HybridRow.Native/RecordIOFormatter.h b/src/Serialization/HybridRow.Native/RecordIOFormatter.h new file mode 100644 index 0000000..4f6a4bf --- /dev/null +++ b/src/Serialization/HybridRow.Native/RecordIOFormatter.h @@ -0,0 +1,27 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "Result.h" +#include "RowBuffer.h" + +namespace cdb_hr +{ + class Segment; + + struct RecordIOFormatter final + { + // Static Class + [[nodiscard]] RecordIOFormatter() = delete; + RecordIOFormatter(const RecordIOFormatter& other) = delete; + RecordIOFormatter(RecordIOFormatter&& other) noexcept = delete; + RecordIOFormatter& operator=(const RecordIOFormatter& other) = delete; + RecordIOFormatter& operator=(RecordIOFormatter&& other) noexcept = delete; + + static std::tuple FormatSegment(const Segment& segment, ISpanResizer& resizer) noexcept; + static std::tuple FormatRecord(const cdb_core::ReadOnlyMemory& body, + ISpanResizer& resizer) noexcept; + }; +} diff --git a/src/Serialization/HybridRow.Native/RecordIOParser.cpp b/src/Serialization/HybridRow.Native/RecordIOParser.cpp new file mode 100644 index 0000000..8f37e90 --- /dev/null +++ b/src/Serialization/HybridRow.Native/RecordIOParser.cpp @@ -0,0 +1,198 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "RowCursor.h" +#include "RowBuffer.h" +#include "IHybridRowSerializer.h" +#include "SystemSchema.h" +#include "Segment.h" +#include "RecordIOParser.h" + +// ReSharper disable CppClangTidyCppcoreguidelinesAvoidGoto +// ReSharper disable CppRedundantControlFlowJump +namespace cdb_hr +{ + std::tuple, uint32_t, uint32_t> + cdb_hr::RecordIOParser::Process(const cdb_core::ReadOnlyMemory& buffer) noexcept + { + Result r = Result::Failure; + cdb_core::ReadOnlyMemory b = buffer; + switch (m_state) + { + case State::Start: + { + m_state = State::NeedSegmentLength; + goto NeedSegmentLength; + } + + case State::NeedSegmentLength: + { + NeedSegmentLength: + uint32_t minimalSegmentRowSize = HybridRowHeader::Size + SegmentHybridRowSerializer::Size; + if (b.Length() < minimalSegmentRowSize) + { + uint32_t need = minimalSegmentRowSize; + uint32_t consumed = buffer.Length() - b.Length(); + return {Result::InsufficientBuffer, ProductionType::None, {}, need, consumed}; + } + + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal::AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + cdb_core::ReadOnlyMemory mem = b.Slice(0, minimalSegmentRowSize); + RowBuffer row{ + cdb_core::MemoryMarshal::AsMemory(mem).AsSpan(), + HybridRowVersion::V1, + &SchemasHrSchema::GetLayoutResolver(), + nullptr + }; + RowCursor root = RowCursor::Create(row); + std::tie(r, m_segment) = SegmentHybridRowSerializer::Read(row, root, true); + if (r != Result::Success) + { + break; + } + + m_state = State::NeedSegment; + goto NeedSegment; + } + + case State::NeedSegment: + { + NeedSegment: + if (b.Length() < static_cast(m_segment->GetLength())) + { + uint32_t need = static_cast(m_segment->GetLength()); + uint32_t consumed = buffer.Length() - b.Length(); + return {Result::InsufficientBuffer, ProductionType::None, {}, need, consumed}; + } + + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal::AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + cdb_core::ReadOnlyMemory mem = b.Slice(0, static_cast(m_segment->GetLength())); + RowBuffer row{ + cdb_core::MemoryMarshal::AsMemory(mem).AsSpan(), + HybridRowVersion::V1, + &SchemasHrSchema::GetLayoutResolver(), + nullptr + }; + RowCursor root = RowCursor::Create(row); + std::tie(r, m_segment) = SegmentHybridRowSerializer::Read(row, root, true); + if (r != Result::Success) + { + break; + } + + cdb_core::ReadOnlyMemory record = b.Slice(0, mem.Length()); + b = b.Slice(mem.Length()); + uint32_t need = 0; + m_state = State::NeedHeader; + uint32_t consumed = buffer.Length() - b.Length(); + return {Result::Success, ProductionType::Segment, record, need, consumed}; + } + + case State::NeedHeader: + { + if (b.Length() < HybridRowHeader::Size) + { + uint32_t need = HybridRowHeader::Size; + uint32_t consumed = buffer.Length() - b.Length(); + return {Result::InsufficientBuffer, ProductionType::None, {}, need, consumed}; + } + + HybridRowHeader header = cdb_core::MemoryMarshal::Read(b.AsSpan()); + if (header.GetVersion() != HybridRowVersion::V1) + { + r = Result::InvalidRow; + break; + } + + if (header.GetSchemaId() == SegmentHybridRowSerializer::Id) + { + goto NeedSegment; + } + + if (header.GetSchemaId() == RecordHybridRowSerializer::Id) + { + goto NeedRecord; + } + + r = Result::InvalidRow; + break; + } + + case State::NeedRecord: + { + NeedRecord: + uint32_t minimalRecordRowSize = HybridRowHeader::Size + RecordHybridRowSerializer::Size; + if (b.Length() < minimalRecordRowSize) + { + uint32_t need = minimalRecordRowSize; + uint32_t consumed = buffer.Length() - b.Length(); + return {Result::InsufficientBuffer, ProductionType::None, {}, need, consumed}; + } + + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal::AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + cdb_core::ReadOnlyMemory mem = b.Slice(0, minimalRecordRowSize); + RowBuffer row{ + cdb_core::MemoryMarshal::AsMemory(mem).AsSpan(), + HybridRowVersion::V1, + &SchemasHrSchema::GetLayoutResolver(), + nullptr + }; + RowCursor root = RowCursor::Create(row); + std::tie(r, m_record) = RecordHybridRowSerializer::Read(row, root, true); + if (r != Result::Success) + { + break; + } + + b = b.Slice(mem.Length()); + m_state = State::NeedRow; + goto NeedRow; + } + + case State::NeedRow: + { + NeedRow: + if (b.Length() < static_cast(m_record->GetLength())) + { + uint32_t need = static_cast(m_record->GetLength()); + uint32_t consumed = buffer.Length() - b.Length(); + return {Result::InsufficientBuffer, ProductionType::None, {}, need, consumed}; + } + + cdb_core::ReadOnlyMemory record = b.Slice(0, static_cast(m_record->GetLength())); + + // Validate that the record has not been corrupted. + uint32_t crc32 = cdb_core::Crc32::Update(0, record.AsSpan()); + if (crc32 != m_record->GetCrc32()) + { + r = Result::InvalidRow; + break; + } + + b = b.Slice(m_record->GetLength()); + uint32_t need = 0; + m_state = State::NeedHeader; + uint32_t consumed = buffer.Length() - b.Length(); + return {Result::Success, ProductionType::Record, record, need, consumed}; + } + default: + cdb_core::Contract::Fail("Invalid state"); + } + + m_state = State::Error; + uint32_t need = 0; + uint32_t consumed = buffer.Length() - b.Length(); + return {r, ProductionType::None, {}, need, consumed}; + } +} diff --git a/src/Serialization/HybridRow.Native/RecordIOParser.h b/src/Serialization/HybridRow.Native/RecordIOParser.h new file mode 100644 index 0000000..f99b017 --- /dev/null +++ b/src/Serialization/HybridRow.Native/RecordIOParser.h @@ -0,0 +1,95 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// + /// A push parser for RecordIO streams. + /// + /// + /// The RecordIO parser is a linear value type. It is intended to be created on the stack + /// and then repeatedly pushed buffers in a sequence. It then signals when the boundaries + /// of the RecordIO tokens are reached. + /// + struct RecordIOParser final + { + [[nodiscard]] RecordIOParser() noexcept = default; + ~RecordIOParser() noexcept = default; + RecordIOParser(const RecordIOParser& other) noexcept = delete; + RecordIOParser(RecordIOParser&& other) noexcept = default; + RecordIOParser& operator=(const RecordIOParser& other) noexcept = delete; + RecordIOParser& operator=(RecordIOParser&& other) noexcept = default; + + /// Describes the type of Hybrid Rows produced by the parser. + enum class ProductionType + { + /// No hybrid row was produced. The parser needs more data. + None = 0, + + /// A new segment row was produced. + Segment, + + /// A record in the current segment was produced. + Record, + }; + + /// True if a valid segment has been parsed. + [[nodiscard]] bool HaveSegment() const noexcept; + + /// If a valid segment has been parsed then current active segment, otherwise undefined. + [[nodiscard]] const Segment& GetSegment() const noexcept; + + /// Processes one buffers worth of data possibly advancing the parser state. + /// The buffer to consume. + /// + /// A tuple: {result, type, record, need, consumed} + ///

result: + /// if no error + /// has occurred, otherwise a valid + /// of the last error encountered + /// during parsing. + ///

+ ///

type: Indicates the type of Hybrid Row produced in record.

+ ///

record: If non-empty, then the body of the next record in the sequence.

+ ///

need: + /// The smallest number of bytes needed to advanced the parser state further. It is + /// recommended that Process not be called again until at least this number of bytes are available. + ///

+ ///

consumed: + /// The number of bytes consumed from the input buffer. This number may be less + /// than the total buffer size if the parser moved to a new state. + ///

+ ///
+ std::tuple, uint32_t, uint32_t> + Process(const cdb_core::ReadOnlyMemory& buffer) noexcept; + + private: + /// The states for the internal state machine. + /// Note: numerical ordering of these states matters. + enum class State : uint8_t + { + Start = 0, // Start: no buffers have yet been provided to the parser. + Error, // Unrecoverable parse error encountered. + NeedSegmentLength, // Parsing segment header length + NeedSegment, // Parsing segment header + NeedHeader, // Parsing HybridRow header + NeedRecord, // Parsing record header + NeedRow, // Parsing row body + }; + + State m_state; + std::unique_ptr m_segment; + std::unique_ptr m_record; + }; + + inline bool cdb_hr::RecordIOParser::HaveSegment() const noexcept { return m_state >= State::NeedHeader; } + + inline const cdb_hr::Segment& cdb_hr::RecordIOParser::GetSegment() const noexcept + { + cdb_core::Contract::Requires(HaveSegment()); + return *m_segment; + } +} diff --git a/src/Serialization/HybridRow.Native/Result.h b/src/Serialization/HybridRow.Native/Result.h new file mode 100644 index 0000000..5bc9536 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Result.h @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ +enum class Result +{ + Success = 0, + Failure = 1, + NotFound = 2, + Exists = 3, + TooBig = 4, + + /// + /// The type of an existing field does not match the expected type for this operation. + /// + TypeMismatch = 5, + + /// + /// An attempt to write in a read-only scope. + /// + InsufficientPermissions = 6, + + /// + /// An attempt to write a field that did not match its (optional) type constraints. + /// + TypeConstraint = 7, + + /// + /// The byte sequence could not be parsed as a valid row. + /// + InvalidRow = 8, + + /// + /// The byte sequence was too short for the requested action. + /// + InsufficientBuffer = 9, + + /// + /// The operation was cancelled. + /// + Canceled = 10, +}; +} diff --git a/src/Serialization/HybridRow.Native/RowBuffer.cpp b/src/Serialization/HybridRow.Native/RowBuffer.cpp new file mode 100644 index 0000000..255fcff --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowBuffer.cpp @@ -0,0 +1,2351 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "HybridRowVersion.h" +#include "HybridRowHeader.h" +#include "LayoutCode.h" +#include "LayoutCodeTraits.h" +#include "RowOptions.h" +#include "RowCursor.h" +#include "RowBuffer.h" +#include "LayoutType.h" +#include "Layout.h" +#include "LayoutResolver.h" + +namespace cdb_hr +{ + /// + /// represents a single item within a set/map scope that needs + /// to be indexed. + /// + /// + /// This structure is used when rebuilding a set/map index during row streaming via + /// . + /// + /// Each item encodes its offsets and length within the row. + /// + struct RowBuffer::UniqueIndexItem final + { + /// The layout code of the value. + LayoutCode Code; + + /// + /// If existing, the offset to the metadata of the existing field, otherwise the location to + /// insert a new field. + /// + uint32_t MetaOffset; + + /// If existing, the offset to the value of the existing field, otherwise undefined. + uint32_t ValueOffset; + + /// Size of the target element. + uint32_t Size; + }; + + RowBuffer::RowBuffer(uint32_t capacity, ISpanResizer* resizer) noexcept : + m_resizer(resizer), + m_buffer(m_resizer->Resize(capacity)), + m_resolver(nullptr), + m_length(0) { } + + RowBuffer::RowBuffer(cdb_core::Span buffer, HybridRowVersion version, const LayoutResolver* resolver, + ISpanResizer* resizer) noexcept : + m_resizer(resizer), + m_buffer(buffer), + m_resolver(resolver), + m_length(buffer.Length()) + { + cdb_core::Contract::Requires(buffer.Length() >= HybridRowHeader::Size); + + HybridRowHeader header = ReadHeader(0); + cdb_core::Contract::Invariant(header.GetVersion() == version); + const Layout& layout = resolver->Resolve(header.GetSchemaId()); + cdb_core::Contract::Assert(header.GetSchemaId() == layout.GetSchemaId()); + cdb_core::Contract::Invariant(HybridRowHeader::Size + layout.GetSize() <= m_length); + } + + void RowBuffer::Reset() noexcept + { + m_length = 0; + m_resolver = nullptr; + } + + #if false + byte[] RowBuffer::ToArray() const noexcept + { + return m_buffer.Slice(0, m_length).ToArray(); + } + + /// Copies the content of the buffer into the target stream. + void RowBuffer::WriteTo(Stream stream) const noexcept + { + stream.Write(m_buffer.Slice(0, m_length)); + } + + /// + /// Reads in the contents of the RowBuffer from an input stream and initializes the row buffer + /// with the associated layout and rowVersion. + /// + /// true if the serialization succeeded. false if the input stream was corrupted. + bool RowBuffer::ReadFrom(Stream inputStream, uint32_t bytesCount, HybridRowVersion rowVersion, const LayoutResolver* resolver) noexcept + { + Contract::Requires(inputStream != nullptr); + Contract::Assert(bytesCount >= HybridRowHeader::Size); + + Reset(); + m_resolver = resolver; + Ensure(bytesCount); + Contract::Assert(m_buffer.Length() >= bytesCount); + m_length = bytesCount; + Span active = m_buffer.Slice(0, bytesCount); + int bytesRead; + do + { + bytesRead = inputStream.Read(active); + active = active.Slice(bytesRead); + } + while (bytesRead != 0); + + if (active.Length() != 0) + { + return false; + } + + return InitReadFrom(rowVersion); + } + #endif + + /// + /// Reads in the contents of the RowBuffer from an existing block of memory and initializes + /// the row buffer with the associated layout and rowVersion. + /// + /// true if the serialization succeeded. false if the input stream was corrupted. + bool RowBuffer::ReadFrom(cdb_core::ReadOnlySpan input, HybridRowVersion rowVersion, + const LayoutResolver* resolver) noexcept + { + uint32_t bytesCount = input.Length(); + cdb_core::Contract::Assert(bytesCount >= HybridRowHeader::Size); + + Reset(); + m_resolver = resolver; + Ensure(bytesCount); + cdb_core::Contract::Assert(m_buffer.Length() >= bytesCount); + input.CopyTo(m_buffer); + m_length = bytesCount; + return InitReadFrom(rowVersion); + } + + /// Initializes a row to the minimal size for the given layout. + /// The version of the Hybrid Row format to use for encoding this row. + /// The layout that describes the column layout of the row. + /// The resolver for UDTs. + /// + /// The row is initialized to default row for the given layout. All fixed columns have their + /// default values. All variable columns are null. No sparse columns are present. The row is valid. + /// + void RowBuffer::InitLayout(HybridRowVersion version, const Layout& layout, const LayoutResolver* resolver) noexcept + { + m_resolver = resolver; + + // Ensure sufficient space for fixed schema fields. + Ensure(HybridRowHeader::Size + layout.GetSize()); + m_length = HybridRowHeader::Size + layout.GetSize(); + + // Clear all presence bits. + m_buffer.Slice(HybridRowHeader::Size, layout.GetSize()).Fill(std::byte{0}); + + // Set the header. + WriteHeader(0, HybridRowHeader{version, layout.GetSchemaId()}); + } + + void RowBuffer::WriteHeader(uint32_t offset, HybridRowHeader value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + HybridRowHeader RowBuffer::ReadHeader(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteSchemaId(uint32_t offset, SchemaId value) noexcept + { + WriteInt32(offset, value.Id()); + } + + SchemaId RowBuffer::ReadSchemaId(uint32_t offset) const noexcept + { + return SchemaId(ReadInt32(offset)); + } + + void RowBuffer::SetBit(uint32_t offset, LayoutBit bit) noexcept + { + // If the bit to be read is itself undefined, then return true. This is used to + // short-circuit the non-nullable vs. nullable field cases. For nullable fields + // the bit indicates the presence bit to be read for the field. For non-nullable + // fields there is no presence bit, and so "undefined" is passed and true is always + // returned indicating the field *is* present (as non-nullable fields are ALWAYS + // present). + if (bit.IsInvalid()) + { + return; + } + + m_buffer[bit.GetOffset(offset)] |= static_cast(1 << bit.GetBit()); + } + + void RowBuffer::UnsetBit(uint32_t offset, LayoutBit bit) noexcept + { + cdb_core::Contract::Assert(bit != LayoutBit::Invalid()); + m_buffer[bit.GetOffset(offset)] &= static_cast(~(1 << bit.GetBit())); + } + + bool RowBuffer::ReadBit(uint32_t offset, LayoutBit bit) const noexcept + { + // If the bit to be read is itself undefined, then return true. This is used to + // short-circuit the non-nullable vs. nullable field cases. For nullable fields + // the bit indicates the presence bit to be read for the field. For non-nullable + // fields there is no presence bit, and so "undefined" is passed and true is always + // returned indicating the field *is* present (as non-nullable fields are ALWAYS + // present). + if (bit.IsInvalid()) + { + return true; + } + + return (m_buffer[bit.GetOffset(offset)] & static_cast(1 << bit.GetBit())) != static_cast(0); + } + + void RowBuffer::DeleteVariable(uint32_t offset, bool isVarint) noexcept + { + auto [existingValueBytes, spaceAvailable] = Read7BitEncodedUInt(offset); + if (!isVarint) + { + cdb_core::Contract::Invariant(existingValueBytes < m_length); + spaceAvailable += static_cast(existingValueBytes); // "size" already in spaceAvailable + } + + m_buffer.Slice(offset + spaceAvailable, m_length - (offset + spaceAvailable)).CopyTo(m_buffer.Slice(offset)); + m_length -= spaceAvailable; + } + + void RowBuffer::WriteInt8(uint32_t offset, int8_t value) noexcept + { + m_buffer[offset] = static_cast(value); + } + + int8_t RowBuffer::ReadInt8(uint32_t offset) const noexcept + { + return static_cast(m_buffer[offset]); + } + + void RowBuffer::WriteUInt8(uint32_t offset, uint8_t value) noexcept + { + m_buffer[offset] = static_cast(value); + } + + uint8_t RowBuffer::ReadUInt8(uint32_t offset) const noexcept + { + return static_cast(m_buffer[offset]); + } + + void RowBuffer::WriteInt16(uint32_t offset, int16_t value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + int16_t RowBuffer::ReadInt16(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteUInt16(uint32_t offset, uint16_t value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + uint16_t RowBuffer::ReadUInt16(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteInt32(uint32_t offset, int32_t value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + int32_t RowBuffer::ReadInt32(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::IncrementUInt32(uint32_t offset, uint32_t increment) noexcept + { + cdb_core::MemoryMarshal::Cast(m_buffer.Slice(offset))[0] += increment; + } + + void RowBuffer::DecrementUInt32(uint32_t offset, uint32_t decrement) noexcept + { + cdb_core::MemoryMarshal::Cast(m_buffer.Slice(offset))[0] -= decrement; + } + + void RowBuffer::WriteUInt32(uint32_t offset, uint32_t value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + uint32_t RowBuffer::ReadUInt32(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteInt64(uint32_t offset, int64_t value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + int64_t RowBuffer::ReadInt64(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteUInt64(uint32_t offset, uint64_t value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + uint64_t RowBuffer::ReadUInt64(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + uint32_t RowBuffer::Write7BitEncodedUInt(uint32_t offset, uint64_t value) noexcept + { + // Write out an unsigned long 7 bits at a time. The high bit of the byte, + // when set, indicates there are more bytes. + uint32_t i = 0; + while (value >= 0x80) + { + WriteUInt8(offset + i++, static_cast(value | 0x80)); + value >>= 7; + } + + WriteUInt8(offset + i++, static_cast(value)); + return i; + } + + /// Returns (uint64_t value, uint32_t lenInBytes) + std::tuple RowBuffer::Read7BitEncodedUInt(uint32_t offset) const noexcept + { + // Read out an unsigned long 7 bits at a time. The high bit of the byte, + // when set, indicates there are more bytes. + uint64_t b = static_cast(m_buffer[offset]); + if (b < 0x80) + { + return {b, 1}; + } + + uint64_t retval = b & 0x7F; + int shift = 7; + do + { + cdb_core::Contract::Assert(shift < 10 * 7); + b = static_cast(m_buffer[++offset]); + retval |= (b & 0x7F) << shift; + shift += 7; + } while (b >= 0x80); + + return {retval, shift / 7}; + } + + uint32_t RowBuffer::Write7BitEncodedInt(uint32_t offset, int64_t value) noexcept + { + return Write7BitEncodedUInt(offset, RotateSignToLsb(value)); + } + + std::tuple RowBuffer::Read7BitEncodedInt(uint32_t offset) const noexcept + { + auto [value, lenInBytes] = Read7BitEncodedUInt(offset); + return {RotateSignToMsb(value), lenInBytes}; + } + + void RowBuffer::WriteFloat32(uint32_t offset, float32_t value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + float32_t RowBuffer::ReadFloat32(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteFloat64(uint32_t offset, float64_t value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + float64_t RowBuffer::ReadFloat64(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteFloat128(uint32_t offset, Float128 value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + Float128 RowBuffer::ReadFloat128(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteDecimal(uint32_t offset, Decimal value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + Decimal RowBuffer::ReadDecimal(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteDateTime(uint32_t offset, DateTime value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + DateTime RowBuffer::ReadDateTime(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteUnixDateTime(uint32_t offset, UnixDateTime value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + UnixDateTime RowBuffer::ReadUnixDateTime(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteGuid(uint32_t offset, Guid value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + Guid RowBuffer::ReadGuid(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + void RowBuffer::WriteMongoDbObjectId(uint32_t offset, MongoDbObjectId value) noexcept + { + cdb_core::MemoryMarshal::Write(m_buffer.Slice(offset), value); + } + + MongoDbObjectId RowBuffer::ReadMongoDbObjectId(uint32_t offset) const noexcept + { + return cdb_core::MemoryMarshal::Read(m_buffer.Slice(offset)); + } + + std::string_view RowBuffer::ReadFixedString(uint32_t offset, uint32_t len) const noexcept + { + return cdb_core::Utf8Span::UnsafeFromUtf8BytesNoValidation(m_buffer.Slice(offset, len)); + } + + void RowBuffer::WriteFixedString(uint32_t offset, std::string_view value) noexcept + { + cdb_core::Utf8Span::GetSpan(value).CopyTo(m_buffer.Slice(offset)); + } + + cdb_core::ReadOnlySpan RowBuffer::ReadFixedBinary(uint32_t offset, uint32_t len) const noexcept + { + return m_buffer.Slice(offset, len); + } + + void RowBuffer::WriteFixedBinary(uint32_t offset, cdb_core::ReadOnlySpan value, uint32_t len) noexcept + { + value.CopyTo(m_buffer.Slice(offset, len)); + if (value.Length() < len) + { + m_buffer.Slice(offset + value.Length(), len - value.Length()).Fill(std::byte{0}); + } + } + + std::string_view RowBuffer::ReadVariableString(uint32_t offset) const noexcept + { + auto [value, sizeLenInBytes] = ReadString(offset); + return value; + } + + /// number of bytes shifted (may be negative). + int32_t RowBuffer::WriteVariableString(uint32_t offset, const std::string_view value, bool exists) noexcept + { + uint32_t numBytes = static_cast(value.size()); + auto [spaceNeeded, shift] = EnsureVariable(offset, false, numBytes, exists); + + uint32_t sizeLenInBytes = WriteString(offset, value); + cdb_core::Contract::Assert(spaceNeeded == numBytes + sizeLenInBytes); + m_length += shift; + return shift; + } + + cdb_core::ReadOnlySpan RowBuffer::ReadVariableBinary(uint32_t offset) const noexcept + { + auto [value, sizeLenInBytes] = ReadBinary(offset); + return value; + } + + /// number of bytes shifted (may be negative). + int32_t RowBuffer::WriteVariableBinary(uint32_t offset, cdb_core::ReadOnlySpan value, bool exists) noexcept + { + uint32_t numBytes = value.Length(); + auto [spaceNeeded, shift] = EnsureVariable(offset, false, numBytes, exists); + + uint32_t sizeLenInBytes = WriteBinary(offset, value); + cdb_core::Contract::Assert(spaceNeeded == numBytes + sizeLenInBytes); + m_length += shift; + return shift; + } + + int64_t RowBuffer::ReadVariableInt(uint32_t offset) const noexcept + { + auto [value, lenInBytes] = Read7BitEncodedInt(offset); + return value; + } + + /// number of bytes shifted (may be negative). + int32_t RowBuffer::WriteVariableInt(uint32_t offset, int64_t value, bool exists) noexcept + { + uint32_t numBytes = Count7BitEncodedInt(value); + auto [spaceNeeded, shift] = EnsureVariable(offset, true, numBytes, exists); + + uint32_t sizeLenInBytes = Write7BitEncodedInt(offset, value); + cdb_core::Contract::Assert(sizeLenInBytes == numBytes); + cdb_core::Contract::Assert(spaceNeeded == numBytes); + m_length += shift; + return shift; + } + + uint64_t RowBuffer::ReadVariableUInt(uint32_t offset) const noexcept + { + auto [value, lenInBytes] = Read7BitEncodedUInt(offset); + return value; + } + + /// number of bytes shifted (may be negative). + int32_t RowBuffer::WriteVariableUInt(uint32_t offset, uint64_t value, bool exists) noexcept + { + uint32_t numBytes = Count7BitEncodedUInt(value); + auto [spaceNeeded, shift] = EnsureVariable(offset, true, numBytes, exists); + + uint32_t sizeLenInBytes = Write7BitEncodedUInt(offset, value); + cdb_core::Contract::Assert(sizeLenInBytes == numBytes); + cdb_core::Contract::Assert(spaceNeeded == numBytes); + m_length += shift; + return shift; + } + + const LayoutType* RowBuffer::ReadSparseTypeCode(uint32_t offset) const noexcept + { + return LayoutLiteral::FromCode(static_cast(ReadUInt8(offset))); + } + + void RowBuffer::WriteSparseTypeCode(uint32_t offset, LayoutCode code) noexcept + { + WriteUInt8(offset, static_cast(code)); + } + + template + T RowBuffer::ReadSparseFixed(const TLayoutType* layoutType, RowCursor& edit) const noexcept + { + ValidateSparsePrimitiveTypeCode(edit, layoutType); + edit.m_endOffset = edit.m_valueOffset + sizeof(T); + return std::invoke(ReadFunc, this, edit.m_valueOffset); + } + + template + void RowBuffer::WriteSparseFixed(const TLayoutType* layoutType, RowCursor& edit, T value, + UpdateOptions options) noexcept + { + uint32_t numBytes = sizeof(T); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, layoutType, {}, numBytes, options); + WriteSparseMetadata(edit, layoutType, {}, metaBytes); + std::invoke(WriteFunc, this, edit.m_valueOffset, value); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + sizeof(T)); + edit.m_endOffset = edit.m_metaOffset + spaceNeeded; + m_length += shift; + } + + int8_t RowBuffer::ReadSparseInt8(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Int8, edit); + } + + void RowBuffer::WriteSparseInt8(RowCursor& edit, int8_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Int8, edit, value, options); + } + + int16_t RowBuffer::ReadSparseInt16(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Int16, edit); + } + + void RowBuffer::WriteSparseInt16(RowCursor& edit, int16_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Int16, edit, value, options); + } + + int32_t RowBuffer::ReadSparseInt32(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Int32, edit); + } + + void RowBuffer::WriteSparseInt32(RowCursor& edit, int32_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Int32, edit, value, options); + } + + int64_t RowBuffer::ReadSparseInt64(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Int64, edit); + } + + void RowBuffer::WriteSparseInt64(RowCursor& edit, int64_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Int64, edit, value, options); + } + + uint8_t RowBuffer::ReadSparseUInt8(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::UInt8, edit); + } + + void RowBuffer::WriteSparseUInt8(RowCursor& edit, uint8_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::UInt8, edit, value, options); + } + + uint16_t RowBuffer::ReadSparseUInt16(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::UInt16, edit); + } + + void RowBuffer::WriteSparseUInt16(RowCursor& edit, uint16_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::UInt16, edit, value, options); + } + + uint32_t RowBuffer::ReadSparseUInt32(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::UInt32, edit); + } + + void RowBuffer::WriteSparseUInt32(RowCursor& edit, uint32_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::UInt32, edit, value, options); + } + + uint64_t RowBuffer::ReadSparseUInt64(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::UInt64, edit); + } + + void RowBuffer::WriteSparseUInt64(RowCursor& edit, uint64_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::UInt64, edit, value, options); + } + + int64_t RowBuffer::ReadSparseVarInt(RowCursor& edit) const noexcept + { + ValidateSparsePrimitiveTypeCode(edit, &LayoutLiteral::VarInt); + auto [value, sizeLenInBytes] = Read7BitEncodedInt(edit.m_valueOffset); + edit.m_endOffset = edit.m_valueOffset + sizeLenInBytes; + return value; + } + + void RowBuffer::WriteSparseVarInt(RowCursor& edit, int64_t value, UpdateOptions options) noexcept + { + uint32_t numBytes = Count7BitEncodedInt(value); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, &LayoutLiteral::VarInt, {}, numBytes, options); + WriteSparseMetadata(edit, &LayoutLiteral::VarInt, {}, metaBytes); + uint32_t sizeLenInBytes = Write7BitEncodedInt(edit.m_valueOffset, value); + cdb_core::Contract::Assert(sizeLenInBytes == numBytes); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + sizeLenInBytes); + edit.m_endOffset = edit.m_metaOffset + spaceNeeded; + m_length += shift; + } + + uint64_t RowBuffer::ReadSparseVarUInt(RowCursor& edit) const noexcept + { + ValidateSparsePrimitiveTypeCode(edit, &LayoutLiteral::VarUInt); + auto [value, sizeLenInBytes] = Read7BitEncodedUInt(edit.m_valueOffset); + edit.m_endOffset = edit.m_valueOffset + sizeLenInBytes; + return value; + } + + void RowBuffer::WriteSparseVarUInt(RowCursor& edit, uint64_t value, UpdateOptions options) noexcept + { + uint32_t numBytes = Count7BitEncodedUInt(value); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, &LayoutLiteral::VarUInt, {}, numBytes, options); + WriteSparseMetadata(edit, &LayoutLiteral::VarUInt, {}, metaBytes); + uint32_t sizeLenInBytes = Write7BitEncodedUInt(edit.m_valueOffset, value); + cdb_core::Contract::Assert(sizeLenInBytes == numBytes); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + sizeLenInBytes); + edit.m_endOffset = edit.m_metaOffset + spaceNeeded; + m_length += shift; + } + + float32_t RowBuffer::ReadSparseFloat32(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Float32, edit); + } + + void RowBuffer::WriteSparseFloat32(RowCursor& edit, float32_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Float32, edit, value, options); + } + + float64_t RowBuffer::ReadSparseFloat64(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Float64, edit); + } + + void RowBuffer::WriteSparseFloat64(RowCursor& edit, float64_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Float64, edit, value, options); + } + + float128_t RowBuffer::ReadSparseFloat128(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Float128, edit); + } + + void RowBuffer::WriteSparseFloat128(RowCursor& edit, float128_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Float128, edit, value, options); + } + + decimal_t RowBuffer::ReadSparseDecimal(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Decimal, edit); + } + + void RowBuffer::WriteSparseDecimal(RowCursor& edit, decimal_t value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Decimal, edit, value, options); + } + + DateTime RowBuffer::ReadSparseDateTime(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::DateTime, edit); + } + + void RowBuffer::WriteSparseDateTime(RowCursor& edit, DateTime value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::DateTime, edit, value, options); + } + + UnixDateTime RowBuffer::ReadSparseUnixDateTime(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::UnixDateTime, edit); + } + + void RowBuffer::WriteSparseUnixDateTime(RowCursor& edit, UnixDateTime value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::UnixDateTime, edit, value, options); + } + + Guid RowBuffer::ReadSparseGuid(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::Guid, edit); + } + + void RowBuffer::WriteSparseGuid(RowCursor& edit, Guid value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::Guid, edit, value, options); + } + + MongoDbObjectId RowBuffer::ReadSparseMongoDbObjectId(RowCursor& edit) const noexcept + { + return ReadSparseFixed(&LayoutLiteral::MongoDbObjectId, edit); + } + + void RowBuffer::WriteSparseMongoDbObjectId(RowCursor& edit, MongoDbObjectId value, UpdateOptions options) noexcept + { + WriteSparseFixed(&LayoutLiteral::MongoDbObjectId, edit, value, + options); + } + + NullValue RowBuffer::ReadSparseNull(RowCursor& edit) const noexcept + { + ValidateSparsePrimitiveTypeCode(edit, &LayoutLiteral::Null); + edit.m_endOffset = edit.m_valueOffset; + return {}; + } + + void RowBuffer::WriteSparseNull(RowCursor& edit, NullValue value, UpdateOptions options) noexcept + { + uint32_t numBytes = 0; + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, &LayoutLiteral::Null, {}, numBytes, options); + WriteSparseMetadata(edit, &LayoutLiteral::Null, {}, metaBytes); + cdb_core::Contract::Assert(spaceNeeded == metaBytes); + edit.m_endOffset = edit.m_metaOffset + spaceNeeded; + m_length += shift; + } + + bool RowBuffer::ReadSparseBool(RowCursor& edit) const noexcept + { + ValidateSparsePrimitiveTypeCode(edit, &LayoutLiteral::Boolean); + edit.m_endOffset = edit.m_valueOffset; + return edit.m_cellType == &LayoutLiteral::Boolean; + } + + void RowBuffer::WriteSparseBool(RowCursor& edit, bool value, UpdateOptions options) noexcept + { + uint32_t numBytes = 0; + auto [metaBytes, spaceNeeded, shift] = EnsureSparse( + edit, + value ? &LayoutLiteral::Boolean : &LayoutLiteral::BooleanFalse, + {}, + numBytes, + options); + WriteSparseMetadata(edit, value ? &LayoutLiteral::Boolean : &LayoutLiteral::BooleanFalse, {}, metaBytes); + cdb_core::Contract::Assert(spaceNeeded == metaBytes); + edit.m_endOffset = edit.m_metaOffset + spaceNeeded; + m_length += shift; + } + + std::string_view RowBuffer::ReadSparseString(RowCursor& edit) const noexcept + { + ValidateSparsePrimitiveTypeCode(edit, &LayoutLiteral::Utf8); + auto [str, sizeLenInBytes] = ReadString(edit.m_valueOffset); + edit.m_endOffset = edit.m_valueOffset + sizeLenInBytes + static_cast(str.size()); + return str; + } + + void RowBuffer::WriteSparseString(RowCursor& edit, std::string_view value, UpdateOptions options) noexcept + { + uint32_t len = static_cast(value.size()); + uint32_t numBytes = len + Count7BitEncodedUInt(static_cast(len)); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, &LayoutLiteral::Utf8, {}, numBytes, options); + WriteSparseMetadata(edit, &LayoutLiteral::Utf8, {}, metaBytes); + uint32_t sizeLenInBytes = WriteString(edit.m_valueOffset, value); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + len + sizeLenInBytes); + edit.m_endOffset = edit.m_metaOffset + spaceNeeded; + m_length += shift; + } + + cdb_core::ReadOnlySpan RowBuffer::ReadSparseBinary(RowCursor& edit) const noexcept + { + ValidateSparsePrimitiveTypeCode(edit, &LayoutLiteral::Binary); + auto [span, sizeLenInBytes] = ReadBinary(edit.m_valueOffset); + edit.m_endOffset = edit.m_valueOffset + sizeLenInBytes + span.Length(); + return span; + } + + void RowBuffer::WriteSparseBinary(RowCursor& edit, cdb_core::ReadOnlySpan value, UpdateOptions options) noexcept + { + uint32_t len = value.Length(); + uint32_t numBytes = len + Count7BitEncodedUInt(static_cast(len)); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, &LayoutLiteral::Binary, {}, numBytes, options); + WriteSparseMetadata(edit, &LayoutLiteral::Binary, {}, metaBytes); + uint32_t sizeLenInBytes = WriteBinary(edit.m_valueOffset, value); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + len + sizeLenInBytes); + edit.m_endOffset = edit.m_metaOffset + spaceNeeded; + m_length += shift; + } + + RowCursor RowBuffer::WriteSparseObject(RowCursor& edit, const LayoutScope* scopeType, UpdateOptions options) noexcept + { + uint32_t numBytes = sizeof(LayoutCode); // end scope type code. + TypeArgumentList typeArgs{}; + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, {}, metaBytes); + WriteSparseTypeCode(edit.m_valueOffset, LayoutCode::EndScope); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + + m_length += shift; + return RowCursor{ + *edit.m_layout, // layout + scopeType, // scopeType + {}, // scopeTypeArgs + edit.m_valueOffset, // start + edit.m_valueOffset, // metaOffset + edit.m_valueOffset, // valueOffset + }; + } + + RowCursor RowBuffer::WriteSparseArray(RowCursor& edit, const LayoutScope* scopeType, UpdateOptions options) noexcept + { + uint32_t numBytes = sizeof(LayoutCode); // end scope type code. + TypeArgumentList typeArgs{}; + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, typeArgs, metaBytes); + WriteSparseTypeCode(edit.m_valueOffset, LayoutCode::EndScope); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + m_length += shift; + return RowCursor{ + *edit.m_layout, // layout + scopeType, // scopeType + typeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + edit.m_valueOffset, // metaOffset + edit.m_valueOffset, // valueOffset + }; + } + + RowCursor RowBuffer::WriteTypedArray(RowCursor& edit, const LayoutScope* scopeType, const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept + { + uint32_t numBytes = sizeof(uint32_t); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, typeArgs, metaBytes); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + WriteUInt32(edit.m_valueOffset, 0); + uint32_t valueOffset = edit.m_valueOffset + sizeof(uint32_t); // Point after the Size + m_length += shift; + return RowCursor{ + *edit.m_layout, // layout + scopeType, // scopeType + typeArgs, // scopeTypeArgs + edit.m_valueOffset, // start - Point at the Size + valueOffset, // metaOffset + valueOffset, // valueOffset + }; + } + + RowCursor RowBuffer::WriteTypedSet(RowCursor& edit, const LayoutScope* scopeType, const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept + { + uint32_t numBytes = sizeof(uint32_t); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, typeArgs, metaBytes); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + WriteUInt32(edit.m_valueOffset, 0); + uint32_t valueOffset = edit.m_valueOffset + sizeof(uint32_t); // Point after the Size + m_length += shift; + return RowCursor + { + *edit.m_layout, // layout + scopeType, // scopeType + typeArgs, // scopeTypeArgs + edit.m_valueOffset, // start - Point at the Size + valueOffset, // metaOffset + valueOffset, // valueOffset + }; + } + + RowCursor RowBuffer::WriteTypedMap(RowCursor& edit, const LayoutScope* scopeType, const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept + { + uint32_t numBytes = sizeof(uint32_t); // Sized scope. + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, typeArgs, metaBytes); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + WriteUInt32(edit.m_valueOffset, 0); + uint32_t valueOffset = edit.m_valueOffset + sizeof(uint32_t); // Point after the Size + m_length += shift; + return RowCursor + { + *edit.m_layout, // layout + scopeType, // scopeType + typeArgs, // scopeTypeArgs + edit.m_valueOffset, // start - Point at the Size + valueOffset, // metaOffset + valueOffset, // valueOffset + }; + } + + RowCursor RowBuffer::WriteSparseTuple(RowCursor& edit, const LayoutScope* scopeType, const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept + { + uint32_t numBytes = static_cast(sizeof(LayoutCode) * (1 + typeArgs.GetCount()) + ); // nulls for each element. + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, typeArgs, metaBytes); + uint32_t valueOffset = edit.m_valueOffset; + for (size_t i = 0; i < typeArgs.GetCount(); i++) + { + WriteSparseTypeCode(valueOffset, LayoutCode::Null); + valueOffset += sizeof(LayoutCode); + } + + WriteSparseTypeCode(valueOffset, LayoutCode::EndScope); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + m_length += shift; + return RowCursor + { + *edit.m_layout, // layout + scopeType, // scopeType + typeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + edit.m_valueOffset, // metaOffset + edit.m_valueOffset, // valueOffset + false, // immutable + static_cast(typeArgs.GetCount()), // count + }; + } + + RowCursor RowBuffer::WriteTypedTuple(RowCursor& edit, const LayoutScope* scopeType, const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept + { + uint32_t numBytes = CountDefaultValue(scopeType, typeArgs); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, typeArgs, metaBytes); + uint32_t numWritten = WriteDefaultValue(edit.m_valueOffset, scopeType, typeArgs); + cdb_core::Contract::Assert(numBytes == numWritten); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + m_length += shift; + + RowCursor newScope = RowCursor + { + *edit.m_layout, // layout + scopeType, // scopeType + typeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + edit.m_valueOffset, // metaOffset + edit.m_valueOffset, // valueOffset + false, // immutable + static_cast(typeArgs.GetCount()), // count + }; + newScope.MoveNext(*this); + return newScope; + } + + RowCursor RowBuffer::WriteNullable(RowCursor& edit, const LayoutScope* scopeType, const TypeArgumentList& typeArgs, + UpdateOptions options, bool hasValue) noexcept + { + uint32_t numBytes = CountDefaultValue(scopeType, typeArgs); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, typeArgs, metaBytes); + uint32_t numWritten = WriteDefaultValue(edit.m_valueOffset, scopeType, typeArgs); + cdb_core::Contract::Assert(numBytes == numWritten); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + if (hasValue) + { + WriteInt8(edit.m_valueOffset, 1); + } + + m_length += shift; + uint32_t valueOffset = edit.m_valueOffset + 1; + RowCursor newScope = RowCursor + { + *edit.m_layout, // layout + scopeType, // scopeType + typeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + valueOffset, // metaOffset + valueOffset, // valueOffset + false, // immutable + 2, // count + 1, // index + }; + newScope.MoveNext(*this); + return newScope; + } + + RowCursor RowBuffer::WriteSparseUDT(RowCursor& edit, const LayoutScope* scopeType, const Layout& udt, + UpdateOptions options) noexcept + { + TypeArgumentList typeArgs{udt.GetSchemaId()}; + uint32_t numBytes = udt.GetSize() + sizeof(LayoutCode); + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, scopeType, typeArgs, numBytes, options); + WriteSparseMetadata(edit, scopeType, typeArgs, metaBytes); + + // Clear all presence bits. + m_buffer.Slice(edit.m_valueOffset, udt.GetSize()).Fill(std::byte{0}); + + // Write scope terminator. + uint32_t valueOffset = edit.m_valueOffset + udt.GetSize(); + WriteSparseTypeCode(valueOffset, LayoutCode::EndScope); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + m_length += shift; + return RowCursor( + udt, // layout + scopeType, // scopeType + typeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + valueOffset, // metaOffset + valueOffset // valueOffset + ); + } + + void RowBuffer::DeleteSparse(RowCursor& edit) noexcept + { + // If the field doesn't exist, then nothing to do. + if (!edit.m_exists) + { + return; + } + + uint32_t numBytes = 0; + auto [metaBytes, spaceNeeded, shift] = EnsureSparse(edit, edit.m_cellType, edit.m_cellTypeArgs, numBytes, + RowOptions::Delete); + m_length += shift; + } + + uint64_t RowBuffer::RotateSignToLsb(int64_t value) noexcept + { + // Rotate sign to LSB + bool isNegative = value < 0; + uint64_t uvalue = static_cast(value); + uvalue = isNegative ? ((~uvalue + 1ul) << 1) + 1ul : uvalue << 1; + return uvalue; + } + + int64_t RowBuffer::RotateSignToMsb(uint64_t uvalue) noexcept + { + // Rotate sign to MSB + bool isNegative = uvalue % 2 != 0; + int64_t value = static_cast(isNegative ? (~(uvalue >> 1) + 1ul) | 0x8000000000000000ul : uvalue >> 1); + return value; + } + + uint32_t RowBuffer::ComputeVariableValueOffset(const Layout& layout, uint32_t scopeOffset, + uint32_t varIndex) const noexcept + { + uint32_t index = layout.GetNumFixed() + varIndex; + const tla::vector& columns = layout.GetColumns(); + cdb_core::Contract::Assert(index <= columns.size()); + uint32_t offset = scopeOffset + layout.GetSize(); + for (uint32_t i = layout.GetNumFixed(); i < index; i++) + { + const LayoutColumn& col = *columns[i]; + if (ReadBit(scopeOffset, col.GetNullBit())) + { + auto [valueSizeInBytes, lengthSizeInBytes] = Read7BitEncodedUInt(offset); + if (col.GetType()->IsVarint()) + { + offset += lengthSizeInBytes; + } + else + { + offset += static_cast(valueSizeInBytes) + lengthSizeInBytes; + } + } + } + + return offset; + } + + bool RowBuffer::SparseIteratorMoveNext(RowCursor& edit) const noexcept + { + if (edit.m_cellType != nullptr) + { + // Move to the next element of an indexed scope. + if (edit.m_scopeType->IsIndexedScope()) + { + edit.m_index++; + } + + // Skip forward to the end of the current value. + if (edit.m_endOffset != 0) + { + edit.m_metaOffset = edit.m_endOffset; + edit.m_endOffset = 0; + } + else + { + edit.m_metaOffset += SparseComputeSize(edit); + } + } + + // Check if reached end of buffer. + if (edit.m_metaOffset < m_length) + { + // Check if reached end of sized scope. + if (!edit.m_scopeType->IsSizedScope() || (edit.m_index != edit.m_count)) + { + // Read the metadata. + ReadSparseMetadata(edit); + + // Check if reached end of sparse scope. + if (!(edit.m_cellType->IsLayoutEndScope())) + { + edit.m_exists = true; + return true; + } + } + } + + edit.m_cellType = &LayoutLiteral::EndScope; + edit.m_exists = false; + edit.m_valueOffset = edit.m_metaOffset; + return false; + } + + RowCursor RowBuffer::SparseIteratorReadScope(const RowCursor& edit, bool immutable) const noexcept + { + LayoutCode scopeCode = edit.m_cellType->GetLayoutCode(); + const LayoutScope* scopeType = + static_cast(edit.m_cellType); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + switch (LayoutCodeTraits::ClearImmutableBit(scopeCode)) + { + case LayoutCode::ObjectScope: + case LayoutCode::ArrayScope: + { + return RowCursor{ + *edit.m_layout, // layout + scopeType, // scopeType + edit.m_cellTypeArgs, // scopeArgs + edit.m_valueOffset, // start + edit.m_valueOffset, // metaOffset + edit.m_valueOffset, // valueOffset + immutable, // immutable + }; + } + + case LayoutCode::TypedArrayScope: + case LayoutCode::TypedSetScope: + case LayoutCode::TypedMapScope: + { + uint32_t valueOffset = edit.m_valueOffset + sizeof(uint32_t); // Point after the Size + return RowCursor{ + *edit.m_layout, // layout + scopeType, // scopeType + edit.m_cellTypeArgs, // scopeTypeArgs + edit.m_valueOffset, // start = Point at the Size + valueOffset, // metaOffset + valueOffset, // valueOffset + immutable, // immutable + ReadUInt32(edit.m_valueOffset), // count + }; + } + + case LayoutCode::TypedTupleScope: + case LayoutCode::TupleScope: + case LayoutCode::TaggedScope: + case LayoutCode::Tagged2Scope: + { + return RowCursor{ + *edit.m_layout, // layout + scopeType, // scopeType + edit.m_cellTypeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + edit.m_valueOffset, // metaOffset + edit.m_valueOffset, // valueOffset + immutable, // immutable + static_cast(edit.m_cellTypeArgs.GetCount()), // count + }; + } + + case LayoutCode::NullableScope: + { + bool hasValue = ReadInt8(edit.m_valueOffset) != 0; + if (hasValue) + { + // Start at the T so it can be read. + uint32_t valueOffset = edit.m_valueOffset + 1; + return RowCursor{ + *edit.m_layout, // layout + scopeType, // scopeType + edit.m_cellTypeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + valueOffset, // metaOffset + valueOffset, // valueOffset + immutable, // immutable + 2, // count + 1, // index + }; + } + + // Start at the end of the scope, instead of at the T, so the T will be skipped. + TypeArgument typeArg = edit.m_cellTypeArgs[0]; + uint32_t valueOffset = edit.m_valueOffset + 1 + CountDefaultValue(typeArg.GetType(), typeArg.GetTypeArgs()); + return RowCursor{ + *edit.m_layout, // layout + scopeType, // scopeType + edit.m_cellTypeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + valueOffset, // metaOffset + valueOffset, // valueOffset + immutable, // immutable + 2, // count + 2, // index + }; + } + + case LayoutCode::Schema: + { + const Layout& udt = m_resolver->Resolve(edit.m_cellTypeArgs.GetSchemaId()); + uint32_t valueOffset = ComputeVariableValueOffset(udt, edit.m_valueOffset, udt.GetNumVariable()); + return RowCursor{ + udt, // layout + scopeType, // scopeType + edit.m_cellTypeArgs, // scopeTypeArgs + edit.m_valueOffset, // start + valueOffset, // metaOffset + valueOffset, // valueOffset + immutable, // immutable + }; + } + + default: + cdb_core::Contract::Fail("Not a scope type."); + } + } + + RowCursor RowBuffer::PrepareSparseMove(const RowCursor& scope, RowCursor& srcEdit) const noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUniqueScope()); + + cdb_core::Contract::Requires(scope.m_index == 0); + RowCursor dstEdit = scope.Clone(); + + dstEdit.m_metaOffset = scope.m_valueOffset; + uint32_t srcSize = SparseComputeSize(srcEdit); + uint32_t srcBytes = srcSize - (srcEdit.m_valueOffset - srcEdit.m_metaOffset); + while (dstEdit.m_index < dstEdit.m_count) + { + ReadSparseMetadata(dstEdit); + cdb_core::Contract::Assert(dstEdit.m_pathOffset == 0); + + uint32_t elmSize = UINT32_MAX; // defer calculating the full size until needed. + int cmp; + if (scope.m_scopeType->IsLayoutTypedMap()) + { + cmp = CompareKeyValueFieldValue(srcEdit, dstEdit); + } + else + { + elmSize = SparseComputeSize(dstEdit); + uint32_t elmBytes = elmSize - (dstEdit.m_valueOffset - dstEdit.m_metaOffset); + cmp = CompareFieldValue(srcEdit, srcBytes, dstEdit, elmBytes); + } + + if (cmp <= 0) + { + dstEdit.m_exists = cmp == 0; + return dstEdit; + } + + elmSize = (elmSize == UINT32_MAX) ? SparseComputeSize(dstEdit) : elmSize; + dstEdit.m_index++; + dstEdit.m_metaOffset += elmSize; + } + + dstEdit.m_exists = false; + dstEdit.m_cellType = &LayoutLiteral::EndScope; + dstEdit.m_valueOffset = dstEdit.m_metaOffset; + return dstEdit; + } + + void RowBuffer::TypedCollectionMoveField(RowCursor& dstEdit, RowCursor& srcEdit, RowOptions options) noexcept + { + uint32_t encodedSize = SparseComputeSize(srcEdit); + uint32_t numBytes = encodedSize - (srcEdit.m_valueOffset - srcEdit.m_metaOffset); + + // Insert the field metadata into its new location. + auto [metaBytes, spaceNeeded, shiftInsert] = EnsureSparse(dstEdit, srcEdit.m_cellType, srcEdit.m_cellTypeArgs, + numBytes, options); + WriteSparseMetadata(dstEdit, srcEdit.m_cellType, srcEdit.m_cellTypeArgs, metaBytes); + cdb_core::Contract::Assert(spaceNeeded == metaBytes + numBytes); + if (srcEdit.m_metaOffset >= dstEdit.m_metaOffset) + { + srcEdit.m_metaOffset += shiftInsert; + srcEdit.m_valueOffset += shiftInsert; + } + + // Copy the value bits from the old location. + m_buffer.Slice(srcEdit.m_valueOffset, numBytes).CopyTo(m_buffer.Slice(dstEdit.m_valueOffset)); + m_length += shiftInsert; + + // Delete the old location. + auto [_, __, shiftDelete] = EnsureSparse(srcEdit, srcEdit.m_cellType, srcEdit.m_cellTypeArgs, numBytes, + RowOptions::Delete); + cdb_core::Contract::Assert(shiftDelete < 0); + m_length += shiftDelete; + } + + Result RowBuffer::TypedCollectionUniqueIndexRebuild(RowCursor& scope) noexcept + { + cdb_core::Contract::Requires(scope.m_scopeType->IsUniqueScope()); + cdb_core::Contract::Requires(scope.m_index == 0); + RowCursor dstEdit = scope.Clone(); + if (dstEdit.m_count <= 1) + { + return Result::Success; + } + + // Compute Index Elements. + auto stackDeleter = [](void* p) -> void { ::_freea(static_cast(p)); }; + std::unique_ptr uniqueIndexBuffer{ + _malloca(sizeof(UniqueIndexItem) * dstEdit.m_count), + stackDeleter + }; + cdb_core::Span uniqueIndex{ + static_cast(uniqueIndexBuffer.get()), + dstEdit.m_count + }; + dstEdit.m_metaOffset = scope.m_valueOffset; + for (; dstEdit.m_index < dstEdit.m_count; dstEdit.m_index++) + { + ReadSparseMetadata(dstEdit); + cdb_core::Contract::Assert(dstEdit.m_pathOffset == 0); + uint32_t elmSize = SparseComputeSize(dstEdit); + + uniqueIndex[dstEdit.m_index] = UniqueIndexItem + { + dstEdit.m_cellType->GetLayoutCode(), // Code + dstEdit.m_metaOffset, // MetaOffset + dstEdit.m_valueOffset, // ValueOffset + elmSize, // Size + }; + + dstEdit.m_metaOffset += elmSize; + } + + // Create scratch space equal to the sum of the sizes of the scope's values. + // Implementation Note: theoretically this scratch space could be eliminated by + // performing the item move operations directly during the Insertion Sort, however, + // doing so might result in moving the same item multiple times. Under the assumption + // that items are relatively large, using scratch space requires each item to be moved + // AT MOST once. Given that row buffer memory is likely reused, scratch space is + // relatively memory efficient. + int32_t shift = dstEdit.m_metaOffset - scope.m_valueOffset; + + // Sort and check for duplicates. + if (!InsertionSort(scope, dstEdit, uniqueIndex)) + { + return Result::Exists; + } + + // Move elements. + uint32_t metaOffset = scope.m_valueOffset; + Ensure(m_length + shift); + m_buffer.Slice(metaOffset, m_length - metaOffset).CopyTo(m_buffer.Slice(metaOffset + shift)); + for (const UniqueIndexItem& x : uniqueIndex) + { + m_buffer.Slice(x.MetaOffset + shift, x.Size).CopyTo(m_buffer.Slice(metaOffset)); + metaOffset += x.Size; + } + + // Delete the scratch space (if necessary - if it doesn't just fall off the end of the row). + if (metaOffset != m_length) + { + m_buffer.Slice(metaOffset + shift, m_length - metaOffset).CopyTo(m_buffer.Slice(metaOffset)); + } + + #if _DEBUG + // Fill deleted bits (in debug builds) to detect overflow/alignment errors. + m_buffer.Slice(m_length, shift).Fill(std::byte{0xFF}); + #endif + + return Result::Success; + } + + uint32_t RowBuffer::CountSparsePath(RowCursor& edit) noexcept + { + if (!edit.m_writePathToken.IsNull()) + { + return edit.m_writePathToken.GetVarint().Length(); + } + + const auto& [found, token] = edit.m_layout->GetTokenizer().TryFindToken(edit.m_writePath); + if (found) + { + edit.m_writePathToken = token; + return edit.m_writePathToken.GetVarint().Length(); + } + + size_t numBytes = edit.m_writePath.size(); + uint32_t sizeLenInBytes = Count7BitEncodedUInt( + static_cast(edit.m_layout->GetTokenizer().GetCount() + numBytes)); + return static_cast(sizeLenInBytes + numBytes); + } + + uint32_t RowBuffer::Count7BitEncodedUInt(uint64_t value) noexcept + { + // Count the number of bytes needed to write out an int 7 bits at a time. + uint32_t i = 0; + while (value >= 0x80) + { + i++; + value >>= 7; + } + + i++; + return i; + } + + uint32_t RowBuffer::Count7BitEncodedInt(int64_t value) noexcept + { + return Count7BitEncodedUInt(RotateSignToLsb(value)); + } + + bool RowBuffer::InitReadFrom(HybridRowVersion rowVersion) const noexcept + { + HybridRowHeader header = ReadHeader(0); + const Layout& layout = m_resolver->Resolve(header.GetSchemaId()); + cdb_core::Contract::Assert(header.GetSchemaId() == layout.GetSchemaId()); + if ((header.GetVersion() != rowVersion) || (HybridRowHeader::Size + layout.GetSize() > m_length)) + { + return false; + } + + return true; + } + + uint32_t RowBuffer::SkipScope(RowCursor& edit) const noexcept + { + while (SparseIteratorMoveNext(edit)) { } + + if (!edit.m_scopeType->IsSizedScope()) + { + edit.m_metaOffset += sizeof(LayoutCode); // Move past the end of scope marker. + } + + return edit.m_metaOffset; + } + + int RowBuffer::CompareFieldValue(const RowCursor& left, int leftLen, const RowCursor& right, + int rightLen) const noexcept + { + if (left.m_cellType->GetLayoutCode() < right.m_cellType->GetLayoutCode()) + { + return -1; + } + + if (left.m_cellType == right.m_cellType) + { + if (leftLen < rightLen) + { + return -1; + } + + if (leftLen == rightLen) + { + return m_buffer.Slice(left.m_valueOffset, leftLen).SequenceCompareTo(m_buffer.Slice(right.m_valueOffset, + rightLen)); + } + } + + return 1; + } + + int RowBuffer::CompareKeyValueFieldValue(const RowCursor& left, const RowCursor& right) const noexcept + { + cdb_core::Contract::Requires(left.m_cellType->IsLayoutTypedTuple()); + cdb_core::Contract::Requires(right.m_cellType->IsLayoutTypedTuple()); + const LayoutTypedTuple* leftScopeType = reinterpret_cast(left.m_cellType); + const LayoutTypedTuple* rightScopeType = reinterpret_cast(right.m_cellType); + cdb_core::Contract::Requires(left.m_cellTypeArgs.GetCount() == 2); + cdb_core::Contract::Requires(left.m_cellTypeArgs == right.m_cellTypeArgs); + + RowCursor leftKey = RowCursor + { + *left.m_layout, // layout + leftScopeType, // scopeType + left.m_cellTypeArgs, // scopeTypeArgs + left.m_valueOffset, // start + left.m_valueOffset, // metaOffset + 0, // valueOffset + }; + + ReadSparseMetadata(leftKey); + cdb_core::Contract::Assert(leftKey.m_pathOffset == 0); + uint32_t leftKeyLen = SparseComputeSize(leftKey) - (leftKey.m_valueOffset - leftKey.m_metaOffset); + + RowCursor rightKey = RowCursor + { + *right.m_layout, // layout + rightScopeType, // scopeType + right.m_cellTypeArgs, // scopeTypeArgs + right.m_valueOffset, // start + right.m_valueOffset, // metaOffset + 0, // valueOffset + }; + + ReadSparseMetadata(rightKey); + cdb_core::Contract::Assert(rightKey.m_pathOffset == 0); + uint32_t rightKeyLen = SparseComputeSize(rightKey) - (rightKey.m_valueOffset - rightKey.m_metaOffset); + + return CompareFieldValue(leftKey, leftKeyLen, rightKey, rightKeyLen); + } + + bool RowBuffer::InsertionSort(const RowCursor& scope, const RowCursor& dstEdit, + cdb_core::Span uniqueIndex) const noexcept + { + RowCursor leftEdit = dstEdit; + RowCursor rightEdit = dstEdit; + + for (int i = 1; i < static_cast(uniqueIndex.Length()); i++) + { + UniqueIndexItem x = uniqueIndex[i]; + leftEdit.m_cellType = LayoutLiteral::FromCode(x.Code); + leftEdit.m_metaOffset = x.MetaOffset; + leftEdit.m_valueOffset = x.ValueOffset; + uint32_t leftBytes = x.Size - (x.ValueOffset - x.MetaOffset); + + // Walk backwards searching for the insertion point for the item as position i. + int j; + for (j = i - 1; j >= 0; j--) + { + UniqueIndexItem y = uniqueIndex[j]; + rightEdit.m_cellType = LayoutLiteral::FromCode(y.Code); + rightEdit.m_metaOffset = y.MetaOffset; + rightEdit.m_valueOffset = y.ValueOffset; + + int cmp; + if (scope.m_scopeType->IsLayoutTypedMap()) + { + cmp = CompareKeyValueFieldValue(leftEdit, rightEdit); + } + else + { + uint32_t rightBytes = y.Size - (y.ValueOffset - y.MetaOffset); + cmp = CompareFieldValue(leftEdit, leftBytes, rightEdit, rightBytes); + } + + // If there are duplicates then fail. + if (cmp == 0) + { + return false; + } + + if (cmp > 0) + { + break; + } + + // Swap the jth item to the right to make space for the ith item which is smaller. + uniqueIndex[j + 1] = uniqueIndex[j]; + } + + // Insert the ith item into the sorted array. + uniqueIndex[j + 1] = x; + } + + return true; + } + + /// Reads the length of an encoded, optionally tokenized, sparse path at the given offset. + /// + /// If the path is tokenized, then returns the token, the size of the token, and the path has no offset. + ///

+ /// If the path is not tokenized, then returns the token, the length of the path include the size prefix, the path offset begins after the length prefix. + /// + /// [uint32_t token, uint32_t pathLenInBytes, uint32_t pathOffset] + std::tuple RowBuffer::ReadSparsePathLen( + const Layout& layout, uint32_t offset) const noexcept + { + auto [token, sizeLenInBytes] = Read7BitEncodedUInt(offset); + const auto& tokenizer = layout.GetTokenizer(); + if (token < tokenizer.GetCount()) + { + return {static_cast(token), sizeLenInBytes, offset}; + } + + uint32_t numBytes = static_cast(token) - static_cast(tokenizer.GetCount()); + return {static_cast(token), numBytes + sizeLenInBytes, offset + sizeLenInBytes}; + } + + std::string_view RowBuffer::ReadSparsePath(const RowCursor& edit) const noexcept + { + const auto& [found, path] = edit.m_layout->GetTokenizer().TryFindString(static_cast(edit.m_pathToken)); + if (found) + { + return path; + } + + uint32_t numBytes = edit.m_pathToken - static_cast(edit.m_layout->GetTokenizer().GetCount()); + return cdb_core::Utf8Span::UnsafeFromUtf8BytesNoValidation(m_buffer.Slice(edit.m_pathOffset, numBytes)); + } + + void RowBuffer::WriteSparsePath(RowCursor& edit, uint32_t offset) noexcept + { + // Some scopes don't encode paths, therefore the cost is always zero. + if (edit.m_scopeType->IsIndexedScope()) + { + edit.m_pathToken = {}; + edit.m_pathOffset = {}; + return; + } + + #if _DEBUG + const auto& [found, _] = edit.m_layout->GetTokenizer().TryFindToken(edit.m_writePath); + cdb_core::Contract::Assert(!found || !edit.m_writePathToken.IsNull()); + #endif + + if (!edit.m_writePathToken.IsNull()) + { + edit.m_writePathToken.GetVarint().CopyTo(m_buffer.Slice(offset)); + edit.m_pathToken = static_cast(edit.m_writePathToken.GetId()); + edit.m_pathOffset = offset; + } + else + { + const tla::string& span = edit.m_writePath; + edit.m_pathToken = static_cast(edit.m_layout->GetTokenizer().GetCount() + span.size()); + uint32_t sizeLenInBytes = Write7BitEncodedUInt(offset, static_cast(edit.m_pathToken)); + edit.m_pathOffset = offset + sizeLenInBytes; + cdb_core::Utf8Span::GetSpan(span).CopyTo(m_buffer.Slice(offset + sizeLenInBytes)); + } + } + + std::tuple RowBuffer::ReadString(uint32_t offset) const noexcept + { + auto [numBytes, sizeLenInBytes] = Read7BitEncodedUInt(offset); + return { + cdb_core::Utf8Span::UnsafeFromUtf8BytesNoValidation( + m_buffer.Slice(offset + sizeLenInBytes, static_cast(numBytes))), + sizeLenInBytes + }; + } + + uint32_t RowBuffer::WriteString(uint32_t offset, std::string_view value) noexcept + { + uint32_t sizeLenInBytes = Write7BitEncodedUInt(offset, static_cast(value.size())); + cdb_core::Utf8Span::GetSpan(value).CopyTo(m_buffer.Slice(offset + sizeLenInBytes)); + return sizeLenInBytes; + } + + std::tuple, uint32_t> RowBuffer::ReadBinary(uint32_t offset) const noexcept + { + auto [numBytes, sizeLenInBytes] = Read7BitEncodedUInt(offset); + return {m_buffer.Slice(offset + sizeLenInBytes, static_cast(numBytes)), sizeLenInBytes}; + } + + uint32_t RowBuffer::WriteBinary(uint32_t offset, cdb_core::ReadOnlySpan value) noexcept + { + uint32_t sizeLenInBytes = Write7BitEncodedUInt(offset, static_cast(value.Length())); + value.CopyTo(m_buffer.Slice(offset + sizeLenInBytes)); + return sizeLenInBytes; + } + + void RowBuffer::Ensure(uint32_t size) noexcept + { + if (m_buffer.Length() < size) + { + cdb_core::Contract::Invariant(m_resizer != nullptr); + m_buffer = m_resizer->Resize(size, m_buffer); + } + } + + /// [uint32_t spaceNeeded, int32_t shift] + /// + /// spaceNeeded: the number of bytes needed to encode the value. + /// shift: the number of bytes the current content was shifted by (may be negative). + /// + std::tuple RowBuffer::EnsureVariable(uint32_t offset, bool isVarint, uint32_t numBytes, + bool exists) noexcept + { + uint32_t spaceNeeded; + uint32_t spaceAvailable = 0; + uint64_t existingValueBytes = 0; + if (exists) + { + std::tie(existingValueBytes, spaceAvailable) = Read7BitEncodedUInt(offset); + } + + if (isVarint) + { + spaceNeeded = numBytes; + } + else + { + spaceAvailable += static_cast(existingValueBytes); // size already in spaceAvailable + spaceNeeded = numBytes + Count7BitEncodedUInt(static_cast(numBytes)); + } + + int32_t shift = spaceNeeded - spaceAvailable; + if (shift > 0) + { + Ensure(m_length + shift); + } + m_buffer.Slice(offset + spaceAvailable, m_length - (offset + spaceAvailable)).CopyTo( + m_buffer.Slice(offset + spaceNeeded)); + return {spaceNeeded, shift}; + } + + #if !_DEBUG + void RowBuffer::ValidateSparsePrimitiveTypeCode(const RowCursor& edit, const LayoutType* code) const noexcept {} + #else + void RowBuffer::ValidateSparsePrimitiveTypeCode(const RowCursor& edit, const LayoutType* code) const noexcept + { + cdb_core::Contract::Assert(edit.m_exists); + + if (edit.m_scopeType->HasImplicitTypeCode(edit)) + { + if (edit.m_scopeType->IsLayoutNullable()) + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() == 1); + cdb_core::Contract::Assert(edit.m_index == 1); + cdb_core::Contract::Assert(edit.m_scopeTypeArgs[0].GetType() == code); + cdb_core::Contract::Assert(edit.m_scopeTypeArgs[0].GetTypeArgs().GetCount() == 0); + } + else if (edit.m_scopeType->IsFixedArity()) + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() > edit.m_index); + cdb_core::Contract::Assert(edit.m_scopeTypeArgs[edit.m_index].GetType() == code); + cdb_core::Contract::Assert(edit.m_scopeTypeArgs[edit.m_index].GetTypeArgs().GetCount() == 0); + } + else + { + cdb_core::Contract::Assert(edit.m_scopeTypeArgs.GetCount() == 1); + cdb_core::Contract::Assert(edit.m_scopeTypeArgs[0].GetType() == code); + cdb_core::Contract::Assert(edit.m_scopeTypeArgs[0].GetTypeArgs().GetCount() == 0); + } + } + else + { + if (code == &LayoutLiteral::Boolean) + { + code = ReadSparseTypeCode(edit.m_metaOffset); + cdb_core::Contract::Assert(code == &LayoutLiteral::Boolean || code == &LayoutLiteral::BooleanFalse); + } + else + { + cdb_core::Contract::Assert(ReadSparseTypeCode(edit.m_metaOffset) == code); + } + } + + if (edit.m_scopeType->IsIndexedScope()) + { + cdb_core::Contract::Assert(edit.m_pathOffset == 0); + cdb_core::Contract::Assert(edit.m_pathToken == 0); + } + else + { + auto [token, pathLenInBytes, pathOffset] = ReadSparsePathLen(*edit.m_layout, + edit.m_metaOffset + sizeof(LayoutCode)); + cdb_core::Contract::Assert(edit.m_pathOffset == pathOffset); + cdb_core::Contract::Assert(edit.m_pathToken == token); + } + } + #endif + + void RowBuffer::WriteSparseMetadata(RowCursor& edit, const LayoutType* cellType, const TypeArgumentList& typeArgs, + uint32_t metaBytes) noexcept + { + int metaOffset = edit.m_metaOffset; + if (!edit.m_scopeType->HasImplicitTypeCode(edit)) + { + metaOffset += cellType->WriteTypeArgument(*this, metaOffset, typeArgs); + } + + WriteSparsePath(edit, metaOffset); + edit.m_valueOffset = edit.m_metaOffset + metaBytes; + cdb_core::Contract::Assert(edit.m_valueOffset == edit.m_metaOffset + metaBytes); + } + + /// (uint32_t metaBytes, uint32_t spaceNeeded, int32_t shift) + std::tuple RowBuffer::EnsureSparse( + RowCursor& edit, + const LayoutType* cellType, + const TypeArgumentList& typeArgs, + uint32_t numBytes, + UpdateOptions options) noexcept + { + return EnsureSparse(edit, cellType, typeArgs, numBytes, static_cast(options)); + } + + /// (uint32_t metaBytes, uint32_t spaceNeeded, int32_t shift) + std::tuple RowBuffer::EnsureSparse( + RowCursor& edit, + const LayoutType* cellType, + const TypeArgumentList& typeArgs, + uint32_t numBytes, + RowOptions options) noexcept + { + uint32_t metaBytes; + uint32_t metaOffset = edit.m_metaOffset; + uint32_t spaceAvailable = 0; + + // Compute the metadata offsets + if (edit.m_scopeType->HasImplicitTypeCode(edit)) + { + metaBytes = 0; + } + else + { + metaBytes = cellType->CountTypeArgument(typeArgs); + } + + if (!edit.m_scopeType->IsIndexedScope()) + { + int pathLenInBytes = CountSparsePath(edit); + metaBytes += pathLenInBytes; + } + + if (edit.m_exists) + { + // Compute value offset for existing value to be overwritten. + spaceAvailable = SparseComputeSize(edit); + } + + uint32_t spaceNeeded = options == RowOptions::Delete ? 0 : metaBytes + numBytes; + int32_t shift = spaceNeeded - spaceAvailable; + if (shift > 0) + { + Ensure(m_length + shift); + } + + m_buffer.Slice(metaOffset + spaceAvailable, m_length - (metaOffset + spaceAvailable)) + .CopyTo(m_buffer.Slice(metaOffset + spaceNeeded)); + + #if _DEBUG + if (shift < 0) + { + // Fill deleted bits (in debug builds) to detect overflow/alignment errors. + m_buffer.Slice(m_length + shift, -shift).Fill(std::byte{0xFF}); + } + #endif + + // Update the stored size (fixed arity scopes don't store the size because it is implied by the type args). + if (edit.m_scopeType->IsSizedScope() && !edit.m_scopeType->IsFixedArity()) + { + if ((options == RowOptions::Insert) || (options == RowOptions::InsertAt) || ((options == RowOptions::Upsert) && ! + edit.m_exists)) + { + // Add one to the current scope count. + cdb_core::Contract::Assert(!edit.m_exists); + IncrementUInt32(edit.m_start, 1); + edit.m_count++; + } + else if ((options == RowOptions::Delete) && edit.m_exists) + { + // Subtract one from the current scope count. + cdb_core::Contract::Assert(ReadUInt32(edit.m_start) > 0); + DecrementUInt32(edit.m_start, 1); + edit.m_count--; + } + } + + if (options == RowOptions::Delete) + { + edit.m_cellType = nullptr; + edit.m_cellTypeArgs = {}; + edit.m_exists = false; + } + else + { + edit.m_cellType = cellType; + edit.m_cellTypeArgs = typeArgs; + edit.m_exists = true; + } + + return {metaBytes, spaceNeeded, shift}; + } + + void RowBuffer::ReadSparseMetadata(RowCursor& edit) const noexcept + { + if (edit.m_scopeType->HasImplicitTypeCode(edit)) + { + edit.m_scopeType->SetImplicitTypeCode(edit); + edit.m_valueOffset = edit.m_metaOffset; + } + else + { + edit.m_cellType = ReadSparseTypeCode(edit.m_metaOffset); + edit.m_valueOffset = edit.m_metaOffset + sizeof(LayoutCode); + edit.m_cellTypeArgs = {}; + if (edit.m_cellType->IsLayoutEndScope()) + { + // Reached end of current scope without finding another field. + edit.m_pathToken = 0; + edit.m_pathOffset = 0; + edit.m_valueOffset = edit.m_metaOffset; + return; + } + + uint32_t sizeLenInBytes; + std::tie(edit.m_cellTypeArgs, sizeLenInBytes) = edit.m_cellType->ReadTypeArgumentList(*this, edit.m_valueOffset); + edit.m_valueOffset += sizeLenInBytes; + } + + edit.m_scopeType->ReadSparsePath(*this, edit); + } + + uint32_t RowBuffer::SparseComputeSize(const RowCursor& edit) const noexcept + { + if (!edit.m_cellType->IsLayoutScope()) + { + return SparseComputePrimitiveSize(edit.m_cellType, edit.m_metaOffset, edit.m_valueOffset); + } + + // Compute offset to end of value for current value. + RowCursor newScope = SparseIteratorReadScope(edit, true); + return SkipScope(newScope) - edit.m_metaOffset; + } + + ///

Compute the size of a sparse (primitive) field. + /// The type of the current sparse field. + /// The 0-based offset from the beginning of the row where the field begins. + /// + /// The 0-based offset from the beginning of the row where the field's value + /// begins. + /// + /// The length (in bytes) of the encoded field including the metadata and the value. + uint32_t RowBuffer::SparseComputePrimitiveSize(const LayoutType* cellType, uint32_t metaOffset, + uint32_t valueOffset) const noexcept + { + uint32_t metaBytes = valueOffset - metaOffset; + LayoutCode code = cellType->GetLayoutCode(); + switch (code) + { + case LayoutCode::Null: + cdb_core::Contract::Assert(LayoutLiteral::Null.GetSize() == 0); + return metaBytes; + + case LayoutCode::Boolean: + case LayoutCode::BooleanFalse: + cdb_core::Contract::Assert(LayoutLiteral::Boolean.GetSize() == 0); + return metaBytes; + + case LayoutCode::Int8: + return metaBytes + sizeof(int8_t); + + case LayoutCode::Int16: + return metaBytes + sizeof(int16_t); + + case LayoutCode::Int32: + return metaBytes + sizeof(int32_t); + + case LayoutCode::Int64: + return metaBytes + sizeof(int64_t); + + case LayoutCode::UInt8: + return metaBytes + sizeof(uint8_t); + + case LayoutCode::UInt16: + return metaBytes + sizeof(uint16_t); + + case LayoutCode::UInt32: + return metaBytes + sizeof(uint32_t); + + case LayoutCode::UInt64: + return metaBytes + sizeof(uint64_t); + + case LayoutCode::Float32: + return metaBytes + sizeof(float32_t); + + case LayoutCode::Float64: + return metaBytes + sizeof(float64_t); + + case LayoutCode::Float128: + return metaBytes + sizeof(float128_t); + + case LayoutCode::Decimal: + return metaBytes + sizeof(decimal_t); + + case LayoutCode::DateTime: + return metaBytes + sizeof(DateTime); + + case LayoutCode::UnixDateTime: + return metaBytes + sizeof(UnixDateTime); + + case LayoutCode::Guid: + return metaBytes + sizeof(Guid); + + case LayoutCode::MongoDbObjectId: + return metaBytes + sizeof(MongoDbObjectId); + + case LayoutCode::Utf8: + case LayoutCode::Binary: + { + auto [numBytes, sizeLenInBytes] = Read7BitEncodedUInt(metaOffset + metaBytes); + return metaBytes + sizeLenInBytes + static_cast(numBytes); + } + + case LayoutCode::VarInt: + case LayoutCode::VarUInt: + { + auto [_, sizeLenInBytes] = Read7BitEncodedUInt(metaOffset + metaBytes); + return metaBytes + sizeLenInBytes; + } + + default: + cdb_core::Contract::Fail(cdb_core::make_string("Not Implemented: %d", static_cast(code))); + } + } + + uint32_t RowBuffer::CountDefaultValue(const LayoutType* layoutType, const TypeArgumentList& typeArgs) const noexcept + { + LayoutCode code = layoutType->GetLayoutCode(); + switch (code) + { + case LayoutCode::Null: + case LayoutCode::Boolean: + case LayoutCode::BooleanFalse: + return 1; + + case LayoutCode::Int8: + return sizeof(int8_t); + + case LayoutCode::Int16: + return sizeof(int16_t); + + case LayoutCode::Int32: + return sizeof(int32_t); + + case LayoutCode::Int64: + return sizeof(int64_t); + + case LayoutCode::UInt8: + return sizeof(uint8_t); + + case LayoutCode::UInt16: + return sizeof(uint16_t); + + case LayoutCode::UInt32: + return sizeof(uint32_t); + + case LayoutCode::UInt64: + return sizeof(uint64_t); + + case LayoutCode::Float32: + return sizeof(float32_t); + + case LayoutCode::Float64: + return sizeof(float64_t); + + case LayoutCode::Float128: + return sizeof(float128_t); + + case LayoutCode::Decimal: + return sizeof(decimal_t); + + case LayoutCode::DateTime: + return sizeof(DateTime); + + case LayoutCode::UnixDateTime: + return sizeof(UnixDateTime); + + case LayoutCode::Guid: + return sizeof(Guid); + + case LayoutCode::MongoDbObjectId: + return sizeof(MongoDbObjectId); + + case LayoutCode::Utf8: + case LayoutCode::Binary: + case LayoutCode::VarInt: + case LayoutCode::VarUInt: + + // Variable length types preceded by their varuint size take 1 byte for a size of 0. + return 1; + + case LayoutCode::ObjectScope: + case LayoutCode::ImmutableObjectScope: + case LayoutCode::ArrayScope: + case LayoutCode::ImmutableArrayScope: + + // Variable length sparse collection scopes take 1 byte for the end-of-scope terminator. + return sizeof(LayoutCode); + + case LayoutCode::TypedArrayScope: + case LayoutCode::ImmutableTypedArrayScope: + case LayoutCode::TypedSetScope: + case LayoutCode::ImmutableTypedSetScope: + case LayoutCode::TypedMapScope: + case LayoutCode::ImmutableTypedMapScope: + + // Variable length typed collection scopes preceded by their scope size take sizeof(uint32_t) for a size of 0. + return sizeof(uint32_t); + + case LayoutCode::TupleScope: + case LayoutCode::ImmutableTupleScope: + { + // Fixed arity sparse collections take 1 byte for end-of-scope plus a null for each element. + size_t length = sizeof(LayoutCode) + (sizeof(LayoutCode) * typeArgs.GetCount()); + cdb_core::Contract::Invariant(length <= UINT32_MAX); + return static_cast(length); + } + case LayoutCode::TypedTupleScope: + case LayoutCode::ImmutableTypedTupleScope: + case LayoutCode::TaggedScope: + case LayoutCode::ImmutableTaggedScope: + case LayoutCode::Tagged2Scope: + case LayoutCode::ImmutableTagged2Scope: + { + // Fixed arity typed collections take the sum of the default values of each element. The scope size is implied by the arity. + int sum = 0; + for (const TypeArgument& arg : typeArgs) + { + sum += CountDefaultValue(arg.GetType(), arg.GetTypeArgs()); + } + + return sum; + } + case LayoutCode::NullableScope: + case LayoutCode::ImmutableNullableScope: + + // Nullables take the default values of the value plus null. The scope size is implied by the arity. + return 1 + CountDefaultValue(typeArgs[0].GetType(), typeArgs[0].GetTypeArgs()); + + case LayoutCode::Schema: + case LayoutCode::ImmutableSchema: + { + const Layout& udt = m_resolver->Resolve(typeArgs.GetSchemaId()); + return udt.GetSize() + sizeof(LayoutCode); + } + default: + cdb_core::Contract::Fail(cdb_core::make_string("Not Implemented: %d", static_cast(code))); + } + } + + uint32_t RowBuffer::WriteDefaultValue(uint32_t offset, const LayoutType* layoutType, + const TypeArgumentList& typeArgs) noexcept + { + LayoutCode code = layoutType->GetLayoutCode(); + switch (code) + { + case LayoutCode::Null: + WriteSparseTypeCode(offset, LayoutCode::Null); + return 1; + + case LayoutCode::Boolean: + case LayoutCode::BooleanFalse: + WriteSparseTypeCode(offset, LayoutCode::BooleanFalse); + return 1; + + case LayoutCode::Int8: + WriteInt8(offset, 0); + return sizeof(int8_t); + + case LayoutCode::Int16: + WriteInt16(offset, 0); + return sizeof(int16_t); + + case LayoutCode::Int32: + WriteInt32(offset, 0); + return sizeof(int32_t); + + case LayoutCode::Int64: + WriteInt64(offset, 0); + return sizeof(int64_t); + + case LayoutCode::UInt8: + WriteUInt8(offset, 0); + return sizeof(uint8_t); + + case LayoutCode::UInt16: + WriteUInt16(offset, 0); + return sizeof(uint16_t); + + case LayoutCode::UInt32: + WriteUInt32(offset, 0); + return sizeof(uint32_t); + + case LayoutCode::UInt64: + WriteUInt64(offset, 0); + return sizeof(uint64_t); + + case LayoutCode::Float32: + WriteFloat32(offset, {}); + return sizeof(float32_t); + + case LayoutCode::Float64: + WriteFloat64(offset, {}); + return sizeof(float64_t); + + case LayoutCode::Float128: + WriteFloat128(offset, {}); + return sizeof(float128_t); + + case LayoutCode::Decimal: + WriteDecimal(offset, {}); + return sizeof(decimal_t); + + case LayoutCode::DateTime: + WriteDateTime(offset, {}); + return sizeof(DateTime); + + case LayoutCode::UnixDateTime: + WriteUnixDateTime(offset, {}); + return sizeof(UnixDateTime); + + case LayoutCode::Guid: + WriteGuid(offset, {}); + return sizeof(Guid); + + case LayoutCode::MongoDbObjectId: + WriteMongoDbObjectId(offset, {}); + return sizeof(MongoDbObjectId); + + case LayoutCode::Utf8: + case LayoutCode::Binary: + case LayoutCode::VarInt: + case LayoutCode::VarUInt: + + // Variable length types preceded by their varuint size take 1 byte for a size of 0. + return Write7BitEncodedUInt(offset, 0); + + case LayoutCode::ObjectScope: + case LayoutCode::ImmutableObjectScope: + case LayoutCode::ArrayScope: + case LayoutCode::ImmutableArrayScope: + + // Variable length sparse collection scopes take 1 byte for the end-of-scope terminator. + WriteSparseTypeCode(offset, LayoutCode::EndScope); + return sizeof(LayoutCode); + + case LayoutCode::TypedArrayScope: + case LayoutCode::ImmutableTypedArrayScope: + case LayoutCode::TypedSetScope: + case LayoutCode::ImmutableTypedSetScope: + case LayoutCode::TypedMapScope: + case LayoutCode::ImmutableTypedMapScope: + + // Variable length typed collection scopes preceded by their scope size take sizeof(uint32_t) for a size of 0. + WriteUInt32(offset, 0); + return sizeof(uint32_t); + + case LayoutCode::TupleScope: + case LayoutCode::ImmutableTupleScope: + { + // Fixed arity sparse collections take 1 byte for end-of-scope plus a null for each element. + for (size_t i = 0; i < typeArgs.GetCount(); i++) + { + WriteSparseTypeCode(offset, LayoutCode::Null); + } + + WriteSparseTypeCode(offset, LayoutCode::EndScope); + return sizeof(LayoutCode) + (sizeof(LayoutCode) * static_cast(typeArgs.GetCount())); + } + case LayoutCode::TypedTupleScope: + case LayoutCode::ImmutableTypedTupleScope: + case LayoutCode::TaggedScope: + case LayoutCode::ImmutableTaggedScope: + case LayoutCode::Tagged2Scope: + case LayoutCode::ImmutableTagged2Scope: + { + // Fixed arity typed collections take the sum of the default values of each element. The scope size is implied by the arity. + uint32_t sum = 0; + for (const TypeArgument& arg : typeArgs) + { + sum += WriteDefaultValue(offset + sum, arg.GetType(), arg.GetTypeArgs()); + } + + return sum; + } + case LayoutCode::NullableScope: + case LayoutCode::ImmutableNullableScope: + + // Nullables take the default values of the value plus null. The scope size is implied by the arity. + WriteInt8(offset, 0); + return 1 + WriteDefaultValue(offset + 1, typeArgs[0].GetType(), typeArgs[0].GetTypeArgs()); + + case LayoutCode::Schema: + case LayoutCode::ImmutableSchema: + { + // Clear all presence bits. + const Layout& udt = m_resolver->Resolve(typeArgs.GetSchemaId()); + uint32_t udtSize = udt.GetSize(); + m_buffer.Slice(offset, udtSize).Fill(std::byte{0}); + + // Write scope terminator. + WriteSparseTypeCode(offset + udtSize, LayoutCode::EndScope); + return udtSize + sizeof(LayoutCode); + } + default: + cdb_core::Contract::Fail(cdb_core::make_string("Not Implemented: %d", static_cast(code))); + } + } +} diff --git a/src/Serialization/HybridRow.Native/RowBuffer.h b/src/Serialization/HybridRow.Native/RowBuffer.h new file mode 100644 index 0000000..22c62bf --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowBuffer.h @@ -0,0 +1,696 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "HybridRowHeader.h" +#include "HybridRowVersion.h" +#include "ISpanResizer.h" +#include "LayoutCode.h" +#include "LayoutResolver.h" +//#include "Layout.h" +#include "UpdateOptions.h" + +namespace cdb_hr +{ + class LayoutType; + class LayoutScope; + class ScalarLayoutTypeBase; + class LayoutUniqueScope; + class LayoutInt8; + class LayoutInt16; + class LayoutInt32; + class LayoutInt64; + class LayoutUInt8; + class LayoutUInt16; + class LayoutUInt32; + class LayoutUInt64; + class LayoutVarInt; + class LayoutVarUInt; + class LayoutFloat32; + class LayoutFloat64; + class LayoutFloat128; + class LayoutDecimal; + class LayoutDateTime; + class LayoutUnixDateTime; + class LayoutGuid; + class LayoutMongoDbObjectId; + class LayoutNull; + class LayoutBoolean; + class LayoutUtf8; + class LayoutBinary; + class LayoutObject; + class LayoutArray; + class LayoutTypedArray; + class LayoutTypedSet; + class LayoutTypedMap; + class LayoutTuple; + class LayoutTypedTuple; + class LayoutTagged; + class LayoutTagged2; + class LayoutNullable; + class LayoutUDT; + struct RowReader; + struct RowWriter; + enum class RowOptions; + struct RowCursor; + + class RowBuffer final + { + public: + ~RowBuffer() noexcept = default; + RowBuffer(const RowBuffer& other) noexcept = default; + RowBuffer(RowBuffer&& other) noexcept = default; + RowBuffer& operator=(const RowBuffer& other) noexcept = default; + RowBuffer& operator=(RowBuffer&& other) noexcept = default; + + /// Initializes a new instance of the struct. + /// Initial buffer capacity. + /// Optional memory resizer. + RowBuffer(uint32_t capacity, ISpanResizer* resizer) noexcept; + + /// Initializes a new instance of the struct from an existing buffer. + /// The buffer. + /// The version of the Hybrid Row format to used to encoding the buffer. + /// The resolver for UDTs. + /// Optional memory resizer. + RowBuffer(cdb_core::Span buffer, + HybridRowVersion version, + const LayoutResolver* resolver, + ISpanResizer* resizer) noexcept; + + /// The root header for the row. + [[nodiscard]] HybridRowHeader GetHeader() const noexcept { return ReadHeader(0); } + + /// The length of row in bytes. + [[nodiscard]] uint32_t GetLength() const noexcept { return m_length; } + + /// The full encoded content of the row. + [[nodiscard]] cdb_core::ReadOnlySpan AsSpan() const noexcept { return m_buffer.Slice(0, m_length); } + + /// The resolver for UDTs. + [[nodiscard]] const LayoutResolver* GetResolver() const noexcept { return m_resolver; } + + /// Clears all content from the row. The row is empty after this method. + void Reset() noexcept; + + /// + /// Reads in the contents of the RowBuffer from an existing block of memory and initializes + /// the row buffer with the associated layout and rowVersion. + /// + /// true if the serialization succeeded. false if the input stream was corrupted. + [[nodiscard]] + bool ReadFrom(cdb_core::ReadOnlySpan input, HybridRowVersion rowVersion, + const LayoutResolver* resolver) noexcept; + + /// Initializes a row to the minimal size for the given layout. + /// The version of the Hybrid Row format to use for encoding this row. + /// The layout that describes the column layout of the row. + /// The resolver for UDTs. + /// + /// The row is initialized to default row for the given layout. All fixed columns have their + /// default values. All variable columns are null. No sparse columns are present. The row is valid. + /// + void InitLayout(HybridRowVersion version, const Layout& layout, const LayoutResolver* resolver) noexcept; + + private: + friend struct RowCursor; + friend class LayoutInt8; + friend class LayoutInt16; + friend class LayoutInt32; + friend class LayoutInt64; + friend class LayoutUInt8; + friend class LayoutUInt16; + friend class LayoutUInt32; + friend class LayoutUInt64; + friend class LayoutVarInt; + friend class LayoutVarUInt; + friend class LayoutFloat32; + friend class LayoutFloat64; + friend class LayoutFloat128; + friend class LayoutDecimal; + friend class LayoutDateTime; + friend class LayoutUnixDateTime; + friend class LayoutGuid; + friend class LayoutMongoDbObjectId; + friend class LayoutNull; + friend class LayoutBoolean; + friend class LayoutUtf8; + friend class LayoutBinary; + friend class LayoutType; + friend class LayoutScope; + friend class LayoutObject; + friend class LayoutArray; + friend class LayoutTypedArray; + friend class LayoutTypedSet; + friend class LayoutTypedMap; + friend class LayoutTuple; + friend class LayoutTypedTuple; + friend class LayoutTagged; + friend class LayoutTagged2; + friend class LayoutNullable; + friend class LayoutUDT; + friend class LayoutUniqueScope; + friend class ScalarLayoutTypeBase; + friend struct RowReader; + friend struct RowWriter; + template + friend struct TypedMapHybridRowSerializer; + + void WriteHeader(uint32_t offset, HybridRowHeader value) noexcept; + [[nodiscard]] + HybridRowHeader ReadHeader(uint32_t offset) const noexcept; + + void WriteSchemaId(uint32_t offset, SchemaId value) noexcept; + [[nodiscard]] + SchemaId ReadSchemaId(uint32_t offset) const noexcept; + + void SetBit(uint32_t offset, LayoutBit bit) noexcept; + void UnsetBit(uint32_t offset, LayoutBit bit) noexcept; + [[nodiscard]] + bool ReadBit(uint32_t offset, LayoutBit bit) const noexcept; + + void DeleteVariable(uint32_t offset, bool isVarint) noexcept; + + void WriteInt8(uint32_t offset, int8_t value) noexcept; + [[nodiscard]] + int8_t ReadInt8(uint32_t offset) const noexcept; + void WriteUInt8(uint32_t offset, uint8_t value) noexcept; + [[nodiscard]] + uint8_t ReadUInt8(uint32_t offset) const noexcept; + void WriteInt16(uint32_t offset, int16_t value) noexcept; + [[nodiscard]] + int16_t ReadInt16(uint32_t offset) const noexcept; + void WriteUInt16(uint32_t offset, uint16_t value) noexcept; + [[nodiscard]] + uint16_t ReadUInt16(uint32_t offset) const noexcept; + + void WriteInt32(uint32_t offset, int32_t value) noexcept; + [[nodiscard]] + int32_t ReadInt32(uint32_t offset) const noexcept; + + void IncrementUInt32(uint32_t offset, uint32_t increment) noexcept; + void DecrementUInt32(uint32_t offset, uint32_t decrement) noexcept; + + void WriteUInt32(uint32_t offset, uint32_t value) noexcept; + [[nodiscard]] + uint32_t ReadUInt32(uint32_t offset) const noexcept; + + void WriteInt64(uint32_t offset, int64_t value) noexcept; + [[nodiscard]] + int64_t ReadInt64(uint32_t offset) const noexcept; + + void WriteUInt64(uint32_t offset, uint64_t value) noexcept; + [[nodiscard]] + uint64_t ReadUInt64(uint32_t offset) const noexcept; + + RowCursor WriteSparseUDT(RowCursor& edit, const LayoutScope* scopeType, const Layout& udt, + UpdateOptions options) noexcept; + + /// Delete the sparse field at the indicated path. + /// The field to delete. + void DeleteSparse(RowCursor& edit) noexcept; + + /// Rotates the sign bit of a two's complement value to the least significant bit. + /// A signed value. + /// An unsigned value encoding the same value but with the sign bit in the LSB. + /// + /// Moves the signed bit of a two's complement value to the least significant bit (LSB) by: + /// + /// + /// If negative, take the two's complement. + /// + /// Left shift the value by 1 bit. + /// + /// If negative, set the LSB to 1. + /// + /// + /// + static uint64_t RotateSignToLsb(int64_t value) noexcept; + + /// Undoes the rotation introduced by . + /// An unsigned value with the sign bit in the LSB. + /// A signed two's complement value encoding the same value. + static int64_t RotateSignToMsb(uint64_t uvalue) noexcept; + + [[nodiscard]] + std::tuple ReadSparsePathLen(const Layout& layout, uint32_t offset) const noexcept; + [[nodiscard]] std::string_view ReadSparsePath(const RowCursor& edit) const noexcept; + void WriteSparsePath(RowCursor& edit, uint32_t offset) noexcept; + + /// [std::string_view value, uint32_t sizeLenInBytes] + /// + /// value: the value read. + /// sizeLenInBytes: is size of the length encoding that precedes the value (in bytes). This size does not include the value itself. + /// + [[nodiscard]] std::tuple ReadString(uint32_t offset) const noexcept; + /// The size of the length encoding that precedes the value (in bytes). This size does not include the value itself. + [[nodiscard]] uint32_t WriteString(uint32_t offset, std::string_view value) noexcept; + /// [ReadOnlySpan{byte} value, uint32_t sizeLenInBytes] + /// + /// value: the value read. + /// sizeLenInBytes: is size of the length encoding that precedes the value (in bytes). This size does not include the value itself. + /// + [[nodiscard]] std::tuple, uint32_t> ReadBinary(uint32_t offset) const noexcept; + /// The size of the length encoding that precedes the value (in bytes). This size does not include the value itself. + [[nodiscard]] uint32_t WriteBinary(uint32_t offset, cdb_core::ReadOnlySpan value) noexcept; + + /// Ensure there is at least size bytes in the internal buffer. + void Ensure(uint32_t size) noexcept; + + /// + /// Ensure there is at least sufficient space in the internal buffer to store a variable value. + /// + /// [uint32_t spaceNeeded, int32_t shift] + /// + /// spaceNeeded: the number of bytes needed to encode the value. + /// shift: the number of bytes the current content was shifted by (may be negative). + /// + [[nodiscard]] std::tuple EnsureVariable(uint32_t offset, bool isVarint, uint32_t numBytes, + bool exists) noexcept; + + void ValidateSparsePrimitiveTypeCode(const RowCursor& edit, const LayoutType* code) const noexcept; + + /// Compute the byte offset from the beginning of the row for a given variable column's value. + /// The (optional) layout of the current scope. + /// The 0-based offset to the beginning of the scope's value. + /// The 0-based index of the variable column within the variable segment. + /// + /// The byte offset from the beginning of the row where the variable column's value should be + /// located. + /// + uint32_t ComputeVariableValueOffset(const Layout& layout, uint32_t scopeOffset, uint32_t varIndex) const noexcept; + + /// Move a sparse iterator to the next field within the same sparse scope. + /// The iterator to advance. + /// + /// + /// On success, the path of the field at the given offset, otherwise + /// undefined. + /// + /// + /// If found, the offset to the metadata of the field, otherwise a + /// location to insert the field. + /// + /// + /// If found, the layout code of the matching field found, otherwise + /// undefined. + /// + /// + /// If found, the offset to the value of the field, otherwise + /// undefined. + /// . + /// + /// True if there is another field, false if there are no more. + bool SparseIteratorMoveNext(RowCursor& edit) const noexcept; + + void WriteSparseMetadata(RowCursor& edit, const LayoutType* cellType, const TypeArgumentList& typeArgs, + uint32_t metaBytes) noexcept; + + /// Ensure that sufficient space exists in the row buffer to write the current value. + /// (uint32_t metaBytes, uint32_t spaceNeeded, int32_t shift) + std::tuple EnsureSparse( + RowCursor& edit, + const LayoutType* cellType, + const TypeArgumentList& typeArgs, + uint32_t numBytes, + UpdateOptions options) noexcept; + + /// Ensure that sufficient space exists in the row buffer to write the current value. + /// + /// The prepared edit indicating where and in what context the current write will + /// happen. + /// + /// The type of the field to be written. + /// The type arguments of the field to be written. + /// The number of bytes needed to encode the value of the field to be written. + /// The kind of edit to be performed. + /// + ///

metaBytes: + /// On success, the number of bytes needed to encode the metadata of the new + /// field. + ///

+ ///

spaceNeeded: + /// On success, the number of bytes needed in total to encode the new field + /// and its metadata. + ///

+ ///

shift: + /// On success, the number of bytes the length of the row buffer was increased + /// (which may be negative if the row buffer was shrunk). + ///

+ ///
+ /// (uint32_t metaBytes, uint32_t spaceNeeded, int32_t shift) + std::tuple EnsureSparse( + RowCursor& edit, + const LayoutType* cellType, + const TypeArgumentList& typeArgs, + uint32_t numBytes, + RowOptions options) noexcept; + + /// Read the metadata of an encoded sparse field. + /// The edit structure to fill in. + /// + /// + /// On success, the path of the field at the given offset, otherwise + /// undefined. + /// + /// + /// On success, the offset to the metadata of the field, otherwise a + /// location to insert the field. + /// + /// + /// On success, the layout code of the existing field, otherwise + /// undefined. + /// + /// + /// On success, the type args of the existing field, otherwise + /// undefined. + /// + /// + /// On success, the offset to the value of the field, otherwise + /// undefined. + /// . + /// + void ReadSparseMetadata(RowCursor& edit) const noexcept; + + /// Produce a new scope from the current iterator position. + /// An initialized iterator pointing at a scope. + /// True if the new scope should be marked immutable (read-only). + /// A new scope beginning at the current iterator position. + [[nodiscard]] RowCursor SparseIteratorReadScope(const RowCursor& edit, bool immutable) const noexcept; + + /// + /// Compute the byte offsets from the beginning of the row for a given sparse field insertion + /// into a set/map. + /// + /// The sparse scope to insert into. + /// The field to move into the set/map. + /// The prepared edit context. + RowCursor PrepareSparseMove(const RowCursor& scope, RowCursor& srcEdit) const noexcept; + + void TypedCollectionMoveField(RowCursor& dstEdit, RowCursor& srcEdit, RowOptions options) noexcept; + + /// Rebuild the unique index for a set/map scope. + /// The sparse scope to rebuild an index for. + /// Success if the index could be built, an error otherwise. + /// + /// The MUST be a set or map scope. + /// + /// The scope may have been built (e.g. via RowWriter) with relaxed uniqueness constraint checking. + /// This operation rebuilds an index to support verification of uniqueness constraints during + /// subsequent partial updates. If the appropriate uniqueness constraints cannot be established (i.e. + /// a duplicate exists), this operation fails. Before continuing, the resulting scope should either: + /// + /// + /// + /// Be repaired (e.g. by deleting duplicates) and the index rebuild operation should be + /// run again. + /// + /// + /// Be deleted. The entire scope should be removed including its items. + /// + /// Failure to perform one of these actions will leave the row is potentially in a corrupted + /// state where partial updates may subsequent fail. + /// + /// + /// The target may or may not have already been indexed. This + /// operation is idempotent. + /// + /// + Result TypedCollectionUniqueIndexRebuild(RowCursor& scope) noexcept; + + static uint32_t CountSparsePath(RowCursor& edit) noexcept; + + /// + /// Compute the number of bytes necessary to store the unsigned integer using the varuint + /// encoding. + /// + /// The value to be encoded. + /// The number of bytes needed to store the varuint encoding of . + static uint32_t Count7BitEncodedUInt(uint64_t value) noexcept; + + /// + /// Compute the number of bytes necessary to store the signed integer using the varint + /// encoding. + /// + /// The value to be encoded. + /// The number of bytes needed to store the varint encoding of . + static uint32_t Count7BitEncodedInt(int64_t value) noexcept; + + /// + /// Reads in the contents of the RowBuffer from an input stream and initializes the row buffer + /// with the associated layout and rowVersion. + /// + /// true if the serialization succeeded. false if the input stream was corrupted. + [[nodiscard]] bool InitReadFrom(HybridRowVersion rowVersion) const noexcept; + + /// Skip over a nested scope. + /// The sparse scope to search. + /// The 0-based byte offset immediately following the scope end marker. + uint32_t SkipScope(RowCursor& edit) const noexcept; + + /// Compares the values of two encoded fields using the hybrid row binary collation. + /// An edit describing the left field. + /// The size of the left field's value in bytes. + /// An edit describing the right field. + /// The size of the right field's value in bytes. + /// + /// + /// + /// -1left less than right. + /// + /// 0left and right are equal. + /// + /// 1left is greater than right. + /// + /// + /// + [[nodiscard]] int CompareFieldValue(const RowCursor& left, int leftLen, const RowCursor& right, + int rightLen) const noexcept; + + /// + /// Compares the values of two encoded key-value pair fields using the hybrid row binary + /// collation. + /// + /// An edit describing the left field. + /// An edit describing the right field. + /// + /// + /// + /// -1left less than right. + /// + /// 0left and right are equal. + /// + /// 1left is greater than right. + /// + /// + /// + [[nodiscard]] int CompareKeyValueFieldValue(const RowCursor& left, const RowCursor& right) const noexcept; + + // Forward declaration. + struct UniqueIndexItem; + + /// + /// Sorts the array structure using the hybrid row binary + /// collation. + /// + /// The scope to be sorted. + /// A edit that points at the scope. + /// + /// A unique index array structure that identifies the row offsets of each + /// element in the scope. + /// + /// true if the array was sorted, false if a duplicate was found during sorting. + /// + /// Implementation Note: + /// This method MUST guarantee that if at least one duplicate exists it will be found. + /// Insertion Sort is used for this purpose as it guarantees that each value is eventually compared + /// against its previous item in sorted order. If any two successive items are the same they must be + /// duplicates. + /// + /// Other search algorithms, such as Quick Sort or Merge Sort, may offer fewer comparisons in the + /// limit but don't necessarily guarantee that duplicates will be discovered. If an alternative + /// algorithm is used, then an independent duplicate pass MUST be employed. + /// + /// + /// Under the current operational assumptions, the expected cardinality of sets and maps is + /// expected to be relatively small. If this assumption changes, Insertion Sort may no longer be the + /// best choice. + /// + /// + [[nodiscard]] bool InsertionSort(const RowCursor& scope, const RowCursor& dstEdit, + cdb_core::Span uniqueIndex) const noexcept; + + /// The number of bytes written. + uint32_t Write7BitEncodedUInt(uint32_t offset, uint64_t value) noexcept; + /// Returns (uint64_t value, uint32_t lenInBytes) + [[nodiscard]] + std::tuple Read7BitEncodedUInt(uint32_t offset) const noexcept; + + uint32_t Write7BitEncodedInt(uint32_t offset, int64_t value) noexcept; + + /// Returns (uint64_t value, uint32_t lenInBytes) + [[nodiscard]] std::tuple Read7BitEncodedInt(uint32_t offset) const noexcept; + + void WriteFloat32(uint32_t offset, float32_t value) noexcept; + [[nodiscard]] float32_t ReadFloat32(uint32_t offset) const noexcept; + void WriteFloat64(uint32_t offset, float64_t value) noexcept; + [[nodiscard]] float64_t ReadFloat64(uint32_t offset) const noexcept; + void WriteFloat128(uint32_t offset, Float128 value) noexcept; + [[nodiscard]] Float128 ReadFloat128(uint32_t offset) const noexcept; + void WriteDecimal(uint32_t offset, Decimal value) noexcept; + [[nodiscard]] Decimal ReadDecimal(uint32_t offset) const noexcept; + void WriteDateTime(uint32_t offset, DateTime value) noexcept; + [[nodiscard]] DateTime ReadDateTime(uint32_t offset) const noexcept; + void WriteUnixDateTime(uint32_t offset, UnixDateTime value) noexcept; + [[nodiscard]] UnixDateTime ReadUnixDateTime(uint32_t offset) const noexcept; + void WriteGuid(uint32_t offset, Guid value) noexcept; + [[nodiscard]] Guid ReadGuid(uint32_t offset) const noexcept; + void WriteMongoDbObjectId(uint32_t offset, MongoDbObjectId value) noexcept; + [[nodiscard]] MongoDbObjectId ReadMongoDbObjectId(uint32_t offset) const noexcept; + + [[nodiscard]] std::string_view ReadFixedString(uint32_t offset, uint32_t len) const noexcept; + void WriteFixedString(uint32_t offset, std::string_view value) noexcept; + [[nodiscard]] cdb_core::ReadOnlySpan ReadFixedBinary(uint32_t offset, uint32_t len) const noexcept; + void WriteFixedBinary(uint32_t offset, cdb_core::ReadOnlySpan value, uint32_t len) noexcept; + + [[nodiscard]] std::string_view ReadVariableString(uint32_t offset) const noexcept; + /// number of bytes shifted (may be negative). + int32_t WriteVariableString(uint32_t offset, std::string_view value, bool exists) noexcept; + [[nodiscard]] cdb_core::ReadOnlySpan ReadVariableBinary(uint32_t offset) const noexcept; + /// number of bytes shifted (may be negative). + int32_t WriteVariableBinary(uint32_t offset, cdb_core::ReadOnlySpan value, bool exists) noexcept; + [[nodiscard]] int64_t ReadVariableInt(uint32_t offset) const noexcept; + /// number of bytes shifted (may be negative). + int32_t WriteVariableInt(uint32_t offset, int64_t value, bool exists) noexcept; + [[nodiscard]] uint64_t ReadVariableUInt(uint32_t offset) const noexcept; + /// number of bytes shifted (may be negative). + int32_t WriteVariableUInt(uint32_t offset, uint64_t value, bool exists) noexcept; + + [[nodiscard]] const LayoutType* ReadSparseTypeCode(uint32_t offset) const noexcept; + void WriteSparseTypeCode(uint32_t offset, LayoutCode code) noexcept; + + template + T ReadSparseFixed(const TLayoutType* layoutType, RowCursor& edit) const noexcept; + template + void WriteSparseFixed(const TLayoutType* layoutType, RowCursor& edit, T value, UpdateOptions options) noexcept; + + [[nodiscard]] int8_t ReadSparseInt8(RowCursor& edit) const noexcept; + void WriteSparseInt8(RowCursor& edit, int8_t value, UpdateOptions options) noexcept; + [[nodiscard]] int16_t ReadSparseInt16(RowCursor& edit) const noexcept; + void WriteSparseInt16(RowCursor& edit, int16_t value, UpdateOptions options) noexcept; + [[nodiscard]] int32_t ReadSparseInt32(RowCursor& edit) const noexcept; + void WriteSparseInt32(RowCursor& edit, int32_t value, UpdateOptions options) noexcept; + [[nodiscard]] int64_t ReadSparseInt64(RowCursor& edit) const noexcept; + void WriteSparseInt64(RowCursor& edit, int64_t value, UpdateOptions options) noexcept; + [[nodiscard]] uint8_t ReadSparseUInt8(RowCursor& edit) const noexcept; + void WriteSparseUInt8(RowCursor& edit, uint8_t value, UpdateOptions options) noexcept; + [[nodiscard]] uint16_t ReadSparseUInt16(RowCursor& edit) const noexcept; + void WriteSparseUInt16(RowCursor& edit, uint16_t value, UpdateOptions options) noexcept; + [[nodiscard]] uint32_t ReadSparseUInt32(RowCursor& edit) const noexcept; + void WriteSparseUInt32(RowCursor& edit, uint32_t value, UpdateOptions options) noexcept; + [[nodiscard]] uint64_t ReadSparseUInt64(RowCursor& edit) const noexcept; + void WriteSparseUInt64(RowCursor& edit, uint64_t value, UpdateOptions options) noexcept; + + [[nodiscard]] int64_t ReadSparseVarInt(RowCursor& edit) const noexcept; + void WriteSparseVarInt(RowCursor& edit, int64_t value, UpdateOptions options) noexcept; + [[nodiscard]] uint64_t ReadSparseVarUInt(RowCursor& edit) const noexcept; + void WriteSparseVarUInt(RowCursor& edit, uint64_t value, UpdateOptions options) noexcept; + + [[nodiscard]] float32_t ReadSparseFloat32(RowCursor& edit) const noexcept; + void WriteSparseFloat32(RowCursor& edit, float32_t value, UpdateOptions options) noexcept; + [[nodiscard]] float64_t ReadSparseFloat64(RowCursor& edit) const noexcept; + void WriteSparseFloat64(RowCursor& edit, float64_t value, UpdateOptions options) noexcept; + [[nodiscard]] float128_t ReadSparseFloat128(RowCursor& edit) const noexcept; + void WriteSparseFloat128(RowCursor& edit, float128_t value, UpdateOptions options) noexcept; + [[nodiscard]] decimal_t ReadSparseDecimal(RowCursor& edit) const noexcept; + void WriteSparseDecimal(RowCursor& edit, decimal_t value, UpdateOptions options) noexcept; + + [[nodiscard]] DateTime ReadSparseDateTime(RowCursor& edit) const noexcept; + void WriteSparseDateTime(RowCursor& edit, DateTime value, UpdateOptions options) noexcept; + [[nodiscard]] UnixDateTime ReadSparseUnixDateTime(RowCursor& edit) const noexcept; + void WriteSparseUnixDateTime(RowCursor& edit, UnixDateTime value, UpdateOptions options) noexcept; + [[nodiscard]] Guid ReadSparseGuid(RowCursor& edit) const noexcept; + void WriteSparseGuid(RowCursor& edit, Guid value, UpdateOptions options) noexcept; + [[nodiscard]] MongoDbObjectId ReadSparseMongoDbObjectId(RowCursor& edit) const noexcept; + void WriteSparseMongoDbObjectId(RowCursor& edit, MongoDbObjectId value, UpdateOptions options) noexcept; + + [[nodiscard]] NullValue ReadSparseNull(RowCursor& edit) const noexcept; + void WriteSparseNull(RowCursor& edit, NullValue value, UpdateOptions options) noexcept; + [[nodiscard]] bool ReadSparseBool(RowCursor& edit) const noexcept; + void WriteSparseBool(RowCursor& edit, bool value, UpdateOptions options) noexcept; + + [[nodiscard]] std::string_view ReadSparseString(RowCursor& edit) const noexcept; + void WriteSparseString(RowCursor& edit, std::string_view value, UpdateOptions options) noexcept; + [[nodiscard]] cdb_core::ReadOnlySpan ReadSparseBinary(RowCursor& edit) const noexcept; + void WriteSparseBinary(RowCursor& edit, cdb_core::ReadOnlySpan value, UpdateOptions options) noexcept; + + [[nodiscard]] RowCursor WriteSparseObject(RowCursor& edit, const LayoutScope* scopeType, + UpdateOptions options) noexcept; + [[nodiscard]] RowCursor WriteSparseArray(RowCursor& edit, const LayoutScope* scopeType, + UpdateOptions options) noexcept; + [[nodiscard]] RowCursor WriteTypedArray(RowCursor& edit, const LayoutScope* scopeType, + const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept; + [[nodiscard]] RowCursor WriteTypedSet(RowCursor& edit, const LayoutScope* scopeType, + const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept; + [[nodiscard]] RowCursor WriteTypedMap(RowCursor& edit, const LayoutScope* scopeType, + const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept; + [[nodiscard]] RowCursor WriteSparseTuple(RowCursor& edit, const LayoutScope* scopeType, + const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept; + [[nodiscard]] RowCursor WriteTypedTuple(RowCursor& edit, const LayoutScope* scopeType, + const TypeArgumentList& typeArgs, + UpdateOptions options) noexcept; + [[nodiscard]] RowCursor WriteNullable(RowCursor& edit, const LayoutScope* scopeType, + const TypeArgumentList& typeArgs, + UpdateOptions options, bool hasValue) noexcept; + + /// Compute the size of a sparse field. + /// The edit structure describing the field to measure. + /// The length (in bytes) of the encoded field including the metadata and the value. + [[nodiscard]] uint32_t SparseComputeSize(const RowCursor& edit) const noexcept; + + /// Compute the size of a sparse (primitive) field. + /// The type of the current sparse field. + /// The 0-based offset from the beginning of the row where the field begins. + /// + /// The 0-based offset from the beginning of the row where the field's value + /// begins. + /// + /// The length (in bytes) of the encoded field including the metadata and the value. + [[nodiscard]] uint32_t SparseComputePrimitiveSize(const LayoutType* cellType, uint32_t metaOffset, + uint32_t valueOffset) const noexcept; + + /// Return the size (in bytes) of the default sparse value for the type. + /// The type of the default value. + /// + [[nodiscard]] uint32_t CountDefaultValue(const LayoutType* layoutType, + const TypeArgumentList& typeArgs) const noexcept; + + /// Write the default value of the given type at the offset provided. + /// The offset in the buffer to write at. + /// The type of the default value. + /// + [[nodiscard]] uint32_t WriteDefaultValue(uint32_t offset, const LayoutType* layoutType, + const TypeArgumentList& typeArgs) noexcept; + + /// Resizer for growing the memory buffer. + ISpanResizer* m_resizer; + + /// A sequence of bytes managed by this . + /// + /// A Hybrid Row begins in the 0-th byte of the . Remaining byte + /// sequence is defined by the Hybrid Row grammar. + /// + cdb_core::Span m_buffer; + + /// The resolver for UDTs. + const LayoutResolver* m_resolver; + + /// The length of row in bytes. + uint32_t m_length; + }; +} diff --git a/src/Serialization/HybridRow.Native/RowCursor.cpp b/src/Serialization/HybridRow.Native/RowCursor.cpp new file mode 100644 index 0000000..a12aa39 --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowCursor.cpp @@ -0,0 +1,181 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "RowCursor.h" +#include "HybridRowHeader.h" +#include "RowBuffer.h" +#include "LayoutResolver.h" + +namespace cdb_hr +{ + RowCursor RowCursor::Create(const RowBuffer& row) noexcept + { + SchemaId schemaId = row.ReadSchemaId(1 /* offset */); + const Layout& layout = row.GetResolver()->Resolve(schemaId); + uint32_t sparseSegmentOffset = row.ComputeVariableValueOffset(layout, + HybridRowHeader::Size, layout.GetNumVariable()); + + return RowCursor + { + layout, + &LayoutLiteral::UDT, // scopeType + TypeArgumentList(schemaId), // scopeTypeArgs + HybridRowHeader::Size, // start + sparseSegmentOffset, // metaOffset + sparseSegmentOffset, // valueOffset + }; + } + + RowCursor RowCursor::CreateForAppend(const RowBuffer& row) noexcept + { + SchemaId schemaId = row.ReadSchemaId(1 /* offset */); + const Layout& layout = row.GetResolver()->Resolve(schemaId); + return RowCursor + { + layout, + &LayoutLiteral::UDT, // scopeType + TypeArgumentList(schemaId), // scopeTypeArgs + HybridRowHeader::Size, // start + row.GetLength(), // metaOffset + row.GetLength(), // valueOffset + }; + } + + tla::string RowCursor::ToString() const noexcept + { + static_assert(cdb_core::is_stringable_v); + + if (m_scopeType == nullptr) + { + return ""; + } + + TypeArgument scopeTypeArg = (m_scopeType == nullptr) || (m_scopeType->GetLayoutCode() == LayoutCode::EndScope) + ? TypeArgument{} + : TypeArgument{m_scopeType, m_scopeTypeArgs}; + + TypeArgument typeArg = (m_cellType == nullptr) || (m_scopeType->GetLayoutCode() == LayoutCode::EndScope) + ? TypeArgument{} + : TypeArgument{m_cellType, m_cellTypeArgs}; + + tla::string pathOrIndex = !m_writePath.empty() ? m_writePath : cdb_core::make_string("%u", m_index); + return cdb_core::make_string("%s[%s] : %s@%u/%u%s", + scopeTypeArg.ToString().c_str(), + pathOrIndex.c_str(), + typeArg.ToString().c_str(), + m_metaOffset, + m_valueOffset, + m_immutable ? " immutable" : ""); + } + + RowCursor RowCursor::Clone() const noexcept + { + return *this; + } + + RowCursor RowCursor::AsReadOnly() const noexcept + { + RowCursor dest{*this}; + dest.m_immutable = true; + return dest; + } + + RowCursor& RowCursor::Find(const RowBuffer& row, std::string_view path) noexcept + { + cdb_core::Contract::Requires(!m_scopeType->IsIndexedScope()); + + if (!(m_cellType != nullptr && m_cellType->IsLayoutEndScope())) + { + while (row.SparseIteratorMoveNext(*this)) + { + if (path == row.ReadSparsePath(*this)) + { + m_exists = true; + break; + } + } + } + + m_writePath = path; + m_writePathToken = {}; + return *this; + } + + RowCursor& RowCursor::Find(const RowBuffer& row, const StringTokenizer::StringToken& pathToken) noexcept + { + cdb_core::Contract::Requires(!m_scopeType->IsIndexedScope()); + + if (!(m_cellType != nullptr && m_cellType->IsLayoutEndScope())) + { + while (row.SparseIteratorMoveNext(*this)) + { + if (pathToken.GetId() == static_cast(m_pathToken)) + { + m_exists = true; + break; + } + } + } + + m_writePath = pathToken.GetPath(); + m_writePathToken = pathToken; + return *this; + } + + bool RowCursor::MoveNext(const RowBuffer& row) noexcept + { + m_writePath = {}; + m_writePathToken = {}; + return row.SparseIteratorMoveNext(*this); + } + + bool RowCursor::MoveNext(const RowBuffer& row, RowCursor& childScope) noexcept + { + if (childScope.m_scopeType != nullptr) + { + Skip(row, childScope); + } + + return MoveNext(row); + } + + bool RowCursor::MoveTo(const RowBuffer& row, uint32_t index) noexcept + { + cdb_core::Contract::Assert(m_index <= index); + m_writePath = {}; + m_writePathToken = {}; + while (m_index < index) + { + if (!row.SparseIteratorMoveNext(*this)) + { + return false; + } + } + + return true; + } + + void RowCursor::Skip(const RowBuffer& row, RowCursor& childScope) noexcept + { + cdb_core::Contract::Requires(childScope.m_start == m_valueOffset); + if (!(childScope.m_cellType != nullptr && childScope.m_cellType->IsLayoutEndScope())) + { + while (row.SparseIteratorMoveNext(childScope)) { } + } + + if (childScope.m_scopeType->IsSizedScope()) + { + m_endOffset = childScope.m_metaOffset; + } + else + { + m_endOffset = childScope.m_metaOffset + sizeof(LayoutCode); // Move past the end of scope marker. + } + + #if _DEBUG + childScope.m_scopeType = nullptr; + #endif + } +} diff --git a/src/Serialization/HybridRow.Native/RowCursor.h b/src/Serialization/HybridRow.Native/RowCursor.h new file mode 100644 index 0000000..2fe0982 --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowCursor.h @@ -0,0 +1,325 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include "StringTokenizer.h" +#include "TypeArgument.h" +#include "TypeArgumentList.h" +#include "LayoutType.h" +#include "Layout.h" + +namespace cdb_hr_test +{ + class LayoutCompilerUnitTests; +} + +namespace cdb_hr +{ + struct RowReader; + struct RowWriter; + class RowBuffer; + + struct RowCursor final + { + RowCursor() noexcept: + m_layout{nullptr}, + m_scopeType{}, + m_scopeTypeArgs{}, + m_immutable{false}, + m_deferUniqueIndex{false}, + m_start{0}, + m_exists{false}, + m_writePath{}, + m_writePathToken{}, + m_pathOffset{0}, + m_pathToken{0}, + m_metaOffset{0}, + m_cellType{}, + m_valueOffset{0}, + m_endOffset{0}, + m_count{0}, + m_index{0}, + m_cellTypeArgs{} {} + + ~RowCursor() noexcept = default; + + RowCursor(const RowCursor& other) noexcept : // NOLINT(modernize-use-equals-default) + m_layout{other.m_layout}, + m_scopeType{other.m_scopeType}, + m_scopeTypeArgs{other.m_scopeTypeArgs}, + m_immutable{other.m_immutable}, + m_deferUniqueIndex{other.m_deferUniqueIndex}, + m_start{other.m_start}, + m_exists{other.m_exists}, + m_writePath{other.m_writePath}, + m_writePathToken{other.m_writePathToken}, + m_pathOffset{other.m_pathOffset}, + m_pathToken{other.m_pathToken}, + m_metaOffset{other.m_metaOffset}, + m_cellType{other.m_cellType}, + m_valueOffset{other.m_valueOffset}, + m_endOffset{other.m_endOffset}, + m_count{other.m_count}, + m_index{other.m_index}, + m_cellTypeArgs{other.m_cellTypeArgs} {} + + RowCursor(RowCursor&& other) noexcept = default; + + RowCursor& operator=(const RowCursor& other) noexcept + { + if(this == &other) {return *this;} + m_layout = other.m_layout; + m_scopeType = other.m_scopeType; + m_scopeTypeArgs = other.m_scopeTypeArgs; + m_immutable = other.m_immutable; + m_deferUniqueIndex = other.m_deferUniqueIndex; + m_start = other.m_start; + m_exists = other.m_exists; + m_writePath = other.m_writePath; + m_writePathToken = other.m_writePathToken; + m_pathOffset = other.m_pathOffset; + m_pathToken = other.m_pathToken; + m_metaOffset = other.m_metaOffset; + m_cellType = other.m_cellType; + m_valueOffset = other.m_valueOffset; + m_endOffset = other.m_endOffset; + m_count = other.m_count; + m_index = other.m_index; + m_cellTypeArgs = other.m_cellTypeArgs; + return *this; + } + + RowCursor& operator=(RowCursor&& other) noexcept = default; + + static RowCursor Create(const RowBuffer& row) noexcept; + static RowCursor CreateForAppend(const RowBuffer& row) noexcept; + + /// Makes a copy of the current cursor. + /// + /// The two cursors will have independent and unconnected lifetimes after cloning. However, + /// mutations to a can invalidate any active cursors over the same row. + /// + [[nodiscard]] + RowCursor Clone() const noexcept; + + /// Returns an equivalent scope that is read-only. + [[nodiscard]] + RowCursor AsReadOnly() const noexcept; + + /// + /// Moves the current cursor to the child cell of the current scope with path . + /// + /// The row buffer. + /// The path to find. + /// A reference to this. + RowCursor& Find(const RowBuffer& row, std::string_view path) noexcept; + RowCursor& Find(const RowBuffer& row, const StringTokenizer::StringToken& pathToken) noexcept; + + bool MoveNext(const RowBuffer& row) noexcept; + bool MoveNext(const RowBuffer& row, RowCursor& childScope) noexcept; + bool MoveTo(const RowBuffer& row, uint32_t index) noexcept; + void Skip(const RowBuffer& row, RowCursor& childScope) noexcept; + + /// For schematized sparse fields, the token of the path, otherwise 0. + [[nodiscard]] uint64_t GetToken() const noexcept { return static_cast(m_pathToken); } + + /// For indexed scopes (e.g. Array), the 0-based index into the scope of the next insertion. + [[nodiscard]] uint32_t GetIndex() const noexcept { return m_index; } + + /// If true, this scope's nested fields cannot be updated individually. + /// The entire scope can still be replaced. + [[nodiscard]] bool GetImmutable() const noexcept { return m_immutable; } + + /// The kind of scope. + [[nodiscard]] const LayoutScope* GetScopeType() const noexcept { return m_scopeType; } + + /// For types with generic parameters (e.g. , the type parameters. + [[nodiscard]] const TypeArgumentList& GetScopeTypeArgs() const noexcept { return m_scopeTypeArgs; } + + /// The layout describing the contents of the scope, or null if the scope is unschematized. + [[nodiscard]] const Layout& GetLayout() const noexcept { return *m_layout; } + + /// The full logical type. + [[nodiscard]] TypeArgument GetTypeArg() const noexcept { return TypeArgument(m_cellType, m_cellTypeArgs); } + + [[nodiscard]] tla::string ToString() const noexcept; + + private: + friend class ScalarLayoutTypeBase; + friend class LayoutIndexedScope; + friend class LayoutType; + friend class LayoutScope; + friend class LayoutInt8; + friend class LayoutInt16; + friend class LayoutInt32; + friend class LayoutInt64; + friend class LayoutUInt8; + friend class LayoutUInt16; + friend class LayoutUInt32; + friend class LayoutUInt64; + friend class LayoutVarInt; + friend class LayoutVarUInt; + friend class LayoutFloat32; + friend class LayoutFloat64; + friend class LayoutFloat128; + friend class LayoutDecimal; + friend class LayoutDateTime; + friend class LayoutUnixDateTime; + friend class LayoutGuid; + friend class LayoutMongoDbObjectId; + friend class LayoutNull; + friend class LayoutBoolean; + friend class LayoutUtf8; + friend class LayoutBinary; + friend class LayoutObject; + friend class LayoutArray; + friend class LayoutTypedArray; + friend class LayoutTypedSet; + friend class LayoutTypedMap; + friend class LayoutTuple; + friend class LayoutTypedTuple; + friend class LayoutTagged; + friend class LayoutTagged2; + friend class LayoutNullable; + friend class LayoutUniqueScope; + friend class RowBuffer; + friend struct RowReader; + friend struct RowWriter; + template + friend struct TypedMapHybridRowSerializer; + friend class cdb_hr_test::LayoutCompilerUnitTests; + + [[nodiscard]] + RowCursor(const Layout& layout, + const LayoutScope* scopeType, + TypeArgumentList scopeTypeArgs, + uint32_t start, + uint32_t metaOffset, + uint32_t valueOffset, + bool immutable = false, + uint32_t count = 0, + uint32_t index = 0) noexcept : + m_layout{&layout}, + m_scopeType{scopeType}, + m_scopeTypeArgs{std::move(scopeTypeArgs)}, + m_immutable{immutable}, + m_deferUniqueIndex{false}, + m_start{start}, + m_exists{false}, + m_writePath{}, + m_writePathToken{}, + m_pathOffset{0}, + m_pathToken{0}, + m_metaOffset{metaOffset}, + m_cellType{nullptr}, + m_valueOffset{valueOffset}, + m_endOffset{0}, + m_count{count}, + m_index{index}, + m_cellTypeArgs{} {} + + [[nodiscard]] + RowCursor(const Layout& layout, + const LayoutScope* scopeType, + TypeArgumentList scopeTypeArgs, + bool immutable, + bool deferUniqueIndex, + uint32_t start, + bool exists, + tla::string writePath, + StringTokenizer::StringToken writePathToken, + uint32_t pathOffset, + uint32_t pathToken, + uint32_t metaOffset, + const LayoutType* cellType, + uint32_t valueOffset, + uint32_t endOffset, + uint32_t count, + uint32_t index, + TypeArgumentList cellTypeArgs) noexcept : + m_layout{&layout}, + m_scopeType{scopeType}, + m_scopeTypeArgs{std::move(scopeTypeArgs)}, + m_immutable{immutable}, + m_deferUniqueIndex{deferUniqueIndex}, + m_start{start}, + m_exists{exists}, + m_writePath{std::move(writePath)}, + m_writePathToken{writePathToken}, + m_pathOffset{pathOffset}, + m_pathToken{pathToken}, + m_metaOffset{metaOffset}, + m_cellType{cellType}, + m_valueOffset{valueOffset}, + m_endOffset{endOffset}, + m_count{count}, + m_index{index}, + m_cellTypeArgs{std::move(cellTypeArgs)} {} + + /// The layout describing the contents of the scope, or null if the scope is unschematized. + const Layout* m_layout; + + /// The kind of scope within which this edit was prepared. + const LayoutScope* m_scopeType; + + /// The type parameters of the scope within which this edit was prepared. + TypeArgumentList m_scopeTypeArgs; + + /// If true, this scope's nested fields cannot be updated individually. + /// The entire scope can still be replaced. + bool m_immutable; + + /// If true, this scope is an unique index scope whose index will be built after its items are written. + bool m_deferUniqueIndex; + + /// + /// The 0-based byte offset from the beginning of the row where the first sparse field within + /// the scope begins. + /// + uint32_t m_start; + + /// True if an existing field matching the search criteria was found. + bool m_exists; + + /// If existing, the scope relative path for writing. + tla::string m_writePath; + + /// If WritePath is tokenized, then its token. + StringTokenizer::StringToken m_writePathToken; + + /// If existing, the offset scope relative path for reading. + uint32_t m_pathOffset; + + /// If existing, the layout string token of scope relative path for reading. + uint32_t m_pathToken; + + /// + /// If existing, the offset to the metadata of the existing field, otherwise the location to + /// insert a new field. + /// + uint32_t m_metaOffset; + + /// If existing, the layout code of the existing field, otherwise undefined. + const LayoutType* m_cellType; + + /// If existing, the offset to the value of the existing field, otherwise undefined. + uint32_t m_valueOffset; + + /// + /// If existing, the offset to the end of the existing field. Used as a hint when skipping + /// forward. + /// + uint32_t m_endOffset; + + /// For sized scopes (e.g. Typed Array), the number of elements. + uint32_t m_count; + + /// For indexed scopes (e.g. Array), the 0-based index into the scope of the sparse field. + uint32_t m_index; + + /// For types with generic parameters (e.g. , the type parameters. + TypeArgumentList m_cellTypeArgs; + }; +} diff --git a/src/Serialization/HybridRow.Native/RowOptions.h b/src/Serialization/HybridRow.Native/RowOptions.h new file mode 100644 index 0000000..544f3a1 --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowOptions.h @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ +/// Describes the desired behavior when mutating a hybrid row. +enum class RowOptions +{ + None = 0, + + /// Overwrite an existing value. + /// + /// An existing value is assumed to exist at the offset provided. The existing value is + /// replaced inline. The remainder of the row is resized to accomodate either an increase or decrease + /// in required space. + /// + Update = 1, + + /// Insert a new value. + /// + /// An existing value is assumed NOT to exist at the offset provided. The new value is + /// inserted immediately at the offset. The remainder of the row is resized to accomodate either an + /// increase or decrease in required space. + /// + Insert = 2, + + /// Update an existing value or insert a new value, if no value exists. + /// + /// If a value exists, then this operation becomes , otherwise it + /// becomes . + /// + Upsert = 3, + + /// Insert a new value moving existing values to the right. + /// + /// Within an array scope, inserts a new value immediately at the index moving all subsequent + /// items to the right. In any other scope behaves the same as . + /// + InsertAt = 4, + + /// Delete an existing value. + /// + /// If a value exists, then it is removed. The remainder of the row is resized to accomodate + /// a decrease in required space. If no value exists this operation is a no-op. + /// + Delete = 5, +}; +} diff --git a/src/Serialization/HybridRow.Native/RowReader.cpp b/src/Serialization/HybridRow.Native/RowReader.cpp new file mode 100644 index 0000000..75ff525 --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowReader.cpp @@ -0,0 +1,393 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "RowReader.h" + +namespace cdb_hr +{ + RowReader::RowReader(RowBuffer& row, RowCursor scope) noexcept : + m_row{row}, + m_cursor{std::move(scope)}, + m_columns{m_cursor.GetLayout().GetColumns()}, + m_schematizedCount{static_cast(m_cursor.GetLayout().GetNumFixed() + m_cursor.GetLayout().GetNumVariable())}, + m_state{States::None}, + m_columnIndex{-1} { } + + /// Initializes a new instance of the struct. + /// The row to be read. + RowReader::RowReader(RowBuffer& row) noexcept : RowReader(row, RowCursor::Create(row)) { } + + /// Initializes a new instance of the struct. + /// The buffer. + /// The version of the Hybrid Row format to used to encoding the buffer. + /// The resolver for UDTs. + RowReader::RowReader(cdb_core::Memory buffer, HybridRowVersion version, + const LayoutResolver* resolver) noexcept : + m_row{buffer.AsSpan(), version, resolver, nullptr}, + m_cursor{RowCursor::Create(m_row)}, + m_columns{m_cursor.GetLayout().GetColumns()}, + m_schematizedCount{static_cast(m_cursor.GetLayout().GetNumFixed() + m_cursor.GetLayout().GetNumVariable())}, + m_state{States::None}, + m_columnIndex{-1} { } + + /// The length of row in bytes. + uint32_t RowReader::GetLength() const noexcept { return m_row.GetLength(); } + + /// The root header for the row. + HybridRowHeader RowReader::GetHeader() const noexcept { return m_row.GetHeader(); }; + + /// The storage placement of the field (if positioned on a field, undefined otherwise). + StorageKind RowReader::GetStorage() const noexcept + { + switch (m_state) + { + case States::Schematized: + return m_columns[m_columnIndex]->GetStorage(); + case States::Sparse: + return StorageKind::Sparse; + default: + return {}; + } + } + + /// The type of the field (if positioned on a field, undefined otherwise). + const LayoutType* RowReader::GetType() const noexcept + { + switch (m_state) + { + case States::Schematized: + return m_columns[m_columnIndex]->GetType(); + case States::Sparse: + return m_cursor.m_cellType; + default: + return {}; + } + } + + /// The type arguments of the field (if positioned on a field, undefined otherwise). + const TypeArgumentList& RowReader::GetTypeArgs() const noexcept + { + switch (m_state) + { + case States::Schematized: + return m_columns[m_columnIndex]->GetTypeArgs(); + case States::Sparse: + return m_cursor.m_cellTypeArgs; + default: + return TypeArgumentList::Empty; + } + } + + /// True if field has a value (if positioned on a field, undefined otherwise). + /// + /// If the current field is a Nullable scope, this method return true if the value is not + /// null. If the current field is a nullable Null primitive value, this method return true if the value + /// is set (even though its values is set to null). + /// + bool RowReader::HasValue() const noexcept + { + switch (m_state) + { + case States::Schematized: + return true; + case States::Sparse: + if (m_cursor.m_cellType->IsLayoutNullable()) + { + RowCursor nullableScope = m_row.SparseIteratorReadScope(m_cursor, true); + return LayoutNullable::HasValue(m_row, nullableScope) == Result::Success; + } + + return true; + default: + return false; + } + } + + /// + /// The path, relative to the scope, of the field (if positioned on a field, undefined + /// otherwise). + /// + /// When enumerating an indexed scope, this value is always null (see ). + std::string_view RowReader::GetPath() const noexcept + { + switch (m_state) + { + case States::Schematized: + return m_columns[m_columnIndex]->GetPath(); + case States::Sparse: + return m_row.ReadSparsePath(m_cursor); + default: + return {}; + } + } + + /// + /// The 0-based index, relative to the start of the scope, of the field (if positioned on a + /// field, undefined otherwise). + /// + /// When enumerating a non-indexed scope, this value is always 0 (see ). + uint32_t RowReader::GetIndex() const noexcept + { + switch (m_state) + { + case States::Schematized: + return 0; + case States::Sparse: + return m_cursor.m_index; + default: + return 0; + } + } + + /// Advances the reader to the next field. + /// True, if there is another field to be read, false otherwise. + bool RowReader::Read() noexcept + { + switch (m_state) + { + case States::None: + { + if (m_cursor.m_scopeType->IsUDT()) + { + m_state = States::Schematized; + goto Schematized; // NOLINT(cppcoreguidelines-avoid-goto, hicpp-avoid-goto) + } + + m_state = States::Sparse; + goto Sparse; // NOLINT(cppcoreguidelines-avoid-goto, hicpp-avoid-goto) + } + + case States::Schematized: + { + Schematized: + m_columnIndex++; + if (m_columnIndex >= m_schematizedCount) + { + m_state = States::Sparse; + goto Sparse; // NOLINT(cppcoreguidelines-avoid-goto, hicpp-avoid-goto) + } + + cdb_core::Contract::Assert(m_cursor.m_scopeType->IsUDT()); + const LayoutColumn& col = *m_columns[m_columnIndex]; + if (!m_row.ReadBit(m_cursor.m_start, col.GetNullBit())) + { + // Skip schematized values if they aren't present. + goto Schematized; // NOLINT(cppcoreguidelines-avoid-goto, hicpp-avoid-goto) + } + + return true; + } + + case States::Sparse: + { + Sparse: + if (!m_cursor.MoveNext(m_row)) + { + m_state = States::Done; + goto Done; // NOLINT(cppcoreguidelines-avoid-goto, hicpp-avoid-goto) + } + + return true; + } + + case States::Done: + { + Done: + return false; + } + } + + return false; + } + + /// Read the current field as a . + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadBool() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a null. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadNull() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 8-bit, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadInt8() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 16-bit, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadInt16() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 32-bit, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadInt32() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 64-bit, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadInt64() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 8-bit, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadUInt8() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 16-bit, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadUInt16() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 32-bit, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadUInt32() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 64-bit, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadUInt64() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a variable length, 7-bit encoded, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadVarInt() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a variable length, 7-bit encoded, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadVarUInt() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 32-bit, IEEE-encoded floating point value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadFloat32() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 64-bit, IEEE-encoded floating point value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadFloat64() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length, 128-bit, IEEE-encoded floating point value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadFloat128() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadDecimal() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadDateTime() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadUnixDateTime() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadGuid() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadMongoDbObjectId() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a variable length, UTF8 encoded, string value. + /// Success if the read is successful, an error code otherwise. + std::tuple RowReader::ReadString() noexcept + { + return RowReader::ReadPrimitiveValue(); + } + + /// Read the current field as a variable length, sequence of bytes. + /// Success if the read is successful, an error code otherwise. + std::tuple> RowReader::ReadBinary() noexcept + { + return RowReader::ReadPrimitiveValue, LayoutCode::Binary, &RowBuffer::ReadSparseBinary + >(); + } + + /// Read the current field as a nested, structured, sparse scope. + /// + /// Child readers can be used to read all sparse scope types including typed and untyped + /// objects, arrays, tuples, set, and maps. + /// + /// Nested child readers are independent of their parent. + /// + RowReader RowReader::ReadScope() noexcept + { + RowCursor newScope = m_row.SparseIteratorReadScope(m_cursor, true); + return RowReader{m_row, newScope}; + } + + /// + /// Advance a reader to the end of a child reader. The child reader is also advanced to the + /// end of its scope. + /// + /// + /// The reader must not have been advanced since the child reader was created with ReadScope. + /// This method can be used when the overload of that takes a + /// function is not an option. + /// + Result RowReader::SkipScope(RowReader& nestedReader) noexcept + { + if (nestedReader.m_cursor.m_start != m_cursor.m_valueOffset) + { + return Result::Failure; + } + + m_cursor.Skip(m_row, nestedReader.m_cursor); + return Result::Success; + } +} diff --git a/src/Serialization/HybridRow.Native/RowReader.h b/src/Serialization/HybridRow.Native/RowReader.h new file mode 100644 index 0000000..b3fa8ff --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowReader.h @@ -0,0 +1,312 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "LayoutCodeTraits.h" +#include "Result.h" +#include "RowBuffer.h" +#include "RowCursor.h" +#include "IHybridRowSerializer.h" + +namespace cdb_hr +{ + class LayoutColumn; + + /// A forward-only, streaming, field reader for . + /// + /// A allows the traversal in a streaming, left to right fashion, of + /// an entire HybridRow. The row's layout provides decoding for any schematized portion of the row. + /// However, unschematized sparse fields are read directly from the sparse segment with or without + /// schematization allowing all fields within the row, both known and unknown, to be read. + /// + /// Modifying a invalidates any reader or child reader associated with it. In + /// general 's should not be mutated while being enumerated. + /// + struct RowReader + { + /// Initializes a new instance of the struct. + /// The row to be read. + RowReader(RowBuffer& row) noexcept; + + /// Initializes a new instance of the struct. + /// The buffer. + /// The version of the Hybrid Row format to used to encoding the buffer. + /// The resolver for UDTs. + RowReader(cdb_core::Memory buffer, HybridRowVersion version, const LayoutResolver* resolver) noexcept; + + /// The length of row in bytes. + [[nodiscard]] uint32_t GetLength() const noexcept; + + /// The root header for the row. + [[nodiscard]] HybridRowHeader GetHeader() const noexcept; + + /// The storage placement of the field (if positioned on a field, undefined otherwise). + [[nodiscard]] StorageKind GetStorage() const noexcept; + + /// The type of the field (if positioned on a field, undefined otherwise). + [[nodiscard]] const LayoutType* GetType() const noexcept; + + /// The type arguments of the field (if positioned on a field, undefined otherwise). + [[nodiscard]] const TypeArgumentList& GetTypeArgs() const noexcept; + + /// True if field has a value (if positioned on a field, undefined otherwise). + /// + /// If the current field is a Nullable scope, this method return true if the value is not + /// null. If the current field is a nullable Null primitive value, this method return true if the value + /// is set (even though its values is set to null). + /// + [[nodiscard]] bool HasValue() const noexcept; + + /// + /// The path, relative to the scope, of the field (if positioned on a field, undefined + /// otherwise). + /// + /// When enumerating an indexed scope, this value is always empty (see ). + [[nodiscard]] std::string_view GetPath() const noexcept; + + /// + /// The 0-based index, relative to the start of the scope, of the field (if positioned on a + /// field, undefined otherwise). + /// + /// When enumerating a non-indexed scope, this value is always 0 (see ). + [[nodiscard]] uint32_t GetIndex() const noexcept; + + /// Advances the reader to the next field. + /// True, if there is another field to be read, false otherwise. + [[nodiscard]] bool Read() noexcept; + + /// Read the current field as a . + /// Success if the read is successful, an error code otherwise. + std::tuple ReadBool() noexcept; + + /// Read the current field as a null. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadNull() noexcept; + + /// Read the current field as a fixed length, 8-bit, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadInt8() noexcept; + + /// Read the current field as a fixed length, 16-bit, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadInt16() noexcept; + + /// Read the current field as a fixed length, 32-bit, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadInt32() noexcept; + + /// Read the current field as a fixed length, 64-bit, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadInt64() noexcept; + + /// Read the current field as a fixed length, 8-bit, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadUInt8() noexcept; + + /// Read the current field as a fixed length, 16-bit, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadUInt16() noexcept; + + /// Read the current field as a fixed length, 32-bit, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadUInt32() noexcept; + + /// Read the current field as a fixed length, 64-bit, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadUInt64() noexcept; + + /// Read the current field as a variable length, 7-bit encoded, signed integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadVarInt() noexcept; + + /// Read the current field as a variable length, 7-bit encoded, unsigned integer. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadVarUInt() noexcept; + + /// Read the current field as a fixed length, 32-bit, IEEE-encoded floating point value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadFloat32() noexcept; + + /// Read the current field as a fixed length, 64-bit, IEEE-encoded floating point value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadFloat64() noexcept; + + /// Read the current field as a fixed length, 128-bit, IEEE-encoded floating point value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadFloat128() noexcept; + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadDecimal() noexcept; + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadDateTime() noexcept; + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadUnixDateTime() noexcept; + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadGuid() noexcept; + + /// Read the current field as a fixed length value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadMongoDbObjectId() noexcept; + + /// Read the current field as a variable length, UTF8 encoded, string value. + /// Success if the read is successful, an error code otherwise. + std::tuple ReadString() noexcept; + + /// Read the current field as a variable length, sequence of bytes. + /// Success if the read is successful, an error code otherwise. + std::tuple> ReadBinary() noexcept; + + /// Read the current field as a nested, structured, sparse scope. + /// + /// Child readers can be used to read all sparse scope types including typed and untyped + /// objects, arrays, tuples, set, and maps. + /// + /// Nested child readers are independent of their parent. + /// + RowReader ReadScope() noexcept; + + /// Read the current field as a nested, structured, sparse scope. + template>> + std::tuple> ReadScope(); + + /// Read the current field as a nested, structured, sparse scope. + /// + /// Child readers can be used to read all sparse scope types including typed and untyped + /// objects, arrays, tuples, set, and maps. + /// + template> + Result ReadScope(TCallable& func) noexcept; + + /// + /// Advance a reader to the end of a child reader. The child reader is also advanced to the + /// end of its scope. + /// + /// + /// The reader must not have been advanced since the child reader was created with ReadScope. + /// This method can be used when the overload of that takes a + /// function is not an option. + /// + Result SkipScope(RowReader& nestedReader) noexcept; + + private: + /// Read a generic schematized field value via the scope's layout. + /// The expected type of the field. + /// Success if the read is successful, an error code otherwise. + template + std::tuple ReadPrimitiveValue() noexcept; + + /// Initializes a new instance of the struct. + /// The row to be read. + /// The scope whose fields should be enumerated. + /// + /// A instance traverses all of the top-level fields of a given + /// scope. If the root scope is provided then all top-level fields in the row are enumerated. Nested + /// child instances can be access through the method + /// to process nested content. + /// + RowReader(RowBuffer& row, RowCursor scope) noexcept; + + /// The current traversal state of the reader. + enum States : unsigned char + { + /// The reader has not be started yet. + None, + + /// Enumerating schematized fields (fixed and variable) from left to right. + Schematized, + + /// Enumerating top-level fields of the current scope. + Sparse, + + /// The reader has completed the scope. + Done, + }; + + RowBuffer m_row; + RowCursor m_cursor; + const tla::vector& m_columns; + int m_schematizedCount; + States m_state; + int m_columnIndex; + }; + + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + // Template Definitions + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + + template + std::tuple> RowReader::ReadScope() + { + return TSerializer{}.Read(m_row, m_cursor, /* isRoot */ false); + } + + /// Read the current field as a nested, structured, sparse scope. + /// + /// Child readers can be used to read all sparse scope types including typed and untyped + /// objects, arrays, tuples, set, and maps. + /// + template + Result RowReader::ReadScope(TCallable& func) noexcept + { + RowCursor childScope = m_row.SparseIteratorReadScope(m_cursor, true); + RowReader nestedReader = RowReader{m_row, std::move(childScope)}; + Result result = func ? std::invoke(func, nestedReader) : Result::Success; + if (result != Result::Success) + { + return result; + } + + m_cursor.Skip(m_row, nestedReader.m_cursor); + return Result::Success; + } + + /// Read a generic schematized field value via the scope's layout. + /// The expected type of the field. + /// Success if the read is successful, an error code otherwise. + template + std::tuple RowReader::ReadPrimitiveValue() noexcept + { + switch (m_state) + { + case States::Schematized: + { + const LayoutColumn& col = *m_columns[m_columnIndex]; + const LayoutType* t = m_columns[m_columnIndex]->GetType(); + if (LayoutCodeTraits::Canonicalize(t->GetLayoutCode()) != code) + { + return {Result::TypeMismatch, {}}; + } + + switch (col.GetStorage()) + { + case StorageKind::Fixed: + return static_cast*>(t)->ReadFixed(m_row, m_cursor, col); + case StorageKind::Variable: + return static_cast*>(t)->ReadVariable(m_row, m_cursor, col); + default: + cdb_core::Contract::Assert(false); + return {Result::Failure, {}}; + } + } + case States::Sparse: + if (LayoutCodeTraits::Canonicalize(m_cursor.m_cellType->GetLayoutCode()) != code) + { + return {Result::TypeMismatch, {}}; + } + + return {Result::Success, std::invoke(ReadSparseFunc, m_row, m_cursor)}; + default: + return {Result::Failure, {}}; + } + } +} diff --git a/src/Serialization/HybridRow.Native/RowWriter.cpp b/src/Serialization/HybridRow.Native/RowWriter.cpp new file mode 100644 index 0000000..8fcc9f6 --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowWriter.cpp @@ -0,0 +1,37 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "RowWriter.h" + +// ReSharper disable CppClangTidyCppcoreguidelinesProTypeStaticCastDowncast +namespace cdb_hr +{ + Result RowWriter::PrepareSparseWrite(std::string_view path, const TypeArgument& typeArg) noexcept + { + if (m_cursor.m_scopeType->IsFixedArity() && !(m_cursor.m_scopeType->IsLayoutNullable())) + { + if ((m_cursor.m_index < m_cursor.m_scopeTypeArgs.GetCount()) && + (typeArg != m_cursor.m_scopeTypeArgs[m_cursor.m_index])) + { + return Result::TypeConstraint; + } + } + else if (m_cursor.m_scopeType->IsLayoutTypedMap()) + { + const LayoutUniqueScope* t = static_cast(m_cursor.m_scopeType); + if (typeArg != t->FieldType(m_cursor)) + { + return Result::TypeConstraint; + } + } + else if (m_cursor.m_scopeType->IsTypedScope() && (typeArg != m_cursor.m_scopeTypeArgs[0])) + { + return Result::TypeConstraint; + } + + m_cursor.m_writePath = path; + return Result::Success; + } +} diff --git a/src/Serialization/HybridRow.Native/RowWriter.h b/src/Serialization/HybridRow.Native/RowWriter.h new file mode 100644 index 0000000..7c288dc --- /dev/null +++ b/src/Serialization/HybridRow.Native/RowWriter.h @@ -0,0 +1,530 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "LayoutCodeTraits.h" +#include "Result.h" +#include "RowBuffer.h" +#include "RowCursor.h" +#include "IHybridRowSerializer.h" + +namespace cdb_hr +{ + struct RowWriter final + { + /// The resolver for UDTs. + [[nodiscard]] const LayoutResolver* GetResolver() const noexcept { return m_row.GetResolver(); } + + /// The length of row in bytes. + [[nodiscard]] uint32_t GetLength() const noexcept { return m_row.GetLength(); } + + /// The active layout of the current writer scope. + [[nodiscard]] const Layout& GetLayout() const noexcept { return m_cursor.GetLayout(); } + + /// Write an entire row in a streaming left-to-right way. + /// The type of the context value to pass to . + /// The row to write. + /// A function to write the entire row. + /// Success if the write is successful, an error code otherwise. + template> + static Result WriteBuffer(RowBuffer& row, TCallable& func) + { + RowWriter writer{row, RowCursor::Create(row)}; + TypeArgument typeArg{&LayoutLiteral::UDT, {writer.GetLayout().GetSchemaId()}}; + Result result = std::invoke(func, writer, typeArg); + row = writer.m_row; + return result; + } + + /// Write a field as a . + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteBool(std::string_view path, bool value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Boolean); + } + + /// Write a field as a . + /// The scope-relative path of the field to write. + /// Success if the write is successful, an error code otherwise. + Result WriteNull(std::string_view path) + { + return WritePrimitive( + path, + {}, + &LayoutLiteral::Null); + } + + /// Write a field as a fixed length, 8-bit, signed integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteInt8(std::string_view path, int8_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Int8); + } + + /// Write a field as a fixed length, 16-bit, signed integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteInt16(std::string_view path, int16_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Int16); + } + + /// Write a field as a fixed length, 32-bit, signed integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteInt32(std::string_view path, int32_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Int32); + } + + /// Write a field as a fixed length, 64-bit, signed integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteInt64(std::string_view path, int64_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Int64); + } + + /// Write a field as a fixed length, 8-bit, unsigned integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteUInt8(std::string_view path, uint8_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::UInt8); + } + + /// Write a field as a fixed length, 16-bit, unsigned integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteUInt16(std::string_view path, uint16_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::UInt16); + } + + /// Write a field as a fixed length, 32-bit, unsigned integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteUInt32(std::string_view path, uint32_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::UInt32); + } + + /// Write a field as a fixed length, 64-bit, unsigned integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteUInt64(std::string_view path, uint64_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::UInt64); + } + + /// Write a field as a variable length, 7-bit encoded, signed integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteVarInt(std::string_view path, int64_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::VarInt); + } + + /// Write a field as a variable length, 7-bit encoded, unsigned integer. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteVarUInt(std::string_view path, uint64_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::VarUInt); + } + + /// Write a field as a fixed length, 32-bit, IEEE-encoded floating point value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteFloat32(std::string_view path, float32_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Float32); + } + + /// Write a field as a fixed length, 64-bit, IEEE-encoded floating point value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteFloat64(std::string_view path, float64_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Float64); + } + + /// Write a field as a fixed length, 128-bit, IEEE-encoded floating point value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteFloat128(std::string_view path, float128_t value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Float128); + } + + /// Write a field as a fixed length value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteDecimal(std::string_view path, Decimal value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Decimal); + } + + /// Write a field as a fixed length value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteDateTime(std::string_view path, DateTime value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::DateTime); + } + + /// Write a field as a fixed length value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteUnixDateTime(std::string_view path, UnixDateTime value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::UnixDateTime); + } + + /// Write a field as a fixed length value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteGuid(std::string_view path, Guid value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Guid); + } + + /// Write a field as a fixed length value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteMongoDbObjectId(std::string_view path, MongoDbObjectId value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::MongoDbObjectId); + } + + /// Write a field as a variable length, UTF8 encoded, string value. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteString(std::string_view path, std::string_view value) + { + return WritePrimitive( + path, + value, + &LayoutLiteral::Utf8); + } + + /// Write a field as a variable length, sequence of bytes. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + Result WriteBinary(std::string_view path, cdb_core::ReadOnlySpan value) + { + return WritePrimitive, LayoutCode::Binary, &RowBuffer::WriteSparseBinary>( + path, + value, + &LayoutLiteral::Binary); + } + + template>> + Result WriteScope(std::string_view path, const TypeArgument& typeArg, const T& value) noexcept + { + Result result = PrepareSparseWrite(path, typeArg); + if (result != Result::Success) + { + return result; + } + + result = TSerializer{}.Write(m_row, m_cursor, /* isRoot */ false, typeArg.GetTypeArgs(), value); + if (result != Result::Success) + { + return result; + } + + m_cursor.MoveNext(m_row); + return Result::Success; + } + + template> + Result WriteScope(std::string_view path, const TypeArgument& typeArg, TCallable& func) + { + const LayoutType* type = typeArg.GetType(); + Result result = PrepareSparseWrite(path, typeArg); + if (result != Result::Success) + { + return result; + } + + RowCursor nestedScope; + const LayoutScope* scopeType = static_cast(type); + switch (LayoutCodeTraits::ClearImmutableBit(type->GetLayoutCode())) + { + case LayoutCode::ObjectScope: + nestedScope = m_row.WriteSparseObject(m_cursor, scopeType, UpdateOptions::Upsert); + break; + case LayoutCode::ArrayScope: + nestedScope = m_row.WriteSparseArray(m_cursor, scopeType, UpdateOptions::Upsert); + break; + case LayoutCode::TypedArrayScope: + nestedScope = m_row.WriteTypedArray( + m_cursor, + scopeType, + typeArg.GetTypeArgs(), + UpdateOptions::Upsert); + + break; + case LayoutCode::TupleScope: + nestedScope = m_row.WriteSparseTuple( + m_cursor, + scopeType, + typeArg.GetTypeArgs(), + UpdateOptions::Upsert); + + break; + case LayoutCode::TypedTupleScope: + nestedScope = m_row.WriteTypedTuple( + m_cursor, + scopeType, + typeArg.GetTypeArgs(), + UpdateOptions::Upsert); + + break; + case LayoutCode::TaggedScope: + nestedScope = m_row.WriteTypedTuple( + m_cursor, + scopeType, + typeArg.GetTypeArgs(), + UpdateOptions::Upsert); + + break; + case LayoutCode::Tagged2Scope: + nestedScope = m_row.WriteTypedTuple( + m_cursor, + scopeType, + typeArg.GetTypeArgs(), + UpdateOptions::Upsert); + + break; + case LayoutCode::NullableScope: + nestedScope = m_row.WriteNullable( + m_cursor, + scopeType, + typeArg.GetTypeArgs(), + UpdateOptions::Upsert, + func != false); + + break; + case LayoutCode::Schema: + const Layout& udt = m_row.GetResolver()->Resolve(typeArg.GetTypeArgs().GetSchemaId()); + nestedScope = m_row.WriteSparseUDT(m_cursor, scopeType, &udt, UpdateOptions::Upsert); + break; + + case LayoutCode::TypedSetScope: + nestedScope = m_row.WriteTypedSet( + m_cursor, + scopeType, + typeArg.GetTypeArgs(), + UpdateOptions::Upsert); + + break; + case LayoutCode::TypedMapScope: + nestedScope = m_row.WriteTypedMap( + m_cursor, + scopeType, + typeArg.GetTypeArgs(), + UpdateOptions::Upsert); + + break; + + default: + return Result::Failure; + } + + RowWriter nestedWriter{m_row, nestedScope}; + result = func ? std::invoke(func, nestedWriter, typeArg) : Result::Success; + m_row = nestedWriter.m_row; + nestedScope.m_count = nestedWriter.m_cursor.m_count; + + if (result != Result::Success) + { + // TODO: what about unique violations here? + return result; + } + + if (type->IsLayoutUniqueScope()) + { + result = m_row.TypedCollectionUniqueIndexRebuild(nestedScope); + if (result != Result::Success) + { + // TODO: If the index rebuild fails then the row is corrupted. Should we automatically clean up here? + return result; + } + } + + m_cursor.MoveNext(m_row, nestedWriter.m_cursor); + return Result::Success; + } + + private: + /// Helper for writing a primitive value. + /// The type of the primitive value. + /// The scope-relative path of the field to write. + /// The value to write. + /// The layout type. + /// Success if the write is successful, an error code otherwise. + template + Result WritePrimitive(std::string_view path, TValue value, const ScalarLayoutType* type) noexcept + { + Result result = Result::NotFound; + if (m_cursor.m_scopeType->IsUDT()) + { + result = WriteSchematizedValue(path, value); + } + + if (result == Result::NotFound) + { + // Write sparse value. + result = PrepareSparseWrite(path, type->GetTypeArg()); + if (result != Result::Success) + { + return result; + } + + std::invoke(WriteSparseFunc, &m_row, m_cursor, value, UpdateOptions::Upsert); + m_cursor.MoveNext(m_row); + } + + return result; + } + + /// Helper for preparing the write of a sparse field. + /// The path identifying the field to write. + /// The (optional) type constraints. + /// Success if the write is permitted, the error code otherwise. + Result PrepareSparseWrite(std::string_view path, const TypeArgument& typeArg) noexcept; + + /// Write a generic schematized field value via the scope's layout. + /// The expected type of the field. + /// The scope-relative path of the field to write. + /// The value to write. + /// Success if the write is successful, an error code otherwise. + template + Result WriteSchematizedValue(std::string_view path, TValue value) noexcept + { + auto [found, pCol] = m_cursor.m_layout->TryFind(path); + if (!found) + { + return Result::NotFound; + } + const LayoutColumn& col = *pCol; + + if (LayoutCodeTraits::Canonicalize(col.GetType()->GetLayoutCode()) != code) + { + return Result::NotFound; + } + + const ScalarLayoutType* t = static_cast*>(col.GetType()); + switch (col.GetStorage()) + { + case StorageKind::Fixed: + return t->WriteFixed(m_row, m_cursor, col, value); + + case StorageKind::Variable: + return t->WriteVariable(m_row, m_cursor, col, value); + + default: + return Result::NotFound; + } + } + + /// Initializes a new instance of the struct. + /// The row to be read. + /// The scope into which items should be written. + /// + /// A 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. + /// + RowWriter(RowBuffer& row, RowCursor scope) noexcept : m_row{row}, m_cursor{std::move(scope)} { } + + RowBuffer m_row; + RowCursor m_cursor; + }; +} diff --git a/src/Serialization/HybridRow.Native/SamplingUtf8StringComparer.h b/src/Serialization/HybridRow.Native/SamplingUtf8StringComparer.h new file mode 100644 index 0000000..10c9e5e --- /dev/null +++ b/src/Serialization/HybridRow.Native/SamplingUtf8StringComparer.h @@ -0,0 +1,38 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_hr +{ +struct SamplingUtf8StringComparer +{ + std::size_t operator()(std::string_view obj) const noexcept + { + uint32_t hash1 = 5381; + uint32_t hash2 = hash1; + const size_t numSamples = 4; + const size_t modulus = 13; + + const char* utf8 = obj.data(); + const size_t length = obj.size(); + const size_t max = std::min(length, numSamples); + for (size_t i = 0; i < max; i++) + { + const auto c = static_cast(static_cast(utf8[(i * modulus) % length])); + if (i % 2 == 0) + { + hash1 = ((hash1 << 5) + hash1) ^ c; + } + else + { + hash2 = ((hash2 << 5) + hash2) ^ c; + } + } + + return (hash1 + (hash2 * 1566083941)); + } +}; +} diff --git a/src/Serialization/HybridRow.Native/Schema.cpp b/src/Serialization/HybridRow.Native/Schema.cpp new file mode 100644 index 0000000..81dc57b --- /dev/null +++ b/src/Serialization/HybridRow.Native/Schema.cpp @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "LayoutCompiler.h" +#include "Namespace.h" + +namespace cdb_hr +{ + std::unique_ptr Schema::Compile(const Namespace& ns) const noexcept(false) + { + cdb_core::Contract::Requires(std::find_if(ns.GetSchemas().begin(), ns.GetSchemas().end(), + [schema = this](const std::unique_ptr& p) { return p.get() == schema; }) != ns.GetSchemas().end()); + + return LayoutCompiler::Compile(ns, *this); + } + + [[nodiscard]] SchemaLanguageVersion Schema::GetEffectiveSdlVersion(const Namespace& ns) const noexcept + { + return m_version != SchemaLanguageVersion::Unspecified ? m_version : ns.GetEffectiveSdlVersion(); + } +} diff --git a/src/Serialization/HybridRow.Native/Schema.h b/src/Serialization/HybridRow.Native/Schema.h new file mode 100644 index 0000000..7798072 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Schema.h @@ -0,0 +1,184 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include +#include +#include + +#include "SchemaId.h" +#include "SchemaLanguageVersion.h" +#include "SchemaOptions.h" +#include "TypeKind.h" +#include "Property.h" +#include "PartitionKey.h" +#include "PrimarySortKey.h" +#include "StaticKey.h" + +namespace cdb_hr +{ + class Layout; + class LayoutCompiler; + class Namespace; + + /// A schema describes either table or UDT metadata. + /// + /// The schema of a table or UDT describes the structure of row (i.e. which columns and the + /// types of those columns). A table schema represents the description of the contents of a collection + /// level row directly. UDTs described nested structured objects that may appear either within a table + /// column or within another UDT (i.e. nested UDTs). + /// + class Schema final + { + public: + Schema() noexcept : + m_version{SchemaLanguageVersion::Unspecified}, + m_comment{}, + m_name{}, + m_schemaId{SchemaId::Invalid()}, + m_baseName{}, + m_baseSchemaId{SchemaId::Invalid()}, + m_options{}, + m_type{TypeKind::Schema}, + m_partitionKeys{}, + m_primaryKeys{}, + m_staticKeys{}, + m_properties{} {} + + ~Schema() noexcept = default; + Schema(const Schema& other) = delete; + Schema(Schema&& other) = delete; + Schema& operator=(const Schema& other) = delete; + Schema& operator=(Schema&& other) = delete; + + /// The version of the HybridRow Schema Definition Language used to encode this schema. + [[nodiscard]] SchemaLanguageVersion GetVersion() const noexcept { return m_version; } + void SetVersion(SchemaLanguageVersion value) noexcept { m_version = value; } + + /// An (optional) comment describing the purpose of this schema. + /// Comments are for documentary purpose only and do not affect the schema at runtime. + [[nodiscard]] std::string_view GetComment() const noexcept { return m_comment; } + void SetComment(std::string_view value) noexcept { m_comment = value; } + + /// The name of the schema. + /// + /// The name of a schema MUST be unique within its namespace. + /// + /// Names must begin with an alpha-numeric character and can only contain alpha-numeric characters and + /// underscores. + /// + [[nodiscard]] std::string_view GetName() const noexcept { return m_name; } + void SetName(std::string_view value) noexcept { m_name = value; } + + /// The unique identifier for a schema. + /// Identifiers must be unique within the scope of the database in which they are used. + [[nodiscard]] SchemaId GetSchemaId() const noexcept { return m_schemaId; } + void SetSchemaId(SchemaId value) noexcept { m_schemaId = value; } + + /// The name of the schema this schema derives from. + [[nodiscard]] std::string_view GetBaseName() const noexcept { return m_baseName; } + void SetBaseName(std::string_view value) noexcept { m_baseName = value; } + + /// The unique identifier of the schema this schema derives from. + [[nodiscard]] SchemaId GetBaseSchemaId() const noexcept { return m_baseSchemaId; } + void SetBaseSchemaId(SchemaId value) noexcept { m_baseSchemaId = value; } + + /// Schema-wide operations. + [[nodiscard]] std::optional> GetOptions() const noexcept + { + return m_options ? std::optional>{*m_options} : std::nullopt; + } + + void SetOptions(std::unique_ptr value) noexcept { m_options = std::move(value); } + + /// The type of this schema. This value MUST be . + [[nodiscard]] TypeKind GetType() const noexcept { return m_type; } + void SetType(TypeKind value) noexcept { m_type = value; } + + /// A list of zero or more property definitions that define the columns within the schema. + /// This field is never null. + [[nodiscard]] const std::vector>& GetProperties() const noexcept { return m_properties; } + std::vector>& GetProperties() noexcept { return m_properties; } + void SetProperties(std::vector> value) noexcept { m_properties = std::move(value); } + + /// An (optional) list of zero or more logical paths that form the partition key. + /// All paths referenced MUST map to a property within the schema. + /// + /// This field is never null. + [[nodiscard]] const std::vector>& GetPartitionKeys() const noexcept + { + return m_partitionKeys; + } + + std::vector>& GetPartitionKeys() noexcept { return m_partitionKeys; } + + void SetPartitionKeys(std::vector> value) noexcept + { + m_partitionKeys = std::move(value); + } + + /// An (optional) list of zero or more logical paths that form the primary sort key. + /// All paths referenced MUST map to a property within the schema. + /// + /// This field is never null. + [[nodiscard]] const std::vector>& GetPrimaryKeys() const noexcept + { + return m_primaryKeys; + } + + std::vector>& GetPrimaryKeys() noexcept { return m_primaryKeys; } + + void SetPrimaryKeys(std::vector> value) noexcept + { + m_primaryKeys = std::move(value); + } + + /// An (optional) list of zero or more logical paths that hold data shared by all documents with same partition key. + /// All paths referenced MUST map to a property within the schema. + /// + /// This field is never null. + [[nodiscard]] const std::vector>& GetStaticKeys() const noexcept { return m_staticKeys; } + std::vector>& GetStaticKeys() noexcept { return m_staticKeys; } + void SetStaticKeys(std::vector> value) noexcept { m_staticKeys = std::move(value); } + + /// + /// Compiles this logical schema into a physical layout that can be used to read and write + /// rows. + /// + /// The namespace within which this schema is defined. + /// The layout for the schema. + [[nodiscard]] std::unique_ptr Compile(const Namespace& ns) const noexcept(false); + private: + friend class LayoutCompiler; + + /// + /// Returns the effective SDL language version of the current schema in the context of the given . + /// + /// The namespace used to resolve the schema. + /// The effective SDL language version. + [[nodiscard]] SchemaLanguageVersion GetEffectiveSdlVersion(const Namespace& ns) const noexcept; + + SchemaLanguageVersion m_version; + tla::string m_comment; + tla::string m_name; + SchemaId m_schemaId; + tla::string m_baseName; + SchemaId m_baseSchemaId; + std::unique_ptr m_options; + TypeKind m_type; + + /// An (optional) list of zero or more logical paths that form the partition key. + std::vector> m_partitionKeys; + + /// An (optional) list of zero or more logical paths that form the primary sort key. + std::vector> m_primaryKeys; + + /// An (optional) list of zero or more logical paths that hold data shared by all documents that have the same partition key. + std::vector> m_staticKeys; + + /// A list of zero or more property definitions that define the columns within the schema. + std::vector> m_properties; + }; +} diff --git a/src/Serialization/HybridRow.Native/SchemaId.h b/src/Serialization/HybridRow.Native/SchemaId.h new file mode 100644 index 0000000..6ddfdf7 --- /dev/null +++ b/src/Serialization/HybridRow.Native/SchemaId.h @@ -0,0 +1,73 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// The unique identifier for a schema. + /// Identifiers must be unique within the scope of the database in which they are used. + struct SchemaId final + { + constexpr static uint32_t Size = sizeof(int32_t); + constexpr static SchemaId Invalid() { return SchemaId(); } + + /// Initializes a new instance of the struct. + constexpr SchemaId() noexcept : m_id{0} {} + ~SchemaId() noexcept = default; + constexpr SchemaId(const SchemaId& other) noexcept = default; + constexpr SchemaId(SchemaId&& other) noexcept = default; + SchemaId& operator=(const SchemaId& other) noexcept = default; + SchemaId& operator=(SchemaId&& other) noexcept = default; + + /// Initializes a new instance of the struct. + /// The underlying globally unique identifier of the schema. + explicit constexpr SchemaId(int32_t id) noexcept : m_id(id) { } + + /// The underlying identifier. + [[nodiscard]] constexpr int32_t Id() const noexcept { return m_id; } + + /// Integer conversion operator. + explicit constexpr operator int32_t() const { return m_id; } + + /// Operator == overload. + friend bool operator==(const SchemaId& lhs, const SchemaId& rhs) noexcept { return lhs.m_id == rhs.m_id; } + + /// Operator != overload. + friend bool operator!=(const SchemaId& lhs, const SchemaId& rhs) noexcept { return !(lhs == rhs); } + + [[nodiscard]] std::string ToString() const noexcept + { + return cdb_core::make_string("%d", m_id); + } + + [[nodiscard]] size_t GetHashCode() const noexcept + { + return std::hash{}(m_id); + } + + private: + friend struct std::hash; + + /// The underlying identifier. + int32_t m_id; + }; + + // Declare constraints. + static_assert(cdb_core::is_blittable_v); + static_assert(cdb_core::is_hashable_v); + static_assert(cdb_core::is_stringable_v); +} + +namespace std +{ + template<> + struct hash + { + std::size_t operator()(cdb_hr::SchemaId const& s) const noexcept + { + return s.GetHashCode(); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/SchemaLanguageVersion.h b/src/Serialization/HybridRow.Native/SchemaLanguageVersion.h new file mode 100644 index 0000000..8f6efdf --- /dev/null +++ b/src/Serialization/HybridRow.Native/SchemaLanguageVersion.h @@ -0,0 +1,28 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// Versions of the HybridRow Schema Description Language. + enum class SchemaLanguageVersion : unsigned char + { + /// Initial version of the HybridRow Schema Description Language. + V1 = 0, + + /// Introduced Enums, Inheritance. + V2 = 2, + + /// The latest version. + Latest = V2, + + /// No version is specified. + /// + /// When applied to a Namespace, unspecified will map to . + /// When applied to a Schema, unspecified will map to the version given in the namespace. + /// + Unspecified = 255, + }; +} diff --git a/src/Serialization/HybridRow.Native/SchemaOptions.h b/src/Serialization/HybridRow.Native/SchemaOptions.h new file mode 100644 index 0000000..923fb0a --- /dev/null +++ b/src/Serialization/HybridRow.Native/SchemaOptions.h @@ -0,0 +1,63 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// Describes the set of options that apply to the entire schema and the way it is validated. + class SchemaOptions final + { + public: + constexpr SchemaOptions() noexcept : + m_disallowUnschematized{false}, + m_enablePropertyLevelTimestamp{false}, + m_disableSystemPrefix{false}, + m_abstract{false} {} + + ~SchemaOptions() noexcept = default; + SchemaOptions(const SchemaOptions& other) = delete; + SchemaOptions(SchemaOptions&& other) = delete; + SchemaOptions& operator=(const SchemaOptions& other) = delete; + SchemaOptions& operator=(SchemaOptions&& other) = delete; + + /// If true then structural schema validation is enabled. + /// + /// When structural schema validation is enabled then attempting to store an unschematized + /// path in the row, or a value whose type does not conform to the type constraints defined for that + /// path within the schema will lead to a schema validation error. When structural schema validation is + /// NOT enabled, then storing an unschematized path or non-confirming value will lead to a sparse + /// column override of the path. The value will be stored (and any existing value at that path will be + /// overwritten). No error will be given. + /// + [[nodiscard]] bool GetDisallowUnschematized() const noexcept { return m_disallowUnschematized; } + void SetDisallowUnschematized(bool value) noexcept { m_disallowUnschematized = value; } + + /// + /// If set and has the value true, then triggers behavior in the Schema that acts based on + /// property level timestamps. In Cassandra, this means that new columns are added for each top level + /// property that has values of the client side timestamp. This is then used in conflict resolution to + /// independently resolve each property based on the timestamp value of that property. + /// + [[nodiscard]] bool GetEnablePropertyLevelTimestamp() const noexcept { return m_enablePropertyLevelTimestamp; } + void SetEnablePropertyLevelTimestamp(bool value) noexcept { m_enablePropertyLevelTimestamp = value; } + + /// + /// If the is value true, then disables prefixing the system properties with a prefix __sys_ + /// for reserved properties owned by the store layer. + /// + [[nodiscard]] bool GetDisableSystemPrefix() const noexcept { return m_disableSystemPrefix; } + void SetDisableSystemPrefix(bool value) noexcept { m_disableSystemPrefix = value; } + + /// If true then instances of this schema cannot be created directly, only through subtypes. + [[nodiscard]] bool GetAbstract() const noexcept { return m_abstract; } + void SetAbstract(bool value) noexcept { m_abstract = value; } + + private: + bool m_disallowUnschematized; + bool m_enablePropertyLevelTimestamp; + bool m_disableSystemPrefix; + bool m_abstract; + }; +} diff --git a/src/Serialization/HybridRow.Native/ScopePropertyType.h b/src/Serialization/HybridRow.Native/ScopePropertyType.h new file mode 100644 index 0000000..48b714a --- /dev/null +++ b/src/Serialization/HybridRow.Native/ScopePropertyType.h @@ -0,0 +1,38 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "PropertyType.h" + +namespace cdb_hr +{ + class ScopePropertyType : public PropertyType + { + public: + ~ScopePropertyType() noexcept override = default; + ScopePropertyType(ScopePropertyType&) = delete; + ScopePropertyType(ScopePropertyType&&) = delete; + ScopePropertyType& operator=(const ScopePropertyType&) = delete; + ScopePropertyType& operator=(ScopePropertyType&&) = delete; + + /// True if the property's child elements cannot be mutated in place. + /// Immutable properties can still be replaced in their entirety. + [[nodiscard]] bool GetImmutable() const noexcept { return m_immutable; } + void SetImmutable(bool value) noexcept { m_immutable = value; } + + private: + friend class ArrayPropertyType; + friend class ObjectPropertyType; + friend class MapPropertyType; + friend class SetPropertyType; + friend class TaggedPropertyType; + friend class TuplePropertyType; + friend class UdtPropertyType; + + ScopePropertyType(TypeKind type, bool nullable = true, bool immutable = false) noexcept : + PropertyType{type, nullable}, m_immutable{immutable} { } + + bool m_immutable; + }; +} diff --git a/src/Serialization/HybridRow.Native/Segment.h b/src/Serialization/HybridRow.Native/Segment.h new file mode 100644 index 0000000..6cc9aa7 --- /dev/null +++ b/src/Serialization/HybridRow.Native/Segment.h @@ -0,0 +1,52 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "Namespace.h" + +namespace cdb_hr +{ + class Segment final + { + public: + [[nodiscard]] Segment() noexcept : + m_length{}, + m_comment{}, + m_sdl{}, + m_schema{} {} + + [[nodiscard]] int32_t GetLength() const noexcept { return m_length; } + void SetLength(int32_t value) noexcept { m_length = value; } + [[nodiscard]] std::string_view GetComment() const noexcept { return m_comment; } + void SetComment(std::string_view value) noexcept { m_comment = value; } + [[nodiscard]] std::string_view GetSDL() const noexcept { return m_sdl; } + void SetSDL(std::string_view value) noexcept { m_sdl = value; } + [[nodiscard]] const std::unique_ptr& GetSchema() const noexcept { return m_schema; } + void SetSchema(std::unique_ptr value) noexcept { m_schema = std::move(value); } + private: + int32_t m_length; + tla::string m_comment; + tla::string m_sdl; + std::unique_ptr m_schema; + }; + + class Record final + { + public: + [[nodiscard]] Record() noexcept : + m_length{}, + m_crc32{} {} + [[nodiscard]] Record(int32_t length, uint32_t crc32) noexcept : + m_length{length}, + m_crc32{crc32} {} + + [[nodiscard]] int32_t GetLength() const noexcept { return m_length; } + void SetLength(int32_t value) noexcept { m_length = value; } + [[nodiscard]] uint32_t GetCrc32() const noexcept { return m_crc32; } + void SetCrc32(uint32_t value) noexcept { m_crc32 = value; } + private: + int32_t m_length{}; + uint32_t m_crc32{}; + }; +} diff --git a/src/Serialization/HybridRow.Native/SetPropertyType.h b/src/Serialization/HybridRow.Native/SetPropertyType.h new file mode 100644 index 0000000..471481d --- /dev/null +++ b/src/Serialization/HybridRow.Native/SetPropertyType.h @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include "ScopePropertyType.h" + +namespace cdb_hr +{ + /// Set properties represent an unbounded set of zero or more unique items. + /// + /// Sets may be typed or untyped. Within typed sets, all items MUST be the same type. The + /// type of items is specified via . Typed sets may be stored more efficiently than + /// untyped sets. When is unspecified, the set is untyped and its items may be + /// heterogeneous. Each item within a set must be unique. Uniqueness is defined by the HybridRow + /// encoded sequence of bytes for the item. + /// + class SetPropertyType final : public ScopePropertyType + { + public: + SetPropertyType() noexcept : ScopePropertyType{TypeKind::Set}, m_items{} {} + + SetPropertyType(std::unique_ptr items, bool nullable = true, bool immutable = false) noexcept : + ScopePropertyType{TypeKind::Set, nullable, immutable}, + m_items{std::move(items)} {} + + ~SetPropertyType() noexcept override = default; + SetPropertyType(SetPropertyType&) = delete; + SetPropertyType(SetPropertyType&&) = delete; + SetPropertyType& operator=(const SetPropertyType&) = delete; + SetPropertyType& operator=(SetPropertyType&&) = delete; + + [[nodiscard]] SchemaId GetRuntimeSchemaId() const noexcept override { return SchemaId{2147473664}; } + [[nodiscard]] PropertyKind GetKind() const noexcept override { return PropertyKind::Set; } + + /// (Optional) type of the elements of the set, if a typed set, otherwise null. + [[nodiscard]] std::optional> GetItems() const noexcept + { + return m_items ? std::optional>{*m_items} : std::nullopt; + } + + void SetItems(std::unique_ptr value) noexcept { m_items = std::move(value); } + + private: + std::unique_ptr m_items; + }; +} diff --git a/src/Serialization/HybridRow.Native/SortDirection.h b/src/Serialization/HybridRow.Native/SortDirection.h new file mode 100644 index 0000000..219cadb --- /dev/null +++ b/src/Serialization/HybridRow.Native/SortDirection.h @@ -0,0 +1,18 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// Describes the sort order direction. + enum class SortDirection : unsigned char + { + /// Sorts from the lowest to the highest value. + Ascending = 0, + + /// Sorts from the highest to the lowest value. + Descending, + }; +} diff --git a/src/Serialization/HybridRow.Native/StaticKey.h b/src/Serialization/HybridRow.Native/StaticKey.h new file mode 100644 index 0000000..e7004ab --- /dev/null +++ b/src/Serialization/HybridRow.Native/StaticKey.h @@ -0,0 +1,32 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include + +namespace cdb_hr +{ + /// + /// Describes a property or set of properties whose values MUST be the same for all rows that share the same partition key. + /// + class StaticKey final + { + public: + StaticKey() noexcept : m_path{} {} + StaticKey(std::string_view path) noexcept : m_path{path} {} + ~StaticKey() noexcept = default; + StaticKey(const StaticKey& other) = default; + StaticKey(StaticKey&& other) noexcept = default; + StaticKey& operator=(const StaticKey& other) = default; + StaticKey& operator=(StaticKey&& other) noexcept = default; + + /// The logical path of the referenced property. + /// Static path MUST refer to properties defined within the same schema. + [[nodiscard]] std::string_view GetPath() const noexcept { return m_path; } + void SetPath(std::string_view value) noexcept { m_path = value; } + + private: + tla::string m_path; + }; +} diff --git a/src/Serialization/HybridRow.Native/StorageKind.h b/src/Serialization/HybridRow.Native/StorageKind.h new file mode 100644 index 0000000..df16d89 --- /dev/null +++ b/src/Serialization/HybridRow.Native/StorageKind.h @@ -0,0 +1,41 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + /// Describes the storage placement for primitive properties. + enum class StorageKind : unsigned char + { + /// The property defines a sparse column. + /// + /// Columns marked consume no space in the row when not present. When + /// present they appear in an unordered linked list at the end of the row. Access time for + /// columns is proportional to the number of columns in the + /// row. + /// + Sparse = 0, + + /// The property is a fixed-length, space-reserved column. + /// + /// The column will consume 1 null-bit, and its byte-width regardless of whether the value is + /// present in the row. + /// + Fixed, + + /// The property is a variable-length column. + /// + /// The column will consume 1 null-bit regardless of whether the value is present. When the value is + /// present it will also consume a variable number of bytes to encode the length preceding the actual + /// value. + /// + /// When a long value is marked then a null-bit is reserved and + /// the value is optionally encoded as if small enough to fit, otherwise the + /// null-bit is set and the value is encoded as . + /// + /// + Variable, + }; +} diff --git a/src/Serialization/HybridRow.Native/StringTokenizer.cpp b/src/Serialization/HybridRow.Native/StringTokenizer.cpp new file mode 100644 index 0000000..ef5f540 --- /dev/null +++ b/src/Serialization/HybridRow.Native/StringTokenizer.cpp @@ -0,0 +1,108 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "StringTokenizer.h" + +namespace cdb_hr +{ + using namespace std::literals; + + constexpr StringTokenizer::StringToken Empty = {}; + + StringTokenizer::StringTokenizer() noexcept : + m_tokens(), + m_strings(), + m_count(1) + { + try + { + m_tokens.emplace(""sv, StringToken(0, ""sv)); + m_strings.emplace_back(""sv); + } + catch (std::bad_alloc&) + { + cdb_core::Contract::Fail("Allocation failure"); + } + } + + size_t StringTokenizer::GetCount() const noexcept { return m_count; } + + std::tuple StringTokenizer::TryFindToken( + std::string_view path) const noexcept + { + const auto iter = m_tokens.find(path); + if (iter == m_tokens.end()) + { + return {false, Empty}; + } + return {true, iter->second}; + } + + std::tuple StringTokenizer::TryFindString(uint64_t token) const noexcept + { + if (token >= static_cast(m_strings.size())) + { + return {false, ""sv}; + } + + const auto& path = m_strings[static_cast(token)]; + return {true, path}; + } + + const StringTokenizer::StringToken& StringTokenizer::Add(std::string_view path) noexcept + { + const auto iter = m_tokens.find(path); + if (iter == m_tokens.end()) + { + return AllocateToken(path); + } + return iter->second; + } + + /// Allocates a new token and assigns the string to it. + /// The string that needs a new token. + /// The new allocated token. + const StringTokenizer::StringToken& StringTokenizer::AllocateToken(std::string_view path) noexcept + { + try + { + uint64_t id = static_cast(m_count++); + StringToken token = StringToken(id, path); + const auto [iter, success] = m_tokens.emplace(path, token); + cdb_core::Contract::Invariant(success); + m_strings.emplace_back(path); + cdb_core::Contract::Assert(static_cast(m_strings.size()) - 1 == id); + return iter->second; + } + catch (std::bad_alloc&) + { + cdb_core::Contract::Fail("Allocation failure"); + } + } + + constexpr StringTokenizer::StringToken::StringToken(uint64_t id, std::string_view path) noexcept : + m_id{id}, + m_varintLength{0}, + m_varint{}, + m_path{path} + { + // Write out an unsigned long 7 bits at a time. The high bit of the byte, + // when set, indicates there are more bytes. + while (id >= 0x80) + { + m_varint[m_varintLength] = static_cast(id | 0x80); + m_varintLength++; + id >>= 7; + } + + m_varint[m_varintLength] = static_cast(id); + m_varintLength++; + } + + bool StringTokenizer::StringToken::IsNull() const noexcept + { + return m_varintLength == 0; + } +} diff --git a/src/Serialization/HybridRow.Native/StringTokenizer.h b/src/Serialization/HybridRow.Native/StringTokenizer.h new file mode 100644 index 0000000..4dc85c6 --- /dev/null +++ b/src/Serialization/HybridRow.Native/StringTokenizer.h @@ -0,0 +1,92 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include + +namespace cdb_hr_test +{ + class StringTokenizerUnitTests; +} + +namespace cdb_hr +{ + struct StringTokenizer final + { + struct StringToken; + + /// Initializes a new instance of the class. + StringTokenizer() noexcept; + ~StringTokenizer() noexcept = default; + StringTokenizer(const StringTokenizer& other) = delete; + StringTokenizer(StringTokenizer&& other) noexcept = default; + StringTokenizer& operator=(const StringTokenizer& other) = delete; + StringTokenizer& operator=(StringTokenizer&& other) noexcept = default; + + /// The number of unique tokens described by the encoding. + [[nodiscard]] size_t GetCount() const noexcept; + + /// Looks up a string's corresponding token. + /// The string to look up. + /// {True, the string's assigned token} if successful, {false, undefined} otherwise. + [[nodiscard]] std::tuple TryFindToken(std::string_view path) const noexcept; + + /// Looks up a token's corresponding string. + /// The token to look up. + /// {True, the token's assigned string} if successful, {false, undefined} otherwise. + [[nodiscard]] std::tuple TryFindString(uint64_t token) const noexcept; + + struct StringToken final + { + constexpr StringToken() noexcept; + ~StringToken() noexcept = default; + StringToken(const StringToken& other) noexcept = default; + StringToken(StringToken&& other) noexcept = default; + StringToken& operator=(const StringToken& other) noexcept = default; + StringToken& operator=(StringToken&& other) noexcept = default; + + [[nodiscard]] bool IsNull() const noexcept; + + [[nodiscard]] uint64_t GetId() const noexcept { return m_id; } + + [[nodiscard]] cdb_core::ReadOnlySpan GetVarint() const noexcept + { + return {m_varint.data(), m_varintLength}; + } + + [[nodiscard]] std::string_view GetPath() const noexcept { return m_path; } + + private: + friend struct StringTokenizer; + constexpr StringToken(uint64_t id, std::string_view path) noexcept; + + uint64_t m_id; + uint32_t m_varintLength; + std::array m_varint; + std::string_view m_path; + }; + + private: + friend class Layout; + friend class cdb_hr_test::StringTokenizerUnitTests; + + /// Assign a token to the string. + /// If the string already has a token, that token is returned instead. + /// The string to assign a new token. + /// The token assigned to the string. + const StringToken& Add(std::string_view path) noexcept; + + /// Allocates a new token and assigns the string to it. + /// The string that needs a new token. + /// The new allocated token. + const StringToken& AllocateToken(std::string_view path) noexcept; + + std::unordered_map m_tokens; + std::vector m_strings; + uint64_t m_count; + }; + + constexpr StringTokenizer::StringToken::StringToken() noexcept : m_id{0}, m_varintLength{0}, m_varint{} {} +} diff --git a/src/Serialization/HybridRow.Native/SystemSchema.cpp b/src/Serialization/HybridRow.Native/SystemSchema.cpp new file mode 100644 index 0000000..dc2c43e --- /dev/null +++ b/src/Serialization/HybridRow.Native/SystemSchema.cpp @@ -0,0 +1,4611 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "HybridRow.Native.h" +#include "SystemSchema.h" + +// ------------------------------------------------------------ +// This file was generated by: +// Microsoft.Azure.Cosmos.Serialization.HybridRowCLI: 1.0.0.0 +// +// This file should not be modified directly. +// ------------------------------------------------------------ + +// ReSharper disable CppClangTidyCppcoreguidelinesProTypeStaticCastDowncast +// ReSharper disable CppClangTidyPerformanceMoveConstArg +// ReSharper disable CppRedundantControlFlowJump +// ReSharper disable CppClangTidyClangDiagnosticExitTimeDestructors +namespace cdb_hr +{ + using namespace std::literals; + + class SchemasHrSchema::Literal final + { + friend struct SchemasHrSchema; + + + static const cdb_hr::Namespace& GetNamespace() noexcept + { + return *s_namespace; + } + + static std::unique_ptr LoadSchema() + { + auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n) + { + n.SetName("Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas"); + n.SetVersion(cdb_hr::SchemaLanguageVersion::V2); + n.SetCppNamespace("cdb_hr"); + ////////////////////////////////////////////////////////////////////////////// + n.GetEnums().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumSchema& es) + { + es.SetName("SchemaLanguageVersion"); + es.SetComment("Versions of the HybridRow Schema Description Language."); + es.SetType(cdb_hr::TypeKind::UInt8); + es.GetValues().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumValue& ev) + { + ev.SetName("V1"); + ev.SetComment("Initial version of the HybridRow Schema Description Language."); + ev.SetValue(0); + })); + es.GetValues().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumValue& ev) + { + ev.SetName("V2"); + ev.SetComment("Introduced Enums, Inheritance."); + ev.SetValue(2); + })); + es.GetValues().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumValue& ev) + { + ev.SetName("Unspecified"); + ev.SetComment("No version is specified."); + ev.SetValue(255); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetEnums().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumSchema& es) + { + es.SetName("TypeKind"); + es.SetComment("Describes the logical type of a property."); + es.SetType(cdb_hr::TypeKind::UInt8); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetEnums().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumSchema& es) + { + es.SetName("StorageKind"); + es.SetComment("Describes the storage placement for primitive properties."); + es.SetType(cdb_hr::TypeKind::UInt8); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetEnums().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumSchema& es) + { + es.SetName("SortDirection"); + es.SetComment("Describes the sort order direction."); + es.SetType(cdb_hr::TypeKind::UInt8); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetEnums().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumSchema& es) + { + es.SetName("AllowEmptyKind"); + es.SetComment("Describes the empty canonicalization for properties."); + es.SetType(cdb_hr::TypeKind::UInt8); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("EmptySchema"); + s.SetSchemaId(cdb_hr::SchemaId{2147473650}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("Segment"); + s.SetSchemaId(cdb_hr::SchemaId{2147473648}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("length"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int32); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetRowBufferSize(true); + })); + p.SetComment("(Required) length (in bytes) of this RecordIO segment header itself. Does NOT include the length of the records that follow."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("comment"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("A comment describing the data in this RecordIO segment."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("sdl"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("A HybridRow Schema in SDL (json-format)."); + p.SetApiName("SDL"); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("schema"); + p.SetPropertyType( + std::make_unique("Namespace", cdb_hr::SchemaId{2147473651})); + p.SetComment("A HybridRow Schema."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("Record"); + s.SetSchemaId(cdb_hr::SchemaId{2147473649}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("length"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int32); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetNullable(false); + })); + p.SetComment("(Required) length (in bytes) of the HybridRow value that follows this record header."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("crc32"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::UInt32); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetNullable(false); + })); + p.SetComment("(Optional) CRC-32 as described in ISO 3309."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("Namespace"); + s.SetSchemaId(cdb_hr::SchemaId{2147473651}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("version"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Enum); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetEnum("SchemaLanguageVersion"); + pt.SetNullable(false); + })); + p.SetComment("(Required) SDL language version."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("name"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + p.SetComment("(Optional) Name of the namespace."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("comment"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("(Optional) Comment field describing the namespace."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("schemas"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("Schema", cdb_hr::SchemaId{2147473652}, false))); + p.SetComment("The set of schemas that make up the namespace."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("enums"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("EnumSchema", cdb_hr::SchemaId{2147473668}, false))); + p.SetComment("The set of enums defined in the namespace."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("cppNamespace"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("An (optional) namespace to use when performing C++ codegen."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("Schema"); + s.SetSchemaId(cdb_hr::SchemaId{2147473652}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("version"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Enum); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetEnum("SchemaLanguageVersion"); + pt.SetNullable(false); + })); + p.SetComment("(Optional) SDL language version."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("type"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Enum); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetEnum("TypeKind"); + pt.SetNullable(false); + })); + p.SetComment("(Required) Type of the schema element."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("id"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int32); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetApiType("SchemaId"); + pt.SetNullable(false); + })); + p.SetComment("(Required) Globally unique id of the schema."); + p.SetApiName("SchemaId"); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("name"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + p.SetComment("(Optional) Name of the schema."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("comment"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("(Optional) Comment field describing the schema."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("options"); + p.SetPropertyType( + std::make_unique("SchemaOptions", cdb_hr::SchemaId{2147473653})); + p.SetComment("(Optional) Schema options."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("partitionKeys"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("PartitionKey", cdb_hr::SchemaId{2147473654}, false))); + p.SetComment("(Optional) List of zero or more logical paths that form the partition key."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("primaryKeys"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("PrimarySortKey", cdb_hr::SchemaId{2147473655}, false))); + p.SetComment("(Optional) List of zero or more logical paths that form the primary sort key."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("staticKeys"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("StaticKey", cdb_hr::SchemaId{2147473656}, false))); + p.SetComment("(Optional) List of zero or more logical paths that hold data shared by all documents that have the same partition key."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("properties"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("Property", cdb_hr::SchemaId{2147473657}, false))); + p.SetComment("(Optional) List of zero or more property definitions that define the columns within the schema."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("baseName"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("The name of the schema this schema derives from."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("baseId"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int32); + pt.SetApiType("SchemaId"); + })); + p.SetComment("The unique identifier of the schema this schema derives from."); + p.SetApiName("BaseSchemaId"); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("SchemaOptions"); + s.SetSchemaId(cdb_hr::SchemaId{2147473653}); + s.SetComment("Describes the set of options that apply to the entire schema and the way it is validated."); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("disallowUnschematized"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("enablePropertyLevelTimestamp"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("disableSystemPrefix"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("abstract"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + })); + p.SetComment("If true then instances of this schema cannot be created directly, only through subtypes."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("PartitionKey"); + s.SetSchemaId(cdb_hr::SchemaId{2147473654}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("path"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("PrimarySortKey"); + s.SetSchemaId(cdb_hr::SchemaId{2147473655}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("path"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("direction"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Enum); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetEnum("SortDirection"); + pt.SetNullable(false); + })); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("StaticKey"); + s.SetSchemaId(cdb_hr::SchemaId{2147473656}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("path"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("Property"); + s.SetSchemaId(cdb_hr::SchemaId{2147473657}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("path"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("comment"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("type"); + p.SetPropertyType( + std::make_unique("PropertyType", cdb_hr::SchemaId{2147473658})); + p.SetComment("The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType."); + p.SetApiName("PropertyType"); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("apiname"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetApiName("ApiName"); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("allowEmpty"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Enum); + pt.SetEnum("AllowEmptyKind"); + })); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("PropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473658}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + so.SetAbstract(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("apitype"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + p.SetApiName("ApiType"); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("type"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Enum); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetEnum("TypeKind"); + pt.SetNullable(false); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("nullable"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetNullable(false); + })); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("PrimitivePropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473659}); + s.SetBaseName("PropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473658}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("length"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int32); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetNullable(false); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("storage"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Enum); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetEnum("StorageKind"); + pt.SetNullable(false); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("enum"); + p.SetAllowEmpty(AllowEmptyKind::EmptyAsNull); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("rowBufferSize"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + })); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("ScopePropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473660}); + s.SetBaseName("PropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473658}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + so.SetAbstract(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("immutable"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Boolean); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetNullable(false); + })); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("ArrayPropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473661}); + s.SetBaseName("ScopePropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473660}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("items"); + p.SetPropertyType( + std::make_unique("PropertyType", cdb_hr::SchemaId{2147473658})); + p.SetComment("The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("ObjectPropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473662}); + s.SetBaseName("ScopePropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473660}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("properties"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("Property", cdb_hr::SchemaId{2147473657}, false))); + p.SetComment("(Optional) List of zero or more property definitions that define the columns within the schema."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("UdtPropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473663}); + s.SetBaseName("ScopePropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473660}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("name"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("id"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int32); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetApiType("SchemaId"); + pt.SetNullable(false); + })); + p.SetApiName("SchemaId"); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("SetPropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473664}); + s.SetBaseName("ScopePropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473660}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("items"); + p.SetPropertyType( + std::make_unique("PropertyType", cdb_hr::SchemaId{2147473658})); + p.SetComment("The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("MapPropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473665}); + s.SetBaseName("ScopePropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473660}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("keys"); + p.SetPropertyType( + std::make_unique("PropertyType", cdb_hr::SchemaId{2147473658})); + p.SetComment("The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("values"); + p.SetPropertyType( + std::make_unique("PropertyType", cdb_hr::SchemaId{2147473658})); + p.SetComment("The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("TuplePropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473666}); + s.SetBaseName("ScopePropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473660}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("items"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("PropertyType", cdb_hr::SchemaId{2147473658}, false))); + p.SetComment("The type of the properties. This field is polymorphic and may contain any defined subtype of PropertyType."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("TaggedPropertyType"); + s.SetSchemaId(cdb_hr::SchemaId{2147473667}); + s.SetBaseName("ScopePropertyType"); + s.SetBaseSchemaId(cdb_hr::SchemaId{2147473660}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("items"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("PropertyType", cdb_hr::SchemaId{2147473658}, false))); + p.SetComment("The type of the properties. This field is polymorphic and may contain any defined subtype of PropertyType."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("EnumSchema"); + s.SetSchemaId(cdb_hr::SchemaId{2147473668}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("type"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Enum); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + pt.SetEnum("TypeKind"); + pt.SetNullable(false); + })); + p.SetComment("(Required) Type of the schema element."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("name"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + p.SetComment("(Optional) Name of the schema."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("comment"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("(Optional) Comment field describing the schema."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("apitype"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("Api-specific type annotations for the property."); + p.SetApiName("ApiType"); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("values"); + p.SetAllowEmpty(AllowEmptyKind::Both); + p.SetPropertyType( + std::make_unique( + std::make_unique("EnumValue", cdb_hr::SchemaId{2147473669}, false))); + p.SetComment("(Optional) List of zero or more values."); + })); + })); + ////////////////////////////////////////////////////////////////////////////// + n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s) + { + s.SetName("EnumValue"); + s.SetSchemaId(cdb_hr::SchemaId{2147473669}); + s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so) + { + so.SetDisallowUnschematized(true); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("name"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + pt.SetStorage(cdb_hr::StorageKind::Variable); + })); + p.SetComment("(Optional) Name of the schema."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("value"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Int64); + pt.SetStorage(cdb_hr::StorageKind::Fixed); + })); + p.SetComment("The numerical value of the enum value."); + })); + s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p) + { + p.SetPath("comment"); + p.SetPropertyType(cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt) + { + pt.SetType(cdb_hr::TypeKind::Utf8); + })); + p.SetComment("(Optional) Comment field describing the schema."); + })); + })); + }); + + s_namespace = ns.get(); + return std::make_unique(std::move(ns)); + } + + inline static cdb_hr::Namespace* s_namespace{nullptr}; + inline static std::unique_ptr s_layoutResolver{LoadSchema()}; + }; + + const cdb_hr::Namespace& SchemasHrSchema::GetNamespace() noexcept + { + return *Literal::s_namespace; + } + + const cdb_hr::LayoutResolver& SchemasHrSchema::GetLayoutResolver() noexcept + { + return *Literal::s_layoutResolver; + } + + class SegmentHybridRowSerializer::Literal final + { + friend struct SegmentHybridRowSerializer; + + constexpr static std::string_view LengthName{"length"sv}; + constexpr static std::string_view CommentName{"comment"sv}; + constexpr static std::string_view SDLName{"sdl"sv}; + constexpr static std::string_view SchemaName{"schema"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& LengthColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, LengthName)}; + inline static const cdb_hr::LayoutColumn& CommentColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, CommentName)}; + inline static const cdb_hr::LayoutColumn& SDLColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, SDLName)}; + inline static const cdb_hr::LayoutColumn& SchemaColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, SchemaName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& CommentToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, CommentColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& SDLToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, SDLColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& SchemaToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, SchemaColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Segment& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Segment& value); + }; + + cdb_hr::Result SegmentHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const Segment& value) noexcept + { + if (isRoot) + { + return SegmentHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = SegmentHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result SegmentHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Segment& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetComment())) + { + scope.Find(row, CommentColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetComment())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetSDL())) + { + scope.Find(row, SDLColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetSDL())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetSchema())) + { + scope.Find(row, SchemaColumn.GetPath()); + cdb_hr::Result r = NamespaceHybridRowSerializer::Write( + row, scope, false, SchemaColumn.GetTypeArgs(), cdb_hr::IHybridRowSerializer::get(value.GetSchema())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + // Emit RowBufferSize field with actual size of RowBuffer. + cdb_hr::Result r = cdb_hr::LayoutLiteral::Int32.WriteFixed( + row, scope, LengthColumn, static_cast(row.GetLength())); + if (r != cdb_hr::Result::Success) + { + return r; + } + + return cdb_hr::Result::Success; + } + + std::tuple> SegmentHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = SegmentHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = SegmentHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result SegmentHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Segment& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Int32.ReadFixed(row, scope, LengthColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetLength(fieldValue); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == CommentToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetComment(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == SDLToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetSDL(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == SchemaToken.GetId()) + { + auto [r, fieldValue] = NamespaceHybridRowSerializer::Read(row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetSchema(std::move(fieldValue)); + continue; + } + } + + return cdb_hr::Result::Success; + } + + class RecordHybridRowSerializer::Literal final + { + friend struct RecordHybridRowSerializer; + + constexpr static std::string_view LengthName{"length"sv}; + constexpr static std::string_view Crc32Name{"crc32"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& LengthColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, LengthName)}; + inline static const cdb_hr::LayoutColumn& Crc32Column{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, Crc32Name)}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Record& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Record& value); + }; + + cdb_hr::Result RecordHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const Record& value) noexcept + { + if (isRoot) + { + return RecordHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = RecordHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result RecordHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Record& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetLength())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Int32.WriteFixed( + row, scope, LengthColumn, cdb_hr::IHybridRowSerializer::get(value.GetLength())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetCrc32())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt32.WriteFixed( + row, scope, Crc32Column, cdb_hr::IHybridRowSerializer::get(value.GetCrc32())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> RecordHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = RecordHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = RecordHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result RecordHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Record& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Int32.ReadFixed(row, scope, LengthColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetLength(fieldValue); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt32.ReadFixed(row, scope, Crc32Column); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetCrc32(fieldValue); + break; + default: + return r; + } + } + + return cdb_hr::Result::Success; + } + + class NamespaceHybridRowSerializer::Literal final + { + friend struct NamespaceHybridRowSerializer; + + constexpr static std::string_view VersionName{"version"sv}; + constexpr static std::string_view NameName{"name"sv}; + constexpr static std::string_view CommentName{"comment"sv}; + constexpr static std::string_view SchemasName{"schemas"sv}; + constexpr static std::string_view EnumsName{"enums"sv}; + constexpr static std::string_view CppNamespaceName{"cppNamespace"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& VersionColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, VersionName)}; + inline static const cdb_hr::LayoutColumn& NameColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, NameName)}; + inline static const cdb_hr::LayoutColumn& CommentColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, CommentName)}; + inline static const cdb_hr::LayoutColumn& SchemasColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, SchemasName)}; + inline static const cdb_hr::LayoutColumn& EnumsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, EnumsName)}; + inline static const cdb_hr::LayoutColumn& CppNamespaceColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, CppNamespaceName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& CommentToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, CommentColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& SchemasToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, SchemasColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& EnumsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, EnumsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& CppNamespaceToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, CppNamespaceColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Namespace& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Namespace& value); + }; + + cdb_hr::Result NamespaceHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const Namespace& value) noexcept + { + if (isRoot) + { + return NamespaceHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = NamespaceHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result NamespaceHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Namespace& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetVersion())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt8.WriteFixed( + row, scope, VersionColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetVersion()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetName())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, NameColumn, cdb_hr::IHybridRowSerializer::get(value.GetName())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetComment())) + { + scope.Find(row, CommentColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetComment())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetSchemas())) + { + scope.Find(row, SchemasColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + SchemasColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetSchemas())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetEnums())) + { + scope.Find(row, EnumsColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + EnumsColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetEnums())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetCppNamespace())) + { + scope.Find(row, CppNamespaceColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetCppNamespace())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> NamespaceHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = NamespaceHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = NamespaceHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result NamespaceHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Namespace& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt8.ReadFixed(row, scope, VersionColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetVersion(static_cast(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, NameColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetName(std::string(fieldValue)); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == CommentToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetComment(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == SchemasToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetSchemas(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == EnumsToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetEnums(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == CppNamespaceToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetCppNamespace(std::move(fieldValue)); + continue; + } + } + + return cdb_hr::Result::Success; + } + + class SchemaHybridRowSerializer::Literal final + { + friend struct SchemaHybridRowSerializer; + + constexpr static std::string_view VersionName{"version"sv}; + constexpr static std::string_view TypeName{"type"sv}; + constexpr static std::string_view SchemaIdName{"id"sv}; + constexpr static std::string_view NameName{"name"sv}; + constexpr static std::string_view CommentName{"comment"sv}; + constexpr static std::string_view OptionsName{"options"sv}; + constexpr static std::string_view PartitionKeysName{"partitionKeys"sv}; + constexpr static std::string_view PrimaryKeysName{"primaryKeys"sv}; + constexpr static std::string_view StaticKeysName{"staticKeys"sv}; + constexpr static std::string_view PropertiesName{"properties"sv}; + constexpr static std::string_view BaseNameName{"baseName"sv}; + constexpr static std::string_view BaseSchemaIdName{"baseId"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& VersionColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, VersionName)}; + inline static const cdb_hr::LayoutColumn& TypeColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, TypeName)}; + inline static const cdb_hr::LayoutColumn& SchemaIdColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, SchemaIdName)}; + inline static const cdb_hr::LayoutColumn& NameColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, NameName)}; + inline static const cdb_hr::LayoutColumn& CommentColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, CommentName)}; + inline static const cdb_hr::LayoutColumn& OptionsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, OptionsName)}; + inline static const cdb_hr::LayoutColumn& PartitionKeysColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PartitionKeysName)}; + inline static const cdb_hr::LayoutColumn& PrimaryKeysColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PrimaryKeysName)}; + inline static const cdb_hr::LayoutColumn& StaticKeysColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, StaticKeysName)}; + inline static const cdb_hr::LayoutColumn& PropertiesColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PropertiesName)}; + inline static const cdb_hr::LayoutColumn& BaseNameColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, BaseNameName)}; + inline static const cdb_hr::LayoutColumn& BaseSchemaIdColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, BaseSchemaIdName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& CommentToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, CommentColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& OptionsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, OptionsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& PartitionKeysToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, PartitionKeysColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& PrimaryKeysToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, PrimaryKeysColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& StaticKeysToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, StaticKeysColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& PropertiesToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, PropertiesColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& BaseNameToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, BaseNameColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& BaseSchemaIdToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, BaseSchemaIdColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Schema& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Schema& value); + }; + + cdb_hr::Result SchemaHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const Schema& value) noexcept + { + if (isRoot) + { + return SchemaHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = SchemaHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result SchemaHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Schema& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetVersion())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt8.WriteFixed( + row, scope, VersionColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetVersion()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetType())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt8.WriteFixed( + row, scope, TypeColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetType()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetSchemaId())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Int32.WriteFixed( + row, scope, SchemaIdColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetSchemaId()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetName())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, NameColumn, cdb_hr::IHybridRowSerializer::get(value.GetName())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetComment())) + { + scope.Find(row, CommentColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetComment())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetOptions())) + { + scope.Find(row, OptionsColumn.GetPath()); + cdb_hr::Result r = SchemaOptionsHybridRowSerializer::Write( + row, scope, false, OptionsColumn.GetTypeArgs(), cdb_hr::IHybridRowSerializer::get(value.GetOptions())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetPartitionKeys())) + { + scope.Find(row, PartitionKeysColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + PartitionKeysColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetPartitionKeys())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetPrimaryKeys())) + { + scope.Find(row, PrimaryKeysColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + PrimaryKeysColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetPrimaryKeys())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetStaticKeys())) + { + scope.Find(row, StaticKeysColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + StaticKeysColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetStaticKeys())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetProperties())) + { + scope.Find(row, PropertiesColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + PropertiesColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetProperties())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetBaseName())) + { + scope.Find(row, BaseNameColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetBaseName())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetBaseSchemaId())) + { + scope.Find(row, BaseSchemaIdColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Int32.WriteSparse( + row, scope, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetBaseSchemaId()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> SchemaHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = SchemaHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = SchemaHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result SchemaHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Schema& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt8.ReadFixed(row, scope, VersionColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetVersion(static_cast(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt8.ReadFixed(row, scope, TypeColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetType(static_cast(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Int32.ReadFixed(row, scope, SchemaIdColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetSchemaId(static_cast(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, NameColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetName(std::string(fieldValue)); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == CommentToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetComment(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == OptionsToken.GetId()) + { + auto [r, fieldValue] = SchemaOptionsHybridRowSerializer::Read(row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetOptions(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == PartitionKeysToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetPartitionKeys(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == PrimaryKeysToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetPrimaryKeys(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == StaticKeysToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetStaticKeys(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == PropertiesToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetProperties(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == BaseNameToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetBaseName(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == BaseSchemaIdToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Int32.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetBaseSchemaId(static_cast(std::move(fieldValue))); + continue; + } + } + + return cdb_hr::Result::Success; + } + + class SchemaOptionsHybridRowSerializer::Literal final + { + friend struct SchemaOptionsHybridRowSerializer; + + constexpr static std::string_view DisallowUnschematizedName{"disallowUnschematized"sv}; + constexpr static std::string_view EnablePropertyLevelTimestampName{"enablePropertyLevelTimestamp"sv}; + constexpr static std::string_view DisableSystemPrefixName{"disableSystemPrefix"sv}; + constexpr static std::string_view AbstractName{"abstract"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& DisallowUnschematizedColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, DisallowUnschematizedName)}; + inline static const cdb_hr::LayoutColumn& EnablePropertyLevelTimestampColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, EnablePropertyLevelTimestampName)}; + inline static const cdb_hr::LayoutColumn& DisableSystemPrefixColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, DisableSystemPrefixName)}; + inline static const cdb_hr::LayoutColumn& AbstractColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, AbstractName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& DisallowUnschematizedToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, DisallowUnschematizedColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& EnablePropertyLevelTimestampToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, EnablePropertyLevelTimestampColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& DisableSystemPrefixToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, DisableSystemPrefixColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& AbstractToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, AbstractColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const SchemaOptions& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, SchemaOptions& value); + }; + + cdb_hr::Result SchemaOptionsHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const SchemaOptions& value) noexcept + { + if (isRoot) + { + return SchemaOptionsHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = SchemaOptionsHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result SchemaOptionsHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const SchemaOptions& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetDisallowUnschematized())) + { + scope.Find(row, DisallowUnschematizedColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Boolean.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetDisallowUnschematized())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetEnablePropertyLevelTimestamp())) + { + scope.Find(row, EnablePropertyLevelTimestampColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Boolean.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetEnablePropertyLevelTimestamp())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetDisableSystemPrefix())) + { + scope.Find(row, DisableSystemPrefixColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Boolean.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetDisableSystemPrefix())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetAbstract())) + { + scope.Find(row, AbstractColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Boolean.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetAbstract())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> SchemaOptionsHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = SchemaOptionsHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = SchemaOptionsHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result SchemaOptionsHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, SchemaOptions& value) + { + while (scope.MoveNext(row)) + { + if (scope.GetToken() == DisallowUnschematizedToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Boolean.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetDisallowUnschematized(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == EnablePropertyLevelTimestampToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Boolean.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetEnablePropertyLevelTimestamp(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == DisableSystemPrefixToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Boolean.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetDisableSystemPrefix(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == AbstractToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Boolean.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetAbstract(std::move(fieldValue)); + continue; + } + } + + return cdb_hr::Result::Success; + } + + class PartitionKeyHybridRowSerializer::Literal final + { + friend struct PartitionKeyHybridRowSerializer; + + constexpr static std::string_view PathName{"path"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& PathColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PathName)}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PartitionKey& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PartitionKey& value); + }; + + cdb_hr::Result PartitionKeyHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const PartitionKey& value) noexcept + { + if (isRoot) + { + return PartitionKeyHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = PartitionKeyHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result PartitionKeyHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PartitionKey& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetPath())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, PathColumn, cdb_hr::IHybridRowSerializer::get(value.GetPath())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> PartitionKeyHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = PartitionKeyHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = PartitionKeyHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result PartitionKeyHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PartitionKey& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, PathColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetPath(std::string(fieldValue)); + break; + default: + return r; + } + } + + return cdb_hr::Result::Success; + } + + class PrimarySortKeyHybridRowSerializer::Literal final + { + friend struct PrimarySortKeyHybridRowSerializer; + + constexpr static std::string_view PathName{"path"sv}; + constexpr static std::string_view DirectionName{"direction"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& PathColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PathName)}; + inline static const cdb_hr::LayoutColumn& DirectionColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, DirectionName)}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PrimarySortKey& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PrimarySortKey& value); + }; + + cdb_hr::Result PrimarySortKeyHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const PrimarySortKey& value) noexcept + { + if (isRoot) + { + return PrimarySortKeyHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = PrimarySortKeyHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result PrimarySortKeyHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PrimarySortKey& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetDirection())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt8.WriteFixed( + row, scope, DirectionColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetDirection()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetPath())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, PathColumn, cdb_hr::IHybridRowSerializer::get(value.GetPath())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> PrimarySortKeyHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = PrimarySortKeyHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = PrimarySortKeyHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result PrimarySortKeyHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PrimarySortKey& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt8.ReadFixed(row, scope, DirectionColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetDirection(static_cast(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, PathColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetPath(std::string(fieldValue)); + break; + default: + return r; + } + } + + return cdb_hr::Result::Success; + } + + class StaticKeyHybridRowSerializer::Literal final + { + friend struct StaticKeyHybridRowSerializer; + + constexpr static std::string_view PathName{"path"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& PathColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PathName)}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const StaticKey& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, StaticKey& value); + }; + + cdb_hr::Result StaticKeyHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const StaticKey& value) noexcept + { + if (isRoot) + { + return StaticKeyHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = StaticKeyHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result StaticKeyHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const StaticKey& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetPath())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, PathColumn, cdb_hr::IHybridRowSerializer::get(value.GetPath())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> StaticKeyHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = StaticKeyHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = StaticKeyHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result StaticKeyHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, StaticKey& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, PathColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetPath(std::string(fieldValue)); + break; + default: + return r; + } + } + + return cdb_hr::Result::Success; + } + + class PropertyHybridRowSerializer::Literal final + { + friend struct PropertyHybridRowSerializer; + + constexpr static std::string_view PathName{"path"sv}; + constexpr static std::string_view CommentName{"comment"sv}; + constexpr static std::string_view PropertyTypeName{"type"sv}; + constexpr static std::string_view ApiNameName{"apiname"sv}; + constexpr static std::string_view AllowEmptyName{"allowEmpty"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& PathColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PathName)}; + inline static const cdb_hr::LayoutColumn& CommentColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, CommentName)}; + inline static const cdb_hr::LayoutColumn& PropertyTypeColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PropertyTypeName)}; + inline static const cdb_hr::LayoutColumn& ApiNameColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ApiNameName)}; + inline static const cdb_hr::LayoutColumn& AllowEmptyColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, AllowEmptyName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& CommentToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, CommentColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& PropertyTypeToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, PropertyTypeColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& ApiNameToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, ApiNameColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& AllowEmptyToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, AllowEmptyColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Property& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Property& value); + }; + + cdb_hr::Result PropertyHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const Property& value) noexcept + { + if (isRoot) + { + return PropertyHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = PropertyHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result PropertyHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const Property& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetPath())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, PathColumn, cdb_hr::IHybridRowSerializer::get(value.GetPath())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetComment())) + { + scope.Find(row, CommentColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetComment())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetPropertyType())) + { + scope.Find(row, PropertyTypeColumn.GetPath()); + cdb_hr::Result r = PropertyTypeHybridRowSerializer::Write( + row, scope, false, PropertyTypeColumn.GetTypeArgs(), cdb_hr::IHybridRowSerializer::get(value.GetPropertyType())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetApiName())) + { + scope.Find(row, ApiNameColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetApiName())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetAllowEmpty())) + { + scope.Find(row, AllowEmptyColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt8.WriteSparse( + row, scope, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetAllowEmpty()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> PropertyHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = PropertyHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = PropertyHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result PropertyHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, Property& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, PathColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetPath(std::string(fieldValue)); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == CommentToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetComment(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == PropertyTypeToken.GetId()) + { + auto [r, fieldValue] = PropertyTypeHybridRowSerializer::Read(row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetPropertyType(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == ApiNameToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetApiName(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == AllowEmptyToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetAllowEmpty(static_cast(std::move(fieldValue))); + continue; + } + } + + return cdb_hr::Result::Success; + } + + class PropertyTypeHybridRowSerializer::Literal final + { + friend struct PropertyTypeHybridRowSerializer; + + constexpr static std::string_view ApiTypeName{"apitype"sv}; + constexpr static std::string_view TypeName{"type"sv}; + constexpr static std::string_view NullableName{"nullable"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& ApiTypeColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ApiTypeName)}; + inline static const cdb_hr::LayoutColumn& TypeColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, TypeName)}; + inline static const cdb_hr::LayoutColumn& NullableColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, NullableName)}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PropertyType& value); + }; + + cdb_hr::Result PropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const PropertyType& value) noexcept + { + switch (value.GetRuntimeSchemaId().Id()) + { + case PrimitivePropertyTypeHybridRowSerializer::Id.Id(): + { + return PrimitivePropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + case ArrayPropertyTypeHybridRowSerializer::Id.Id(): + case ObjectPropertyTypeHybridRowSerializer::Id.Id(): + case UdtPropertyTypeHybridRowSerializer::Id.Id(): + case SetPropertyTypeHybridRowSerializer::Id.Id(): + case MapPropertyTypeHybridRowSerializer::Id.Id(): + case TuplePropertyTypeHybridRowSerializer::Id.Id(): + case TaggedPropertyTypeHybridRowSerializer::Id.Id(): + case ScopePropertyTypeHybridRowSerializer::Id.Id(): + { + return ScopePropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + default: + break; + } + + cdb_core::Contract::Fail("Type is abstract."); + } + + cdb_hr::Result PropertyTypeHybridRowSerializer::WriteBase(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PropertyType& value) noexcept + { + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = PropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result PropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetType())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt8.WriteFixed( + row, scope, TypeColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetType()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetNullable())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Boolean.WriteFixed( + row, scope, NullableColumn, cdb_hr::IHybridRowSerializer::get(value.GetNullable())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetApiType())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, ApiTypeColumn, cdb_hr::IHybridRowSerializer::get(value.GetApiType())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> PropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (!(scope.GetTypeArg().GetType()->IsUDT())) + { + return {cdb_hr::Result::TypeMismatch, std::unique_ptr{}}; + } + + switch (scope.GetTypeArg().GetTypeArgs().GetSchemaId().Id()) + { + case PrimitivePropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = PrimitivePropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + case ArrayPropertyTypeHybridRowSerializer::Id.Id(): + case ObjectPropertyTypeHybridRowSerializer::Id.Id(): + case UdtPropertyTypeHybridRowSerializer::Id.Id(): + case SetPropertyTypeHybridRowSerializer::Id.Id(): + case MapPropertyTypeHybridRowSerializer::Id.Id(): + case TuplePropertyTypeHybridRowSerializer::Id.Id(): + case TaggedPropertyTypeHybridRowSerializer::Id.Id(): + case ScopePropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = ScopePropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + default: + break; + } + + cdb_core::Contract::Fail("Type is abstract."); + } + + cdb_hr::Result PropertyTypeHybridRowSerializer::ReadBase(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PropertyType& value) + { + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = PropertyTypeHybridRowSerializer::Literal::Read(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result PropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PropertyType& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt8.ReadFixed(row, scope, TypeColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetType(static_cast(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Boolean.ReadFixed(row, scope, NullableColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetNullable(fieldValue); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, ApiTypeColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetApiType(std::string(fieldValue)); + break; + default: + return r; + } + } + + return cdb_hr::Result::Success; + } + + class PrimitivePropertyTypeHybridRowSerializer::Literal final + { + friend struct PrimitivePropertyTypeHybridRowSerializer; + + constexpr static std::string_view LengthName{"length"sv}; + constexpr static std::string_view StorageName{"storage"sv}; + constexpr static std::string_view EnumName{"enum"sv}; + constexpr static std::string_view RowBufferSizeName{"rowBufferSize"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& LengthColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, LengthName)}; + inline static const cdb_hr::LayoutColumn& StorageColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, StorageName)}; + inline static const cdb_hr::LayoutColumn& EnumColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, EnumName)}; + inline static const cdb_hr::LayoutColumn& RowBufferSizeColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, RowBufferSizeName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& EnumToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, EnumColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& RowBufferSizeToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, RowBufferSizeColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PrimitivePropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PrimitivePropertyType& value); + }; + + cdb_hr::Result PrimitivePropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const PrimitivePropertyType& value) noexcept + { + if (isRoot) + { + return PrimitivePropertyTypeHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = PrimitivePropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result PrimitivePropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PrimitivePropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetLength())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Int32.WriteFixed( + row, scope, LengthColumn, cdb_hr::IHybridRowSerializer::get(value.GetLength())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetStorage())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt8.WriteFixed( + row, scope, StorageColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetStorage()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetEnum())) + { + scope.Find(row, EnumColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetEnum())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetRowBufferSize())) + { + scope.Find(row, RowBufferSizeColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Boolean.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetRowBufferSize())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = PropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> PrimitivePropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = PrimitivePropertyTypeHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = PrimitivePropertyTypeHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result PrimitivePropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, PrimitivePropertyType& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Int32.ReadFixed(row, scope, LengthColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetLength(fieldValue); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt8.ReadFixed(row, scope, StorageColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetStorage(static_cast(fieldValue)); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == EnumToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetEnum(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == RowBufferSizeToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Boolean.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetRowBufferSize(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = PropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class ScopePropertyTypeHybridRowSerializer::Literal final + { + friend struct ScopePropertyTypeHybridRowSerializer; + + constexpr static std::string_view ImmutableName{"immutable"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& ImmutableColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ImmutableName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const ScopePropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, ScopePropertyType& value); + }; + + cdb_hr::Result ScopePropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const ScopePropertyType& value) noexcept + { + switch (value.GetRuntimeSchemaId().Id()) + { + case ArrayPropertyTypeHybridRowSerializer::Id.Id(): + { + return ArrayPropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + case ObjectPropertyTypeHybridRowSerializer::Id.Id(): + { + return ObjectPropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + case UdtPropertyTypeHybridRowSerializer::Id.Id(): + { + return UdtPropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + case SetPropertyTypeHybridRowSerializer::Id.Id(): + { + return SetPropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + case MapPropertyTypeHybridRowSerializer::Id.Id(): + { + return MapPropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + case TuplePropertyTypeHybridRowSerializer::Id.Id(): + { + return TuplePropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + case TaggedPropertyTypeHybridRowSerializer::Id.Id(): + { + return TaggedPropertyTypeHybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + } + + default: + break; + } + + cdb_core::Contract::Fail("Type is abstract."); + } + + cdb_hr::Result ScopePropertyTypeHybridRowSerializer::WriteBase(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const ScopePropertyType& value) noexcept + { + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = ScopePropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result ScopePropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const ScopePropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetImmutable())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Boolean.WriteFixed( + row, scope, ImmutableColumn, cdb_hr::IHybridRowSerializer::get(value.GetImmutable())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = PropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> ScopePropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (!(scope.GetTypeArg().GetType()->IsUDT())) + { + return {cdb_hr::Result::TypeMismatch, std::unique_ptr{}}; + } + + switch (scope.GetTypeArg().GetTypeArgs().GetSchemaId().Id()) + { + case ArrayPropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = ArrayPropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + case ObjectPropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = ObjectPropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + case UdtPropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = UdtPropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + case SetPropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = SetPropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + case MapPropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = MapPropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + case TuplePropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = TuplePropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + case TaggedPropertyTypeHybridRowSerializer::Id.Id(): + { + auto [r, fieldValue] = TaggedPropertyTypeHybridRowSerializer::Read(row, scope, false); + return {r, std::move(fieldValue)}; + } + + default: + break; + } + + cdb_core::Contract::Fail("Type is abstract."); + } + + cdb_hr::Result ScopePropertyTypeHybridRowSerializer::ReadBase(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, ScopePropertyType& value) + { + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = ScopePropertyTypeHybridRowSerializer::Literal::Read(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result ScopePropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, ScopePropertyType& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Boolean.ReadFixed(row, scope, ImmutableColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetImmutable(fieldValue); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = PropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class ArrayPropertyTypeHybridRowSerializer::Literal final + { + friend struct ArrayPropertyTypeHybridRowSerializer; + + constexpr static std::string_view ItemsName{"items"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& ItemsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ItemsName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& ItemsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, ItemsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const ArrayPropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, ArrayPropertyType& value); + }; + + cdb_hr::Result ArrayPropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const ArrayPropertyType& value) noexcept + { + if (isRoot) + { + return ArrayPropertyTypeHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = ArrayPropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result ArrayPropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const ArrayPropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetItems())) + { + scope.Find(row, ItemsColumn.GetPath()); + cdb_hr::Result r = PropertyTypeHybridRowSerializer::Write( + row, scope, false, ItemsColumn.GetTypeArgs(), cdb_hr::IHybridRowSerializer::get(value.GetItems())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> ArrayPropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = ArrayPropertyTypeHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = ArrayPropertyTypeHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result ArrayPropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, ArrayPropertyType& value) + { + while (scope.MoveNext(row)) + { + if (scope.GetToken() == ItemsToken.GetId()) + { + auto [r, fieldValue] = PropertyTypeHybridRowSerializer::Read(row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetItems(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class ObjectPropertyTypeHybridRowSerializer::Literal final + { + friend struct ObjectPropertyTypeHybridRowSerializer; + + constexpr static std::string_view PropertiesName{"properties"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& PropertiesColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, PropertiesName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& PropertiesToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, PropertiesColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const ObjectPropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, ObjectPropertyType& value); + }; + + cdb_hr::Result ObjectPropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const ObjectPropertyType& value) noexcept + { + if (isRoot) + { + return ObjectPropertyTypeHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = ObjectPropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result ObjectPropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const ObjectPropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetProperties())) + { + scope.Find(row, PropertiesColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + PropertiesColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetProperties())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> ObjectPropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = ObjectPropertyTypeHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = ObjectPropertyTypeHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result ObjectPropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, ObjectPropertyType& value) + { + while (scope.MoveNext(row)) + { + if (scope.GetToken() == PropertiesToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetProperties(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class UdtPropertyTypeHybridRowSerializer::Literal final + { + friend struct UdtPropertyTypeHybridRowSerializer; + + constexpr static std::string_view NameName{"name"sv}; + constexpr static std::string_view SchemaIdName{"id"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& NameColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, NameName)}; + inline static const cdb_hr::LayoutColumn& SchemaIdColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, SchemaIdName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const UdtPropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, UdtPropertyType& value); + }; + + cdb_hr::Result UdtPropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const UdtPropertyType& value) noexcept + { + if (isRoot) + { + return UdtPropertyTypeHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = UdtPropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result UdtPropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const UdtPropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetSchemaId())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Int32.WriteFixed( + row, scope, SchemaIdColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetSchemaId()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetName())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, NameColumn, cdb_hr::IHybridRowSerializer::get(value.GetName())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> UdtPropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = UdtPropertyTypeHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = UdtPropertyTypeHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result UdtPropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, UdtPropertyType& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Int32.ReadFixed(row, scope, SchemaIdColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetSchemaId(static_cast(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, NameColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetName(std::string(fieldValue)); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class SetPropertyTypeHybridRowSerializer::Literal final + { + friend struct SetPropertyTypeHybridRowSerializer; + + constexpr static std::string_view ItemsName{"items"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& ItemsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ItemsName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& ItemsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, ItemsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const SetPropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, SetPropertyType& value); + }; + + cdb_hr::Result SetPropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const SetPropertyType& value) noexcept + { + if (isRoot) + { + return SetPropertyTypeHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = SetPropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result SetPropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const SetPropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetItems())) + { + scope.Find(row, ItemsColumn.GetPath()); + cdb_hr::Result r = PropertyTypeHybridRowSerializer::Write( + row, scope, false, ItemsColumn.GetTypeArgs(), cdb_hr::IHybridRowSerializer::get(value.GetItems())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> SetPropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = SetPropertyTypeHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = SetPropertyTypeHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result SetPropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, SetPropertyType& value) + { + while (scope.MoveNext(row)) + { + if (scope.GetToken() == ItemsToken.GetId()) + { + auto [r, fieldValue] = PropertyTypeHybridRowSerializer::Read(row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetItems(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class MapPropertyTypeHybridRowSerializer::Literal final + { + friend struct MapPropertyTypeHybridRowSerializer; + + constexpr static std::string_view KeysName{"keys"sv}; + constexpr static std::string_view ValuesName{"values"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& KeysColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, KeysName)}; + inline static const cdb_hr::LayoutColumn& ValuesColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ValuesName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& KeysToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, KeysColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& ValuesToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, ValuesColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const MapPropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, MapPropertyType& value); + }; + + cdb_hr::Result MapPropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const MapPropertyType& value) noexcept + { + if (isRoot) + { + return MapPropertyTypeHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = MapPropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result MapPropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const MapPropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetKeys())) + { + scope.Find(row, KeysColumn.GetPath()); + cdb_hr::Result r = PropertyTypeHybridRowSerializer::Write( + row, scope, false, KeysColumn.GetTypeArgs(), cdb_hr::IHybridRowSerializer::get(value.GetKeys())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetValues())) + { + scope.Find(row, ValuesColumn.GetPath()); + cdb_hr::Result r = PropertyTypeHybridRowSerializer::Write( + row, scope, false, ValuesColumn.GetTypeArgs(), cdb_hr::IHybridRowSerializer::get(value.GetValues())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> MapPropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = MapPropertyTypeHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = MapPropertyTypeHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result MapPropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, MapPropertyType& value) + { + while (scope.MoveNext(row)) + { + if (scope.GetToken() == KeysToken.GetId()) + { + auto [r, fieldValue] = PropertyTypeHybridRowSerializer::Read(row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetKeys(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == ValuesToken.GetId()) + { + auto [r, fieldValue] = PropertyTypeHybridRowSerializer::Read(row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetValues(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class TuplePropertyTypeHybridRowSerializer::Literal final + { + friend struct TuplePropertyTypeHybridRowSerializer; + + constexpr static std::string_view ItemsName{"items"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& ItemsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ItemsName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& ItemsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, ItemsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const TuplePropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, TuplePropertyType& value); + }; + + cdb_hr::Result TuplePropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const TuplePropertyType& value) noexcept + { + if (isRoot) + { + return TuplePropertyTypeHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = TuplePropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result TuplePropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const TuplePropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetItems())) + { + scope.Find(row, ItemsColumn.GetPath()); + cdb_hr::Result r = cdb_hr::ArrayHybridRowSerializer::Write( + row, + scope, + false, + ItemsColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetItems())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> TuplePropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = TuplePropertyTypeHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = TuplePropertyTypeHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result TuplePropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, TuplePropertyType& value) + { + while (scope.MoveNext(row)) + { + if (scope.GetToken() == ItemsToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::ArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetItems(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class TaggedPropertyTypeHybridRowSerializer::Literal final + { + friend struct TaggedPropertyTypeHybridRowSerializer; + + constexpr static std::string_view ItemsName{"items"sv}; + constexpr static std::string_view __BaseName{"__base"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& ItemsColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ItemsName)}; + inline static const cdb_hr::LayoutColumn& __BaseColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& ItemsToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, ItemsColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& __BaseToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const TaggedPropertyType& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, TaggedPropertyType& value); + }; + + cdb_hr::Result TaggedPropertyTypeHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const TaggedPropertyType& value) noexcept + { + if (isRoot) + { + return TaggedPropertyTypeHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = TaggedPropertyTypeHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result TaggedPropertyTypeHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const TaggedPropertyType& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetItems())) + { + scope.Find(row, ItemsColumn.GetPath()); + cdb_hr::Result r = cdb_hr::ArrayHybridRowSerializer::Write( + row, + scope, + false, + ItemsColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetItems())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + { + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> TaggedPropertyTypeHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = TaggedPropertyTypeHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = TaggedPropertyTypeHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result TaggedPropertyTypeHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, TaggedPropertyType& value) + { + while (scope.MoveNext(row)) + { + if (scope.GetToken() == ItemsToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::ArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetItems(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == __BaseToken.GetId()) + { + cdb_hr::Result r = ScopePropertyTypeHybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + continue; + } + } + + return cdb_hr::Result::Success; + } + + class EnumSchemaHybridRowSerializer::Literal final + { + friend struct EnumSchemaHybridRowSerializer; + + constexpr static std::string_view TypeName{"type"sv}; + constexpr static std::string_view NameName{"name"sv}; + constexpr static std::string_view CommentName{"comment"sv}; + constexpr static std::string_view ApiTypeName{"apitype"sv}; + constexpr static std::string_view ValuesName{"values"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& TypeColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, TypeName)}; + inline static const cdb_hr::LayoutColumn& NameColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, NameName)}; + inline static const cdb_hr::LayoutColumn& CommentColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, CommentName)}; + inline static const cdb_hr::LayoutColumn& ApiTypeColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ApiTypeName)}; + inline static const cdb_hr::LayoutColumn& ValuesColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ValuesName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& CommentToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, CommentColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& ApiTypeToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, ApiTypeColumn.GetPath()) + }; + inline static const cdb_hr::StringTokenizer::StringToken& ValuesToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, ValuesColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const EnumSchema& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, EnumSchema& value); + }; + + cdb_hr::Result EnumSchemaHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const EnumSchema& value) noexcept + { + if (isRoot) + { + return EnumSchemaHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = EnumSchemaHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result EnumSchemaHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const EnumSchema& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetType())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::UInt8.WriteFixed( + row, scope, TypeColumn, static_cast(cdb_hr::IHybridRowSerializer::get(value.GetType()))); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetName())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, NameColumn, cdb_hr::IHybridRowSerializer::get(value.GetName())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetComment())) + { + scope.Find(row, CommentColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetComment())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetApiType())) + { + scope.Find(row, ApiTypeColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetApiType())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.GetValues())) + { + scope.Find(row, ValuesColumn.GetPath()); + cdb_hr::Result r = cdb_hr::TypedArrayHybridRowSerializer::Write( + row, + scope, + false, + ValuesColumn.GetTypeArgs(), + cdb_hr::IHybridRowSerializer::get(value.GetValues())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> EnumSchemaHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = EnumSchemaHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = EnumSchemaHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result EnumSchemaHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, EnumSchema& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::UInt8.ReadFixed(row, scope, TypeColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetType(static_cast(fieldValue)); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, NameColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetName(std::string(fieldValue)); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == CommentToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetComment(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == ApiTypeToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetApiType(std::move(fieldValue)); + continue; + } + + if (scope.GetToken() == ValuesToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::TypedArrayHybridRowSerializer::Read( + row, scope, false); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetValues(std::move(fieldValue)); + continue; + } + } + + return cdb_hr::Result::Success; + } + + class EnumValueHybridRowSerializer::Literal final + { + friend struct EnumValueHybridRowSerializer; + + constexpr static std::string_view NameName{"name"sv}; + constexpr static std::string_view ValueName{"value"sv}; + constexpr static std::string_view CommentName{"comment"sv}; + + inline static const cdb_hr::Layout& Layout{SchemasHrSchema::GetLayoutResolver().Resolve(Id)}; + + inline static const cdb_hr::LayoutColumn& NameColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, NameName)}; + inline static const cdb_hr::LayoutColumn& ValueColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, ValueName)}; + inline static const cdb_hr::LayoutColumn& CommentColumn{cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, CommentName)}; + + inline static const cdb_hr::StringTokenizer::StringToken& CommentToken{ + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, CommentColumn.GetPath()) + }; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const EnumValue& value) noexcept; + static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, EnumValue& value); + }; + + cdb_hr::Result EnumValueHybridRowSerializer::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const EnumValue& value) noexcept + { + if (isRoot) + { + return EnumValueHybridRowSerializer::Literal::Write(row, scope, value); + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + { + return r; + } + + r = EnumValueHybridRowSerializer::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + } + + cdb_hr::Result EnumValueHybridRowSerializer::Literal::Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const EnumValue& value) noexcept + { + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetValue())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Int64.WriteFixed( + row, scope, ValueColumn, cdb_hr::IHybridRowSerializer::get(value.GetValue())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetName())) + { + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteVariable( + row, scope, NameColumn, cdb_hr::IHybridRowSerializer::get(value.GetName())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + if (!cdb_hr::IHybridRowSerializer::is_default(value.GetComment())) + { + scope.Find(row, CommentColumn.GetPath()); + cdb_hr::Result r = cdb_hr::LayoutLiteral::Utf8.WriteSparse( + row, scope, cdb_hr::IHybridRowSerializer::get(value.GetComment())); + if (r != cdb_hr::Result::Success) + { + return r; + } + } + + return cdb_hr::Result::Success; + } + + std::tuple> EnumValueHybridRowSerializer::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot) + { + if (isRoot) + { + std::unique_ptr value = std::make_unique(); + cdb_hr::Result r = EnumValueHybridRowSerializer::Literal::Read(row, scope, *value); + return {r, std::move(value)}; + } + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + std::unique_ptr value = std::make_unique(); + r = EnumValueHybridRowSerializer::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + { + return {r, std::unique_ptr{}}; + } + + scope.Skip(row, childScope); + return {cdb_hr::Result::Success, std::move(value)}; + } + + cdb_hr::Result EnumValueHybridRowSerializer::Literal::Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, EnumValue& value) + { + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Int64.ReadFixed(row, scope, ValueColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetValue(fieldValue); + break; + default: + return r; + } + } + + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadVariable(row, scope, NameColumn); + switch (r) + { + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.SetName(std::string(fieldValue)); + break; + default: + return r; + } + } + + while (scope.MoveNext(row)) + { + if (scope.GetToken() == CommentToken.GetId()) + { + auto [r, fieldValue] = cdb_hr::LayoutLiteral::Utf8.ReadSparse(row, scope); + if (r != cdb_hr::Result::Success) + { + return r; + } + + value.SetComment(std::move(fieldValue)); + continue; + } + } + + return cdb_hr::Result::Success; + } +} diff --git a/src/Serialization/HybridRow.Native/SystemSchema.h b/src/Serialization/HybridRow.Native/SystemSchema.h new file mode 100644 index 0000000..0cb4df5 --- /dev/null +++ b/src/Serialization/HybridRow.Native/SystemSchema.h @@ -0,0 +1,431 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +// ------------------------------------------------------------ +// This file was generated by: +// Microsoft.Azure.Cosmos.Serialization.HybridRowCLI: 1.0.0.0 +// +// This file should not be modified directly. +// ------------------------------------------------------------ + +namespace cdb_hr +{ + class Segment; + class Record; + class Namespace; + class Schema; + class SchemaOptions; + class PartitionKey; + class PrimarySortKey; + class StaticKey; + class Property; + class PropertyType; + class PrimitivePropertyType; + class ScopePropertyType; + class ArrayPropertyType; + class ObjectPropertyType; + class UdtPropertyType; + class SetPropertyType; + class MapPropertyType; + class TuplePropertyType; + class TaggedPropertyType; + class EnumSchema; + class EnumValue; + + struct SchemasHrSchema final + { + static const cdb_hr::Namespace& GetNamespace() noexcept; + static const cdb_hr::LayoutResolver& GetLayoutResolver() noexcept; + + private: + class Literal; + }; + + struct SegmentHybridRowSerializer final + { + using value_type = Segment; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473648}; + constexpr static uint32_t Size{5}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const Segment& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct RecordHybridRowSerializer final + { + using value_type = Record; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473649}; + constexpr static uint32_t Size{8}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const Record& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct NamespaceHybridRowSerializer final + { + using value_type = Namespace; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473651}; + constexpr static uint32_t Size{2}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const Namespace& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct SchemaHybridRowSerializer final + { + using value_type = Schema; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473652}; + constexpr static uint32_t Size{7}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const Schema& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + /// + /// Describes the set of options that apply to the entire schema and the way it is validated. + /// + struct SchemaOptionsHybridRowSerializer final + { + using value_type = SchemaOptions; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473653}; + constexpr static uint32_t Size{0}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const SchemaOptions& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct PartitionKeyHybridRowSerializer final + { + using value_type = PartitionKey; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473654}; + constexpr static uint32_t Size{1}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const PartitionKey& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct PrimarySortKeyHybridRowSerializer final + { + using value_type = PrimarySortKey; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473655}; + constexpr static uint32_t Size{2}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const PrimarySortKey& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct StaticKeyHybridRowSerializer final + { + using value_type = StaticKey; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473656}; + constexpr static uint32_t Size{1}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const StaticKey& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct PropertyHybridRowSerializer final + { + using value_type = Property; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473657}; + constexpr static uint32_t Size{1}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const Property& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct PropertyTypeHybridRowSerializer final + { + using value_type = PropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473658}; + constexpr static uint32_t Size{2}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const PropertyType& value) noexcept; + static cdb_hr::Result WriteBase(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const PropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + static cdb_hr::Result ReadBase(const RowBuffer& row, RowCursor& scope, PropertyType& value); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct PrimitivePropertyTypeHybridRowSerializer final + { + using value_type = PrimitivePropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473659}; + constexpr static uint32_t Size{5}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const PrimitivePropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct ScopePropertyTypeHybridRowSerializer final + { + using value_type = ScopePropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473660}; + constexpr static uint32_t Size{1}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const ScopePropertyType& value) noexcept; + static cdb_hr::Result WriteBase(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const ScopePropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + static cdb_hr::Result ReadBase(const RowBuffer& row, RowCursor& scope, ScopePropertyType& value); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct ArrayPropertyTypeHybridRowSerializer final + { + using value_type = ArrayPropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473661}; + constexpr static uint32_t Size{0}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const ArrayPropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct ObjectPropertyTypeHybridRowSerializer final + { + using value_type = ObjectPropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473662}; + constexpr static uint32_t Size{0}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const ObjectPropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct UdtPropertyTypeHybridRowSerializer final + { + using value_type = UdtPropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473663}; + constexpr static uint32_t Size{5}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const UdtPropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct SetPropertyTypeHybridRowSerializer final + { + using value_type = SetPropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473664}; + constexpr static uint32_t Size{0}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const SetPropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct MapPropertyTypeHybridRowSerializer final + { + using value_type = MapPropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473665}; + constexpr static uint32_t Size{0}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const MapPropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct TuplePropertyTypeHybridRowSerializer final + { + using value_type = TuplePropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473666}; + constexpr static uint32_t Size{0}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const TuplePropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct TaggedPropertyTypeHybridRowSerializer final + { + using value_type = TaggedPropertyType; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473667}; + constexpr static uint32_t Size{0}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const TaggedPropertyType& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct EnumSchemaHybridRowSerializer final + { + using value_type = EnumSchema; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473668}; + constexpr static uint32_t Size{2}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const EnumSchema& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); + + struct EnumValueHybridRowSerializer final + { + using value_type = EnumValue; + using owning_type = std::unique_ptr; + constexpr static cdb_hr::SchemaId Id{2147473669}; + constexpr static uint32_t Size{9}; + + static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const EnumValue& value) noexcept; + static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot); + + private: + class Literal; + }; + + static_assert(cdb_hr::is_hybridrow_serializer_v); +} diff --git a/src/Serialization/HybridRow.Native/SystemSchemaLiteral.h b/src/Serialization/HybridRow.Native/SystemSchemaLiteral.h new file mode 100644 index 0000000..6a7492c --- /dev/null +++ b/src/Serialization/HybridRow.Native/SystemSchemaLiteral.h @@ -0,0 +1,27 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "Namespace.h" +#include "SystemSchema.h" + +namespace cdb_hr +{ + struct SystemSchemaLiteral final + { + SystemSchemaLiteral() = delete; + + /// + /// SchemaId of the empty schema. This schema has no defined cells but can accomodate + /// unschematized sparse content. + /// + constexpr static SchemaId EmptySchemaId{2147473650}; + + static const Namespace& GetNamespace(); + static const LayoutResolver& GetLayoutResolver(); + }; + + inline const Namespace& SystemSchemaLiteral::GetNamespace() { return SchemasHrSchema::GetNamespace(); } + inline const LayoutResolver& SystemSchemaLiteral::GetLayoutResolver() { return SchemasHrSchema::GetLayoutResolver(); } +} diff --git a/src/Serialization/HybridRow.Native/TaggedPropertyType.h b/src/Serialization/HybridRow.Native/TaggedPropertyType.h new file mode 100644 index 0000000..703e32e --- /dev/null +++ b/src/Serialization/HybridRow.Native/TaggedPropertyType.h @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include +#include "ScopePropertyType.h" + +namespace cdb_hr +{ + /// Tagged properties pair one or more typed values with an API-specific uint8 type code. + /// + /// The uint8 type code is implicitly in position 0 within the resulting tagged and should not + /// be specified in . + /// + class TaggedPropertyType final : public ScopePropertyType + { + public: + constexpr static uint32_t MinTaggedArguments = 1; + constexpr static uint32_t MaxTaggedArguments = 2; + + TaggedPropertyType() noexcept : ScopePropertyType{TypeKind::Tagged}, m_items{} {} + + explicit TaggedPropertyType(std::vector> items, + bool nullable = true, + bool immutable = false) noexcept : + ScopePropertyType{TypeKind::Tagged, nullable, immutable}, + m_items{std::move(items)} {} + + ~TaggedPropertyType() noexcept override = default; + TaggedPropertyType(TaggedPropertyType&) = delete; + TaggedPropertyType(TaggedPropertyType&&) = delete; + TaggedPropertyType& operator=(const TaggedPropertyType&) = delete; + TaggedPropertyType& operator=(TaggedPropertyType&&) = delete; + + [[nodiscard]] SchemaId GetRuntimeSchemaId() const noexcept override { return SchemaId{2147473667}; } + [[nodiscard]] PropertyKind GetKind() const noexcept override { return PropertyKind::Tagged; } + + /// Types of the elements of the tagged in element order. + [[nodiscard]] const std::vector>& GetItems() const noexcept { return m_items; } + std::vector>& GetItems() noexcept { return m_items; } + void SetItems(std::vector> value) noexcept { m_items = std::move(value); } + + private: + std::vector> m_items; + }; +} diff --git a/src/Serialization/HybridRow.Native/TuplePropertyType.h b/src/Serialization/HybridRow.Native/TuplePropertyType.h new file mode 100644 index 0000000..95d7b2e --- /dev/null +++ b/src/Serialization/HybridRow.Native/TuplePropertyType.h @@ -0,0 +1,41 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include +#include "ScopePropertyType.h" + +namespace cdb_hr +{ + /// Tuple properties represent a typed, finite, ordered set of two or more items. + class TuplePropertyType final : public ScopePropertyType + { + public: + TuplePropertyType() noexcept : ScopePropertyType{TypeKind::Tuple}, m_items{} {} + + explicit TuplePropertyType(std::vector> items, + bool nullable = true, + bool immutable = false) noexcept : + ScopePropertyType{TypeKind::Tuple, nullable, immutable}, + m_items{std::move(items)} {} + + ~TuplePropertyType() noexcept override = default; + TuplePropertyType(TuplePropertyType&) = delete; + TuplePropertyType(TuplePropertyType&&) = delete; + TuplePropertyType& operator=(const TuplePropertyType&) = delete; + TuplePropertyType& operator=(TuplePropertyType&&) = delete; + + [[nodiscard]] SchemaId GetRuntimeSchemaId() const noexcept override { return SchemaId{2147473666}; } + [[nodiscard]] PropertyKind GetKind() const noexcept override { return PropertyKind::Tuple; } + + /// Types of the elements of the tuple in element order. + [[nodiscard]] const std::vector>& GetItems() const noexcept { return m_items; } + std::vector>& GetItems() noexcept { return m_items; } + void SetItems(std::vector> value) noexcept { m_items = std::move(value); } + + private: + std::vector> m_items; + }; +} diff --git a/src/Serialization/HybridRow.Native/TypeArgument.cpp b/src/Serialization/HybridRow.Native/TypeArgument.cpp new file mode 100644 index 0000000..ceea798 --- /dev/null +++ b/src/Serialization/HybridRow.Native/TypeArgument.cpp @@ -0,0 +1,56 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include +#include "TypeArgument.h" +#include "TypeArgumentList.h" +#include "LayoutType.h" + +namespace cdb_hr +{ + using namespace tla::literals; + + TypeArgument::TypeArgument(const LayoutType* type, TypeArgumentList typeArgs) noexcept : + m_type{type}, + m_typeArgs(std::move(typeArgs)) + { + cdb_core::Contract::Requires(type != nullptr); + } + + const LayoutType* TypeArgument::GetType() const noexcept { return m_type; } + const TypeArgumentList& TypeArgument::GetTypeArgs() const noexcept { return m_typeArgs; } + + tla::string TypeArgument::ToString() const noexcept + { + static_assert(cdb_core::is_stringable_v); + + if (m_type == nullptr) + { + return ""_s; + } + + tla::string_view name = m_type->GetName(); + return tla::string(name.data(), name.size()) + m_typeArgs.ToString(); + } + + size_t TypeArgument::GetHashCode() const noexcept + { + static_assert(cdb_core::is_hashable_v); + + if (m_type == nullptr) + { + return 397; + } + + return (m_type->GetHashCode() * 397) ^ m_typeArgs.GetHashCode(); + } + + bool operator==(const TypeArgument& lhs, const TypeArgument& rhs) + { + return lhs.m_type == rhs.m_type && lhs.m_typeArgs == rhs.m_typeArgs; + } + + bool operator!=(const TypeArgument& lhs, const TypeArgument& rhs) { return !(lhs == rhs); } +} diff --git a/src/Serialization/HybridRow.Native/TypeArgument.h b/src/Serialization/HybridRow.Native/TypeArgument.h new file mode 100644 index 0000000..3b44004 --- /dev/null +++ b/src/Serialization/HybridRow.Native/TypeArgument.h @@ -0,0 +1,61 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include "TypeArgumentList.h" + +namespace cdb_hr +{ + class LayoutType; + struct ILayoutType; + + struct TypeArgument final + { + constexpr TypeArgument() noexcept : m_type{nullptr}, m_typeArgs{} {} + ~TypeArgument() noexcept = default; + TypeArgument(const TypeArgument& other) noexcept = default; + TypeArgument(TypeArgument&& other) noexcept = default; + TypeArgument& operator=(const TypeArgument& other) noexcept = default; + TypeArgument& operator=(TypeArgument&& other) noexcept = default; + + /// Initializes a new instance of the struct. + /// The type of the constraint. + constexpr TypeArgument(const LayoutType* type) noexcept; + + /// Initializes a new instance of the struct. + /// The type of the constraint. + /// For generic types the type parameters. + TypeArgument(const LayoutType* type, TypeArgumentList typeArgs) noexcept; + + /// The physical layout type. + [[nodiscard]] + const LayoutType* GetType() const noexcept; + + /// If the type argument is itself generic, then its type arguments. + [[nodiscard]] + const TypeArgumentList& GetTypeArgs() const noexcept; + + friend bool operator==(const TypeArgument& lhs, const TypeArgument& rhs); + friend bool operator!=(const TypeArgument& lhs, const TypeArgument& rhs); + + /// The physical layout type of the field cast to the specified type. + template>> + const T& TypeAs() const; + + [[nodiscard]] + tla::string ToString() const noexcept; + + [[nodiscard]] + size_t GetHashCode() const noexcept; + + private: + const LayoutType* m_type; + TypeArgumentList m_typeArgs; + }; + + constexpr TypeArgument::TypeArgument(const LayoutType* type) noexcept: m_type{type}, m_typeArgs{} + { + cdb_core::Contract::Requires(type != nullptr); + } +} diff --git a/src/Serialization/HybridRow.Native/TypeArgumentList.cpp b/src/Serialization/HybridRow.Native/TypeArgumentList.cpp new file mode 100644 index 0000000..37eb4c2 --- /dev/null +++ b/src/Serialization/HybridRow.Native/TypeArgumentList.cpp @@ -0,0 +1,124 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" +#include "TypeArgument.h" +#include "TypeArgumentList.h" +#include "LayoutType.h" + +namespace cdb_hr +{ + TypeArgumentList::TypeArgumentList(const tla::vector& args) noexcept : + m_args{std::allocate_shared>(tla::allocator>{}, args)}, + m_schemaId{} { } + + TypeArgumentList::TypeArgumentList(tla::vector&& args) noexcept : + m_args{std::allocate_shared>(tla::allocator>{}, args)}, + m_schemaId{} { } + + TypeArgumentList::TypeArgumentList(const TypeArgument& arg1) noexcept : + m_args{std::allocate_shared>(tla::allocator>{}, 1, arg1)}, + m_schemaId{} { } + + TypeArgumentList::TypeArgumentList(SchemaId schemaId) noexcept : + m_args{nullptr, std::default_delete>{}, tla::allocator>{}}, + m_schemaId{schemaId} { } + + size_t TypeArgumentList::GetCount() const noexcept { return m_args == nullptr ? 0 : m_args->size(); } + SchemaId TypeArgumentList::GetSchemaId() const noexcept { return m_schemaId; } + const TypeArgument& TypeArgumentList::operator[](size_t i) const noexcept { return (*m_args)[i]; } + + TypeArgumentList::Enumerator TypeArgumentList::begin() const + { + return TypeArgumentList::Enumerator(m_args.get(), 0); + } + + TypeArgumentList::Enumerator TypeArgumentList::end() const + { + return TypeArgumentList::Enumerator(m_args.get(), GetCount()); + } + + tla::string TypeArgumentList::ToString() const noexcept + { + static_assert(cdb_core::is_stringable_v); + + if (m_schemaId != SchemaId::Invalid()) + { + return cdb_core::make_string("<%s>", m_schemaId.ToString().c_str()); + } + + if (m_args == nullptr || m_args->empty()) + { + return tla::string(); + } + + tla::string ret{}; + for (const auto& s : *m_args) + { + ret += s.ToString(); + } + return cdb_core::make_string("<%s>", ret.c_str()); + } + + size_t TypeArgumentList::GetHashCode() const noexcept + { + static_assert(cdb_core::is_hashable_v); + + size_t hash = 19; + hash = (hash * 397) ^ m_schemaId.GetHashCode(); + if (m_args != nullptr) + { + for (const auto& a : *m_args) + { + hash = (hash * 397) ^ a.GetHashCode(); + } + } + + return hash; + } + + bool operator==(const TypeArgumentList& left, const TypeArgumentList& right) noexcept + { + return (left.m_schemaId == right.m_schemaId) && left.m_args == right.m_args; + } + + bool operator!=(const TypeArgumentList& left, const TypeArgumentList& right) noexcept + { + return !(left == right); + } + + TypeArgumentList::Enumerator& TypeArgumentList::Enumerator::operator++() noexcept + { + m_index++; + return *this; + } + + TypeArgumentList::Enumerator TypeArgumentList::Enumerator::operator++(int) noexcept + { + m_index++; + return Enumerator(m_list, m_index - 1); + } + + const TypeArgument& TypeArgumentList::Enumerator::operator*() const noexcept + { + return (*m_list)[m_index]; + } + + TypeArgumentList::Enumerator::Enumerator(const tla::vector* list, size_t index) noexcept : + m_list(list), m_index(index) + { + cdb_core::Contract::Requires(list != nullptr); + cdb_core::Contract::Requires(index <= list->size()); + } + + bool operator==(const TypeArgumentList::Enumerator& lhs, const TypeArgumentList::Enumerator& rhs) + { + return (lhs.m_list == rhs.m_list) && (lhs.m_index == rhs.m_index); + } + + bool operator!=(const TypeArgumentList::Enumerator& lhs, const TypeArgumentList::Enumerator& rhs) + { + return !(lhs == rhs); + } +} diff --git a/src/Serialization/HybridRow.Native/TypeArgumentList.h b/src/Serialization/HybridRow.Native/TypeArgumentList.h new file mode 100644 index 0000000..a786666 --- /dev/null +++ b/src/Serialization/HybridRow.Native/TypeArgumentList.h @@ -0,0 +1,103 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include "SchemaId.h" + +namespace cdb_hr +{ + struct TypeArgument; + class LayoutType; + + struct TypeArgumentList final + { + struct Enumerator; + + public: + constexpr TypeArgumentList() noexcept : m_args{}, m_schemaId{} {} + ~TypeArgumentList() noexcept = default; + TypeArgumentList(const TypeArgumentList& other) noexcept = default; + TypeArgumentList(TypeArgumentList&& other) noexcept = default; + TypeArgumentList& operator=(const TypeArgumentList& other) noexcept = default; + TypeArgumentList& operator=(TypeArgumentList&& other) noexcept = default; + TypeArgumentList(const tla::vector& args) noexcept; + TypeArgumentList(tla::vector&& args) noexcept; + TypeArgumentList(const TypeArgument& arg1) noexcept; + + /// Empty value. + static TypeArgumentList Empty; + + /// Initializes a new instance of the struct. + /// For UDT fields, the schema id of the nested layout. + TypeArgumentList(SchemaId schemaId) noexcept; + + [[nodiscard]] + size_t GetCount() const noexcept; + + /// For UDT fields, the schema id of the nested layout. + [[nodiscard]] + SchemaId GetSchemaId() const noexcept; + + const TypeArgument& operator[](size_t i) const noexcept; + friend bool operator==(const TypeArgumentList& left, const TypeArgumentList& right) noexcept; + friend bool operator!=(const TypeArgumentList& left, const TypeArgumentList& right) noexcept; + + /// Gets beginning enumerator for this span. + [[nodiscard]] + Enumerator begin() const; + + /// Gets the end enumerator for this span. + [[nodiscard]] + Enumerator end() const; + + [[nodiscard]] + tla::string ToString() const noexcept; + + [[nodiscard]] + size_t GetHashCode() const noexcept; + + /// Enumerates the elements of a . + struct Enumerator final + { + Enumerator() noexcept = delete; + ~Enumerator() noexcept = default; + Enumerator(const Enumerator& other) noexcept = default; + Enumerator(Enumerator&& other) noexcept = default; + Enumerator& operator=(const Enumerator& other) noexcept = default; + Enumerator& operator=(Enumerator&& other) noexcept = default; + + friend bool operator==(const Enumerator& lhs, const Enumerator& rhs); + friend bool operator!=(const Enumerator& lhs, const Enumerator& rhs); + + /// Advances the enumerator to the next element of the span. + Enumerator& operator++() noexcept; + Enumerator operator++(int) noexcept; + + /// Gets the element at the current position of the enumerator. + [[nodiscard]] + const TypeArgument& operator*() const noexcept; + + private: + friend struct TypeArgumentList; + + /// Initializes a new instance of the struct. + /// The list to enumerate. + /// Index within the list to start. + Enumerator(const tla::vector* list, size_t index) noexcept; + + /// The list being enumerated. + const tla::vector* m_list; + + /// The next index to yield. + size_t m_index; + }; + + private: + std::shared_ptr> m_args; + + /// For UDT fields, the schema id of the nested layout. + SchemaId m_schemaId; + }; +} diff --git a/src/Serialization/HybridRow.Native/TypeKind.h b/src/Serialization/HybridRow.Native/TypeKind.h new file mode 100644 index 0000000..9f38c11 --- /dev/null +++ b/src/Serialization/HybridRow.Native/TypeKind.h @@ -0,0 +1,167 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ + using namespace std::literals; + + /// Describes the logical type of a property. + enum class TypeKind : unsigned char + { + /// Reserved. + Invalid = 0, + + /// The literal null. + /// + /// When used as a fixed column, only a presence bit is allocated. When used as a sparse + /// column, a sparse value with 0-length payload is written. + /// + Null, + + /// + /// A boolean property. Boolean properties are allocated a single bit when schematized within + /// a row. + /// + Boolean, + + /// 8-bit signed integer. + Int8, + + /// + /// 16-bit signed integer. + /// + Int16, + + /// 32-bit signed integer. + Int32, + + /// 64-bit signed integer. + Int64, + + /// 8-bit unsigned integer. + UInt8, + + /// 16-bit unsigned integer. + UInt16, + + /// 32-bit unsigned integer. + UInt32, + + /// 64-bit unsigned integer. + UInt64, + + /// Variable length encoded signed integer. + VarInt, + + /// Variable length encoded unsigned integer. + VarUInt, + + /// 32-bit IEEE 754 floating point value. + Float32, + + /// 64-bit IEEE 754 floating point value. + Float64, + + /// 128-bit IEEE 754-2008 floating point value. + Float128, + + /// 128-bit floating point value. See + Decimal, + + /// + /// 64-bit date/time value in 100ns increments from C# epoch. See + /// + DateTime, + + /// + /// 64-bit date/time value in milliseconds increments from Unix epoch. See + /// + UnixDateTime, + + /// 128-bit globally unique identifier (in little-endian byte order). + Guid, + + /// 12-byte MongoDB Object Identifier (in little-endian byte order). + MongoDbObjectId, + + /// Zero to MAX_ROW_SIZE bytes encoded as UTF-8 code points. + Utf8, + + /// Zero to MAX_ROW_SIZE untyped bytes. + Binary, + + /// An object property. + Object, + + /// An array property, either typed or untyped. + Array, + + /// A set property, either typed or untyped. + Set, + + /// A map property, either typed or untyped. + Map, + + /// A tuple property. Tuples are typed, finite, ordered, sets. + Tuple, + + /// A tagged property. Tagged properties pair one or more typed values with an API-specific uint8 type code. + Tagged, + + /// A row with schema. + /// May define either a top-level table schema or a UDT (nested row). + Schema, + + /// An untyped sparse field. + /// May only be used to define the type within a nested scope (e.g. or . + Any, + + /// + /// An enum type defined in the same namespace. + /// + Enum, + }; + + constexpr std::string_view ToStringView(const TypeKind value) noexcept + { + switch (value) + { + case TypeKind::Invalid: return "Invalid"sv; + case TypeKind::Null: return "Null"sv; + case TypeKind::Boolean: return "Boolean"sv; + case TypeKind::Int8: return "Int8"sv; + case TypeKind::Int16: return "Int16"sv; + case TypeKind::Int32: return "Int32"sv; + case TypeKind::Int64: return "Int64"sv; + case TypeKind::UInt8: return "UInt8"sv; + case TypeKind::UInt16: return "UInt16"sv; + case TypeKind::UInt32: return "UInt32"sv; + case TypeKind::UInt64: return "UInt64"sv; + case TypeKind::VarInt: return "VarInt"sv; + case TypeKind::VarUInt: return "VarUInt"sv; + case TypeKind::Float32: return "Float32"sv; + case TypeKind::Float64: return "Float64"sv; + case TypeKind::Float128: return "Float128"sv; + case TypeKind::Decimal: return "Decimal"sv; + case TypeKind::DateTime: return "DateTime"sv; + case TypeKind::UnixDateTime: return "UnixDateTime"sv; + case TypeKind::Guid: return "Guid"sv; + case TypeKind::MongoDbObjectId: return "MongoDbObjectId"sv; + case TypeKind::Utf8: return "Utf8"sv; + case TypeKind::Binary: return "Binary"sv; + case TypeKind::Object: return "Object"sv; + case TypeKind::Array: return "Array"sv; + case TypeKind::Set: return "Set"sv; + case TypeKind::Map: return "Map"sv; + case TypeKind::Tuple: return "Tuple"sv; + case TypeKind::Tagged: return "Tagged"sv; + case TypeKind::Schema: return "Schema"sv; + case TypeKind::Any: return "Any"sv; + case TypeKind::Enum: return "Enum"sv; + default: cdb_core::Contract::Fail("Unknown TypeKind"); + } + } +} diff --git a/src/Serialization/HybridRow.Native/TypedArrayHybridRowSerializer.h b/src/Serialization/HybridRow.Native/TypedArrayHybridRowSerializer.h new file mode 100644 index 0000000..64a566d --- /dev/null +++ b/src/Serialization/HybridRow.Native/TypedArrayHybridRowSerializer.h @@ -0,0 +1,107 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "IHybridRowSerializer.h" +#include "LayoutType.h" +#include "RowCursor.h" + +namespace cdb_hr +{ + template>> + struct TypedArrayHybridRowSerializer final + { + struct TypedArrayComparer; + using value_type = std::vector; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = TypedArrayComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const_reference value) noexcept + { + cdb_core::Contract::Assert(!isRoot); + auto [r, childScope] = LayoutLiteral::TypedArray.WriteScope(row, scope, typeArgs); + if (r != Result::Success) + { + return r; + } + + for (const auto& item : value) + { + r = TSerializer::Write(row, childScope, false, typeArgs[0].GetTypeArgs(), IHybridRowSerializer::get(item)); + if (r != Result::Success) + { + return r; + } + + childScope.MoveNext(row); + } + + scope.Skip(row, childScope); + return Result::Success; + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + cdb_core::Contract::Assert(!isRoot); + auto [r, childScope] = LayoutLiteral::TypedArray.ReadScope(row, scope); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + value_type items{}; + while (childScope.MoveNext(row)) + { + auto [r, item] = TSerializer::Read(row, childScope, isRoot); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + items.emplace_back(std::move(item)); + } + + scope.Skip(row, childScope); + return {Result::Success, std::move(items)}; + } + + struct TypedArrayComparer final + { + constexpr bool operator()(const_reference x, const_reference y) const + { + if (&x == &y) + { + return true; + } + if (x.size() != y.size()) + { + return false; + } + + for (int i = 0; i < x.size(); i++) + { + if (!typename TSerializer::comparer_type{}.operator()(x[i], y[i])) + { + return false; + } + } + + return true; + } + + constexpr std::size_t operator()(const_reference s) const noexcept + { + cdb_core::HashCode hash{}; + for (int i = 0; i < s.size(); i++) + { + hash.Add(s[i]); + } + return hash.ToHashCode(); + } + }; + }; +} diff --git a/src/Serialization/HybridRow.Native/TypedMapHybridRowSerializer.h b/src/Serialization/HybridRow.Native/TypedMapHybridRowSerializer.h new file mode 100644 index 0000000..99d0677 --- /dev/null +++ b/src/Serialization/HybridRow.Native/TypedMapHybridRowSerializer.h @@ -0,0 +1,137 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "IHybridRowSerializer.h" +#include "LayoutType.h" +#include "RowCursor.h" +#include "TypedTupleHybridRowSerializer.h" + +namespace cdb_hr +{ + template>, + typename = std::enable_if_t>> + struct TypedMapHybridRowSerializer final + { + struct TypedMapComparer; + using TKey = typename TKeySerializer::owning_type; + using TValue = typename TValueSerializer::owning_type; + using value_type = std::unordered_map< + typename TKeySerializer::owning_type, + typename TValueSerializer::owning_type, + typename TKeySerializer::comparer_type, + typename TKeySerializer::comparer_type + >; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = TypedMapComparer; + + static Result Write(RowBuffer& row, RowCursor& scope, bool isRoot, const TypeArgumentList& typeArgs, + const_reference value) noexcept + { + cdb_core::Contract::Assert(!isRoot); + auto [r, uniqueScope] = LayoutLiteral::TypedMap.WriteScope(row, scope, typeArgs); + if (r != Result::Success) + { + return r; + } + + RowCursor childScope = uniqueScope.Clone(); + childScope.m_deferUniqueIndex = true; + + for (std::pair item : value) + { + r = cdb_hr::TypedTupleHybridRowSerializer + ::Write(row, childScope, false, typeArgs, std::make_tuple(item.first, item.second)); + if (r != Result::Success) + { + return r; + } + + childScope.MoveNext(row); + } + + uniqueScope.m_count = childScope.m_count; + r = row.TypedCollectionUniqueIndexRebuild(uniqueScope); + if (r != Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return Result::Success; + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + cdb_core::Contract::Assert(!isRoot); + auto [r, childScope] = LayoutLiteral::TypedMap.ReadScope(row, scope); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + value_type items{}; + while (childScope.MoveNext(row)) + { + auto [r, item] = cdb_hr::TypedTupleHybridRowSerializer + ::Read(row, childScope, isRoot); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + items.emplace(std::make_pair(std::get<0>(item), std::get<1>(item))); + } + + scope.Skip(row, childScope); + return {Result::Success, std::move(items)}; + } + + struct TypedMapComparer final + { + constexpr bool operator()(const_reference x, const_reference y) const + { + if (&x == &y) + { + return true; + } + if (x.size() != y.size()) + { + return false; + } + + for (const auto&p : x) + { + typename value_type::const_iterator iter = y.find(p.first); + if (iter == y.end()) + { + return false; + } + + if (!typename TValueSerializer::comparer_type{}.operator()(p.second, iter->second)) + { + return false; + } + + } + + return true; + } + + constexpr std::size_t operator()(const_reference s) const noexcept + { + cdb_core::HashCode hash{}; + for (std::pair p : s) + { + hash.Add(p.first); + hash.Add(p.second); + } + return hash.ToHashCode(); + } + }; + }; +} diff --git a/src/Serialization/HybridRow.Native/TypedTupleHybridRowSerializer.h b/src/Serialization/HybridRow.Native/TypedTupleHybridRowSerializer.h new file mode 100644 index 0000000..1040334 --- /dev/null +++ b/src/Serialization/HybridRow.Native/TypedTupleHybridRowSerializer.h @@ -0,0 +1,134 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "IHybridRowSerializer.h" +#include "LayoutType.h" +#include "RowCursor.h" + +namespace cdb_hr +{ + template + struct TypedTupleHybridRowSerializer final + { + static_assert(std::conjunction_v...>); + struct TypedTupleComparer; + using value_type = std::tuple; + using owning_type = value_type; + using const_reference = const value_type&; + using comparer_type = TypedTupleComparer; + + static Result Write( + RowBuffer& row, + RowCursor& scope, + bool isRoot, + const TypeArgumentList& typeArgs, + const value_type& value) noexcept + { + cdb_core::Contract::Assert(!isRoot); + auto [r, childScope] = LayoutLiteral::TypedTuple.WriteScope(row, scope, typeArgs); + WriteCols(r, row, childScope, typeArgs, value, std::make_index_sequence>{}); + if (r != Result::Success) + { + return r; + } + + scope.Skip(row, childScope); + return Result::Success; + } + + static std::tuple Read(const RowBuffer& row, RowCursor& scope, bool isRoot) + { + cdb_core::Contract::Assert(!isRoot); + auto [r, childScope] = LayoutLiteral::TypedTuple.ReadScope(row, scope); + value_type retval{}; + ReadCols(r, row, childScope, retval, std::make_index_sequence>{}); + if (r != Result::Success) + { + return {r, value_type{}}; + } + + scope.Skip(row, childScope); + return {Result::Success, std::move(retval)}; + } + + struct TypedTupleComparer final + { + constexpr bool operator()(const_reference x, const_reference y) const + { + return EqualCols(x, y, std::make_index_sequence>{}); + } + + constexpr std::size_t operator()(const_reference s) const noexcept + { + cdb_core::HashCode hash{}; + HashCols(hash, s, std::make_index_sequence>{}); + return hash.ToHashCode(); + } + + private: + template + static bool EqualCols(const_reference x, const_reference y, std::index_sequence) noexcept + { + return (... && (EqualCol(std::get(x), std::get(y)))); + } + + template + static bool EqualCol(const typename TS::owning_type& x, const typename TS::owning_type& y) noexcept + { + return typename TS::comparer_type{}.operator()(x, y); + } + + template + static void HashCols(cdb_core::HashCode& hash, const_reference s, std::index_sequence) noexcept + { + ((HashCol(hash, std::get(s))), ...); + } + + template + static void HashCol(cdb_core::HashCode& hash, const typename TS::owning_type& s) noexcept + { + hash.Add(s); + } + }; + + private: + template + static void WriteCols(Result& r, RowBuffer& row, RowCursor& childScope, const TypeArgumentList& typeArgs, + const value_type& value, std::index_sequence) noexcept + { + (((r != Result::Success) + ? void(0) + : WriteCol(r, row, childScope, typeArgs[I].GetTypeArgs(), std::get(value))), ...); + } + + template + static void WriteCol(Result& r, RowBuffer& row, RowCursor& childScope, const TypeArgumentList& typeArgs, + const typename TS::owning_type& value) noexcept + { + r = TS::Write(row, childScope, false, typeArgs, value); + if (r == Result::Success) + { + childScope.MoveNext(row); + } + } + + template + static void ReadCols(Result& r, const RowBuffer& row, RowCursor& childScope, value_type& value, + std::index_sequence) noexcept + { + (((r != Result::Success) ? void(0) : ReadCol(r, row, childScope, std::get(value))), ...); + } + + template + static void ReadCol(Result& r, const RowBuffer& row, RowCursor& childScope, typename TS::owning_type& item) + { + if (childScope.MoveNext(row)) + { + std::tie(r, item) = TS::Read(row, childScope, false); + } + } + }; +} diff --git a/src/Serialization/HybridRow.Native/UdtPropertyType.h b/src/Serialization/HybridRow.Native/UdtPropertyType.h new file mode 100644 index 0000000..879880f --- /dev/null +++ b/src/Serialization/HybridRow.Native/UdtPropertyType.h @@ -0,0 +1,63 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include "SchemaId.h" +#include "ScopePropertyType.h" + +namespace cdb_hr +{ + /// UDT properties represent nested structures with an independent schema. + /// + /// UDT properties include a nested row within an existing row as a column. The schema of the + /// nested row may be evolved independently of the outer row. Changes to the independent schema affect + /// all outer schemas where the UDT is used. + /// + class UdtPropertyType final : public ScopePropertyType + { + public: + UdtPropertyType() noexcept : ScopePropertyType{TypeKind::Schema, true}, m_name{}, m_schemaId{} { } + + UdtPropertyType(std::string_view name, + SchemaId schemaId = SchemaId::Invalid(), + bool nullable = true, + bool immutable = false) noexcept : + ScopePropertyType{TypeKind::Schema, nullable, immutable}, + m_name{name}, + m_schemaId{schemaId} { } + + ~UdtPropertyType() noexcept override = default; + UdtPropertyType(UdtPropertyType&) = delete; + UdtPropertyType(UdtPropertyType&&) = delete; + UdtPropertyType& operator=(const UdtPropertyType&) = delete; + UdtPropertyType& operator=(UdtPropertyType&&) = delete; + + [[nodiscard]] SchemaId GetRuntimeSchemaId() const noexcept override { return SchemaId{2147473663}; } + [[nodiscard]] PropertyKind GetKind() const noexcept override { return PropertyKind::Udt; } + + /// The identifier of the UDT schema defining the structure for the nested row. + /// + /// The UDT schema MUST be defined within the same as the schema that + /// references it. + /// + [[nodiscard]] std::string_view GetName() const noexcept { return m_name; } + void SetName(std::string_view value) noexcept { m_name = value; } + + /// The unique identifier for a schema. + /// + /// Optional uniquifier if multiple versions of appears within the Namespace. + ///

+ /// If multiple versions of a UDT are defined within the then the globally + /// unique identifier of the specific version referenced MUST be provided. + ///

+ ///
+ [[nodiscard]] SchemaId GetSchemaId() const noexcept { return m_schemaId; } + void SetSchemaId(SchemaId value) noexcept { m_schemaId = value; } + + private: + tla::string m_name; + SchemaId m_schemaId; + }; +} diff --git a/src/Serialization/HybridRow.Native/UnixDateTime.h b/src/Serialization/HybridRow.Native/UnixDateTime.h new file mode 100644 index 0000000..2e2682d --- /dev/null +++ b/src/Serialization/HybridRow.Native/UnixDateTime.h @@ -0,0 +1,62 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include + +namespace cdb_hr +{ + /// A wall clock time expressed in milliseconds since the Unix Epoch. + /// + /// A is a fixed length value-type providing millisecond + /// granularity as a signed offset from the Unix Epoch (midnight, January 1, 1970 UTC). + /// + #pragma pack(push, 1) + struct UnixDateTime final + { + /// The size (in bytes) of a UnixDateTime. + constexpr static int Size = sizeof(int64_t); + + /// The unix epoch. + /// values are signed values centered on . + constexpr static UnixDateTime Epoch() { return UnixDateTime{}; } + + constexpr UnixDateTime() noexcept : m_milliseconds{0} {} + ~UnixDateTime() noexcept = default; + + /// Initializes a new instance of the struct. + /// The number of milliseconds since . + constexpr UnixDateTime(int64_t milliseconds) noexcept : m_milliseconds(milliseconds) { } + UnixDateTime(const UnixDateTime& other) noexcept = default; + UnixDateTime(UnixDateTime&& other) noexcept = default; + UnixDateTime& operator=(const UnixDateTime& other) noexcept = default; + UnixDateTime& operator=(UnixDateTime&& other) noexcept = default; + + bool operator==(const UnixDateTime& other) const noexcept { return m_milliseconds == other.m_milliseconds; } + bool operator!=(const UnixDateTime& other) const noexcept { return m_milliseconds != other.m_milliseconds; } + + /// The number of milliseconds since . + /// This value may be negative. + [[nodiscard]] int64_t GetMilliseconds() const noexcept { return m_milliseconds; } + + private: + friend std::hash; + + int64_t m_milliseconds; + }; + #pragma pack(pop) +} + +namespace std +{ + template<> + struct hash + { + constexpr std::size_t operator()(cdb_hr::UnixDateTime const& s) const noexcept + { + return cdb_core::HashCode::Combine(s.m_milliseconds); + } + }; +} diff --git a/src/Serialization/HybridRow.Native/UpdateOptions.h b/src/Serialization/HybridRow.Native/UpdateOptions.h new file mode 100644 index 0000000..9cdaf0f --- /dev/null +++ b/src/Serialization/HybridRow.Native/UpdateOptions.h @@ -0,0 +1,44 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +namespace cdb_hr +{ +/// Describes the desired behavior when writing a . +enum class UpdateOptions +{ + None = 0, + + /// Overwrite an existing value. + /// + /// An existing value is assumed to exist at the offset provided. The existing value is + /// replaced inline. The remainder of the row is resized to accomodate either an increase or decrease + /// in required space. + /// + Update = 1, + + /// Insert a new value. + /// + /// An existing value is assumed NOT to exist at the offset provided. The new value is + /// inserted immediately at the offset. The remainder of the row is resized to accomodate either an + /// increase or decrease in required space. + /// + Insert = 2, + + /// Update an existing value or insert a new value, if no value exists. + /// + /// If a value exists, then this operation becomes , otherwise it becomes + /// . + /// + Upsert = 3, + + /// Insert a new value moving existing values to the right. + /// + /// Within an array scope, inserts a new value immediately at the index moving all subsequent + /// items to the right. In any other scope behaves the same as . + /// + InsertAt = 4, +}; +} diff --git a/src/Serialization/HybridRow.Native/framework.h b/src/Serialization/HybridRow.Native/framework.h new file mode 100644 index 0000000..ffc3298 --- /dev/null +++ b/src/Serialization/HybridRow.Native/framework.h @@ -0,0 +1,30 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +using std::byte; + +#include "../../Core/Core.Native/Core.Native.h" + +typedef float_t float32_t; +typedef double_t float64_t; +#include "Float128.h" +typedef cdb_hr::Float128 float128_t; +#include "Decimal.h" +typedef cdb_hr::Decimal decimal_t; +#include "DateTime.h" +#include "UnixDateTime.h" +#include "Guid.h" +#include "MongoDbObjectId.h" +#include "NullValue.h" + diff --git a/src/Serialization/HybridRow.Native/pch.cpp b/src/Serialization/HybridRow.Native/pch.cpp new file mode 100644 index 0000000..690da7f --- /dev/null +++ b/src/Serialization/HybridRow.Native/pch.cpp @@ -0,0 +1,5 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#include "pch.h" diff --git a/src/Serialization/HybridRow.Native/pch.h b/src/Serialization/HybridRow.Native/pch.h new file mode 100644 index 0000000..ee1da47 --- /dev/null +++ b/src/Serialization/HybridRow.Native/pch.h @@ -0,0 +1,7 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma once + +#include "framework.h" diff --git a/dotnet/src/HybridRow.Tests.Perf/BenchmarkSuiteBase.cs b/src/Serialization/HybridRow.Tests.Perf/BenchmarkSuiteBase.cs similarity index 75% rename from dotnet/src/HybridRow.Tests.Perf/BenchmarkSuiteBase.cs rename to src/Serialization/HybridRow.Tests.Perf/BenchmarkSuiteBase.cs index ba2740d..16e5f94 100644 --- a/dotnet/src/HybridRow.Tests.Perf/BenchmarkSuiteBase.cs +++ b/src/Serialization/HybridRow.Tests.Perf/BenchmarkSuiteBase.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf using System; using System.Collections.Generic; using System.IO; + using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core.Utf8; using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; @@ -23,7 +24,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf private protected const int InitialCapacity = 2 * 1024 * 1024; private protected LayoutResolverNamespace DefaultResolver = (LayoutResolverNamespace)SystemSchema.LayoutResolver; - private protected async Task<(List>, LayoutResolverNamespace)> LoadExpectedAsync(string expectedFile) + private protected async Task<(List> Expected, LayoutResolverNamespace LayoutResolver)> LoadExpectedAsync(string expectedFile) { LayoutResolverNamespace resolver = this.DefaultResolver; List> expected = new List>(); @@ -32,19 +33,26 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf // Read a RecordIO stream. MemorySpanResizer resizer = new MemorySpanResizer(BenchmarkSuiteBase.InitialCapacity); Result r = await stm.ReadRecordIOAsync( - record => + (ReadOnlyMemory record) => { r = BenchmarkSuiteBase.LoadOneRow(record, resolver, out Dictionary rowValue); ResultAssert.IsSuccess(r); expected.Add(rowValue); return Result.Success; }, - segment => + (ReadOnlyMemory segment) => { - r = SegmentSerializer.Read(segment.Span, SystemSchema.LayoutResolver, out Segment s); + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + RowBuffer row = new RowBuffer(MemoryMarshal.AsMemory(segment).Span, + HybridRowVersion.V1, SchemasHrSchema.LayoutResolver); + RowCursor root = RowCursor.Create(ref row); + r = default(SegmentHybridRowSerializer).Read(ref row, ref root, true, out Segment s); ResultAssert.IsSuccess(r); - Assert.IsNotNull(s.SDL); - resolver = new LayoutResolverNamespace(Namespace.Parse(s.SDL), resolver); + Assert.IsNotNull(s.Schema); + resolver = new LayoutResolverNamespace(s.Schema, resolver); return Result.Success; }, resizer); @@ -57,7 +65,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf private protected static async Task WriteAllRowsAsync( string file, - string sdl, + Namespace ns, LayoutResolver resolver, Layout layout, List> rows) @@ -69,7 +77,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf // Write a RecordIO stream. Result r = await stm.WriteRecordIOAsync( - new Segment("HybridRow.Tests.Perf Expected Results", sdl), + new Segment("HybridRow.Tests.Perf Expected Results", ns), (long index, out ReadOnlyMemory body) => { body = default; @@ -98,10 +106,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf } } - private protected static Result LoadOneRow(Memory buffer, LayoutResolver resolver, out Dictionary rowValue) + private protected static Result LoadOneRow(ReadOnlyMemory buffer, LayoutResolver resolver, out Dictionary rowValue) { - RowBuffer row = new RowBuffer(buffer.Span, HybridRowVersion.V1, resolver); - RowReader reader = new RowReader(ref row); + RowReader reader = new RowReader(buffer, HybridRowVersion.V1, resolver); return DiagnosticConverter.ReaderToDynamic(ref reader, out rowValue); } diff --git a/dotnet/src/HybridRow.Tests.Perf/BsonJsonModelRowGenerator.cs b/src/Serialization/HybridRow.Tests.Perf/BsonJsonModelRowGenerator.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/BsonJsonModelRowGenerator.cs rename to src/Serialization/HybridRow.Tests.Perf/BsonJsonModelRowGenerator.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/BsonReaderExtensions.cs b/src/Serialization/HybridRow.Tests.Perf/BsonReaderExtensions.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/BsonReaderExtensions.cs rename to src/Serialization/HybridRow.Tests.Perf/BsonReaderExtensions.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/BsonRowGenerator.cs b/src/Serialization/HybridRow.Tests.Perf/BsonRowGenerator.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/BsonRowGenerator.cs rename to src/Serialization/HybridRow.Tests.Perf/BsonRowGenerator.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/CassandraProto/CassandraHotelSchema.cs b/src/Serialization/HybridRow.Tests.Perf/CassandraProto/CassandraHotelSchema.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/CassandraProto/CassandraHotelSchema.cs rename to src/Serialization/HybridRow.Tests.Perf/CassandraProto/CassandraHotelSchema.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/CodeGenMicroBenchmarkSuite.cs b/src/Serialization/HybridRow.Tests.Perf/CodeGenMicroBenchmarkSuite.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/CodeGenMicroBenchmarkSuite.cs rename to src/Serialization/HybridRow.Tests.Perf/CodeGenMicroBenchmarkSuite.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/CodeGenRowGenerator.cs b/src/Serialization/HybridRow.Tests.Perf/CodeGenRowGenerator.cs similarity index 98% rename from dotnet/src/HybridRow.Tests.Perf/CodeGenRowGenerator.cs rename to src/Serialization/HybridRow.Tests.Perf/CodeGenRowGenerator.cs index 40923ad..3698d1e 100644 --- a/dotnet/src/HybridRow.Tests.Perf/CodeGenRowGenerator.cs +++ b/src/Serialization/HybridRow.Tests.Perf/CodeGenRowGenerator.cs @@ -2,6 +2,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ +#pragma warning disable IDE0052 // Remove unread private members + namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf { using System; @@ -245,18 +247,18 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf ref root, this.addresses.TypeArgs, (this, (List)value), - (ref RowBuffer row2, ref RowCursor childScope, (GuestsHybridRowSerializer _this, List value) ctx) => + (ref RowBuffer row2, ref RowCursor childScope, (GuestsHybridRowSerializer This, List Value) ctx) => { - foreach (object item in ctx.value) + foreach (object item in ctx.Value) { Result r2 = LayoutType.TypedTuple.WriteScope( ref row2, ref childScope, - ctx._this.addresses.TypeArgs, - (ctx._this, (List)item), - (ref RowBuffer row3, ref RowCursor tupleScope, (GuestsHybridRowSerializer _this, List value) ctx2) => + ctx.This.addresses.TypeArgs, + (ctx.This, (List)item), + (ref RowBuffer row3, ref RowCursor tupleScope, (GuestsHybridRowSerializer This, List Value) ctx2) => { - Result r3 = LayoutType.Utf8.WriteSparse(ref row3, ref tupleScope, (Utf8String)ctx2.value[0]); + Result r3 = LayoutType.Utf8.WriteSparse(ref row3, ref tupleScope, (Utf8String)ctx2.Value[0]); if (r3 != Result.Success) { return r3; @@ -266,9 +268,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf return LayoutType.ImmutableUDT.WriteScope( ref row3, ref tupleScope, - ctx2._this.addresses.TypeArgs[1].TypeArgs, - (Dictionary)ctx2.value[1], - ctx2._this.addressSerializerWriter); + ctx2.This.addresses.TypeArgs[1].TypeArgs, + (Dictionary)ctx2.Value[1], + ctx2.This.addressSerializerWriter); }); if (r2 != Result.Success) { diff --git a/dotnet/src/HybridRow.Tests.Perf/GenerateBenchmarkSuite.cs b/src/Serialization/HybridRow.Tests.Perf/GenerateBenchmarkSuite.cs similarity index 91% rename from dotnet/src/HybridRow.Tests.Perf/GenerateBenchmarkSuite.cs rename to src/Serialization/HybridRow.Tests.Perf/GenerateBenchmarkSuite.cs index fb34dd7..b433c2f 100644 --- a/dotnet/src/HybridRow.Tests.Perf/GenerateBenchmarkSuite.cs +++ b/src/Serialization/HybridRow.Tests.Perf/GenerateBenchmarkSuite.cs @@ -18,14 +18,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf [DeploymentItem(TestData.SchemaFile, TestData.Target)] public sealed class GenerateBenchmarkSuite : BenchmarkSuiteBase { - private string sdl; + private Namespace schemas; [TestInitialize] - public void ParseNamespaceExample() + public void TestInitialize() { - this.sdl = File.ReadAllText(TestData.SchemaFile); - Namespace schema = Namespace.Parse(this.sdl); - this.DefaultResolver = new LayoutResolverNamespace(schema, SystemSchema.LayoutResolver); + this.schemas = TestData.LoadFromHrSchema(TestData.SchemaFile); + this.DefaultResolver = new LayoutResolverNamespace(this.schemas, SystemSchema.LayoutResolver); } [TestMethod] @@ -91,7 +90,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf if (!allMatch) { - await BenchmarkSuiteBase.WriteAllRowsAsync(expectedFile, this.sdl, resolver, resolver.Resolve(tableSchema.SchemaId), rows); + await BenchmarkSuiteBase.WriteAllRowsAsync(expectedFile, this.schemas, 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."); } diff --git a/dotnet/src/HybridRow.Tests.Perf/GenerateProtoBuf.cmd b/src/Serialization/HybridRow.Tests.Perf/GenerateProtoBuf.cmd similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/GenerateProtoBuf.cmd rename to src/Serialization/HybridRow.Tests.Perf/GenerateProtoBuf.cmd diff --git a/dotnet/src/HybridRow.Tests.Perf/HybridRowPerf.csv b/src/Serialization/HybridRow.Tests.Perf/HybridRowPerf.csv similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/HybridRowPerf.csv rename to src/Serialization/HybridRow.Tests.Perf/HybridRowPerf.csv diff --git a/dotnet/src/HybridRow.Tests.Perf/JsonModelRowGenerator.cs b/src/Serialization/HybridRow.Tests.Perf/JsonModelRowGenerator.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/JsonModelRowGenerator.cs rename to src/Serialization/HybridRow.Tests.Perf/JsonModelRowGenerator.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/Measurements.cs b/src/Serialization/HybridRow.Tests.Perf/Measurements.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/Measurements.cs rename to src/Serialization/HybridRow.Tests.Perf/Measurements.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/MicroBenchmarkSuiteBase.cs b/src/Serialization/HybridRow.Tests.Perf/MicroBenchmarkSuiteBase.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/MicroBenchmarkSuiteBase.cs rename to src/Serialization/HybridRow.Tests.Perf/MicroBenchmarkSuiteBase.cs diff --git a/src/Serialization/HybridRow.Tests.Perf/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.csproj b/src/Serialization/HybridRow.Tests.Perf/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.csproj new file mode 100644 index 0000000..2614fa0 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Perf/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf.csproj @@ -0,0 +1,41 @@ + + + + true + true + Library + Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf + Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf + netcoreapp3.1 + AnyCPU + TestData + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/dotnet/src/HybridRow.Tests.Perf/ProtobufRowGenerator.cs b/src/Serialization/HybridRow.Tests.Perf/ProtobufRowGenerator.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/ProtobufRowGenerator.cs rename to src/Serialization/HybridRow.Tests.Perf/ProtobufRowGenerator.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/ReaderBenchmark.cs b/src/Serialization/HybridRow.Tests.Perf/ReaderBenchmark.cs similarity index 90% rename from dotnet/src/HybridRow.Tests.Perf/ReaderBenchmark.cs rename to src/Serialization/HybridRow.Tests.Perf/ReaderBenchmark.cs index 108a82e..2711ea2 100644 --- a/dotnet/src/HybridRow.Tests.Perf/ReaderBenchmark.cs +++ b/src/Serialization/HybridRow.Tests.Perf/ReaderBenchmark.cs @@ -9,12 +9,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; + using System.Runtime.InteropServices; 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.Azure.Cosmos.Serialization.HybridRow.Schemas; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] @@ -70,16 +72,23 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf BenchmarkContext context = (BenchmarkContext)ctx; MemorySpanResizer resizer = new MemorySpanResizer(ReaderBenchmark.InitialCapacity); Result r = await context.Input.ReadRecordIOAsync( - record => + (ReadOnlyMemory record) => { context.IncrementRecordCount(); r = ReaderBenchmark.VisitOneRow(record, context.Resolver); Assert.AreEqual(Result.Success, r); return Result.Success; }, - segment => + (ReadOnlyMemory segment) => { - r = SegmentSerializer.Read(segment.Span, context.Resolver, out Segment _); + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + RowBuffer row = new RowBuffer(MemoryMarshal.AsMemory(segment).Span, + HybridRowVersion.V1, SchemasHrSchema.LayoutResolver); + RowCursor root = RowCursor.Create(ref row); + r = default(SegmentHybridRowSerializer).Read(ref row, ref root, true, out Segment _); Assert.AreEqual(Result.Success, r); // TODO: do something with embedded schema. @@ -90,10 +99,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf Assert.AreEqual(Result.Success, r); } - private static Result VisitOneRow(Memory buffer, LayoutResolver resolver) + private static Result VisitOneRow(ReadOnlyMemory buffer, LayoutResolver resolver) { - RowBuffer row = new RowBuffer(buffer.Span, HybridRowVersion.V1, resolver); - RowReader reader = new RowReader(ref row); + RowReader reader = new RowReader(buffer, HybridRowVersion.V1, resolver); return reader.VisitReader(); } diff --git a/dotnet/src/HybridRow.Tests.Perf/RowReaderExtensions.cs b/src/Serialization/HybridRow.Tests.Perf/RowReaderExtensions.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/RowReaderExtensions.cs rename to src/Serialization/HybridRow.Tests.Perf/RowReaderExtensions.cs diff --git a/dotnet/src/HybridRow.Tests.Perf/SchematizedMicroBenchmarkSuite.cs b/src/Serialization/HybridRow.Tests.Perf/SchematizedMicroBenchmarkSuite.cs similarity index 99% rename from dotnet/src/HybridRow.Tests.Perf/SchematizedMicroBenchmarkSuite.cs rename to src/Serialization/HybridRow.Tests.Perf/SchematizedMicroBenchmarkSuite.cs index e372b28..b64e911 100644 --- a/dotnet/src/HybridRow.Tests.Perf/SchematizedMicroBenchmarkSuite.cs +++ b/src/Serialization/HybridRow.Tests.Perf/SchematizedMicroBenchmarkSuite.cs @@ -29,13 +29,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf Formatting = Formatting.Indented }; - private string sdl; - [TestInitialize] public void ParseNamespaceExample() { - this.sdl = File.ReadAllText(TestData.SchemaFile); - Namespace schema = Namespace.Parse(this.sdl); + Namespace schema = TestData.LoadFromHrSchema(TestData.SchemaFile); this.DefaultResolver = new LayoutResolverNamespace(schema, SystemSchema.LayoutResolver); } diff --git a/dotnet/src/HybridRow.Tests.Perf/TestData.cs b/src/Serialization/HybridRow.Tests.Perf/TestData.cs similarity index 54% rename from dotnet/src/HybridRow.Tests.Perf/TestData.cs rename to src/Serialization/HybridRow.Tests.Perf/TestData.cs index 9e65909..b72edcc 100644 --- a/dotnet/src/HybridRow.Tests.Perf/TestData.cs +++ b/src/Serialization/HybridRow.Tests.Perf/TestData.cs @@ -3,6 +3,11 @@ // ------------------------------------------------------------ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf { + using System.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + using Microsoft.VisualStudio.TestTools.UnitTesting; + /// /// Names of assets in the TestData folder. /// @@ -13,10 +18,24 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf /// public const string Target = "TestData"; - public const string SchemaFile = @"TestData\CassandraHotelSchema.json"; + public const string SchemaFile = @"TestData\CassandraHotelSchema.hrschema"; 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"; + + private const int InitialCapacity = 2 * 1024 * 1024; + + public static Namespace LoadFromHrSchema(string filename) + { + using (Stream stm = new FileStream(filename, FileMode.Open)) + { + RowBuffer row = new RowBuffer(InitialCapacity); + row.ReadFrom(stm, (int)stm.Length, HybridRowVersion.V1, SystemSchema.LayoutResolver); + Result r = Namespace.Read(ref row, out Namespace ns); + Assert.AreEqual(Result.Success, r); + return ns; + } + } } } diff --git a/dotnet/src/HybridRow.Tests.Perf/TestData/CassandraHotelSchema.json b/src/Serialization/HybridRow.Tests.Perf/TestData/CassandraHotelSchema.json similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/TestData/CassandraHotelSchema.json rename to src/Serialization/HybridRow.Tests.Perf/TestData/CassandraHotelSchema.json diff --git a/dotnet/src/HybridRow.Tests.Perf/TestData/CassandraHotelSchema.proto b/src/Serialization/HybridRow.Tests.Perf/TestData/CassandraHotelSchema.proto similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/TestData/CassandraHotelSchema.proto rename to src/Serialization/HybridRow.Tests.Perf/TestData/CassandraHotelSchema.proto diff --git a/dotnet/src/HybridRow.Tests.Perf/TestData/CombinedScriptsData.hr b/src/Serialization/HybridRow.Tests.Perf/TestData/CombinedScriptsData.hr similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/TestData/CombinedScriptsData.hr rename to src/Serialization/HybridRow.Tests.Perf/TestData/CombinedScriptsData.hr diff --git a/dotnet/src/HybridRow.Tests.Perf/TestData/GuestsSchemaExpected.hr b/src/Serialization/HybridRow.Tests.Perf/TestData/GuestsSchemaExpected.hr similarity index 94% rename from dotnet/src/HybridRow.Tests.Perf/TestData/GuestsSchemaExpected.hr rename to src/Serialization/HybridRow.Tests.Perf/TestData/GuestsSchemaExpected.hr index a275dea..ee154ce 100644 Binary files a/dotnet/src/HybridRow.Tests.Perf/TestData/GuestsSchemaExpected.hr and b/src/Serialization/HybridRow.Tests.Perf/TestData/GuestsSchemaExpected.hr differ diff --git a/dotnet/src/HybridRow.Tests.Perf/TestData/HotelSchemaExpected.hr b/src/Serialization/HybridRow.Tests.Perf/TestData/HotelSchemaExpected.hr similarity index 82% rename from dotnet/src/HybridRow.Tests.Perf/TestData/HotelSchemaExpected.hr rename to src/Serialization/HybridRow.Tests.Perf/TestData/HotelSchemaExpected.hr index 2635ecd..61db252 100644 Binary files a/dotnet/src/HybridRow.Tests.Perf/TestData/HotelSchemaExpected.hr and b/src/Serialization/HybridRow.Tests.Perf/TestData/HotelSchemaExpected.hr differ diff --git a/dotnet/src/HybridRow.Tests.Perf/TestData/Messages1KExpected.hr b/src/Serialization/HybridRow.Tests.Perf/TestData/Messages1KExpected.hr similarity index 99% rename from dotnet/src/HybridRow.Tests.Perf/TestData/Messages1KExpected.hr rename to src/Serialization/HybridRow.Tests.Perf/TestData/Messages1KExpected.hr index d37d502..dc44274 100644 Binary files a/dotnet/src/HybridRow.Tests.Perf/TestData/Messages1KExpected.hr and b/src/Serialization/HybridRow.Tests.Perf/TestData/Messages1KExpected.hr differ diff --git a/src/Serialization/HybridRow.Tests.Perf/TestData/RoomsSchemaExpected.hr b/src/Serialization/HybridRow.Tests.Perf/TestData/RoomsSchemaExpected.hr new file mode 100644 index 0000000..e9c7a59 Binary files /dev/null and b/src/Serialization/HybridRow.Tests.Perf/TestData/RoomsSchemaExpected.hr differ diff --git a/dotnet/src/HybridRow.Tests.Perf/UnschematizedMicroBenchmarkSuite.cs b/src/Serialization/HybridRow.Tests.Perf/UnschematizedMicroBenchmarkSuite.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Perf/UnschematizedMicroBenchmarkSuite.cs rename to src/Serialization/HybridRow.Tests.Perf/UnschematizedMicroBenchmarkSuite.cs diff --git a/test-data/BatchApiSchema.json b/src/Serialization/HybridRow.Tests.Schema/BatchApiSchema.json similarity index 100% rename from test-data/BatchApiSchema.json rename to src/Serialization/HybridRow.Tests.Schema/BatchApiSchema.json diff --git a/test-data/CoverageSchema.json b/src/Serialization/HybridRow.Tests.Schema/CoverageSchema.json similarity index 100% rename from test-data/CoverageSchema.json rename to src/Serialization/HybridRow.Tests.Schema/CoverageSchema.json diff --git a/test-data/CrossVersioningSchema.json b/src/Serialization/HybridRow.Tests.Schema/CrossVersioningSchema.json similarity index 100% rename from test-data/CrossVersioningSchema.json rename to src/Serialization/HybridRow.Tests.Schema/CrossVersioningSchema.json diff --git a/test-data/CustomerSchema.json b/src/Serialization/HybridRow.Tests.Schema/CustomerSchema.json similarity index 68% rename from test-data/CustomerSchema.json rename to src/Serialization/HybridRow.Tests.Schema/CustomerSchema.json index 1777ca2..fc20af6 100644 --- a/test-data/CustomerSchema.json +++ b/src/Serialization/HybridRow.Tests.Schema/CustomerSchema.json @@ -20,7 +20,7 @@ { "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" } } + { "path": "postal_code", "apiname": "PostalCode", "type": { "type": "schema", "name": "PostalCode" } } ] }, { @@ -29,7 +29,7 @@ "type": "schema", "partitionkeys": [{ "path": "hotel_id" }], "properties": [ - { "path": "hotel_id", "type": { "type": "utf8", "storage": "variable" } }, + { "path": "hotel_id", "apiname": "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 } } @@ -42,10 +42,10 @@ "partitionkeys": [{ "path": "hotel_id" }], "primarykeys": [{ "path": "date" }, { "path": "room_number", "direction": "desc" }], "properties": [ - { "path": "hotel_id", "type": { "type": "utf8", "storage": "variable" } }, + { "path": "hotel_id", "apiname": "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" } } + { "path": "room_number", "apiname": "RoomNumber", "type": { "type": "uint8", "storage": "fixed" } }, + { "path": "is_available", "apiname": "IsAvailable", "type": { "type": "bool" } } ] }, { @@ -55,12 +55,12 @@ "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": "guest_id", "apiname": "Id", "type": { "type": "guid", "storage": "fixed" } }, + { "path": "first_name", "apiname": "FirstName", "type": { "type": "utf8", "storage": "variable" } }, + { "path": "last_name", "apiname": "LastName", "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": "phone_numbers", "apiname": "PhoneNumbers", "type": { "type": "array", "items": { "type": "utf8", "nullable": false } } }, { "path": "addresses", "type": { @@ -69,7 +69,7 @@ "values": { "type": "schema", "name": "Address", "immutable": true, "nullable": false } } }, - { "path": "confirm_number", "type": { "type": "utf8", "storage": "variable" } } + { "path": "confirm_number", "apiname": "ConfirmNumber", "type": { "type": "utf8", "storage": "variable" } } ] } ] diff --git a/src/Serialization/HybridRow.Tests.Schema/HybridRow.Tests.Schema.msbuildproj b/src/Serialization/HybridRow.Tests.Schema/HybridRow.Tests.Schema.msbuildproj new file mode 100644 index 0000000..30152d9 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Schema/HybridRow.Tests.Schema.msbuildproj @@ -0,0 +1,10 @@ + + + netstandard2.0 + + + + + + + \ No newline at end of file diff --git a/src/Serialization/HybridRow.Tests.Schema/HybridRow.Tests.Schema.props b/src/Serialization/HybridRow.Tests.Schema/HybridRow.Tests.Schema.props new file mode 100644 index 0000000..4c44e80 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Schema/HybridRow.Tests.Schema.props @@ -0,0 +1,9 @@ + + + + + + diff --git a/test-data/MovieSchema.json b/src/Serialization/HybridRow.Tests.Schema/MovieSchema.json similarity index 100% rename from test-data/MovieSchema.json rename to src/Serialization/HybridRow.Tests.Schema/MovieSchema.json diff --git a/test-data/NullableSchema.json b/src/Serialization/HybridRow.Tests.Schema/NullableSchema.json similarity index 100% rename from test-data/NullableSchema.json rename to src/Serialization/HybridRow.Tests.Schema/NullableSchema.json diff --git a/test-data/PerfCounterSchema.json b/src/Serialization/HybridRow.Tests.Schema/PerfCounterSchema.json similarity index 86% rename from test-data/PerfCounterSchema.json rename to src/Serialization/HybridRow.Tests.Schema/PerfCounterSchema.json index 8bb24d7..897a27e 100644 --- a/test-data/PerfCounterSchema.json +++ b/src/Serialization/HybridRow.Tests.Schema/PerfCounterSchema.json @@ -1,5 +1,8 @@ // Performance Counter demo schema that utilizes tuples. { + "name": "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedTuple", + "cppNamespace": "cdb_hr_test::tuple", + "version": "v2", "schemas": [ { "name": "Coord", @@ -11,7 +14,7 @@ ] }, { - "name": "Counters", + "name": "PerfCounter", "id": 1, "type": "schema", "partitionkeys": [{ "path": "name" }], @@ -27,6 +30,7 @@ }, { "path": "minmeanmax", + "apiname": "MinMaxValue", "type": { "type": "tuple", "immutable": true, @@ -63,7 +67,7 @@ "path": "history", "type": { "type": "array", - "items": { "type": "schema", "name": "Counters", "nullable": false } + "items": { "type": "schema", "name": "PerfCounter", "nullable": false } } } ] diff --git a/test-data/ReaderSchema.json b/src/Serialization/HybridRow.Tests.Schema/ReaderSchema.json similarity index 100% rename from test-data/ReaderSchema.json rename to src/Serialization/HybridRow.Tests.Schema/ReaderSchema.json diff --git a/test-data/SchemaHashCoverageSchema.json b/src/Serialization/HybridRow.Tests.Schema/SchemaHashCoverageSchema.json similarity index 92% rename from test-data/SchemaHashCoverageSchema.json rename to src/Serialization/HybridRow.Tests.Schema/SchemaHashCoverageSchema.json index 37b423b..ee043a8 100644 --- a/test-data/SchemaHashCoverageSchema.json +++ b/src/Serialization/HybridRow.Tests.Schema/SchemaHashCoverageSchema.json @@ -1,5 +1,20 @@ { "name": "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.SchemaHashTest", + "version": "v2", + "enums": [ + { + "name": "MyEnum", + "comment": "Versions of the HybridRow Schema Description Language.", + "type": "uint8", + "values": [ + { + "name": "V1", + "comment": "Some value.", + "value": 42 + } + ] + } + ], "schemas": [ { "version": "v1", diff --git a/test-data/TagSchema.json b/src/Serialization/HybridRow.Tests.Schema/TagSchema.json similarity index 88% rename from test-data/TagSchema.json rename to src/Serialization/HybridRow.Tests.Schema/TagSchema.json index fcc1b07..ee921ae 100644 --- a/test-data/TagSchema.json +++ b/src/Serialization/HybridRow.Tests.Schema/TagSchema.json @@ -1,6 +1,9 @@ // Tag demo schema that utilizes typed arrays. { - "schemas": [ + "name": "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedArray", + "cppNamespace": "cdb_hr_test::typed_array", + "version": "v2", + "schemas": [ { "name": "Tagged", "id": 1, "type": "schema", "properties": [ { "path": "title", "type": { "type": "utf8", "storage": "variable" } }, diff --git a/test-data/TaggedApiSchema.json b/src/Serialization/HybridRow.Tests.Schema/TaggedApiSchema.json similarity index 100% rename from test-data/TaggedApiSchema.json rename to src/Serialization/HybridRow.Tests.Schema/TaggedApiSchema.json diff --git a/test-data/TodoSchema.json b/src/Serialization/HybridRow.Tests.Schema/TodoSchema.json similarity index 100% rename from test-data/TodoSchema.json rename to src/Serialization/HybridRow.Tests.Schema/TodoSchema.json diff --git a/dotnet/src/HybridRow.Tests.Unit/ArrayAssert.cs b/src/Serialization/HybridRow.Tests.Unit/ArrayAssert.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/ArrayAssert.cs rename to src/Serialization/HybridRow.Tests.Unit/ArrayAssert.cs diff --git a/dotnet/src/HybridRow.Tests.Unit/AssertThrowsException.cs b/src/Serialization/HybridRow.Tests.Unit/AssertThrowsException.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/AssertThrowsException.cs rename to src/Serialization/HybridRow.Tests.Unit/AssertThrowsException.cs diff --git a/dotnet/src/HybridRow.Tests.Unit/CrossVersioningUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/CrossVersioningUnitTests.cs similarity index 99% rename from dotnet/src/HybridRow.Tests.Unit/CrossVersioningUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/CrossVersioningUnitTests.cs index 8375643..8b8f51e 100644 --- a/dotnet/src/HybridRow.Tests.Unit/CrossVersioningUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/CrossVersioningUnitTests.cs @@ -20,7 +20,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [DeploymentItem(CrossVersioningUnitTests.ExpectedFile, "TestData")] public sealed class CrossVersioningUnitTests { - private const string SchemaFile = @"TestData\CrossVersioningSchema.json"; + private const string SchemaFile = @"TestData\CrossVersioningSchema.hrschema"; 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}"); @@ -35,9 +35,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestInitialize] public void ParseNamespace() { - string json = File.ReadAllText(CrossVersioningUnitTests.SchemaFile); - this.schema = Namespace.Parse(json); - json = File.ReadAllText(CrossVersioningUnitTests.ExpectedFile); + this.schema = SchemaUtil.LoadFromHrSchema(CrossVersioningUnitTests.SchemaFile); + string json = File.ReadAllText(CrossVersioningUnitTests.ExpectedFile); this.expected = JsonConvert.DeserializeObject(json); this.resolver = new LayoutResolverNamespace(this.schema); } diff --git a/dotnet/src/HybridRow.Tests.Unit/CustomerExampleUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/CustomerExampleUnitTests.cs similarity index 82% rename from dotnet/src/HybridRow.Tests.Unit/CustomerExampleUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/CustomerExampleUnitTests.cs index f18ced6..59d1af3 100644 --- a/dotnet/src/HybridRow.Tests.Unit/CustomerExampleUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/CustomerExampleUnitTests.cs @@ -6,8 +6,6 @@ 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; @@ -16,11 +14,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit // 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() + private readonly Hotels hotelExample = new Hotels { Id = "The-Westin-St-John-Resort-Villas-1187", Name = "The Westin St. John Resort Villas", @@ -38,19 +34,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit }, }; - 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); + this.hotelLayout = CustomerSchemaHrSchema.LayoutResolver.Resolve((SchemaId)HotelsHybridRowSerializer.SchemaId); + this.guestLayout = CustomerSchemaHrSchema.LayoutResolver.Resolve((SchemaId)GuestsHybridRowSerializer.SchemaId); } [TestMethod] @@ -58,16 +49,16 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void CreateHotel() { RowBuffer row = new RowBuffer(0); - row.InitLayout(HybridRowVersion.V1, this.hotelLayout, this.customerResolver); + row.InitLayout(HybridRowVersion.V1, this.hotelLayout, CustomerSchemaHrSchema.LayoutResolver); - Hotel h1 = this.hotelExample; + Hotels 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); + Hotels h2 = this.ReadHotel(ref row, ref root); - Assert.AreEqual(h1, h2); + Assert.IsTrue(default(HotelsHybridRowSerializer).Comparer.Equals(h1, h2)); } [TestMethod] @@ -75,14 +66,18 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void FrozenHotel() { RowBuffer row = new RowBuffer(0); - row.InitLayout(HybridRowVersion.V1, this.hotelLayout, this.customerResolver); + row.InitLayout(HybridRowVersion.V1, this.hotelLayout, CustomerSchemaHrSchema.LayoutResolver); - Hotel h1 = this.hotelExample; + Hotels 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" })); + ResultAssert.InsufficientPermissions( + this.PartialUpdateHotelAddress( + ref row, + ref root, + new Address { Street = "300B Brownie Way" })); } [TestMethod] @@ -90,9 +85,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void CreateGuest() { RowBuffer row = new RowBuffer(1024 * 1024); - row.InitLayout(HybridRowVersion.V1, this.guestLayout, this.customerResolver); + row.InitLayout(HybridRowVersion.V1, this.guestLayout, CustomerSchemaHrSchema.LayoutResolver); - Guest g1 = new Guest() + Guests g1 = new Guests { Id = Guid.Parse("64d9d6d3-fd6b-4556-8c6e-d960a7ece7b9"), FirstName = "John", @@ -100,8 +95,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Title = "President of the United States", PhoneNumbers = new List { "(202) 456-1111" }, ConfirmNumber = "(202) 456-1111", - Emails = new SortedSet { "president@whitehouse.gov" }, - Addresses = new Dictionary + Emails = new List { "president@whitehouse.gov" }, + Addresses = new Dictionary(default(Utf8HybridRowSerializer).Comparer) { ["home"] = new Address { @@ -120,8 +115,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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); + Guests g2 = this.ReadGuest(ref row, ref rc2); + Assert.IsTrue(default(GuestsHybridRowSerializer).Comparer.Equals(g1, g2)); // Append an item to an existing list. RowCursor rc3 = RowCursor.Create(ref row); @@ -130,22 +125,27 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit g1.Emails.Add("vice_president@whitehouse.gov"); RowCursor rc4 = RowCursor.Create(ref row); g2 = this.ReadGuest(ref row, ref rc4); - Assert.AreEqual(g1, g2); + Assert.IsTrue(default(GuestsHybridRowSerializer).Comparer.Equals(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 { "ex_president@whitehouse.gov", "president@whitehouse.gov", "vice_president@whitehouse.gov" }; + g1.Emails = new List + { + "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); + Assert.IsTrue(default(GuestsHybridRowSerializer).Comparer.Equals(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 + g1.Emails = new List { "ex_president@whitehouse.gov", "future_president@whitehouse.gov", @@ -155,7 +155,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit RowCursor rc8 = RowCursor.Create(ref row); g2 = this.ReadGuest(ref row, ref rc8); - Assert.AreEqual(g1, g2); + Assert.IsTrue(default(GuestsHybridRowSerializer).Comparer.Equals(g1, g2)); } private static Address ReadAddress(ref RowBuffer row, ref RowCursor addressScope) @@ -163,11 +163,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Address a = new Address(); Layout addressLayout = addressScope.Layout; Assert.IsTrue(addressLayout.TryFind("street", out LayoutColumn c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref addressScope, c, out a.Street)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref addressScope, c, out string value)); + a.Street = value; Assert.IsTrue(addressLayout.TryFind("city", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref addressScope, c, out a.City)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref addressScope, c, out value)); + a.City = value; Assert.IsTrue(addressLayout.TryFind("state", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref addressScope, c, out a.State)); + ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref addressScope, c, out value)); + a.State = value; Assert.IsTrue(addressLayout.TryFind("postal_code", out c)); addressScope.Find(ref row, c.Path); @@ -182,7 +185,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Layout postalCodeLayout = postalCodeScope.Layout; PostalCode pc = new PostalCode(); Assert.IsTrue(postalCodeLayout.TryFind("zip", out LayoutColumn c)); - ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref postalCodeScope, c, out pc.Zip)); + ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref postalCodeScope, c, out int value)); + pc.Zip = value; Assert.IsTrue(postalCodeLayout.TryFind("plus4", out c)); postalCodeScope.Find(ref row, c.Path); @@ -194,7 +198,38 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit return pc; } - private void WriteHotel(ref RowBuffer row, ref RowCursor root, Hotel h) + private static void WriteAddress(ref RowBuffer row, ref RowCursor addressScope, TypeArgumentList typeArgs, Address a) + { + Layout addressLayout = CustomerSchemaHrSchema.LayoutResolver.Resolve(typeArgs.SchemaId); + Assert.IsTrue(addressLayout.TryFind("street", out LayoutColumn c)); + ResultAssert.IsSuccess(c.TypeAs().WriteVariable(ref row, ref addressScope, c, a.Street)); + Assert.IsTrue(addressLayout.TryFind("city", out c)); + ResultAssert.IsSuccess(c.TypeAs().WriteVariable(ref row, ref addressScope, c, a.City)); + Assert.IsTrue(addressLayout.TryFind("state", out c)); + ResultAssert.IsSuccess(c.TypeAs().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().WriteScope(ref row, ref addressScope, c.TypeArgs, out RowCursor postalCodeScope)); + CustomerExampleUnitTests.WritePostalCode(ref row, ref postalCodeScope, c.TypeArgs, a.PostalCode); + addressScope.Skip(ref row, ref postalCodeScope); + } + + private static void WritePostalCode(ref RowBuffer row, ref RowCursor postalCodeScope, TypeArgumentList typeArgs, PostalCode pc) + { + Layout postalCodeLayout = CustomerSchemaHrSchema.LayoutResolver.Resolve(typeArgs.SchemaId); + Assert.IsNotNull(postalCodeLayout); + Assert.IsTrue(postalCodeLayout.TryFind("zip", out LayoutColumn c)); + ResultAssert.IsSuccess(c.TypeAs().WriteFixed(ref row, ref postalCodeScope, c, pc.Zip)); + if (pc.Plus4 != default) + { + Assert.IsTrue(postalCodeLayout.TryFind("plus4", out c)); + postalCodeScope.Find(ref row, c.Path); + ResultAssert.IsSuccess(c.TypeAs().WriteSparse(ref row, ref postalCodeScope, pc.Plus4)); + } + } + + private void WriteHotel(ref RowBuffer row, ref RowCursor root, Hotels h) { Assert.IsTrue(this.hotelLayout.TryFind("hotel_id", out LayoutColumn c)); ResultAssert.IsSuccess(c.TypeAs().WriteVariable(ref row, ref root, c, h.Id)); @@ -206,11 +241,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.IsTrue(this.hotelLayout.TryFind("address", out c)); root.Find(ref row, c.Path); ResultAssert.IsSuccess(c.TypeAs().WriteScope(ref row, ref root, c.TypeArgs, out RowCursor addressScope)); - this.WriteAddress(ref row, ref addressScope, c.TypeArgs, h.Address); + CustomerExampleUnitTests.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) + private void WriteGuest(ref RowBuffer row, ref RowCursor root, Guests g) { Assert.IsTrue(this.guestLayout.TryFind("guest_id", out LayoutColumn c)); ResultAssert.IsSuccess(c.TypeAs().WriteFixed(ref row, ref root, c, g.Id)); @@ -269,7 +304,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.IsTrue(tupleScope.MoveNext(ref row)); ResultAssert.IsSuccess( t1.TypeAs().WriteScope(ref row, ref tupleScope, t1.TypeArgs, out RowCursor addressScope)); - this.WriteAddress(ref row, ref addressScope, t1.TypeArgs, pair.Value); + CustomerExampleUnitTests.WriteAddress(ref row, ref addressScope, t1.TypeArgs, pair.Value); Assert.IsFalse(tupleScope.MoveNext(ref row, ref addressScope)); ResultAssert.IsSuccess(c.TypeAs().MoveField(ref row, ref addressesScope, ref tempCursor)); } @@ -307,23 +342,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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().WriteVariable(ref row, ref addressScope, c, a.Street)); - Assert.IsTrue(addressLayout.TryFind("city", out c)); - ResultAssert.IsSuccess(c.TypeAs().WriteVariable(ref row, ref addressScope, c, a.City)); - Assert.IsTrue(addressLayout.TryFind("state", out c)); - ResultAssert.IsSuccess(c.TypeAs().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().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)); @@ -375,35 +393,24 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit return r; } - this.WritePostalCode(ref row, ref postalCodeScope, c.TypeArgs, a.PostalCode); + CustomerExampleUnitTests.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) + private Hotels ReadHotel(ref RowBuffer row, ref RowCursor root) { - Layout postalCodeLayout = this.customerResolver.Resolve(typeArgs.SchemaId); - Assert.IsNotNull(postalCodeLayout); - Assert.IsTrue(postalCodeLayout.TryFind("zip", out LayoutColumn c)); - ResultAssert.IsSuccess(c.TypeAs().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().WriteSparse(ref row, ref postalCodeScope, pc.Plus4.Value)); - } - } - - private Hotel ReadHotel(ref RowBuffer row, ref RowCursor root) - { - Hotel h = new Hotel(); + Hotels h = new Hotels(); Assert.IsTrue(this.hotelLayout.TryFind("hotel_id", out LayoutColumn c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out h.Id)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out string value)); + h.Id = value; Assert.IsTrue(this.hotelLayout.TryFind("name", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out h.Name)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out value)); + h.Name = value; Assert.IsTrue(this.hotelLayout.TryFind("phone", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out h.Phone)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out value)); + h.Phone = value; Assert.IsTrue(this.hotelLayout.TryFind("address", out c)); Assert.IsTrue(c.Type.Immutable); @@ -415,25 +422,30 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit return h; } - private Guest ReadGuest(ref RowBuffer row, ref RowCursor root) + private Guests ReadGuest(ref RowBuffer row, ref RowCursor root) { - Guest g = new Guest(); + Guests g = new Guests(); Assert.IsTrue(this.guestLayout.TryFind("guest_id", out LayoutColumn c)); - ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref root, c, out g.Id)); + ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref root, c, out Guid value)); + g.Id = value; Assert.IsTrue(this.guestLayout.TryFind("first_name", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out g.FirstName)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out string value2)); + g.FirstName = value2; Assert.IsTrue(this.guestLayout.TryFind("last_name", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out g.LastName)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out value2)); + g.LastName = value2; Assert.IsTrue(this.guestLayout.TryFind("title", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out g.Title)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out value2)); + g.Title = value2; Assert.IsTrue(this.guestLayout.TryFind("confirm_number", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out g.ConfirmNumber)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out value2)); + g.ConfirmNumber = value2; Assert.IsTrue(this.guestLayout.TryFind("emails", out c)); root.Clone(out RowCursor emailScope).Find(ref row, c.Path); if (c.TypeAs().ReadScope(ref row, ref emailScope, out emailScope) == Result.Success) { - g.Emails = new SortedSet(); + g.Emails = new List(); while (emailScope.MoveNext(ref row)) { ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs().ReadSparse(ref row, ref emailScope, out string item)); @@ -460,7 +472,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit TypeArgument tupleType = LayoutType.TypedMap.FieldType(ref addressesScope); TypeArgument t0 = tupleType.TypeArgs[0]; TypeArgument t1 = tupleType.TypeArgs[1]; - g.Addresses = new Dictionary(); + g.Addresses = new Dictionary(default(Utf8HybridRowSerializer).Comparer); RowCursor pairScope = default; while (addressesScope.MoveNext(ref row, ref pairScope)) { @@ -469,8 +481,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit ResultAssert.IsSuccess(t0.TypeAs().ReadSparse(ref row, ref pairScope, out string key)); Assert.IsTrue(pairScope.MoveNext(ref row)); ResultAssert.IsSuccess(t1.TypeAs().ReadScope(ref row, ref pairScope, out RowCursor addressScope)); - Address value = CustomerExampleUnitTests.ReadAddress(ref row, ref addressScope); - g.Addresses.Add(key, value); + Address value3 = CustomerExampleUnitTests.ReadAddress(ref row, ref addressScope); + g.Addresses.Add(key, value3); Assert.IsFalse(pairScope.MoveNext(ref row, ref addressScope)); } } diff --git a/src/Serialization/HybridRow.Tests.Unit/Generated/CustomerSchema.cs b/src/Serialization/HybridRow.Tests.Unit/Generated/CustomerSchema.cs new file mode 100644 index 0000000..ee0d715 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/Generated/CustomerSchema.cs @@ -0,0 +1,1619 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// This file was generated by: +// Microsoft.Azure.Cosmos.Serialization.HybridRowCLI: 1.0.0.0 +// +// This file should not be modified directly. +// ------------------------------------------------------------ + +#pragma warning disable NamespaceMatchesFolderStructure // Namespace Declarations must match folder structure. +#pragma warning disable CA1707 // Identifiers should not contain underscores. +#pragma warning disable CA1034 // Do not nest types. +#pragma warning disable CA2104 // Do not declare readonly mutable reference types. +#pragma warning disable SA1129 // Do not use default value type constructor. +#pragma warning disable SA1309 // Field should not begin with an underscore. +#pragma warning disable SA1310 // Field names should not contain underscore. +#pragma warning disable SA1402 // File may only contain a single type. +#pragma warning disable SA1414 // Tuple types in signatures should have element names. +#pragma warning disable SA1514 // Element documentation header should be preceded by blank line. +#pragma warning disable SA1516 // Elements should be separated by blank line. +#pragma warning disable SA1649 // File name should match first type name. + +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantEmptySwitchSection +// ReSharper disable JoinDeclarationAndInitializer +// ReSharper disable TooWideLocalVariableScope +// ReSharper disable ArrangeStaticMemberQualifier +// ReSharper disable RedundantJumpStatement +// ReSharper disable RedundantUsingDirective +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Core.Utf8; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + 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; + + internal static class CustomerSchemaHrSchema + { + public static readonly Namespace Namespace = CustomerSchemaHrSchema.CreateSchema(); + public static readonly LayoutResolver LayoutResolver = CustomerSchemaHrSchema.LoadSchema(); + + private static Namespace CreateSchema() + { + return new Namespace + { + Name = "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.CustomerSchema", + Version = SchemaLanguageVersion.V2, + Schemas = new List + { + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "PostalCode", + SchemaId = new SchemaId(1), + Properties = new List + { + new Property + { + Path = "zip", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int32, + Storage = StorageKind.Fixed, + }, + }, + new Property + { + Path = "plus4", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int16, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Address", + SchemaId = new SchemaId(2), + Properties = new List + { + new Property + { + Path = "street", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "city", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "state", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Length = 2, + Storage = StorageKind.Fixed, + }, + }, + new Property + { + Path = "postal_code", + PropertyType = new UdtPropertyType + { + Name = "PostalCode", + SchemaId = new SchemaId(0), + }, + ApiName = "PostalCode", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Hotels", + SchemaId = new SchemaId(3), + PartitionKeys = new List + { + new PartitionKey + { + Path = "hotel_id", + }, + }, + Properties = new List + { + new Property + { + Path = "hotel_id", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + ApiName = "Id", + }, + new Property + { + Path = "name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "phone", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "address", + PropertyType = new UdtPropertyType + { + Name = "Address", + SchemaId = new SchemaId(0), + Immutable = true, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Available_Rooms_By_Hotel_Date", + SchemaId = new SchemaId(4), + PartitionKeys = new List + { + new PartitionKey + { + Path = "hotel_id", + }, + }, + PrimaryKeys = new List + { + new PrimarySortKey + { + Path = "date", + }, + new PrimarySortKey + { + Path = "room_number", + Direction = SortDirection.Descending, + }, + }, + Properties = new List + { + new Property + { + Path = "hotel_id", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + ApiName = "Id", + }, + new Property + { + Path = "date", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.DateTime, + Storage = StorageKind.Fixed, + }, + }, + new Property + { + Path = "room_number", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.UInt8, + Storage = StorageKind.Fixed, + }, + ApiName = "RoomNumber", + }, + new Property + { + Path = "is_available", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Boolean, + }, + ApiName = "IsAvailable", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Guests", + SchemaId = new SchemaId(5), + PartitionKeys = new List + { + new PartitionKey + { + Path = "guest_id", + }, + }, + PrimaryKeys = new List + { + new PrimarySortKey + { + Path = "first_name", + }, + new PrimarySortKey + { + Path = "phone_numbers", + Direction = SortDirection.Descending, + }, + }, + Properties = new List + { + new Property + { + Path = "guest_id", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Guid, + Storage = StorageKind.Fixed, + }, + ApiName = "Id", + }, + new Property + { + Path = "first_name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + ApiName = "FirstName", + }, + new Property + { + Path = "last_name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + ApiName = "LastName", + }, + new Property + { + Path = "title", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "emails", + PropertyType = new ArrayPropertyType + { + Items = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Nullable = false, + }, + }, + }, + new Property + { + Path = "phone_numbers", + PropertyType = new ArrayPropertyType + { + Items = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Nullable = false, + }, + }, + ApiName = "PhoneNumbers", + }, + new Property + { + Path = "addresses", + PropertyType = new MapPropertyType + { + Keys = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Nullable = false, + }, + Values = new UdtPropertyType + { + Name = "Address", + SchemaId = new SchemaId(0), + Nullable = false, + Immutable = true, + }, + }, + }, + new Property + { + Path = "confirm_number", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + ApiName = "ConfirmNumber", + }, + }, + }, + }, + }; + } + + private static LayoutResolver LoadSchema() + { + return new LayoutResolverNamespace(CustomerSchemaHrSchema.Namespace); + } + } + + public sealed class PostalCode + { + public int Zip { get; set; } + public short Plus4 { get; set; } + } + + public sealed class Address + { + public string Street { get; set; } + public string City { get; set; } + public string State { get; set; } + public PostalCode PostalCode { get; set; } + } + + public sealed class Hotels + { + public string Id { get; set; } + public string Name { get; set; } + public string Phone { get; set; } + public Address Address { get; set; } + } + + public sealed class Available_Rooms_By_Hotel_Date + { + public string Id { get; set; } + public DateTime Date { get; set; } + public byte RoomNumber { get; set; } + public bool IsAvailable { get; set; } + } + + public sealed class Guests + { + public Guid Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Title { get; set; } + public List Emails { get; set; } + public List PhoneNumbers { get; set; } + public Dictionary Addresses { get; set; } + public string ConfirmNumber { get; set; } + } + + public readonly struct PostalCodeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 1; + public const int Size = 5; + public IEqualityComparer Comparer => PostalCodeComparer.Default; + private static readonly Utf8String ZipName = Utf8String.TranscodeUtf16("zip"); + private static readonly Utf8String Plus4Name = Utf8String.TranscodeUtf16("plus4"); + + private static readonly LayoutColumn ZipColumn; + private static readonly LayoutColumn Plus4Column; + + private static readonly StringToken Plus4Token; + + static PostalCodeHybridRowSerializer() + { + Layout layout = CustomerSchemaHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(ZipName, out ZipColumn); + Contract.Invariant(found); + found = layout.TryFind(Plus4Name, out Plus4Column); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(Plus4Column.Path, out Plus4Token); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, PostalCode value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, PostalCode value) + { + Result r; + if (value.Zip != default) + { + r = LayoutType.Int32.WriteFixed(ref row, ref scope, ZipColumn, value.Zip); + if (r != Result.Success) + { + return r; + } + } + + if (value.Plus4 != default) + { + scope.Find(ref row, Plus4Column.Path); + r = LayoutType.Int16.WriteSparse(ref row, ref scope, value.Plus4); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out PostalCode value) + { + if (isRoot) + { + value = new PostalCode(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new PostalCode(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref PostalCode value) + { + Result r; + { + r = LayoutType.Int32.ReadFixed(ref row, ref scope, ZipColumn, out int fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Zip = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == Plus4Token.Id) + { + r = LayoutType.Int16.ReadSparse(ref row, ref scope, out short fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Plus4 = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class PostalCodeComparer : EqualityComparer + { + public static new readonly PostalCodeComparer Default = new PostalCodeComparer(); + + public override bool Equals(PostalCode x, PostalCode y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Int32HybridRowSerializer).Comparer.Equals(x.Zip, y.Zip) && + default(Int16HybridRowSerializer).Comparer.Equals(x.Plus4, y.Plus4); + } + + public override int GetHashCode(PostalCode obj) + { + return HashCode.Combine( + default(Int32HybridRowSerializer).Comparer.GetHashCode(obj.Zip), + default(Int16HybridRowSerializer).Comparer.GetHashCode(obj.Plus4)); + } + } + } + + public readonly struct AddressHybridRowSerializer : IHybridRowSerializer
+ { + public const int SchemaId = 2; + public const int Size = 3; + public IEqualityComparer
Comparer => AddressComparer.Default; + 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 static readonly LayoutColumn StreetColumn; + private static readonly LayoutColumn CityColumn; + private static readonly LayoutColumn StateColumn; + private static readonly LayoutColumn PostalCodeColumn; + + private static readonly StringToken PostalCodeToken; + + static AddressHybridRowSerializer() + { + Layout layout = CustomerSchemaHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(StreetName, out StreetColumn); + Contract.Invariant(found); + found = layout.TryFind(CityName, out CityColumn); + Contract.Invariant(found); + found = layout.TryFind(StateName, out StateColumn); + Contract.Invariant(found); + found = layout.TryFind(PostalCodeName, out PostalCodeColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(PostalCodeColumn.Path, out PostalCodeToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Address value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Address value) + { + Result r; + if (value.State != default) + { + r = LayoutType.Utf8.WriteFixed(ref row, ref scope, StateColumn, value.State); + if (r != Result.Success) + { + return r; + } + } + + if (value.Street != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, StreetColumn, value.Street); + if (r != Result.Success) + { + return r; + } + } + + if (value.City != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, CityColumn, value.City); + if (r != Result.Success) + { + return r; + } + } + + if (value.PostalCode != default) + { + scope.Find(ref row, PostalCodeColumn.Path); + r = default(PostalCodeHybridRowSerializer) + .Write(ref row, ref scope, false, PostalCodeColumn.TypeArgs, value.PostalCode); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Address value) + { + if (isRoot) + { + value = new Address(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Address(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Address value) + { + Result r; + { + r = LayoutType.Utf8.ReadFixed(ref row, ref scope, StateColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.State = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, StreetColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Street = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, CityColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.City = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == PostalCodeToken.Id) + { + r = default(PostalCodeHybridRowSerializer) + .Read(ref row, ref scope, false, out PostalCode fieldValue); + if (r != Result.Success) + { + return r; + } + + value.PostalCode = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class AddressComparer : EqualityComparer
+ { + public static new readonly AddressComparer Default = new AddressComparer(); + + public override bool Equals(Address x, Address y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Street, y.Street) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.City, y.City) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.State, y.State) && + default(PostalCodeHybridRowSerializer).Comparer.Equals(x.PostalCode, y.PostalCode); + } + + public override int GetHashCode(Address obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Street), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.City), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.State), + default(PostalCodeHybridRowSerializer).Comparer.GetHashCode(obj.PostalCode)); + } + } + } + + public readonly struct HotelsHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 3; + public const int Size = 1; + public IEqualityComparer Comparer => HotelsComparer.Default; + private static readonly Utf8String IdName = 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 static readonly LayoutColumn IdColumn; + private static readonly LayoutColumn NameColumn; + private static readonly LayoutColumn PhoneColumn; + private static readonly LayoutColumn AddressColumn; + + private static readonly StringToken AddressToken; + + static HotelsHybridRowSerializer() + { + Layout layout = CustomerSchemaHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(IdName, out IdColumn); + Contract.Invariant(found); + found = layout.TryFind(NameName, out NameColumn); + Contract.Invariant(found); + found = layout.TryFind(PhoneName, out PhoneColumn); + Contract.Invariant(found); + found = layout.TryFind(AddressName, out AddressColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(AddressColumn.Path, out AddressToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Hotels value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Hotels value) + { + Result r; + if (value.Id != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, IdColumn, value.Id); + if (r != Result.Success) + { + return r; + } + } + + if (value.Name != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, NameColumn, value.Name); + if (r != Result.Success) + { + return r; + } + } + + if (value.Phone != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, PhoneColumn, value.Phone); + if (r != Result.Success) + { + return r; + } + } + + if (value.Address != default) + { + scope.Find(ref row, AddressColumn.Path); + r = default(AddressHybridRowSerializer) + .Write(ref row, ref scope, false, AddressColumn.TypeArgs, value.Address); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Hotels value) + { + if (isRoot) + { + value = new Hotels(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Hotels(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Hotels value) + { + Result r; + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, IdColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Id = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, NameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Name = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, PhoneColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Phone = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == AddressToken.Id) + { + r = default(AddressHybridRowSerializer) + .Read(ref row, ref scope, false, out Address fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Address = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class HotelsComparer : EqualityComparer + { + public static new readonly HotelsComparer Default = new HotelsComparer(); + + public override bool Equals(Hotels x, Hotels y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Id, y.Id) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Name, y.Name) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Phone, y.Phone) && + default(AddressHybridRowSerializer).Comparer.Equals(x.Address, y.Address); + } + + public override int GetHashCode(Hotels obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Id), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Name), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Phone), + default(AddressHybridRowSerializer).Comparer.GetHashCode(obj.Address)); + } + } + } + + public readonly struct Available_Rooms_By_Hotel_DateHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 4; + public const int Size = 10; + public IEqualityComparer Comparer => Available_Rooms_By_Hotel_DateComparer.Default; + private static readonly Utf8String IdName = 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 static readonly LayoutColumn IdColumn; + private static readonly LayoutColumn DateColumn; + private static readonly LayoutColumn RoomNumberColumn; + private static readonly LayoutColumn IsAvailableColumn; + + private static readonly StringToken IsAvailableToken; + + static Available_Rooms_By_Hotel_DateHybridRowSerializer() + { + Layout layout = CustomerSchemaHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(IdName, out IdColumn); + Contract.Invariant(found); + found = layout.TryFind(DateName, out DateColumn); + Contract.Invariant(found); + found = layout.TryFind(RoomNumberName, out RoomNumberColumn); + Contract.Invariant(found); + found = layout.TryFind(IsAvailableName, out IsAvailableColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(IsAvailableColumn.Path, out IsAvailableToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Available_Rooms_By_Hotel_Date value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Available_Rooms_By_Hotel_Date value) + { + Result r; + if (value.Date != default) + { + r = LayoutType.DateTime.WriteFixed(ref row, ref scope, DateColumn, value.Date); + if (r != Result.Success) + { + return r; + } + } + + if (value.RoomNumber != default) + { + r = LayoutType.UInt8.WriteFixed(ref row, ref scope, RoomNumberColumn, value.RoomNumber); + if (r != Result.Success) + { + return r; + } + } + + if (value.Id != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, IdColumn, value.Id); + if (r != Result.Success) + { + return r; + } + } + + if (value.IsAvailable != default) + { + scope.Find(ref row, IsAvailableColumn.Path); + r = LayoutType.Boolean.WriteSparse(ref row, ref scope, value.IsAvailable); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Available_Rooms_By_Hotel_Date value) + { + if (isRoot) + { + value = new Available_Rooms_By_Hotel_Date(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Available_Rooms_By_Hotel_Date(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Available_Rooms_By_Hotel_Date value) + { + Result r; + { + r = LayoutType.DateTime.ReadFixed(ref row, ref scope, DateColumn, out DateTime fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Date = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.UInt8.ReadFixed(ref row, ref scope, RoomNumberColumn, out byte fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.RoomNumber = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, IdColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Id = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == IsAvailableToken.Id) + { + r = LayoutType.Boolean.ReadSparse(ref row, ref scope, out bool fieldValue); + if (r != Result.Success) + { + return r; + } + + value.IsAvailable = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class Available_Rooms_By_Hotel_DateComparer : EqualityComparer + { + public static new readonly Available_Rooms_By_Hotel_DateComparer Default = new Available_Rooms_By_Hotel_DateComparer(); + + public override bool Equals(Available_Rooms_By_Hotel_Date x, Available_Rooms_By_Hotel_Date y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Id, y.Id) && + default(DateTimeHybridRowSerializer).Comparer.Equals(x.Date, y.Date) && + default(UInt8HybridRowSerializer).Comparer.Equals(x.RoomNumber, y.RoomNumber) && + default(BooleanHybridRowSerializer).Comparer.Equals(x.IsAvailable, y.IsAvailable); + } + + public override int GetHashCode(Available_Rooms_By_Hotel_Date obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Id), + default(DateTimeHybridRowSerializer).Comparer.GetHashCode(obj.Date), + default(UInt8HybridRowSerializer).Comparer.GetHashCode(obj.RoomNumber), + default(BooleanHybridRowSerializer).Comparer.GetHashCode(obj.IsAvailable)); + } + } + } + + public readonly struct GuestsHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 5; + public const int Size = 17; + public IEqualityComparer Comparer => GuestsComparer.Default; + private static readonly Utf8String IdName = 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 static readonly LayoutColumn IdColumn; + private static readonly LayoutColumn FirstNameColumn; + private static readonly LayoutColumn LastNameColumn; + private static readonly LayoutColumn TitleColumn; + private static readonly LayoutColumn EmailsColumn; + private static readonly LayoutColumn PhoneNumbersColumn; + private static readonly LayoutColumn AddressesColumn; + private static readonly LayoutColumn ConfirmNumberColumn; + + private static readonly StringToken EmailsToken; + private static readonly StringToken PhoneNumbersToken; + private static readonly StringToken AddressesToken; + + static GuestsHybridRowSerializer() + { + Layout layout = CustomerSchemaHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(IdName, out IdColumn); + Contract.Invariant(found); + found = layout.TryFind(FirstNameName, out FirstNameColumn); + Contract.Invariant(found); + found = layout.TryFind(LastNameName, out LastNameColumn); + Contract.Invariant(found); + found = layout.TryFind(TitleName, out TitleColumn); + Contract.Invariant(found); + found = layout.TryFind(EmailsName, out EmailsColumn); + Contract.Invariant(found); + found = layout.TryFind(PhoneNumbersName, out PhoneNumbersColumn); + Contract.Invariant(found); + found = layout.TryFind(AddressesName, out AddressesColumn); + Contract.Invariant(found); + found = layout.TryFind(ConfirmNumberName, out ConfirmNumberColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(EmailsColumn.Path, out EmailsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(PhoneNumbersColumn.Path, out PhoneNumbersToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(AddressesColumn.Path, out AddressesToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Guests value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Guests value) + { + Result r; + if (value.Id != default) + { + r = LayoutType.Guid.WriteFixed(ref row, ref scope, IdColumn, value.Id); + if (r != Result.Success) + { + return r; + } + } + + if (value.FirstName != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, FirstNameColumn, value.FirstName); + if (r != Result.Success) + { + return r; + } + } + + if (value.LastName != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, LastNameColumn, value.LastName); + if (r != Result.Success) + { + return r; + } + } + + if (value.Title != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, TitleColumn, value.Title); + if (r != Result.Success) + { + return r; + } + } + + if (value.ConfirmNumber != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, ConfirmNumberColumn, value.ConfirmNumber); + if (r != Result.Success) + { + return r; + } + } + + if (value.Emails != default) + { + scope.Find(ref row, EmailsColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + EmailsColumn.TypeArgs, + value.Emails); + if (r != Result.Success) + { + return r; + } + } + + if (value.PhoneNumbers != default) + { + scope.Find(ref row, PhoneNumbersColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + PhoneNumbersColumn.TypeArgs, + value.PhoneNumbers); + if (r != Result.Success) + { + return r; + } + } + + if (value.Addresses != default) + { + scope.Find(ref row, AddressesColumn.Path); + r = default(TypedMapHybridRowSerializer< + string, Utf8HybridRowSerializer, + Address, AddressHybridRowSerializer + >).Write( + ref row, + ref scope, + false, + AddressesColumn.TypeArgs, + value.Addresses); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Guests value) + { + if (isRoot) + { + value = new Guests(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Guests(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Guests value) + { + Result r; + { + r = LayoutType.Guid.ReadFixed(ref row, ref scope, IdColumn, out Guid fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Id = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, FirstNameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.FirstName = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, LastNameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.LastName = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, TitleColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Title = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, ConfirmNumberColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.ConfirmNumber = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == EmailsToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Emails = fieldValue; + continue; + } + + if (scope.Token == PhoneNumbersToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.PhoneNumbers = fieldValue; + continue; + } + + if (scope.Token == AddressesToken.Id) + { + r = default(TypedMapHybridRowSerializer< + string, Utf8HybridRowSerializer, + Address, AddressHybridRowSerializer + >).Read(ref row, ref scope, false, out Dictionary fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Addresses = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class GuestsComparer : EqualityComparer + { + public static new readonly GuestsComparer Default = new GuestsComparer(); + + public override bool Equals(Guests x, Guests y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(GuidHybridRowSerializer).Comparer.Equals(x.Id, y.Id) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.FirstName, y.FirstName) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.LastName, y.LastName) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Title, y.Title) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Emails, y.Emails) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.PhoneNumbers, y.PhoneNumbers) && + default(TypedMapHybridRowSerializer).Comparer.Equals(x.Addresses, y.Addresses) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.ConfirmNumber, y.ConfirmNumber); + } + + public override int GetHashCode(Guests obj) + { + HashCode hash = default; + hash.Add(obj.Id, default(GuidHybridRowSerializer).Comparer); + hash.Add(obj.FirstName, default(Utf8HybridRowSerializer).Comparer); + hash.Add(obj.LastName, default(Utf8HybridRowSerializer).Comparer); + hash.Add(obj.Title, default(Utf8HybridRowSerializer).Comparer); + hash.Add(obj.Emails, default(TypedArrayHybridRowSerializer).Comparer); + hash.Add(obj.PhoneNumbers, default(TypedArrayHybridRowSerializer).Comparer); + hash.Add(obj.Addresses, default(TypedMapHybridRowSerializer).Comparer); + hash.Add(obj.ConfirmNumber, default(Utf8HybridRowSerializer).Comparer); + return hash.ToHashCode(); + } + } + } +} diff --git a/src/Serialization/HybridRow.Tests.Unit/Generated/PerfCounterSchema.cs b/src/Serialization/HybridRow.Tests.Unit/Generated/PerfCounterSchema.cs new file mode 100644 index 0000000..010bc97 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/Generated/PerfCounterSchema.cs @@ -0,0 +1,781 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// This file was generated by: +// Microsoft.Azure.Cosmos.Serialization.HybridRowCLI: 1.0.0.0 +// +// This file should not be modified directly. +// ------------------------------------------------------------ + +#pragma warning disable NamespaceMatchesFolderStructure // Namespace Declarations must match folder structure. +#pragma warning disable CA1707 // Identifiers should not contain underscores. +#pragma warning disable CA1034 // Do not nest types. +#pragma warning disable CA2104 // Do not declare readonly mutable reference types. +#pragma warning disable SA1129 // Do not use default value type constructor. +#pragma warning disable SA1309 // Field should not begin with an underscore. +#pragma warning disable SA1310 // Field names should not contain underscore. +#pragma warning disable SA1402 // File may only contain a single type. +#pragma warning disable SA1414 // Tuple types in signatures should have element names. +#pragma warning disable SA1514 // Element documentation header should be preceded by blank line. +#pragma warning disable SA1516 // Elements should be separated by blank line. +#pragma warning disable SA1649 // File name should match first type name. + +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantEmptySwitchSection +// ReSharper disable JoinDeclarationAndInitializer +// ReSharper disable TooWideLocalVariableScope +// ReSharper disable ArrangeStaticMemberQualifier +// ReSharper disable RedundantJumpStatement +// ReSharper disable RedundantUsingDirective +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedTuple +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Core.Utf8; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + 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; + + internal static class TypedTupleHrSchema + { + public static readonly Namespace Namespace = TypedTupleHrSchema.CreateSchema(); + public static readonly LayoutResolver LayoutResolver = TypedTupleHrSchema.LoadSchema(); + + private static Namespace CreateSchema() + { + return new Namespace + { + Name = "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedTuple", + Version = SchemaLanguageVersion.V2, + CppNamespace = "cdb_hr_test::tuple", + Schemas = new List + { + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Coord", + SchemaId = new SchemaId(2), + Properties = new List + { + new Property + { + Path = "lat", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int64, + Storage = StorageKind.Fixed, + }, + }, + new Property + { + Path = "lng", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int64, + Storage = StorageKind.Fixed, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "PerfCounter", + SchemaId = new SchemaId(1), + PartitionKeys = new List + { + new PartitionKey + { + Path = "name", + }, + }, + Properties = new List + { + new Property + { + Path = "name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "value", + PropertyType = new TuplePropertyType + { + Items = new List + { + new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Nullable = false, + }, + new PrimitivePropertyType + { + Type = TypeKind.Int64, + Nullable = false, + }, + }, + Immutable = true, + }, + }, + new Property + { + Path = "minmeanmax", + PropertyType = new TuplePropertyType + { + Items = new List + { + new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Nullable = false, + }, + new TuplePropertyType + { + Items = new List + { + new PrimitivePropertyType + { + Type = TypeKind.Int64, + Nullable = false, + }, + new PrimitivePropertyType + { + Type = TypeKind.Int64, + Nullable = false, + }, + new PrimitivePropertyType + { + Type = TypeKind.Int64, + Nullable = false, + }, + }, + Nullable = false, + }, + }, + Immutable = true, + }, + ApiName = "MinMaxValue", + }, + new Property + { + Path = "coord", + PropertyType = new TuplePropertyType + { + Items = new List + { + new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Nullable = false, + }, + new UdtPropertyType + { + Name = "Coord", + SchemaId = new SchemaId(0), + Nullable = false, + }, + }, + Immutable = true, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "CounterSet", + SchemaId = new SchemaId(3), + Properties = new List + { + new Property + { + Path = "history", + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "PerfCounter", + SchemaId = new SchemaId(0), + Nullable = false, + }, + }, + }, + }, + }, + }, + }; + } + + private static LayoutResolver LoadSchema() + { + return new LayoutResolverNamespace(TypedTupleHrSchema.Namespace); + } + } + + public sealed class Coord + { + public long Lat { get; set; } + public long Lng { get; set; } + } + + public sealed class PerfCounter + { + public string Name { get; set; } + public (string, long) Value { get; set; } + public (string, (long, long, long)) MinMaxValue { get; set; } + public (string, Coord) Coord { get; set; } + } + + public sealed class CounterSet + { + public List History { get; set; } + } + + public readonly struct CoordHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2; + public const int Size = 17; + public IEqualityComparer Comparer => CoordComparer.Default; + private static readonly Utf8String LatName = Utf8String.TranscodeUtf16("lat"); + private static readonly Utf8String LngName = Utf8String.TranscodeUtf16("lng"); + + private static readonly LayoutColumn LatColumn; + private static readonly LayoutColumn LngColumn; + + static CoordHybridRowSerializer() + { + Layout layout = TypedTupleHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(LatName, out LatColumn); + Contract.Invariant(found); + found = layout.TryFind(LngName, out LngColumn); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Coord value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Coord value) + { + Result r; + if (value.Lat != default) + { + r = LayoutType.Int64.WriteFixed(ref row, ref scope, LatColumn, value.Lat); + if (r != Result.Success) + { + return r; + } + } + + if (value.Lng != default) + { + r = LayoutType.Int64.WriteFixed(ref row, ref scope, LngColumn, value.Lng); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Coord value) + { + if (isRoot) + { + value = new Coord(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Coord(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Coord value) + { + Result r; + { + r = LayoutType.Int64.ReadFixed(ref row, ref scope, LatColumn, out long fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Lat = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Int64.ReadFixed(ref row, ref scope, LngColumn, out long fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Lng = fieldValue; + break; + default: + return r; + } + } + + return Result.Success; + } + + public sealed class CoordComparer : EqualityComparer + { + public static new readonly CoordComparer Default = new CoordComparer(); + + public override bool Equals(Coord x, Coord y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Int64HybridRowSerializer).Comparer.Equals(x.Lat, y.Lat) && + default(Int64HybridRowSerializer).Comparer.Equals(x.Lng, y.Lng); + } + + public override int GetHashCode(Coord obj) + { + return HashCode.Combine( + default(Int64HybridRowSerializer).Comparer.GetHashCode(obj.Lat), + default(Int64HybridRowSerializer).Comparer.GetHashCode(obj.Lng)); + } + } + } + + public readonly struct PerfCounterHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 1; + public const int Size = 1; + public IEqualityComparer Comparer => PerfCounterComparer.Default; + private static readonly Utf8String NameName = Utf8String.TranscodeUtf16("name"); + private static readonly Utf8String ValueName = Utf8String.TranscodeUtf16("value"); + private static readonly Utf8String MinMaxValueName = Utf8String.TranscodeUtf16("minmeanmax"); + private static readonly Utf8String CoordName = Utf8String.TranscodeUtf16("coord"); + + private static readonly LayoutColumn NameColumn; + private static readonly LayoutColumn ValueColumn; + private static readonly LayoutColumn MinMaxValueColumn; + private static readonly LayoutColumn CoordColumn; + + private static readonly StringToken ValueToken; + private static readonly StringToken MinMaxValueToken; + private static readonly StringToken CoordToken; + + static PerfCounterHybridRowSerializer() + { + Layout layout = TypedTupleHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(NameName, out NameColumn); + Contract.Invariant(found); + found = layout.TryFind(ValueName, out ValueColumn); + Contract.Invariant(found); + found = layout.TryFind(MinMaxValueName, out MinMaxValueColumn); + Contract.Invariant(found); + found = layout.TryFind(CoordName, out CoordColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(ValueColumn.Path, out ValueToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(MinMaxValueColumn.Path, out MinMaxValueToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(CoordColumn.Path, out CoordToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, PerfCounter value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, PerfCounter value) + { + Result r; + if (value.Name != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, NameColumn, value.Name); + if (r != Result.Success) + { + return r; + } + } + + if (value.Value != default) + { + scope.Find(ref row, ValueColumn.Path); + r = default(TypedTupleHybridRowSerializer).Write( + ref row, + ref scope, + false, + ValueColumn.TypeArgs, + value.Value); + if (r != Result.Success) + { + return r; + } + } + + if (value.MinMaxValue != default) + { + scope.Find(ref row, MinMaxValueColumn.Path); + r = default(TypedTupleHybridRowSerializer>).Write( + ref row, + ref scope, + false, + MinMaxValueColumn.TypeArgs, + value.MinMaxValue); + if (r != Result.Success) + { + return r; + } + } + + if (value.Coord != default) + { + scope.Find(ref row, CoordColumn.Path); + r = default(TypedTupleHybridRowSerializer).Write( + ref row, + ref scope, + false, + CoordColumn.TypeArgs, + value.Coord); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out PerfCounter value) + { + if (isRoot) + { + value = new PerfCounter(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new PerfCounter(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref PerfCounter value) + { + Result r; + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, NameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Name = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == ValueToken.Id) + { + r = default(TypedTupleHybridRowSerializer) + .Read(ref row, ref scope, false, out (string, long) fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Value = fieldValue; + continue; + } + + if (scope.Token == MinMaxValueToken.Id) + { + r = default(TypedTupleHybridRowSerializer>) + .Read(ref row, ref scope, false, out (string, (long, long, long)) fieldValue); + if (r != Result.Success) + { + return r; + } + + value.MinMaxValue = fieldValue; + continue; + } + + if (scope.Token == CoordToken.Id) + { + r = default(TypedTupleHybridRowSerializer) + .Read(ref row, ref scope, false, out (string, Coord) fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Coord = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class PerfCounterComparer : EqualityComparer + { + public static new readonly PerfCounterComparer Default = new PerfCounterComparer(); + + public override bool Equals(PerfCounter x, PerfCounter y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Name, y.Name) && + default(TypedTupleHybridRowSerializer).Comparer.Equals(x.Value, y.Value) && + default(TypedTupleHybridRowSerializer>).Comparer.Equals(x.MinMaxValue, y.MinMaxValue) && + default(TypedTupleHybridRowSerializer).Comparer.Equals(x.Coord, y.Coord); + } + + public override int GetHashCode(PerfCounter obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Name), + default(TypedTupleHybridRowSerializer).Comparer.GetHashCode(obj.Value), + default(TypedTupleHybridRowSerializer>).Comparer.GetHashCode(obj.MinMaxValue), + default(TypedTupleHybridRowSerializer).Comparer.GetHashCode(obj.Coord)); + } + } + } + + public readonly struct CounterSetHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 3; + public const int Size = 0; + public IEqualityComparer Comparer => CounterSetComparer.Default; + private static readonly Utf8String HistoryName = Utf8String.TranscodeUtf16("history"); + + private static readonly LayoutColumn HistoryColumn; + + private static readonly StringToken HistoryToken; + + static CounterSetHybridRowSerializer() + { + Layout layout = TypedTupleHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(HistoryName, out HistoryColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(HistoryColumn.Path, out HistoryToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, CounterSet value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, CounterSet value) + { + Result r; + if (value.History != default) + { + scope.Find(ref row, HistoryColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + HistoryColumn.TypeArgs, + value.History); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out CounterSet value) + { + if (isRoot) + { + value = new CounterSet(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new CounterSet(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref CounterSet value) + { + Result r; + while (scope.MoveNext(ref row)) + { + if (scope.Token == HistoryToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.History = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class CounterSetComparer : EqualityComparer + { + public static new readonly CounterSetComparer Default = new CounterSetComparer(); + + public override bool Equals(CounterSet x, CounterSet y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return default(TypedArrayHybridRowSerializer).Comparer.Equals(x.History, y.History); + } + + public override int GetHashCode(CounterSet obj) + { + return default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.History); + } + } + } +} diff --git a/src/Serialization/HybridRow.Tests.Unit/Generated/TaggedSchema.cs b/src/Serialization/HybridRow.Tests.Unit/Generated/TaggedSchema.cs new file mode 100644 index 0000000..06d433f --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/Generated/TaggedSchema.cs @@ -0,0 +1,680 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// This file was generated by: +// Microsoft.Azure.Cosmos.Serialization.HybridRowCLI: 1.0.0.0 +// +// This file should not be modified directly. +// ------------------------------------------------------------ + +#pragma warning disable NamespaceMatchesFolderStructure // Namespace Declarations must match folder structure. +#pragma warning disable CA1707 // Identifiers should not contain underscores. +#pragma warning disable CA1034 // Do not nest types. +#pragma warning disable CA2104 // Do not declare readonly mutable reference types. +#pragma warning disable SA1129 // Do not use default value type constructor. +#pragma warning disable SA1309 // Field should not begin with an underscore. +#pragma warning disable SA1310 // Field names should not contain underscore. +#pragma warning disable SA1402 // File may only contain a single type. +#pragma warning disable SA1414 // Tuple types in signatures should have element names. +#pragma warning disable SA1514 // Element documentation header should be preceded by blank line. +#pragma warning disable SA1516 // Elements should be separated by blank line. +#pragma warning disable SA1649 // File name should match first type name. + +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantEmptySwitchSection +// ReSharper disable JoinDeclarationAndInitializer +// ReSharper disable TooWideLocalVariableScope +// ReSharper disable ArrangeStaticMemberQualifier +// ReSharper disable RedundantJumpStatement +// ReSharper disable RedundantUsingDirective +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedArray +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Core.Utf8; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + 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; + + internal static class TypedArrayHrSchema + { + public static readonly Namespace Namespace = TypedArrayHrSchema.CreateSchema(); + public static readonly LayoutResolver LayoutResolver = TypedArrayHrSchema.LoadSchema(); + + private static Namespace CreateSchema() + { + return new Namespace + { + Name = "Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedArray", + Version = SchemaLanguageVersion.V2, + CppNamespace = "cdb_hr_test::typed_array", + Schemas = new List + { + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Tagged", + SchemaId = new SchemaId(1), + Properties = new List + { + new Property + { + Path = "title", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "tags", + PropertyType = new ArrayPropertyType + { + Items = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Nullable = false, + }, + }, + }, + new Property + { + Path = "options", + PropertyType = new ArrayPropertyType + { + Items = new PrimitivePropertyType + { + Type = TypeKind.Int32, + }, + }, + }, + new Property + { + Path = "ratings", + PropertyType = new ArrayPropertyType + { + Items = new ArrayPropertyType + { + Items = new PrimitivePropertyType + { + Type = TypeKind.Float64, + Nullable = false, + }, + Nullable = false, + }, + }, + }, + new Property + { + Path = "similars", + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "SimilarMatch", + SchemaId = new SchemaId(0), + Nullable = false, + }, + }, + }, + new Property + { + Path = "priority", + PropertyType = new ArrayPropertyType + { + Items = new TuplePropertyType + { + Items = new List + { + new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Nullable = false, + }, + new PrimitivePropertyType + { + Type = TypeKind.Int64, + Nullable = false, + }, + }, + Nullable = false, + }, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "SimilarMatch", + SchemaId = new SchemaId(2), + Properties = new List + { + new Property + { + Path = "thumbprint", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Length = 18, + Storage = StorageKind.Fixed, + }, + }, + new Property + { + Path = "score", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Float64, + Storage = StorageKind.Fixed, + }, + }, + }, + }, + }, + }; + } + + private static LayoutResolver LoadSchema() + { + return new LayoutResolverNamespace(TypedArrayHrSchema.Namespace); + } + } + + public sealed class Tagged + { + public string Title { get; set; } + public List Tags { get; set; } + public List Options { get; set; } + public List> Ratings { get; set; } + public List Similars { get; set; } + public List<(string, long)> Priority { get; set; } + } + + public sealed class SimilarMatch + { + public string Thumbprint { get; set; } + public double Score { get; set; } + } + + public readonly struct TaggedHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 1; + public const int Size = 1; + public IEqualityComparer Comparer => TaggedComparer.Default; + private static readonly Utf8String TitleName = Utf8String.TranscodeUtf16("title"); + private static readonly Utf8String TagsName = Utf8String.TranscodeUtf16("tags"); + private static readonly Utf8String OptionsName = Utf8String.TranscodeUtf16("options"); + private static readonly Utf8String RatingsName = Utf8String.TranscodeUtf16("ratings"); + private static readonly Utf8String SimilarsName = Utf8String.TranscodeUtf16("similars"); + private static readonly Utf8String PriorityName = Utf8String.TranscodeUtf16("priority"); + + private static readonly LayoutColumn TitleColumn; + private static readonly LayoutColumn TagsColumn; + private static readonly LayoutColumn OptionsColumn; + private static readonly LayoutColumn RatingsColumn; + private static readonly LayoutColumn SimilarsColumn; + private static readonly LayoutColumn PriorityColumn; + + private static readonly StringToken TagsToken; + private static readonly StringToken OptionsToken; + private static readonly StringToken RatingsToken; + private static readonly StringToken SimilarsToken; + private static readonly StringToken PriorityToken; + + static TaggedHybridRowSerializer() + { + Layout layout = TypedArrayHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(TitleName, out TitleColumn); + Contract.Invariant(found); + found = layout.TryFind(TagsName, out TagsColumn); + Contract.Invariant(found); + found = layout.TryFind(OptionsName, out OptionsColumn); + Contract.Invariant(found); + found = layout.TryFind(RatingsName, out RatingsColumn); + Contract.Invariant(found); + found = layout.TryFind(SimilarsName, out SimilarsColumn); + Contract.Invariant(found); + found = layout.TryFind(PriorityName, out PriorityColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(TagsColumn.Path, out TagsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(OptionsColumn.Path, out OptionsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(RatingsColumn.Path, out RatingsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(SimilarsColumn.Path, out SimilarsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(PriorityColumn.Path, out PriorityToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Tagged value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Tagged value) + { + Result r; + if (value.Title != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, TitleColumn, value.Title); + if (r != Result.Success) + { + return r; + } + } + + if (value.Tags != default) + { + scope.Find(ref row, TagsColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + TagsColumn.TypeArgs, + value.Tags); + if (r != Result.Success) + { + return r; + } + } + + if (value.Options != default) + { + scope.Find(ref row, OptionsColumn.Path); + r = default(TypedArrayHybridRowSerializer>).Write( + ref row, + ref scope, + false, + OptionsColumn.TypeArgs, + value.Options); + if (r != Result.Success) + { + return r; + } + } + + if (value.Ratings != default) + { + scope.Find(ref row, RatingsColumn.Path); + r = default(TypedArrayHybridRowSerializer, TypedArrayHybridRowSerializer>).Write( + ref row, + ref scope, + false, + RatingsColumn.TypeArgs, + value.Ratings); + if (r != Result.Success) + { + return r; + } + } + + if (value.Similars != default) + { + scope.Find(ref row, SimilarsColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + SimilarsColumn.TypeArgs, + value.Similars); + if (r != Result.Success) + { + return r; + } + } + + if (value.Priority != default) + { + scope.Find(ref row, PriorityColumn.Path); + r = default(TypedArrayHybridRowSerializer<(string, long), TypedTupleHybridRowSerializer>).Write( + ref row, + ref scope, + false, + PriorityColumn.TypeArgs, + value.Priority); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Tagged value) + { + if (isRoot) + { + value = new Tagged(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Tagged(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Tagged value) + { + Result r; + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, TitleColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Title = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == TagsToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Tags = fieldValue; + continue; + } + + if (scope.Token == OptionsToken.Id) + { + r = default(TypedArrayHybridRowSerializer>) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Options = fieldValue; + continue; + } + + if (scope.Token == RatingsToken.Id) + { + r = default(TypedArrayHybridRowSerializer, TypedArrayHybridRowSerializer>) + .Read(ref row, ref scope, false, out List> fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Ratings = fieldValue; + continue; + } + + if (scope.Token == SimilarsToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Similars = fieldValue; + continue; + } + + if (scope.Token == PriorityToken.Id) + { + r = default(TypedArrayHybridRowSerializer<(string, long), TypedTupleHybridRowSerializer>) + .Read(ref row, ref scope, false, out List<(string, long)> fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Priority = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class TaggedComparer : EqualityComparer + { + public static new readonly TaggedComparer Default = new TaggedComparer(); + + public override bool Equals(Tagged x, Tagged y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Title, y.Title) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Tags, y.Tags) && + default(TypedArrayHybridRowSerializer>).Comparer.Equals(x.Options, y.Options) && + default(TypedArrayHybridRowSerializer, TypedArrayHybridRowSerializer>).Comparer.Equals(x.Ratings, y.Ratings) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Similars, y.Similars) && + default(TypedArrayHybridRowSerializer<(string, long), TypedTupleHybridRowSerializer>).Comparer.Equals(x.Priority, y.Priority); + } + + public override int GetHashCode(Tagged obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Title), + default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.Tags), + default(TypedArrayHybridRowSerializer>).Comparer.GetHashCode(obj.Options), + default(TypedArrayHybridRowSerializer, TypedArrayHybridRowSerializer>).Comparer.GetHashCode(obj.Ratings), + default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.Similars), + default(TypedArrayHybridRowSerializer<(string, long), TypedTupleHybridRowSerializer>).Comparer.GetHashCode(obj.Priority)); + } + } + } + + public readonly struct SimilarMatchHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2; + public const int Size = 27; + public IEqualityComparer Comparer => SimilarMatchComparer.Default; + private static readonly Utf8String ThumbprintName = Utf8String.TranscodeUtf16("thumbprint"); + private static readonly Utf8String ScoreName = Utf8String.TranscodeUtf16("score"); + + private static readonly LayoutColumn ThumbprintColumn; + private static readonly LayoutColumn ScoreColumn; + + static SimilarMatchHybridRowSerializer() + { + Layout layout = TypedArrayHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(ThumbprintName, out ThumbprintColumn); + Contract.Invariant(found); + found = layout.TryFind(ScoreName, out ScoreColumn); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, SimilarMatch value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, SimilarMatch value) + { + Result r; + if (value.Thumbprint != default) + { + r = LayoutType.Utf8.WriteFixed(ref row, ref scope, ThumbprintColumn, value.Thumbprint); + if (r != Result.Success) + { + return r; + } + } + + if (value.Score != default) + { + r = LayoutType.Float64.WriteFixed(ref row, ref scope, ScoreColumn, value.Score); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out SimilarMatch value) + { + if (isRoot) + { + value = new SimilarMatch(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new SimilarMatch(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref SimilarMatch value) + { + Result r; + { + r = LayoutType.Utf8.ReadFixed(ref row, ref scope, ThumbprintColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Thumbprint = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Float64.ReadFixed(ref row, ref scope, ScoreColumn, out double fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Score = fieldValue; + break; + default: + return r; + } + } + + return Result.Success; + } + + public sealed class SimilarMatchComparer : EqualityComparer + { + public static new readonly SimilarMatchComparer Default = new SimilarMatchComparer(); + + public override bool Equals(SimilarMatch x, SimilarMatch y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Thumbprint, y.Thumbprint) && + default(Float64HybridRowSerializer).Comparer.Equals(x.Score, y.Score); + } + + public override int GetHashCode(SimilarMatch obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Thumbprint), + default(Float64HybridRowSerializer).Comparer.GetHashCode(obj.Score)); + } + } + } +} diff --git a/src/Serialization/HybridRow.Tests.Unit/HybridRowSerializerUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/HybridRowSerializerUnitTests.cs new file mode 100644 index 0000000..cc00f33 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/HybridRowSerializerUnitTests.cs @@ -0,0 +1,140 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit +{ + using System; + using System.Collections.Generic; + 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] + public sealed class HybridRowSerializerUnitTests + { + [TestMethod] + [Owner("jthunter")] + public void TypedArraySerializerTest() + { + TypeArgumentList typeArgs1 = new TypeArgumentList(new[] { new TypeArgument(LayoutType.Utf8) }); + HybridRowSerializerUnitTests.TestSerializer, TypedArrayHybridRowSerializer>( + typeArgs1, + new List { "abc", "xyz" }, + new List { "abc", "ghk" }, + new List { "abc" }); + + TypeArgumentList typeArgs2 = new TypeArgumentList(new[] { new TypeArgument(LayoutType.Int32) }); + HybridRowSerializerUnitTests.TestSerializer, TypedArrayHybridRowSerializer>( + typeArgs2, + new List { 123, 456 }, + new List { 123, 789 }, + new List { 4733584 }); + } + + [TestMethod] + [Owner("jthunter")] + public void ArraySerializerTest() + { + TypeArgumentList typeArgs1 = new TypeArgumentList(new[] { new TypeArgument(LayoutType.Utf8) }); + HybridRowSerializerUnitTests.TestSerializer, ArrayHybridRowSerializer>( + typeArgs1, + new List { "abc", "xyz" }, + new List { "abc", "ghk" }, + new List { "abc" }); + + TypeArgumentList typeArgs2 = new TypeArgumentList(new[] { new TypeArgument(LayoutType.Int32) }); + HybridRowSerializerUnitTests.TestSerializer, ArrayHybridRowSerializer>( + typeArgs2, + new List { 123, 456 }, + new List { 123, 789 }, + new List { 4733584 }); + } + + [TestMethod] + [Owner("jthunter")] + public void TypedTupleSerializerTest() + { + TypeArgumentList typeArgs1 = new TypeArgumentList( + new[] + { + new TypeArgument(LayoutType.Utf8), + new TypeArgument(LayoutType.Int32), + }); + HybridRowSerializerUnitTests + .TestSerializer<(string, int), TypedTupleHybridRowSerializer + >(typeArgs1, ("abc", 123), ("xyz", 123), ("abc", 456)); + + TypeArgumentList typeArgs2 = new TypeArgumentList( + new[] + { + new TypeArgument(LayoutType.Int64), + new TypeArgument(LayoutType.Int32), + new TypeArgument(LayoutType.DateTime), + }); + HybridRowSerializerUnitTests.TestSerializer<(long, int, DateTime), TypedTupleHybridRowSerializer< + long, Int64HybridRowSerializer, + int, Int32HybridRowSerializer, + DateTime, DateTimeHybridRowSerializer> + >(typeArgs2, (789, 123, new DateTime(456)), (654, 123, new DateTime(456)), (789, 123, new DateTime(789))); + } + + [TestMethod] + [Owner("jthunter")] + public void NullableSerializerTest() + { + TypeArgumentList typeArgs1 = new TypeArgumentList(new[] { new TypeArgument(LayoutType.Int32) }); + HybridRowSerializerUnitTests.TestSerializer>( + typeArgs1, + 123, + 456, + null); + } + + [TestMethod] + [Owner("jthunter")] + public void MapSerializerTest() + { + TypeArgumentList typeArgs1 = new TypeArgumentList( + new[] + { + new TypeArgument(LayoutType.Utf8), + new TypeArgument(LayoutType.Int32), + }); + + HybridRowSerializerUnitTests.TestSerializer, + TypedMapHybridRowSerializer>( + typeArgs1, + new Dictionary { { "abc", 123 } }, + new Dictionary { { "xyz", 123 } }, + new Dictionary { { "abc", 456 } }); + } + + private static void TestSerializer(TypeArgumentList typeArgs, T t1, T t2, T t3) + where TS : IHybridRowSerializer + { + LayoutResolver resolver = SchemasHrSchema.LayoutResolver; + Layout layout = resolver.Resolve(SystemSchema.EmptySchemaId); + + MemorySpanResizer resizer = new MemorySpanResizer(); + RowBuffer row = new RowBuffer(0, resizer); + row.InitLayout(HybridRowVersion.V1, layout, resolver); + + RowCursor root = RowCursor.Create(ref row); + ResultAssert.IsSuccess(default(TS).Write(ref row, ref root.Clone(out RowCursor _).Find(ref row, "a"), false, typeArgs, t1)); + Result r2 = default(TS).Read(ref row, ref root.Clone(out RowCursor _).Find(ref row, "a"), false, out T v1); + ResultAssert.IsSuccess(r2); + + Assert.IsTrue(default(TS).Comparer.Equals(t1, v1)); + Assert.AreEqual(default(TS).Comparer.GetHashCode(t1), default(TS).Comparer.GetHashCode(v1)); + Assert.AreNotEqual(default(TS).Comparer.Equals(t1), 0); + + Assert.IsFalse(default(TS).Comparer.Equals(t1, t2)); + Assert.AreNotEqual(default(TS).Comparer.GetHashCode(t1), default(TS).Comparer.GetHashCode(t2)); + + Assert.IsFalse(default(TS).Comparer.Equals(t1, t3)); + Assert.AreNotEqual(default(TS).Comparer.GetHashCode(t1), default(TS).Comparer.GetHashCode(t3)); + } + } +} diff --git a/dotnet/src/HybridRow.Tests.Unit/Internal/MurmurHash3UnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/Internal/MurmurHash3UnitTests.cs similarity index 97% rename from dotnet/src/HybridRow.Tests.Unit/Internal/MurmurHash3UnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/Internal/MurmurHash3UnitTests.cs index 70b3fc9..9ca668c 100644 --- a/dotnet/src/HybridRow.Tests.Unit/Internal/MurmurHash3UnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/Internal/MurmurHash3UnitTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.Internal [TestClass] public class MurmurHash3UnitTests { - private static readonly (ulong low, ulong high)[] Expected = new[] + private static readonly (ulong Low, ulong High)[] Expected = new[] { (0x56F1549659CBEE1AUL, 0xCEB3EE124C3E3855UL), (0xFE84B58886F9D717UL, 0xD24C5DE024F5EA6BUL), @@ -69,8 +69,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.Internal 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); + Assert.AreEqual(MurmurHash3UnitTests.Expected[i].High, high); + Assert.AreEqual(MurmurHash3UnitTests.Expected[i].Low, low); } // Measure performance. diff --git a/dotnet/src/HybridRow.Tests.Unit/LayoutCompilerUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/LayoutCompilerUnitTests.cs similarity index 62% rename from dotnet/src/HybridRow.Tests.Unit/LayoutCompilerUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/LayoutCompilerUnitTests.cs index 53d2f57..9eb7e92 100644 --- a/dotnet/src/HybridRow.Tests.Unit/LayoutCompilerUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/LayoutCompilerUnitTests.cs @@ -2,12 +2,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ -// ReSharper disable CommentTypo -// ReSharper disable StringLiteralTypo #pragma warning disable SA1201 // Elements should appear in the correct order #pragma warning disable SA1401 // Fields should be private #pragma warning disable IDE0008 // Use explicit type +// ReSharper disable CommentTypo +// ReSharper disable StringLiteralTypo namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { using System; @@ -33,7 +33,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void PackNullAndBoolBits() { // Test that null bits and bool bits are packed tightly in the layout. - Schema s = new Schema { Name = "TestSchema", SchemaId = new SchemaId(1), Type = TypeKind.Schema }; + Schema s = new Schema { Name = "TestSchema", SchemaId = new SchemaId(1) }; for (int i = 0; i < 32; i++) { s.Properties.Add( @@ -43,7 +43,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit PropertyType = new PrimitivePropertyType { Type = TypeKind.Boolean, Storage = StorageKind.Fixed }, }); - Layout layout = s.Compile(new Namespace { Schemas = new List { s } }); + Layout layout = s.Compile(new Namespace { Schemas = { s } }); Assert.IsTrue(layout.Size == LayoutBit.DivCeiling((i + 1) * 2, LayoutType.BitsPerByte), "Size: {0}, i: {1}", layout.Size, i); } } @@ -55,33 +55,34 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit // Test all fixed column types. RoundTripFixed.Expected[] expectedSchemas = new[] { - new RoundTripFixed.Expected { TypeName = "null", Default = default(NullValue), Value = NullValue.Default }, - new RoundTripFixed.Expected { TypeName = "bool", Default = default(bool), Value = false }, + new RoundTripFixed.Expected { TypeName = TypeKind.Null, Default = default(NullValue), Value = NullValue.Default }, + new RoundTripFixed.Expected { TypeName = TypeKind.Boolean, Default = default(bool), Value = false }, - new RoundTripFixed.Expected { TypeName = "int8", Default = default(sbyte), Value = (sbyte)42 }, - new RoundTripFixed.Expected { TypeName = "int16", Default = default(short), Value = (short)42 }, - new RoundTripFixed.Expected { TypeName = "int32", Default = default(int), Value = 42 }, - new RoundTripFixed.Expected { TypeName = "int64", Default = default(long), Value = 42L }, - new RoundTripFixed.Expected { TypeName = "uint8", Default = default(byte), Value = (byte)42 }, - new RoundTripFixed.Expected { TypeName = "uint16", Default = default(ushort), Value = (ushort)42 }, - new RoundTripFixed.Expected { TypeName = "uint32", Default = default(uint), Value = 42U }, - new RoundTripFixed.Expected { TypeName = "uint64", Default = default(ulong), Value = 42UL }, + new RoundTripFixed.Expected { TypeName = TypeKind.Int8, Default = default(sbyte), Value = (sbyte)42 }, + new RoundTripFixed.Expected { TypeName = TypeKind.Int16, Default = default(short), Value = (short)42 }, + new RoundTripFixed.Expected { TypeName = TypeKind.Int32, Default = default(int), Value = 42 }, + new RoundTripFixed.Expected { TypeName = TypeKind.Int64, Default = default(long), Value = 42L }, + new RoundTripFixed.Expected { TypeName = TypeKind.UInt8, Default = default(byte), Value = (byte)42 }, + new RoundTripFixed.Expected { TypeName = TypeKind.UInt16, Default = default(ushort), Value = (ushort)42 }, + new RoundTripFixed.Expected { TypeName = TypeKind.UInt32, Default = default(uint), Value = 42U }, + new RoundTripFixed.Expected { TypeName = TypeKind.UInt64, Default = default(ulong), Value = 42UL }, - new RoundTripFixed.Expected { TypeName = "float32", Default = default(float), Value = 4.2F }, - new RoundTripFixed.Expected { TypeName = "float64", Default = default(double), Value = 4.2 }, - new RoundTripFixed.Expected { TypeName = "float128", Default = default(Float128), Value = new Float128(0, 42) }, - new RoundTripFixed.Expected { TypeName = "decimal", Default = default(decimal), Value = 4.2M }, + new RoundTripFixed.Expected { TypeName = TypeKind.Float32, Default = default(float), Value = 4.2F }, + new RoundTripFixed.Expected { TypeName = TypeKind.Float64, Default = default(double), Value = 4.2 }, + new RoundTripFixed.Expected { TypeName = TypeKind.Float128, Default = default(Float128), Value = new Float128(0, 42) }, + new RoundTripFixed.Expected { TypeName = TypeKind.Decimal, Default = default(decimal), Value = 4.2M }, - new RoundTripFixed.Expected { TypeName = "datetime", Default = default(DateTime), Value = DateTime.UtcNow }, - new RoundTripFixed.Expected { TypeName = "unixdatetime", Default = default(UnixDateTime), Value = new UnixDateTime(42) }, + new RoundTripFixed.Expected { TypeName = TypeKind.DateTime, Default = default(DateTime), Value = DateTime.UtcNow }, + new RoundTripFixed.Expected { TypeName = TypeKind.UnixDateTime, Default = default(UnixDateTime), Value = new UnixDateTime(42) }, - new RoundTripFixed.Expected { TypeName = "guid", Default = default(Guid), Value = Guid.NewGuid() }, - new RoundTripFixed.Expected { TypeName = "mongodbobjectid", Default = default(MongoDbObjectId), Value = new MongoDbObjectId(0, 42) }, + new RoundTripFixed.Expected { TypeName = TypeKind.Guid, Default = default(Guid), Value = Guid.NewGuid() }, + new RoundTripFixed.Expected + { TypeName = TypeKind.MongoDbObjectId, Default = default(MongoDbObjectId), Value = new MongoDbObjectId(0, 42) }, - new RoundTripFixed.Expected { TypeName = "utf8", Default = "\0\0", Value = "AB", Length = 2 }, + new RoundTripFixed.Expected { TypeName = TypeKind.Utf8, Default = "\0\0", Value = "AB", Length = 2 }, new RoundTripFixed.Expected { - TypeName = "binary", + TypeName = TypeKind.Binary, Default = new byte[] { 0x00, 0x00 }, Value = new byte[] { 0x01, 0x02 }, Length = 2, @@ -89,27 +90,45 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit }; RowBuffer row = new RowBuffer(LayoutCompilerUnitTests.InitialRowSize); - foreach (string nullable in new[] { "true", "false" }) + foreach (bool nullable in new[] { true, false }) { foreach (RoundTripFixed.Expected exp in expectedSchemas) { RoundTripFixed.Expected expected = exp; - string typeSchema = $@"{{'type': '{expected.TypeName}', 'storage': 'fixed', 'length': {expected.Length}, 'nullable': {nullable}}}"; - expected.Json = typeSchema; - string propSchema = $@"{{'path': 'a', 'type': {typeSchema}}}"; - string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{propSchema}] }}"; + string typeSchema = + $@"{{'{expected.TypeName}', 'length': {expected.Length}, 'nullable': {nullable}}}"; + expected.Tag = typeSchema; try { - Schema s = Schema.Parse(tableSchema); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = new List { s } }); + Schema s = new Schema() + { + Name = "table", + SchemaId = new SchemaId(-1), + Properties = + { + new Property + { + Path = "a", + PropertyType = new PrimitivePropertyType + { + Type = expected.TypeName, + Storage = StorageKind.Fixed, + Length = expected.Length, + Nullable = nullable, + }, + }, + }, + }; + + LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = { s } }); Layout layout = resolver.Resolve(new SchemaId(-1)); - Assert.AreEqual(1, layout.Columns.Length, "Json: {0}", expected.Json); - Assert.AreEqual(s.Name, layout.Name, "Json: {0}", expected.Json); - Assert.IsTrue(layout.ToString().Length > 0, "Json: {0}", expected.Json); + Assert.AreEqual(1, layout.Columns.Length, "Tag: {0}", expected.Tag); + Assert.AreEqual(s.Name, layout.Name, "Tag: {0}", expected.Tag); + Assert.IsTrue(layout.ToString().Length > 0, "Tag: {0}", expected.Tag); bool found = layout.TryFind("a", out LayoutColumn col); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Fixed, col.Storage, "Json: {0}", expected.Json); - Assert.AreEqual(expected.Length == 0, col.Type.IsFixed, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Fixed, col.Storage, "Tag: {0}", expected.Tag); + Assert.AreEqual(expected.Length == 0, col.Type.IsFixed, "Tag: {0}", expected.Tag); // Try writing a row using the layout. row.Reset(); @@ -120,7 +139,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(layout.SchemaId, header.SchemaId); RowCursor root = RowCursor.Create(ref row); - this.LayoutCodeSwitch( + LayoutCompilerUnitTests.LayoutCodeSwitch( col.Type.LayoutCode, ref row, ref root, @@ -128,8 +147,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit } catch (LayoutCompilationException) { - Assert.AreEqual(expected.TypeName, "null"); - Assert.AreEqual("false", nullable); + Assert.AreEqual(expected.TypeName, TypeKind.Null); + Assert.AreEqual(false, nullable); } } } @@ -167,7 +186,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { new RoundTripVariable.Expected { - Json = @"{'type': 'utf8', 'storage': 'variable', 'length': 100}", + Type = new PrimitivePropertyType { Type = TypeKind.Utf8, Storage = StorageKind.Variable, Length = 100 }, + Tag = "utf8", Short = MakeS(2), Value = MakeS(20), Long = MakeS(100), @@ -175,31 +195,45 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit }, new RoundTripVariable.Expected { - Json = @"{'type': 'binary', 'storage': 'variable', 'length': 100}", + Type = new PrimitivePropertyType { Type = TypeKind.Binary, Storage = StorageKind.Variable, Length = 100 }, + Tag = "binary", Short = MakeB(2), Value = MakeB(20), Long = MakeB(100), TooBig = MakeB(200), }, - new RoundTripVariable.Expected { Json = @"{'type': 'varint', 'storage': 'variable'}", Short = 1L, Value = 255L, Long = long.MaxValue }, - new RoundTripVariable.Expected { Json = @"{'type': 'varuint', 'storage': 'variable'}", Short = 1UL, Value = 255UL, Long = ulong.MaxValue }, + new RoundTripVariable.Expected + { + Type = new PrimitivePropertyType { Type = TypeKind.VarInt, Storage = StorageKind.Variable }, + Tag = "varint", Short = 1L, Value = 255L, Long = long.MaxValue + }, + new RoundTripVariable.Expected + { + Type = new PrimitivePropertyType { Type = TypeKind.VarUInt, Storage = StorageKind.Variable }, + Tag = "varuint", Short = 1UL, Value = 255UL, Long = ulong.MaxValue + }, }; RowBuffer row = new RowBuffer(LayoutCompilerUnitTests.InitialRowSize); foreach (RoundTripVariable.Expected expected in expectedSchemas) { - string propSchema = $@"{{'path': 'a', 'type': {expected.Json}}}, - {{'path': 'b', 'type': {expected.Json}}}, - {{'path': 'c', 'type': {expected.Json}}}"; - - string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{propSchema}] }}"; - Schema s = Schema.Parse(tableSchema); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = new List { s } }); + List propSchema = new List + { + new Property { Path = "a", PropertyType = expected.Type }, + new Property { Path = "b", PropertyType = expected.Type }, + new Property { Path = "c", PropertyType = expected.Type }, + }; + Schema s = new Schema + { + Name = "table", SchemaId = new SchemaId(-1), Properties = propSchema + }; + Namespace ns = new Namespace { Schemas = { s } }; + LayoutResolverNamespace resolver = new LayoutResolverNamespace(ns); Layout layout = resolver.Resolve(new SchemaId(-1)); bool found = layout.TryFind("a", out LayoutColumn col); - Assert.IsTrue(found, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); Assert.IsTrue(col.Type.AllowVariable); - Assert.AreEqual(StorageKind.Variable, col.Storage, "Json: {0}", expected.Json); + Assert.AreEqual(StorageKind.Variable, col.Storage, "Tag: {0}", expected.Tag); // Try writing a row using the layout. row.Reset(); @@ -210,8 +244,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(layout.SchemaId, header.SchemaId); RowCursor root = RowCursor.Create(ref row); - this.LayoutCodeSwitch( - col.Type.LayoutCode, ref row, ref root, new RoundTripVariable.Closure { Layout = layout, Col = col, Expected = expected }); + LayoutCompilerUnitTests.LayoutCodeSwitch( + col.Type.LayoutCode, + ref row, + ref root, + new RoundTripVariable.Closure { Layout = layout, Col = col, Expected = expected }); } } @@ -254,8 +291,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit foreach (RoundTripSparseOrdering.Expected field in permutation) { RowCursor root = RowCursor.Create(ref row); - this.LayoutCodeSwitch( - field.Type.LayoutCode, ref row, ref root, new RoundTripSparseOrdering.Closure { Expected = field, Json = json }); + LayoutCompilerUnitTests.LayoutCodeSwitch( + field.Type.LayoutCode, + ref row, + ref root, + new RoundTripSparseOrdering.Closure { Expected = field, Json = json }); } } } @@ -268,48 +308,51 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit // Test all sparse column types. RoundTripSparseSimple.Expected[] expectedSchemas = new[] { - new RoundTripSparseSimple.Expected { Json = @"{'type': 'null', 'storage': 'sparse'}", Value = NullValue.Default }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'bool', 'storage': 'sparse'}", Value = true }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'bool', 'storage': 'sparse'}", Value = false }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Null, Tag = "null", Value = NullValue.Default }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Boolean, Tag = "bool", Value = true }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Boolean, Tag = "bool", Value = false }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'int8', 'storage': 'sparse'}", Value = (sbyte)42 }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'int16', 'storage': 'sparse'}", Value = (short)42 }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'int32', 'storage': 'sparse'}", Value = 42 }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'int64', 'storage': 'sparse'}", Value = 42L }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'uint8', 'storage': 'sparse'}", Value = (byte)42 }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'uint16', 'storage': 'sparse'}", Value = (ushort)42 }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'uint32', 'storage': 'sparse'}", Value = 42U }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'uint64', 'storage': 'sparse'}", Value = 42UL }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'varint', 'storage': 'sparse'}", Value = 42L }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'varuint', 'storage': 'sparse'}", Value = 42UL }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Int8, Tag = "int8", Value = (sbyte)42 }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Int16, Tag = "int16", Value = (short)42 }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Int32, Tag = "int32", Value = 42 }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Int64, Tag = "int64", Value = 42L }, + new RoundTripSparseSimple.Expected { Type = TypeKind.UInt8, Tag = "uint8", Value = (byte)42 }, + new RoundTripSparseSimple.Expected { Type = TypeKind.UInt16, Tag = "uint16", Value = (ushort)42 }, + new RoundTripSparseSimple.Expected { Type = TypeKind.UInt32, Tag = "uint32", Value = 42U }, + new RoundTripSparseSimple.Expected { Type = TypeKind.UInt64, Tag = "uint64", Value = 42UL }, + new RoundTripSparseSimple.Expected { Type = TypeKind.VarInt, Tag = "varint", Value = 42L }, + new RoundTripSparseSimple.Expected { Type = TypeKind.VarUInt, Tag = "varuint", Value = 42UL }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'float32', 'storage': 'sparse'}", Value = 4.2F }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'float64', 'storage': 'sparse'}", Value = 4.2 }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'float128', 'storage': 'sparse'}", Value = new Float128(0, 42) }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'decimal', 'storage': 'sparse'}", Value = 4.2M }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Float32, Tag = "float32", Value = 4.2F }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Float64, Tag = "float64", Value = 4.2 }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Float128, Tag = "float128", Value = new Float128(0, 42) }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Decimal, Tag = "decimal", Value = 4.2M }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'datetime', 'storage': 'sparse'}", Value = DateTime.UtcNow }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'unixdatetime', 'storage': 'sparse'}", Value = new UnixDateTime(42) }, + new RoundTripSparseSimple.Expected { Type = TypeKind.DateTime, Tag = "datetime", Value = DateTime.UtcNow }, + new RoundTripSparseSimple.Expected { Type = TypeKind.UnixDateTime, Tag = "unixdatetime", Value = new UnixDateTime(42) }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'guid', 'storage': 'sparse'}", Value = Guid.NewGuid() }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'mongodbobjectid', 'storage': 'sparse'}", Value = new MongoDbObjectId(0, 42) }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Guid, Tag = "guid", Value = Guid.NewGuid() }, + new RoundTripSparseSimple.Expected { Type = TypeKind.MongoDbObjectId, Tag = "mongodbobjectid", Value = new MongoDbObjectId(0, 42) }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'utf8', 'storage': 'sparse'}", Value = "AB" }, - new RoundTripSparseSimple.Expected { Json = @"{'type': 'binary', 'storage': 'sparse'}", Value = new byte[] { 0x01, 0x02 } }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Utf8, Tag = "utf8", Value = "AB" }, + new RoundTripSparseSimple.Expected { Type = TypeKind.Binary, Tag = "binary", Value = new byte[] { 0x01, 0x02 } }, }; RowBuffer row = new RowBuffer(LayoutCompilerUnitTests.InitialRowSize); foreach (RoundTripSparseSimple.Expected expected in expectedSchemas) { - string propSchema = $@"{{'path': 'a', 'type': {expected.Json}}}"; - string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{propSchema}] }}"; - Schema s = Schema.Parse(tableSchema); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = new List { s } }); + PropertyType propType = new PrimitivePropertyType { Type = expected.Type, Storage = StorageKind.Sparse }; + Property propSchema = new Property { Path = "a", PropertyType = propType }; + Schema s = new Schema + { + Name = "table", SchemaId = new SchemaId(-1), Properties = { propSchema } + }; + LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = { s } }); Layout layout = resolver.Resolve(new SchemaId(-1)); - Assert.AreEqual(1, layout.Columns.Length, "Json: {0}", expected.Json); + Assert.AreEqual(1, layout.Columns.Length, "Tag: {0}", expected.Tag); bool found = layout.TryFind("a", out LayoutColumn col); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, col.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, col.Storage, "Tag: {0}", expected.Tag); // Try writing a row using the layout. row.Reset(); @@ -320,8 +363,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(layout.SchemaId, header.SchemaId); RowCursor root = RowCursor.Create(ref row); - this.LayoutCodeSwitch( - col.Type.LayoutCode, ref row, ref root, new RoundTripSparseSimple.Closure { Col = col, Expected = expected }); + LayoutCompilerUnitTests.LayoutCodeSwitch( + col.Type.LayoutCode, + ref row, + ref root, + new RoundTripSparseSimple.Closure { Col = col, Expected = expected }); } } @@ -329,30 +375,50 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [Owner("jthunter")] public void ParseSchemaUDT() { - string namespaceJson = @" - {'name': 'myNamespace', - 'schemas': [ - {'name': 'udtA', 'id': 1, 'type': 'schema', 'options': { 'disallowUnschematized': false }, - 'properties': [ - { 'path': 'a', 'type': { 'type': 'int8', 'storage': 'fixed' }}, - { 'path': 'b', 'type': { 'type': 'utf8', 'storage': 'variable', 'length': 100 }} - ] + Namespace n1 = new Namespace + { + Name = "myNamespace", + Schemas = + { + new Schema + { + Name = "udtA", + SchemaId = new SchemaId(1), + Options = new SchemaOptions { DisallowUnschematized = false }, + Properties = + { + new Property + { + Path = "a", + PropertyType = new PrimitivePropertyType { Type = TypeKind.Int8, Storage = StorageKind.Fixed } + }, + new Property + { + Path = "b", + PropertyType = new PrimitivePropertyType { Type = TypeKind.Utf8, Storage = StorageKind.Variable, Length = 100 } + } + } }, - {'name': 'udtB', 'id': 2, 'type': 'schema'}, - {'name': 'udtB', 'id': 3, 'type': 'schema'}, - {'name': 'udtB', 'id': 4, 'type': 'schema'}, - {'name': 'table', 'id': -1, 'type': 'schema', - 'properties': [ - { 'path': 'u', 'type': { 'type': 'schema', 'name': 'udtA' }}, - { 'path': 'v', 'type': { 'type': 'schema', 'name': 'udtB', 'id': 3 }}, - ] + new Schema { Name = "udtB", SchemaId = new SchemaId(2) }, + new Schema { Name = "udtB", SchemaId = new SchemaId(3), }, + new Schema { Name = "udtB", SchemaId = new SchemaId(4), }, + new Schema + { + Name = "table", + SchemaId = new SchemaId(-1), + Properties = + { + new Property { Path = "u", PropertyType = new UdtPropertyType { Name = "udtA" } }, + new Property + { + Path = "v", PropertyType = new UdtPropertyType { Name = "udtB", SchemaId = new SchemaId(3) } + }, + } } - ] - }"; + } + }; - Namespace n1 = Namespace.Parse(namespaceJson); - - string tag = $"Json: {namespaceJson}"; + string tag = "Tag: myNamespace"; RowBuffer row = new RowBuffer(LayoutCompilerUnitTests.InitialRowSize); Schema s = n1.Schemas.Find(x => x.Name == "table"); @@ -395,7 +461,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit ResultAssert.IsSuccess(r, tag); Assert.AreSame(udtLayout, udtScope2.Layout, tag); Assert.AreEqual(udtScope1.ScopeType, udtScope2.ScopeType, tag); - Assert.AreEqual(udtScope1.start, udtScope2.start, tag); + Assert.AreEqual(udtScope1.start, udtScope2.start, tag); Assert.AreEqual(udtScope1.Immutable, udtScope2.Immutable, tag); var expectedSchemas = new[] @@ -404,7 +470,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { Storage = StorageKind.Fixed, Path = "a", - FixedExpected = new RoundTripFixed.Expected { Json = @"{ 'type': 'int8', 'storage': 'fixed' }", Value = (sbyte)42 }, + FixedExpected = new RoundTripFixed.Expected { Tag = @"{ 'type': 'int8', 'storage': 'fixed' }", Value = (sbyte)42 }, VariableExpected = default(RoundTripVariable.Expected), }, new @@ -412,7 +478,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Storage = StorageKind.Variable, Path = "b", FixedExpected = default(RoundTripFixed.Expected), - VariableExpected = new RoundTripVariable.Expected { Json = @"{ 'type': 'utf8', 'storage': 'variable' }", Value = "AB" }, + VariableExpected = new RoundTripVariable.Expected { Tag = @"{ 'type': 'utf8', 'storage': 'variable' }", Value = "AB" }, }, }; @@ -424,16 +490,22 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit switch (storage) { case StorageKind.Fixed: - this.LayoutCodeSwitch( - col.Type.LayoutCode, ref row, ref udtScope1, new RoundTripFixed.Closure + LayoutCompilerUnitTests.LayoutCodeSwitch( + col.Type.LayoutCode, + ref row, + ref udtScope1, + new RoundTripFixed.Closure { Col = col, Expected = expected.FixedExpected, }); break; case StorageKind.Variable: - this.LayoutCodeSwitch( - col.Type.LayoutCode, ref row, ref udtScope1, new RoundTripVariable.Closure + LayoutCompilerUnitTests.LayoutCodeSwitch( + col.Type.LayoutCode, + ref row, + ref udtScope1, + new RoundTripVariable.Closure { Col = col, Layout = layout, Expected = expected.VariableExpected, @@ -471,26 +543,33 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void ParseSchemaSparseObject() { // Test all fixed column types. - RoundTripSparseObject.Expected[] expectedSchemas = new[] + RoundTripSparseObject.Expected[] expectedSchemas = { - new RoundTripSparseObject.Expected { Json = @"{'path': 'b', 'type': {'type': 'int8'}}", Value = (sbyte)42 }, + new RoundTripSparseObject.Expected { Type = TypeKind.Int8, Tag = "int8", Value = (sbyte)42 }, }; RowBuffer row = new RowBuffer(LayoutCompilerUnitTests.InitialRowSize); foreach (RoundTripSparseObject.Expected expected in expectedSchemas) { - string objectColumnSchema = $"{{'path': 'a', 'type': {{'type': 'object', 'properties': [{expected.Json}] }} }}"; - string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{objectColumnSchema}] }}"; - Schema s = Schema.Parse(tableSchema); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = new List { s } }); + Property nestProp = new Property { Path = "b", PropertyType = new PrimitivePropertyType { Type = expected.Type } }; + Property objectColumnSchema = new Property + { + Path = "a", PropertyType = new ObjectPropertyType { Properties = { nestProp } } + }; + Schema s = new Schema + { + Name = "table", SchemaId = new SchemaId(-1), + Properties = { objectColumnSchema } + }; + LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = { s } }); Layout layout = resolver.Resolve(new SchemaId(-1)); - Assert.AreEqual(1, layout.Columns.Length, "Json: {0}", expected.Json); + Assert.AreEqual(1, layout.Columns.Length, "Tag: {0}", expected.Tag); bool found = layout.TryFind("a", out LayoutColumn objCol); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, objCol.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, objCol.Storage, "Tag: {0}", expected.Tag); found = layout.TryFind("a.b", out LayoutColumn col); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, col.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, col.Storage, "Tag: {0}", expected.Tag); // Try writing a row using the layout. row.Reset(); @@ -501,8 +580,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(layout.SchemaId, header.SchemaId); RowCursor root = RowCursor.Create(ref row); - this.LayoutCodeSwitch( - col.Type.LayoutCode, ref row, ref root, new RoundTripSparseObject.Closure + LayoutCompilerUnitTests.LayoutCodeSwitch( + col.Type.LayoutCode, + ref row, + ref root, + new RoundTripSparseObject.Closure { ObjCol = objCol, Col = col, @@ -520,41 +602,58 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { new RoundTripSparseObjectMulti.Expected { - Json = @"{'path': 'b', 'type': {'type': 'int8'}}", - Props = new[] + Tag = @"{'path': 'b', 'type': {'type': 'int8'}}", + Props = new List { - new RoundTripSparseObjectMulti.Property { Path = "a.b", Value = (sbyte)42 }, + new Property { Path = "b", PropertyType = new PrimitivePropertyType { Type = TypeKind.Int8 } }, + }, + ExpectedProps = new[] + { + new RoundTripSparseObjectMulti.ExpectedProperty { Path = "a.b", Value = (sbyte)42 }, }, }, new RoundTripSparseObjectMulti.Expected { - Json = @"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'utf8'}}", - Props = new[] + Tag = @"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'utf8'}}", + Props = new List { - new RoundTripSparseObjectMulti.Property { Path = "a.b", Value = (sbyte)42 }, - new RoundTripSparseObjectMulti.Property { Path = "a.c", Value = "abc" }, + new Property { Path = "b", PropertyType = new PrimitivePropertyType { Type = TypeKind.Int8 } }, + new Property { Path = "c", PropertyType = new PrimitivePropertyType { Type = TypeKind.Utf8 } }, + }, + ExpectedProps = new[] + { + new RoundTripSparseObjectMulti.ExpectedProperty { Path = "a.b", Value = (sbyte)42 }, + new RoundTripSparseObjectMulti.ExpectedProperty { Path = "a.c", Value = "abc" }, }, }, new RoundTripSparseObjectMulti.Expected { - Json = @"{'path': 'b', 'type': {'type': 'int8'}}, + Tag = @"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'bool'}}, {'path': 'd', 'type': {'type': 'binary'}}, {'path': 'e', 'type': {'type': 'null'}}", - Props = new[] + Props = new List { - new RoundTripSparseObjectMulti.Property { Path = "a.b", Value = (sbyte)42 }, - new RoundTripSparseObjectMulti.Property { Path = "a.c", Value = true }, - new RoundTripSparseObjectMulti.Property { Path = "a.d", Value = new byte[] { 0x01, 0x02, 0x03 } }, - new RoundTripSparseObjectMulti.Property { Path = "a.e", Value = NullValue.Default }, + new Property { Path = "b", PropertyType = new PrimitivePropertyType { Type = TypeKind.Int8 } }, + new Property { Path = "c", PropertyType = new PrimitivePropertyType { Type = TypeKind.Boolean } }, + new Property { Path = "d", PropertyType = new PrimitivePropertyType { Type = TypeKind.Binary } }, + new Property { Path = "e", PropertyType = new PrimitivePropertyType { Type = TypeKind.Null } }, + }, + ExpectedProps = new[] + { + new RoundTripSparseObjectMulti.ExpectedProperty { Path = "a.b", Value = (sbyte)42 }, + new RoundTripSparseObjectMulti.ExpectedProperty { Path = "a.c", Value = true }, + new RoundTripSparseObjectMulti.ExpectedProperty { Path = "a.d", Value = new byte[] { 0x01, 0x02, 0x03 } }, + new RoundTripSparseObjectMulti.ExpectedProperty { Path = "a.e", Value = NullValue.Default }, }, }, new RoundTripSparseObjectMulti.Expected { - Json = @"{'path': 'b', 'type': {'type': 'object'}}", - Props = new[] + Tag = @"{'path': 'b', 'type': {'type': 'object'}}", + Props = new List { new Property { Path = "b", PropertyType = new ObjectPropertyType() } }, + ExpectedProps = new[] { - new RoundTripSparseObjectMulti.Property { Path = "a.b" }, + new RoundTripSparseObjectMulti.ExpectedProperty { Path = "a.b" }, }, }, }; @@ -562,15 +661,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit RowBuffer row = new RowBuffer(LayoutCompilerUnitTests.InitialRowSize); foreach (RoundTripSparseObjectMulti.Expected expected in expectedSchemas) { - string objectColumnSchema = $"{{'path': 'a', 'type': {{'type': 'object', 'properties': [{expected.Json}] }} }}"; - string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{objectColumnSchema}] }}"; - Schema s = Schema.Parse(tableSchema); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = new List { s } }); + Property objectColumnSchema = new Property { Path = "a", PropertyType = new ObjectPropertyType { Properties = expected.Props } }; + Schema s = new Schema { Name = "table", SchemaId = new SchemaId(-1), Properties = { objectColumnSchema } }; + LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = { s } }); Layout layout = resolver.Resolve(new SchemaId(-1)); - Assert.AreEqual(1, layout.Columns.Length, "Json: {0}", expected.Json); + Assert.AreEqual(1, layout.Columns.Length, "Tag: {0}", expected.Tag); bool found = layout.TryFind("a", out LayoutColumn objCol); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, objCol.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, objCol.Storage, "Tag: {0}", expected.Tag); // Try writing a row using the layout. row.Reset(); @@ -585,22 +683,25 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.IsNotNull(objT); RowCursor.Create(ref row, out RowCursor field).Find(ref row, objCol.Path); Result r = objT.ReadScope(ref row, ref field, out RowCursor scope); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); // Write the object and the nested column. r = objT.WriteScope(ref row, ref field, objCol.TypeArgs, out scope); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); - foreach (IEnumerable permutation in expected.Props.Permute()) + foreach (IEnumerable permutation in expected.ExpectedProps.Permute()) { - foreach (RoundTripSparseObjectMulti.Property prop in permutation) + foreach (RoundTripSparseObjectMulti.ExpectedProperty prop in permutation) { found = layout.TryFind(prop.Path, out LayoutColumn col); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, col.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, col.Storage, "Tag: {0}", expected.Tag); - this.LayoutCodeSwitch( - col.Type.LayoutCode, ref row, ref scope, new RoundTripSparseObjectMulti.Closure + LayoutCompilerUnitTests.LayoutCodeSwitch( + col.Type.LayoutCode, + ref row, + ref scope, + new RoundTripSparseObjectMulti.Closure { Col = col, Prop = prop, @@ -613,18 +714,18 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit UtfAnyString otherColumnPath = "not-" + objCol.Path; field.Clone(out RowCursor otherColumn).Find(ref row, otherColumnPath); r = LayoutType.Boolean.WriteSparse(ref row, ref otherColumn, true); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); // Overwrite the whole scope. r = LayoutType.Null.WriteSparse(ref row, ref field, NullValue.Default); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = objT.ReadScope(ref row, ref field, out RowCursor _); - ResultAssert.TypeMismatch(r, "Json: {0}", expected.Json); + ResultAssert.TypeMismatch(r, "Tag: {0}", expected.Tag); // Read the thing after the scope and verify it is still there. field.Clone(out otherColumn).Find(ref row, otherColumnPath); r = LayoutType.Boolean.ReadSparse(ref row, ref otherColumn, out bool notScope); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); Assert.IsTrue(notScope); } } @@ -638,41 +739,61 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { new RoundTripSparseObjectNested.Expected { - Json = @"{'path': 'c', 'type': {'type': 'int8'}}", - Props = new[] + Tag = @"{'path': 'c', 'type': {'type': 'int8'}}", + Props = new List { - new RoundTripSparseObjectNested.Property { Path = "a.b.c", Value = (sbyte)42 }, + new Property { Path = "c", PropertyType = new PrimitivePropertyType { Type = TypeKind.Int8 } } + }, + ExpectedProps = new[] + { + new RoundTripSparseObjectNested.ExpectedProperty { Path = "a.b.c", Value = (sbyte)42 }, }, }, new RoundTripSparseObjectNested.Expected { - Json = @"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'utf8'}}", - Props = new[] + Tag = @"{'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'utf8'}}", + Props = new List { - new RoundTripSparseObjectNested.Property { Path = "a.b.b", Value = (sbyte)42 }, - new RoundTripSparseObjectNested.Property { Path = "a.b.c", Value = "abc" }, + new Property { Path = "b", PropertyType = new PrimitivePropertyType { Type = TypeKind.Int8 } }, + new Property { Path = "c", PropertyType = new PrimitivePropertyType { Type = TypeKind.Utf8 } }, + }, + ExpectedProps = new[] + { + new RoundTripSparseObjectNested.ExpectedProperty { Path = "a.b.b", Value = (sbyte)42 }, + new RoundTripSparseObjectNested.ExpectedProperty { Path = "a.b.c", Value = "abc" }, }, }, new RoundTripSparseObjectNested.Expected { - Json = @"{'path': 'b', 'type': {'type': 'int8'}}, + Tag = @" {'path': 'b', 'type': {'type': 'int8'}}, {'path': 'c', 'type': {'type': 'bool'}}, {'path': 'd', 'type': {'type': 'binary'}}, {'path': 'e', 'type': {'type': 'null'}}", - Props = new[] + Props = new List { - new RoundTripSparseObjectNested.Property { Path = "a.b.b", Value = (sbyte)42 }, - new RoundTripSparseObjectNested.Property { Path = "a.b.c", Value = true }, - new RoundTripSparseObjectNested.Property { Path = "a.b.d", Value = new byte[] { 0x01, 0x02, 0x03 } }, - new RoundTripSparseObjectNested.Property { Path = "a.b.e", Value = NullValue.Default }, + new Property { Path = "b", PropertyType = new PrimitivePropertyType { Type = TypeKind.Int8 } }, + new Property { Path = "c", PropertyType = new PrimitivePropertyType { Type = TypeKind.Boolean } }, + new Property { Path = "d", PropertyType = new PrimitivePropertyType { Type = TypeKind.Binary } }, + new Property { Path = "e", PropertyType = new PrimitivePropertyType { Type = TypeKind.Null } }, + }, + ExpectedProps = new[] + { + new RoundTripSparseObjectNested.ExpectedProperty { Path = "a.b.b", Value = (sbyte)42 }, + new RoundTripSparseObjectNested.ExpectedProperty { Path = "a.b.c", Value = true }, + new RoundTripSparseObjectNested.ExpectedProperty { Path = "a.b.d", Value = new byte[] { 0x01, 0x02, 0x03 } }, + new RoundTripSparseObjectNested.ExpectedProperty { Path = "a.b.e", Value = NullValue.Default }, }, }, new RoundTripSparseObjectNested.Expected { - Json = @"{'path': 'b', 'type': {'type': 'object'}}", - Props = new[] + Tag = @"{'path': 'b', 'type': {'type': 'object'}}", + Props = new List { - new RoundTripSparseObjectNested.Property { Path = "a.b.b" }, + new Property { Path = "b", PropertyType = new ObjectPropertyType() }, + }, + ExpectedProps = new[] + { + new RoundTripSparseObjectNested.ExpectedProperty { Path = "a.b.b" }, }, }, }; @@ -680,18 +801,26 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit RowBuffer row = new RowBuffer(LayoutCompilerUnitTests.InitialRowSize); foreach (RoundTripSparseObjectNested.Expected expected in expectedSchemas) { - string nestedColumnSchema = $"{{'path': 'b', 'type': {{'type': 'object', 'properties': [{expected.Json}] }} }}"; - string objectColumnSchema = $"{{'path': 'a', 'type': {{'type': 'object', 'properties': [{nestedColumnSchema}] }} }}"; - string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{objectColumnSchema}] }}"; - Schema s = Schema.Parse(tableSchema); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = new List { s } }); + Property nestedColumnSchema = new Property + { + Path = "b", PropertyType = new ObjectPropertyType { Properties = expected.Props } + }; + Property objectColumnSchema = new Property + { + Path = "a", PropertyType = new ObjectPropertyType { Properties = { nestedColumnSchema } }, + }; + Schema s = new Schema + { + Name = "table", SchemaId = new SchemaId(-1), Properties = { objectColumnSchema } + }; + LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = { s } }); Layout layout = resolver.Resolve(new SchemaId(-1)); bool found = layout.TryFind("a", out LayoutColumn objCol); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, objCol.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, objCol.Storage, "Tag: {0}", expected.Tag); found = layout.TryFind("a.b", out LayoutColumn objCol2); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, objCol2.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, objCol2.Storage, "Tag: {0}", expected.Tag); // Try writing a row using the layout. row.Reset(); @@ -707,18 +836,21 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.IsNotNull(objT); root.Clone(out RowCursor field).Find(ref row, objCol.Path); Result r = objT.WriteScope(ref row, ref field, objCol.TypeArgs, out RowCursor _); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); - foreach (IEnumerable permutation in expected.Props.Permute()) + foreach (IEnumerable permutation in expected.ExpectedProps.Permute()) { - foreach (RoundTripSparseObjectNested.Property prop in permutation) + foreach (RoundTripSparseObjectNested.ExpectedProperty prop in permutation) { found = layout.TryFind(prop.Path, out LayoutColumn col); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, col.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, col.Storage, "Tag: {0}", expected.Tag); - this.LayoutCodeSwitch( - col.Type.LayoutCode, ref row, ref root, new RoundTripSparseObjectNested.Closure + LayoutCompilerUnitTests.LayoutCodeSwitch( + col.Type.LayoutCode, + ref row, + ref root, + new RoundTripSparseObjectNested.Closure { Col = col, Prop = prop, @@ -738,64 +870,106 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { new RoundTripSparseArray.Expected { - Json = @"array[null]", - Type = LayoutType.Null, + Tag = @"array[null]", + Type = TypeKind.Null, + LayoutType = LayoutType.Null, Value = new List { NullValue.Default, NullValue.Default, NullValue.Default }, }, - new RoundTripSparseArray.Expected { Json = @"array[bool]", Type = LayoutType.Boolean, Value = new List { true, false, true } }, - - new RoundTripSparseArray.Expected { Json = @"array[int8]", Type = LayoutType.Int8, Value = new List { (sbyte)42, (sbyte)43, (sbyte)44 } }, - new RoundTripSparseArray.Expected { Json = @"array[int16]", Type = LayoutType.Int16, Value = new List { (short)42, (short)43, (short)44 } }, - new RoundTripSparseArray.Expected { Json = @"array[int32]", Type = LayoutType.Int32, Value = new List { 42, 43, 44 } }, - new RoundTripSparseArray.Expected { Json = @"array[int64]", Type = LayoutType.Int64, Value = new List { 42L, 43L, 44L } }, - new RoundTripSparseArray.Expected { Json = @"array[uint8]", Type = LayoutType.UInt8, Value = new List { (byte)42, (byte)43, (byte)44 } }, - new RoundTripSparseArray.Expected { Json = @"array[uint16]", Type = LayoutType.UInt16, Value = new List { (ushort)42, (ushort)43, (ushort)44 } }, - new RoundTripSparseArray.Expected { Json = @"array[uint32]", Type = LayoutType.UInt32, Value = new List { 42u, 43u, 44u } }, - new RoundTripSparseArray.Expected { Json = @"array[uint64]", Type = LayoutType.UInt64, Value = new List { 42UL, 43UL, 44UL } }, - - new RoundTripSparseArray.Expected { Json = @"array[varint]", Type = LayoutType.VarInt, Value = new List { 42L, 43L, 44L } }, - new RoundTripSparseArray.Expected { Json = @"array[varuint]", Type = LayoutType.VarUInt, Value = new List { 42UL, 43UL, 44UL } }, - - new RoundTripSparseArray.Expected { Json = @"array[float32]", Type = LayoutType.Float32, Value = new List { 4.2F, 4.3F, 4.4F } }, - new RoundTripSparseArray.Expected { Json = @"array[float64]", Type = LayoutType.Float64, Value = new List { 4.2, 4.3, 4.4 } }, new RoundTripSparseArray.Expected { - Json = @"array[float128]", - Type = LayoutType.Float128, + Tag = @"array[bool]", Type = TypeKind.Boolean, LayoutType = LayoutType.Boolean, Value = new List { true, false, true } + }, + + new RoundTripSparseArray.Expected + { + Tag = @"array[int8]", Type = TypeKind.Int8, LayoutType = LayoutType.Int8, + Value = new List { (sbyte)42, (sbyte)43, (sbyte)44 } + }, + new RoundTripSparseArray.Expected + { + Tag = @"array[int16]", Type = TypeKind.Int16, LayoutType = LayoutType.Int16, + Value = new List { (short)42, (short)43, (short)44 } + }, + new RoundTripSparseArray.Expected + { Tag = @"array[int32]", Type = TypeKind.Int32, LayoutType = LayoutType.Int32, Value = new List { 42, 43, 44 } }, + new RoundTripSparseArray.Expected + { Tag = @"array[int64]", Type = TypeKind.Int64, LayoutType = LayoutType.Int64, Value = new List { 42L, 43L, 44L } }, + new RoundTripSparseArray.Expected + { + Tag = @"array[uint8]", Type = TypeKind.UInt8, LayoutType = LayoutType.UInt8, + Value = new List { (byte)42, (byte)43, (byte)44 } + }, + new RoundTripSparseArray.Expected + { + Tag = @"array[uint16]", Type = TypeKind.UInt16, LayoutType = LayoutType.UInt16, + Value = new List { (ushort)42, (ushort)43, (ushort)44 } + }, + new RoundTripSparseArray.Expected + { Tag = @"array[uint32]", Type = TypeKind.UInt32, LayoutType = LayoutType.UInt32, Value = new List { 42u, 43u, 44u } }, + new RoundTripSparseArray.Expected + { Tag = @"array[uint64]", Type = TypeKind.UInt64, LayoutType = LayoutType.UInt64, Value = new List { 42UL, 43UL, 44UL } }, + + new RoundTripSparseArray.Expected + { Tag = @"array[varint]", Type = TypeKind.VarInt, LayoutType = LayoutType.VarInt, Value = new List { 42L, 43L, 44L } }, + new RoundTripSparseArray.Expected + { + Tag = @"array[varuint]", Type = TypeKind.VarUInt, LayoutType = LayoutType.VarUInt, Value = new List { 42UL, 43UL, 44UL } + }, + + new RoundTripSparseArray.Expected + { + Tag = @"array[float32]", Type = TypeKind.Float32, LayoutType = LayoutType.Float32, Value = new List { 4.2F, 4.3F, 4.4F } + }, + new RoundTripSparseArray.Expected + { Tag = @"array[float64]", Type = TypeKind.Float64, LayoutType = LayoutType.Float64, Value = new List { 4.2, 4.3, 4.4 } }, + new RoundTripSparseArray.Expected + { + Tag = @"array[float128]", + Type = TypeKind.Float128, + LayoutType = LayoutType.Float128, Value = new List { new Float128(0, 42), new Float128(0, 43), new Float128(0, 44) }, }, - new RoundTripSparseArray.Expected { Json = @"array[decimal]", Type = LayoutType.Decimal, Value = new List { 4.2M, 4.3M, 4.4M } }, + new RoundTripSparseArray.Expected + { + Tag = @"array[decimal]", Type = TypeKind.Decimal, LayoutType = LayoutType.Decimal, Value = new List { 4.2M, 4.3M, 4.4M } + }, new RoundTripSparseArray.Expected { - Json = @"array[datetime]", - Type = LayoutType.DateTime, + Tag = @"array[datetime]", + Type = TypeKind.DateTime, + LayoutType = LayoutType.DateTime, Value = new List { DateTime.UtcNow, DateTime.UtcNow.AddTicks(1), DateTime.UtcNow.AddTicks(2) }, }, new RoundTripSparseArray.Expected { - Json = @"array[unixdatetime]", - Type = LayoutType.UnixDateTime, + Tag = @"array[unixdatetime]", + Type = TypeKind.UnixDateTime, + LayoutType = LayoutType.UnixDateTime, Value = new List { new UnixDateTime(1), new UnixDateTime(2), new UnixDateTime(3) }, }, new RoundTripSparseArray.Expected { - Json = @"array[guid]", - Type = LayoutType.Guid, + Tag = @"array[guid]", + Type = TypeKind.Guid, + LayoutType = LayoutType.Guid, Value = new List { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }, }, new RoundTripSparseArray.Expected { - Json = @"array[mongodbobjectid]", - Type = LayoutType.MongoDbObjectId, + Tag = @"array[mongodbobjectid]", + Type = TypeKind.MongoDbObjectId, + LayoutType = LayoutType.MongoDbObjectId, Value = new List { new MongoDbObjectId(0, 1), new MongoDbObjectId(0, 2), new MongoDbObjectId(0, 3) }, }, - new RoundTripSparseArray.Expected { Json = @"array[utf8]", Type = LayoutType.Utf8, Value = new List { "abc", "def", "xyz" } }, + new RoundTripSparseArray.Expected + { Tag = @"array[utf8]", Type = TypeKind.Utf8, LayoutType = LayoutType.Utf8, Value = new List { "abc", "def", "xyz" } }, new RoundTripSparseArray.Expected { - Json = @"array[binary]", - Type = LayoutType.Binary, + Tag = @"array[binary]", + Type = TypeKind.Binary, + LayoutType = LayoutType.Binary, Value = new List { new byte[] { 0x01, 0x02 }, new byte[] { 0x03, 0x04 }, new byte[] { 0x05, 0x06 } }, }, }; @@ -805,20 +979,19 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { foreach (Type arrT in new[] { typeof(LayoutTypedArray), typeof(LayoutArray) }) { - string arrayColumnSchema = @"{'path': 'a', 'type': {'type': 'array', 'items': {'type': 'any'}} }"; + Property arrayColumnSchema = new Property { Path = "a", PropertyType = new ArrayPropertyType() }; if (arrT == typeof(LayoutTypedArray)) { - arrayColumnSchema = $@"{{'path': 'a', 'type': {{'type': 'array', - 'items': {{'type': '{expected.Type.Name}', 'nullable': false }}}} }}"; + ((ArrayPropertyType)arrayColumnSchema.PropertyType).Items = + new PrimitivePropertyType { Type = expected.Type, Nullable = false }; } - string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{arrayColumnSchema}] }}"; - Schema s = Schema.Parse(tableSchema); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = new List { s } }); + Schema s = new Schema { Name = "table", SchemaId = new SchemaId(-1), Properties = { arrayColumnSchema } }; + LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = { s } }); Layout layout = resolver.Resolve(new SchemaId(-1)); bool found = layout.TryFind("a", out LayoutColumn arrCol); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, arrCol.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, arrCol.Storage, "Tag: {0}", expected.Tag); // Try writing a row using the layout. row.Reset(); @@ -829,8 +1002,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(layout.SchemaId, header.SchemaId); RowCursor root = RowCursor.Create(ref row); - this.LayoutCodeSwitch( - expected.Type.LayoutCode, ref row, ref root, new RoundTripSparseArray.Closure + LayoutCompilerUnitTests.LayoutCodeSwitch( + expected.LayoutType.LayoutCode, + ref row, + ref root, + new RoundTripSparseArray.Closure { ArrCol = arrCol, Expected = expected, @@ -847,27 +1023,57 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit // Test all fixed column types. RoundTripSparseSet.Expected[] expectedSchemas = new[] { - new RoundTripSparseSet.Expected { Json = @"set[null]", Type = LayoutType.Null, Value = new List { NullValue.Default } }, - new RoundTripSparseSet.Expected { Json = @"set[bool]", Type = LayoutType.Boolean, Value = new List { false, true } }, + new RoundTripSparseSet.Expected + { Tag = @"set[null]", Type = TypeKind.Null, LayoutType = LayoutType.Null, Value = new List { NullValue.Default } }, + new RoundTripSparseSet.Expected + { Tag = @"set[bool]", Type = TypeKind.Boolean, LayoutType = LayoutType.Boolean, Value = new List { false, true } }, - new RoundTripSparseSet.Expected { Json = @"set[int8]", Type = LayoutType.Int8, Value = new List { (sbyte)42, (sbyte)43, (sbyte)44 } }, - - new RoundTripSparseSet.Expected { Json = @"set[int16]", Type = LayoutType.Int16, Value = new List { (short)42, (short)43, (short)44 } }, - new RoundTripSparseSet.Expected { Json = @"set[int32]", Type = LayoutType.Int32, Value = new List { 42, 43, 44 } }, - new RoundTripSparseSet.Expected { Json = @"set[int64]", Type = LayoutType.Int64, Value = new List { 42L, 43L, 44L } }, - new RoundTripSparseSet.Expected { Json = @"set[uint8]", Type = LayoutType.UInt8, Value = new List { (byte)42, (byte)43, (byte)44 } }, - new RoundTripSparseSet.Expected { Json = @"set[uint16]", Type = LayoutType.UInt16, Value = new List { (ushort)42, (ushort)43, (ushort)44 } }, - new RoundTripSparseSet.Expected { Json = @"set[uint32]", Type = LayoutType.UInt32, Value = new List { 42u, 43u, 44u } }, - new RoundTripSparseSet.Expected { Json = @"set[uint64]", Type = LayoutType.UInt64, Value = new List { 42UL, 43UL, 44UL } }, - - new RoundTripSparseSet.Expected { Json = @"set[varint]", Type = LayoutType.VarInt, Value = new List { 42L, 43L, 44L } }, - new RoundTripSparseSet.Expected { Json = @"set[varuint]", Type = LayoutType.VarUInt, Value = new List { 42UL, 43UL, 44UL } }, - - new RoundTripSparseSet.Expected { Json = @"set[float32]", Type = LayoutType.Float32, Value = new List { 4.2F, 4.3F, 4.4F } }, new RoundTripSparseSet.Expected { - Json = @"set[float64]", - Type = LayoutType.Float64, + Tag = @"set[int8]", Type = TypeKind.Int8, LayoutType = LayoutType.Int8, + Value = new List { (sbyte)42, (sbyte)43, (sbyte)44 } + }, + + new RoundTripSparseSet.Expected + { + Tag = @"set[int16]", Type = TypeKind.Int16, LayoutType = LayoutType.Int16, + Value = new List { (short)42, (short)43, (short)44 } + }, + new RoundTripSparseSet.Expected + { Tag = @"set[int32]", Type = TypeKind.Int32, LayoutType = LayoutType.Int32, Value = new List { 42, 43, 44 } }, + new RoundTripSparseSet.Expected + { Tag = @"set[int64]", Type = TypeKind.Int64, LayoutType = LayoutType.Int64, Value = new List { 42L, 43L, 44L } }, + new RoundTripSparseSet.Expected + { + Tag = @"set[uint8]", Type = TypeKind.UInt8, LayoutType = LayoutType.UInt8, + Value = new List { (byte)42, (byte)43, (byte)44 } + }, + new RoundTripSparseSet.Expected + { + Tag = @"set[uint16]", Type = TypeKind.UInt16, LayoutType = LayoutType.UInt16, + Value = new List { (ushort)42, (ushort)43, (ushort)44 } + }, + new RoundTripSparseSet.Expected + { Tag = @"set[uint32]", Type = TypeKind.UInt32, LayoutType = LayoutType.UInt32, Value = new List { 42u, 43u, 44u } }, + new RoundTripSparseSet.Expected + { Tag = @"set[uint64]", Type = TypeKind.UInt64, LayoutType = LayoutType.UInt64, Value = new List { 42UL, 43UL, 44UL } }, + + new RoundTripSparseSet.Expected + { Tag = @"set[varint]", Type = TypeKind.VarInt, LayoutType = LayoutType.VarInt, Value = new List { 42L, 43L, 44L } }, + new RoundTripSparseSet.Expected + { + Tag = @"set[varuint]", Type = TypeKind.VarUInt, LayoutType = LayoutType.VarUInt, Value = new List { 42UL, 43UL, 44UL } + }, + + new RoundTripSparseSet.Expected + { + Tag = @"set[float32]", Type = TypeKind.Float32, LayoutType = LayoutType.Float32, Value = new List { 4.2F, 4.3F, 4.4F } + }, + new RoundTripSparseSet.Expected + { + Tag = @"set[float64]", + Type = TypeKind.Float64, + LayoutType = LayoutType.Float64, Value = new List { (double)0xAAAAAAAAAAAAAAAA, @@ -875,12 +1081,16 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit (double)0xCCCCCCCCCCCCCCCC, }, }, - new RoundTripSparseSet.Expected { Json = @"set[decimal]", Type = LayoutType.Decimal, Value = new List { 4.2M, 4.3M, 4.4M } }, + new RoundTripSparseSet.Expected + { + Tag = @"set[decimal]", Type = TypeKind.Decimal, LayoutType = LayoutType.Decimal, Value = new List { 4.2M, 4.3M, 4.4M } + }, new RoundTripSparseSet.Expected { - Json = @"set[datetime]", - Type = LayoutType.DateTime, + Tag = @"set[datetime]", + Type = TypeKind.DateTime, + LayoutType = LayoutType.DateTime, Value = new List { new DateTime(1, DateTimeKind.Unspecified), @@ -890,8 +1100,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit }, new RoundTripSparseSet.Expected { - Json = @"set[guid]", - Type = LayoutType.Guid, + Tag = @"set[guid]", + Type = TypeKind.Guid, + LayoutType = LayoutType.Guid, Value = new List { Guid.Parse("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"), @@ -900,11 +1111,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit }, }, - new RoundTripSparseSet.Expected { Json = @"set[utf8]", Type = LayoutType.Utf8, Value = new List { "abc", "def", "xyz" } }, + new RoundTripSparseSet.Expected + { Tag = @"set[utf8]", Type = TypeKind.Utf8, LayoutType = LayoutType.Utf8, Value = new List { "abc", "def", "xyz" } }, new RoundTripSparseSet.Expected { - Json = @"set[binary]", - Type = LayoutType.Binary, + Tag = @"set[binary]", + Type = TypeKind.Binary, + LayoutType = LayoutType.Binary, Value = new List { new byte[] { 0x01, 0x02 }, new byte[] { 0x03, 0x04 }, new byte[] { 0x05, 0x06 } }, }, }; @@ -914,20 +1127,19 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { foreach (Type setT in new[] { typeof(LayoutTypedSet) }) { - string setColumnSchema = @"{'path': 'a', 'type': {'type': 'set', 'items': {'type': 'any'}} }"; + Property setColumnSchema = new Property { Path = "a", PropertyType = new SetPropertyType() }; if (setT == typeof(LayoutTypedSet)) { - setColumnSchema = $@"{{'path': 'a', 'type': {{'type': 'set', - 'items': {{'type': '{expected.Type.Name}', 'nullable': false }}}} }}"; + ((SetPropertyType)setColumnSchema.PropertyType).Items = + new PrimitivePropertyType { Type = expected.Type, Nullable = false }; } - string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{setColumnSchema}] }}"; - Schema s = Schema.Parse(tableSchema); - LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = new List { s } }); + Schema s = new Schema { Name = "table", SchemaId = new SchemaId(-1), Properties = { setColumnSchema } }; + LayoutResolverNamespace resolver = new LayoutResolverNamespace(new Namespace { Schemas = { s } }); Layout layout = resolver.Resolve(new SchemaId(-1)); bool found = layout.TryFind("a", out LayoutColumn setCol); - Assert.IsTrue(found, "Json: {0}", expected.Json); - Assert.AreEqual(StorageKind.Sparse, setCol.Storage, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); + Assert.AreEqual(StorageKind.Sparse, setCol.Storage, "Tag: {0}", expected.Tag); // Try writing a row using the layout. row.Reset(); @@ -938,8 +1150,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(layout.SchemaId, header.SchemaId); RowCursor root = RowCursor.Create(ref row); - this.LayoutCodeSwitch( - expected.Type.LayoutCode, ref row, ref root, new RoundTripSparseSet.Closure + LayoutCompilerUnitTests.LayoutCodeSwitch( + expected.LayoutType.LayoutCode, + ref row, + ref root, + new RoundTripSparseSet.Closure { SetCol = setCol, Expected = expected, @@ -976,7 +1191,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit return scope; } - private void LayoutCodeSwitch(LayoutCode code, ref RowBuffer row, ref RowCursor scope, TClosure closure) + private static void LayoutCodeSwitch(LayoutCode code, ref RowBuffer row, ref RowCursor scope, TClosure closure) where TDispatcher : TestActionDispatcher, new() { TDispatcher dispatcher = new TDispatcher(); @@ -1059,21 +1274,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit private sealed class RoundTripFixed : TestActionDispatcher { - public struct Expected - { - public string TypeName; - public string Json; - public object Value; - public object Default; - public int Length; - } - - public struct Closure - { - public LayoutColumn Col; - public Expected Expected; - } - public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) { LayoutColumn col = closure.Col; @@ -1081,38 +1281,38 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Result r; TValue value; - Console.WriteLine("{0}", expected.Json); + Console.WriteLine("{0}", expected.Tag); TLayout t = (TLayout)col.Type; if (col.NullBit != LayoutBit.Invalid) { r = t.ReadFixed(ref row, ref root, col, out value); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); } else { r = t.ReadFixed(ref row, ref root, col, out value); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); if (expected.Default is Array defaultArray) { - CollectionAssert.AreEqual(defaultArray, (ICollection)value, "Json: {0}", expected.Json); + CollectionAssert.AreEqual(defaultArray, (ICollection)value, "Tag: {0}", expected.Tag); } else { - Assert.AreEqual(expected.Default, value, "Json: {0}", expected.Json); + Assert.AreEqual(expected.Default, value, "Tag: {0}", expected.Tag); } } r = t.WriteFixed(ref row, ref root, col, (TValue)expected.Value); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = t.ReadFixed(ref row, ref root, col, out value); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); if (expected.Value is Array array) { - CollectionAssert.AreEqual(array, (ICollection)value, "Json: {0}", expected.Json); + CollectionAssert.AreEqual(array, (ICollection)value, "Tag: {0}", expected.Tag); } else { - Assert.AreEqual(expected.Value, value, "Json: {0}", expected.Json); + Assert.AreEqual(expected.Value, value, "Tag: {0}", expected.Tag); } root.AsReadOnly(out RowCursor roRoot); @@ -1128,13 +1328,77 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit ResultAssert.TypeMismatch(t.DeleteFixed(ref row, ref root, col)); } } + + public struct Expected + { + public TypeKind TypeName; + public string Tag; + public object Value; + public object Default; + public int Length; + } + + public struct Closure + { + public LayoutColumn Col; + public Expected Expected; + } } private class RoundTripVariable : TestActionDispatcher { + public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) + { + LayoutColumn col = closure.Col; + Expected expected = closure.Expected; + + Console.WriteLine("{0}", expected.Tag); + + RoundTripVariable.RoundTrip(ref row, ref root, col, expected.Value, expected); + } + + protected static void RoundTrip( + ref RowBuffer row, + ref RowCursor root, + LayoutColumn col, + object exValue, + Expected expected) + where TLayout : LayoutType + { + TLayout t = (TLayout)col.Type; + Result r = t.WriteVariable(ref row, ref root, col, (TValue)exValue); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + RoundTripVariable.Compare(ref row, ref root, col, exValue, expected); + + root.AsReadOnly(out RowCursor roRoot); + ResultAssert.InsufficientPermissions(t.WriteVariable(ref row, ref roRoot, col, (TValue)expected.Value)); + } + + protected static void Compare( + ref RowBuffer row, + ref RowCursor root, + LayoutColumn col, + object exValue, + Expected expected) + where TLayout : LayoutType + { + TLayout t = (TLayout)col.Type; + Result r = t.ReadVariable(ref row, ref root, col, out TValue value); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + if (exValue is Array array) + { + CollectionAssert.AreEqual(array, (ICollection)value, "Tag: {0}", expected.Tag); + } + else + { + Assert.AreEqual(exValue, value, "Tag: {0}", expected.Tag); + } + } + public struct Expected { - public string Json; + public PropertyType Type; + public string Tag; public object Short; public object Value; public object Long; @@ -1147,54 +1411,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public Layout Layout; public Expected Expected; } - - public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) - { - LayoutColumn col = closure.Col; - Expected expected = closure.Expected; - - Console.WriteLine("{0}", expected.Json); - - this.RoundTrip(ref row, ref root, col, expected.Value, expected); - } - - protected void RoundTrip( - ref RowBuffer row, - ref RowCursor root, - LayoutColumn col, - object exValue, - Expected expected) - where TLayout : LayoutType - { - TLayout t = (TLayout)col.Type; - Result r = t.WriteVariable(ref row, ref root, col, (TValue)exValue); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - this.Compare(ref row, ref root, col, exValue, expected); - - root.AsReadOnly(out RowCursor roRoot); - ResultAssert.InsufficientPermissions(t.WriteVariable(ref row, ref roRoot, col, (TValue)expected.Value)); - } - - protected void Compare( - ref RowBuffer row, - ref RowCursor root, - LayoutColumn col, - object exValue, - Expected expected) - where TLayout : LayoutType - { - TLayout t = (TLayout)col.Type; - Result r = t.ReadVariable(ref row, ref root, col, out TValue value); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - if (exValue is Array array) - { - CollectionAssert.AreEqual(array, (ICollection)value, "Json: {0}", expected.Json); - } - else - { - Assert.AreEqual(exValue, value, "Json: {0}", expected.Json); - } - } } private sealed class VariableInterleaving : RoundTripVariable @@ -1204,77 +1420,116 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Layout layout = closure.Layout; Expected expected = closure.Expected; - Console.WriteLine("{0}", expected.Json); + Console.WriteLine("{0}", expected.Tag); - LayoutColumn a = this.Verify(ref row, ref root, layout, "a", expected); - LayoutColumn b = this.Verify(ref row, ref root, layout, "b", expected); - LayoutColumn c = this.Verify(ref row, ref root, layout, "c", expected); + LayoutColumn a = VariableInterleaving.Verify(ref row, ref root, layout, "a", expected); + LayoutColumn b = VariableInterleaving.Verify(ref row, ref root, layout, "b", expected); + LayoutColumn c = VariableInterleaving.Verify(ref row, ref root, layout, "c", expected); - this.RoundTrip(ref row, ref root, b, expected.Value, expected); - this.RoundTrip(ref row, ref root, a, expected.Value, expected); - this.RoundTrip(ref row, ref root, c, expected.Value, expected); + RoundTripVariable.RoundTrip(ref row, ref root, b, expected.Value, expected); + RoundTripVariable.RoundTrip(ref row, ref root, a, expected.Value, expected); + RoundTripVariable.RoundTrip(ref row, ref root, c, expected.Value, expected); // Make the var column shorter. int rowSizeBeforeShrink = row.Length; - this.RoundTrip(ref row, ref root, a, expected.Short, expected); - this.Compare(ref row, ref root, c, expected.Value, expected); + RoundTripVariable.RoundTrip(ref row, ref root, a, expected.Short, expected); + RoundTripVariable.Compare(ref row, ref root, c, expected.Value, expected); int rowSizeAfterShrink = row.Length; - Assert.IsTrue(rowSizeAfterShrink < rowSizeBeforeShrink, "Json: {0}", expected.Json); + Assert.IsTrue(rowSizeAfterShrink < rowSizeBeforeShrink, "Tag: {0}", expected.Tag); // Make the var column longer. - this.RoundTrip(ref row, ref root, a, expected.Long, expected); - this.Compare(ref row, ref root, c, expected.Value, expected); + RoundTripVariable.RoundTrip(ref row, ref root, a, expected.Long, expected); + RoundTripVariable.Compare(ref row, ref root, c, expected.Value, expected); int rowSizeAfterGrow = row.Length; - Assert.IsTrue(rowSizeAfterGrow > rowSizeAfterShrink, "Json: {0}", expected.Json); - Assert.IsTrue(rowSizeAfterGrow > rowSizeBeforeShrink, "Json: {0}", expected.Json); + Assert.IsTrue(rowSizeAfterGrow > rowSizeAfterShrink, "Tag: {0}", expected.Tag); + Assert.IsTrue(rowSizeAfterGrow > rowSizeBeforeShrink, "Tag: {0}", expected.Tag); // Check for size overflow errors. if (a.Size > 0) { - this.TooBig(ref row, ref root, a, expected); + VariableInterleaving.TooBig(ref row, ref root, a, expected); } // Delete the var column. - this.Delete(ref row, ref root, b, expected); - this.Delete(ref row, ref root, c, expected); - this.Delete(ref row, ref root, a, expected); + VariableInterleaving.Delete(ref row, ref root, b, expected); + VariableInterleaving.Delete(ref row, ref root, c, expected); + VariableInterleaving.Delete(ref row, ref root, a, expected); } - private LayoutColumn Verify(ref RowBuffer row, ref RowCursor root, Layout layout, string path, Expected expected) + private static LayoutColumn Verify(ref RowBuffer row, ref RowCursor root, Layout layout, string path, Expected expected) where TLayout : LayoutType { bool found = layout.TryFind(path, out LayoutColumn col); - Assert.IsTrue(found, "Json: {0}", expected.Json); + Assert.IsTrue(found, "Tag: {0}", expected.Tag); Assert.IsTrue(col.Type.AllowVariable); TLayout t = (TLayout)col.Type; Result r = t.ReadVariable(ref row, ref root, col, out TValue _); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); return col; } - private void TooBig(ref RowBuffer row, ref RowCursor root, LayoutColumn col, Expected expected) + private static void TooBig(ref RowBuffer row, ref RowCursor root, LayoutColumn col, Expected expected) where TLayout : LayoutType { TLayout t = (TLayout)col.Type; Result r = t.WriteVariable(ref row, ref root, col, (TValue)expected.TooBig); - Assert.AreEqual(Result.TooBig, r, "Json: {0}", expected.Json); + Assert.AreEqual(Result.TooBig, r, "Tag: {0}", expected.Tag); } - private void Delete(ref RowBuffer row, ref RowCursor root, LayoutColumn col, Expected expected) + private static void Delete(ref RowBuffer row, ref RowCursor root, LayoutColumn col, Expected expected) where TLayout : LayoutType { TLayout t = (TLayout)col.Type; root.AsReadOnly(out RowCursor roRoot); ResultAssert.InsufficientPermissions(t.DeleteVariable(ref row, ref roRoot, col)); Result r = t.DeleteVariable(ref row, ref root, col); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = t.ReadVariable(ref row, ref root, col, out TValue _); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); } } private sealed class RoundTripSparseOrdering : TestActionDispatcher { + public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) + { + LayoutType type = closure.Expected.Type; + string path = closure.Expected.Path; + object exValue = closure.Expected.Value; + string json = closure.Json; + + TLayout t = (TLayout)type; + TValue value = (TValue)exValue; + root.Clone(out RowCursor field).Find(ref row, path); + Result r = t.WriteSparse(ref row, ref field, value); + ResultAssert.IsSuccess(r, "Tag: {0}", json); + r = t.ReadSparse(ref row, ref field, out value); + ResultAssert.IsSuccess(r, "Tag: {0}", json); + if (exValue is Array array) + { + CollectionAssert.AreEqual(array, (ICollection)value, "Tag: {0}", json); + } + else + { + Assert.AreEqual(exValue, value, "Tag: {0}", json); + } + + if (t is LayoutNull) + { + r = LayoutType.Boolean.WriteSparse(ref row, ref field, false); + ResultAssert.IsSuccess(r, "Tag: {0}", json); + r = t.ReadSparse(ref row, ref field, out value); + ResultAssert.TypeMismatch(r, "Tag: {0}", json); + } + else + { + r = LayoutType.Null.WriteSparse(ref row, ref field, NullValue.Default); + ResultAssert.IsSuccess(r, "Tag: {0}", json); + r = t.ReadSparse(ref row, ref field, out value); + ResultAssert.TypeMismatch(r, "Tag: {0}", json); + } + } + public struct Expected { public string Path; @@ -1287,61 +1542,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public string Json; public Expected Expected; } - - public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) - { - LayoutType type = closure.Expected.Type; - string path = closure.Expected.Path; - object exValue = closure.Expected.Value; - string json = closure.Json; - - TLayout t = (TLayout)type; - TValue value = (TValue)exValue; - root.Clone(out RowCursor field).Find(ref row, path); - Result r = t.WriteSparse(ref row, ref field, value); - ResultAssert.IsSuccess(r, "Json: {0}", json); - r = t.ReadSparse(ref row, ref field, out value); - ResultAssert.IsSuccess(r, "Json: {0}", json); - if (exValue is Array array) - { - CollectionAssert.AreEqual(array, (ICollection)value, "Json: {0}", json); - } - else - { - Assert.AreEqual(exValue, value, "Json: {0}", json); - } - - if (t is LayoutNull) - { - r = LayoutType.Boolean.WriteSparse(ref row, ref field, false); - ResultAssert.IsSuccess(r, "Json: {0}", json); - r = t.ReadSparse(ref row, ref field, out value); - ResultAssert.TypeMismatch(r, "Json: {0}", json); - } - else - { - r = LayoutType.Null.WriteSparse(ref row, ref field, NullValue.Default); - ResultAssert.IsSuccess(r, "Json: {0}", json); - r = t.ReadSparse(ref row, ref field, out value); - ResultAssert.TypeMismatch(r, "Json: {0}", json); - } - } } private sealed class RoundTripSparseSimple : TestActionDispatcher { - public struct Expected - { - public string Json; - public object Value; - } - - public struct Closure - { - public LayoutColumn Col; - public Expected Expected; - } - public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) { LayoutColumn col = closure.Col; @@ -1351,22 +1555,22 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit TLayout t = (TLayout)col.Type; root.Clone(out RowCursor field).Find(ref row, col.Path); Result r = t.ReadSparse(ref row, ref field, out TValue value); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); r = t.WriteSparse(ref row, ref field, (TValue)expected.Value, UpdateOptions.Update); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); r = t.WriteSparse(ref row, ref field, (TValue)expected.Value); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = t.WriteSparse(ref row, ref field, (TValue)expected.Value, UpdateOptions.Insert); - ResultAssert.Exists(r, "Json: {0}", expected.Json); + ResultAssert.Exists(r, "Tag: {0}", expected.Tag); r = t.ReadSparse(ref row, ref field, out value); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); if (expected.Value is Array array) { - CollectionAssert.AreEqual(array, (ICollection)value, "Json: {0}", expected.Json); + CollectionAssert.AreEqual(array, (ICollection)value, "Tag: {0}", expected.Tag); } else { - Assert.AreEqual(expected.Value, value, "Json: {0}", expected.Json); + Assert.AreEqual(expected.Value, value, "Tag: {0}", expected.Tag); } root.AsReadOnly(out RowCursor roRoot).Find(ref row, col.Path); @@ -1376,36 +1580,122 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit if (t is LayoutNull) { r = LayoutType.Boolean.WriteSparse(ref row, ref field, false); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = t.ReadSparse(ref row, ref field, out value); - ResultAssert.TypeMismatch(r, "Json: {0}", expected.Json); + ResultAssert.TypeMismatch(r, "Tag: {0}", expected.Tag); } else { r = LayoutType.Null.WriteSparse(ref row, ref field, NullValue.Default); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = t.ReadSparse(ref row, ref field, out value); - ResultAssert.TypeMismatch(r, "Json: {0}", expected.Json); + ResultAssert.TypeMismatch(r, "Tag: {0}", expected.Tag); } r = t.DeleteSparse(ref row, ref field); - ResultAssert.TypeMismatch(r, "Json: {0}", expected.Json); + ResultAssert.TypeMismatch(r, "Tag: {0}", expected.Tag); // Overwrite it again, then delete it. r = t.WriteSparse(ref row, ref field, (TValue)expected.Value, UpdateOptions.Update); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = t.DeleteSparse(ref row, ref field); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = t.ReadSparse(ref row, ref field, out value); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); + } + + public struct Expected + { + public TypeKind Type; + public string Tag; + public object Value; + } + + public struct Closure + { + public LayoutColumn Col; + public Expected Expected; } } private sealed class RoundTripSparseObject : TestActionDispatcher { + public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) + { + LayoutColumn objCol = closure.ObjCol; + LayoutObject objT = objCol.Type as LayoutObject; + LayoutColumn col = closure.Col; + Expected expected = closure.Expected; + + Console.WriteLine("{0}", col.Type.Name); + Assert.IsNotNull(objT, "Tag: {0}", expected.Tag); + Assert.AreEqual(objCol, col.Parent, "Tag: {0}", expected.Tag); + + TLayout t = (TLayout)col.Type; + + // Attempt to read the object and the nested column. + root.Clone(out RowCursor field).Find(ref row, objCol.Path); + Result r = objT.ReadScope(ref row, ref field, out RowCursor scope); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); + + // Write the object and the nested column. + r = objT.WriteScope(ref row, ref field, objCol.TypeArgs, out scope); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + + // Verify the nested field doesn't yet appear within the new scope. + scope.Clone(out RowCursor nestedField).Find(ref row, col.Path); + r = t.ReadSparse(ref row, ref nestedField, out TValue value); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); + + // Write the nested field. + r = t.WriteSparse(ref row, ref nestedField, (TValue)expected.Value); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + + // Read the object and the nested column, validate the nested column has the proper value. + r = objT.ReadScope(ref row, ref field, out RowCursor scope2); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + Assert.AreEqual(scope.ScopeType, scope2.ScopeType, "Tag: {0}", expected.Tag); + Assert.AreEqual(scope.start, scope2.start, "Tag: {0}", expected.Tag); + Assert.AreEqual(scope.Immutable, scope2.Immutable, "Tag: {0}", expected.Tag); + + // Read the nested field + scope2.Clone(out nestedField).Find(ref row, col.Path); + r = t.ReadSparse(ref row, ref nestedField, out value); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + if (expected.Value is Array array) + { + CollectionAssert.AreEqual(array, (ICollection)value, "Tag: {0}", expected.Tag); + } + else + { + Assert.AreEqual(expected.Value, value, "Tag: {0}", expected.Tag); + } + + root.AsReadOnly(out RowCursor roRoot).Find(ref row, objCol.Path); + ResultAssert.InsufficientPermissions(objT.DeleteScope(ref row, ref roRoot)); + ResultAssert.InsufficientPermissions(objT.WriteScope(ref row, ref roRoot, objCol.TypeArgs, out scope2)); + + // Overwrite the whole scope. + r = LayoutType.Null.WriteSparse(ref row, ref field, NullValue.Default); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + r = objT.ReadScope(ref row, ref field, out RowCursor _); + ResultAssert.TypeMismatch(r, "Tag: {0}", expected.Tag); + r = objT.DeleteScope(ref row, ref field); + ResultAssert.TypeMismatch(r, "Tag: {0}", expected.Tag); + + // Overwrite it again, then delete it. + r = objT.WriteScope(ref row, ref field, objCol.TypeArgs, out RowCursor _, UpdateOptions.Update); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + r = objT.DeleteScope(ref row, ref field); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); + r = objT.ReadScope(ref row, ref field, out RowCursor _); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); + } + public struct Expected { - public string Json; + public TypeKind Type; + public string Tag; public object Value; } @@ -1415,114 +1705,23 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public LayoutColumn Col; public Expected Expected; } - - public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) - { - LayoutColumn objCol = closure.ObjCol; - LayoutObject objT = objCol.Type as LayoutObject; - LayoutColumn col = closure.Col; - Expected expected = closure.Expected; - - Console.WriteLine("{0}", col.Type.Name); - Assert.IsNotNull(objT, "Json: {0}", expected.Json); - Assert.AreEqual(objCol, col.Parent, "Json: {0}", expected.Json); - - TLayout t = (TLayout)col.Type; - - // Attempt to read the object and the nested column. - root.Clone(out RowCursor field).Find(ref row, objCol.Path); - Result r = objT.ReadScope(ref row, ref field, out RowCursor scope); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); - - // Write the object and the nested column. - r = objT.WriteScope(ref row, ref field, objCol.TypeArgs, out scope); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - - // Verify the nested field doesn't yet appear within the new scope. - scope.Clone(out RowCursor nestedField).Find(ref row, col.Path); - r = t.ReadSparse(ref row, ref nestedField, out TValue value); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); - - // Write the nested field. - r = t.WriteSparse(ref row, ref nestedField, (TValue)expected.Value); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - - // Read the object and the nested column, validate the nested column has the proper value. - r = objT.ReadScope(ref row, ref field, out RowCursor scope2); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - Assert.AreEqual(scope.ScopeType, scope2.ScopeType, "Json: {0}", expected.Json); - Assert.AreEqual(scope.start, scope2.start, "Json: {0}", expected.Json); - Assert.AreEqual(scope.Immutable, scope2.Immutable, "Json: {0}", expected.Json); - - // Read the nested field - scope2.Clone(out nestedField).Find(ref row, col.Path); - r = t.ReadSparse(ref row, ref nestedField, out value); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - if (expected.Value is Array array) - { - CollectionAssert.AreEqual(array, (ICollection)value, "Json: {0}", expected.Json); - } - else - { - Assert.AreEqual(expected.Value, value, "Json: {0}", expected.Json); - } - - root.AsReadOnly(out RowCursor roRoot).Find(ref row, objCol.Path); - ResultAssert.InsufficientPermissions(objT.DeleteScope(ref row, ref roRoot)); - ResultAssert.InsufficientPermissions(objT.WriteScope(ref row, ref roRoot, objCol.TypeArgs, out scope2)); - - // Overwrite the whole scope. - r = LayoutType.Null.WriteSparse(ref row, ref field, NullValue.Default); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - r = objT.ReadScope(ref row, ref field, out RowCursor _); - ResultAssert.TypeMismatch(r, "Json: {0}", expected.Json); - r = objT.DeleteScope(ref row, ref field); - ResultAssert.TypeMismatch(r, "Json: {0}", expected.Json); - - // Overwrite it again, then delete it. - r = objT.WriteScope(ref row, ref field, objCol.TypeArgs, out RowCursor _, UpdateOptions.Update); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - r = objT.DeleteScope(ref row, ref field); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); - r = objT.ReadScope(ref row, ref field, out RowCursor _); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); - } } private sealed class RoundTripSparseObjectMulti : TestActionDispatcher { - public struct Expected - { - public string Json; - public Property[] Props; - } - - public struct Property - { - public string Path; - public object Value; - } - - public struct Closure - { - public LayoutColumn Col; - public Property Prop; - public Expected Expected; - } - - public override void Dispatch(ref RowBuffer row, ref RowCursor scope, Closure closure) + public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) { LayoutColumn col = closure.Col; - Property prop = closure.Prop; + ExpectedProperty prop = closure.Prop; Expected expected = closure.Expected; - string tag = string.Format("Prop: {1}: Json: {0}", expected.Json, prop.Path); + string tag = string.Format("Prop: {1}: Tag: {0}", expected.Tag, prop.Path); Console.WriteLine(tag); TLayout t = (TLayout)col.Type; // Verify the nested field doesn't yet appear within the new scope. - scope.Clone(out RowCursor nestedField).Find(ref row, col.Path); + root.Clone(out RowCursor nestedField).Find(ref row, col.Path); Result r = t.ReadSparse(ref row, ref nestedField, out TValue value); Assert.IsTrue(r == Result.NotFound || r == Result.TypeMismatch, tag); @@ -1562,9 +1761,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public override void DispatchObject(ref RowBuffer row, ref RowCursor scope, Closure closure) { LayoutColumn col = closure.Col; - Property prop = closure.Prop; + ExpectedProperty prop = closure.Prop; Expected expected = closure.Expected; - string tag = string.Format("Prop: {1}: Json: {0}", expected.Json, prop.Path); + string tag = $"Prop: {prop.Path}: Tag: {expected.Tag}"; Console.WriteLine(tag); @@ -1593,17 +1792,15 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit r = t.ReadScope(ref row, ref nestedField, out scope3); ResultAssert.TypeMismatch(r, tag); } - } - private sealed class RoundTripSparseObjectNested : TestActionDispatcher - { public struct Expected { - public string Json; - public Property[] Props; + public List Props; + public string Tag; + public ExpectedProperty[] ExpectedProps; } - public struct Property + public struct ExpectedProperty { public string Path; public object Value; @@ -1612,16 +1809,19 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public struct Closure { public LayoutColumn Col; - public Property Prop; + public ExpectedProperty Prop; public Expected Expected; } + } + private sealed class RoundTripSparseObjectNested : TestActionDispatcher + { public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) { LayoutColumn col = closure.Col; - Property prop = closure.Prop; + ExpectedProperty prop = closure.Prop; Expected expected = closure.Expected; - string tag = string.Format("Prop: {1}: Json: {0}", expected.Json, prop.Path); + string tag = string.Format("Prop: {1}: Tag: {0}", expected.Tag, prop.Path); Console.WriteLine(tag); @@ -1667,9 +1867,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public override void DispatchObject(ref RowBuffer row, ref RowCursor root, Closure closure) { LayoutColumn col = closure.Col; - Property prop = closure.Prop; + ExpectedProperty prop = closure.Prop; Expected expected = closure.Expected; - string tag = string.Format("Prop: {1}: Json: {0}", expected.Json, prop.Path); + string tag = string.Format("Prop: {1}: Tag: {0}", expected.Tag, prop.Path); Console.WriteLine(tag); @@ -1677,34 +1877,41 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit RowCursor scope = LayoutCompilerUnitTests.EnsureScope(ref row, ref root, col, tag); Assert.AreNotEqual(root, scope); } - } - private sealed class RoundTripSparseArray : TestActionDispatcher - { public struct Expected { - public string Json; - public LayoutType Type; - public List Value; + public List Props; + public string Tag; + public ExpectedProperty[] ExpectedProps; + } + + public struct ExpectedProperty + { + public string Path; + public object Value; } public struct Closure { - public LayoutColumn ArrCol; + public LayoutColumn Col; + public ExpectedProperty Prop; public Expected Expected; } + } + private sealed class RoundTripSparseArray : TestActionDispatcher + { public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) { LayoutColumn arrCol = closure.ArrCol; LayoutIndexedScope arrT = arrCol.Type as LayoutIndexedScope; Expected expected = closure.Expected; - string tag = $"Json: {expected.Json}, Array: {arrCol.Type.Name}"; + string tag = $"Tag: {expected.Tag}, Array: {arrCol.Type.Name}"; Console.WriteLine(tag); Assert.IsNotNull(arrT, tag); - TLayout t = (TLayout)expected.Type; + TLayout t = (TLayout)expected.LayoutType; // Verify the array doesn't yet exist. root.Clone(out RowCursor field).Find(ref row, arrCol.Path); @@ -1791,44 +1998,45 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit r = arrT.ReadScope(ref row, ref field, out RowCursor _); ResultAssert.TypeMismatch(r, tag); r = arrT.DeleteScope(ref row, ref field); - ResultAssert.TypeMismatch(r, "Json: {0}", expected.Json); + ResultAssert.TypeMismatch(r, "Tag: {0}", expected.Tag); // Overwrite it again, then delete it. r = arrT.WriteScope(ref row, ref field, arrCol.TypeArgs, out RowCursor _, UpdateOptions.Update); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = arrT.DeleteScope(ref row, ref field); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = arrT.ReadScope(ref row, ref field, out RowCursor _); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); } - } - private sealed class RoundTripSparseSet : TestActionDispatcher - { public struct Expected { - public string Json; - public LayoutType Type; + public string Tag; + public TypeKind Type; + public LayoutType LayoutType; public List Value; } public struct Closure { - public LayoutColumn SetCol; + public LayoutColumn ArrCol; public Expected Expected; } + } + private sealed class RoundTripSparseSet : TestActionDispatcher + { public override void Dispatch(ref RowBuffer row, ref RowCursor root, Closure closure) { LayoutColumn setCol = closure.SetCol; LayoutUniqueScope setT = setCol.Type as LayoutUniqueScope; Expected expected = closure.Expected; - string tag = $"Json: {expected.Json}, Set: {setCol.Type.Name}"; + string tag = $"Tag: {expected.Tag}, Set: {setCol.Type.Name}"; Console.WriteLine(tag); Assert.IsNotNull(setT, tag); - TLayout t = (TLayout)expected.Type; + TLayout t = (TLayout)expected.LayoutType; // Verify the Set doesn't yet exist. root.Clone(out RowCursor field).Find(ref row, setCol.Path); @@ -1973,21 +2181,35 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit r = setT.ReadScope(ref row, ref field, out RowCursor _); ResultAssert.TypeMismatch(r, tag); r = setT.DeleteScope(ref row, ref field); - ResultAssert.TypeMismatch(r, "Json: {0}", expected.Json); + ResultAssert.TypeMismatch(r, "Tag: {0}", expected.Tag); // Overwrite it again, then delete it. r = setT.WriteScope(ref row, ref field, setCol.TypeArgs, out RowCursor _, UpdateOptions.Update); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = setT.DeleteScope(ref row, ref field); - ResultAssert.IsSuccess(r, "Json: {0}", expected.Json); + ResultAssert.IsSuccess(r, "Tag: {0}", expected.Tag); r = setT.ReadScope(ref row, ref field, out RowCursor _); - ResultAssert.NotFound(r, "Json: {0}", expected.Json); + ResultAssert.NotFound(r, "Tag: {0}", expected.Tag); + } + + public struct Expected + { + public string Tag; + public TypeKind Type; + public LayoutType LayoutType; + public List Value; + } + + public struct Closure + { + public LayoutColumn SetCol; + public Expected Expected; } } private abstract class TestActionDispatcher { - public abstract void Dispatch(ref RowBuffer row, ref RowCursor scope, TClosure closure) + public abstract void Dispatch(ref RowBuffer row, ref RowCursor root, TClosure closure) where TLayout : LayoutType; public virtual void DispatchObject(ref RowBuffer row, ref RowCursor scope, TClosure closure) diff --git a/dotnet/src/HybridRow.Tests.Unit/LayoutTypeUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/LayoutTypeUnitTests.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/LayoutTypeUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/LayoutTypeUnitTests.cs diff --git a/src/Serialization/HybridRow.Tests.Unit/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.csproj b/src/Serialization/HybridRow.Tests.Unit/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.csproj new file mode 100644 index 0000000..c191bd3 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.csproj @@ -0,0 +1,40 @@ + + + + true + true + Library + Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit + Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit + netcoreapp3.1 + AnyCPU + TestData + true + Generated + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + diff --git a/dotnet/src/HybridRow.Tests.Unit/NullableUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/NullableUnitTests.cs similarity index 99% rename from dotnet/src/HybridRow.Tests.Unit/NullableUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/NullableUnitTests.cs index d3cab60..6b59eb6 100644 --- a/dotnet/src/HybridRow.Tests.Unit/NullableUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/NullableUnitTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [DeploymentItem(NullableUnitTests.SchemaFile, "TestData")] public sealed class NullableUnitTests { - private const string SchemaFile = @"TestData\NullableSchema.json"; + private const string SchemaFile = @"TestData\NullableSchema.hrschema"; private const int InitialRowSize = 2 * 1024 * 1024; private Namespace schema; @@ -28,8 +28,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestInitialize] public void ParseNamespaceExample() { - string json = File.ReadAllText(NullableUnitTests.SchemaFile); - this.schema = Namespace.Parse(json); + this.schema = SchemaUtil.LoadFromHrSchema(NullableUnitTests.SchemaFile); this.resolver = new LayoutResolverNamespace(this.schema); this.layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "Nullables").SchemaId); } @@ -369,7 +368,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public override bool Equals(object obj) { - if (object.ReferenceEquals(null, obj)) + if (obj is null) { return false; } diff --git a/dotnet/src/HybridRow.Tests.Unit/PermuteExtensions.cs b/src/Serialization/HybridRow.Tests.Unit/PermuteExtensions.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/PermuteExtensions.cs rename to src/Serialization/HybridRow.Tests.Unit/PermuteExtensions.cs diff --git a/dotnet/src/HybridRow.Tests.Unit/RandomGeneratorUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/RandomGeneratorUnitTests.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/RandomGeneratorUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/RandomGeneratorUnitTests.cs diff --git a/dotnet/src/HybridRow.Tests.Unit/RecordIOUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/RecordIOUnitTests.cs similarity index 59% rename from dotnet/src/HybridRow.Tests.Unit/RecordIOUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/RecordIOUnitTests.cs index c6b3a74..7dde89b 100644 --- a/dotnet/src/HybridRow.Tests.Unit/RecordIOUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/RecordIOUnitTests.cs @@ -2,13 +2,12 @@ // 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.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; @@ -19,37 +18,22 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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); + LayoutResolver systemResolver = SchemasHrSchema.LayoutResolver; + Layout segmentLayout = systemResolver.Resolve((SchemaId)SegmentHybridRowSerializer.SchemaId); Assert.AreEqual(segmentLayout.Name, "Segment"); - Assert.AreEqual(segmentLayout.SchemaId, SystemSchema.SegmentSchemaId); + Assert.AreEqual(segmentLayout.SchemaId, (SchemaId)SegmentHybridRowSerializer.SchemaId); - Layout recordLayout = systemResolver.Resolve(SystemSchema.RecordSchemaId); + Layout recordLayout = systemResolver.Resolve((SchemaId)RecordHybridRowSerializer.SchemaId); Assert.AreEqual(recordLayout.Name, "Record"); - Assert.AreEqual(recordLayout.SchemaId, SystemSchema.RecordSchemaId); + Assert.AreEqual(recordLayout.SchemaId, (SchemaId)RecordHybridRowSerializer.SchemaId); } [TestMethod] @@ -82,16 +66,16 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit }; string sampleComment = "hello there"; - string sampleSDL = "some SDL"; + Namespace sampleNs = new Namespace { Name = "some sample namespace" }; using (Stream stm = new MemoryStream()) { // Create a reusable, resizable buffer. - MemorySpanResizer resizer = new MemorySpanResizer(RecordIOUnitTests.InitialRowSize); + MemorySpanResizer resizer = new MemorySpanResizer(); // Write a RecordIO stream. Result r = await stm.WriteRecordIOAsync( - new Segment(sampleComment, sampleSDL), + new Segment(sampleComment, sampleNs), (long index, out ReadOnlyMemory body) => { body = default; @@ -100,19 +84,21 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit return Result.Success; } - return this.WriteAddress(resizer, addresses[index], out body); + return RecordIOUnitTests.WriteAddress(resizer, addresses[index], out body); }); + ResultAssert.IsSuccess(r); + // Read a RecordIO stream. List
addressesRead = new List
(); stm.Position = 0; resizer = new MemorySpanResizer(1); r = await stm.ReadRecordIOAsync( - record => + (ReadOnlyMemory record) => { Assert.IsFalse(record.IsEmpty); - r = this.ReadAddress(record, out Address obj); + r = RecordIOUnitTests.ReadAddress(record, out Address obj); ResultAssert.IsSuccess(r); addressesRead.Add(obj); return Result.Success; @@ -121,10 +107,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { Assert.IsFalse(segment.IsEmpty); - r = this.ReadSegment(segment, out Segment obj); + r = RecordIOUnitTests.ReadSegment(segment, out Segment obj); ResultAssert.IsSuccess(r); Assert.AreEqual(obj.Comment, sampleComment); - Assert.AreEqual(obj.SDL, sampleSDL); + Assert.AreEqual(obj.Schema.Name, sampleNs.Name); return Result.Success; }, resizer); @@ -135,16 +121,44 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(addresses.Length, addressesRead.Count); for (int i = 0; i < addresses.Length; i++) { - Assert.AreEqual(addresses[i], addressesRead[i]); + Assert.IsTrue(default(AddressHybridRowSerializer).Comparer.Equals(addresses[i], addressesRead[i])); } } } - private Result WriteAddress(MemorySpanResizer resizer, Address obj, out ReadOnlyMemory buffer) + private static Result ReadSegment(ReadOnlyMemory buffer, out Segment obj) + { + RowReader reader = new RowReader(buffer, HybridRowVersion.V1, SchemasHrSchema.LayoutResolver); + + // 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. + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + RowBuffer row = new RowBuffer( + MemoryMarshal.AsMemory(buffer).Span, + HybridRowVersion.V1, + SchemasHrSchema.LayoutResolver); + RowCursor root = RowCursor.Create(ref row); + return default(SegmentHybridRowSerializer).Read(ref row, ref root, true, out obj); + } + + private static Result WriteAddress(MemorySpanResizer resizer, Address obj, out ReadOnlyMemory 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); + Layout addressLayout = CustomerSchemaHrSchema.LayoutResolver.Resolve((SchemaId)AddressHybridRowSerializer.SchemaId); + row.InitLayout(HybridRowVersion.V1, addressLayout, CustomerSchemaHrSchema.LayoutResolver); + Result r = default(AddressHybridRowSerializer).Write(ref row, ref RowCursor.Create(ref row, out RowCursor _), true, default, obj); if (r != Result.Success) { buffer = default; @@ -155,10 +169,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit return Result.Success; } - private Result ReadAddress(Memory buffer, out Address obj) + private static Result ReadAddress(ReadOnlyMemory buffer, out Address obj) { - RowBuffer row = new RowBuffer(buffer.Span, HybridRowVersion.V1, this.resolver); - RowReader reader = new RowReader(ref row); + RowReader reader = new RowReader(buffer, HybridRowVersion.V1, CustomerSchemaHrSchema.LayoutResolver); // Use the reader to dump to the screen. Result r = DiagnosticConverter.ReaderToString(ref reader, out string str); @@ -171,28 +184,15 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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 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); + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + RowBuffer row = new RowBuffer( + MemoryMarshal.AsMemory(buffer).Span, + HybridRowVersion.V1, + CustomerSchemaHrSchema.LayoutResolver); + return default(AddressHybridRowSerializer).Read(ref row, ref RowCursor.Create(ref row, out RowCursor _), true, out obj); } } } diff --git a/dotnet/src/HybridRow.Tests.Unit/ResultAssert.cs b/src/Serialization/HybridRow.Tests.Unit/ResultAssert.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/ResultAssert.cs rename to src/Serialization/HybridRow.Tests.Unit/ResultAssert.cs diff --git a/dotnet/src/HybridRow.Tests.Unit/RowBufferUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/RowBufferUnitTests.cs similarity index 65% rename from dotnet/src/HybridRow.Tests.Unit/RowBufferUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/RowBufferUnitTests.cs index 619f543..d8b789b 100644 --- a/dotnet/src/HybridRow.Tests.Unit/RowBufferUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/RowBufferUnitTests.cs @@ -19,22 +19,22 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit for (int i = short.MinValue; i <= short.MaxValue; i++) { short s = (short)i; - this.RoundTripVarInt(s); + RowBufferUnitTests.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); + RowBufferUnitTests.RoundTripVarInt(0); + RowBufferUnitTests.RoundTripVarInt(int.MinValue); + RowBufferUnitTests.RoundTripVarInt(unchecked((int)0x80000000ul)); + RowBufferUnitTests.RoundTripVarInt(unchecked((int)0x7FFFFFFFul)); + RowBufferUnitTests.RoundTripVarInt(int.MaxValue); + RowBufferUnitTests.RoundTripVarInt(long.MinValue); + RowBufferUnitTests.RoundTripVarInt(unchecked((long)0x8000000000000000ul)); + RowBufferUnitTests.RoundTripVarInt(unchecked((long)0x7FFFFFFFFFFFFFFFul)); + RowBufferUnitTests.RoundTripVarInt(long.MaxValue); } - private void RoundTripVarInt(short s) + private static void RoundTripVarInt(short s) { ulong encoded = RowBuffer.RotateSignToLsb(s); long decoded = RowBuffer.RotateSignToMsb(encoded); @@ -42,7 +42,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(s, t, "Value: {0}", s); } - private void RoundTripVarInt(int s) + private static void RoundTripVarInt(int s) { ulong encoded = RowBuffer.RotateSignToLsb(s); long decoded = RowBuffer.RotateSignToMsb(encoded); @@ -50,7 +50,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.AreEqual(s, t, "Value: {0}", s); } - private void RoundTripVarInt(long s) + private static void RoundTripVarInt(long s) { ulong encoded = RowBuffer.RotateSignToLsb(s); long decoded = RowBuffer.RotateSignToMsb(encoded); diff --git a/dotnet/src/HybridRow.Tests.Unit/RowOperationDispatcher.cs b/src/Serialization/HybridRow.Tests.Unit/RowOperationDispatcher.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/RowOperationDispatcher.cs rename to src/Serialization/HybridRow.Tests.Unit/RowOperationDispatcher.cs diff --git a/dotnet/src/HybridRow.Tests.Unit/RowReaderUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/RowReaderUnitTests.cs similarity index 86% rename from dotnet/src/HybridRow.Tests.Unit/RowReaderUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/RowReaderUnitTests.cs index d3cd6f7..102a494 100644 --- a/dotnet/src/HybridRow.Tests.Unit/RowReaderUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/RowReaderUnitTests.cs @@ -6,8 +6,6 @@ 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; @@ -19,7 +17,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [DeploymentItem(RowReaderUnitTests.SchemaFile, "TestData")] public sealed class RowReaderUnitTests { - private const string SchemaFile = @"TestData\ReaderSchema.json"; + private const string SchemaFile = @"TestData\ReaderSchema.hrschema"; 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); @@ -32,8 +30,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestInitialize] public void ParseNamespaceExample() { - string json = File.ReadAllText(RowReaderUnitTests.SchemaFile); - this.schema = Namespace.Parse(json); + this.schema = SchemaUtil.LoadFromHrSchema(RowReaderUnitTests.SchemaFile); this.resolver = new LayoutResolverNamespace(this.schema); } @@ -177,12 +174,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { TypeArgument tupleArgument = new TypeArgument( LayoutType.Tuple, - new TypeArgumentList(new[] - { - new TypeArgument(LayoutType.Int32), - new TypeArgument(LayoutType.Int32), - new TypeArgument(LayoutType.Int32), - })); + new TypeArgumentList( + new[] + { + new TypeArgument(LayoutType.Int32), + new TypeArgument(LayoutType.Int32), + new TypeArgument(LayoutType.Int32), + })); Result WriteTuple(ref RowWriter tupleWriter, TypeArgument tupleTypeArgument, int unused) { @@ -214,16 +212,16 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit switch (reader.Type.LayoutCode) { case LayoutCode.TupleScope: - { - ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadTuplePartial)); - break; - } + { + ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadTuplePartial)); + break; + } case LayoutCode.ObjectScope: - { - ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadNestedDocumentDelegate)); - break; - } + { + ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadNestedDocumentDelegate)); + break; + } } } @@ -237,19 +235,19 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit switch (reader.Type.LayoutCode) { case LayoutCode.TupleScope: - { - RowReader nested = reader.ReadScope(); - ResultAssert.IsSuccess(RowReaderUnitTests.ReadTuplePartial(ref nested, 0)); - break; - } + { + 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; - } + { + RowReader nested = reader.ReadScope(); + ResultAssert.IsSuccess(RowReaderUnitTests.ReadNestedDocumentNonDelegate(ref nested, 0)); + ResultAssert.IsSuccess(reader.ReadScope(0, RowReaderUnitTests.ReadNestedDocumentDelegate)); + break; + } } } @@ -263,21 +261,21 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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; - } + { + 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; - } + { + 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; + } } } diff --git a/dotnet/src/HybridRow.Tests.Unit/RowWriterUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/RowWriterUnitTests.cs similarity index 99% rename from dotnet/src/HybridRow.Tests.Unit/RowWriterUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/RowWriterUnitTests.cs index b341a4e..2b42975 100644 --- a/dotnet/src/HybridRow.Tests.Unit/RowWriterUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/RowWriterUnitTests.cs @@ -7,7 +7,6 @@ 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; @@ -20,7 +19,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public sealed class RowWriterUnitTests { private const int InitialRowSize = 2 * 1024 * 1024; - private const string SchemaFile = @"TestData\ReaderSchema.json"; + private const string SchemaFile = @"TestData\ReaderSchema.hrschema"; 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); @@ -33,8 +32,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestInitialize] public void TestInitialize() { - string json = File.ReadAllText(RowWriterUnitTests.SchemaFile); - this.schema = Namespace.Parse(json); + this.schema = SchemaUtil.LoadFromHrSchema(RowWriterUnitTests.SchemaFile); this.resolver = new LayoutResolverNamespace(this.schema); } diff --git a/dotnet/src/HybridRow.Tests.Unit/SchemaHashUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/SchemaHashUnitTests.cs similarity index 53% rename from dotnet/src/HybridRow.Tests.Unit/SchemaHashUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/SchemaHashUnitTests.cs index d91dd64..cbc61e0 100644 --- a/dotnet/src/HybridRow.Tests.Unit/SchemaHashUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/SchemaHashUnitTests.cs @@ -2,10 +2,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ +#pragma warning disable DontUseVarForVariableTypes // Do not use 'var' for variable declarations + 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; @@ -15,15 +16,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [DeploymentItem(SchemaHashUnitTests.SchemaFile, "TestData")] public class SchemaHashUnitTests { - private const string SchemaFile = @"TestData\SchemaHashCoverageSchema.json"; + private const string SchemaFile = @"TestData\SchemaHashCoverageSchema.hrschema"; private Namespace ns; private Schema tableSchema; [TestInitialize] public void InitializeSuite() { - string json = File.ReadAllText(SchemaHashUnitTests.SchemaFile); - this.ns = Namespace.Parse(json); + this.ns = SchemaUtil.LoadFromHrSchema(SchemaHashUnitTests.SchemaFile); this.tableSchema = this.ns.Schemas.Find(s => s.Name == "Table"); } @@ -39,10 +39,50 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [Owner("jthunter")] public void SchemaHashTest() { + var expected = new + { + InitialHash = (5669474132123492693ul, 9149729557667844748ul), + SeedChange = (18392417753810872046ul, 17942086702536564543ul), + NameChange = (5669474132123492693ul, 9149729557667844748ul), + CommentChange = (5669474132123492693ul, 9149729557667844748ul), + VersionChange = (3856530766227562582ul, 544919695479649775ul), + SchemaIdChange = (14486877418262750995ul, 4531483667481505499ul), + TypeChange = (3039179951777091547ul, 1683112938996382945ul), + EnablePropertyLevelTimestampOptionChange = (7645756771083738022ul, 1505823556482207368ul), + DisallowUnschematizedOptionChange = (10032203022394252833ul, 13373685281699412414ul), + DisableSystemPrefixOptionChange = (2805049018802494774ul, 16613880718597095035ul), + PartitionKeyChange = (2005412391729232690ul, 12469959147379506889ul), + PrimarySortKeyChange = (17537189815393436921ul, 9806309626171519256ul), + PrimarySortKeyDirectionChange = (3645051070505371462ul, 16796982542562331583ul), + StaticKeyChange = (491397920750345147ul, 3021634280592375817ul), + PropertiesPathChange = (16209488961438556450ul, 11525200464633711354ul), + PropertiesCommentChange = (5669474132123492693ul, 9149729557667844748ul), + PropertyTypeApiTypeChange = (4743184271904044875ul, 1966348344707118709ul), + PropertyTypeTypeChange = (6879279770561561526ul, 14002153371235144973ul), + PropertyTypeNullableChange = (1647792025069746688ul, 8521296509338272757ul), + PrimitivePropertyTypeLengthChange = (15199012632295198483ul, 15751981913385622765ul), + PrimitivePropertyTypeStorageChange = (1878445231444454307ul, 15971568751688372596ul), + ScopePropertyTypeImmutableChange = (11741304668915234934ul, 5395444112671886365ul), + ArrayPropertyTypeChange = (17156676977790645735ul, 4418145320474226565ul), + ObjectPropertyTypeChange = (10699938245219323478ul, 10048100595014077038ul), + MapPropertyTypeKeyChange = (8420584249106838521ul, 11863310851610194608ul), + MapPropertyTypeValueChange = (12611700989791556979ul, 8459671636552654180ul), + SetPropertyTypeChange = (15043350412225158280ul, 2432573808846394367ul), + TaggedPropertyTypeChange = (6486848089786756212ul, 12631967091371867447ul), + TuplePropertyTypeChange = (15457776547521378835ul, 6838666961365903865ul), + InitialV2Hash = (6209563332574727552ul, 5024404402010362065ul), + EnumPropertyAdd = (3147450154933785163ul, 15476171830180674562ul), + EnumBaseTypeChange = (4795354614928101891ul, 3597527276465802490ul), + EnumPropertyValueChange = (2592048700226976894ul, 7778124937175953351ul), + }; + (ulong low, ulong high) hash = SchemaHash.ComputeHash(this.ns, this.tableSchema); Assert.AreNotEqual((0, 0), hash); + Assert.AreEqual(expected.InitialHash, hash); + (ulong low, ulong high) hash2 = SchemaHash.ComputeHash(this.ns, this.tableSchema, (1, 1)); Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.SeedChange, hash2); // Test clone are the same. Schema clone = SchemaHashUnitTests.Clone(this.tableSchema); @@ -54,152 +94,190 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit clone.Name = "something else"; hash2 = SchemaHash.ComputeHash(this.ns, clone); Assert.AreEqual(hash, hash2); // Name not part of the hash + Assert.AreEqual(expected.NameChange, hash2); 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 + Assert.AreEqual(expected.CommentChange, hash2); 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 + Assert.AreNotEqual(hash, hash2); // Encoding version is part of the hash + Assert.AreEqual(expected.VersionChange, hash2); clone = SchemaHashUnitTests.Clone(this.tableSchema); clone.SchemaId = new SchemaId(42); hash2 = SchemaHash.ComputeHash(this.ns, clone); Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.SchemaIdChange, hash2); clone = SchemaHashUnitTests.Clone(this.tableSchema); clone.Type = TypeKind.Int8; hash2 = SchemaHash.ComputeHash(this.ns, clone); Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.TypeChange, 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); + Assert.AreEqual(expected.EnablePropertyLevelTimestampOptionChange, hash2); clone = SchemaHashUnitTests.Clone(this.tableSchema); clone.Options.DisallowUnschematized = !clone.Options.DisallowUnschematized; hash2 = SchemaHash.ComputeHash(this.ns, clone); Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.DisallowUnschematizedOptionChange, hash2); + + clone = SchemaHashUnitTests.Clone(this.tableSchema); + clone.Options.DisableSystemPrefix = !clone.Options.DisableSystemPrefix; + hash2 = SchemaHash.ComputeHash(this.ns, clone); + Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.DisableSystemPrefixOptionChange, 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); + Assert.AreEqual(expected.PartitionKeyChange, hash2); // Test Primary Sort Keys changes clone = SchemaHashUnitTests.Clone(this.tableSchema); - clone.PrimarySortKeys[0].Path = "something else"; + clone.PrimaryKeys[0].Path = "something else"; hash2 = SchemaHash.ComputeHash(this.ns, clone); Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.PrimarySortKeyChange, hash2); clone = SchemaHashUnitTests.Clone(this.tableSchema); - clone.PrimarySortKeys[0].Direction = SortDirection.Descending; + clone.PrimaryKeys[0].Direction = SortDirection.Descending; hash2 = SchemaHash.ComputeHash(this.ns, clone); Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.PrimarySortKeyDirectionChange, 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); + Assert.AreEqual(expected.StaticKeyChange, hash2); + + // Test static keys count changes + clone = SchemaHashUnitTests.Clone(this.tableSchema); + clone.StaticKeys = null; + 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); + Assert.AreEqual(expected.PropertiesPathChange, hash2); + + 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 + Assert.AreEqual(expected.PropertiesCommentChange, 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); + Assert.AreEqual(expected.PropertyTypeApiTypeChange, hash2); clone = SchemaHashUnitTests.Clone(this.tableSchema); clone.Properties[0].PropertyType.Type = TypeKind.Binary; hash2 = SchemaHash.ComputeHash(this.ns, clone); Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.PropertyTypeTypeChange, 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); + Assert.AreEqual(expected.PropertyTypeNullableChange, 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); + Assert.AreEqual(expected.PrimitivePropertyTypeLengthChange, 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); + Assert.AreEqual(expected.PrimitivePropertyTypeStorageChange, 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); + Assert.AreEqual(expected.ScopePropertyTypeImmutableChange, 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); + Assert.AreEqual(expected.ArrayPropertyTypeChange, 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); + Assert.AreEqual(expected.ObjectPropertyTypeChange, 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); + Assert.AreEqual(expected.MapPropertyTypeKeyChange, 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); + Assert.AreEqual(expected.MapPropertyTypeValueChange, 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); + Assert.AreEqual(expected.SetPropertyTypeChange, 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); + Assert.AreEqual(expected.TaggedPropertyTypeChange, 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); + Assert.AreEqual(expected.TuplePropertyTypeChange, 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); + _ = SchemaHash.ComputeHash(this.ns, clone); Assert.Fail("Should have thrown an exception."); } catch (Exception ex) @@ -211,7 +289,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { clone = SchemaHashUnitTests.Clone(this.tableSchema); (clone.Properties[7].PropertyType as UdtPropertyType).Name = "Table"; - hash2 = SchemaHash.ComputeHash(this.ns, clone); + _ = SchemaHash.ComputeHash(this.ns, clone); Assert.Fail("Should have thrown an exception."); } catch (Exception ex) @@ -226,11 +304,84 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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"; + try + { + 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); + } + finally + { + this.ns.Schemas[0].Name = "UDT"; + } + + // Test SDL V2 features. + this.tableSchema.Version = SchemaLanguageVersion.V2; + hash2 = SchemaHash.ComputeHash(this.ns, this.tableSchema); + Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.InitialV2Hash, hash2); + hash = hash2; + clone = SchemaHashUnitTests.Clone(this.tableSchema); - (clone.Properties[7].PropertyType as UdtPropertyType).Name = "RenameActualUDT"; + clone.Properties.Add( + new Property + { + Path = "myEnum", + PropertyType = new PrimitivePropertyType { Type = TypeKind.Enum, Enum = "MyEnum" } + }); hash2 = SchemaHash.ComputeHash(this.ns, clone); - Assert.AreEqual(hash, hash2); + Assert.AreNotEqual(hash, hash2); + Assert.AreEqual(expected.EnumPropertyAdd, hash2); + + string enumName = this.ns.Enums[0].Name; + TypeKind enumBaseType = this.ns.Enums[0].Type; + string enumComment = this.ns.Enums[0].Comment; + string enumApiType = this.ns.Enums[0].ApiType; + long enumValue = this.ns.Enums[0].Values[0].Value; + string enumValueComment = this.ns.Enums[0].Values[0].Comment; + + try + { + this.ns.Enums[0].Type = TypeKind.UInt16; + hash2 = SchemaHash.ComputeHash(this.ns, clone); + Assert.AreEqual(expected.EnumBaseTypeChange, hash2); + + this.ns.Enums[0].Values[0].Value = 666; + hash2 = SchemaHash.ComputeHash(this.ns, clone); + Assert.AreEqual(expected.EnumPropertyValueChange, hash2); + + this.ns.Enums[0].Values[0].Name = "Names of enum values don't matter"; + hash2 = SchemaHash.ComputeHash(this.ns, clone); + Assert.AreEqual(expected.EnumPropertyValueChange, hash2); + + this.ns.Enums[0].Values[0].Comment = "Comments of enum values don't matter"; + hash2 = SchemaHash.ComputeHash(this.ns, clone); + Assert.AreEqual(expected.EnumPropertyValueChange, hash2); + + (clone.Properties[8].PropertyType as PrimitivePropertyType).Enum = "Names of enums don't matter"; + this.ns.Enums[0].Name = "Names of enums don't matter"; + hash2 = SchemaHash.ComputeHash(this.ns, clone); + Assert.AreEqual(expected.EnumPropertyValueChange, hash2); + + this.ns.Enums[0].Comment = "Comments of enums don't matter"; + hash2 = SchemaHash.ComputeHash(this.ns, clone); + Assert.AreEqual(expected.EnumPropertyValueChange, hash2); + + this.ns.Enums[0].ApiType = "ApiType of enums don't matter"; + hash2 = SchemaHash.ComputeHash(this.ns, clone); + Assert.AreEqual(expected.EnumPropertyValueChange, hash2); + } + finally + { + this.ns.Enums[0].Name = enumName; + this.ns.Enums[0].Type = enumBaseType; + this.ns.Enums[0].Comment = enumComment; + this.ns.Enums[0].ApiType = enumApiType; + this.ns.Enums[0].Values[0].Value = enumValue; + this.ns.Enums[0].Values[0].Comment = enumValueComment; + } } private static Schema Clone(Schema s) diff --git a/dotnet/src/HybridRow.Tests.Unit/SchemaIdUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/SchemaIdUnitTests.cs similarity index 94% rename from dotnet/src/HybridRow.Tests.Unit/SchemaIdUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/SchemaIdUnitTests.cs index 8b95fdd..7c08a4d 100644 --- a/dotnet/src/HybridRow.Tests.Unit/SchemaIdUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/SchemaIdUnitTests.cs @@ -16,13 +16,15 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { SchemaId a = new SchemaId(1); SchemaId b = new SchemaId(2); - SchemaId c = default(SchemaId); + SchemaId c = default; Assert.AreEqual(1, a.Id); Assert.AreEqual(2, b.Id); Assert.AreEqual(SchemaId.Invalid, c); Assert.AreNotEqual(2, a.Id); Assert.AreNotEqual(a, b); + + // ReSharper disable once EqualExpressionComparison Assert.IsTrue(a == a); Assert.IsTrue(a != b); Assert.IsFalse(a.Equals(null)); diff --git a/dotnet/src/HybridRow.Tests.Unit/SchemaUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/SchemaUnitTests.cs similarity index 91% rename from dotnet/src/HybridRow.Tests.Unit/SchemaUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/SchemaUnitTests.cs index f548d6e..f996245 100644 --- a/dotnet/src/HybridRow.Tests.Unit/SchemaUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/SchemaUnitTests.cs @@ -6,15 +6,15 @@ 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 { + private const string SchemaFile = @"TestData\CoverageSchema.hrschema"; + [TestMethod] [Owner("jthunter")] public void ParseNamespace() @@ -27,7 +27,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit @"{'schemas': [ ] }", @"{'name': null }", @"{'name': null, 'schemas': null }", - @"{'version': 'v1', 'name': null, 'schemas': null }", + @"{'version': 'v2', 'name': null, 'schemas': null }", }; foreach (string json in emptyNamespaceJsonInput) @@ -36,41 +36,42 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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); + Assert.AreEqual(SchemaLanguageVersion.V2, n1.Version); } } // Test simple schemas and schema options. { - string json = @"{'name': 'myschema', 'schemas': null }"; + string json = @"{'name': 'MySchema', 'schemas': null }"; Namespace n1 = Namespace.Parse(json); - Assert.AreEqual("myschema", n1.Name, "Json: {0}", 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); + // Version defaults properly when NOT specified. + Assert.AreEqual(SchemaLanguageVersion.V2, n1.Version); } { - string json = @"{'name': 'myschema', 'schemas': [ + 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("MySchema", n1.Name, "Json: {0}", json); Assert.AreEqual(1, n1.Schemas.Count, "Json: {0}", json); + Assert.AreEqual(SchemaLanguageVersion.V1, n1.Schemas[0].Version, "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); + Assert.AreEqual(SchemaLanguageVersion.V2, n1.Version); } // Test basic schema with primitive columns. { - string json = @"{'name': 'myschema', 'schemas': [ + string json = @"{'name': 'MySchema', 'schemas': [ {'name': 'myUDT', 'id': 1, 'type': 'schema', 'options': { 'disallowUnschematized': false }, 'properties': [ { 'path': 'a', 'type': { 'type': 'int8', 'storage': 'fixed' }}, @@ -81,6 +82,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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(SchemaLanguageVersion.Unspecified, n1.Schemas[0].Version, "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); @@ -105,20 +107,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestMethod] [Owner("jthunter")] - [DeploymentItem(@"TestData\CoverageSchema.json", "TestData")] + [DeploymentItem(SchemaUnitTests.SchemaFile, "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 n1 = SchemaUtil.LoadFromHrSchema(SchemaUnitTests.SchemaFile); + string json2 = Namespace.ToJson(n1); Namespace n2 = Namespace.Parse(json2); - string json3 = JsonConvert.SerializeObject(n2, settings); + string json3 = Namespace.ToJson(n2); Assert.AreEqual(json2, json3); } @@ -166,7 +161,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { string columnSchema = $"{{'path': 'a', 'type': {expected.Json}}}"; string tableSchema = $"{{'name': 'table', 'id': -1, 'type': 'schema', 'properties': [{columnSchema}]}}"; - Schema s = Schema.Parse(tableSchema); + string nsSchema = $"{{'schemas': [{tableSchema}]}}"; + Schema s = Namespace.Parse(nsSchema).Schemas[0]; 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); @@ -209,7 +205,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { 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); + string udtSchema = "{'name': 'myUDT', 'id': 1, 'type': 'schema' }"; + string nsSchema = $"{{'schemas': [{tableSchema}, {udtSchema}]}}"; + Schema s = Namespace.Parse(nsSchema).Schemas[0]; 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); @@ -243,7 +241,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit // 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': 'int8' }}", 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" }, @@ -253,9 +251,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { string objectColumnSchema = $"{{'path': 'a', 'type': {{'type': 'object', 'properties': [{expected.Json}] }} }}"; string tableSchema = $"{{'name': 'table', 'id': -2, 'type': 'schema', 'properties': [{objectColumnSchema}] }}"; + string udtSchema = "{'name': 'myUDT', 'id': 1, 'type': 'schema' }"; + string nsSchema = $"{{'schemas': [{tableSchema}, {udtSchema}]}}"; try { - Schema s = Schema.Parse(tableSchema); + Schema s = Namespace.Parse(nsSchema).Schemas[0]; 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); @@ -318,20 +318,21 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 'partitionkeys': [{expected.JsonPK}], 'primarykeys': [{expected.JsonCK}] }}"; + string nsSchema = $"{{'schemas': [{tableSchema}]}}"; try { - Schema s = Schema.Parse(tableSchema); + Schema s = Namespace.Parse(nsSchema).Schemas[0]; 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++) + for (int i = 0; i < s.PrimaryKeys.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); + Assert.AreEqual(expected.CK[i].Path, s.PrimaryKeys[i].Path, "PK: {0}, CK: {1}", expected.JsonPK, expected.JsonCK); + Assert.AreEqual(expected.CK[i].Dir, s.PrimaryKeys[i].Direction, "PK: {0}, CK: {1}", expected.JsonPK, expected.JsonCK); } } catch (Exception ex) @@ -342,7 +343,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit } [TestMethod] - [Owner("vahemesw")] + [Owner("jthunter")] public void ParseSchemaStaticKeys() { var expectedSchemas = new[] @@ -377,10 +378,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 'primarykeys': [{{'path': 'b', 'direction': 'desc'}}], 'statickeys': [{expected.JsonStaticKeys}] }}"; + string nsSchema = $"{{'schemas': [{tableSchema}]}}"; try { - Schema s = Schema.Parse(tableSchema); + Schema s = Namespace.Parse(nsSchema).Schemas[0]; Assert.AreEqual(expected.NumberOfPaths, s.StaticKeys.Count); for (int i = 0; i < s.StaticKeys.Count; i++) { @@ -409,7 +411,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { string columnSchema = $"{{'path': 'a', 'type': {expected.Json}}}"; string tableSchema = $"{{'name': 'table', 'id': -4, 'type': 'schema', 'properties': [{columnSchema}]}}"; - Schema s = Schema.Parse(tableSchema); + string nsSchema = $"{{'schemas': [{tableSchema}]}}"; + Schema s = Namespace.Parse(nsSchema).Schemas[0]; Assert.AreEqual(1, s.Properties.Count, "Json: {0}", expected.Json); Property p = s.Properties[0]; switch (p.PropertyType.Type) @@ -429,7 +432,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [Owner("jthunter")] public void SchemaRef() { - NamespaceParserTest[] tests = new NamespaceParserTest[] + NamespaceParserTest[] tests = new[] { new NamespaceParserTest { @@ -475,7 +478,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [Owner("jthunter")] public void NegativeNamespaceParser() { - NamespaceParserTest[] tests = new NamespaceParserTest[] + NamespaceParserTest[] tests = new[] { new NamespaceParserTest { diff --git a/src/Serialization/HybridRow.Tests.Unit/SchemaUtil.cs b/src/Serialization/HybridRow.Tests.Unit/SchemaUtil.cs new file mode 100644 index 0000000..df3a404 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/SchemaUtil.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit +{ + using System.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + + internal static class SchemaUtil + { + private const int InitialCapacity = 2 * 1024 * 1024; + + public static Namespace LoadFromHrSchema(string filename) + { + using (Stream stm = new FileStream(filename, FileMode.Open)) + { + RowBuffer row = new RowBuffer(SchemaUtil.InitialCapacity); + row.ReadFrom(stm, (int)stm.Length, HybridRowVersion.V1, SystemSchema.LayoutResolver); + Result r = Namespace.Read(ref row, out Namespace ns); + ResultAssert.IsSuccess(r); + return ns; + } + } + } +} diff --git a/src/Serialization/HybridRow.Tests.Unit/SchemaValidatorUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/SchemaValidatorUnitTests.cs new file mode 100644 index 0000000..680a89e --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/SchemaValidatorUnitTests.cs @@ -0,0 +1,222 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit +{ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class SchemaValidatorUnitTests + { + [TestMethod] + [Owner("jthunter")] + public void EnumSchemaValidator() + { + Namespace MakeNs() + { + return new Namespace + { + Enums = new List + { + new EnumSchema + { + Name = "MyEnum", Type = TypeKind.Int8, + Values = new List + { + new EnumValue { Name = "MyValue", Value = 42 } + } + } + } + }; + } + + void AssertSuccess(string label, Action modify) + { + Namespace ns = MakeNs(); + modify(ns); + try + { + SchemaValidator.Validate(ns); + } + catch (SchemaException ex) + { + Assert.Fail($"{label} should not have thrown a validation error {ex}."); + } + } + + void AssertError(string label, Action modify) + { + Namespace ns = MakeNs(); + modify(ns); + try + { + SchemaValidator.Validate(ns); + Assert.Fail($"{label} should have thrown a validation error."); + } + catch (SchemaException ex) + { + Assert.IsNotNull(ex); + } + } + + void SetValue(EnumSchema es, TypeKind type, long value) + { + es.Type = type; + es.Values[0].Value = value; + } + + AssertSuccess("Init", ns => { }); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int8, sbyte.MinValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int8, sbyte.MaxValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int16, short.MinValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int16, short.MaxValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int32, int.MinValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int32, int.MaxValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int64, long.MinValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int64, long.MaxValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt8, byte.MinValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt8, byte.MaxValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt16, ushort.MinValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt16, ushort.MaxValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt32, uint.MinValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt32, uint.MaxValue)); + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt64, (long)ulong.MinValue)); + unchecked + { + AssertSuccess("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt64, (long)ulong.MaxValue)); + } + + AssertError("SDL v2", ns => ns.Version = SchemaLanguageVersion.V1); + AssertError("Duplicate Enum", ns => ns.Enums.Add(new EnumSchema { Name = "MyEnum", Type = TypeKind.Int8 })); + AssertError("Duplicate Value", ns => ns.Enums[0].Values.Add(new EnumValue { Name = "MyValue" })); + + // Check that only numeric types are validate base types. + foreach (TypeKind type in Enum.GetValues(typeof(TypeKind))) + { + switch (type) + { + case TypeKind.Int8: + case TypeKind.Int16: + case TypeKind.Int32: + case TypeKind.Int64: + case TypeKind.UInt8: + case TypeKind.UInt16: + case TypeKind.UInt32: + case TypeKind.UInt64: + case TypeKind.VarInt: + case TypeKind.VarUInt: + AssertSuccess("Valid base type", ns => ns.Enums[0].Type = type); + break; + default: + AssertError("Invalid base type", ns => ns.Enums[0].Type = type); + break; + } + } + + AssertError("New Value Fit", ns => ns.Enums[0].Values.Add(new EnumValue { Name = "MyValue", Value = 256 })); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int8, sbyte.MinValue - 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int8, sbyte.MaxValue + 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int16, short.MinValue - 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int16, short.MaxValue + 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int32, (long)int.MinValue - 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.Int32, (long)int.MaxValue + 1)); + + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt8, byte.MinValue - 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt8, byte.MaxValue + 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt16, ushort.MinValue - 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt16, ushort.MaxValue + 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt32, (long)uint.MinValue - 1)); + AssertError("Value Fit", ns => SetValue(ns.Enums[0], TypeKind.UInt32, (long)uint.MaxValue + 1)); + } + + [TestMethod] + [Owner("jthunter")] + public void RowBufferSizeSchemaValidator() + { + Namespace MakeNs() + { + return new Namespace + { + Schemas = new List + { + new Schema + { + Name = "MyType", + SchemaId = new SchemaId(1), + Properties = new List + { + new Property + { + Path = "Prop", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int32, + Storage = StorageKind.Fixed, + RowBufferSize = true, + } + } + } + } + } + }; + } + + void AssertSuccess(string label, Action modify) + { + Namespace ns = MakeNs(); + modify(ns); + try + { + SchemaValidator.Validate(ns); + } + catch (SchemaException ex) + { + Assert.Fail($"{label} should not have thrown a validation error {ex}."); + } + } + + void AssertError(string label, Action modify) + { + Namespace ns = MakeNs(); + modify(ns); + try + { + SchemaValidator.Validate(ns); + Assert.Fail($"{label} should have thrown a validation error."); + } + catch (SchemaException ex) + { + Assert.IsNotNull(ex); + } + } + + AssertSuccess("Init", ns => { }); + + void Set(Namespace ns, TypeKind type, StorageKind storage) + { + Property p = ns.Schemas[0].Properties[0]; + PrimitivePropertyType pp = p.PropertyType as PrimitivePropertyType; + pp.Type = type; + pp.Storage = storage; + } + + for (TypeKind t = TypeKind.Null; t < TypeKind.Object; t++) + { + if (t == TypeKind.Int32) + { + continue; + } + + // ReSharper disable once AccessToModifiedClosure + AssertError("Wrong type", ns => Set(ns, t, StorageKind.Fixed)); + } + + AssertError("Wrong storage", ns => Set(ns, TypeKind.Int32, StorageKind.Sparse)); + AssertError("Wrong storage", ns => Set(ns, TypeKind.Int32, StorageKind.Variable)); + } + } +} diff --git a/dotnet/src/HybridRow.Tests.Unit/SerializerUnitTest.cs b/src/Serialization/HybridRow.Tests.Unit/SerializerUnitTest.cs similarity index 98% rename from dotnet/src/HybridRow.Tests.Unit/SerializerUnitTest.cs rename to src/Serialization/HybridRow.Tests.Unit/SerializerUnitTest.cs index 38d6eb6..270432e 100644 --- a/dotnet/src/HybridRow.Tests.Unit/SerializerUnitTest.cs +++ b/src/Serialization/HybridRow.Tests.Unit/SerializerUnitTest.cs @@ -12,7 +12,6 @@ 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; @@ -24,7 +23,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [DeploymentItem(SerializerUnitTest.SchemaFile, "TestData")] public sealed class SerializerUnitTest { - private const string SchemaFile = @"TestData\BatchApiSchema.json"; + private const string SchemaFile = @"TestData\BatchApiSchema.hrschema"; private const int InitialRowSize = 2 * 1024 * 1024; private Namespace schema; @@ -34,8 +33,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestInitialize] public void InitTestSuite() { - string json = File.ReadAllText(SerializerUnitTest.SchemaFile); - this.schema = Namespace.Parse(json); + this.schema = SchemaUtil.LoadFromHrSchema(SerializerUnitTest.SchemaFile); this.resolver = new LayoutResolverNamespace(this.schema); this.layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "BatchRequest").SchemaId); } diff --git a/src/Serialization/HybridRow.Tests.Unit/SystemSchemaUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/SystemSchemaUnitTests.cs new file mode 100644 index 0000000..3ee1688 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/SystemSchemaUnitTests.cs @@ -0,0 +1,357 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit +{ + using System.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestTools.UnitTesting.Logging; + + [TestClass] + public class SystemSchemaUnitTests + { + private const string CrossVersioningSchema = @"TestData\CrossVersioningSchema.json"; + private const string CustomerSchema = @"TestData\CustomerSchema.json"; + private const string NullableSchema = @"TestData\NullableSchema.json"; + private const string ReaderSchema = @"TestData\ReaderSchema.json"; + private const string SchemaHashCoverageSchema = @"TestData\SchemaHashCoverageSchema.json"; + private const string CoverageSchema = @"TestData\CoverageSchema.json"; + private const string BatchApiSchema = @"TestData\BatchApiSchema.json"; + private const string TaggedApiSchema = @"TestData\TaggedApiSchema.json"; + private const string PerfCounterSchema = @"TestData\PerfCounterSchema.json"; + private const string TagSchema = @"TestData\TagSchema.json"; + private const string MovieSchema = @"TestData\MovieSchema.json"; + private const string SchemaTodoSchema = @"TestData\TodoSchema.json"; + + [TestMethod] + [Owner("jthunter")] + public void LoadGeneratedHrSchema() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve((SchemaId)SchemaOptionsHybridRowSerializer.SchemaId); + Assert.AreEqual((SchemaId)SchemaOptionsHybridRowSerializer.SchemaId, layout.SchemaId); + } + + [TestMethod] + [Owner("jthunter")] + public void LoadTest() + { + LayoutResolver resolver = SystemSchema.LayoutResolver; + + SchemaId[] systemSchemas = + { + SystemSchema.EmptySchemaId, + (SchemaId)SegmentHybridRowSerializer.SchemaId, + (SchemaId)RecordHybridRowSerializer.SchemaId, + (SchemaId)NamespaceHybridRowSerializer.SchemaId, + (SchemaId)SchemaHybridRowSerializer.SchemaId, + (SchemaId)SchemaOptionsHybridRowSerializer.SchemaId, + (SchemaId)PartitionKeyHybridRowSerializer.SchemaId, + (SchemaId)PrimarySortKeyHybridRowSerializer.SchemaId, + (SchemaId)StaticKeyHybridRowSerializer.SchemaId, + (SchemaId)PropertyHybridRowSerializer.SchemaId, + (SchemaId)PropertyTypeHybridRowSerializer.SchemaId, + (SchemaId)PrimitivePropertyTypeHybridRowSerializer.SchemaId, + (SchemaId)ScopePropertyTypeHybridRowSerializer.SchemaId, + (SchemaId)ArrayPropertyTypeHybridRowSerializer.SchemaId, + (SchemaId)ObjectPropertyTypeHybridRowSerializer.SchemaId, + (SchemaId)SetPropertyTypeHybridRowSerializer.SchemaId, + (SchemaId)MapPropertyTypeHybridRowSerializer.SchemaId, + (SchemaId)TuplePropertyTypeHybridRowSerializer.SchemaId, + (SchemaId)TaggedPropertyTypeHybridRowSerializer.SchemaId, + }; + + // Make sure all system schemas are loadable. + foreach (SchemaId id in systemSchemas) + { + Layout l = resolver.Resolve(id); + Assert.AreEqual(id, l.SchemaId); + } + + // Make sure all system schema ids are unique. + foreach (SchemaId id in systemSchemas) + { + int count = 0; + foreach (SchemaId other in systemSchemas) + { + if (other == id) + { + count++; + } + } + + Assert.AreEqual(1, count); + } + } + + [TestMethod] + [Owner("jthunter")] + public void SerializeSystemNamespaceTest() + { + Namespace ns1 = SystemSchema.GetNamespace(); + SystemSchemaUnitTests.SerializerRoundtripNamespace(ns1); + } + + [TestMethod] + [Owner("jthunter")] + [DeploymentItem(SystemSchemaUnitTests.CrossVersioningSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.CustomerSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.NullableSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.ReaderSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.SchemaHashCoverageSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.CoverageSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.BatchApiSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.TaggedApiSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.PerfCounterSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.TagSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.MovieSchema, "TestData")] + [DeploymentItem(SystemSchemaUnitTests.SchemaTodoSchema, "TestData")] + public void SerializeTestSchemasTest() + { + string[] files = + { + SystemSchemaUnitTests.CrossVersioningSchema, + SystemSchemaUnitTests.CustomerSchema, + SystemSchemaUnitTests.NullableSchema, + SystemSchemaUnitTests.ReaderSchema, + SystemSchemaUnitTests.SchemaHashCoverageSchema, + SystemSchemaUnitTests.CoverageSchema, + SystemSchemaUnitTests.BatchApiSchema, + SystemSchemaUnitTests.TaggedApiSchema, + SystemSchemaUnitTests.PerfCounterSchema, + SystemSchemaUnitTests.TagSchema, + SystemSchemaUnitTests.MovieSchema, + SystemSchemaUnitTests.SchemaTodoSchema, + }; + + foreach (string filename in files) + { + Logger.LogMessage("Filename: {0}", filename); + string json = File.ReadAllText(filename); + Namespace ns1 = Namespace.Parse(json); + SystemSchemaUnitTests.SerializerRoundtripNamespace(ns1); + } + } + + private static void SerializerRoundtripNamespace(Namespace ns1) + { + Layout layout = SystemSchema.LayoutResolver.Resolve((SchemaId)NamespaceHybridRowSerializer.SchemaId); + + RowBuffer row = new RowBuffer(0); + row.InitLayout(HybridRowVersion.V1, layout, SystemSchema.LayoutResolver); + + // Write the whole namespace to a row. + Result r = ns1.Write(ref row); + ResultAssert.IsSuccess(r); + + // Read the namespace back. + r = Namespace.Read(ref row, out Namespace ns2); + ResultAssert.IsSuccess(r); + + // Compare the materialized row with the original in-memory object model. + Assert.AreEqual(ns1.Version, ns2.Version); + Assert.AreEqual(ns1.Name, ns2.Name); + Assert.AreEqual(ns1.Comment, ns2.Comment); + Assert.AreEqual(ns1.CppNamespace, ns2.CppNamespace); + Assert.AreEqual(ns1.Schemas.Count, ns2.Schemas.Count); + for (int i = 0; i < ns1.Schemas.Count; i++) + { + Schema s1 = ns1.Schemas[i]; + Schema s2 = ns2.Schemas[i]; + SystemSchemaUnitTests.AssertEqual(s1, s2); + } + Assert.AreEqual(ns1.Enums.Count, ns2.Enums.Count); + for (int i = 0; i < ns1.Enums.Count; i++) + { + EnumSchema s1 = ns1.Enums[i]; + EnumSchema s2 = ns2.Enums[i]; + SystemSchemaUnitTests.AssertEqual(s1, s2); + } + } + + private static void AssertEqual(Schema s1, Schema s2) + { + Assert.AreEqual(s1.Version, s2.Version); + Assert.AreEqual(s1.Type, s2.Type); + Assert.AreEqual(s1.SchemaId, s2.SchemaId); + Assert.AreEqual(s1.Name, s2.Name); + Assert.AreEqual(s1.Comment, s2.Comment); + Assert.AreEqual(s1.BaseName, s2.BaseName); + Assert.AreEqual(s1.BaseSchemaId, s2.BaseSchemaId); + + if (s1.Options == null) + { + Assert.IsNull(s2.Options); + } + else + { + Assert.IsNotNull(s2.Options); + Assert.AreEqual(s1.Options.DisallowUnschematized, s2.Options.DisallowUnschematized); + Assert.AreEqual(s1.Options.EnablePropertyLevelTimestamp, s2.Options.EnablePropertyLevelTimestamp); + Assert.AreEqual(s1.Options.DisableSystemPrefix, s2.Options.DisableSystemPrefix); + Assert.AreEqual(s1.Options.Abstract, s2.Options.Abstract); + } + + Assert.AreEqual(s1.PartitionKeys.Count, s2.PartitionKeys.Count); + for (int i = 0; i < s1.PartitionKeys.Count; i++) + { + SystemSchemaUnitTests.AssertEqual(s1.PartitionKeys[i], s2.PartitionKeys[i]); + } + + Assert.AreEqual(s1.PrimaryKeys.Count, s2.PrimaryKeys.Count); + for (int i = 0; i < s1.PrimaryKeys.Count; i++) + { + SystemSchemaUnitTests.AssertEqual(s1.PrimaryKeys[i], s2.PrimaryKeys[i]); + } + + Assert.AreEqual(s1.StaticKeys.Count, s2.StaticKeys.Count); + for (int i = 0; i < s1.StaticKeys.Count; i++) + { + SystemSchemaUnitTests.AssertEqual(s1.StaticKeys[i], s2.StaticKeys[i]); + } + + Assert.AreEqual(s1.Properties.Count, s2.Properties.Count); + for (int i = 0; i < s1.Properties.Count; i++) + { + SystemSchemaUnitTests.AssertEqual(s1.Properties[i], s2.Properties[i]); + } + } + + private static void AssertEqual(PartitionKey i1, PartitionKey i2) + { + Assert.AreEqual(i1.Path, i2.Path); + } + + private static void AssertEqual(PrimarySortKey i1, PrimarySortKey i2) + { + Assert.AreEqual(i1.Path, i2.Path); + Assert.AreEqual(i1.Direction, i2.Direction); + } + + private static void AssertEqual(StaticKey i1, StaticKey i2) + { + Assert.AreEqual(i1.Path, i2.Path); + } + + private static void AssertEqual(Property i1, Property i2) + { + Assert.AreEqual(i1.Comment, i2.Comment); + Assert.AreEqual(i1.Path, i2.Path); + Assert.AreEqual(i1.ApiName, i2.ApiName); + Assert.AreEqual(i1.AllowEmpty, i2.AllowEmpty); + SystemSchemaUnitTests.AssertEqual(i1.PropertyType, i2.PropertyType); + } + + private static void AssertEqual(PropertyType i1, PropertyType i2) + { + Assert.AreEqual(i1.ApiType, i2.ApiType); + Assert.AreEqual(i1.Type, i2.Type); + Assert.AreEqual(i1.Nullable, i2.Nullable); + Assert.AreEqual(i1.GetType(), i2.GetType()); + + switch (i1) + { + case PrimitivePropertyType p: + SystemSchemaUnitTests.AssertEqual(p, (PrimitivePropertyType)i2); + return; + case ScopePropertyType p: + SystemSchemaUnitTests.AssertEqual(p, (ScopePropertyType)i2); + return; + default: + Assert.Fail("Type is abstract."); + return; + } + } + + private static void AssertEqual(PrimitivePropertyType i1, PrimitivePropertyType i2) + { + Assert.AreEqual(i1.Length, i2.Length); + Assert.AreEqual(i1.Storage, i2.Storage); + } + + private static void AssertEqual(ScopePropertyType i1, ScopePropertyType i2) + { + Assert.AreEqual(i1.Immutable, i2.Immutable); + + switch (i1) + { + case ArrayPropertyType p: + SystemSchemaUnitTests.AssertEqual(p.Items, ((ArrayPropertyType)i2).Items); + break; + case ObjectPropertyType p: + { + ObjectPropertyType q = (ObjectPropertyType)i2; + Assert.AreEqual(p.Properties.Count, q.Properties.Count); + for (int i = 0; i < p.Properties.Count; i++) + { + SystemSchemaUnitTests.AssertEqual(p.Properties[i], q.Properties[i]); + } + + break; + } + + case SetPropertyType p: + SystemSchemaUnitTests.AssertEqual(p.Items, ((SetPropertyType)i2).Items); + break; + case MapPropertyType p: + SystemSchemaUnitTests.AssertEqual(p.Keys, ((MapPropertyType)i2).Keys); + SystemSchemaUnitTests.AssertEqual(p.Values, ((MapPropertyType)i2).Values); + break; + case TuplePropertyType p: + { + TuplePropertyType q = (TuplePropertyType)i2; + Assert.AreEqual(p.Items.Count, q.Items.Count); + for (int i = 0; i < p.Items.Count; i++) + { + SystemSchemaUnitTests.AssertEqual(p.Items[i], q.Items[i]); + } + + break; + } + + case TaggedPropertyType p: + { + TaggedPropertyType q = (TaggedPropertyType)i2; + Assert.AreEqual(p.Items.Count, q.Items.Count); + for (int i = 0; i < p.Items.Count; i++) + { + SystemSchemaUnitTests.AssertEqual(p.Items[i], q.Items[i]); + } + + break; + } + + case UdtPropertyType p: + Assert.AreEqual(p.Name, ((UdtPropertyType)i2).Name); + Assert.AreEqual(p.SchemaId, ((UdtPropertyType)i2).SchemaId); + break; + default: + Assert.Fail("Type is abstract."); + return; + } + } + + private static void AssertEqual(EnumSchema s1, EnumSchema s2) + { + Assert.AreEqual(s1.Type, s2.Type); + Assert.AreEqual(s1.ApiType, s2.ApiType); + Assert.AreEqual(s1.Name, s2.Name); + Assert.AreEqual(s1.Comment, s2.Comment); + + Assert.AreEqual(s1.Values.Count, s2.Values.Count); + for (int i = 0; i < s1.Values.Count; i++) + { + SystemSchemaUnitTests.AssertEqual(s1.Values[i], s2.Values[i]); + } + } + + private static void AssertEqual(EnumValue s1, EnumValue s2) + { + Assert.AreEqual(s1.Value, s2.Value); + Assert.AreEqual(s1.Name, s2.Name); + Assert.AreEqual(s1.Comment, s2.Comment); + } + } +} diff --git a/dotnet/src/HybridRow.Tests.Unit/TaggedUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/TaggedUnitTests.cs similarity index 92% rename from dotnet/src/HybridRow.Tests.Unit/TaggedUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/TaggedUnitTests.cs index 2e3e0e0..ee1f73b 100644 --- a/dotnet/src/HybridRow.Tests.Unit/TaggedUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/TaggedUnitTests.cs @@ -5,7 +5,6 @@ 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; @@ -14,7 +13,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [DeploymentItem(TaggedUnitTests.SchemaFile, "TestData")] public sealed class TaggedUnitTests { - private const string SchemaFile = @"TestData\TaggedApiSchema.json"; + private const string SchemaFile = @"TestData\TaggedApiSchema.hrschema"; private const int InitialRowSize = 2 * 1024 * 1024; private Namespace schema; @@ -24,8 +23,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestInitialize] public void ParseNamespaceExample() { - string json = File.ReadAllText(TaggedUnitTests.SchemaFile); - this.schema = Namespace.Parse(json); + this.schema = SchemaUtil.LoadFromHrSchema(TaggedUnitTests.SchemaFile); this.resolver = new LayoutResolverNamespace(this.schema); this.layout = this.resolver.Resolve(this.schema.Schemas.Find(x => x.Name == "TaggedApi").SchemaId); } @@ -117,7 +115,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public override bool Equals(object obj) { - if (object.ReferenceEquals(null, obj)) + if (obj is null) { return false; } @@ -132,13 +130,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public override int GetHashCode() { - unchecked - { - int hashCode = 0; - hashCode = (hashCode * 397) ^ this.Tag1.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Tag2.GetHashCode(); - return hashCode; - } + return System.HashCode.Combine(this.Tag1, this.Tag2); } } } diff --git a/test-data/CrossVersioningExpected.json b/src/Serialization/HybridRow.Tests.Unit/TestData/CrossVersioningExpected.json similarity index 100% rename from test-data/CrossVersioningExpected.json rename to src/Serialization/HybridRow.Tests.Unit/TestData/CrossVersioningExpected.json diff --git a/dotnet/src/HybridRow.Tests.Unit/TupleUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/TupleUnitTests.cs similarity index 77% rename from dotnet/src/HybridRow.Tests.Unit/TupleUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/TupleUnitTests.cs index 63adcee..1e14379 100644 --- a/dotnet/src/HybridRow.Tests.Unit/TupleUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/TupleUnitTests.cs @@ -5,16 +5,16 @@ 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.Serialization.HybridRow.Layouts; using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedArray; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedTuple; 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; @@ -22,20 +22,16 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit private readonly PerfCounter counterExample = new PerfCounter() { Name = "RowInserts", - Value = Tuple.Create("units", 12046L), + Value = ("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); + this.countersLayout = TypedTupleHrSchema.LayoutResolver.Resolve((SchemaId)PerfCounterHybridRowSerializer.SchemaId); + } [TestMethod] @@ -43,12 +39,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void CreateCounter() { RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize); - row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver); + row.InitLayout(HybridRowVersion.V1, this.countersLayout, TypedTupleHrSchema.LayoutResolver); 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); + Assert.IsTrue(PerfCounterComparer.Default.Equals(c1, c2)); } [TestMethod] @@ -56,7 +52,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void VerifyTypeConstraintsCounter() { RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize); - row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver); + row.InitLayout(HybridRowVersion.V1, this.countersLayout, TypedTupleHrSchema.LayoutResolver); PerfCounter c1 = this.counterExample; this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1); @@ -76,7 +72,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void PreventInsertsAndDeletesInFixedArityCounter() { RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize); - row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver); + row.InitLayout(HybridRowVersion.V1, this.countersLayout, TypedTupleHrSchema.LayoutResolver); PerfCounter c1 = this.counterExample; this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1); @@ -101,17 +97,17 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void CreateMinMeanMaxCounter() { RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize); - row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver); + row.InitLayout(HybridRowVersion.V1, this.countersLayout, TypedTupleHrSchema.LayoutResolver); PerfCounter c1 = new PerfCounter() { Name = "RowInserts", - MinMaxValue = Tuple.Create("units", Tuple.Create(12L, 542L, 12046L)), + MinMaxValue = ("units", (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); + Assert.IsTrue(PerfCounterComparer.Default.Equals(c1, c2)); } [TestMethod] @@ -119,17 +115,17 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void CreateCoordCounter() { RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize); - row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver); + row.InitLayout(HybridRowVersion.V1, this.countersLayout, TypedTupleHrSchema.LayoutResolver); PerfCounter c1 = new PerfCounter() { Name = "CoordInserts", - Coord = Tuple.Create("units", new Coord { Lat = 12L, Lng = 40L }), + Coord = ("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); + Assert.IsTrue(PerfCounterComparer.Default.Equals(c1, c2)); } [TestMethod] @@ -137,12 +133,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void VerifyTypeConstraintsMinMeanMaxCounter() { RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize); - row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver); + row.InitLayout(HybridRowVersion.V1, this.countersLayout, TypedTupleHrSchema.LayoutResolver); PerfCounter c1 = new PerfCounter() { Name = "RowInserts", - MinMaxValue = Tuple.Create("units", Tuple.Create(12L, 542L, 12046L)), + MinMaxValue = ("units", (12L, 542L, 12046L)), }; this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1); @@ -204,12 +200,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit public void VerifyTypeConstraintsCoordCounter() { RowBuffer row = new RowBuffer(TupleUnitTests.InitialRowSize); - row.InitLayout(HybridRowVersion.V1, this.countersLayout, this.countersResolver); + row.InitLayout(HybridRowVersion.V1, this.countersLayout, TypedTupleHrSchema.LayoutResolver); PerfCounter c1 = new PerfCounter() { Name = "RowInserts", - Coord = Tuple.Create("units", new Coord { Lat = 12L, Lng = 40L }), + Coord = ("units", new Coord { Lat = 12L, Lng = 40L }), }; this.WriteCounter(ref row, ref RowCursor.Create(ref row, out RowCursor _), c1); @@ -239,8 +235,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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); + Layout layout = TypedTupleHrSchema.LayoutResolver.Resolve((SchemaId)CounterSetHybridRowSerializer.SchemaId); + row.InitLayout(HybridRowVersion.V1, layout, TypedTupleHrSchema.LayoutResolver); Assert.IsTrue(layout.TryFind("history", out LayoutColumn col)); Assert.IsTrue(layout.Tokenizer.TryFindToken(col.Path, out StringToken historyToken)); @@ -265,52 +261,56 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit (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.TypedTuple.WriteScope( + ref row3, + ref udtCur.Find(ref row3, col3.Path), + col3.TypeArgs, + ctx3, + (ref RowBuffer row4, ref RowCursor tupCur, int ctx4) => { - ResultAssert.IsSuccess(LayoutType.Utf8.WriteSparse(ref row4, ref tupCur, "abc")); - } + 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) + 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) => { - ResultAssert.IsSuccess(LayoutType.Int64.WriteSparse(ref row5, ref tupCur2, 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 > 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)); - } + 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; - })); + return Result.Success; + })); return Result.Success; })); @@ -322,12 +322,35 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit })); } + 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().WriteFixed(ref row, ref coordScope, c, cd.Lat)); + Assert.IsTrue(coordLayout.TryFind("lng", out c)); + ResultAssert.IsSuccess(c.TypeAs().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().ReadFixed(ref row, ref coordScope, c, out long lat)); + cd.Lat = lat; + Assert.IsTrue(coordLayout.TryFind("lng", out c)); + ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref coordScope, c, out long lng)); + cd.Lng = lng; + + return cd; + } + private void WriteCounter(ref RowBuffer row, ref RowCursor root, PerfCounter pc) { Assert.IsTrue(this.countersLayout.TryFind("name", out LayoutColumn c)); ResultAssert.IsSuccess(c.TypeAs().WriteVariable(ref row, ref root, c, pc.Name)); - if (pc.Value != null) + if (pc.Value != default) { Assert.IsTrue(this.countersLayout.TryFind("value", out c)); root.Clone(out RowCursor valueScope).Find(ref row, c.Path); @@ -337,7 +360,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs().WriteSparse(ref row, ref valueScope, pc.Value.Item2)); } - if (pc.MinMaxValue != null) + if (pc.MinMaxValue != default) { // ReSharper disable once StringLiteralTypo Assert.IsTrue(this.countersLayout.TryFind("minmeanmax", out c)); @@ -362,7 +385,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit mmmType.TypeArgs[2].Type.TypeAs().WriteSparse(ref row, ref mmmScope, pc.MinMaxValue.Item2.Item3)); } - if (pc.Coord != null) + if (pc.Coord != default) { Assert.IsTrue(this.countersLayout.TryFind("coord", out c)); root.Clone(out RowCursor valueScope).Find(ref row, c.Path); @@ -381,7 +404,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit { PerfCounter pc = new PerfCounter(); Assert.IsTrue(this.countersLayout.TryFind("name", out LayoutColumn c)); - ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out pc.Name)); + ResultAssert.IsSuccess(c.TypeAs().ReadVariable(ref row, ref root, c, out string name)); + pc.Name = name; Assert.IsTrue(this.countersLayout.TryFind("value", out c)); Assert.IsTrue(c.Type.Immutable); @@ -393,7 +417,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs().ReadSparse(ref row, ref valueScope, out string units)); Assert.IsTrue(valueScope.MoveNext(ref row)); ResultAssert.IsSuccess(c.TypeArgs[1].Type.TypeAs().ReadSparse(ref row, ref valueScope, out long metric)); - pc.Value = Tuple.Create(units, metric); + pc.Value = (units, metric); } // ReSharper disable once StringLiteralTypo @@ -419,7 +443,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.IsTrue(mmmScope.MoveNext(ref row)); ResultAssert.IsSuccess(mmmType.TypeArgs[2].Type.TypeAs().ReadSparse(ref row, ref mmmScope, out long max)); - pc.MinMaxValue = Tuple.Create(units, Tuple.Create(min, mean, max)); + pc.MinMaxValue = (units, (min, mean, max)); } Assert.IsTrue(this.countersLayout.TryFind("coord", out c)); @@ -434,112 +458,80 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit Assert.IsTrue(valueScope.MoveNext(ref row)); ResultAssert.IsSuccess( c.TypeArgs[1].Type.TypeAs().ReadScope(ref row, ref valueScope, out RowCursor coordScope)); - pc.Coord = Tuple.Create(units, TupleUnitTests.ReadCoord(ref row, ref coordScope)); + pc.Coord = (units, TupleUnitTests.ReadCoord(ref row, ref coordScope)); } return pc; } - private static void WriteCoord(ref RowBuffer row, ref RowCursor coordScope, TypeArgumentList typeArgs, Coord cd) + private sealed class PerfCounterComparer : EqualityComparer { - Layout coordLayout = row.Resolver.Resolve(typeArgs.SchemaId); - Assert.IsTrue(coordLayout.TryFind("lat", out LayoutColumn c)); - ResultAssert.IsSuccess(c.TypeAs().WriteFixed(ref row, ref coordScope, c, cd.Lat)); - Assert.IsTrue(coordLayout.TryFind("lng", out c)); - ResultAssert.IsSuccess(c.TypeAs().WriteFixed(ref row, ref coordScope, c, cd.Lng)); - } + public static new readonly PerfCounterComparer Default = new PerfCounterComparer(); - 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().ReadFixed(ref row, ref coordScope, c, out cd.Lat)); - Assert.IsTrue(coordLayout.TryFind("lng", out c)); - ResultAssert.IsSuccess(c.TypeAs().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 Value; - public Tuple> MinMaxValue; - - // ReSharper disable once MemberHidesStaticFromOuterClass - public Tuple Coord; - - // ReSharper disable once MemberCanBePrivate.Local - public bool Equals(PerfCounter other) + public override bool Equals(PerfCounter x, PerfCounter y) { - 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)) + if (object.ReferenceEquals(x, y)) { 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)) + if (object.ReferenceEquals(x, null)) { return false; } + if (object.ReferenceEquals(y, null)) + { + return false; + } + if (x.GetType() != y.GetType()) + { + return false; + } + return string.Equals(x.Name, y.Name) && + object.Equals(x.Value, y.Value) && + object.Equals(x.MinMaxValue, y.MinMaxValue) && + object.Equals(x.Coord.Item1, y.Coord.Item1) && + CoordComparer.Default.Equals(x.Coord.Item2, y.Coord.Item2); + } - if (object.ReferenceEquals(this, obj)) + public override int GetHashCode(PerfCounter obj) + { + return HashCode.Combine( + obj.Name, + obj.Value, + obj.MinMaxValue, + obj.Coord.Item1, + CoordComparer.Default.GetHashCode(obj.Coord.Item2)); + } + } + + private sealed class CoordComparer : EqualityComparer + { + public static new readonly CoordComparer Default = new CoordComparer(); + + public override bool Equals(Coord x, Coord y) + { + if (object.ReferenceEquals(x, y)) { return true; } - - return obj is Coord coord && this.Equals(coord); - } - - public override int GetHashCode() - { - unchecked + if (object.ReferenceEquals(x, null)) { - return (this.Lat.GetHashCode() * 397) ^ this.Lng.GetHashCode(); + return false; } + if (object.ReferenceEquals(y, null)) + { + return false; + } + if (x.GetType() != y.GetType()) + { + return false; + } + return x.Lat == y.Lat && x.Lng == y.Lng; } - private bool Equals(Coord other) + public override int GetHashCode(Coord obj) { - return this.Lat == other.Lat && this.Lng == other.Lng; + return HashCode.Combine(obj.Lat, obj.Lng); } } } diff --git a/src/Serialization/HybridRow.Tests.Unit/TypedArrayUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/TypedArrayUnitTests.cs new file mode 100644 index 0000000..abdbb31 --- /dev/null +++ b/src/Serialization/HybridRow.Tests.Unit/TypedArrayUnitTests.cs @@ -0,0 +1,179 @@ +// ------------------------------------------------------------ +// 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.Linq; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit.TypedArray; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public sealed class TypedArrayUnitTests + { + private const int InitialRowSize = 2 * 1024 * 1024; + private Layout layout; + + [TestInitialize] + public void ParseNamespaceExample() + { + this.layout = TypedArrayHrSchema.LayoutResolver.Resolve((SchemaId)TaggedHybridRowSerializer.SchemaId); + } + + [TestMethod] + [Owner("jthunter")] + public void CreateTags() + { + RowBuffer row = new RowBuffer(TypedArrayUnitTests.InitialRowSize); + row.InitLayout(HybridRowVersion.V1, this.layout, TypedArrayHrSchema.LayoutResolver); + + Tagged t1 = new Tagged + { + Title = "Thriller", + Tags = new List { "classic", "Post-disco", "funk" }, + Options = new List { 8, null, 9 }, + Ratings = new List> + { + new List { 1.2, 3.0 }, + new List { 4.1, 5.7 }, + new List { 7.3, 8.12, 9.14 }, + }, + Similars = new List + { + new SimilarMatch { Thumbprint = "TRABACN128F425B784", Score = 0.87173699999999998 }, + new SimilarMatch { Thumbprint = "TRJYGLF12903CB4952", Score = 0.75105200000000005 }, + new SimilarMatch { Thumbprint = "TRWJMMB128F429D550", Score = 0.50866100000000003 }, + }, + Priority = new List<(string, long)> + { + ("80's", 100L), + ("classics", 100L), + ("pop", 50L), + }, + }; + + ResultAssert.IsSuccess( + default(TaggedHybridRowSerializer).Write( + ref row, + ref RowCursor.Create(ref row, out RowCursor _), + true, + default, + t1)); + ResultAssert.IsSuccess( + default(TaggedHybridRowSerializer).Read( + ref row, + ref RowCursor.Create(ref row, out RowCursor _), + true, + out Tagged t2)); + Assert.IsTrue(TaggedComparer.Default.Equals(t1, t2)); + } + + private sealed class TaggedComparer : EqualityComparer + { + public static new readonly TaggedComparer Default = new TaggedComparer(); + + public override bool Equals(Tagged x, Tagged y) + { + if (object.ReferenceEquals(x, y)) + { + return true; + } + if (object.ReferenceEquals(x, null)) + { + return false; + } + if (object.ReferenceEquals(y, null)) + { + return false; + } + if (x.GetType() != y.GetType()) + { + return false; + } + return string.Equals(x.Title, y.Title) && + (object.ReferenceEquals(x.Tags, y.Tags) || + ((x.Tags != null) && (y.Tags != null) && x.Tags.SequenceEqual(y.Tags))) && + (object.ReferenceEquals(x.Options, y.Options) || + ((x.Options != null) && (y.Options != null) && x.Options.SequenceEqual(y.Options))) && + (object.ReferenceEquals(x.Ratings, y.Ratings) || + ((x.Ratings != null) && (y.Ratings != null) && TaggedComparer.NestedSequenceEquals(x.Ratings, y.Ratings))) && + (object.ReferenceEquals(x.Similars, y.Similars) || + ((x.Similars != null) && + (y.Similars != null) && + x.Similars.SequenceEqual(y.Similars, SimilarMatchComparer.Default))) && + (object.ReferenceEquals(x.Priority, y.Priority) || + ((x.Priority != null) && (y.Priority != null) && x.Priority.SequenceEqual(y.Priority))); + } + + public override int GetHashCode(Tagged obj) + { + unchecked + { + int hashCode = obj.Title?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (obj.Tags?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (obj.Options?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (obj.Ratings?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (obj.Similars?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (obj.Priority?.GetHashCode() ?? 0); + return hashCode; + } + } + + private static bool NestedSequenceEquals(List> left, List> 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 sealed class SimilarMatchComparer : EqualityComparer + { + public static new readonly SimilarMatchComparer Default = new SimilarMatchComparer(); + + public override bool Equals(SimilarMatch x, SimilarMatch y) + { + if (object.ReferenceEquals(x, y)) + { + return true; + } + if (object.ReferenceEquals(x, null)) + { + return false; + } + if (object.ReferenceEquals(y, null)) + { + return false; + } + if (x.GetType() != y.GetType()) + { + return false; + } + return x.Thumbprint == y.Thumbprint && + EqualityComparer.Default.Equals(x.Score, y.Score); + } + + public override int GetHashCode(SimilarMatch obj) + { + return HashCode.Combine(obj.Thumbprint, obj.Score); + } + } + } +} diff --git a/dotnet/src/HybridRow.Tests.Unit/TypedMapUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/TypedMapUnitTests.cs similarity index 98% rename from dotnet/src/HybridRow.Tests.Unit/TypedMapUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/TypedMapUnitTests.cs index 4332bec..76641a7 100644 --- a/dotnet/src/HybridRow.Tests.Unit/TypedMapUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/TypedMapUnitTests.cs @@ -7,7 +7,6 @@ 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; @@ -17,7 +16,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [DeploymentItem(TypedMapUnitTests.SchemaFile, "TestData")] public sealed class TypedMapUnitTests { - private const string SchemaFile = @"TestData\MovieSchema.json"; + private const string SchemaFile = @"TestData\MovieSchema.hrschema"; private const int InitialRowSize = 2 * 1024 * 1024; private Namespace counterSchema; @@ -27,8 +26,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestInitialize] public void ParseNamespaceExample() { - string json = File.ReadAllText(TypedMapUnitTests.SchemaFile); - this.counterSchema = Namespace.Parse(json); + this.counterSchema = SchemaUtil.LoadFromHrSchema(TypedMapUnitTests.SchemaFile); this.resolver = new LayoutResolverNamespace(this.counterSchema); this.layout = this.resolver.Resolve(this.counterSchema.Schemas.Find(x => x.Name == "Movie").SchemaId); } @@ -372,6 +370,26 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit return Result.Success; } + 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().WriteFixed(ref row, ref udtScope, c, m.Domestic)); + Assert.IsTrue(udt.TryFind("worldwide", out c)); + ResultAssert.IsSuccess(c.TypeAs().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().ReadFixed(ref row, ref udtScope, c, out m.Domestic)); + Assert.IsTrue(udt.TryFind("worldwide", out c)); + ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref udtScope, c, out m.Worldwide)); + return m; + } + private void WriteMovie(ref RowBuffer row, ref RowCursor root, Movie value) { LayoutColumn c; @@ -541,26 +559,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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().WriteFixed(ref row, ref udtScope, c, m.Domestic)); - Assert.IsTrue(udt.TryFind("worldwide", out c)); - ResultAssert.IsSuccess(c.TypeAs().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().ReadFixed(ref row, ref udtScope, c, out m.Domestic)); - Assert.IsTrue(udt.TryFind("worldwide", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref udtScope, c, out m.Worldwide)); - return m; - } - private static class KeyValuePair { public static KeyValuePair Create(TKey key, TValue value) @@ -579,16 +577,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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); } @@ -674,16 +662,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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); } diff --git a/dotnet/src/HybridRow.Tests.Unit/TypedSetUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/TypedSetUnitTests.cs similarity index 98% rename from dotnet/src/HybridRow.Tests.Unit/TypedSetUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/TypedSetUnitTests.cs index 32c5209..d92ce90 100644 --- a/dotnet/src/HybridRow.Tests.Unit/TypedSetUnitTests.cs +++ b/src/Serialization/HybridRow.Tests.Unit/TypedSetUnitTests.cs @@ -7,7 +7,6 @@ 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; @@ -18,7 +17,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [DeploymentItem(TypedSetUnitTests.SchemaFile, "TestData")] public sealed class TypedSetUnitTests { - private const string SchemaFile = @"TestData\TodoSchema.json"; + private const string SchemaFile = @"TestData\TodoSchema.hrschema"; private const int InitialRowSize = 2 * 1024 * 1024; private Namespace counterSchema; @@ -28,8 +27,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit [TestInitialize] public void ParseNamespaceExample() { - string json = File.ReadAllText(TypedSetUnitTests.SchemaFile); - this.counterSchema = Namespace.Parse(json); + this.counterSchema = SchemaUtil.LoadFromHrSchema(TypedSetUnitTests.SchemaFile); this.resolver = new LayoutResolverNamespace(this.counterSchema); this.layout = this.resolver.Resolve(this.counterSchema.Schemas.Find(x => x.Name == "Todo").SchemaId); } @@ -365,10 +363,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit ResultAssert.IsSuccess(c.TypeArgs[0].Type.TypeAs().DeleteSparse(ref row, ref findScope)); } } - } - [TestMethod] [Owner("jthunter")] public void RowWriterTest() @@ -438,6 +434,26 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit return Result.Success; } + 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().WriteVariable(ref row, ref matchScope, c, m.Label)); + Assert.IsTrue(matchLayout.TryFind("count", out c)); + ResultAssert.IsSuccess(c.TypeAs().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().ReadVariable(ref row, ref matchScope, c, out m.Label)); + Assert.IsTrue(matchLayout.TryFind("count", out c)); + ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref matchScope, c, out m.Count)); + return m; + } + private void WriteTodo(ref RowBuffer row, ref RowCursor root, Todo value) { LayoutColumn c; @@ -704,26 +720,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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().WriteVariable(ref row, ref matchScope, c, m.Label)); - Assert.IsTrue(matchLayout.TryFind("count", out c)); - ResultAssert.IsSuccess(c.TypeAs().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().ReadVariable(ref row, ref matchScope, c, out m.Label)); - Assert.IsTrue(matchLayout.TryFind("count", out c)); - ResultAssert.IsSuccess(c.TypeAs().ReadFixed(ref row, ref matchScope, c, out m.Count)); - return m; - } - [SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1401", Justification = "Test types.")] private sealed class Todo { @@ -737,16 +733,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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); } @@ -847,16 +833,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit 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); } diff --git a/dotnet/src/HybridRow.Tests.Unit/UpdateOptionsUnitTests.cs b/src/Serialization/HybridRow.Tests.Unit/UpdateOptionsUnitTests.cs similarity index 100% rename from dotnet/src/HybridRow.Tests.Unit/UpdateOptionsUnitTests.cs rename to src/Serialization/HybridRow.Tests.Unit/UpdateOptionsUnitTests.cs diff --git a/dotnet/src/HybridRow/DefaultSpanResizer.cs b/src/Serialization/HybridRow/DefaultSpanResizer.cs similarity index 100% rename from dotnet/src/HybridRow/DefaultSpanResizer.cs rename to src/Serialization/HybridRow/DefaultSpanResizer.cs diff --git a/dotnet/src/HybridRow/Docs/Glossary.md b/src/Serialization/HybridRow/Docs/Glossary.md similarity index 100% rename from dotnet/src/HybridRow/Docs/Glossary.md rename to src/Serialization/HybridRow/Docs/Glossary.md diff --git a/dotnet/src/HybridRow/Docs/Grammar.md b/src/Serialization/HybridRow/Docs/Grammar.md similarity index 100% rename from dotnet/src/HybridRow/Docs/Grammar.md rename to src/Serialization/HybridRow/Docs/Grammar.md diff --git a/dotnet/src/HybridRow/Docs/RecordIO.md b/src/Serialization/HybridRow/Docs/RecordIO.md similarity index 100% rename from dotnet/src/HybridRow/Docs/RecordIO.md rename to src/Serialization/HybridRow/Docs/RecordIO.md diff --git a/dotnet/src/HybridRow/Docs/SchemaHash.md b/src/Serialization/HybridRow/Docs/SchemaHash.md similarity index 100% rename from dotnet/src/HybridRow/Docs/SchemaHash.md rename to src/Serialization/HybridRow/Docs/SchemaHash.md diff --git a/dotnet/src/HybridRow/Docs/SchemaId.md b/src/Serialization/HybridRow/Docs/SchemaId.md similarity index 100% rename from dotnet/src/HybridRow/Docs/SchemaId.md rename to src/Serialization/HybridRow/Docs/SchemaId.md diff --git a/src/Serialization/HybridRow/Docs/SystemSchema.md b/src/Serialization/HybridRow/Docs/SystemSchema.md new file mode 100644 index 0000000..133a461 --- /dev/null +++ b/src/Serialization/HybridRow/Docs/SystemSchema.md @@ -0,0 +1,49 @@ +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 +* $2147473651$ - Namespace Schema +* $2147473652$ - Schema Schema +* $2147473653$ - SchemaOptions Schema +* $2147473654$ - PartitionKey Schema +* $2147473655$ - PrimarySortKey Schema +* $2147473656$ - StaticKey Schema +* $2147473657$ - Property Schema +* $2147473658$ - PropertyType Schema +* $2147473659$ - PrimitivePropertyType Schema +* $2147473660$ - ScopePropertyType Schema +* $2147473661$ - ArrayPropertyType Schema +* $2147473662$ - ObjectPropertyType Schema +* $2147473663$ - UdtPropertyType Schema +* $2147473664$ - SetPropertyType Schema +* $2147473665$ - MapPropertyType Schema +* $2147473666$ - TuplePropertyType Schema +* $2147473667$ - TaggedPropertyType Schema +* $2147473668$ - EnumSchema Schema +* $2147473669$ - EnumValue 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$ - Legacy Patch Request (to be deprecated) +* $2145473651$ - Legacy Patch Operation (to be deprecated) +* $2145473652$ - Json Schema +* $2145473653$ - Partition Key Delete Request +* $2145473654$ - Patch operation +* $2145473655$ - Patch Request diff --git a/dotnet/src/HybridRow/Float128.cs b/src/Serialization/HybridRow/Float128.cs similarity index 59% rename from dotnet/src/HybridRow/Float128.cs rename to src/Serialization/HybridRow/Float128.cs index 30ae140..d4ac4d1 100644 --- a/dotnet/src/HybridRow/Float128.cs +++ b/src/Serialization/HybridRow/Float128.cs @@ -6,14 +6,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow { + using System; using System.Diagnostics; using System.Runtime.InteropServices; /// An IEEE 128-bit floating point value. /// /// 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. - /// + /// significand and an exponent range of -6143 to +6144. /// /// Source Link /// @@ -22,13 +22,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow /// /// The spec: https://ieeexplore.ieee.org/document/4610935 /// - /// Decimal Encodings: http://speleotrove.com/decimal/decbits.html + /// Decimal Encodings: + /// http://speleotrove.com/decimal/decbits.html /// /// /// [DebuggerDisplay("{" + nameof(Float128.Low) + "," + nameof(Float128.High) + "}")] [StructLayout(LayoutKind.Sequential, Pack = 1)] - public readonly struct Float128 + public readonly struct Float128 : IEquatable { /// The size (in bytes) of a . public const int Size = sizeof(long) + sizeof(long); @@ -53,5 +54,37 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow this.High = high; this.Low = low; } + + /// Operator == overload. + public static bool operator ==(Float128 left, Float128 right) + { + return left.Equals(right); + } + + /// Operator == overload. + public static bool operator !=(Float128 left, Float128 right) + { + return !left.Equals(right); + } + + /// Returns true if this is the same value as . + /// The value to compare against. + /// True if the two values are the same. + public bool Equals(Float128 other) + { + return this.Low == other.Low && this.High == other.High; + } + + /// overload. + public override bool Equals(object obj) + { + return obj is Float128 other && this.Equals(other); + } + + /// overload. + public override int GetHashCode() + { + return HashCode.Combine(this.Low, this.High); + } } } diff --git a/src/Serialization/HybridRow/Generated/SystemSchema.cs b/src/Serialization/HybridRow/Generated/SystemSchema.cs new file mode 100644 index 0000000..fab4f0a --- /dev/null +++ b/src/Serialization/HybridRow/Generated/SystemSchema.cs @@ -0,0 +1,5697 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// This file was generated by: +// Microsoft.Azure.Cosmos.Serialization.HybridRowCLI: 1.0.0.0 +// +// This file should not be modified directly. +// ------------------------------------------------------------ + +#pragma warning disable NamespaceMatchesFolderStructure // Namespace Declarations must match folder structure. +#pragma warning disable CA1707 // Identifiers should not contain underscores. +#pragma warning disable CA1034 // Do not nest types. +#pragma warning disable CA2104 // Do not declare readonly mutable reference types. +#pragma warning disable SA1129 // Do not use default value type constructor. +#pragma warning disable SA1309 // Field should not begin with an underscore. +#pragma warning disable SA1310 // Field names should not contain underscore. +#pragma warning disable SA1402 // File may only contain a single type. +#pragma warning disable SA1414 // Tuple types in signatures should have element names. +#pragma warning disable SA1514 // Element documentation header should be preceded by blank line. +#pragma warning disable SA1516 // Elements should be separated by blank line. +#pragma warning disable SA1649 // File name should match first type name. + +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantEmptySwitchSection +// ReSharper disable JoinDeclarationAndInitializer +// ReSharper disable TooWideLocalVariableScope +// ReSharper disable ArrangeStaticMemberQualifier +// ReSharper disable RedundantJumpStatement +// ReSharper disable RedundantUsingDirective +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Core.Utf8; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + 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; + + internal static class SchemasHrSchema + { + public static readonly Namespace Namespace = SchemasHrSchema.CreateSchema(); + public static readonly LayoutResolver LayoutResolver = SchemasHrSchema.LoadSchema(); + + private static Namespace CreateSchema() + { + return new Namespace + { + Name = "Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas", + Version = SchemaLanguageVersion.V2, + CppNamespace = "cdb_hr", + Enums = new List + { + ////////////////////////////////////////////////////////////////////////////// + new EnumSchema + { + Name = "SchemaLanguageVersion", + Comment = "Versions of the HybridRow Schema Description Language.", + Type = TypeKind.UInt8, + Values = new List + { + new EnumValue + { + Name = "V1", + Comment = "Initial version of the HybridRow Schema Description Language.", + Value = 0, + }, + new EnumValue + { + Name = "V2", + Comment = "Introduced Enums, Inheritance.", + Value = 2, + }, + new EnumValue + { + Name = "Unspecified", + Comment = "No version is specified.", + Value = 255, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new EnumSchema + { + Name = "TypeKind", + Comment = "Describes the logical type of a property.", + Type = TypeKind.UInt8, + }, + ////////////////////////////////////////////////////////////////////////////// + new EnumSchema + { + Name = "StorageKind", + Comment = "Describes the storage placement for primitive properties.", + Type = TypeKind.UInt8, + }, + ////////////////////////////////////////////////////////////////////////////// + new EnumSchema + { + Name = "SortDirection", + Comment = "Describes the sort order direction.", + Type = TypeKind.UInt8, + }, + ////////////////////////////////////////////////////////////////////////////// + new EnumSchema + { + Name = "AllowEmptyKind", + Comment = "Describes the empty canonicalization for properties.", + Type = TypeKind.UInt8, + }, + }, + Schemas = new List + { + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "EmptySchema", + SchemaId = new SchemaId(2147473650), + Options = new SchemaOptions + { + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Segment", + SchemaId = new SchemaId(2147473648), + Options = new SchemaOptions + { + }, + Properties = new List + { + new Property + { + Path = "length", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int32, + Storage = StorageKind.Fixed, + RowBufferSize = true, + }, + Comment = "(Required) length (in bytes) of this RecordIO segment header itself. Does NOT include the length of the records that follow.", + }, + new Property + { + Path = "comment", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "A comment describing the data in this RecordIO segment.", + }, + new Property + { + Path = "sdl", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "A HybridRow Schema in SDL (json-format).", + ApiName = "SDL", + }, + new Property + { + Path = "schema", + PropertyType = new UdtPropertyType + { + Name = "Namespace", + SchemaId = new SchemaId(2147473651), + }, + Comment = "A HybridRow Schema.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Record", + SchemaId = new SchemaId(2147473649), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "length", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int32, + Storage = StorageKind.Fixed, + Nullable = false, + }, + Comment = "(Required) length (in bytes) of the HybridRow value that follows this record header.", + }, + new Property + { + Path = "crc32", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.UInt32, + Storage = StorageKind.Fixed, + Nullable = false, + }, + Comment = "(Optional) CRC-32 as described in ISO 3309.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Namespace", + SchemaId = new SchemaId(2147473651), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "version", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Enum, + Storage = StorageKind.Fixed, + Enum = "SchemaLanguageVersion", + Nullable = false, + }, + Comment = "(Required) SDL language version.", + }, + new Property + { + Path = "name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + Comment = "(Optional) Name of the namespace.", + }, + new Property + { + Path = "comment", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "(Optional) Comment field describing the namespace.", + }, + new Property + { + Path = "schemas", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "Schema", + SchemaId = new SchemaId(2147473652), + Nullable = false, + }, + }, + Comment = "The set of schemas that make up the namespace.", + }, + new Property + { + Path = "enums", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "EnumSchema", + SchemaId = new SchemaId(2147473668), + Nullable = false, + }, + }, + Comment = "The set of enums defined in the namespace.", + }, + new Property + { + Path = "cppNamespace", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "An (optional) namespace to use when performing C++ codegen.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Schema", + SchemaId = new SchemaId(2147473652), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "version", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Enum, + Storage = StorageKind.Fixed, + Enum = "SchemaLanguageVersion", + Nullable = false, + }, + Comment = "(Optional) SDL language version.", + }, + new Property + { + Path = "type", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Enum, + Storage = StorageKind.Fixed, + Enum = "TypeKind", + Nullable = false, + }, + Comment = "(Required) Type of the schema element.", + }, + new Property + { + Path = "id", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int32, + Storage = StorageKind.Fixed, + Nullable = false, + ApiType = "SchemaId", + }, + Comment = "(Required) Globally unique id of the schema.", + ApiName = "SchemaId", + }, + new Property + { + Path = "name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + Comment = "(Optional) Name of the schema.", + }, + new Property + { + Path = "comment", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "(Optional) Comment field describing the schema.", + }, + new Property + { + Path = "options", + PropertyType = new UdtPropertyType + { + Name = "SchemaOptions", + SchemaId = new SchemaId(2147473653), + }, + Comment = "(Optional) Schema options.", + }, + new Property + { + Path = "partitionKeys", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "PartitionKey", + SchemaId = new SchemaId(2147473654), + Nullable = false, + }, + }, + Comment = "(Optional) List of zero or more logical paths that form the partition key.", + }, + new Property + { + Path = "primaryKeys", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "PrimarySortKey", + SchemaId = new SchemaId(2147473655), + Nullable = false, + }, + }, + Comment = "(Optional) List of zero or more logical paths that form the primary sort key.", + }, + new Property + { + Path = "staticKeys", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "StaticKey", + SchemaId = new SchemaId(2147473656), + Nullable = false, + }, + }, + Comment = "(Optional) List of zero or more logical paths that hold data shared by all documents that have the same partition key.", + }, + new Property + { + Path = "properties", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "Property", + SchemaId = new SchemaId(2147473657), + Nullable = false, + }, + }, + Comment = "(Optional) List of zero or more property definitions that define the columns within the schema.", + }, + new Property + { + Path = "baseName", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "The name of the schema this schema derives from.", + }, + new Property + { + Path = "baseId", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int32, + ApiType = "SchemaId", + }, + Comment = "The unique identifier of the schema this schema derives from.", + ApiName = "BaseSchemaId", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "SchemaOptions", + SchemaId = new SchemaId(2147473653), + Comment = "Describes the set of options that apply to the entire schema and the way it is validated.", + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "disallowUnschematized", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Boolean, + }, + }, + new Property + { + Path = "enablePropertyLevelTimestamp", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Boolean, + }, + }, + new Property + { + Path = "disableSystemPrefix", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Boolean, + }, + }, + new Property + { + Path = "abstract", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Boolean, + }, + Comment = "If true then instances of this schema cannot be created directly, only through subtypes.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "PartitionKey", + SchemaId = new SchemaId(2147473654), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "path", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "PrimarySortKey", + SchemaId = new SchemaId(2147473655), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "path", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "direction", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Enum, + Storage = StorageKind.Fixed, + Enum = "SortDirection", + Nullable = false, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "StaticKey", + SchemaId = new SchemaId(2147473656), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "path", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "Property", + SchemaId = new SchemaId(2147473657), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "path", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "comment", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + }, + new Property + { + Path = "type", + PropertyType = new UdtPropertyType + { + Name = "PropertyType", + SchemaId = new SchemaId(2147473658), + }, + Comment = "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType.", + ApiName = "PropertyType", + }, + new Property + { + Path = "apiname", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + ApiName = "ApiName", + }, + new Property + { + Path = "allowEmpty", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Enum, + Enum = "AllowEmptyKind", + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "PropertyType", + SchemaId = new SchemaId(2147473658), + Options = new SchemaOptions + { + DisallowUnschematized = true, + Abstract = true, + }, + Properties = new List + { + new Property + { + Path = "apitype", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + ApiName = "ApiType", + }, + new Property + { + Path = "type", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Enum, + Storage = StorageKind.Fixed, + Enum = "TypeKind", + Nullable = false, + }, + }, + new Property + { + Path = "nullable", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Boolean, + Storage = StorageKind.Fixed, + Nullable = false, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "PrimitivePropertyType", + SchemaId = new SchemaId(2147473659), + BaseName = "PropertyType", + BaseSchemaId = new SchemaId(2147473658), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "length", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int32, + Storage = StorageKind.Fixed, + Nullable = false, + }, + }, + new Property + { + Path = "storage", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Enum, + Storage = StorageKind.Fixed, + Enum = "StorageKind", + Nullable = false, + }, + }, + new Property + { + Path = "enum", + AllowEmpty = AllowEmptyKind.EmptyAsNull, + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + }, + new Property + { + Path = "rowBufferSize", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Boolean, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "ScopePropertyType", + SchemaId = new SchemaId(2147473660), + BaseName = "PropertyType", + BaseSchemaId = new SchemaId(2147473658), + Options = new SchemaOptions + { + DisallowUnschematized = true, + Abstract = true, + }, + Properties = new List + { + new Property + { + Path = "immutable", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Boolean, + Storage = StorageKind.Fixed, + Nullable = false, + }, + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "ArrayPropertyType", + SchemaId = new SchemaId(2147473661), + BaseName = "ScopePropertyType", + BaseSchemaId = new SchemaId(2147473660), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "items", + PropertyType = new UdtPropertyType + { + Name = "PropertyType", + SchemaId = new SchemaId(2147473658), + }, + Comment = "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "ObjectPropertyType", + SchemaId = new SchemaId(2147473662), + BaseName = "ScopePropertyType", + BaseSchemaId = new SchemaId(2147473660), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "properties", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "Property", + SchemaId = new SchemaId(2147473657), + Nullable = false, + }, + }, + Comment = "(Optional) List of zero or more property definitions that define the columns within the schema.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "UdtPropertyType", + SchemaId = new SchemaId(2147473663), + BaseName = "ScopePropertyType", + BaseSchemaId = new SchemaId(2147473660), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + }, + new Property + { + Path = "id", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int32, + Storage = StorageKind.Fixed, + Nullable = false, + ApiType = "SchemaId", + }, + ApiName = "SchemaId", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "SetPropertyType", + SchemaId = new SchemaId(2147473664), + BaseName = "ScopePropertyType", + BaseSchemaId = new SchemaId(2147473660), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "items", + PropertyType = new UdtPropertyType + { + Name = "PropertyType", + SchemaId = new SchemaId(2147473658), + }, + Comment = "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "MapPropertyType", + SchemaId = new SchemaId(2147473665), + BaseName = "ScopePropertyType", + BaseSchemaId = new SchemaId(2147473660), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "keys", + PropertyType = new UdtPropertyType + { + Name = "PropertyType", + SchemaId = new SchemaId(2147473658), + }, + Comment = "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType.", + }, + new Property + { + Path = "values", + PropertyType = new UdtPropertyType + { + Name = "PropertyType", + SchemaId = new SchemaId(2147473658), + }, + Comment = "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "TuplePropertyType", + SchemaId = new SchemaId(2147473666), + BaseName = "ScopePropertyType", + BaseSchemaId = new SchemaId(2147473660), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "items", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "PropertyType", + SchemaId = new SchemaId(2147473658), + Nullable = false, + }, + }, + Comment = "The type of the properties. This field is polymorphic and may contain any defined subtype of PropertyType.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "TaggedPropertyType", + SchemaId = new SchemaId(2147473667), + BaseName = "ScopePropertyType", + BaseSchemaId = new SchemaId(2147473660), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "items", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "PropertyType", + SchemaId = new SchemaId(2147473658), + Nullable = false, + }, + }, + Comment = "The type of the properties. This field is polymorphic and may contain any defined subtype of PropertyType.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "EnumSchema", + SchemaId = new SchemaId(2147473668), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "type", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Enum, + Storage = StorageKind.Fixed, + Enum = "TypeKind", + Nullable = false, + }, + Comment = "(Required) Type of the schema element.", + }, + new Property + { + Path = "name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + Comment = "(Optional) Name of the schema.", + }, + new Property + { + Path = "comment", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "(Optional) Comment field describing the schema.", + }, + new Property + { + Path = "apitype", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "Api-specific type annotations for the property.", + ApiName = "ApiType", + }, + new Property + { + Path = "values", + AllowEmpty = AllowEmptyKind.Both, + PropertyType = new ArrayPropertyType + { + Items = new UdtPropertyType + { + Name = "EnumValue", + SchemaId = new SchemaId(2147473669), + Nullable = false, + }, + }, + Comment = "(Optional) List of zero or more values.", + }, + }, + }, + ////////////////////////////////////////////////////////////////////////////// + new Schema + { + Name = "EnumValue", + SchemaId = new SchemaId(2147473669), + Options = new SchemaOptions + { + DisallowUnschematized = true, + }, + Properties = new List + { + new Property + { + Path = "name", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + Storage = StorageKind.Variable, + }, + Comment = "(Optional) Name of the schema.", + }, + new Property + { + Path = "value", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Int64, + Storage = StorageKind.Fixed, + }, + Comment = "The numerical value of the enum value.", + }, + new Property + { + Path = "comment", + PropertyType = new PrimitivePropertyType + { + Type = TypeKind.Utf8, + }, + Comment = "(Optional) Comment field describing the schema.", + }, + }, + }, + }, + }; + } + + private static LayoutResolver LoadSchema() + { + return new LayoutResolverNamespace(SchemasHrSchema.Namespace); + } + } + + public readonly struct SegmentHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473648; + public const int Size = 5; + public IEqualityComparer Comparer => SegmentComparer.Default; + private static readonly Utf8String LengthName = Utf8String.TranscodeUtf16("length"); + private static readonly Utf8String CommentName = Utf8String.TranscodeUtf16("comment"); + private static readonly Utf8String SDLName = Utf8String.TranscodeUtf16("sdl"); + private static readonly Utf8String SchemaName = Utf8String.TranscodeUtf16("schema"); + + private static readonly LayoutColumn LengthColumn; + private static readonly LayoutColumn CommentColumn; + private static readonly LayoutColumn SDLColumn; + private static readonly LayoutColumn SchemaColumn; + + private static readonly StringToken CommentToken; + private static readonly StringToken SDLToken; + private static readonly StringToken SchemaToken; + + static SegmentHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(LengthName, out LengthColumn); + Contract.Invariant(found); + found = layout.TryFind(CommentName, out CommentColumn); + Contract.Invariant(found); + found = layout.TryFind(SDLName, out SDLColumn); + Contract.Invariant(found); + found = layout.TryFind(SchemaName, out SchemaColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(CommentColumn.Path, out CommentToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(SDLColumn.Path, out SDLToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(SchemaColumn.Path, out SchemaToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Segment value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Segment value) + { + Result r; + if (value.Comment != default) + { + scope.Find(ref row, CommentColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.Comment); + if (r != Result.Success) + { + return r; + } + } + + if (value.SDL != default) + { + scope.Find(ref row, SDLColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.SDL); + if (r != Result.Success) + { + return r; + } + } + + if (value.Schema != default) + { + scope.Find(ref row, SchemaColumn.Path); + r = default(NamespaceHybridRowSerializer) + .Write(ref row, ref scope, false, SchemaColumn.TypeArgs, value.Schema); + if (r != Result.Success) + { + return r; + } + } + + // Emit RowBufferSize field with actual size of RowBuffer. + r = LayoutType.Int32.WriteFixed(ref row, ref scope, LengthColumn, row.Length); + if (r != Result.Success) + { + return r; + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Segment value) + { + if (isRoot) + { + value = new Segment(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Segment(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Segment value) + { + Result r; + { + r = LayoutType.Int32.ReadFixed(ref row, ref scope, LengthColumn, out int fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Length = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == CommentToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Comment = fieldValue; + continue; + } + + if (scope.Token == SDLToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.SDL = fieldValue; + continue; + } + + if (scope.Token == SchemaToken.Id) + { + r = default(NamespaceHybridRowSerializer) + .Read(ref row, ref scope, false, out Namespace fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Schema = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class SegmentComparer : EqualityComparer + { + public static new readonly SegmentComparer Default = new SegmentComparer(); + + public override bool Equals(Segment x, Segment y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Int32HybridRowSerializer).Comparer.Equals(x.Length, y.Length) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Comment, y.Comment) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.SDL, y.SDL) && + default(NamespaceHybridRowSerializer).Comparer.Equals(x.Schema, y.Schema); + } + + public override int GetHashCode(Segment obj) + { + return HashCode.Combine( + default(Int32HybridRowSerializer).Comparer.GetHashCode(obj.Length), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Comment), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.SDL), + default(NamespaceHybridRowSerializer).Comparer.GetHashCode(obj.Schema)); + } + } + } + + public readonly struct RecordHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473649; + public const int Size = 8; + public IEqualityComparer Comparer => RecordComparer.Default; + private static readonly Utf8String LengthName = Utf8String.TranscodeUtf16("length"); + private static readonly Utf8String Crc32Name = Utf8String.TranscodeUtf16("crc32"); + + private static readonly LayoutColumn LengthColumn; + private static readonly LayoutColumn Crc32Column; + + static RecordHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(LengthName, out LengthColumn); + Contract.Invariant(found); + found = layout.TryFind(Crc32Name, out Crc32Column); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Record value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Record value) + { + Result r; + if (value.Length != default) + { + r = LayoutType.Int32.WriteFixed(ref row, ref scope, LengthColumn, value.Length); + if (r != Result.Success) + { + return r; + } + } + + if (value.Crc32 != default) + { + r = LayoutType.UInt32.WriteFixed(ref row, ref scope, Crc32Column, value.Crc32); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Record value) + { + if (isRoot) + { + value = new Record(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Record(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Record value) + { + Result r; + { + r = LayoutType.Int32.ReadFixed(ref row, ref scope, LengthColumn, out int fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Length = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.UInt32.ReadFixed(ref row, ref scope, Crc32Column, out uint fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Crc32 = fieldValue; + break; + default: + return r; + } + } + + return Result.Success; + } + + public sealed class RecordComparer : EqualityComparer + { + public static new readonly RecordComparer Default = new RecordComparer(); + + public override bool Equals(Record x, Record y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Int32HybridRowSerializer).Comparer.Equals(x.Length, y.Length) && + default(UInt32HybridRowSerializer).Comparer.Equals(x.Crc32, y.Crc32); + } + + public override int GetHashCode(Record obj) + { + return HashCode.Combine( + default(Int32HybridRowSerializer).Comparer.GetHashCode(obj.Length), + default(UInt32HybridRowSerializer).Comparer.GetHashCode(obj.Crc32)); + } + } + } + + public readonly struct NamespaceHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473651; + public const int Size = 2; + public IEqualityComparer Comparer => NamespaceComparer.Default; + private static readonly Utf8String VersionName = Utf8String.TranscodeUtf16("version"); + private static readonly Utf8String NameName = Utf8String.TranscodeUtf16("name"); + private static readonly Utf8String CommentName = Utf8String.TranscodeUtf16("comment"); + private static readonly Utf8String SchemasName = Utf8String.TranscodeUtf16("schemas"); + private static readonly Utf8String EnumsName = Utf8String.TranscodeUtf16("enums"); + private static readonly Utf8String CppNamespaceName = Utf8String.TranscodeUtf16("cppNamespace"); + + private static readonly LayoutColumn VersionColumn; + private static readonly LayoutColumn NameColumn; + private static readonly LayoutColumn CommentColumn; + private static readonly LayoutColumn SchemasColumn; + private static readonly LayoutColumn EnumsColumn; + private static readonly LayoutColumn CppNamespaceColumn; + + private static readonly StringToken CommentToken; + private static readonly StringToken SchemasToken; + private static readonly StringToken EnumsToken; + private static readonly StringToken CppNamespaceToken; + + static NamespaceHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(VersionName, out VersionColumn); + Contract.Invariant(found); + found = layout.TryFind(NameName, out NameColumn); + Contract.Invariant(found); + found = layout.TryFind(CommentName, out CommentColumn); + Contract.Invariant(found); + found = layout.TryFind(SchemasName, out SchemasColumn); + Contract.Invariant(found); + found = layout.TryFind(EnumsName, out EnumsColumn); + Contract.Invariant(found); + found = layout.TryFind(CppNamespaceName, out CppNamespaceColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(CommentColumn.Path, out CommentToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(SchemasColumn.Path, out SchemasToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(EnumsColumn.Path, out EnumsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(CppNamespaceColumn.Path, out CppNamespaceToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Namespace value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Namespace value) + { + Result r; + if (value.Version != default) + { + r = LayoutType.UInt8.WriteFixed(ref row, ref scope, VersionColumn, (byte)value.Version); + if (r != Result.Success) + { + return r; + } + } + + if (value.Name != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, NameColumn, value.Name); + if (r != Result.Success) + { + return r; + } + } + + if (value.Comment != default) + { + scope.Find(ref row, CommentColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.Comment); + if (r != Result.Success) + { + return r; + } + } + + if ((value.Schemas != null) && (value.Schemas.Count > 0)) + { + scope.Find(ref row, SchemasColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + SchemasColumn.TypeArgs, + value.Schemas); + if (r != Result.Success) + { + return r; + } + } + + if ((value.Enums != null) && (value.Enums.Count > 0)) + { + scope.Find(ref row, EnumsColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + EnumsColumn.TypeArgs, + value.Enums); + if (r != Result.Success) + { + return r; + } + } + + if (value.CppNamespace != default) + { + scope.Find(ref row, CppNamespaceColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.CppNamespace); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Namespace value) + { + if (isRoot) + { + value = new Namespace(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Namespace(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Namespace value) + { + Result r; + { + r = LayoutType.UInt8.ReadFixed(ref row, ref scope, VersionColumn, out byte fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Version = (SchemaLanguageVersion)fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, NameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Name = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == CommentToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Comment = fieldValue; + continue; + } + + if (scope.Token == SchemasToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Schemas = fieldValue; + continue; + } + + if (scope.Token == EnumsToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Enums = fieldValue; + continue; + } + + if (scope.Token == CppNamespaceToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.CppNamespace = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class NamespaceComparer : EqualityComparer + { + public static new readonly NamespaceComparer Default = new NamespaceComparer(); + + public override bool Equals(Namespace x, Namespace y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(UInt8HybridRowSerializer).Comparer.Equals((byte)x.Version, (byte)y.Version) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Name, y.Name) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Comment, y.Comment) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Schemas, y.Schemas) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Enums, y.Enums) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.CppNamespace, y.CppNamespace); + } + + public override int GetHashCode(Namespace obj) + { + return HashCode.Combine( + default(UInt8HybridRowSerializer).Comparer.GetHashCode((byte)obj.Version), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Name), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Comment), + default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.Schemas), + default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.Enums), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.CppNamespace)); + } + } + } + + public readonly struct SchemaHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473652; + public const int Size = 7; + public IEqualityComparer Comparer => SchemaComparer.Default; + private static readonly Utf8String VersionName = Utf8String.TranscodeUtf16("version"); + private static readonly Utf8String TypeName = Utf8String.TranscodeUtf16("type"); + private static readonly Utf8String SchemaIdName = Utf8String.TranscodeUtf16("id"); + private static readonly Utf8String NameName = Utf8String.TranscodeUtf16("name"); + private static readonly Utf8String CommentName = Utf8String.TranscodeUtf16("comment"); + private static readonly Utf8String OptionsName = Utf8String.TranscodeUtf16("options"); + private static readonly Utf8String PartitionKeysName = Utf8String.TranscodeUtf16("partitionKeys"); + private static readonly Utf8String PrimaryKeysName = Utf8String.TranscodeUtf16("primaryKeys"); + private static readonly Utf8String StaticKeysName = Utf8String.TranscodeUtf16("staticKeys"); + private static readonly Utf8String PropertiesName = Utf8String.TranscodeUtf16("properties"); + private static readonly Utf8String BaseNameName = Utf8String.TranscodeUtf16("baseName"); + private static readonly Utf8String BaseSchemaIdName = Utf8String.TranscodeUtf16("baseId"); + + private static readonly LayoutColumn VersionColumn; + private static readonly LayoutColumn TypeColumn; + private static readonly LayoutColumn SchemaIdColumn; + private static readonly LayoutColumn NameColumn; + private static readonly LayoutColumn CommentColumn; + private static readonly LayoutColumn OptionsColumn; + private static readonly LayoutColumn PartitionKeysColumn; + private static readonly LayoutColumn PrimaryKeysColumn; + private static readonly LayoutColumn StaticKeysColumn; + private static readonly LayoutColumn PropertiesColumn; + private static readonly LayoutColumn BaseNameColumn; + private static readonly LayoutColumn BaseSchemaIdColumn; + + private static readonly StringToken CommentToken; + private static readonly StringToken OptionsToken; + private static readonly StringToken PartitionKeysToken; + private static readonly StringToken PrimaryKeysToken; + private static readonly StringToken StaticKeysToken; + private static readonly StringToken PropertiesToken; + private static readonly StringToken BaseNameToken; + private static readonly StringToken BaseSchemaIdToken; + + static SchemaHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(VersionName, out VersionColumn); + Contract.Invariant(found); + found = layout.TryFind(TypeName, out TypeColumn); + Contract.Invariant(found); + found = layout.TryFind(SchemaIdName, out SchemaIdColumn); + Contract.Invariant(found); + found = layout.TryFind(NameName, out NameColumn); + Contract.Invariant(found); + found = layout.TryFind(CommentName, out CommentColumn); + Contract.Invariant(found); + found = layout.TryFind(OptionsName, out OptionsColumn); + Contract.Invariant(found); + found = layout.TryFind(PartitionKeysName, out PartitionKeysColumn); + Contract.Invariant(found); + found = layout.TryFind(PrimaryKeysName, out PrimaryKeysColumn); + Contract.Invariant(found); + found = layout.TryFind(StaticKeysName, out StaticKeysColumn); + Contract.Invariant(found); + found = layout.TryFind(PropertiesName, out PropertiesColumn); + Contract.Invariant(found); + found = layout.TryFind(BaseNameName, out BaseNameColumn); + Contract.Invariant(found); + found = layout.TryFind(BaseSchemaIdName, out BaseSchemaIdColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(CommentColumn.Path, out CommentToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(OptionsColumn.Path, out OptionsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(PartitionKeysColumn.Path, out PartitionKeysToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(PrimaryKeysColumn.Path, out PrimaryKeysToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(StaticKeysColumn.Path, out StaticKeysToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(PropertiesColumn.Path, out PropertiesToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(BaseNameColumn.Path, out BaseNameToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(BaseSchemaIdColumn.Path, out BaseSchemaIdToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Schema value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Schema value) + { + Result r; + if (value.Version != default) + { + r = LayoutType.UInt8.WriteFixed(ref row, ref scope, VersionColumn, (byte)value.Version); + if (r != Result.Success) + { + return r; + } + } + + if (value.Type != default) + { + r = LayoutType.UInt8.WriteFixed(ref row, ref scope, TypeColumn, (byte)value.Type); + if (r != Result.Success) + { + return r; + } + } + + if (value.SchemaId != default) + { + r = LayoutType.Int32.WriteFixed(ref row, ref scope, SchemaIdColumn, (int)value.SchemaId); + if (r != Result.Success) + { + return r; + } + } + + if (value.Name != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, NameColumn, value.Name); + if (r != Result.Success) + { + return r; + } + } + + if (value.Comment != default) + { + scope.Find(ref row, CommentColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.Comment); + if (r != Result.Success) + { + return r; + } + } + + if (value.Options != default) + { + scope.Find(ref row, OptionsColumn.Path); + r = default(SchemaOptionsHybridRowSerializer) + .Write(ref row, ref scope, false, OptionsColumn.TypeArgs, value.Options); + if (r != Result.Success) + { + return r; + } + } + + if ((value.PartitionKeys != null) && (value.PartitionKeys.Count > 0)) + { + scope.Find(ref row, PartitionKeysColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + PartitionKeysColumn.TypeArgs, + value.PartitionKeys); + if (r != Result.Success) + { + return r; + } + } + + if ((value.PrimaryKeys != null) && (value.PrimaryKeys.Count > 0)) + { + scope.Find(ref row, PrimaryKeysColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + PrimaryKeysColumn.TypeArgs, + value.PrimaryKeys); + if (r != Result.Success) + { + return r; + } + } + + if ((value.StaticKeys != null) && (value.StaticKeys.Count > 0)) + { + scope.Find(ref row, StaticKeysColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + StaticKeysColumn.TypeArgs, + value.StaticKeys); + if (r != Result.Success) + { + return r; + } + } + + if ((value.Properties != null) && (value.Properties.Count > 0)) + { + scope.Find(ref row, PropertiesColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + PropertiesColumn.TypeArgs, + value.Properties); + if (r != Result.Success) + { + return r; + } + } + + if (value.BaseName != default) + { + scope.Find(ref row, BaseNameColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.BaseName); + if (r != Result.Success) + { + return r; + } + } + + if (value.BaseSchemaId != default) + { + scope.Find(ref row, BaseSchemaIdColumn.Path); + r = LayoutType.Int32.WriteSparse(ref row, ref scope, (int)value.BaseSchemaId); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Schema value) + { + if (isRoot) + { + value = new Schema(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Schema(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Schema value) + { + Result r; + { + r = LayoutType.UInt8.ReadFixed(ref row, ref scope, VersionColumn, out byte fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Version = (SchemaLanguageVersion)fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.UInt8.ReadFixed(ref row, ref scope, TypeColumn, out byte fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Type = (TypeKind)fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Int32.ReadFixed(ref row, ref scope, SchemaIdColumn, out int fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.SchemaId = (SchemaId)fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, NameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Name = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == CommentToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Comment = fieldValue; + continue; + } + + if (scope.Token == OptionsToken.Id) + { + r = default(SchemaOptionsHybridRowSerializer) + .Read(ref row, ref scope, false, out SchemaOptions fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Options = fieldValue; + continue; + } + + if (scope.Token == PartitionKeysToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.PartitionKeys = fieldValue; + continue; + } + + if (scope.Token == PrimaryKeysToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.PrimaryKeys = fieldValue; + continue; + } + + if (scope.Token == StaticKeysToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.StaticKeys = fieldValue; + continue; + } + + if (scope.Token == PropertiesToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Properties = fieldValue; + continue; + } + + if (scope.Token == BaseNameToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.BaseName = fieldValue; + continue; + } + + if (scope.Token == BaseSchemaIdToken.Id) + { + r = LayoutType.Int32.ReadSparse(ref row, ref scope, out int fieldValue); + if (r != Result.Success) + { + return r; + } + + value.BaseSchemaId = (SchemaId)fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class SchemaComparer : EqualityComparer + { + public static new readonly SchemaComparer Default = new SchemaComparer(); + + public override bool Equals(Schema x, Schema y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(UInt8HybridRowSerializer).Comparer.Equals((byte)x.Version, (byte)y.Version) && + default(UInt8HybridRowSerializer).Comparer.Equals((byte)x.Type, (byte)y.Type) && + default(Int32HybridRowSerializer).Comparer.Equals((int)x.SchemaId, (int)y.SchemaId) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Name, y.Name) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Comment, y.Comment) && + default(SchemaOptionsHybridRowSerializer).Comparer.Equals(x.Options, y.Options) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.PartitionKeys, y.PartitionKeys) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.PrimaryKeys, y.PrimaryKeys) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.StaticKeys, y.StaticKeys) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Properties, y.Properties) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.BaseName, y.BaseName) && + default(Int32HybridRowSerializer).Comparer.Equals((int)x.BaseSchemaId, (int)y.BaseSchemaId); + } + + public override int GetHashCode(Schema obj) + { + HashCode hash = default; + hash.Add((byte)obj.Version, default(UInt8HybridRowSerializer).Comparer); + hash.Add((byte)obj.Type, default(UInt8HybridRowSerializer).Comparer); + hash.Add((int)obj.SchemaId, default(Int32HybridRowSerializer).Comparer); + hash.Add(obj.Name, default(Utf8HybridRowSerializer).Comparer); + hash.Add(obj.Comment, default(Utf8HybridRowSerializer).Comparer); + hash.Add(obj.Options, default(SchemaOptionsHybridRowSerializer).Comparer); + hash.Add(obj.PartitionKeys, default(TypedArrayHybridRowSerializer).Comparer); + hash.Add(obj.PrimaryKeys, default(TypedArrayHybridRowSerializer).Comparer); + hash.Add(obj.StaticKeys, default(TypedArrayHybridRowSerializer).Comparer); + hash.Add(obj.Properties, default(TypedArrayHybridRowSerializer).Comparer); + hash.Add(obj.BaseName, default(Utf8HybridRowSerializer).Comparer); + hash.Add((int)obj.BaseSchemaId, default(Int32HybridRowSerializer).Comparer); + return hash.ToHashCode(); + } + } + } + + /// + /// Describes the set of options that apply to the entire schema and the way it is validated. + /// + public readonly struct SchemaOptionsHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473653; + public const int Size = 0; + public IEqualityComparer Comparer => SchemaOptionsComparer.Default; + private static readonly Utf8String DisallowUnschematizedName = Utf8String.TranscodeUtf16("disallowUnschematized"); + private static readonly Utf8String EnablePropertyLevelTimestampName = Utf8String.TranscodeUtf16("enablePropertyLevelTimestamp"); + private static readonly Utf8String DisableSystemPrefixName = Utf8String.TranscodeUtf16("disableSystemPrefix"); + private static readonly Utf8String AbstractName = Utf8String.TranscodeUtf16("abstract"); + + private static readonly LayoutColumn DisallowUnschematizedColumn; + private static readonly LayoutColumn EnablePropertyLevelTimestampColumn; + private static readonly LayoutColumn DisableSystemPrefixColumn; + private static readonly LayoutColumn AbstractColumn; + + private static readonly StringToken DisallowUnschematizedToken; + private static readonly StringToken EnablePropertyLevelTimestampToken; + private static readonly StringToken DisableSystemPrefixToken; + private static readonly StringToken AbstractToken; + + static SchemaOptionsHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(DisallowUnschematizedName, out DisallowUnschematizedColumn); + Contract.Invariant(found); + found = layout.TryFind(EnablePropertyLevelTimestampName, out EnablePropertyLevelTimestampColumn); + Contract.Invariant(found); + found = layout.TryFind(DisableSystemPrefixName, out DisableSystemPrefixColumn); + Contract.Invariant(found); + found = layout.TryFind(AbstractName, out AbstractColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(DisallowUnschematizedColumn.Path, out DisallowUnschematizedToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(EnablePropertyLevelTimestampColumn.Path, out EnablePropertyLevelTimestampToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(DisableSystemPrefixColumn.Path, out DisableSystemPrefixToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(AbstractColumn.Path, out AbstractToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, SchemaOptions value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, SchemaOptions value) + { + Result r; + if (value.DisallowUnschematized != default) + { + scope.Find(ref row, DisallowUnschematizedColumn.Path); + r = LayoutType.Boolean.WriteSparse(ref row, ref scope, value.DisallowUnschematized); + if (r != Result.Success) + { + return r; + } + } + + if (value.EnablePropertyLevelTimestamp != default) + { + scope.Find(ref row, EnablePropertyLevelTimestampColumn.Path); + r = LayoutType.Boolean.WriteSparse(ref row, ref scope, value.EnablePropertyLevelTimestamp); + if (r != Result.Success) + { + return r; + } + } + + if (value.DisableSystemPrefix != default) + { + scope.Find(ref row, DisableSystemPrefixColumn.Path); + r = LayoutType.Boolean.WriteSparse(ref row, ref scope, value.DisableSystemPrefix); + if (r != Result.Success) + { + return r; + } + } + + if (value.Abstract != default) + { + scope.Find(ref row, AbstractColumn.Path); + r = LayoutType.Boolean.WriteSparse(ref row, ref scope, value.Abstract); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out SchemaOptions value) + { + if (isRoot) + { + value = new SchemaOptions(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new SchemaOptions(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref SchemaOptions value) + { + Result r; + while (scope.MoveNext(ref row)) + { + if (scope.Token == DisallowUnschematizedToken.Id) + { + r = LayoutType.Boolean.ReadSparse(ref row, ref scope, out bool fieldValue); + if (r != Result.Success) + { + return r; + } + + value.DisallowUnschematized = fieldValue; + continue; + } + + if (scope.Token == EnablePropertyLevelTimestampToken.Id) + { + r = LayoutType.Boolean.ReadSparse(ref row, ref scope, out bool fieldValue); + if (r != Result.Success) + { + return r; + } + + value.EnablePropertyLevelTimestamp = fieldValue; + continue; + } + + if (scope.Token == DisableSystemPrefixToken.Id) + { + r = LayoutType.Boolean.ReadSparse(ref row, ref scope, out bool fieldValue); + if (r != Result.Success) + { + return r; + } + + value.DisableSystemPrefix = fieldValue; + continue; + } + + if (scope.Token == AbstractToken.Id) + { + r = LayoutType.Boolean.ReadSparse(ref row, ref scope, out bool fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Abstract = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class SchemaOptionsComparer : EqualityComparer + { + public static new readonly SchemaOptionsComparer Default = new SchemaOptionsComparer(); + + public override bool Equals(SchemaOptions x, SchemaOptions y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(BooleanHybridRowSerializer).Comparer.Equals(x.DisallowUnschematized, y.DisallowUnschematized) && + default(BooleanHybridRowSerializer).Comparer.Equals(x.EnablePropertyLevelTimestamp, y.EnablePropertyLevelTimestamp) && + default(BooleanHybridRowSerializer).Comparer.Equals(x.DisableSystemPrefix, y.DisableSystemPrefix) && + default(BooleanHybridRowSerializer).Comparer.Equals(x.Abstract, y.Abstract); + } + + public override int GetHashCode(SchemaOptions obj) + { + return HashCode.Combine( + default(BooleanHybridRowSerializer).Comparer.GetHashCode(obj.DisallowUnschematized), + default(BooleanHybridRowSerializer).Comparer.GetHashCode(obj.EnablePropertyLevelTimestamp), + default(BooleanHybridRowSerializer).Comparer.GetHashCode(obj.DisableSystemPrefix), + default(BooleanHybridRowSerializer).Comparer.GetHashCode(obj.Abstract)); + } + } + } + + public readonly struct PartitionKeyHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473654; + public const int Size = 1; + public IEqualityComparer Comparer => PartitionKeyComparer.Default; + private static readonly Utf8String PathName = Utf8String.TranscodeUtf16("path"); + + private static readonly LayoutColumn PathColumn; + + static PartitionKeyHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(PathName, out PathColumn); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, PartitionKey value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, PartitionKey value) + { + Result r; + if (value.Path != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, PathColumn, value.Path); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out PartitionKey value) + { + if (isRoot) + { + value = new PartitionKey(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new PartitionKey(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref PartitionKey value) + { + Result r; + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, PathColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Path = fieldValue; + break; + default: + return r; + } + } + + return Result.Success; + } + + public sealed class PartitionKeyComparer : EqualityComparer + { + public static new readonly PartitionKeyComparer Default = new PartitionKeyComparer(); + + public override bool Equals(PartitionKey x, PartitionKey y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return default(Utf8HybridRowSerializer).Comparer.Equals(x.Path, y.Path); + } + + public override int GetHashCode(PartitionKey obj) + { + return default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Path); + } + } + } + + public readonly struct PrimarySortKeyHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473655; + public const int Size = 2; + public IEqualityComparer Comparer => PrimarySortKeyComparer.Default; + private static readonly Utf8String PathName = Utf8String.TranscodeUtf16("path"); + private static readonly Utf8String DirectionName = Utf8String.TranscodeUtf16("direction"); + + private static readonly LayoutColumn PathColumn; + private static readonly LayoutColumn DirectionColumn; + + static PrimarySortKeyHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(PathName, out PathColumn); + Contract.Invariant(found); + found = layout.TryFind(DirectionName, out DirectionColumn); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, PrimarySortKey value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, PrimarySortKey value) + { + Result r; + if (value.Direction != default) + { + r = LayoutType.UInt8.WriteFixed(ref row, ref scope, DirectionColumn, (byte)value.Direction); + if (r != Result.Success) + { + return r; + } + } + + if (value.Path != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, PathColumn, value.Path); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out PrimarySortKey value) + { + if (isRoot) + { + value = new PrimarySortKey(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new PrimarySortKey(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref PrimarySortKey value) + { + Result r; + { + r = LayoutType.UInt8.ReadFixed(ref row, ref scope, DirectionColumn, out byte fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Direction = (SortDirection)fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, PathColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Path = fieldValue; + break; + default: + return r; + } + } + + return Result.Success; + } + + public sealed class PrimarySortKeyComparer : EqualityComparer + { + public static new readonly PrimarySortKeyComparer Default = new PrimarySortKeyComparer(); + + public override bool Equals(PrimarySortKey x, PrimarySortKey y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Path, y.Path) && + default(UInt8HybridRowSerializer).Comparer.Equals((byte)x.Direction, (byte)y.Direction); + } + + public override int GetHashCode(PrimarySortKey obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Path), + default(UInt8HybridRowSerializer).Comparer.GetHashCode((byte)obj.Direction)); + } + } + } + + public readonly struct StaticKeyHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473656; + public const int Size = 1; + public IEqualityComparer Comparer => StaticKeyComparer.Default; + private static readonly Utf8String PathName = Utf8String.TranscodeUtf16("path"); + + private static readonly LayoutColumn PathColumn; + + static StaticKeyHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(PathName, out PathColumn); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, StaticKey value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, StaticKey value) + { + Result r; + if (value.Path != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, PathColumn, value.Path); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out StaticKey value) + { + if (isRoot) + { + value = new StaticKey(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new StaticKey(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref StaticKey value) + { + Result r; + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, PathColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Path = fieldValue; + break; + default: + return r; + } + } + + return Result.Success; + } + + public sealed class StaticKeyComparer : EqualityComparer + { + public static new readonly StaticKeyComparer Default = new StaticKeyComparer(); + + public override bool Equals(StaticKey x, StaticKey y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return default(Utf8HybridRowSerializer).Comparer.Equals(x.Path, y.Path); + } + + public override int GetHashCode(StaticKey obj) + { + return default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Path); + } + } + } + + public readonly struct PropertyHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473657; + public const int Size = 1; + public IEqualityComparer Comparer => PropertyComparer.Default; + private static readonly Utf8String PathName = Utf8String.TranscodeUtf16("path"); + private static readonly Utf8String CommentName = Utf8String.TranscodeUtf16("comment"); + private static readonly Utf8String PropertyTypeName = Utf8String.TranscodeUtf16("type"); + private static readonly Utf8String ApiNameName = Utf8String.TranscodeUtf16("apiname"); + private static readonly Utf8String AllowEmptyName = Utf8String.TranscodeUtf16("allowEmpty"); + + private static readonly LayoutColumn PathColumn; + private static readonly LayoutColumn CommentColumn; + private static readonly LayoutColumn PropertyTypeColumn; + private static readonly LayoutColumn ApiNameColumn; + private static readonly LayoutColumn AllowEmptyColumn; + + private static readonly StringToken CommentToken; + private static readonly StringToken PropertyTypeToken; + private static readonly StringToken ApiNameToken; + private static readonly StringToken AllowEmptyToken; + + static PropertyHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(PathName, out PathColumn); + Contract.Invariant(found); + found = layout.TryFind(CommentName, out CommentColumn); + Contract.Invariant(found); + found = layout.TryFind(PropertyTypeName, out PropertyTypeColumn); + Contract.Invariant(found); + found = layout.TryFind(ApiNameName, out ApiNameColumn); + Contract.Invariant(found); + found = layout.TryFind(AllowEmptyName, out AllowEmptyColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(CommentColumn.Path, out CommentToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(PropertyTypeColumn.Path, out PropertyTypeToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(ApiNameColumn.Path, out ApiNameToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(AllowEmptyColumn.Path, out AllowEmptyToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Property value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, Property value) + { + Result r; + if (value.Path != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, PathColumn, value.Path); + if (r != Result.Success) + { + return r; + } + } + + if (value.Comment != default) + { + scope.Find(ref row, CommentColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.Comment); + if (r != Result.Success) + { + return r; + } + } + + if (value.PropertyType != default) + { + scope.Find(ref row, PropertyTypeColumn.Path); + r = default(PropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, false, PropertyTypeColumn.TypeArgs, value.PropertyType); + if (r != Result.Success) + { + return r; + } + } + + if (value.ApiName != default) + { + scope.Find(ref row, ApiNameColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.ApiName); + if (r != Result.Success) + { + return r; + } + } + + if (value.AllowEmpty != default) + { + scope.Find(ref row, AllowEmptyColumn.Path); + r = LayoutType.UInt8.WriteSparse(ref row, ref scope, (byte)value.AllowEmpty); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Property value) + { + if (isRoot) + { + value = new Property(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new Property(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref Property value) + { + Result r; + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, PathColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Path = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == CommentToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Comment = fieldValue; + continue; + } + + if (scope.Token == PropertyTypeToken.Id) + { + r = default(PropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out PropertyType fieldValue); + if (r != Result.Success) + { + return r; + } + + value.PropertyType = fieldValue; + continue; + } + + if (scope.Token == ApiNameToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.ApiName = fieldValue; + continue; + } + + if (scope.Token == AllowEmptyToken.Id) + { + r = LayoutType.UInt8.ReadSparse(ref row, ref scope, out byte fieldValue); + if (r != Result.Success) + { + return r; + } + + value.AllowEmpty = (AllowEmptyKind)fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class PropertyComparer : EqualityComparer + { + public static new readonly PropertyComparer Default = new PropertyComparer(); + + public override bool Equals(Property x, Property y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Path, y.Path) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Comment, y.Comment) && + default(PropertyTypeHybridRowSerializer).Comparer.Equals(x.PropertyType, y.PropertyType) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.ApiName, y.ApiName) && + default(UInt8HybridRowSerializer).Comparer.Equals((byte)x.AllowEmpty, (byte)y.AllowEmpty); + } + + public override int GetHashCode(Property obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Path), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Comment), + default(PropertyTypeHybridRowSerializer).Comparer.GetHashCode(obj.PropertyType), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.ApiName), + default(UInt8HybridRowSerializer).Comparer.GetHashCode((byte)obj.AllowEmpty)); + } + } + } + + public readonly struct PropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473658; + public const int Size = 2; + public IEqualityComparer Comparer => PropertyTypeComparer.Default; + private static readonly Utf8String ApiTypeName = Utf8String.TranscodeUtf16("apitype"); + private static readonly Utf8String TypeName = Utf8String.TranscodeUtf16("type"); + private static readonly Utf8String NullableName = Utf8String.TranscodeUtf16("nullable"); + + private static readonly LayoutColumn ApiTypeColumn; + private static readonly LayoutColumn TypeColumn; + private static readonly LayoutColumn NullableColumn; + + static PropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(ApiTypeName, out ApiTypeColumn); + Contract.Invariant(found); + found = layout.TryFind(TypeName, out TypeColumn); + Contract.Invariant(found); + found = layout.TryFind(NullableName, out NullableColumn); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, PropertyType value) + { + switch (value) + { + case PrimitivePropertyType p: + return default(PrimitivePropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + case ScopePropertyType p: + return default(ScopePropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + default: + break; + } + + Contract.Fail("Type is abstract."); + return Result.Failure; + } + + public static Result WriteBase(ref RowBuffer row, ref RowCursor scope, PropertyType value) + { + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, PropertyType value) + { + Result r; + if (value.Type != default) + { + r = LayoutType.UInt8.WriteFixed(ref row, ref scope, TypeColumn, (byte)value.Type); + if (r != Result.Success) + { + return r; + } + } + + if (value.Nullable != default) + { + r = LayoutType.Boolean.WriteFixed(ref row, ref scope, NullableColumn, value.Nullable); + if (r != Result.Success) + { + return r; + } + } + + if (value.ApiType != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, ApiTypeColumn, value.ApiType); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out PropertyType value) + { + if (!(scope.TypeArg.Type is LayoutUDT)) + { + value = default; + return Result.TypeMismatch; + } + + switch (scope.TypeArg.TypeArgs.SchemaId.Id) + { + case PrimitivePropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(PrimitivePropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out PrimitivePropertyType fieldValue); + value = fieldValue; + return r; + } + + case ArrayPropertyTypeHybridRowSerializer.SchemaId: + case ObjectPropertyTypeHybridRowSerializer.SchemaId: + case UdtPropertyTypeHybridRowSerializer.SchemaId: + case SetPropertyTypeHybridRowSerializer.SchemaId: + case MapPropertyTypeHybridRowSerializer.SchemaId: + case TuplePropertyTypeHybridRowSerializer.SchemaId: + case TaggedPropertyTypeHybridRowSerializer.SchemaId: + case ScopePropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(ScopePropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out ScopePropertyType fieldValue); + value = fieldValue; + return r; + } + + default: + break; + } + + Contract.Fail("Type is abstract."); + value = default; + return Result.Failure; + } + + public static Result ReadBase(ref RowBuffer row, ref RowCursor scope, ref PropertyType value) + { + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref PropertyType value) + { + Result r; + { + r = LayoutType.UInt8.ReadFixed(ref row, ref scope, TypeColumn, out byte fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Type = (TypeKind)fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Boolean.ReadFixed(ref row, ref scope, NullableColumn, out bool fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Nullable = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, ApiTypeColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.ApiType = fieldValue; + break; + default: + return r; + } + } + + return Result.Success; + } + + public sealed class PropertyTypeComparer : EqualityComparer + { + public static new readonly PropertyTypeComparer Default = new PropertyTypeComparer(); + + public override bool Equals(PropertyType x, PropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + switch (x) + { + case PrimitivePropertyType p: + return default(PrimitivePropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (PrimitivePropertyType)y); + case ScopePropertyType p: + return default(ScopePropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (ScopePropertyType)y); + default: + break; + } + Contract.Fail("Type is abstract."); + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool EqualsBase(PropertyType x, PropertyType y) + { + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.ApiType, y.ApiType) && + default(UInt8HybridRowSerializer).Comparer.Equals((byte)x.Type, (byte)y.Type) && + default(BooleanHybridRowSerializer).Comparer.Equals(x.Nullable, y.Nullable); + } + + public override int GetHashCode(PropertyType obj) + { + switch (obj) + { + case PrimitivePropertyType p: + return default(PrimitivePropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + case ScopePropertyType p: + return default(ScopePropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + default: + break; + } + Contract.Fail("Type is abstract."); + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GetHashCodeBase(PropertyType obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.ApiType), + default(UInt8HybridRowSerializer).Comparer.GetHashCode((byte)obj.Type), + default(BooleanHybridRowSerializer).Comparer.GetHashCode(obj.Nullable)); + } + } + } + + public readonly struct PrimitivePropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473659; + public const int Size = 5; + public IEqualityComparer Comparer => PrimitivePropertyTypeComparer.Default; + private static readonly Utf8String LengthName = Utf8String.TranscodeUtf16("length"); + private static readonly Utf8String StorageName = Utf8String.TranscodeUtf16("storage"); + private static readonly Utf8String EnumName = Utf8String.TranscodeUtf16("enum"); + private static readonly Utf8String RowBufferSizeName = Utf8String.TranscodeUtf16("rowBufferSize"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn LengthColumn; + private static readonly LayoutColumn StorageColumn; + private static readonly LayoutColumn EnumColumn; + private static readonly LayoutColumn RowBufferSizeColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken EnumToken; + private static readonly StringToken RowBufferSizeToken; + private static readonly StringToken __BaseToken; + + static PrimitivePropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(LengthName, out LengthColumn); + Contract.Invariant(found); + found = layout.TryFind(StorageName, out StorageColumn); + Contract.Invariant(found); + found = layout.TryFind(EnumName, out EnumColumn); + Contract.Invariant(found); + found = layout.TryFind(RowBufferSizeName, out RowBufferSizeColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(EnumColumn.Path, out EnumToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(RowBufferSizeColumn.Path, out RowBufferSizeToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, PrimitivePropertyType value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, PrimitivePropertyType value) + { + Result r; + if (value.Length != default) + { + r = LayoutType.Int32.WriteFixed(ref row, ref scope, LengthColumn, value.Length); + if (r != Result.Success) + { + return r; + } + } + + if (value.Storage != default) + { + r = LayoutType.UInt8.WriteFixed(ref row, ref scope, StorageColumn, (byte)value.Storage); + if (r != Result.Success) + { + return r; + } + } + + if (!string.IsNullOrEmpty(value.Enum)) + { + scope.Find(ref row, EnumColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.Enum); + if (r != Result.Success) + { + return r; + } + } + + if (value.RowBufferSize != default) + { + scope.Find(ref row, RowBufferSizeColumn.Path); + r = LayoutType.Boolean.WriteSparse(ref row, ref scope, value.RowBufferSize); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = PropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out PrimitivePropertyType value) + { + if (isRoot) + { + value = new PrimitivePropertyType(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new PrimitivePropertyType(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref PrimitivePropertyType value) + { + Result r; + { + r = LayoutType.Int32.ReadFixed(ref row, ref scope, LengthColumn, out int fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Length = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.UInt8.ReadFixed(ref row, ref scope, StorageColumn, out byte fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Storage = (StorageKind)fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == EnumToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Enum = fieldValue; + continue; + } + + if (scope.Token == RowBufferSizeToken.Id) + { + r = LayoutType.Boolean.ReadSparse(ref row, ref scope, out bool fieldValue); + if (r != Result.Success) + { + return r; + } + + value.RowBufferSize = fieldValue; + continue; + } + + if (scope.Token == __BaseToken.Id) + { + PropertyType baseValue = value; + r = PropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class PrimitivePropertyTypeComparer : EqualityComparer + { + public static new readonly PrimitivePropertyTypeComparer Default = new PrimitivePropertyTypeComparer(); + + public override bool Equals(PrimitivePropertyType x, PrimitivePropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + PropertyTypeHybridRowSerializer.PropertyTypeComparer.EqualsBase(x, y) && + default(Int32HybridRowSerializer).Comparer.Equals(x.Length, y.Length) && + default(UInt8HybridRowSerializer).Comparer.Equals((byte)x.Storage, (byte)y.Storage) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Enum, y.Enum) && + default(BooleanHybridRowSerializer).Comparer.Equals(x.RowBufferSize, y.RowBufferSize); + } + + public override int GetHashCode(PrimitivePropertyType obj) + { + return HashCode.Combine( + PropertyTypeHybridRowSerializer.PropertyTypeComparer.GetHashCodeBase(obj), + default(Int32HybridRowSerializer).Comparer.GetHashCode(obj.Length), + default(UInt8HybridRowSerializer).Comparer.GetHashCode((byte)obj.Storage), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Enum), + default(BooleanHybridRowSerializer).Comparer.GetHashCode(obj.RowBufferSize)); + } + } + } + + public readonly struct ScopePropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473660; + public const int Size = 1; + public IEqualityComparer Comparer => ScopePropertyTypeComparer.Default; + private static readonly Utf8String ImmutableName = Utf8String.TranscodeUtf16("immutable"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn ImmutableColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken __BaseToken; + + static ScopePropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(ImmutableName, out ImmutableColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, ScopePropertyType value) + { + switch (value) + { + case ArrayPropertyType p: + return default(ArrayPropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + case ObjectPropertyType p: + return default(ObjectPropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + case UdtPropertyType p: + return default(UdtPropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + case SetPropertyType p: + return default(SetPropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + case MapPropertyType p: + return default(MapPropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + case TuplePropertyType p: + return default(TuplePropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + case TaggedPropertyType p: + return default(TaggedPropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + default: + break; + } + + Contract.Fail("Type is abstract."); + return Result.Failure; + } + + public static Result WriteBase(ref RowBuffer row, ref RowCursor scope, ScopePropertyType value) + { + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, ScopePropertyType value) + { + Result r; + if (value.Immutable != default) + { + r = LayoutType.Boolean.WriteFixed(ref row, ref scope, ImmutableColumn, value.Immutable); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = PropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out ScopePropertyType value) + { + if (!(scope.TypeArg.Type is LayoutUDT)) + { + value = default; + return Result.TypeMismatch; + } + + switch (scope.TypeArg.TypeArgs.SchemaId.Id) + { + case ArrayPropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(ArrayPropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out ArrayPropertyType fieldValue); + value = fieldValue; + return r; + } + + case ObjectPropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(ObjectPropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out ObjectPropertyType fieldValue); + value = fieldValue; + return r; + } + + case UdtPropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(UdtPropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out UdtPropertyType fieldValue); + value = fieldValue; + return r; + } + + case SetPropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(SetPropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out SetPropertyType fieldValue); + value = fieldValue; + return r; + } + + case MapPropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(MapPropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out MapPropertyType fieldValue); + value = fieldValue; + return r; + } + + case TuplePropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(TuplePropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out TuplePropertyType fieldValue); + value = fieldValue; + return r; + } + + case TaggedPropertyTypeHybridRowSerializer.SchemaId: + { + Result r = default(TaggedPropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out TaggedPropertyType fieldValue); + value = fieldValue; + return r; + } + + default: + break; + } + + Contract.Fail("Type is abstract."); + value = default; + return Result.Failure; + } + + public static Result ReadBase(ref RowBuffer row, ref RowCursor scope, ref ScopePropertyType value) + { + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref ScopePropertyType value) + { + Result r; + { + r = LayoutType.Boolean.ReadFixed(ref row, ref scope, ImmutableColumn, out bool fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Immutable = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == __BaseToken.Id) + { + PropertyType baseValue = value; + r = PropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class ScopePropertyTypeComparer : EqualityComparer + { + public static new readonly ScopePropertyTypeComparer Default = new ScopePropertyTypeComparer(); + + public override bool Equals(ScopePropertyType x, ScopePropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + switch (x) + { + case ArrayPropertyType p: + return default(ArrayPropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (ArrayPropertyType)y); + case ObjectPropertyType p: + return default(ObjectPropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (ObjectPropertyType)y); + case UdtPropertyType p: + return default(UdtPropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (UdtPropertyType)y); + case SetPropertyType p: + return default(SetPropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (SetPropertyType)y); + case MapPropertyType p: + return default(MapPropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (MapPropertyType)y); + case TuplePropertyType p: + return default(TuplePropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (TuplePropertyType)y); + case TaggedPropertyType p: + return default(TaggedPropertyTypeHybridRowSerializer) + .Comparer.Equals(p, (TaggedPropertyType)y); + default: + break; + } + Contract.Fail("Type is abstract."); + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool EqualsBase(ScopePropertyType x, ScopePropertyType y) + { + + return + PropertyTypeHybridRowSerializer.PropertyTypeComparer.EqualsBase(x, y) && + default(BooleanHybridRowSerializer).Comparer.Equals(x.Immutable, y.Immutable); + } + + public override int GetHashCode(ScopePropertyType obj) + { + switch (obj) + { + case ArrayPropertyType p: + return default(ArrayPropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + case ObjectPropertyType p: + return default(ObjectPropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + case UdtPropertyType p: + return default(UdtPropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + case SetPropertyType p: + return default(SetPropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + case MapPropertyType p: + return default(MapPropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + case TuplePropertyType p: + return default(TuplePropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + case TaggedPropertyType p: + return default(TaggedPropertyTypeHybridRowSerializer) + .Comparer.GetHashCode(p); + default: + break; + } + Contract.Fail("Type is abstract."); + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GetHashCodeBase(ScopePropertyType obj) + { + return HashCode.Combine( + PropertyTypeHybridRowSerializer.PropertyTypeComparer.GetHashCodeBase(obj), + default(BooleanHybridRowSerializer).Comparer.GetHashCode(obj.Immutable)); + } + } + } + + public readonly struct ArrayPropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473661; + public const int Size = 0; + public IEqualityComparer Comparer => ArrayPropertyTypeComparer.Default; + private static readonly Utf8String ItemsName = Utf8String.TranscodeUtf16("items"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn ItemsColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken ItemsToken; + private static readonly StringToken __BaseToken; + + static ArrayPropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(ItemsName, out ItemsColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(ItemsColumn.Path, out ItemsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, ArrayPropertyType value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, ArrayPropertyType value) + { + Result r; + if (value.Items != default) + { + scope.Find(ref row, ItemsColumn.Path); + r = default(PropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, false, ItemsColumn.TypeArgs, value.Items); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = ScopePropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out ArrayPropertyType value) + { + if (isRoot) + { + value = new ArrayPropertyType(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new ArrayPropertyType(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref ArrayPropertyType value) + { + Result r; + while (scope.MoveNext(ref row)) + { + if (scope.Token == ItemsToken.Id) + { + r = default(PropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out PropertyType fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Items = fieldValue; + continue; + } + + if (scope.Token == __BaseToken.Id) + { + ScopePropertyType baseValue = value; + r = ScopePropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class ArrayPropertyTypeComparer : EqualityComparer + { + public static new readonly ArrayPropertyTypeComparer Default = new ArrayPropertyTypeComparer(); + + public override bool Equals(ArrayPropertyType x, ArrayPropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.EqualsBase(x, y) && + default(PropertyTypeHybridRowSerializer).Comparer.Equals(x.Items, y.Items); + } + + public override int GetHashCode(ArrayPropertyType obj) + { + return HashCode.Combine( + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.GetHashCodeBase(obj), + default(PropertyTypeHybridRowSerializer).Comparer.GetHashCode(obj.Items)); + } + } + } + + public readonly struct ObjectPropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473662; + public const int Size = 0; + public IEqualityComparer Comparer => ObjectPropertyTypeComparer.Default; + private static readonly Utf8String PropertiesName = Utf8String.TranscodeUtf16("properties"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn PropertiesColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken PropertiesToken; + private static readonly StringToken __BaseToken; + + static ObjectPropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(PropertiesName, out PropertiesColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(PropertiesColumn.Path, out PropertiesToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, ObjectPropertyType value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, ObjectPropertyType value) + { + Result r; + if ((value.Properties != null) && (value.Properties.Count > 0)) + { + scope.Find(ref row, PropertiesColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + PropertiesColumn.TypeArgs, + value.Properties); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = ScopePropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out ObjectPropertyType value) + { + if (isRoot) + { + value = new ObjectPropertyType(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new ObjectPropertyType(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref ObjectPropertyType value) + { + Result r; + while (scope.MoveNext(ref row)) + { + if (scope.Token == PropertiesToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Properties = fieldValue; + continue; + } + + if (scope.Token == __BaseToken.Id) + { + ScopePropertyType baseValue = value; + r = ScopePropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class ObjectPropertyTypeComparer : EqualityComparer + { + public static new readonly ObjectPropertyTypeComparer Default = new ObjectPropertyTypeComparer(); + + public override bool Equals(ObjectPropertyType x, ObjectPropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.EqualsBase(x, y) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Properties, y.Properties); + } + + public override int GetHashCode(ObjectPropertyType obj) + { + return HashCode.Combine( + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.GetHashCodeBase(obj), + default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.Properties)); + } + } + } + + public readonly struct UdtPropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473663; + public const int Size = 5; + public IEqualityComparer Comparer => UdtPropertyTypeComparer.Default; + private static readonly Utf8String NameName = Utf8String.TranscodeUtf16("name"); + private static readonly Utf8String SchemaIdName = Utf8String.TranscodeUtf16("id"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn NameColumn; + private static readonly LayoutColumn SchemaIdColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken __BaseToken; + + static UdtPropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(NameName, out NameColumn); + Contract.Invariant(found); + found = layout.TryFind(SchemaIdName, out SchemaIdColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, UdtPropertyType value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, UdtPropertyType value) + { + Result r; + if (value.SchemaId != default) + { + r = LayoutType.Int32.WriteFixed(ref row, ref scope, SchemaIdColumn, (int)value.SchemaId); + if (r != Result.Success) + { + return r; + } + } + + if (value.Name != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, NameColumn, value.Name); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = ScopePropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out UdtPropertyType value) + { + if (isRoot) + { + value = new UdtPropertyType(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new UdtPropertyType(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref UdtPropertyType value) + { + Result r; + { + r = LayoutType.Int32.ReadFixed(ref row, ref scope, SchemaIdColumn, out int fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.SchemaId = (SchemaId)fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, NameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Name = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == __BaseToken.Id) + { + ScopePropertyType baseValue = value; + r = ScopePropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class UdtPropertyTypeComparer : EqualityComparer + { + public static new readonly UdtPropertyTypeComparer Default = new UdtPropertyTypeComparer(); + + public override bool Equals(UdtPropertyType x, UdtPropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.EqualsBase(x, y) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Name, y.Name) && + default(Int32HybridRowSerializer).Comparer.Equals((int)x.SchemaId, (int)y.SchemaId); + } + + public override int GetHashCode(UdtPropertyType obj) + { + return HashCode.Combine( + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.GetHashCodeBase(obj), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Name), + default(Int32HybridRowSerializer).Comparer.GetHashCode((int)obj.SchemaId)); + } + } + } + + public readonly struct SetPropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473664; + public const int Size = 0; + public IEqualityComparer Comparer => SetPropertyTypeComparer.Default; + private static readonly Utf8String ItemsName = Utf8String.TranscodeUtf16("items"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn ItemsColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken ItemsToken; + private static readonly StringToken __BaseToken; + + static SetPropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(ItemsName, out ItemsColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(ItemsColumn.Path, out ItemsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, SetPropertyType value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, SetPropertyType value) + { + Result r; + if (value.Items != default) + { + scope.Find(ref row, ItemsColumn.Path); + r = default(PropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, false, ItemsColumn.TypeArgs, value.Items); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = ScopePropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out SetPropertyType value) + { + if (isRoot) + { + value = new SetPropertyType(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new SetPropertyType(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref SetPropertyType value) + { + Result r; + while (scope.MoveNext(ref row)) + { + if (scope.Token == ItemsToken.Id) + { + r = default(PropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out PropertyType fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Items = fieldValue; + continue; + } + + if (scope.Token == __BaseToken.Id) + { + ScopePropertyType baseValue = value; + r = ScopePropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class SetPropertyTypeComparer : EqualityComparer + { + public static new readonly SetPropertyTypeComparer Default = new SetPropertyTypeComparer(); + + public override bool Equals(SetPropertyType x, SetPropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.EqualsBase(x, y) && + default(PropertyTypeHybridRowSerializer).Comparer.Equals(x.Items, y.Items); + } + + public override int GetHashCode(SetPropertyType obj) + { + return HashCode.Combine( + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.GetHashCodeBase(obj), + default(PropertyTypeHybridRowSerializer).Comparer.GetHashCode(obj.Items)); + } + } + } + + public readonly struct MapPropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473665; + public const int Size = 0; + public IEqualityComparer Comparer => MapPropertyTypeComparer.Default; + private static readonly Utf8String KeysName = Utf8String.TranscodeUtf16("keys"); + private static readonly Utf8String ValuesName = Utf8String.TranscodeUtf16("values"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn KeysColumn; + private static readonly LayoutColumn ValuesColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken KeysToken; + private static readonly StringToken ValuesToken; + private static readonly StringToken __BaseToken; + + static MapPropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(KeysName, out KeysColumn); + Contract.Invariant(found); + found = layout.TryFind(ValuesName, out ValuesColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(KeysColumn.Path, out KeysToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(ValuesColumn.Path, out ValuesToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, MapPropertyType value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, MapPropertyType value) + { + Result r; + if (value.Keys != default) + { + scope.Find(ref row, KeysColumn.Path); + r = default(PropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, false, KeysColumn.TypeArgs, value.Keys); + if (r != Result.Success) + { + return r; + } + } + + if (value.Values != default) + { + scope.Find(ref row, ValuesColumn.Path); + r = default(PropertyTypeHybridRowSerializer) + .Write(ref row, ref scope, false, ValuesColumn.TypeArgs, value.Values); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = ScopePropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out MapPropertyType value) + { + if (isRoot) + { + value = new MapPropertyType(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new MapPropertyType(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref MapPropertyType value) + { + Result r; + while (scope.MoveNext(ref row)) + { + if (scope.Token == KeysToken.Id) + { + r = default(PropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out PropertyType fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Keys = fieldValue; + continue; + } + + if (scope.Token == ValuesToken.Id) + { + r = default(PropertyTypeHybridRowSerializer) + .Read(ref row, ref scope, false, out PropertyType fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Values = fieldValue; + continue; + } + + if (scope.Token == __BaseToken.Id) + { + ScopePropertyType baseValue = value; + r = ScopePropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class MapPropertyTypeComparer : EqualityComparer + { + public static new readonly MapPropertyTypeComparer Default = new MapPropertyTypeComparer(); + + public override bool Equals(MapPropertyType x, MapPropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.EqualsBase(x, y) && + default(PropertyTypeHybridRowSerializer).Comparer.Equals(x.Keys, y.Keys) && + default(PropertyTypeHybridRowSerializer).Comparer.Equals(x.Values, y.Values); + } + + public override int GetHashCode(MapPropertyType obj) + { + return HashCode.Combine( + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.GetHashCodeBase(obj), + default(PropertyTypeHybridRowSerializer).Comparer.GetHashCode(obj.Keys), + default(PropertyTypeHybridRowSerializer).Comparer.GetHashCode(obj.Values)); + } + } + } + + public readonly struct TuplePropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473666; + public const int Size = 0; + public IEqualityComparer Comparer => TuplePropertyTypeComparer.Default; + private static readonly Utf8String ItemsName = Utf8String.TranscodeUtf16("items"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn ItemsColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken ItemsToken; + private static readonly StringToken __BaseToken; + + static TuplePropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(ItemsName, out ItemsColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(ItemsColumn.Path, out ItemsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, TuplePropertyType value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, TuplePropertyType value) + { + Result r; + if ((value.Items != null) && (value.Items.Count > 0)) + { + scope.Find(ref row, ItemsColumn.Path); + r = default(ArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + ItemsColumn.TypeArgs, + value.Items); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = ScopePropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out TuplePropertyType value) + { + if (isRoot) + { + value = new TuplePropertyType(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new TuplePropertyType(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref TuplePropertyType value) + { + Result r; + while (scope.MoveNext(ref row)) + { + if (scope.Token == ItemsToken.Id) + { + r = default(ArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Items = fieldValue; + continue; + } + + if (scope.Token == __BaseToken.Id) + { + ScopePropertyType baseValue = value; + r = ScopePropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class TuplePropertyTypeComparer : EqualityComparer + { + public static new readonly TuplePropertyTypeComparer Default = new TuplePropertyTypeComparer(); + + public override bool Equals(TuplePropertyType x, TuplePropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.EqualsBase(x, y) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Items, y.Items); + } + + public override int GetHashCode(TuplePropertyType obj) + { + return HashCode.Combine( + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.GetHashCodeBase(obj), + default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.Items)); + } + } + } + + public readonly struct TaggedPropertyTypeHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473667; + public const int Size = 0; + public IEqualityComparer Comparer => TaggedPropertyTypeComparer.Default; + private static readonly Utf8String ItemsName = Utf8String.TranscodeUtf16("items"); + private static readonly Utf8String __BaseName = Utf8String.TranscodeUtf16("__base"); + + private static readonly LayoutColumn ItemsColumn; + private static readonly LayoutColumn __BaseColumn; + + private static readonly StringToken ItemsToken; + private static readonly StringToken __BaseToken; + + static TaggedPropertyTypeHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(ItemsName, out ItemsColumn); + Contract.Invariant(found); + found = layout.TryFind(__BaseName, out __BaseColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(ItemsColumn.Path, out ItemsToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, TaggedPropertyType value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, TaggedPropertyType value) + { + Result r; + if ((value.Items != null) && (value.Items.Count > 0)) + { + scope.Find(ref row, ItemsColumn.Path); + r = default(ArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + ItemsColumn.TypeArgs, + value.Items); + if (r != Result.Success) + { + return r; + } + } + + { + scope.Find(ref row, __BaseColumn.Path); + r = ScopePropertyTypeHybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out TaggedPropertyType value) + { + if (isRoot) + { + value = new TaggedPropertyType(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new TaggedPropertyType(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref TaggedPropertyType value) + { + Result r; + while (scope.MoveNext(ref row)) + { + if (scope.Token == ItemsToken.Id) + { + r = default(ArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Items = fieldValue; + continue; + } + + if (scope.Token == __BaseToken.Id) + { + ScopePropertyType baseValue = value; + r = ScopePropertyTypeHybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + { + return r; + } + + Contract.Assert(baseValue == value); + continue; + } + } + + return Result.Success; + } + + public sealed class TaggedPropertyTypeComparer : EqualityComparer + { + public static new readonly TaggedPropertyTypeComparer Default = new TaggedPropertyTypeComparer(); + + public override bool Equals(TaggedPropertyType x, TaggedPropertyType y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.EqualsBase(x, y) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Items, y.Items); + } + + public override int GetHashCode(TaggedPropertyType obj) + { + return HashCode.Combine( + ScopePropertyTypeHybridRowSerializer.ScopePropertyTypeComparer.GetHashCodeBase(obj), + default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.Items)); + } + } + } + + public readonly struct EnumSchemaHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473668; + public const int Size = 2; + public IEqualityComparer Comparer => EnumSchemaComparer.Default; + private static readonly Utf8String TypeName = Utf8String.TranscodeUtf16("type"); + private static readonly Utf8String NameName = Utf8String.TranscodeUtf16("name"); + private static readonly Utf8String CommentName = Utf8String.TranscodeUtf16("comment"); + private static readonly Utf8String ApiTypeName = Utf8String.TranscodeUtf16("apitype"); + private static readonly Utf8String ValuesName = Utf8String.TranscodeUtf16("values"); + + private static readonly LayoutColumn TypeColumn; + private static readonly LayoutColumn NameColumn; + private static readonly LayoutColumn CommentColumn; + private static readonly LayoutColumn ApiTypeColumn; + private static readonly LayoutColumn ValuesColumn; + + private static readonly StringToken CommentToken; + private static readonly StringToken ApiTypeToken; + private static readonly StringToken ValuesToken; + + static EnumSchemaHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(TypeName, out TypeColumn); + Contract.Invariant(found); + found = layout.TryFind(NameName, out NameColumn); + Contract.Invariant(found); + found = layout.TryFind(CommentName, out CommentColumn); + Contract.Invariant(found); + found = layout.TryFind(ApiTypeName, out ApiTypeColumn); + Contract.Invariant(found); + found = layout.TryFind(ValuesName, out ValuesColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(CommentColumn.Path, out CommentToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(ApiTypeColumn.Path, out ApiTypeToken); + Contract.Invariant(found); + found = layout.Tokenizer.TryFindToken(ValuesColumn.Path, out ValuesToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, EnumSchema value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, EnumSchema value) + { + Result r; + if (value.Type != default) + { + r = LayoutType.UInt8.WriteFixed(ref row, ref scope, TypeColumn, (byte)value.Type); + if (r != Result.Success) + { + return r; + } + } + + if (value.Name != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, NameColumn, value.Name); + if (r != Result.Success) + { + return r; + } + } + + if (value.Comment != default) + { + scope.Find(ref row, CommentColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.Comment); + if (r != Result.Success) + { + return r; + } + } + + if (value.ApiType != default) + { + scope.Find(ref row, ApiTypeColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.ApiType); + if (r != Result.Success) + { + return r; + } + } + + if ((value.Values != null) && (value.Values.Count > 0)) + { + scope.Find(ref row, ValuesColumn.Path); + r = default(TypedArrayHybridRowSerializer).Write( + ref row, + ref scope, + false, + ValuesColumn.TypeArgs, + value.Values); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out EnumSchema value) + { + if (isRoot) + { + value = new EnumSchema(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new EnumSchema(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref EnumSchema value) + { + Result r; + { + r = LayoutType.UInt8.ReadFixed(ref row, ref scope, TypeColumn, out byte fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Type = (TypeKind)fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, NameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Name = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == CommentToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Comment = fieldValue; + continue; + } + + if (scope.Token == ApiTypeToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.ApiType = fieldValue; + continue; + } + + if (scope.Token == ValuesToken.Id) + { + r = default(TypedArrayHybridRowSerializer) + .Read(ref row, ref scope, false, out List fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Values = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class EnumSchemaComparer : EqualityComparer + { + public static new readonly EnumSchemaComparer Default = new EnumSchemaComparer(); + + public override bool Equals(EnumSchema x, EnumSchema y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(UInt8HybridRowSerializer).Comparer.Equals((byte)x.Type, (byte)y.Type) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Name, y.Name) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Comment, y.Comment) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.ApiType, y.ApiType) && + default(TypedArrayHybridRowSerializer).Comparer.Equals(x.Values, y.Values); + } + + public override int GetHashCode(EnumSchema obj) + { + return HashCode.Combine( + default(UInt8HybridRowSerializer).Comparer.GetHashCode((byte)obj.Type), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Name), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Comment), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.ApiType), + default(TypedArrayHybridRowSerializer).Comparer.GetHashCode(obj.Values)); + } + } + } + + public readonly struct EnumValueHybridRowSerializer : IHybridRowSerializer + { + public const int SchemaId = 2147473669; + public const int Size = 9; + public IEqualityComparer Comparer => EnumValueComparer.Default; + private static readonly Utf8String NameName = Utf8String.TranscodeUtf16("name"); + private static readonly Utf8String ValueName = Utf8String.TranscodeUtf16("value"); + private static readonly Utf8String CommentName = Utf8String.TranscodeUtf16("comment"); + + private static readonly LayoutColumn NameColumn; + private static readonly LayoutColumn ValueColumn; + private static readonly LayoutColumn CommentColumn; + + private static readonly StringToken CommentToken; + + static EnumValueHybridRowSerializer() + { + Layout layout = SchemasHrSchema.LayoutResolver.Resolve(new SchemaId(SchemaId)); + + bool found; + found = layout.TryFind(NameName, out NameColumn); + Contract.Invariant(found); + found = layout.TryFind(ValueName, out ValueColumn); + Contract.Invariant(found); + found = layout.TryFind(CommentName, out CommentColumn); + Contract.Invariant(found); + + found = layout.Tokenizer.TryFindToken(CommentColumn.Path, out CommentToken); + Contract.Invariant(found); + } + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, EnumValue value) + { + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Write(ref RowBuffer row, ref RowCursor scope, EnumValue value) + { + Result r; + if (value.Value != default) + { + r = LayoutType.Int64.WriteFixed(ref row, ref scope, ValueColumn, value.Value); + if (r != Result.Success) + { + return r; + } + } + + if (value.Name != default) + { + r = LayoutType.Utf8.WriteVariable(ref row, ref scope, NameColumn, value.Name); + if (r != Result.Success) + { + return r; + } + } + + if (value.Comment != default) + { + scope.Find(ref row, CommentColumn.Path); + r = LayoutType.Utf8.WriteSparse(ref row, ref scope, value.Comment); + if (r != Result.Success) + { + return r; + } + } + + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out EnumValue value) + { + if (isRoot) + { + value = new EnumValue(); + return Read(ref row, ref scope, ref value); + } + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + value = new EnumValue(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + value = default; + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + private static Result Read(ref RowBuffer row, ref RowCursor scope, ref EnumValue value) + { + Result r; + { + r = LayoutType.Int64.ReadFixed(ref row, ref scope, ValueColumn, out long fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Value = fieldValue; + break; + default: + return r; + } + } + + { + r = LayoutType.Utf8.ReadVariable(ref row, ref scope, NameColumn, out string fieldValue); + switch (r) + { + case Result.NotFound: + break; + case Result.Success: + value.Name = fieldValue; + break; + default: + return r; + } + } + + while (scope.MoveNext(ref row)) + { + if (scope.Token == CommentToken.Id) + { + r = LayoutType.Utf8.ReadSparse(ref row, ref scope, out string fieldValue); + if (r != Result.Success) + { + return r; + } + + value.Comment = fieldValue; + continue; + } + } + + return Result.Success; + } + + public sealed class EnumValueComparer : EqualityComparer + { + public static new readonly EnumValueComparer Default = new EnumValueComparer(); + + public override bool Equals(EnumValue x, EnumValue y) + { + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + + return + default(Utf8HybridRowSerializer).Comparer.Equals(x.Name, y.Name) && + default(Int64HybridRowSerializer).Comparer.Equals(x.Value, y.Value) && + default(Utf8HybridRowSerializer).Comparer.Equals(x.Comment, y.Comment); + } + + public override int GetHashCode(EnumValue obj) + { + return HashCode.Combine( + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Name), + default(Int64HybridRowSerializer).Comparer.GetHashCode(obj.Value), + default(Utf8HybridRowSerializer).Comparer.GetHashCode(obj.Comment)); + } + } + } +} diff --git a/dotnet/src/HybridRow/GlobalSuppressions.cs b/src/Serialization/HybridRow/GlobalSuppressions.cs similarity index 100% rename from dotnet/src/HybridRow/GlobalSuppressions.cs rename to src/Serialization/HybridRow/GlobalSuppressions.cs diff --git a/dotnet/src/HybridRow/HybridRowHeader.cs b/src/Serialization/HybridRow/HybridRowHeader.cs similarity index 100% rename from dotnet/src/HybridRow/HybridRowHeader.cs rename to src/Serialization/HybridRow/HybridRowHeader.cs diff --git a/dotnet/src/HybridRow/HybridRowVersion.cs b/src/Serialization/HybridRow/HybridRowVersion.cs similarity index 100% rename from dotnet/src/HybridRow/HybridRowVersion.cs rename to src/Serialization/HybridRow/HybridRowVersion.cs diff --git a/src/Serialization/HybridRow/IO/HybridRowSerializer.cs b/src/Serialization/HybridRow/IO/HybridRowSerializer.cs new file mode 100644 index 0000000..0fa7da5 --- /dev/null +++ b/src/Serialization/HybridRow/IO/HybridRowSerializer.cs @@ -0,0 +1,69 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO +{ + using System.Runtime.CompilerServices; + + /// + /// Provides adapters and utility functions that simplify generated code by allowing that code + /// to address a larger variety of design choices (e.g. class vs struct) with syntactically identical + /// generated code patterns. + /// + public static class HybridRowSerializer + { + /// Result of performing an equality reference check. + public enum EqualityReferenceResult + { + /// + /// Equality could not be determined simply by examining references, a full property-value + /// check must be performed to determine equality. + /// + Unknown = -1, + + /// + /// The values are definitely not equal (e.g. different types), no property value check is + /// required. + /// + NotEqual = 0, + + /// + /// The values are definitely equal (e.g. the same object), no property value check is + /// required. + /// + Equal = 1, + } + + /// Performs a fast equality check using only the object references. + /// The left value to be compared. + /// The right value to be compared. + /// A result indicating whether equality could be determined or not. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EqualityReferenceResult EqualityReferenceCheck(T x, T y) + { + // Implementation Note: this conditional is known at compile-time based on the generic + // expansion of T and will lead to this method becoming an constant (trivially inlinable) + // for struct values of T. + if (typeof(T).IsValueType) + { + return EqualityReferenceResult.Unknown; + } + + if (object.ReferenceEquals(x, y)) + { + return EqualityReferenceResult.Equal; + } + if (x is null || y is null) + { + return EqualityReferenceResult.NotEqual; + } + if (x.GetType() != y.GetType()) + { + return EqualityReferenceResult.NotEqual; + } + + return EqualityReferenceResult.Unknown; + } + } +} diff --git a/src/Serialization/HybridRow/IO/IHybridRowSerializer{T}.cs b/src/Serialization/HybridRow/IO/IHybridRowSerializer{T}.cs new file mode 100644 index 0000000..3bd21ca --- /dev/null +++ b/src/Serialization/HybridRow/IO/IHybridRowSerializer{T}.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO +{ + using System.Collections.Generic; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + + public interface IHybridRowSerializer + { + /// + /// A comparer for items of type T. + /// + IEqualityComparer Comparer { get; } + + /// Write the object to a row. + /// The row to write into. + /// The position in the row at which to write. + /// + /// True if this object is the top-most element within the row such that the row's + /// layout is the object, or false if object is a nested UDT within the column of some other object. + /// + /// Type arguments if the object is a generic collection. + /// The object to write. + /// Success if the write is successful, an error code otherwise. + Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, T value); + + /// Read and materialize and object from a row. + /// The row to read from. + /// The position in the row to read at. + /// + /// True if this object is the top-most element within the row such that the row's + /// layout is the object, or false if object is a nested UDT within the column of some other object. + /// + /// If successful, the new materialized object based on the read content. + /// Success if the write is successful, an error code otherwise. + Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out T value); + } +} diff --git a/dotnet/src/HybridRow/IO/IRowSerializable.cs b/src/Serialization/HybridRow/IO/IRowSerializable.cs similarity index 100% rename from dotnet/src/HybridRow/IO/IRowSerializable.cs rename to src/Serialization/HybridRow/IO/IRowSerializable.cs diff --git a/dotnet/src/HybridRow/IO/RowReader.cs b/src/Serialization/HybridRow/IO/RowReader.cs similarity index 92% rename from dotnet/src/HybridRow/IO/RowReader.cs rename to src/Serialization/HybridRow/IO/RowReader.cs index 3276cc8..78934a0 100644 --- a/dotnet/src/HybridRow/IO/RowReader.cs +++ b/src/Serialization/HybridRow/IO/RowReader.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO { using System; + using System.Runtime.InteropServices; using Microsoft.Azure.Cosmos.Core; using Microsoft.Azure.Cosmos.Core.Utf8; using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; @@ -25,7 +26,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO public ref struct RowReader { private readonly int schematizedCount; - private readonly ReadOnlySpan columns; + private readonly Layout.ColumnView columns; private RowBuffer row; // State that can be checkpointed. @@ -55,18 +56,53 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO /// Initializes a new instance of the struct. /// The row to be read. - /// The scope whose fields should be enumerated. - /// - /// A instance traverses all of the top-level fields of a given - /// scope. If the root scope is provided then all top-level fields in the row are enumerated. Nested - /// child instances can be access through the method - /// to process nested content. - /// public RowReader(ref RowBuffer row) : this(ref row, RowCursor.Create(ref row)) { } + /// Initializes a new instance of the struct. + /// The buffer. + /// The version of the Hybrid Row format to used to encoding the buffer. + /// The resolver for UDTs. + public RowReader(ReadOnlyMemory buffer, HybridRowVersion version, LayoutResolver resolver) + { + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + this.row = new RowBuffer(MemoryMarshal.AsMemory(buffer).Span, version, resolver); + this.cursor = RowCursor.Create(ref this.row); + this.columns = this.cursor.layout.Columns; + this.schematizedCount = this.cursor.layout.NumFixed + this.cursor.layout.NumVariable; + + this.state = States.None; + this.columnIndex = -1; + } + + /// Initializes a new instance of the struct. + /// The buffer. + /// The version of the Hybrid Row format to used to encoding the buffer. + /// The resolver for UDTs. + /// Saved point from a previous iteration. + public RowReader(ReadOnlyMemory buffer, HybridRowVersion version, LayoutResolver resolver, in Checkpoint checkpoint) + { + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + this.row = new RowBuffer(MemoryMarshal.AsMemory(buffer).Span, version, resolver); + this.columns = checkpoint.Cursor.layout.Columns; + this.schematizedCount = checkpoint.Cursor.layout.NumFixed + checkpoint.Cursor.layout.NumVariable; + + this.state = checkpoint.State; + this.cursor = checkpoint.Cursor; + this.columnIndex = checkpoint.ColumnIndex; + } + + /// Initializes a new instance of the struct. + /// The row to be read. + /// Saved point from a previous iteration. public RowReader(ref RowBuffer row, in Checkpoint checkpoint) { this.row = row; @@ -97,6 +133,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO /// The length of row in bytes. public int Length => this.row.Length; + /// The root header for the row. + public HybridRowHeader Header => this.row.Header; + /// The storage placement of the field (if positioned on a field, undefined otherwise). public StorageKind Storage { @@ -879,6 +918,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO return new RowReader(ref this.row, newScope); } + /// Read the current field as a nested, structured, sparse scope. + public Result ReadScope(out T value) + where TSerializer : struct, IHybridRowSerializer + { + return default(TSerializer).Read(ref this.row, ref this.cursor, isRoot: false, out value); + } + /// A function to reader content from a . /// The type of the context value passed by the caller. /// A forward-only cursor for writing content. diff --git a/dotnet/src/HybridRow/IO/RowReaderExtensions.cs b/src/Serialization/HybridRow/IO/RowReaderExtensions.cs similarity index 100% rename from dotnet/src/HybridRow/IO/RowReaderExtensions.cs rename to src/Serialization/HybridRow/IO/RowReaderExtensions.cs diff --git a/dotnet/src/HybridRow/IO/RowWriter.cs b/src/Serialization/HybridRow/IO/RowWriter.cs similarity index 98% rename from dotnet/src/HybridRow/IO/RowWriter.cs rename to src/Serialization/HybridRow/IO/RowWriter.cs index 8b511b0..a337561 100644 --- a/dotnet/src/HybridRow/IO/RowWriter.cs +++ b/src/Serialization/HybridRow/IO/RowWriter.cs @@ -392,6 +392,25 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO (ref RowWriter w, ReadOnlySequence v) => w.row.WriteSparseBinary(ref w.cursor, v, UpdateOptions.Upsert)); } + public Result WriteScope(UtfAnyString path, TypeArgument typeArg, T value) + where TSerializer : struct, IHybridRowSerializer + { + Result result = this.PrepareSparseWrite(path, typeArg); + if (result != Result.Success) + { + return result; + } + + result = default(TSerializer).Write(ref this.row, ref this.cursor, isRoot: false, typeArg.TypeArgs, value); + if (result != Result.Success) + { + return result; + } + + this.cursor.MoveNext(ref this.row); + return Result.Success; + } + public Result WriteScope(UtfAnyString path, TypeArgument typeArg, TContext context, WriterFunc func) { LayoutType type = typeArg.Type; @@ -717,8 +736,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.IO default: return Result.NotFound; } - - return Result.NotFound; } /// Write a generic schematized field value via the scope's layout. diff --git a/dotnet/src/HybridRow/ISpanResizer.cs b/src/Serialization/HybridRow/ISpanResizer.cs similarity index 100% rename from dotnet/src/HybridRow/ISpanResizer.cs rename to src/Serialization/HybridRow/ISpanResizer.cs diff --git a/dotnet/src/HybridRow/Internal/MurmurHash3.cs b/src/Serialization/HybridRow/Internal/MurmurHash3.cs similarity index 99% rename from dotnet/src/HybridRow/Internal/MurmurHash3.cs rename to src/Serialization/HybridRow/Internal/MurmurHash3.cs index 78f8d71..52f1102 100644 --- a/dotnet/src/HybridRow/Internal/MurmurHash3.cs +++ b/src/Serialization/HybridRow/Internal/MurmurHash3.cs @@ -2,6 +2,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ +#pragma warning disable SA1316 // Tuple element names should use correct casing + namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Internal { using System; diff --git a/dotnet/src/HybridRow/Internal/Utf8StringJsonConverter.cs b/src/Serialization/HybridRow/Internal/Utf8StringJsonConverter.cs similarity index 100% rename from dotnet/src/HybridRow/Internal/Utf8StringJsonConverter.cs rename to src/Serialization/HybridRow/Internal/Utf8StringJsonConverter.cs diff --git a/dotnet/src/HybridRow/Layouts/Layout.cs b/src/Serialization/HybridRow/Layouts/Layout.cs similarity index 75% rename from dotnet/src/HybridRow/Layouts/Layout.cs rename to src/Serialization/HybridRow/Layouts/Layout.cs index 890f04b..a56966c 100644 --- a/dotnet/src/HybridRow/Layouts/Layout.cs +++ b/src/Serialization/HybridRow/Layouts/Layout.cs @@ -2,9 +2,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ +#pragma warning disable CA1034 // Nested types should not be visible + namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts { - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -12,7 +13,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts using Microsoft.Azure.Cosmos.Core.Utf8; using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; - /// A Layout describes the structure of a Hybrd Row. + /// A Layout describes the structure of a Hybrid Row. /// /// A layout indicates the number, order, and type of all schematized columns to be stored /// within a hybrid row. The order and type of columns defines the physical ordering of bytes used to @@ -79,7 +80,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts public SchemaId SchemaId { get; } /// The set of top level columns defined in the layout (in left-to-right order). - public ReadOnlySpan Columns => this.topColumns.AsSpan(); + public ColumnView Columns => new ColumnView(this.topColumns); /// Minimum required size of a row of this layout. /// @@ -171,5 +172,65 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts return sb.ToString(); } + + /// Enumerates the columns of a . + public struct ColumnView + { + /// The list being enumerated. + private readonly LayoutColumn[] list; + + /// The next index to yield. + private int index; + + /// Initializes a new instance of the struct. + /// The list to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ColumnView(LayoutColumn[] list) + { + this.list = list; + this.index = -1; + } + + /// Gets an enumerator for this span. + public ColumnView GetEnumerator() + { + return new ColumnView(this.list); + } + + /// Advances the enumerator to the next element of the span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + int i = this.index + 1; + if (i < this.list.Length) + { + this.index = i; + return true; + } + + return false; + } + + /// Gets the element at the current position of the enumerator. + public LayoutColumn Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.list[this.index]; + } + + /// Gets the element at the position indicated. + public LayoutColumn this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.list[index]; + } + + /// Gets the number of elements. + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.list.Length; + } + } } } diff --git a/dotnet/src/HybridRow/Layouts/LayoutBit.cs b/src/Serialization/HybridRow/Layouts/LayoutBit.cs similarity index 97% rename from dotnet/src/HybridRow/Layouts/LayoutBit.cs rename to src/Serialization/HybridRow/Layouts/LayoutBit.cs index b4416f3..df0d14c 100644 --- a/dotnet/src/HybridRow/Layouts/LayoutBit.cs +++ b/src/Serialization/HybridRow/Layouts/LayoutBit.cs @@ -74,7 +74,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts public override bool Equals(object other) { - return other is LayoutBit && this.Equals((LayoutBit)other); + return other is LayoutBit layoutBit && this.Equals(layoutBit); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -85,7 +85,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts public override int GetHashCode() { - return this.index.GetHashCode(); + return HashCode.Combine(this.index); } /// Compute the division rounding up to the next whole number. diff --git a/dotnet/src/HybridRow/Layouts/LayoutBuilder.cs b/src/Serialization/HybridRow/Layouts/LayoutBuilder.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/LayoutBuilder.cs rename to src/Serialization/HybridRow/Layouts/LayoutBuilder.cs diff --git a/dotnet/src/HybridRow/Layouts/LayoutCode.cs b/src/Serialization/HybridRow/Layouts/LayoutCode.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/LayoutCode.cs rename to src/Serialization/HybridRow/Layouts/LayoutCode.cs diff --git a/dotnet/src/HybridRow/Layouts/LayoutCodeTraits.cs b/src/Serialization/HybridRow/Layouts/LayoutCodeTraits.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/LayoutCodeTraits.cs rename to src/Serialization/HybridRow/Layouts/LayoutCodeTraits.cs diff --git a/dotnet/src/HybridRow/Layouts/LayoutColumn.cs b/src/Serialization/HybridRow/Layouts/LayoutColumn.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/LayoutColumn.cs rename to src/Serialization/HybridRow/Layouts/LayoutColumn.cs diff --git a/dotnet/src/HybridRow/Layouts/LayoutCompilationException.cs b/src/Serialization/HybridRow/Layouts/LayoutCompilationException.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/LayoutCompilationException.cs rename to src/Serialization/HybridRow/Layouts/LayoutCompilationException.cs diff --git a/dotnet/src/HybridRow/Layouts/LayoutCompiler.cs b/src/Serialization/HybridRow/Layouts/LayoutCompiler.cs similarity index 76% rename from dotnet/src/HybridRow/Layouts/LayoutCompiler.cs rename to src/Serialization/HybridRow/Layouts/LayoutCompiler.cs index 67b6aa9..9292ce5 100644 --- a/dotnet/src/HybridRow/Layouts/LayoutCompiler.cs +++ b/src/Serialization/HybridRow/Layouts/LayoutCompiler.cs @@ -11,6 +11,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts /// Converts a logical schema into a physical layout. internal sealed class LayoutCompiler { + private const string BasePropertyName = "__base"; + /// Compiles a logical schema into a physical layout that can be used to read and write rows. /// The namespace within which is defined. /// The logical schema to produce a layout for. @@ -23,18 +25,50 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts Contract.Requires(!string.IsNullOrWhiteSpace(schema.Name)); Contract.Requires(ns.Schemas.Contains(schema)); + SchemaLanguageVersion v = schema.GetEffectiveSdlVersion(ns); LayoutBuilder builder = new LayoutBuilder(schema.Name, schema.SchemaId); - LayoutCompiler.AddProperties(builder, ns, LayoutCode.Schema, schema.Properties); + if (schema.BaseName != null) + { + LayoutCompiler.AddBase(builder, ns, schema); + } + LayoutCompiler.AddProperties(builder, v, ns, LayoutCode.Schema, schema.Properties); return builder.Build(); } - private static void AddProperties(LayoutBuilder builder, Namespace ns, LayoutCode scope, List properties) + private static void AddBase(LayoutBuilder builder, Namespace ns, Schema s) + { + Schema bs; + if (s.BaseSchemaId == SchemaId.Invalid) + { + bs = ns.Schemas.Find(q => q.Name == s.BaseName); + } + else + { + bs = ns.Schemas.Find(q => q.SchemaId == s.BaseSchemaId); + if (bs.Name != s.BaseName) + { + throw new LayoutCompilationException($"Ambiguous schema reference: '{s.BaseName}:{s.BaseSchemaId}'"); + } + } + + if (bs == null) + { + throw new LayoutCompilationException($"Cannot resolve schema reference '{s.BaseName}:{s.BaseSchemaId}'"); + } + + builder.AddTypedScope(LayoutCompiler.BasePropertyName, LayoutType.UDT, new TypeArgumentList(bs.SchemaId)); + } + + private static void AddProperties(LayoutBuilder builder, SchemaLanguageVersion v, Namespace ns, LayoutCode scope, List properties) { foreach (Property p in properties) { - TypeArgumentList typeArgs; - LayoutType type = LayoutCompiler.LogicalToPhysicalType(ns, p.PropertyType, out typeArgs); + if (p.PropertyType is null) + { + throw new LayoutCompilationException("Property missing type"); + } + LayoutType type = LayoutCompiler.LogicalToPhysicalType(v, ns, p.PropertyType, out TypeArgumentList typeArgs); switch (LayoutCodeTraits.ClearImmutableBit(type.LayoutCode)) { case LayoutCode.ObjectScope: @@ -46,7 +80,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts ObjectPropertyType op = (ObjectPropertyType)p.PropertyType; builder.AddObjectScope(p.Path, type); - LayoutCompiler.AddProperties(builder, ns, type.LayoutCode, op.Properties); + LayoutCompiler.AddProperties(builder, v, ns, type.LayoutCode, op.Properties); builder.EndObjectScope(); break; } @@ -79,9 +113,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts default: { - PrimitivePropertyType pp = p.PropertyType as PrimitivePropertyType; - if (pp != null) + if (p.PropertyType is PrimitivePropertyType pp) { + if ((pp.Type == TypeKind.Enum) && (v < SchemaLanguageVersion.V2)) + { + throw new LayoutCompilationException("Enums require SDL v2 or higher."); + } + switch (pp.Storage) { case StorageKind.Fixed: @@ -98,6 +136,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts builder.AddFixedColumn(p.Path, type, pp.Nullable, pp.Length); break; case StorageKind.Variable: + if (pp.Type == TypeKind.Enum) + { + throw new LayoutCompilationException($"Enums cannot have storage specification: {pp.Storage}"); + } + if (LayoutCodeTraits.ClearImmutableBit(scope) != LayoutCode.Schema) { throw new LayoutCompilationException("Cannot have variable storage within a sparse scope."); @@ -133,12 +176,200 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts } } - private static LayoutType LogicalToPhysicalType(Namespace ns, PropertyType logicalType, out TypeArgumentList typeArgs) + private static LayoutType LogicalToPhysicalType( + SchemaLanguageVersion v, + Namespace ns, + PropertyType logicalType, + out TypeArgumentList typeArgs) { typeArgs = TypeArgumentList.Empty; bool immutable = (logicalType as ScopePropertyType)?.Immutable ?? false; switch (logicalType.Type) + { + case TypeKind.Null: + case TypeKind.Boolean: + case TypeKind.Int8: + case TypeKind.Int16: + case TypeKind.Int32: + case TypeKind.Int64: + case TypeKind.UInt8: + case TypeKind.UInt16: + case TypeKind.UInt32: + case TypeKind.UInt64: + case TypeKind.Float32: + case TypeKind.Float64: + case TypeKind.Float128: + case TypeKind.Decimal: + case TypeKind.DateTime: + case TypeKind.UnixDateTime: + case TypeKind.Guid: + case TypeKind.MongoDbObjectId: + case TypeKind.Utf8: + case TypeKind.Binary: + case TypeKind.VarInt: + case TypeKind.VarUInt: + return LayoutCompiler.PrimitiveToPhysicalType(logicalType.Type); + + case TypeKind.Object: + return immutable ? LayoutType.ImmutableObject : LayoutType.Object; + case TypeKind.Array: + ArrayPropertyType ap = (ArrayPropertyType)logicalType; + if ((ap.Items != null) && (ap.Items.Type != TypeKind.Any)) + { + LayoutType itemType = LayoutCompiler.LogicalToPhysicalType(v, ns, ap.Items, out TypeArgumentList itemTypeArgs); + if (ap.Items.Nullable) + { + itemTypeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); + itemType = itemType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; + } + + typeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); + return immutable ? LayoutType.ImmutableTypedArray : LayoutType.TypedArray; + } + + return immutable ? LayoutType.ImmutableArray : LayoutType.Array; + case TypeKind.Set: + SetPropertyType sp = (SetPropertyType)logicalType; + if ((sp.Items != null) && (sp.Items.Type != TypeKind.Any)) + { + LayoutType itemType = LayoutCompiler.LogicalToPhysicalType(v, ns, sp.Items, out TypeArgumentList itemTypeArgs); + if (sp.Items.Nullable) + { + itemTypeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); + itemType = itemType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; + } + + typeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); + return immutable ? LayoutType.ImmutableTypedSet : LayoutType.TypedSet; + } + + // TODO(283638): implement sparse set. + throw new LayoutCompilationException($"Unknown property type: {logicalType.Type}"); + + case TypeKind.Map: + MapPropertyType mp = (MapPropertyType)logicalType; + if ((mp.Keys != null) && (mp.Keys.Type != TypeKind.Any) && (mp.Values != null) && (mp.Values.Type != TypeKind.Any)) + { + LayoutType keyType = LayoutCompiler.LogicalToPhysicalType(v, ns, mp.Keys, out TypeArgumentList keyTypeArgs); + if (mp.Keys.Nullable) + { + keyTypeArgs = new TypeArgumentList(new[] { new TypeArgument(keyType, keyTypeArgs) }); + keyType = keyType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; + } + + LayoutType valueType = LayoutCompiler.LogicalToPhysicalType(v, ns, mp.Values, out TypeArgumentList valueTypeArgs); + if (mp.Values.Nullable) + { + valueTypeArgs = new TypeArgumentList(new[] { new TypeArgument(valueType, valueTypeArgs) }); + valueType = valueType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; + } + + typeArgs = new TypeArgumentList(new[] { new TypeArgument(keyType, keyTypeArgs), new TypeArgument(valueType, valueTypeArgs) }); + return immutable ? LayoutType.ImmutableTypedMap : LayoutType.TypedMap; + } + + // TODO(283638): implement sparse map. + throw new LayoutCompilationException($"Unknown property type: {logicalType.Type}"); + + case TypeKind.Tuple: + TuplePropertyType tp = (TuplePropertyType)logicalType; + TypeArgument[] args = new TypeArgument[tp.Items.Count]; + for (int i = 0; i < tp.Items.Count; i++) + { + LayoutType itemType = LayoutCompiler.LogicalToPhysicalType(v, ns, tp.Items[i], out TypeArgumentList itemTypeArgs); + if (tp.Items[i].Nullable) + { + itemTypeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); + itemType = itemType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; + } + + args[i] = new TypeArgument(itemType, itemTypeArgs); + } + + typeArgs = new TypeArgumentList(args); + return immutable ? LayoutType.ImmutableTypedTuple : LayoutType.TypedTuple; + + case TypeKind.Tagged: + TaggedPropertyType tg = (TaggedPropertyType)logicalType; + if ((tg.Items.Count < TaggedPropertyType.MinTaggedArguments) || (tg.Items.Count > TaggedPropertyType.MaxTaggedArguments)) + { + throw new LayoutCompilationException( + $"Invalid number of arguments in Tagged: {TaggedPropertyType.MinTaggedArguments} <= {tg.Items.Count} <= {TaggedPropertyType.MaxTaggedArguments}"); + } + + TypeArgument[] tgArgs = new TypeArgument[tg.Items.Count + 1]; + tgArgs[0] = new TypeArgument(LayoutType.UInt8, TypeArgumentList.Empty); + for (int i = 0; i < tg.Items.Count; i++) + { + LayoutType itemType = LayoutCompiler.LogicalToPhysicalType(v, ns, tg.Items[i], out TypeArgumentList itemTypeArgs); + if (tg.Items[i].Nullable) + { + itemTypeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); + itemType = itemType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; + } + + tgArgs[i + 1] = new TypeArgument(itemType, itemTypeArgs); + } + + typeArgs = new TypeArgumentList(tgArgs); + switch (tg.Items.Count) + { + case 1: + return immutable ? LayoutType.ImmutableTagged : LayoutType.Tagged; + case 2: + return immutable ? LayoutType.ImmutableTagged2 : LayoutType.Tagged2; + default: + throw new LayoutCompilationException("Unexpected tagged arity"); + } + + case TypeKind.Schema: + UdtPropertyType up = (UdtPropertyType)logicalType; + Schema udtSchema; + if (up.SchemaId == SchemaId.Invalid) + { + udtSchema = ns.Schemas.Find(s => s.Name == up.Name); + } + else + { + udtSchema = ns.Schemas.Find(s => s.SchemaId == up.SchemaId); + if (udtSchema.Name != up.Name) + { + throw new LayoutCompilationException($"Ambiguous schema reference: '{up.Name}:{up.SchemaId}'"); + } + } + + if (udtSchema == null) + { + throw new LayoutCompilationException($"Cannot resolve schema reference '{up.Name}:{up.SchemaId}'"); + } + + typeArgs = new TypeArgumentList(udtSchema.SchemaId); + return immutable ? LayoutType.ImmutableUDT : LayoutType.UDT; + + case TypeKind.Enum: + if (v < SchemaLanguageVersion.V2) + { + throw new LayoutCompilationException("Enums require SDL v2 or higher."); + } + + PrimitivePropertyType ep = (PrimitivePropertyType)logicalType; + EnumSchema enumSchema = ns.Enums.Find(es => es.Name == ep.Enum); + if (enumSchema == null) + { + throw new LayoutCompilationException($"Cannot resolve enum schema reference '{ep.Enum}'"); + } + + return LayoutCompiler.PrimitiveToPhysicalType(enumSchema.Type); + + default: + throw new LayoutCompilationException($"Unknown property type: {logicalType.Type}"); + } + } + + private static LayoutType PrimitiveToPhysicalType(TypeKind type) + { + switch (type) { case TypeKind.Null: return LayoutType.Null; @@ -184,151 +415,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts return LayoutType.VarInt; case TypeKind.VarUInt: return LayoutType.VarUInt; - - case TypeKind.Object: - return immutable ? LayoutType.ImmutableObject : LayoutType.Object; - case TypeKind.Array: - ArrayPropertyType ap = (ArrayPropertyType)logicalType; - if ((ap.Items != null) && (ap.Items.Type != TypeKind.Any)) - { - TypeArgumentList itemTypeArgs; - LayoutType itemType = LayoutCompiler.LogicalToPhysicalType(ns, ap.Items, out itemTypeArgs); - if (ap.Items.Nullable) - { - itemTypeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); - itemType = itemType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; - } - - typeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); - return immutable ? LayoutType.ImmutableTypedArray : LayoutType.TypedArray; - } - - return immutable ? LayoutType.ImmutableArray : LayoutType.Array; - case TypeKind.Set: - SetPropertyType sp = (SetPropertyType)logicalType; - if ((sp.Items != null) && (sp.Items.Type != TypeKind.Any)) - { - TypeArgumentList itemTypeArgs; - LayoutType itemType = LayoutCompiler.LogicalToPhysicalType(ns, sp.Items, out itemTypeArgs); - if (sp.Items.Nullable) - { - itemTypeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); - itemType = itemType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; - } - - typeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); - return immutable ? LayoutType.ImmutableTypedSet : LayoutType.TypedSet; - } - - // TODO(283638): implement sparse set. - throw new LayoutCompilationException($"Unknown property type: {logicalType.Type}"); - - case TypeKind.Map: - MapPropertyType mp = (MapPropertyType)logicalType; - if ((mp.Keys != null) && (mp.Keys.Type != TypeKind.Any) && (mp.Values != null) && (mp.Values.Type != TypeKind.Any)) - { - TypeArgumentList keyTypeArgs; - LayoutType keyType = LayoutCompiler.LogicalToPhysicalType(ns, mp.Keys, out keyTypeArgs); - if (mp.Keys.Nullable) - { - keyTypeArgs = new TypeArgumentList(new[] { new TypeArgument(keyType, keyTypeArgs) }); - keyType = keyType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; - } - - TypeArgumentList valueTypeArgs; - LayoutType valueType = LayoutCompiler.LogicalToPhysicalType(ns, mp.Values, out valueTypeArgs); - if (mp.Values.Nullable) - { - valueTypeArgs = new TypeArgumentList(new[] { new TypeArgument(valueType, valueTypeArgs) }); - valueType = valueType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; - } - - typeArgs = new TypeArgumentList(new[] { new TypeArgument(keyType, keyTypeArgs), new TypeArgument(valueType, valueTypeArgs) }); - return immutable ? LayoutType.ImmutableTypedMap : LayoutType.TypedMap; - } - - // TODO(283638): implement sparse map. - throw new LayoutCompilationException($"Unknown property type: {logicalType.Type}"); - - case TypeKind.Tuple: - TuplePropertyType tp = (TuplePropertyType)logicalType; - TypeArgument[] args = new TypeArgument[tp.Items.Count]; - for (int i = 0; i < tp.Items.Count; i++) - { - TypeArgumentList itemTypeArgs; - LayoutType itemType = LayoutCompiler.LogicalToPhysicalType(ns, tp.Items[i], out itemTypeArgs); - if (tp.Items[i].Nullable) - { - itemTypeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); - itemType = itemType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; - } - - args[i] = new TypeArgument(itemType, itemTypeArgs); - } - - typeArgs = new TypeArgumentList(args); - return immutable ? LayoutType.ImmutableTypedTuple : LayoutType.TypedTuple; - - case TypeKind.Tagged: - TaggedPropertyType tg = (TaggedPropertyType)logicalType; - if ((tg.Items.Count < TaggedPropertyType.MinTaggedArguments) || (tg.Items.Count > TaggedPropertyType.MaxTaggedArguments)) - { - throw new LayoutCompilationException( - $"Invalid number of arguments in Tagged: {TaggedPropertyType.MinTaggedArguments} <= {tg.Items.Count} <= {TaggedPropertyType.MaxTaggedArguments}"); - } - - TypeArgument[] tgArgs = new TypeArgument[tg.Items.Count + 1]; - tgArgs[0] = new TypeArgument(LayoutType.UInt8, TypeArgumentList.Empty); - for (int i = 0; i < tg.Items.Count; i++) - { - TypeArgumentList itemTypeArgs; - LayoutType itemType = LayoutCompiler.LogicalToPhysicalType(ns, tg.Items[i], out itemTypeArgs); - if (tg.Items[i].Nullable) - { - itemTypeArgs = new TypeArgumentList(new[] { new TypeArgument(itemType, itemTypeArgs) }); - itemType = itemType.Immutable ? LayoutType.ImmutableNullable : LayoutType.Nullable; - } - - tgArgs[i + 1] = new TypeArgument(itemType, itemTypeArgs); - } - - typeArgs = new TypeArgumentList(tgArgs); - switch (tg.Items.Count) - { - case 1: - return immutable ? LayoutType.ImmutableTagged : LayoutType.Tagged; - case 2: - return immutable ? LayoutType.ImmutableTagged2 : LayoutType.Tagged2; - default: - throw new LayoutCompilationException("Unexpected tagged arity"); - } - - case TypeKind.Schema: - UdtPropertyType up = (UdtPropertyType)logicalType; - Schema udtSchema; - if (up.SchemaId == SchemaId.Invalid) - { - udtSchema = ns.Schemas.Find(s => s.Name == up.Name); - } - else - { - udtSchema = ns.Schemas.Find(s => s.SchemaId == up.SchemaId); - if (udtSchema.Name != up.Name) - { - throw new LayoutCompilationException($"Ambiguous schema reference: '{up.Name}:{up.SchemaId}'"); - } - } - - if (udtSchema == null) - { - throw new LayoutCompilationException($"Cannot resolve schema reference '{up.Name}:{up.SchemaId}'"); - } - - typeArgs = new TypeArgumentList(udtSchema.SchemaId); - return immutable ? LayoutType.ImmutableUDT : LayoutType.UDT; - default: - throw new LayoutCompilationException($"Unknown property type: {logicalType.Type}"); + throw new LayoutCompilationException($"Unknown property type: {type}"); } } } diff --git a/dotnet/src/HybridRow/Layouts/LayoutResolver.cs b/src/Serialization/HybridRow/Layouts/LayoutResolver.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/LayoutResolver.cs rename to src/Serialization/HybridRow/Layouts/LayoutResolver.cs diff --git a/dotnet/src/HybridRow/Layouts/LayoutResolverNamespace.cs b/src/Serialization/HybridRow/Layouts/LayoutResolverNamespace.cs similarity index 94% rename from dotnet/src/HybridRow/Layouts/LayoutResolverNamespace.cs rename to src/Serialization/HybridRow/Layouts/LayoutResolverNamespace.cs index 6d375a1..55ecc62 100644 --- a/dotnet/src/HybridRow/Layouts/LayoutResolverNamespace.cs +++ b/src/Serialization/HybridRow/Layouts/LayoutResolverNamespace.cs @@ -52,8 +52,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts layout = this.parent?.Resolve(schemaId); if (layout != null) { - bool succeeded = this.layoutCache.TryAdd(schemaId.Id, layout); - Contract.Assert(succeeded); + this.layoutCache.TryAdd(schemaId.Id, layout); return layout; } diff --git a/dotnet/src/HybridRow/Layouts/LayoutResolverSimple.cs b/src/Serialization/HybridRow/Layouts/LayoutResolverSimple.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/LayoutResolverSimple.cs rename to src/Serialization/HybridRow/Layouts/LayoutResolverSimple.cs diff --git a/dotnet/src/HybridRow/Layouts/LayoutType.cs b/src/Serialization/HybridRow/Layouts/LayoutType.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/LayoutType.cs rename to src/Serialization/HybridRow/Layouts/LayoutType.cs diff --git a/dotnet/src/HybridRow/Layouts/SamplingStringComparer.cs b/src/Serialization/HybridRow/Layouts/SamplingStringComparer.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/SamplingStringComparer.cs rename to src/Serialization/HybridRow/Layouts/SamplingStringComparer.cs diff --git a/dotnet/src/HybridRow/Layouts/SamplingUtf8StringComparer.cs b/src/Serialization/HybridRow/Layouts/SamplingUtf8StringComparer.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/SamplingUtf8StringComparer.cs rename to src/Serialization/HybridRow/Layouts/SamplingUtf8StringComparer.cs diff --git a/dotnet/src/HybridRow/Layouts/StringTokenizer.cs b/src/Serialization/HybridRow/Layouts/StringTokenizer.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/StringTokenizer.cs rename to src/Serialization/HybridRow/Layouts/StringTokenizer.cs diff --git a/src/Serialization/HybridRow/Layouts/SystemSchema.cs b/src/Serialization/HybridRow/Layouts/SystemSchema.cs new file mode 100644 index 0000000..cd1346e --- /dev/null +++ b/src/Serialization/HybridRow/Layouts/SystemSchema.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts +{ + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + + public static class SystemSchema + { + /// + /// SchemaId of the empty schema. This schema has no defined cells but can accomodate + /// unschematized sparse content. + /// + public static readonly SchemaId EmptySchemaId = new SchemaId(2147473650); + + /// Returns a copy of the system namespace layout. + public static LayoutResolver LayoutResolver => SchemasHrSchema.LayoutResolver; + + /// Returns a copy of the system namespace. + public static Namespace GetNamespace() => SchemasHrSchema.Namespace; + } +} diff --git a/dotnet/src/HybridRow/Layouts/TypeArgument.cs b/src/Serialization/HybridRow/Layouts/TypeArgument.cs similarity index 96% rename from dotnet/src/HybridRow/Layouts/TypeArgument.cs rename to src/Serialization/HybridRow/Layouts/TypeArgument.cs index 5ad9af4..ad2ff0d 100644 --- a/dotnet/src/HybridRow/Layouts/TypeArgument.cs +++ b/src/Serialization/HybridRow/Layouts/TypeArgument.cs @@ -71,11 +71,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts public override bool Equals(object obj) { - if (object.ReferenceEquals(null, obj)) - { - return false; - } - if (obj is TypeArgument ota) { return this.Equals(ota); diff --git a/dotnet/src/HybridRow/Layouts/TypeArgumentList.cs b/src/Serialization/HybridRow/Layouts/TypeArgumentList.cs similarity index 93% rename from dotnet/src/HybridRow/Layouts/TypeArgumentList.cs rename to src/Serialization/HybridRow/Layouts/TypeArgumentList.cs index d46991a..78a1216 100644 --- a/dotnet/src/HybridRow/Layouts/TypeArgumentList.cs +++ b/src/Serialization/HybridRow/Layouts/TypeArgumentList.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts { using System; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using Microsoft.Azure.Cosmos.Core; @@ -45,6 +46,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts public TypeArgument this[int i] => this.args[i]; + [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Constructor")] + public static implicit operator TypeArgumentList(SchemaId schemaId) + { + return new TypeArgumentList(schemaId); + } + public static bool operator ==(TypeArgumentList left, TypeArgumentList right) { return left.Equals(right); @@ -65,7 +72,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts { if (this.schemaId != SchemaId.Invalid) { - return $"<{this.schemaId.ToString()}>"; + return $"<{this.schemaId}>"; } if (this.args == null || this.args?.Length == 0) @@ -78,11 +85,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts public override bool Equals(object obj) { - if (object.ReferenceEquals(null, obj)) - { - return false; - } - if (obj is TypeArgumentList ota) { return this.Equals(ota); diff --git a/dotnet/src/HybridRow/Layouts/UpdateOptions.cs b/src/Serialization/HybridRow/Layouts/UpdateOptions.cs similarity index 100% rename from dotnet/src/HybridRow/Layouts/UpdateOptions.cs rename to src/Serialization/HybridRow/Layouts/UpdateOptions.cs diff --git a/dotnet/src/HybridRow/MemorySpanResizer.cs b/src/Serialization/HybridRow/MemorySpanResizer.cs similarity index 100% rename from dotnet/src/HybridRow/MemorySpanResizer.cs rename to src/Serialization/HybridRow/MemorySpanResizer.cs diff --git a/dotnet/src/HybridRow/Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj b/src/Serialization/HybridRow/Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj similarity index 52% rename from dotnet/src/HybridRow/Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj rename to src/Serialization/HybridRow/Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj index 2ef68aa..2d76dbd 100644 --- a/dotnet/src/HybridRow/Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj +++ b/src/Serialization/HybridRow/Microsoft.Azure.Cosmos.Serialization.HybridRow.csproj @@ -7,25 +7,28 @@ Library Microsoft.Azure.Cosmos.Serialization.HybridRow Microsoft.Azure.Cosmos.Serialization.HybridRow - netstandard2.0 + netstandard2.1 AnyCPU - - - - - - + + + + + + - + - + + + + \ No newline at end of file diff --git a/dotnet/src/HybridRow/MongoDBObjectId.cs b/src/Serialization/HybridRow/MongoDBObjectId.cs similarity index 94% rename from dotnet/src/HybridRow/MongoDBObjectId.cs rename to src/Serialization/HybridRow/MongoDBObjectId.cs index 4daeb3c..4da1359 100644 --- a/dotnet/src/HybridRow/MongoDBObjectId.cs +++ b/src/Serialization/HybridRow/MongoDBObjectId.cs @@ -2,6 +2,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ +#pragma warning disable IDE0041 // Use 'is null' check + namespace Microsoft.Azure.Cosmos.Serialization.HybridRow { using System; @@ -39,10 +41,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow Contract.Requires(src.Length == MongoDbObjectId.Size); fixed (byte* p = this.data) - fixed (byte* q = src) { - *(ulong*)&p[0] = *(ulong*)&q[0]; - *(uint*)&p[8] = *(uint*)&q[8]; + fixed (byte* q = src) + { + *(ulong*)&p[0] = *(ulong*)&q[0]; + *(uint*)&p[8] = *(uint*)&q[8]; + } } } @@ -83,7 +87,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow return false; } - return obj is MongoDbObjectId && this.Equals((MongoDbObjectId)obj); + return obj is MongoDbObjectId id && this.Equals(id); } /// overload. diff --git a/dotnet/src/HybridRow/NullValue.cs b/src/Serialization/HybridRow/NullValue.cs similarity index 87% rename from dotnet/src/HybridRow/NullValue.cs rename to src/Serialization/HybridRow/NullValue.cs index 1ff40e4..4cd9f12 100644 --- a/dotnet/src/HybridRow/NullValue.cs +++ b/src/Serialization/HybridRow/NullValue.cs @@ -15,7 +15,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow { /// The default null literal. /// This is the same value as default(). - public static readonly NullValue Default = default(NullValue); + public static readonly NullValue Default = default; /// Operator == overload. public static bool operator ==(NullValue left, NullValue right) @@ -40,12 +40,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow /// overload. public override bool Equals(object obj) { - if (object.ReferenceEquals(null, obj)) - { - return false; - } - - return obj is NullValue && this.Equals((NullValue)obj); + return obj is NullValue value && this.Equals(value); } /// overload. diff --git a/src/Serialization/HybridRow/Properties/AssemblyInfo.cs b/src/Serialization/HybridRow/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5bd9f74 --- /dev/null +++ b/src/Serialization/HybridRow/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +using System.Runtime.CompilerServices; + +// Allow tests to see internals. +[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Unit")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Serialization.HybridRow.Tests.Perf")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Serialization.HybridRowStress")] diff --git a/dotnet/src/HybridRow/RecordIO/Record.cs b/src/Serialization/HybridRow/RecordIO/Record.cs similarity index 100% rename from dotnet/src/HybridRow/RecordIO/Record.cs rename to src/Serialization/HybridRow/RecordIO/Record.cs diff --git a/dotnet/src/HybridRow/RecordIO/RecordIOFormatter.cs b/src/Serialization/HybridRow/RecordIO/RecordIOFormatter.cs similarity index 62% rename from dotnet/src/HybridRow/RecordIO/RecordIOFormatter.cs rename to src/Serialization/HybridRow/RecordIO/RecordIOFormatter.cs index 0fc6576..5524099 100644 --- a/dotnet/src/HybridRow/RecordIO/RecordIOFormatter.cs +++ b/src/Serialization/HybridRow/RecordIO/RecordIOFormatter.cs @@ -5,14 +5,16 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO { using System; + using System.Runtime.CompilerServices; 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; public static class RecordIOFormatter { - internal static readonly Layout SegmentLayout = SystemSchema.LayoutResolver.Resolve(SystemSchema.SegmentSchemaId); - internal static readonly Layout RecordLayout = SystemSchema.LayoutResolver.Resolve(SystemSchema.RecordSchemaId); + private static readonly Layout SegmentLayout = SchemasHrSchema.LayoutResolver.Resolve((SchemaId)SegmentHybridRowSerializer.SchemaId); + private static readonly Layout RecordLayout = SchemasHrSchema.LayoutResolver.Resolve((SchemaId)RecordHybridRowSerializer.SchemaId); public static Result FormatSegment(Segment segment, out RowBuffer row, ISpanResizer resizer = default) { @@ -20,7 +22,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO int estimatedSize = HybridRowHeader.Size + RecordIOFormatter.SegmentLayout.Size + segment.Comment?.Length ?? 0 + segment.SDL?.Length ?? 0 + 20; - return RecordIOFormatter.FormatObject(resizer, estimatedSize, RecordIOFormatter.SegmentLayout, segment, SegmentSerializer.Write, out row); + return RecordIOFormatter.FormatObject( + resizer, estimatedSize, RecordIOFormatter.SegmentLayout, segment, out row); } public static Result FormatRecord(ReadOnlyMemory body, out RowBuffer row, ISpanResizer resizer = default) @@ -29,20 +32,23 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO int estimatedSize = HybridRowHeader.Size + RecordIOFormatter.RecordLayout.Size + body.Length; uint crc32 = Crc32.Update(0, body.Span); Record record = new Record(body.Length, crc32); - return RecordIOFormatter.FormatObject(resizer, estimatedSize, RecordIOFormatter.RecordLayout, record, RecordSerializer.Write, out row); + return RecordIOFormatter.FormatObject( + resizer, estimatedSize, RecordIOFormatter.RecordLayout, record, out row); } - private static Result FormatObject( + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Result FormatObject( ISpanResizer resizer, int initialCapacity, Layout layout, T obj, - RowWriter.WriterFunc writer, out RowBuffer row) + where TSerializer : struct, IHybridRowSerializer { row = new RowBuffer(initialCapacity, resizer); row.InitLayout(HybridRowVersion.V1, layout, SystemSchema.LayoutResolver); - Result r = RowWriter.WriteBuffer(ref row, obj, writer); + RowCursor root = RowCursor.Create(ref row); + Result r = default(TSerializer).Write(ref row, ref root, true, default, obj); if (r != Result.Success) { row = default; diff --git a/dotnet/src/HybridRow/RecordIO/RecordIOParser.cs b/src/Serialization/HybridRow/RecordIO/RecordIOParser.cs similarity index 69% rename from dotnet/src/HybridRow/RecordIO/RecordIOParser.cs rename to src/Serialization/HybridRow/RecordIO/RecordIOParser.cs index 30e46ae..fe10f0d 100644 --- a/dotnet/src/HybridRow/RecordIO/RecordIOParser.cs +++ b/src/Serialization/HybridRow/RecordIO/RecordIOParser.cs @@ -7,8 +7,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO using System; using System.Runtime.InteropServices; 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; public struct RecordIOParser { @@ -55,6 +54,15 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO } } + /// Processes one buffers worth of data possibly advancing the parser state. + [Obsolete("Use ReadOnlyMemory override instead.")] + public Result Process(ReadOnlyMemory buffer, out ProductionType type, out Memory record, out int need, out int consumed) + { + Result r = this.Process(buffer, out type, out ReadOnlyMemory rom, out need, out consumed); + record = MemoryMarshal.AsMemory(rom); + return r; + } + /// Processes one buffers worth of data possibly advancing the parser state. /// The buffer to consume. /// Indicates the type of Hybrid Row produced in . @@ -74,10 +82,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO /// during parsing. /// /// > - public Result Process(Memory buffer, out ProductionType type, out Memory record, out int need, out int consumed) + public Result Process(ReadOnlyMemory buffer, out ProductionType type, out ReadOnlyMemory record, out int need, out int consumed) { Result r = Result.Failure; - Memory b = buffer; + ReadOnlyMemory b = buffer; type = ProductionType.None; record = default; switch (this.state) @@ -90,7 +98,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO case State.NeedSegmentLength: { - int minimalSegmentRowSize = HybridRowHeader.Size + RecordIOFormatter.SegmentLayout.Size; + int minimalSegmentRowSize = HybridRowHeader.Size + SegmentHybridRowSerializer.Size; if (b.Length < minimalSegmentRowSize) { need = minimalSegmentRowSize; @@ -98,10 +106,15 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO return Result.InsufficientBuffer; } - Span span = b.Span.Slice(0, minimalSegmentRowSize); - RowBuffer row = new RowBuffer(span, HybridRowVersion.V1, SystemSchema.LayoutResolver); - RowReader reader = new RowReader(ref row); - r = SegmentSerializer.Read(ref reader, out this.segment); + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + ReadOnlyMemory mem = b.Slice(0, minimalSegmentRowSize); + RowBuffer row = new RowBuffer(MemoryMarshal.AsMemory(mem).Span, + HybridRowVersion.V1, SchemasHrSchema.LayoutResolver); + RowCursor root = RowCursor.Create(ref row); + r = default(SegmentHybridRowSerializer).Read(ref row, ref root, true, out this.segment); if (r != Result.Success) { break; @@ -120,17 +133,22 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO return Result.InsufficientBuffer; } - Span span = b.Span.Slice(0, this.segment.Length); - RowBuffer row = new RowBuffer(span, HybridRowVersion.V1, SystemSchema.LayoutResolver); - RowReader reader = new RowReader(ref row); - r = SegmentSerializer.Read(ref reader, out this.segment); + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + ReadOnlyMemory mem = b.Slice(0, this.segment.Length); + RowBuffer row = new RowBuffer(MemoryMarshal.AsMemory(mem).Span, + HybridRowVersion.V1, SchemasHrSchema.LayoutResolver); + RowCursor root = RowCursor.Create(ref row); + r = default(SegmentHybridRowSerializer).Read(ref row, ref root, true, out this.segment); if (r != Result.Success) { break; } - record = b.Slice(0, span.Length); - b = b.Slice(span.Length); + record = b.Slice(0, mem.Length); + b = b.Slice(mem.Length); need = 0; this.state = State.NeedHeader; consumed = buffer.Length - b.Length; @@ -154,12 +172,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO break; } - if (header.SchemaId == SystemSchema.SegmentSchemaId) + if (header.SchemaId == (SchemaId)SegmentHybridRowSerializer.SchemaId) { goto case State.NeedSegment; } - if (header.SchemaId == SystemSchema.RecordSchemaId) + if (header.SchemaId == (SchemaId)RecordHybridRowSerializer.SchemaId) { goto case State.NeedRecord; } @@ -170,7 +188,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO case State.NeedRecord: { - int minimalRecordRowSize = HybridRowHeader.Size + RecordIOFormatter.RecordLayout.Size; + int minimalRecordRowSize = HybridRowHeader.Size + RecordHybridRowSerializer.Size; if (b.Length < minimalRecordRowSize) { need = minimalRecordRowSize; @@ -178,16 +196,21 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO return Result.InsufficientBuffer; } - Span span = b.Span.Slice(0, minimalRecordRowSize); - RowBuffer row = new RowBuffer(span, HybridRowVersion.V1, SystemSchema.LayoutResolver); - RowReader reader = new RowReader(ref row); - r = RecordSerializer.Read(ref reader, out this.record); + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + ReadOnlyMemory mem = b.Slice(0, minimalRecordRowSize); + RowBuffer row = new RowBuffer(MemoryMarshal.AsMemory(mem).Span, + HybridRowVersion.V1, SchemasHrSchema.LayoutResolver); + RowCursor root = RowCursor.Create(ref row); + r = default(RecordHybridRowSerializer).Read(ref row, ref root, true, out this.record); if (r != Result.Success) { break; } - b = b.Slice(span.Length); + b = b.Slice(mem.Length); this.state = State.NeedRow; goto case State.NeedRow; } diff --git a/dotnet/src/HybridRow/RecordIO/RecordIOStream.cs b/src/Serialization/HybridRow/RecordIO/RecordIOStream.cs similarity index 87% rename from dotnet/src/HybridRow/RecordIO/RecordIOStream.cs rename to src/Serialization/HybridRow/RecordIO/RecordIOStream.cs index 2c6fc99..3e23b91 100644 --- a/dotnet/src/HybridRow/RecordIO/RecordIOStream.cs +++ b/src/Serialization/HybridRow/RecordIO/RecordIOStream.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO { using System; using System.IO; + using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core; @@ -31,7 +32,34 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO /// A tuple with: Success if the body was produced without error, the error code otherwise. /// And, the byte sequence of the record body's row buffer. /// - public delegate ValueTask<(Result, ReadOnlyMemory)> ProduceFuncAsync(long index); + public delegate ValueTask<(Result Result, ReadOnlyMemory Buffer)> ProduceFuncAsync(long index); + + /// Reads an entire RecordIO stream. + [Obsolete("Use ReadOnlyMemory override instead.")] + public static Task ReadRecordIOAsync( + this Stream stm, + Func, Result> visitRecord, + Func, Result> visitSegment = default, + MemorySpanResizer resizer = default) + { + return stm.ReadRecordIOAsync( + rom => visitRecord(MemoryMarshal.AsMemory(rom)), + (visitSegment != null) ? rom => visitSegment(MemoryMarshal.AsMemory(rom)) : default(Func, Result>), + resizer); + } + + /// Reads an entire RecordIO stream. + public static Task ReadRecordIOAsync( + this Stream stm, + Func, Result> visitRecord, + Func, Result> visitSegment = default, + MemorySpanResizer resizer = default) + { + return stm.ReadRecordIOAsync( + rom => new ValueTask(visitRecord(rom)), + (visitSegment != null) ? rom => new ValueTask(visitSegment(rom)) : default(Func, ValueTask>), + resizer); + } /// Reads an entire RecordIO stream. /// The stream to read from. @@ -59,8 +87,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO /// Success if the stream is parsed without error, the error code otherwise. public static async Task ReadRecordIOAsync( this Stream stm, - Func, Result> visitRecord, - Func, Result> visitSegment = default, + Func, ValueTask> visitRecord, + Func, ValueTask> visitSegment = default, MemorySpanResizer resizer = default) { Contract.Requires(stm != null); @@ -100,7 +128,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO Result r = parser.Process( avail, out RecordIOParser.ProductionType prodType, - out Memory record, + out ReadOnlyMemory record, out need, out int consumed); @@ -133,10 +161,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO if (prodType == RecordIOParser.ProductionType.Segment) { Contract.Assert(!record.IsEmpty); - r = visitSegment?.Invoke(record) ?? Result.Success; - if (r != Result.Success) + if (visitSegment != null) { - return r; + r = await visitSegment(record); + if (r != Result.Success) + { + return r; + } } } @@ -145,7 +176,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO { Contract.Assert(!record.IsEmpty); - r = visitRecord(record); + r = await visitRecord(record); if (r != Result.Success) { return r; diff --git a/dotnet/src/HybridRow/RecordIO/Segment.cs b/src/Serialization/HybridRow/RecordIO/Segment.cs similarity index 60% rename from dotnet/src/HybridRow/RecordIO/Segment.cs rename to src/Serialization/HybridRow/RecordIO/Segment.cs index 7bc5982..17d4ecd 100644 --- a/dotnet/src/HybridRow/RecordIO/Segment.cs +++ b/src/Serialization/HybridRow/RecordIO/Segment.cs @@ -6,17 +6,31 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO { + using System; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + public struct Segment { public int Length; public string Comment; public string SDL; + public Namespace Schema; + [Obsolete("Use object-model constructor instead.")] public Segment(string comment, string sdl) { this.Length = 0; this.Comment = comment; this.SDL = sdl; + this.Schema = null; + } + + public Segment(string comment, Namespace ns) + { + this.Length = 0; + this.Comment = comment; + this.SDL = null; + this.Schema = ns; } } } diff --git a/dotnet/src/HybridRow/Result.cs b/src/Serialization/HybridRow/Result.cs similarity index 100% rename from dotnet/src/HybridRow/Result.cs rename to src/Serialization/HybridRow/Result.cs diff --git a/dotnet/src/HybridRow/RowBuffer.cs b/src/Serialization/HybridRow/RowBuffer.cs similarity index 97% rename from dotnet/src/HybridRow/RowBuffer.cs rename to src/Serialization/HybridRow/RowBuffer.cs index 5f9ee02..f7b6b34 100644 --- a/dotnet/src/HybridRow/RowBuffer.cs +++ b/src/Serialization/HybridRow/RowBuffer.cs @@ -45,10 +45,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow } /// Initializes a new instance of the struct from an existing buffer. - /// - /// The buffer. The row takes ownership of the buffer and the caller should not - /// maintain a pointer or mutate the buffer after this call returns. - /// + /// The buffer. /// The version of the Hybrid Row format to used to encoding the buffer. /// The resolver for UDTs. /// Optional memory resizer. @@ -73,10 +70,16 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow /// The length of row in bytes. public int Length => this.length; - /// The length of row in bytes. + /// The full encoded content of the row. public byte[] ToArray() { - return this.buffer.Slice(0, this.length).ToArray(); + return this.AsSpan().ToArray(); + } + + /// The full encoded content of the row. + public ReadOnlySpan AsSpan() + { + return this.buffer.Slice(0, this.length); } /// The resolver for UDTs. @@ -196,6 +199,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal void SetBit(int offset, LayoutBit bit) { + // If the bit to be read is itself undefined, then return true. This is used to + // short-circuit the non-nullable vs. nullable field cases. For nullable fields + // the bit indicates the presence bit to be read for the field. For non-nullable + // fields there is no presence bit, and so "undefined" is passed and true is always + // returned indicating the field *is* present (as non-nullable fields are ALWAYS + // present). if (bit.IsInvalid) { return; @@ -212,6 +221,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal bool ReadBit(int offset, LayoutBit bit) { + // If the bit to be read is itself undefined, then return true. This is used to + // short-circuit the non-nullable vs. nullable field cases. For nullable fields + // the bit indicates the presence bit to be read for the field. For non-nullable + // fields there is no presence bit, and so "undefined" is passed and true is always + // returned indicating the field *is* present (as non-nullable fields are ALWAYS + // present). if (bit.IsInvalid) { return true; @@ -574,8 +589,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal sbyte ReadSparseInt8(ref RowCursor edit) { - // TODO: Remove calls to ReadSparsePrimitiveTypeCode once moved to V2 read. - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Int8); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Int8); edit.endOffset = edit.valueOffset + sizeof(sbyte); return this.ReadInt8(edit.valueOffset); } @@ -602,7 +616,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal short ReadSparseInt16(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Int16); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Int16); edit.endOffset = edit.valueOffset + sizeof(short); return this.ReadInt16(edit.valueOffset); } @@ -628,7 +642,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal int ReadSparseInt32(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Int32); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Int32); edit.endOffset = edit.valueOffset + sizeof(int); return this.ReadInt32(edit.valueOffset); } @@ -654,7 +668,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal long ReadSparseInt64(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Int64); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Int64); edit.endOffset = edit.valueOffset + sizeof(long); return this.ReadInt64(edit.valueOffset); } @@ -680,7 +694,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal byte ReadSparseUInt8(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.UInt8); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.UInt8); edit.endOffset = edit.valueOffset + sizeof(byte); return this.ReadUInt8(edit.valueOffset); } @@ -706,7 +720,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal ushort ReadSparseUInt16(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.UInt16); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.UInt16); edit.endOffset = edit.valueOffset + sizeof(ushort); return this.ReadUInt16(edit.valueOffset); } @@ -732,7 +746,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal uint ReadSparseUInt32(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.UInt32); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.UInt32); edit.endOffset = edit.valueOffset + sizeof(uint); return this.ReadUInt32(edit.valueOffset); } @@ -758,7 +772,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal ulong ReadSparseUInt64(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.UInt64); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.UInt64); edit.endOffset = edit.valueOffset + sizeof(ulong); return this.ReadUInt64(edit.valueOffset); } @@ -784,7 +798,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal long ReadSparseVarInt(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.VarInt); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.VarInt); long value = this.Read7BitEncodedInt(edit.valueOffset, out int sizeLenInBytes); edit.endOffset = edit.valueOffset + sizeLenInBytes; return value; @@ -812,7 +826,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal ulong ReadSparseVarUInt(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.VarUInt); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.VarUInt); ulong value = this.Read7BitEncodedUInt(edit.valueOffset, out int sizeLenInBytes); edit.endOffset = edit.valueOffset + sizeLenInBytes; return value; @@ -840,7 +854,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal float ReadSparseFloat32(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Float32); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Float32); edit.endOffset = edit.valueOffset + sizeof(float); return this.ReadFloat32(edit.valueOffset); } @@ -866,7 +880,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal double ReadSparseFloat64(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Float64); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Float64); edit.endOffset = edit.valueOffset + sizeof(double); return this.ReadFloat64(edit.valueOffset); } @@ -892,7 +906,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal Float128 ReadSparseFloat128(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Float128); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Float128); edit.endOffset = edit.valueOffset + Float128.Size; return this.ReadFloat128(edit.valueOffset); } @@ -918,7 +932,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal decimal ReadSparseDecimal(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Decimal); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Decimal); edit.endOffset = edit.valueOffset + sizeof(decimal); return this.ReadDecimal(edit.valueOffset); } @@ -944,7 +958,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal DateTime ReadSparseDateTime(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.DateTime); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.DateTime); edit.endOffset = edit.valueOffset + 8; return this.ReadDateTime(edit.valueOffset); } @@ -970,7 +984,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal UnixDateTime ReadSparseUnixDateTime(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.UnixDateTime); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.UnixDateTime); edit.endOffset = edit.valueOffset + 8; return this.ReadUnixDateTime(edit.valueOffset); } @@ -997,7 +1011,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal Guid ReadSparseGuid(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Guid); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Guid); edit.endOffset = edit.valueOffset + 16; return this.ReadGuid(edit.valueOffset); } @@ -1023,7 +1037,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal MongoDbObjectId ReadSparseMongoDbObjectId(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.MongoDbObjectId); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.MongoDbObjectId); edit.endOffset = edit.valueOffset + MongoDbObjectId.Size; return this.ReadMongoDbObjectId(edit.valueOffset); } @@ -1050,7 +1064,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal NullValue ReadSparseNull(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Null); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Null); edit.endOffset = edit.valueOffset; return NullValue.Default; } @@ -1075,7 +1089,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal bool ReadSparseBool(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Boolean); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Boolean); edit.endOffset = edit.valueOffset; return edit.cellType == LayoutType.Boolean; } @@ -1100,7 +1114,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal Utf8Span ReadSparseString(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Utf8); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Utf8); Utf8Span span = this.ReadString(edit.valueOffset, out int sizeLenInBytes); edit.endOffset = edit.valueOffset + sizeLenInBytes + span.Length; return span; @@ -1128,7 +1142,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow internal ReadOnlySpan ReadSparseBinary(ref RowCursor edit) { - this.ReadSparsePrimitiveTypeCode(ref edit, LayoutType.Binary); + this.ValidateSparsePrimitiveTypeCode(ref edit, LayoutType.Binary); ReadOnlySpan span = this.ReadBinary(edit.valueOffset, out int sizeLenInBytes); edit.endOffset = edit.valueOffset + sizeLenInBytes + span.Length; return span; @@ -1504,7 +1518,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow } int index = layout.NumFixed + varIndex; - ReadOnlySpan columns = layout.Columns; + Layout.ColumnView columns = layout.Columns; Contract.Assert(index <= columns.Length); int offset = scopeOffset + layout.Size; for (int i = layout.NumFixed; i < index; i++) @@ -1616,8 +1630,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow layout = edit.layout, immutable = immutable, }; - - break; } case LayoutTypedArray _: @@ -1636,8 +1648,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow immutable = immutable, count = (int)this.ReadUInt32(edit.valueOffset), }; - - break; } case LayoutTypedTuple _: @@ -1656,8 +1666,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow immutable = immutable, count = edit.cellTypeArgs.Count, }; - - break; } case LayoutNullable _: @@ -1698,8 +1706,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow index = 2, }; } - - break; } case LayoutUDT _: @@ -1716,8 +1722,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow layout = udt, immutable = immutable, }; - - break; } default: @@ -1914,6 +1918,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow } #if DEBUG + // Fill deleted bits (in debug builds) to detect overflow/alignment errors. this.buffer.Slice(this.length, shift).Fill(0xFF); #endif @@ -2312,7 +2317,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow } [Conditional("DEBUG")] - private void ReadSparsePrimitiveTypeCode(ref RowCursor edit, LayoutType code) + private void ValidateSparsePrimitiveTypeCode(ref RowCursor edit, LayoutType code) { Contract.Assert(edit.exists); @@ -2377,11 +2382,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow Contract.Assert(edit.valueOffset == edit.metaOffset + metaBytes); } - /// - /// - /// . - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureSparse( ref RowCursor edit, @@ -2581,7 +2581,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow /// The length (in bytes) of the encoded field including the metadata and the value. private int SparseComputePrimitiveSize(LayoutType cellType, int metaOffset, int valueOffset) { - // JTHTODO: convert to a virtual? int metaBytes = valueOffset - metaOffset; LayoutCode code = cellType.LayoutCode; switch (code) @@ -2670,7 +2669,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow /// private int CountDefaultValue(LayoutType code, TypeArgumentList typeArgs) { - // JTHTODO: convert to a virtual? switch (code) { case LayoutNull _: @@ -2781,7 +2779,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow private int WriteDefaultValue(int offset, LayoutType code, TypeArgumentList typeArgs) { - // JTHTODO: convert to a virtual? switch (code) { case LayoutNull _: diff --git a/dotnet/src/HybridRow/RowCursor.cs b/src/Serialization/HybridRow/RowCursor.cs similarity index 97% rename from dotnet/src/HybridRow/RowCursor.cs rename to src/Serialization/HybridRow/RowCursor.cs index e9d1867..68966e6 100644 --- a/dotnet/src/HybridRow/RowCursor.cs +++ b/src/Serialization/HybridRow/RowCursor.cs @@ -30,7 +30,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow /// The entire scope can still be replaced. internal bool immutable; - /// If true, this scope is an unique index scope whose index will be built after its items are written. + /// If true, this scope is an unique index scope whose index will be built after its items are written. internal bool deferUniqueIndex; /// @@ -132,6 +132,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow return ref cursor; } + /// For schematized sparse fields, the token of the path, otherwise 0. + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public ulong Token + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (ulong)this.pathToken; + } + /// For indexed scopes (e.g. Array), the 0-based index into the scope of the next insertion. [DebuggerBrowsable(DebuggerBrowsableState.Never)] public int Index diff --git a/dotnet/src/HybridRow/RowOptions.cs b/src/Serialization/HybridRow/RowOptions.cs similarity index 100% rename from dotnet/src/HybridRow/RowOptions.cs rename to src/Serialization/HybridRow/RowOptions.cs diff --git a/dotnet/src/HybridRow/SchemaId.cs b/src/Serialization/HybridRow/SchemaId.cs similarity index 81% rename from dotnet/src/HybridRow/SchemaId.cs rename to src/Serialization/HybridRow/SchemaId.cs index 6a446a6..1386660 100644 --- a/dotnet/src/HybridRow/SchemaId.cs +++ b/src/Serialization/HybridRow/SchemaId.cs @@ -2,10 +2,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ +#pragma warning disable IDE0041 // Use 'is null' check +#pragma warning disable IDE0070 // Use 'System.HashCode' + namespace Microsoft.Azure.Cosmos.Serialization.HybridRow { using System; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Microsoft.Azure.Cosmos.Core; using Newtonsoft.Json; @@ -30,6 +34,20 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow /// The underlying identifier. public int Id { get; } + /// Integer conversion operator. + [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Constructor")] + public static explicit operator SchemaId(int id) + { + return new SchemaId(id); + } + + /// Integer conversion operator. + [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Id property")] + public static explicit operator int(SchemaId id) + { + return id.Id; + } + /// Operator == overload. public static bool operator ==(SchemaId left, SchemaId right) { @@ -50,7 +68,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow return false; } - return obj is SchemaId && this.Equals((SchemaId)obj); + return obj is SchemaId id && this.Equals(id); } /// overload. diff --git a/src/Serialization/HybridRow/Schemas/AllowEmptyKind.cs b/src/Serialization/HybridRow/Schemas/AllowEmptyKind.cs new file mode 100644 index 0000000..3f4904f --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/AllowEmptyKind.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// Describes the empty canonicalization for properties. + [JsonConverter(typeof(StringEnumConverter), true)] // camelCase:true + [Flags] + [SuppressMessage("Naming", "CA1714:Flags enums should have plural names", Justification = "Consistency")] + public enum AllowEmptyKind : byte + { + /// Empty and null are treated as distinct. + None = 0, + + /// Empty values are converted to null when written. + EmptyAsNull = 1, + + /// Null values are converted to empty when read. + NullAsEmpty = 2, + + /// + /// Empty values are converted to null when written, and null values are converted to empty + /// when read. + /// + Both = AllowEmptyKind.EmptyAsNull | AllowEmptyKind.NullAsEmpty, + } +} diff --git a/src/Serialization/HybridRow/Schemas/ArrayHybridRowSerializer.cs b/src/Serialization/HybridRow/Schemas/ArrayHybridRowSerializer.cs new file mode 100644 index 0000000..33a0bf4 --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/ArrayHybridRowSerializer.cs @@ -0,0 +1,111 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1034 // Do not nest types. + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + + public struct ArrayHybridRowSerializer : IHybridRowSerializer> + where TSerializer : struct, IHybridRowSerializer + { + public IEqualityComparer> Comparer => ArrayComparer.Default; + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, List value) + { + Result r = LayoutType.Array.WriteScope(ref row, ref scope, default, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + foreach (T item in value) + { + r = default(TSerializer).Write(ref row, ref childScope, false, typeArgs[0].TypeArgs, item); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out List value) + { + Result r = LayoutType.Array.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + List items = new List(); + while (childScope.MoveNext(ref row)) + { + r = default(TSerializer).Read(ref row, ref childScope, isRoot, out T item); + if (r != Result.Success) + { + value = default; + return r; + } + + items.Add(item); + } + + value = items; + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public sealed class ArrayComparer : EqualityComparer> + { + public static new readonly ArrayComparer Default = new ArrayComparer(); + + public override bool Equals(List x, List y) + { + if (object.ReferenceEquals(x, y)) + { + return true; + } + if (x is null || y is null) + { + return false; + } + if (x.Count != y.Count) + { + return false; + } + + for (int i = 0; i < x.Count; i++) + { + if (!default(TSerializer).Comparer.Equals(x[i], y[i])) + { + return false; + } + } + + return true; + } + + public override int GetHashCode(List obj) + { + HashCode hash = default; + IEqualityComparer comparer = default(TSerializer).Comparer; + foreach (T item in obj) + { + hash.Add(item, comparer); + } + return hash.ToHashCode(); + } + } + } +} diff --git a/dotnet/src/HybridRow/Schemas/ArrayPropertyType.cs b/src/Serialization/HybridRow/Schemas/ArrayPropertyType.cs similarity index 79% rename from dotnet/src/HybridRow/Schemas/ArrayPropertyType.cs rename to src/Serialization/HybridRow/Schemas/ArrayPropertyType.cs index e5f8c1f..ce07f2b 100644 --- a/dotnet/src/HybridRow/Schemas/ArrayPropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/ArrayPropertyType.cs @@ -13,8 +13,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// than untyped arrays. When is unspecified, the array is untyped and its items /// may be heterogeneous. /// - public class ArrayPropertyType : ScopePropertyType + public sealed class ArrayPropertyType : ScopePropertyType { + /// Initializes a new instance of the class. + public ArrayPropertyType() + : base(TypeKind.Array) + { + } + /// (Optional) type of the elements of the array, if a typed array, otherwise null. [JsonProperty(PropertyName = "items")] public PropertyType Items { get; set; } diff --git a/src/Serialization/HybridRow/Schemas/EnumSchema.cs b/src/Serialization/HybridRow/Schemas/EnumSchema.cs new file mode 100644 index 0000000..30928f6 --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/EnumSchema.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System.Collections.Generic; + using System.ComponentModel; + using Newtonsoft.Json; + + /// An enum schema describes a set of constrained integer values. + public sealed class EnumSchema + { + /// A list of zero or more value definitions. + private List values; + + /// Initializes a new instance of the class. + public EnumSchema() + { + this.Type = TypeKind.Int32; + this.values = new List(); + } + + /// An (optional) comment describing the purpose of this enum. + /// Comments are for documentary purpose only and do not affect the enum at runtime. + [JsonProperty(PropertyName = "comment", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string Comment { get; set; } + + /// The name of the enum. + /// + /// The name of a enum MUST be unique within its namespace. + /// + /// Names must begin with an alpha-numeric character and can only contain alpha-numeric characters and + /// underscores. + /// + [JsonProperty(PropertyName = "name", Required = Required.Always)] + public string Name { get; set; } + + /// Api-specific type annotations for the property. + [JsonProperty(PropertyName = "apitype", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string ApiType { get; set; } + + /// The logical base type of the enum. + /// This must be a primitive. + [DefaultValue(TypeKind.Int32)] + [JsonProperty(PropertyName = "type", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public TypeKind Type { get; set; } + + /// A list of zero or more value definitions. + /// This field is never null. + [JsonProperty(PropertyName = "values")] + public List Values + { + get => this.values; + set => this.values = value ?? new List(); + } + } +} diff --git a/src/Serialization/HybridRow/Schemas/EnumValue.cs b/src/Serialization/HybridRow/Schemas/EnumValue.cs new file mode 100644 index 0000000..51c3579 --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/EnumValue.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using Newtonsoft.Json; + + /// An enum schema describes a set of constrained integer values. + public sealed class EnumValue + { + /// An (optional) comment describing the purpose of this value. + /// Comments are for documentary purpose only and do not affect the enum at runtime. + [JsonProperty(PropertyName = "comment", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string Comment { get; set; } + + /// The name of the enum value. + /// + /// The name of a value MUST be unique within its . + /// + /// Names must begin with an alpha-numeric character and can only contain alpha-numeric characters and + /// underscores. + /// + [JsonProperty(PropertyName = "name", Required = Required.Always)] + public string Name { get; set; } + + /// The numerical value of the enum value. + [JsonProperty(PropertyName = "value", Required = Required.Always)] + public long Value { get; set; } + } +} diff --git a/dotnet/src/HybridRow/Schemas/MapPropertyType.cs b/src/Serialization/HybridRow/Schemas/MapPropertyType.cs similarity index 84% rename from dotnet/src/HybridRow/Schemas/MapPropertyType.cs rename to src/Serialization/HybridRow/Schemas/MapPropertyType.cs index 393b3b8..da22649 100644 --- a/dotnet/src/HybridRow/Schemas/MapPropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/MapPropertyType.cs @@ -17,8 +17,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// maps. When or is unspecified or marked /// , the map is untyped and its key and/or values may be heterogeneous. /// - public class MapPropertyType : ScopePropertyType + public sealed class MapPropertyType : ScopePropertyType { + /// Initializes a new instance of the class. + public MapPropertyType() + : base(TypeKind.Map) + { + } + /// (Optional) type of the keys of the map, if a typed map, otherwise null. [JsonProperty(PropertyName = "keys")] public PropertyType Keys { get; set; } diff --git a/src/Serialization/HybridRow/Schemas/Namespace.cs b/src/Serialization/HybridRow/Schemas/Namespace.cs new file mode 100644 index 0000000..bf1e132 --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/Namespace.cs @@ -0,0 +1,145 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1716 // Identifiers should not match keywords + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System.Collections.Generic; + using System.ComponentModel; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Newtonsoft.Json; + + [JsonObject] + public sealed class Namespace + { + /// + /// The standard settings used by the JSON parser for interpreting + /// documents. + /// + private static readonly JsonSerializerSettings NamespaceParseSettings = new JsonSerializerSettings() + { + CheckAdditionalContent = true, + NullValueHandling = NullValueHandling.Ignore, + Formatting = Formatting.Indented, + }; + + /// The set of enums for the . + private List enums; + + /// The set of schemas that make up the . + private List schemas; + + /// Initializes a new instance of the class. + public Namespace() + { + this.Version = SchemaLanguageVersion.V2; + this.enums = new List(); + this.schemas = new List(); + } + + /// The version of the HybridRow Schema Definition Language used to encode this namespace. + [DefaultValue(SchemaLanguageVersion.V2)] + [JsonProperty(PropertyName = "version", DefaultValueHandling = DefaultValueHandling.Populate)] + public SchemaLanguageVersion Version { get; set; } + + /// The fully qualified identifier of the namespace. + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } + + /// An (optional) comment describing the purpose of this namespace. + /// Comments are for documentary purpose only and do not affect the namespace at runtime. + [JsonProperty(PropertyName = "comment", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string Comment { get; set; } + + /// An (optional) namespace to use when performing C++ codegen. + [JsonProperty(PropertyName = "cppNamespace", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string CppNamespace { get; set; } + + /// The set of enums defined in . + [JsonProperty(PropertyName = "enums", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public List Enums + { + get => this.enums; + + set => this.enums = value ?? new List(); + } + + /// The set of schemas that make up the . + /// + /// Namespaces may consist of zero or more table schemas along with zero or more UDT schemas. + /// Table schemas can only reference UDT schemas defined in the same namespace. UDT schemas can + /// contain nested UDTs whose schemas are defined within the same namespace. + /// + [JsonProperty(PropertyName = "schemas")] + public List Schemas + { + get => this.schemas; + + set => this.schemas = value ?? new List(); + } + + /// Parse a JSON document and return a full namespace. + /// The JSON text to parse. + /// A namespace containing a set of logical schemas. + public static Namespace Parse(string json) + { + Namespace ns = JsonConvert.DeserializeObject(json, Namespace.NamespaceParseSettings); + SchemaValidator.Validate(ns); + return ns; + } + + /// Writes a JSON document version of the namespace. + /// The namespace to serialize as JSON. + /// The JSON document. + public static string ToJson(Namespace n) + { + return JsonConvert.SerializeObject(n, Namespace.NamespaceParseSettings); + } + + /// Read Namespace as a row. + /// The row to read from. + /// If successful, the materialized value. + /// Success if the read is successful, an error code otherwise. + public static Result Read(ref RowBuffer row, out Namespace value) + { + Contract.Requires(row.Header.SchemaId == new SchemaId(NamespaceHybridRowSerializer.SchemaId)); + RowCursor root = RowCursor.Create(ref row); + return default(NamespaceHybridRowSerializer).Read(ref row, ref root, true, out value); + } + + /// Write Namespace as a row. + /// The row to write into. + /// Success if the write is successful, an error code otherwise. + public Result Write(ref RowBuffer row) + { + Contract.Requires(row.Header.SchemaId == new SchemaId(NamespaceHybridRowSerializer.SchemaId)); + RowCursor root = RowCursor.Create(ref row); + return default(NamespaceHybridRowSerializer).Write(ref row, ref root, true, new SchemaId(NamespaceHybridRowSerializer.SchemaId), this); + } + + + /// + /// Returns the effective SDL language version. + /// + /// The effective SDL language version. + internal SchemaLanguageVersion GetEffectiveSdlVersion() + { + return this.Version != SchemaLanguageVersion.Unspecified ? this.Version : SchemaLanguageVersion.Latest; + } + + /// Controls how Json.NET serializes the property. + /// + /// This method is accessed by Json.NET through Reflection and is used to filter empty enum + /// sets from the JSON serialization. This ensures backward compatible round-tripping with existing + /// namespaces. + /// + /// True if the property should be written. + private bool ShouldSerializeEnums() + { + return this.enums.Count > 0; + } + } +} diff --git a/src/Serialization/HybridRow/Schemas/NullableHybridRowSerializer.cs b/src/Serialization/HybridRow/Schemas/NullableHybridRowSerializer.cs new file mode 100644 index 0000000..6aa6750 --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/NullableHybridRowSerializer.cs @@ -0,0 +1,127 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1034 // Do not nest types. + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + + public struct NullableHybridRowSerializer : IHybridRowSerializer + where TSerializer : struct, IHybridRowSerializer + { + public IEqualityComparer Comparer => NullableComparer.Default; + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, TNullable value) + { + bool hasValue = NullableHybridRowSerializer.HasValue(value); + Result r = LayoutType.Nullable.WriteScope(ref row, ref scope, typeArgs, hasValue, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + if (hasValue) + { + r = default(TSerializer).Write( + ref row, + ref childScope, + false, + typeArgs[0].TypeArgs, + NullableHybridRowSerializer.AsValue(value)); + if (r != Result.Success) + { + return r; + } + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out TNullable value) + { + Result r = LayoutType.Nullable.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + if (childScope.MoveNext(ref row)) + { + r = default(TSerializer).Read(ref row, ref childScope, isRoot, out T item); + if (r != Result.Success) + { + value = default; + return r; + } + + value = NullableHybridRowSerializer.AsNullable(item); + } + else + { + value = default; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public sealed class NullableComparer : EqualityComparer + { + public static new readonly NullableComparer Default = new NullableComparer(); + + public override bool Equals(TNullable x, TNullable y) + { + bool xHasValue = NullableHybridRowSerializer.HasValue(x); + if (xHasValue != NullableHybridRowSerializer.HasValue(y)) + { + return false; + } + if (!xHasValue) + { + return true; + } + + return default(TSerializer).Comparer.Equals( + NullableHybridRowSerializer.AsValue(x), + NullableHybridRowSerializer.AsValue(y)); + } + + public override int GetHashCode(TNullable obj) + { + HashCode hash = default; + if (NullableHybridRowSerializer.HasValue(obj)) + { + hash.Add(NullableHybridRowSerializer.AsValue(obj), default(TSerializer).Comparer); + } + + return hash.ToHashCode(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasValue(TNullable value) + { + return !((object)value is null); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TNullable AsNullable(T value) + { + return (TNullable)(object)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T AsValue(TNullable value) + { + return (T)(object)value; + } + } +} diff --git a/dotnet/src/HybridRow/Schemas/ObjectPropertyType.cs b/src/Serialization/HybridRow/Schemas/ObjectPropertyType.cs similarity index 85% rename from dotnet/src/HybridRow/Schemas/ObjectPropertyType.cs rename to src/Serialization/HybridRow/Schemas/ObjectPropertyType.cs index 2ec8778..bfcb1d9 100644 --- a/dotnet/src/HybridRow/Schemas/ObjectPropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/ObjectPropertyType.cs @@ -14,13 +14,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// design. They are effectively equivalent to defining the same properties explicitly via /// with nested property paths. /// - public class ObjectPropertyType : ScopePropertyType + public sealed class ObjectPropertyType : ScopePropertyType { /// A list of zero or more property definitions that define the columns within the schema. private List properties; /// Initializes a new instance of the class. public ObjectPropertyType() + : base(TypeKind.Object) { this.properties = new List(); } @@ -29,15 +30,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "properties")] public List Properties { - get - { - return this.properties; - } - - set - { - this.properties = value ?? new List(); - } + get => this.properties; + set => this.properties = value ?? new List(); } } } diff --git a/dotnet/src/HybridRow/Schemas/PartitionKey.cs b/src/Serialization/HybridRow/Schemas/PartitionKey.cs similarity index 95% rename from dotnet/src/HybridRow/Schemas/PartitionKey.cs rename to src/Serialization/HybridRow/Schemas/PartitionKey.cs index 68cf9b2..7e8ea59 100644 --- a/dotnet/src/HybridRow/Schemas/PartitionKey.cs +++ b/src/Serialization/HybridRow/Schemas/PartitionKey.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas using Newtonsoft.Json; /// Describes a property or set of properties used to partition the data set across machines. - public class PartitionKey + public sealed class PartitionKey { /// The logical path of the referenced property. /// Partition keys MUST refer to properties defined within the same . diff --git a/dotnet/src/HybridRow/Schemas/PrimarySortKey.cs b/src/Serialization/HybridRow/Schemas/PrimarySortKey.cs similarity index 96% rename from dotnet/src/HybridRow/Schemas/PrimarySortKey.cs rename to src/Serialization/HybridRow/Schemas/PrimarySortKey.cs index 9d02071..a8600bb 100644 --- a/dotnet/src/HybridRow/Schemas/PrimarySortKey.cs +++ b/src/Serialization/HybridRow/Schemas/PrimarySortKey.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// Describes a property or set of properties used to order the data set within a single /// partition. /// - public class PrimarySortKey + public sealed class PrimarySortKey { /// The logical path of the referenced property. /// Primary keys MUST refer to properties defined within the same . diff --git a/src/Serialization/HybridRow/Schemas/PrimitiveHybridRowSerializer.cs b/src/Serialization/HybridRow/Schemas/PrimitiveHybridRowSerializer.cs new file mode 100644 index 0000000..4f4ece5 --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/PrimitiveHybridRowSerializer.cs @@ -0,0 +1,421 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable SA1649 // File name should match first type name +#pragma warning disable CA1034 // Do not nest types. + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + + public struct Int8HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, sbyte value) + { + return LayoutType.Int8.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out sbyte value) + { + return LayoutType.Int8.ReadSparse(ref row, ref scope, out value); + } + } + + public struct Int16HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, short value) + { + return LayoutType.Int16.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out short value) + { + return LayoutType.Int16.ReadSparse(ref row, ref scope, out value); + } + } + + public struct Int32HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, int value) + { + return LayoutType.Int32.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out int value) + { + return LayoutType.Int32.ReadSparse(ref row, ref scope, out value); + } + } + + public struct Int64HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, long value) + { + return LayoutType.Int64.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out long value) + { + return LayoutType.Int64.ReadSparse(ref row, ref scope, out value); + } + } + + public struct UInt8HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, byte value) + { + return LayoutType.UInt8.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out byte value) + { + return LayoutType.UInt8.ReadSparse(ref row, ref scope, out value); + } + } + + public struct UInt16HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, ushort value) + { + return LayoutType.UInt16.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out ushort value) + { + return LayoutType.UInt16.ReadSparse(ref row, ref scope, out value); + } + } + + public struct UInt32HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, uint value) + { + return LayoutType.UInt32.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out uint value) + { + return LayoutType.UInt32.ReadSparse(ref row, ref scope, out value); + } + } + + public struct UInt64HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, ulong value) + { + return LayoutType.UInt64.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out ulong value) + { + return LayoutType.UInt64.ReadSparse(ref row, ref scope, out value); + } + } + + public struct Float32HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, float value) + { + return LayoutType.Float32.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out float value) + { + return LayoutType.Float32.ReadSparse(ref row, ref scope, out value); + } + } + + public struct Float64HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, double value) + { + return LayoutType.Float64.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out double value) + { + return LayoutType.Float64.ReadSparse(ref row, ref scope, out value); + } + } + + public struct Float128HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Float128 value) + { + return LayoutType.Float128.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Float128 value) + { + return LayoutType.Float128.ReadSparse(ref row, ref scope, out value); + } + } + + public struct DecimalHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, decimal value) + { + return LayoutType.Decimal.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out decimal value) + { + return LayoutType.Decimal.ReadSparse(ref row, ref scope, out value); + } + } + + public struct BooleanHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, bool value) + { + return LayoutType.Boolean.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out bool value) + { + return LayoutType.Boolean.ReadSparse(ref row, ref scope, out value); + } + } + + public struct NullHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, NullValue value) + { + return LayoutType.Null.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out NullValue value) + { + return LayoutType.Null.ReadSparse(ref row, ref scope, out value); + } + } + + public struct DateTimeHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, DateTime value) + { + return LayoutType.DateTime.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out DateTime value) + { + return LayoutType.DateTime.ReadSparse(ref row, ref scope, out value); + } + } + + public struct UnixDateTimeHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, UnixDateTime value) + { + return LayoutType.UnixDateTime.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out UnixDateTime value) + { + return LayoutType.UnixDateTime.ReadSparse(ref row, ref scope, out value); + } + } + + public struct GuidHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Guid value) + { + return LayoutType.Guid.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Guid value) + { + return LayoutType.Guid.ReadSparse(ref row, ref scope, out value); + } + } + + public struct MongoDbObjectIdHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, MongoDbObjectId value) + { + return LayoutType.MongoDbObjectId.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out MongoDbObjectId value) + { + return LayoutType.MongoDbObjectId.ReadSparse(ref row, ref scope, out value); + } + } + + public struct Utf8HybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, string value) + { + return LayoutType.Utf8.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out string value) + { + return LayoutType.Utf8.ReadSparse(ref row, ref scope, out value); + } + } + + public struct BinaryHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => BinaryComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, byte[] value) + { + return LayoutType.Binary.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out byte[] value) + { + return LayoutType.Binary.ReadSparse(ref row, ref scope, out value); + } + + public sealed class BinaryComparer : EqualityComparer + { + public static new readonly BinaryComparer Default = new BinaryComparer(); + + public override bool Equals(byte[] x, byte[] y) + { + return x.AsSpan().SequenceEqual(y.AsSpan()); + } + + public override int GetHashCode(byte[] obj) + { + HashCode hash = default; + + // Add bulk in 8-byte words. + ReadOnlySpan span = MemoryMarshal.Cast(obj.AsSpan()); + foreach (ulong i in span) + { + hash.Add(i); + } + + // Add any residual as separate bytes. + ReadOnlySpan residual = obj.AsSpan().Slice(span.Length * sizeof(ulong)); + foreach (byte i in residual) + { + hash.Add(i); + } + + return hash.ToHashCode(); + } + } + } + + public struct VarIntHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, long value) + { + return LayoutType.VarInt.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out long value) + { + return LayoutType.VarInt.ReadSparse(ref row, ref scope, out value); + } + } + + public struct VarUIntHybridRowSerializer : IHybridRowSerializer + { + public IEqualityComparer Comparer => EqualityComparer.Default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, ulong value) + { + return LayoutType.VarUInt.WriteSparse(ref row, ref scope, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out ulong value) + { + return LayoutType.VarUInt.ReadSparse(ref row, ref scope, out value); + } + } +} diff --git a/src/Serialization/HybridRow/Schemas/PrimitivePropertyType.cs b/src/Serialization/HybridRow/Schemas/PrimitivePropertyType.cs new file mode 100644 index 0000000..2314b90 --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/PrimitivePropertyType.cs @@ -0,0 +1,61 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using Newtonsoft.Json; + + /// A primitive property. + /// + /// Primitive properties map to columns one-to-one. Primitive properties indicate how the + /// column should be represented within the row. + /// + public sealed class PrimitivePropertyType : PropertyType + { + public PrimitivePropertyType() + : base(TypeKind.Invalid) + { + } + + public PrimitivePropertyType(TypeKind type) + : base(type) + { + } + + /// The maximum allowable length in bytes. + /// + /// This annotation is only valid for non-fixed length types. A value of 0 means the maximum + /// allowable length. + /// + [JsonProperty(PropertyName = "length")] + [JsonConverter(typeof(StrictIntegerConverter))] + public int Length { get; set; } + + /// Storage requirements of the property. + [JsonProperty(PropertyName = "storage")] + public StorageKind Storage { get; set; } + + /// The identifier of the enum defining the values and base type. + /// + /// This annotation is only valid for enum types. The enum MUST be defined within the + /// same as the schema that references it. + /// + [JsonProperty(PropertyName = "enum", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string Enum { get; set; } + + /// If true then during serialization this field includes the actual row buffer size. + /// + /// + /// Fields with this annotation MUST be fields with . + /// Only a single field per schema have this annotation. + /// + /// + /// During serialization this field is deferred until the very end and written last. + /// + /// + [JsonProperty(PropertyName = "rowBufferSize", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [JsonConverter(typeof(StrictBooleanConverter))] + public bool RowBufferSize { get; set; } + } +} diff --git a/dotnet/src/HybridRow/Schemas/Property.cs b/src/Serialization/HybridRow/Schemas/Property.cs similarity index 77% rename from dotnet/src/HybridRow/Schemas/Property.cs rename to src/Serialization/HybridRow/Schemas/Property.cs index 4a19772..7e20f77 100644 --- a/dotnet/src/HybridRow/Schemas/Property.cs +++ b/src/Serialization/HybridRow/Schemas/Property.cs @@ -9,7 +9,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas using Newtonsoft.Json; /// Describes a single property definition. - public class Property + public sealed class Property { /// An (optional) comment describing the purpose of this property. /// Comments are for documentary purpose only and do not affect the property at runtime. @@ -26,6 +26,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "path", Required = Required.Always)] public string Path { get; set; } + /// Api-specific name annotations for the property. + [JsonProperty(PropertyName = "apiname", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string ApiName { get; set; } + /// The type of the property. /// /// Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a @@ -33,5 +37,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// [JsonProperty(PropertyName = "type", Required = Required.Always)] public PropertyType PropertyType { get; set; } + + /// Empty canonicalization for this property. + [JsonProperty(PropertyName = "allowEmpty", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public AllowEmptyKind AllowEmpty { get; set; } } } diff --git a/dotnet/src/HybridRow/Schemas/PropertySchemaConverter.cs b/src/Serialization/HybridRow/Schemas/PropertySchemaConverter.cs similarity index 97% rename from dotnet/src/HybridRow/Schemas/PropertySchemaConverter.cs rename to src/Serialization/HybridRow/Schemas/PropertySchemaConverter.cs index 4ba0b85..1aec20f 100644 --- a/dotnet/src/HybridRow/Schemas/PropertySchemaConverter.cs +++ b/src/Serialization/HybridRow/Schemas/PropertySchemaConverter.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas using Newtonsoft.Json.Linq; /// Helper class for parsing the polymorphic subclasses from JSON. - internal class PropertySchemaConverter : JsonConverter + internal sealed class PropertySchemaConverter : JsonConverter { public override bool CanWrite => false; diff --git a/dotnet/src/HybridRow/Schemas/PropertyType.cs b/src/Serialization/HybridRow/Schemas/PropertyType.cs similarity index 90% rename from dotnet/src/HybridRow/Schemas/PropertyType.cs rename to src/Serialization/HybridRow/Schemas/PropertyType.cs index 273eb43..b46c7ab 100644 --- a/dotnet/src/HybridRow/Schemas/PropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/PropertyType.cs @@ -16,6 +16,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas this.Nullable = true; } + protected PropertyType(TypeKind type) + { + this.Nullable = true; + this.Type = type; + } + /// Api-specific type annotations for the property. [JsonProperty(PropertyName = "apitype")] public string ApiType { get; set; } diff --git a/dotnet/src/HybridRow/Schemas/Schema.cs b/src/Serialization/HybridRow/Schemas/Schema.cs similarity index 76% rename from dotnet/src/HybridRow/Schemas/Schema.cs rename to src/Serialization/HybridRow/Schemas/Schema.cs index 284afe2..73e05b9 100644 --- a/dotnet/src/HybridRow/Schemas/Schema.cs +++ b/src/Serialization/HybridRow/Schemas/Schema.cs @@ -17,7 +17,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// level row directly. UDTs described nested structured objects that may appear either within a table /// column or within another UDT (i.e. nested UDTs). /// - public class Schema + public sealed class Schema { /// An (optional) list of zero or more logical paths that form the partition key. private List partitionKeys; @@ -34,6 +34,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// Initializes a new instance of the class. public Schema() { + this.Version = SchemaLanguageVersion.Unspecified; this.Type = TypeKind.Schema; this.properties = new List(); this.partitionKeys = new List(); @@ -42,7 +43,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas } /// The version of the HybridRow Schema Definition Language used to encode this schema. - [JsonProperty(PropertyName = "version")] + [DefaultValue(SchemaLanguageVersion.Unspecified)] + [JsonProperty(PropertyName = "version", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] public SchemaLanguageVersion Version { get; set; } /// An (optional) comment describing the purpose of this schema. @@ -65,6 +67,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "id", Required = Required.Always)] public SchemaId SchemaId { get; set; } + /// The name of the schema this schema derives from. + [JsonProperty(PropertyName = "baseName", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string BaseName { get; set; } + + /// The unique identifier of the schema this schema derives from. + [JsonProperty(PropertyName = "baseId", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public SchemaId BaseSchemaId { get; set; } + /// Schema-wide operations. [JsonProperty(PropertyName = "options")] public SchemaOptions Options { get; set; } @@ -79,15 +89,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "properties")] public List Properties { - get - { - return this.properties; - } - - set - { - this.properties = value ?? new List(); - } + get => this.properties; + set => this.properties = value ?? new List(); } /// An (optional) list of zero or more logical paths that form the partition key. @@ -97,15 +100,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "partitionkeys")] public List PartitionKeys { - get - { - return this.partitionKeys; - } - - set - { - this.partitionKeys = value ?? new List(); - } + get => this.partitionKeys; + set => this.partitionKeys = value ?? new List(); } /// An (optional) list of zero or more logical paths that form the primary sort key. @@ -113,17 +109,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// /// This field is never null. [JsonProperty(PropertyName = "primarykeys")] - public List PrimarySortKeys + public List PrimaryKeys { - get - { - return this.primaryKeys; - } - - set - { - this.primaryKeys = value ?? new List(); - } + get => this.primaryKeys; + set => this.primaryKeys = value ?? new List(); } /// An (optional) list of zero or more logical paths that hold data shared by all documents with same partition key. @@ -133,25 +122,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "statickeys", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] public List StaticKeys { - get - { - return this.staticKeys; - } - - set - { - this.staticKeys = value ?? new List(); - } - } - - /// Parse a JSON fragment and return a schema. - /// The JSON text to parse. - /// A logical schema. - public static Schema Parse(string json) - { - return JsonConvert.DeserializeObject(json); - - // TODO: perform structural validation on the Schema after JSON parsing. + get => this.staticKeys; + set => this.staticKeys = value ?? new List(); } /// @@ -167,5 +139,20 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas return LayoutCompiler.Compile(ns, this); } + + /// + /// Returns the effective SDL language version of the current schema in the context of the given . + /// + /// The namespace used to resolve the schema. + /// The effective SDL language version. + internal SchemaLanguageVersion GetEffectiveSdlVersion(Namespace ns) + { + if (this.Version != SchemaLanguageVersion.Unspecified) + { + return this.Version; + } + + return ns.GetEffectiveSdlVersion(); + } } } diff --git a/dotnet/src/HybridRow/Schemas/SchemaException.cs b/src/Serialization/HybridRow/Schemas/SchemaException.cs similarity index 100% rename from dotnet/src/HybridRow/Schemas/SchemaException.cs rename to src/Serialization/HybridRow/Schemas/SchemaException.cs diff --git a/dotnet/src/HybridRow/Schemas/SchemaHash.cs b/src/Serialization/HybridRow/Schemas/SchemaHash.cs similarity index 52% rename from dotnet/src/HybridRow/Schemas/SchemaHash.cs rename to src/Serialization/HybridRow/Schemas/SchemaHash.cs index e9488e4..b98d7f1 100644 --- a/dotnet/src/HybridRow/Schemas/SchemaHash.cs +++ b/src/Serialization/HybridRow/Schemas/SchemaHash.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas using Microsoft.Azure.Cosmos.Core; using Microsoft.Azure.Cosmos.Serialization.HybridRow.Internal; + // ReSharper disable UnusedParameter.Local public static class SchemaHash { /// Computes the logical hash for a logical schema. @@ -18,71 +19,75 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas public static (ulong low, ulong high) ComputeHash(Namespace ns, Schema schema, (ulong low, ulong high) seed = default) { (ulong low, ulong high) hash = seed; + SchemaLanguageVersion v = schema.GetEffectiveSdlVersion(ns); hash = MurmurHash3.Hash128(schema.SchemaId, hash); - hash = MurmurHash3.Hash128(schema.Type, hash); - hash = SchemaHash.ComputeHash(ns, schema.Options, hash); - if (schema.PartitionKeys != null) + hash = SchemaHash.ComputeHash(v, ns, schema.Type, hash); + hash = MurmurHash3.Hash128(v, hash); + hash = SchemaHash.ComputeHash(v, schema.Options, hash); + + hash = MurmurHash3.Hash128(schema.PartitionKeys.Count, hash); + foreach (PartitionKey p in schema.PartitionKeys) { - foreach (PartitionKey p in schema.PartitionKeys) - { - hash = SchemaHash.ComputeHash(ns, p, hash); - } + hash = SchemaHash.ComputeHash(v, ns, p, hash); } - if (schema.PrimarySortKeys != null) + hash = MurmurHash3.Hash128(schema.PrimaryKeys.Count, hash); + foreach (PrimarySortKey p in schema.PrimaryKeys) { - foreach (PrimarySortKey p in schema.PrimarySortKeys) - { - hash = SchemaHash.ComputeHash(ns, p, hash); - } + hash = SchemaHash.ComputeHash(v, ns, p, hash); } - if (schema.StaticKeys != null) + hash = MurmurHash3.Hash128(schema.StaticKeys.Count, hash); + foreach (StaticKey p in schema.StaticKeys) { - foreach (StaticKey p in schema.StaticKeys) - { - hash = SchemaHash.ComputeHash(ns, p, hash); - } + hash = SchemaHash.ComputeHash(v, ns, p, hash); } - if (schema.Properties != null) + hash = MurmurHash3.Hash128(schema.Properties.Count, hash); + foreach (Property p in schema.Properties) { - foreach (Property p in schema.Properties) - { - hash = SchemaHash.ComputeHash(ns, p, hash); - } + hash = SchemaHash.ComputeHash(v, ns, p, hash); } return hash; } - private static (ulong low, ulong high) ComputeHash(Namespace ns, SchemaOptions options, (ulong low, ulong high) seed = default) + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + SchemaOptions options, + (ulong low, ulong high) seed = default) { (ulong low, ulong high) hash = seed; + hash = MurmurHash3.Hash128(options?.DisallowUnschematized ?? false, hash); hash = MurmurHash3.Hash128(options?.EnablePropertyLevelTimestamp ?? false, hash); - if (options?.DisableSystemPrefix ?? false) - { - hash = MurmurHash3.Hash128(true, hash); - } + hash = MurmurHash3.Hash128(options?.DisableSystemPrefix ?? false, hash); return hash; } - private static (ulong low, ulong high) ComputeHash(Namespace ns, Property p, (ulong low, ulong high) seed = default) + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + Property p, + (ulong low, ulong high) seed = default) { Contract.Requires(p != null); (ulong low, ulong high) hash = seed; hash = MurmurHash3.Hash128(p.Path, hash); - hash = SchemaHash.ComputeHash(ns, p.PropertyType, hash); + hash = SchemaHash.ComputeHash(v, ns, p.PropertyType, hash); return hash; } - private static (ulong low, ulong high) ComputeHash(Namespace ns, PropertyType p, (ulong low, ulong high) seed = default) + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + PropertyType p, + (ulong low, ulong high) seed = default) { Contract.Requires(p != null); (ulong low, ulong high) hash = seed; - hash = MurmurHash3.Hash128(p.Type, hash); + hash = SchemaHash.ComputeHash(v, ns, p.Type, hash); hash = MurmurHash3.Hash128(p.Nullable, hash); if (p.ApiType != null) { @@ -94,6 +99,21 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas case PrimitivePropertyType pp: hash = MurmurHash3.Hash128(pp.Storage, hash); hash = MurmurHash3.Hash128(pp.Length, hash); + if (pp.Type == TypeKind.Enum) + { + if (v < SchemaLanguageVersion.V2) + { + throw new SchemaException($"Enums require SDL v2 or higher: {v}."); + } + + EnumSchema enumSchema = ns.Enums.Find(es => es.Name == pp.Enum); + if (enumSchema == null) + { + throw new SchemaException($"Cannot resolve enum schema reference '{pp.Enum}'"); + } + + hash = SchemaHash.ComputeHash(v, ns, enumSchema, hash); + } break; case ScopePropertyType pp: hash = MurmurHash3.Hash128(pp.Immutable, hash); @@ -102,7 +122,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas case ArrayPropertyType spp: if (spp.Items != null) { - hash = SchemaHash.ComputeHash(ns, spp.Items, hash); + hash = SchemaHash.ComputeHash(v, ns, spp.Items, hash); } break; @@ -111,7 +131,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas { foreach (Property opp in spp.Properties) { - hash = SchemaHash.ComputeHash(ns, opp, hash); + hash = SchemaHash.ComputeHash(v, ns, opp, hash); } } @@ -119,19 +139,19 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas case MapPropertyType spp: if (spp.Keys != null) { - hash = SchemaHash.ComputeHash(ns, spp.Keys, hash); + hash = SchemaHash.ComputeHash(v, ns, spp.Keys, hash); } if (spp.Values != null) { - hash = SchemaHash.ComputeHash(ns, spp.Values, hash); + hash = SchemaHash.ComputeHash(v, ns, spp.Values, hash); } break; case SetPropertyType spp: if (spp.Items != null) { - hash = SchemaHash.ComputeHash(ns, spp.Items, hash); + hash = SchemaHash.ComputeHash(v, ns, spp.Items, hash); } break; @@ -140,7 +160,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas { foreach (PropertyType pt in spp.Items) { - hash = SchemaHash.ComputeHash(ns, pt, hash); + hash = SchemaHash.ComputeHash(v, ns, pt, hash); } } @@ -150,7 +170,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas { foreach (PropertyType pt in spp.Items) { - hash = SchemaHash.ComputeHash(ns, pt, hash); + hash = SchemaHash.ComputeHash(v, ns, pt, hash); } } @@ -166,13 +186,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas udtSchema = ns.Schemas.Find(s => s.SchemaId == spp.SchemaId); if (udtSchema.Name != spp.Name) { - throw new Exception($"Ambiguous schema reference: '{spp.Name}:{spp.SchemaId}'"); + throw new SchemaException($"Ambiguous schema reference: '{spp.Name}:{spp.SchemaId}'"); } } if (udtSchema == null) { - throw new Exception($"Cannot resolve schema reference '{spp.Name}:{spp.SchemaId}'"); + throw new SchemaException($"Cannot resolve schema reference '{spp.Name}:{spp.SchemaId}'"); } hash = SchemaHash.ComputeHash(ns, udtSchema, hash); @@ -185,7 +205,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas return hash; } - private static (ulong low, ulong high) ComputeHash(Namespace ns, PartitionKey key, (ulong low, ulong high) seed = default) + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + PartitionKey key, + (ulong low, ulong high) seed = default) { (ulong low, ulong high) hash = seed; if (key != null) @@ -196,19 +220,27 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas return hash; } - private static (ulong low, ulong high) ComputeHash(Namespace ns, PrimarySortKey key, (ulong low, ulong high) seed = default) + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + PrimarySortKey key, + (ulong low, ulong high) seed = default) { (ulong low, ulong high) hash = seed; if (key != null) { hash = MurmurHash3.Hash128(key.Path, hash); - hash = MurmurHash3.Hash128(key.Direction, hash); + hash = SchemaHash.ComputeHash(v, ns, key.Direction, hash); } return hash; } - private static (ulong low, ulong high) ComputeHash(Namespace ns, StaticKey key, (ulong low, ulong high) seed = default) + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + StaticKey key, + (ulong low, ulong high) seed = default) { (ulong low, ulong high) hash = seed; if (key != null) @@ -218,5 +250,52 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas return hash; } + + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + EnumSchema es, + (ulong low, ulong high) seed = default) + { + (ulong low, ulong high) hash = seed; + hash = SchemaHash.ComputeHash(v, ns, es.Type, hash); + + hash = MurmurHash3.Hash128(es.Values.Count, hash); + foreach (EnumValue ev in es.Values) + { + hash = SchemaHash.ComputeHash(v, ns, ev, hash); + } + + return hash; + } + + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + EnumValue ev, + (ulong low, ulong high) seed = default) + { + (ulong low, ulong high) hash = seed; + hash = MurmurHash3.Hash128(ev.Value, hash); + return hash; + } + + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + TypeKind type, + (ulong low, ulong high) seed = default) + { + return MurmurHash3.Hash128((int)type, seed); + } + + private static (ulong low, ulong high) ComputeHash( + SchemaLanguageVersion v, + Namespace ns, + SortDirection direction, + (ulong low, ulong high) seed = default) + { + return MurmurHash3.Hash128((int)direction, seed); + } } } diff --git a/dotnet/src/HybridRow/Schemas/SchemaLanguageVersion.cs b/src/Serialization/HybridRow/Schemas/SchemaLanguageVersion.cs similarity index 55% rename from dotnet/src/HybridRow/Schemas/SchemaLanguageVersion.cs rename to src/Serialization/HybridRow/Schemas/SchemaLanguageVersion.cs index 2af920e..0b9eec9 100644 --- a/dotnet/src/HybridRow/Schemas/SchemaLanguageVersion.cs +++ b/src/Serialization/HybridRow/Schemas/SchemaLanguageVersion.cs @@ -13,7 +13,20 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonConverter(typeof(StringEnumConverter), true)] // camelCase:true public enum SchemaLanguageVersion : byte { - /// Initial version of the HybridRow Schema Description Lanauge. + /// Initial version of the HybridRow Schema Description Language. V1 = 0, + + /// Introduced Enums, Inheritance. + V2 = 2, + + /// The latest version. + Latest = SchemaLanguageVersion.V2, + + /// No version is specified. + /// + /// When applied to a , unspecified will map to . + /// When applied to a , unspecified will map to the version given in the namespace. + /// + Unspecified = 255, } } diff --git a/dotnet/src/HybridRow/Schemas/SchemaOptions.cs b/src/Serialization/HybridRow/Schemas/SchemaOptions.cs similarity index 74% rename from dotnet/src/HybridRow/Schemas/SchemaOptions.cs rename to src/Serialization/HybridRow/Schemas/SchemaOptions.cs index 22dc698..397eb97 100644 --- a/dotnet/src/HybridRow/Schemas/SchemaOptions.cs +++ b/src/Serialization/HybridRow/Schemas/SchemaOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas using Newtonsoft.Json; /// Describes the set of options that apply to the entire schema and the way it is validated. - public class SchemaOptions + public sealed class SchemaOptions { /// If true then structural schema validation is enabled. /// @@ -23,10 +23,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas public bool DisallowUnschematized { get; set; } /// - /// If set and has the value true, then triggers behavior in the Schema that acts based on property - /// level timestamps. In Cassandra, this means that new columns are added for each top level property - /// that has values of the client side timestamp. This is then used in conflict resolution to independently - /// resolve each property based on the timestamp value of that property. + /// If set and has the value true, then triggers behavior in the Schema that acts based on + /// property level timestamps. In Cassandra, this means that new columns are added for each top level + /// property that has values of the client side timestamp. This is then used in conflict resolution to + /// independently resolve each property based on the timestamp value of that property. /// [JsonProperty(PropertyName = "enablePropertyLevelTimestamp")] [JsonConverter(typeof(StrictBooleanConverter))] @@ -39,5 +39,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "disableSystemPrefix", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] [JsonConverter(typeof(StrictBooleanConverter))] public bool DisableSystemPrefix { get; set; } + + /// If true then instances of this schema cannot be created directly, only through subtypes. + [JsonProperty(PropertyName = "abstract", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [JsonConverter(typeof(StrictBooleanConverter))] + public bool Abstract { get; set; } } } diff --git a/dotnet/src/HybridRow/Schemas/SchemaValidator.cs b/src/Serialization/HybridRow/Schemas/SchemaValidator.cs similarity index 55% rename from dotnet/src/HybridRow/Schemas/SchemaValidator.cs rename to src/Serialization/HybridRow/Schemas/SchemaValidator.cs index 3d1f309..a751b50 100644 --- a/dotnet/src/HybridRow/Schemas/SchemaValidator.cs +++ b/src/Serialization/HybridRow/Schemas/SchemaValidator.cs @@ -2,6 +2,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ +#pragma warning disable SA1316 // Tuple element names should use correct casing +#pragma warning disable SA1414 // Tuple types in signatures should have element names + namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas { using System.Collections.Generic; @@ -14,6 +17,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas Dictionary nameVersioningCheck = new Dictionary(ns.Schemas.Count); Dictionary<(string, SchemaId), Schema> nameDupCheck = new Dictionary<(string, SchemaId), Schema>(ns.Schemas.Count); Dictionary idDupCheck = new Dictionary(ns.Schemas.Count); + Dictionary enumDupCheck = new Dictionary(ns.Enums.Count); foreach (Schema s in ns.Schemas) { ValidateAssert.IsValidSchemaId(s.SchemaId, "Schema id"); @@ -35,26 +39,96 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas } } - SchemaValidator.Visit(ns, nameDupCheck, idDupCheck); + foreach (EnumSchema es in ns.Enums) + { + ValidateAssert.IsValidIdentifier(es.Name, "EnumSchema name"); + ValidateAssert.DuplicateCheck(es.Name, es, enumDupCheck, "EnumSchema reference", "Namespace"); + } + + SchemaValidator.Visit(ns, nameDupCheck, idDupCheck, enumDupCheck); } /// Visit an entire namespace and validate its constraints. /// The to validate. /// A map from schema names within the namespace to their schemas. /// A map from schema ids within the namespace to their schemas. - private static void Visit(Namespace ns, Dictionary<(string, SchemaId), Schema> schemas, Dictionary ids) + /// A map from enum schema names within the namespace to their enum schemas. + private static void Visit( + Namespace ns, + Dictionary<(string, SchemaId), Schema> schemas, + Dictionary ids, + Dictionary enums) { foreach (Schema s in ns.Schemas) { - SchemaValidator.Visit(s, schemas, ids); + SchemaValidator.Visit(s.GetEffectiveSdlVersion(ns), s, schemas, ids, enums); + } + + foreach (EnumSchema es in ns.Enums) + { + SchemaValidator.Visit(ns.GetEffectiveSdlVersion(), es); } } /// Visit a single schema and validate its constraints. + /// The version of the SDL in effect. + /// The to validate. + private static void Visit(SchemaLanguageVersion v, EnumSchema es) + { + ValidateAssert.IsTrue(v >= SchemaLanguageVersion.V2, $"Enums require SDL v2 or higher : {v}"); + + Dictionary nameDupCheck = new Dictionary(es.Values.Count); + foreach (EnumValue value in es.Values) + { + ValidateAssert.IsValidIdentifier(value.Name, "EnumSchema name"); + ValidateAssert.DuplicateCheck(value.Name, value, nameDupCheck, "EnumValue", "EnumSchema"); + + switch (es.Type) + { + case TypeKind.Int8: + ValidateAssert.IsValidEnumSize(sbyte.MinValue, sbyte.MaxValue, value, es); + break; + case TypeKind.Int16: + ValidateAssert.IsValidEnumSize(short.MinValue, short.MaxValue, value, es); + break; + case TypeKind.Int32: + ValidateAssert.IsValidEnumSize(int.MinValue, int.MaxValue, value, es); + break; + case TypeKind.VarInt: + case TypeKind.Int64: + ValidateAssert.IsValidEnumSize(long.MinValue, long.MaxValue, value, es); + break; + case TypeKind.UInt8: + ValidateAssert.IsValidEnumUSize(byte.MinValue, byte.MaxValue, value, es); + break; + case TypeKind.UInt16: + ValidateAssert.IsValidEnumUSize(ushort.MinValue, ushort.MaxValue, value, es); + break; + case TypeKind.UInt32: + ValidateAssert.IsValidEnumUSize(uint.MinValue, uint.MaxValue, value, es); + break; + case TypeKind.VarUInt: + case TypeKind.UInt64: + ValidateAssert.IsValidEnumUSize(ulong.MinValue, ulong.MaxValue, value, es); + break; + default: + throw new SchemaException($"{es.Type} is not a valid enumeration type."); + } + } + } + + /// Visit a single schema and validate its constraints. + /// The version of the SDL in effect. /// The to validate. /// A map from schema names within the namespace to their schemas. /// A map from schema ids within the namespace to their schemas. - private static void Visit(Schema s, Dictionary<(string, SchemaId), Schema> schemas, Dictionary ids) + /// A map from enum schema names within the namespace to their enum schemas. + private static void Visit( + SchemaLanguageVersion v, + Schema s, + Dictionary<(string, SchemaId), Schema> schemas, + Dictionary ids, + Dictionary enums) { ValidateAssert.AreEqual(s.Type, TypeKind.Schema, $"The type of a schema MUST be {TypeKind.Schema}: {s.Type}"); Dictionary pathDupCheck = new Dictionary(s.Properties.Count); @@ -68,7 +142,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas ValidateAssert.Exists(pk.Path, pathDupCheck, "Partition key column", "Schema"); } - foreach (PrimarySortKey ps in s.PrimarySortKeys) + foreach (PrimarySortKey ps in s.PrimaryKeys) { ValidateAssert.Exists(ps.Path, pathDupCheck, "Primary sort key column", "Schema"); } @@ -80,21 +154,47 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas foreach (Property p in s.Properties) { - SchemaValidator.Visit(p, s, schemas, ids); + SchemaValidator.Visit(p, s, schemas, ids, enums); + } + + List bases = new List(); + Schema cs = s; + while (cs.BaseName != null) + { + ValidateAssert.IsTrue(v >= SchemaLanguageVersion.V2, $"Inheritance requires SDL v2 or higher : {v}"); + Schema bs = ValidateAssert.Exists((cs.BaseName, cs.BaseSchemaId), schemas, "Schema reference", "Namespace"); + if (cs.BaseSchemaId != SchemaId.Invalid) + { + Schema baseSchema = ValidateAssert.Exists(cs.BaseSchemaId, ids, "Schema id", "Namespace"); + ValidateAssert.AreEqual( + cs.BaseName, + baseSchema.Name, + $"Schema name '{cs.BaseName}' does not match the name of schema with id '{cs.BaseSchemaId}': {baseSchema.Name}"); + } + + ValidateAssert.NotExists(bs.SchemaId, bases, $"Inheritance cycle with '{s.Name}': {s.SchemaId}", "Namespace"); + bases.Add(bs.SchemaId); + cs = bs; } } - private static void Visit(Property p, Schema s, Dictionary<(string, SchemaId), Schema> schemas, Dictionary ids) + private static void Visit( + Property p, + Schema s, + Dictionary<(string, SchemaId), Schema> schemas, + Dictionary ids, + Dictionary enums) { ValidateAssert.IsValidIdentifier(p.Path, "Property path"); - SchemaValidator.Visit(p.PropertyType, null, schemas, ids); + SchemaValidator.Visit(p.PropertyType, null, schemas, ids, enums); } private static void Visit( PropertyType p, PropertyType parent, Dictionary<(string, SchemaId), Schema> schemas, - Dictionary ids) + Dictionary ids, + Dictionary enums) { switch (p) { @@ -104,33 +204,42 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas { ValidateAssert.AreEqual(pp.Storage, StorageKind.Sparse, $"Nested fields MUST have storage {StorageKind.Sparse}"); } + if (pp.Type == TypeKind.Enum) + { + ValidateAssert.Exists(pp.Enum, enums, "Enum reference", "Namespace"); + } + if (pp.RowBufferSize) + { + ValidateAssert.AreEqual(pp.Storage, StorageKind.Fixed, $"RowBufferSize fields MUST have storage {StorageKind.Fixed}"); + ValidateAssert.AreEqual(pp.Type, TypeKind.Int32, "RowBufferSize fields MUST be type int32"); + } break; case ArrayPropertyType ap: if (ap.Items != null) { - SchemaValidator.Visit(ap.Items, p, schemas, ids); + SchemaValidator.Visit(ap.Items, p, schemas, ids, enums); } break; case MapPropertyType mp: - SchemaValidator.Visit(mp.Keys, p, schemas, ids); - SchemaValidator.Visit(mp.Values, p, schemas, ids); + SchemaValidator.Visit(mp.Keys, p, schemas, ids, enums); + SchemaValidator.Visit(mp.Values, p, schemas, ids, enums); break; case SetPropertyType sp: - SchemaValidator.Visit(sp.Items, p, schemas, ids); + SchemaValidator.Visit(sp.Items, p, schemas, ids, enums); break; case TaggedPropertyType gp: foreach (PropertyType item in gp.Items) { - SchemaValidator.Visit(item, p, schemas, ids); + SchemaValidator.Visit(item, p, schemas, ids, enums); } break; case TuplePropertyType tp: foreach (PropertyType item in tp.Items) { - SchemaValidator.Visit(item, p, schemas, ids); + SchemaValidator.Visit(item, p, schemas, ids, enums); } break; @@ -139,7 +248,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas foreach (Property nested in op.Properties) { ValidateAssert.DuplicateCheck(nested.Path, nested, pathDupCheck, "Property path", "Object"); - SchemaValidator.Visit(nested.PropertyType, p, schemas, ids); + SchemaValidator.Visit(nested.PropertyType, p, schemas, ids, enums); } break; @@ -246,6 +355,51 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas return value; } + + /// Validate does appear within the given scope. + /// The type of the keys within the scope. + /// The key to check. + /// The set of existing values within the scope. + /// Diagnostic label describing . + /// Diagnostic label describing . + public static void NotExists(TKey key, List scope, string label, string scopeLabel) + { + if (scope.Contains(key)) + { + throw new SchemaException($"{label} must not exist within a {scopeLabel}: {key}"); + } + } + + /// Validate if fits within the base type of . + /// The min value of the allowable range. + /// The max value of the allowable range. + /// The value to check. + /// The enum being checked. + public static void IsValidEnumSize(long minValue, long maxValue, EnumValue value, EnumSchema es) + { + if ((value.Value < minValue) || (value.Value > maxValue)) + { + throw new SchemaException($"{value.Name} ({value.Value}) cannot fit in {es.Name} of type {es.Type}."); + } + } + + /// Validate if fits within the base type of . + /// The min value of the allowable range. + /// The max value of the allowable range. + /// The value to check. + /// The enum being checked. + public static void IsValidEnumUSize(ulong minValue, ulong maxValue, EnumValue value, EnumSchema es) + { + ulong uvalue; + unchecked + { + uvalue = (ulong)value.Value; + } + if ((uvalue < minValue) || (uvalue > maxValue)) + { + throw new SchemaException($"{value.Name} ({value.Value}) cannot fit in {es.Name} of type {es.Type}."); + } + } } } } diff --git a/dotnet/src/HybridRow/Schemas/ScopePropertyType.cs b/src/Serialization/HybridRow/Schemas/ScopePropertyType.cs similarity index 89% rename from dotnet/src/HybridRow/Schemas/ScopePropertyType.cs rename to src/Serialization/HybridRow/Schemas/ScopePropertyType.cs index b41b730..3c0cc4e 100644 --- a/dotnet/src/HybridRow/Schemas/ScopePropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/ScopePropertyType.cs @@ -8,6 +8,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas public abstract class ScopePropertyType : PropertyType { + protected ScopePropertyType(TypeKind type) : base(type) + { + } + /// True if the property's child elements cannot be mutated in place. /// Immutable properties can still be replaced in their entirety. [JsonProperty(PropertyName = "immutable")] diff --git a/dotnet/src/HybridRow/Schemas/SetPropertyType.cs b/src/Serialization/HybridRow/Schemas/SetPropertyType.cs similarity index 59% rename from dotnet/src/HybridRow/Schemas/SetPropertyType.cs rename to src/Serialization/HybridRow/Schemas/SetPropertyType.cs index 3532056..cf0f714 100644 --- a/dotnet/src/HybridRow/Schemas/SetPropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/SetPropertyType.cs @@ -9,15 +9,19 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// Set properties represent an unbounded set of zero or more unique items. /// /// Sets may be typed or untyped. Within typed sets, all items MUST be the same type. The - /// type of items is specified via . Typed sets may be stored more efficiently - /// than untyped sets. When is unspecified, the set is untyped and its items - /// may be heterogeneous. - /// - /// Each item within a set must be unique. Uniqueness is defined by the HybridRow encoded sequence - /// of bytes for the item. + /// type of items is specified via . Typed sets may be stored more efficiently than + /// untyped sets. When is unspecified, the set is untyped and its items may be + /// heterogeneous. Each item within a set must be unique. Uniqueness is defined by the HybridRow + /// encoded sequence of bytes for the item. /// - public class SetPropertyType : ScopePropertyType + public sealed class SetPropertyType : ScopePropertyType { + /// Initializes a new instance of the class. + public SetPropertyType() + : base(TypeKind.Set) + { + } + /// (Optional) type of the elements of the set, if a typed set, otherwise null. [JsonProperty(PropertyName = "items")] public PropertyType Items { get; set; } diff --git a/dotnet/src/HybridRow/Schemas/SortDirection.cs b/src/Serialization/HybridRow/Schemas/SortDirection.cs similarity index 80% rename from dotnet/src/HybridRow/Schemas/SortDirection.cs rename to src/Serialization/HybridRow/Schemas/SortDirection.cs index 1a2e5f7..a3a6d41 100644 --- a/dotnet/src/HybridRow/Schemas/SortDirection.cs +++ b/src/Serialization/HybridRow/Schemas/SortDirection.cs @@ -2,6 +2,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ +#pragma warning disable CA1028 // Enum Storage should be Int32 + namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas { using System.Runtime.Serialization; @@ -10,13 +12,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// Describes the sort order direction. [JsonConverter(typeof(StringEnumConverter), true)] // camelCase:true - public enum SortDirection + public enum SortDirection : byte { /// Sorts from the lowest to the highest value. [EnumMember(Value = "asc")] Ascending = 0, - /// Sorts from the highests to the lowest value. + /// Sorts from the highest to the lowest value. [EnumMember(Value = "desc")] Descending, } diff --git a/dotnet/src/HybridRow/Schemas/StaticKey.cs b/src/Serialization/HybridRow/Schemas/StaticKey.cs similarity index 95% rename from dotnet/src/HybridRow/Schemas/StaticKey.cs rename to src/Serialization/HybridRow/Schemas/StaticKey.cs index 07f0013..45b8573 100644 --- a/dotnet/src/HybridRow/Schemas/StaticKey.cs +++ b/src/Serialization/HybridRow/Schemas/StaticKey.cs @@ -9,7 +9,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// /// Describes a property or set of properties whose values MUST be the same for all rows that share the same partition key. /// - public class StaticKey + public sealed class StaticKey { /// The logical path of the referenced property. /// Static path MUST refer to properties defined within the same . diff --git a/dotnet/src/HybridRow/Schemas/StorageKind.cs b/src/Serialization/HybridRow/Schemas/StorageKind.cs similarity index 100% rename from dotnet/src/HybridRow/Schemas/StorageKind.cs rename to src/Serialization/HybridRow/Schemas/StorageKind.cs diff --git a/dotnet/src/HybridRow/Schemas/StrictBooleanConverter.cs b/src/Serialization/HybridRow/Schemas/StrictBooleanConverter.cs similarity index 94% rename from dotnet/src/HybridRow/Schemas/StrictBooleanConverter.cs rename to src/Serialization/HybridRow/Schemas/StrictBooleanConverter.cs index ebdfb46..dfff65c 100644 --- a/dotnet/src/HybridRow/Schemas/StrictBooleanConverter.cs +++ b/src/Serialization/HybridRow/Schemas/StrictBooleanConverter.cs @@ -8,7 +8,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; - public class StrictBooleanConverter : JsonConverter + internal sealed class StrictBooleanConverter : JsonConverter { public override bool CanWrite => false; diff --git a/dotnet/src/HybridRow/Schemas/StrictIntegerConverter.cs b/src/Serialization/HybridRow/Schemas/StrictIntegerConverter.cs similarity index 96% rename from dotnet/src/HybridRow/Schemas/StrictIntegerConverter.cs rename to src/Serialization/HybridRow/Schemas/StrictIntegerConverter.cs index 4766ab0..dd785b3 100644 --- a/dotnet/src/HybridRow/Schemas/StrictIntegerConverter.cs +++ b/src/Serialization/HybridRow/Schemas/StrictIntegerConverter.cs @@ -9,7 +9,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas using System.Numerics; using Newtonsoft.Json; - public class StrictIntegerConverter : JsonConverter + internal sealed class StrictIntegerConverter : JsonConverter { public override bool CanWrite => false; diff --git a/dotnet/src/HybridRow/Schemas/TaggedPropertyType.cs b/src/Serialization/HybridRow/Schemas/TaggedPropertyType.cs similarity index 83% rename from dotnet/src/HybridRow/Schemas/TaggedPropertyType.cs rename to src/Serialization/HybridRow/Schemas/TaggedPropertyType.cs index 4194c00..9eca05f 100644 --- a/dotnet/src/HybridRow/Schemas/TaggedPropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/TaggedPropertyType.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// The uint8 type code is implicitly in position 0 within the resulting tagged and should not /// be specified in . /// - public class TaggedPropertyType : ScopePropertyType + public sealed class TaggedPropertyType : ScopePropertyType { internal const int MinTaggedArguments = 1; internal const int MaxTaggedArguments = 2; @@ -22,6 +22,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// Initializes a new instance of the class. public TaggedPropertyType() + : base(TypeKind.Tagged) { this.items = new List(); } @@ -30,15 +31,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "items")] public List Items { - get - { - return this.items; - } - - set - { - this.items = value ?? new List(); - } + get => this.items; + set => this.items = value ?? new List(); } } } diff --git a/dotnet/src/HybridRow/Schemas/TuplePropertyType.cs b/src/Serialization/HybridRow/Schemas/TuplePropertyType.cs similarity index 80% rename from dotnet/src/HybridRow/Schemas/TuplePropertyType.cs rename to src/Serialization/HybridRow/Schemas/TuplePropertyType.cs index 3abf163..55669fa 100644 --- a/dotnet/src/HybridRow/Schemas/TuplePropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/TuplePropertyType.cs @@ -8,13 +8,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas using Newtonsoft.Json; /// Tuple properties represent a typed, finite, ordered set of two or more items. - public class TuplePropertyType : ScopePropertyType + public sealed class TuplePropertyType : ScopePropertyType { /// Types of the elements of the tuple in element order. private List items; /// Initializes a new instance of the class. public TuplePropertyType() + : base(TypeKind.Tuple) { this.items = new List(); } @@ -23,15 +24,8 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas [JsonProperty(PropertyName = "items")] public List Items { - get - { - return this.items; - } - - set - { - this.items = value ?? new List(); - } + get => this.items; + set => this.items = value ?? new List(); } } } diff --git a/dotnet/src/HybridRow/Schemas/TypeKind.cs b/src/Serialization/HybridRow/Schemas/TypeKind.cs similarity index 96% rename from dotnet/src/HybridRow/Schemas/TypeKind.cs rename to src/Serialization/HybridRow/Schemas/TypeKind.cs index 24fca39..2de07ad 100644 --- a/dotnet/src/HybridRow/Schemas/TypeKind.cs +++ b/src/Serialization/HybridRow/Schemas/TypeKind.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// Describes the logical type of a property. [JsonConverter(typeof(StringEnumConverter), true)] // camelCase:true - public enum TypeKind + public enum TypeKind : byte { /// Reserved. Invalid = 0, @@ -129,5 +129,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// An untyped sparse field. /// May only be used to define the type within a nested scope (e.g. or . Any, + + /// + /// An enum type defined in the same namespace. + /// + Enum, } } diff --git a/src/Serialization/HybridRow/Schemas/TypedArrayHybridRowSerializer.cs b/src/Serialization/HybridRow/Schemas/TypedArrayHybridRowSerializer.cs new file mode 100644 index 0000000..c323881 --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/TypedArrayHybridRowSerializer.cs @@ -0,0 +1,111 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1034 // Do not nest types. + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + + public struct TypedArrayHybridRowSerializer : IHybridRowSerializer> + where TSerializer : struct, IHybridRowSerializer + { + public IEqualityComparer> Comparer => TypedArrayComparer.Default; + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, List value) + { + Result r = LayoutType.TypedArray.WriteScope(ref row, ref scope, typeArgs, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + foreach (T item in value) + { + r = default(TSerializer).Write(ref row, ref childScope, false, typeArgs[0].TypeArgs, item); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out List value) + { + Result r = LayoutType.TypedArray.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + List items = new List(); + while (childScope.MoveNext(ref row)) + { + r = default(TSerializer).Read(ref row, ref childScope, isRoot, out T item); + if (r != Result.Success) + { + value = default; + return r; + } + + items.Add(item); + } + + value = items; + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public sealed class TypedArrayComparer : EqualityComparer> + { + public static new readonly TypedArrayComparer Default = new TypedArrayComparer(); + + public override bool Equals(List x, List y) + { + if (object.ReferenceEquals(x, y)) + { + return true; + } + if (x is null || y is null) + { + return false; + } + if (x.Count != y.Count) + { + return false; + } + + for (int i = 0; i < x.Count; i++) + { + if (!default(TSerializer).Comparer.Equals(x[i], y[i])) + { + return false; + } + } + + return true; + } + + public override int GetHashCode(List obj) + { + HashCode hash = default; + IEqualityComparer comparer = default(TSerializer).Comparer; + foreach (T item in obj) + { + hash.Add(item, comparer); + } + return hash.ToHashCode(); + } + } + } +} diff --git a/src/Serialization/HybridRow/Schemas/TypedMapHybridRowSerializer.cs b/src/Serialization/HybridRow/Schemas/TypedMapHybridRowSerializer.cs new file mode 100644 index 0000000..454c55a --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/TypedMapHybridRowSerializer.cs @@ -0,0 +1,131 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1034 // Do not nest types. + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + + public struct TypedMapHybridRowSerializer : IHybridRowSerializer> + where TKeySerializer : struct, IHybridRowSerializer + where TValueSerializer : struct, IHybridRowSerializer + { + public IEqualityComparer> Comparer => TypedMapComparer.Default; + + public Result Write(ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, Dictionary value) + { + Result r = LayoutType.TypedMap.WriteScope(ref row, ref scope, typeArgs, out RowCursor uniqueScope); + if (r != Result.Success) + { + return r; + } + + uniqueScope.Clone(out RowCursor childScope); + childScope.deferUniqueIndex = true; + + foreach (KeyValuePair item in value) + { + r = default(TypedTupleHybridRowSerializer) + .Write(ref row, ref childScope, false, typeArgs, (item.Key, item.Value)); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + } + + uniqueScope.count = childScope.count; + r = row.TypedCollectionUniqueIndexRebuild(ref uniqueScope); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public Result Read(ref RowBuffer row, ref RowCursor scope, bool isRoot, out Dictionary value) + { + Result r = LayoutType.TypedMap.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + Dictionary items = new Dictionary(default(TKeySerializer).Comparer); + while (childScope.MoveNext(ref row)) + { + r = default(TypedTupleHybridRowSerializer) + .Read(ref row, ref childScope, isRoot, out (TKey Key, TValue Value) item); + if (r != Result.Success) + { + value = default; + return r; + } + + items.Add(item.Key, item.Value); + } + + value = items; + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public sealed class TypedMapComparer : EqualityComparer> + { + public static new readonly TypedMapComparer Default = new TypedMapComparer(); + + public override bool Equals(Dictionary x, Dictionary y) + { + if (object.ReferenceEquals(x, y)) + { + return true; + } + if (x is null || y is null) + { + return false; + } + if (x.Count != y.Count) + { + return false; + } + + foreach (KeyValuePair p in x) + { + if (!y.TryGetValue(p.Key, out TValue value)) + { + return false; + } + + if (!default(TValueSerializer).Comparer.Equals(p.Value, value)) + { + return false; + } + } + + return true; + } + + public override int GetHashCode(Dictionary obj) + { + HashCode hash = default; + IEqualityComparer keyComparer = default(TKeySerializer).Comparer; + IEqualityComparer valueComparer = default(TValueSerializer).Comparer; + foreach (KeyValuePair p in obj) + { + hash.Add(p.Key, keyComparer); + hash.Add(p.Value, valueComparer); + } + return hash.ToHashCode(); + } + } + } +} diff --git a/src/Serialization/HybridRow/Schemas/TypedTupleHybridRowSerializer.cs b/src/Serialization/HybridRow/Schemas/TypedTupleHybridRowSerializer.cs new file mode 100644 index 0000000..0f8bd1c --- /dev/null +++ b/src/Serialization/HybridRow/Schemas/TypedTupleHybridRowSerializer.cs @@ -0,0 +1,381 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1034 // Do not nest types. + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas +{ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + + public struct TypedTupleHybridRowSerializer + : IHybridRowSerializer<(T1 Item1, T2 Item2)> + where T1Serializer : struct, IHybridRowSerializer + where T2Serializer : struct, IHybridRowSerializer + { + public IEqualityComparer<(T1 Item1, T2 Item2)> Comparer => TypedTupleComparer.Default; + + public Result Write( + ref RowBuffer row, + ref RowCursor scope, + bool isRoot, + TypeArgumentList typeArgs, + (T1 Item1, T2 Item2) value) + { + Result r = LayoutType.TypedTuple.WriteScope(ref row, ref scope, typeArgs, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = default(T1Serializer).Write(ref row, ref childScope, false, typeArgs[0].TypeArgs, value.Item1); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + r = default(T2Serializer).Write(ref row, ref childScope, false, typeArgs[1].TypeArgs, value.Item2); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public Result Read( + ref RowBuffer row, + ref RowCursor scope, + bool isRoot, + out (T1 Item1, T2 Item2) value) + { + Result r = LayoutType.TypedTuple.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + T1 item1 = default; + T2 item2 = default; + if (childScope.MoveNext(ref row)) + { + r = default(T1Serializer).Read(ref row, ref childScope, isRoot, out item1); + if (r != Result.Success) + { + value = default; + return r; + } + + if (childScope.MoveNext(ref row)) + { + r = default(T2Serializer).Read(ref row, ref childScope, isRoot, out item2); + if (r != Result.Success) + { + value = default; + return r; + } + } + } + + value = (item1, item2); + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public sealed class TypedTupleComparer : EqualityComparer<(T1 Item1, T2 Item2)> + { + public static new readonly TypedTupleComparer Default = new TypedTupleComparer(); + + public override bool Equals((T1 Item1, T2 Item2) x, (T1 Item1, T2 Item2) y) + { + return default(T1Serializer).Comparer.Equals(x.Item1, y.Item1) && + default(T2Serializer).Comparer.Equals(x.Item2, y.Item2); + } + + public override int GetHashCode((T1 Item1, T2 Item2) obj) + { + HashCode hash = default; + hash.Add(obj.Item1, default(T1Serializer).Comparer); + hash.Add(obj.Item2, default(T2Serializer).Comparer); + return hash.ToHashCode(); + } + } + } + + public struct + TypedTupleHybridRowSerializer + : IHybridRowSerializer<(T1 Item1, T2 Item2, T3 Item3)> + where T1Serializer : struct, IHybridRowSerializer + where T2Serializer : struct, IHybridRowSerializer + where T3Serializer : struct, IHybridRowSerializer + { + public IEqualityComparer<(T1 Item1, T2 Item2, T3 Item3)> Comparer => TypedTupleComparer.Default; + + public Result Write( + ref RowBuffer row, + ref RowCursor scope, + bool isRoot, + TypeArgumentList typeArgs, + (T1 Item1, T2 Item2, T3 Item3) value) + { + Result r = LayoutType.TypedTuple.WriteScope(ref row, ref scope, typeArgs, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = default(T1Serializer).Write(ref row, ref childScope, false, typeArgs[0].TypeArgs, value.Item1); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + r = default(T2Serializer).Write(ref row, ref childScope, false, typeArgs[1].TypeArgs, value.Item2); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + r = default(T3Serializer).Write(ref row, ref childScope, false, typeArgs[2].TypeArgs, value.Item3); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public Result Read( + ref RowBuffer row, + ref RowCursor scope, + bool isRoot, + out (T1 Item1, T2 Item2, T3 Item3) value) + { + Result r = LayoutType.TypedTuple.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + T1 item1 = default; + T2 item2 = default; + T3 item3 = default; + if (childScope.MoveNext(ref row)) + { + r = default(T1Serializer).Read(ref row, ref childScope, isRoot, out item1); + if (r != Result.Success) + { + value = default; + return r; + } + + if (childScope.MoveNext(ref row)) + { + r = default(T2Serializer).Read(ref row, ref childScope, isRoot, out item2); + if (r != Result.Success) + { + value = default; + return r; + } + + if (childScope.MoveNext(ref row)) + { + r = default(T3Serializer).Read(ref row, ref childScope, isRoot, out item3); + if (r != Result.Success) + { + value = default; + return r; + } + } + } + } + + value = (item1, item2, item3); + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public sealed class TypedTupleComparer : EqualityComparer<(T1 Item1, T2 Item2, T3 Item3)> + { + public static new readonly TypedTupleComparer Default = new TypedTupleComparer(); + + public override bool Equals((T1 Item1, T2 Item2, T3 Item3) x, (T1 Item1, T2 Item2, T3 Item3) y) + { + return default(T1Serializer).Comparer.Equals(x.Item1, y.Item1) && + default(T2Serializer).Comparer.Equals(x.Item2, y.Item2) && + default(T3Serializer).Comparer.Equals(x.Item3, y.Item3); + } + + public override int GetHashCode((T1 Item1, T2 Item2, T3 Item3) obj) + { + HashCode hash = default; + hash.Add(obj.Item1, default(T1Serializer).Comparer); + hash.Add(obj.Item2, default(T2Serializer).Comparer); + hash.Add(obj.Item3, default(T3Serializer).Comparer); + return hash.ToHashCode(); + } + } + } + + public struct + TypedTupleHybridRowSerializer + : IHybridRowSerializer<(T1 Item1, T2 Item2, T3 Item3, T4 Item4)> + where T1Serializer : struct, IHybridRowSerializer + where T2Serializer : struct, IHybridRowSerializer + where T3Serializer : struct, IHybridRowSerializer + where T4Serializer : struct, IHybridRowSerializer + { + public IEqualityComparer<(T1 Item1, T2 Item2, T3 Item3, T4 Item4)> Comparer => TypedTupleComparer.Default; + + public Result Write( + ref RowBuffer row, + ref RowCursor scope, + bool isRoot, + TypeArgumentList typeArgs, + (T1 Item1, T2 Item2, T3 Item3, T4 Item4) value) + { + Result r = LayoutType.TypedTuple.WriteScope(ref row, ref scope, typeArgs, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = default(T1Serializer).Write(ref row, ref childScope, false, typeArgs[0].TypeArgs, value.Item1); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + r = default(T2Serializer).Write(ref row, ref childScope, false, typeArgs[1].TypeArgs, value.Item2); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + r = default(T3Serializer).Write(ref row, ref childScope, false, typeArgs[2].TypeArgs, value.Item3); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + r = default(T4Serializer).Write(ref row, ref childScope, false, typeArgs[3].TypeArgs, value.Item4); + if (r != Result.Success) + { + return r; + } + + childScope.MoveNext(ref row); + + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public Result Read( + ref RowBuffer row, + ref RowCursor scope, + bool isRoot, + out (T1 Item1, T2 Item2, T3 Item3, T4 Item4) value) + { + Result r = LayoutType.TypedTuple.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + value = default; + return r; + } + + T1 item1 = default; + T2 item2 = default; + T3 item3 = default; + T4 item4 = default; + if (childScope.MoveNext(ref row)) + { + r = default(T1Serializer).Read(ref row, ref childScope, isRoot, out item1); + if (r != Result.Success) + { + value = default; + return r; + } + + if (childScope.MoveNext(ref row)) + { + r = default(T2Serializer).Read(ref row, ref childScope, isRoot, out item2); + if (r != Result.Success) + { + value = default; + return r; + } + + if (childScope.MoveNext(ref row)) + { + r = default(T3Serializer).Read(ref row, ref childScope, isRoot, out item3); + if (r != Result.Success) + { + value = default; + return r; + } + + if (childScope.MoveNext(ref row)) + { + r = default(T4Serializer).Read(ref row, ref childScope, isRoot, out item4); + if (r != Result.Success) + { + value = default; + return r; + } + } + } + } + } + + value = (item1, item2, item3, item4); + scope.Skip(ref row, ref childScope); + return Result.Success; + } + + public sealed class TypedTupleComparer : EqualityComparer<(T1 Item1, T2 Item2, T3 Item3, T4 Item4)> + { + public static new readonly TypedTupleComparer Default = new TypedTupleComparer(); + + public override bool Equals((T1 Item1, T2 Item2, T3 Item3, T4 Item4) x, (T1 Item1, T2 Item2, T3 Item3, T4 Item4) y) + { + return default(T1Serializer).Comparer.Equals(x.Item1, y.Item1) && + default(T2Serializer).Comparer.Equals(x.Item2, y.Item2) && + default(T3Serializer).Comparer.Equals(x.Item3, y.Item3) && + default(T4Serializer).Comparer.Equals(x.Item4, y.Item4); + } + + public override int GetHashCode((T1 Item1, T2 Item2, T3 Item3, T4 Item4) obj) + { + HashCode hash = default; + hash.Add(obj.Item1, default(T1Serializer).Comparer); + hash.Add(obj.Item2, default(T2Serializer).Comparer); + hash.Add(obj.Item3, default(T3Serializer).Comparer); + hash.Add(obj.Item4, default(T4Serializer).Comparer); + return hash.ToHashCode(); + } + } + } +} diff --git a/dotnet/src/HybridRow/Schemas/UdtPropertyType.cs b/src/Serialization/HybridRow/Schemas/UdtPropertyType.cs similarity index 94% rename from dotnet/src/HybridRow/Schemas/UdtPropertyType.cs rename to src/Serialization/HybridRow/Schemas/UdtPropertyType.cs index d1ba9d9..7443b28 100644 --- a/dotnet/src/HybridRow/Schemas/UdtPropertyType.cs +++ b/src/Serialization/HybridRow/Schemas/UdtPropertyType.cs @@ -12,9 +12,10 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas /// nested row may be evolved independently of the outer row. Changes to the independent schema affect /// all outer schemas where the UDT is used. /// - public class UdtPropertyType : ScopePropertyType + public sealed class UdtPropertyType : ScopePropertyType { public UdtPropertyType() + : base(TypeKind.Schema) { this.SchemaId = SchemaId.Invalid; } diff --git a/src/Serialization/HybridRow/SystemSchemas/SystemSchema.json b/src/Serialization/HybridRow/SystemSchemas/SystemSchema.json new file mode 100644 index 0000000..cc125e0 --- /dev/null +++ b/src/Serialization/HybridRow/SystemSchemas/SystemSchema.json @@ -0,0 +1,614 @@ +// HybridRow System Schema +{ + "name": "Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas", + "cppNamespace": "cdb_hr", + "version": "v2", + "enums": [ + { + "name": "SchemaLanguageVersion", + "comment": "Versions of the HybridRow Schema Description Language.", + "type": "uint8", + "values": [ + { + "name": "V1", + "comment": "Initial version of the HybridRow Schema Description Language.", + "value": 0 + }, + { + "name": "V2", + "comment": "Introduced Enums, Inheritance.", + "value": 2 + }, + { + "name": "Unspecified", + "comment": "No version is specified.", + "value": 255 + } + ] + }, + { + "name": "TypeKind", + "comment": "Describes the logical type of a property.", + "type": "uint8" + }, + { + "name": "StorageKind", + "comment": "Describes the storage placement for primitive properties.", + "type": "uint8" + }, + { + "name": "SortDirection", + "comment": "Describes the sort order direction.", + "type": "uint8" + }, + { + "name": "AllowEmptyKind", + "comment": "Describes the empty canonicalization for properties.", + "type": "uint8" + } + ], + "schemas": [ + { + "name": "EmptySchema", + "id": 2147473650, + "type": "schema", + "options": { "disallowUnschematized": false }, + "properties": [] + }, + { + "name": "Segment", + "id": 2147473648, + "type": "schema", + "options": { "disallowUnschematized": false }, + "properties": [ + { + "path": "length", + "type": { "type": "int32", "storage": "fixed", "rowBufferSize": true }, + "comment": + "(Required) length (in bytes) of this RecordIO segment header itself. Does NOT include the length of the records that follow." + }, + { + "path": "comment", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "A comment describing the data in this RecordIO segment." + }, + { + // TODO: this should be converted to a HybridRow UDT instead. + "path": "sdl", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "A HybridRow Schema in SDL (json-format).", + "apiname": "SDL" + }, + { + "path": "schema", + "type": { + "type": "schema", + "storage": "sparse", + "name": "Namespace", + "id": 2147473651 + }, + "comment": "A HybridRow Schema." + } + ] + }, + { + "name": "Record", + "id": 2147473649, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "length", + "type": { "type": "int32", "storage": "fixed", "nullable": false }, + "comment": "(Required) length (in bytes) of the HybridRow value that follows this record header." + }, + { + "path": "crc32", + "type": { "type": "uint32", "storage": "fixed", "nullable": false }, + "comment": "(Optional) CRC-32 as described in ISO 3309." + } + ] + }, + { + "name": "Namespace", + "id": 2147473651, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "version", + "type": { "type": "enum", "enum": "SchemaLanguageVersion", "storage": "fixed", "nullable": false }, + "comment": "(Required) SDL language version." + }, + { + "path": "name", + "type": { "type": "utf8", "storage": "variable" }, + "comment": "(Optional) Name of the namespace." + }, + { + "path": "comment", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "(Optional) Comment field describing the namespace." + }, + { + "path": "schemas", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "Schema", + "id": 2147473652, + "nullable": false + } + }, + "comment": "The set of schemas that make up the namespace." + }, + { + "path": "enums", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "EnumSchema", + "id": 2147473668, + "nullable": false + } + }, + "comment": "The set of enums defined in the namespace." + }, + { + "path": "cppNamespace", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "An (optional) namespace to use when performing C++ codegen." + } + ] + }, + { + "name": "Schema", + "id": 2147473652, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "version", + "type": { "type": "enum", "enum": "SchemaLanguageVersion", "storage": "fixed", "nullable": false }, + "comment": "(Optional) SDL language version." + }, + { + "path": "type", + "type": { "type": "enum", "enum": "TypeKind", "storage": "fixed", "nullable": false }, + "comment": "(Required) Type of the schema element." + }, + { + "path": "id", + "type": { "type": "int32", "apitype": "SchemaId", "storage": "fixed", "nullable": false }, + "comment": "(Required) Globally unique id of the schema.", + "apiname": "SchemaId" + }, + { + "path": "name", + "type": { "type": "utf8", "storage": "variable" }, + "comment": "(Optional) Name of the schema." + }, + { + "path": "comment", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "(Optional) Comment field describing the schema." + }, + { + "path": "options", + "type": { + "type": "schema", + "name": "SchemaOptions", + "id": 2147473653 + }, + "comment": "(Optional) Schema options." + }, + { + "path": "partitionKeys", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "PartitionKey", + "id": 2147473654, + "nullable": false + } + }, + "comment": "(Optional) List of zero or more logical paths that form the partition key." + }, + { + "path": "primaryKeys", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "PrimarySortKey", + "id": 2147473655, + "nullable": false + } + }, + "comment": "(Optional) List of zero or more logical paths that form the primary sort key." + }, + { + "path": "staticKeys", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "StaticKey", + "id": 2147473656, + "nullable": false + } + }, + "comment": + "(Optional) List of zero or more logical paths that hold data shared by all documents that have the same partition key." + }, + { + "path": "properties", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "Property", + "id": 2147473657, + "nullable": false + } + }, + "comment": "(Optional) List of zero or more property definitions that define the columns within the schema." + }, + { + "path": "baseName", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "The name of the schema this schema derives from." + }, + { + "path": "baseId", + "type": { "type": "int32", "apitype": "SchemaId", "storage": "sparse" }, + "comment": "The unique identifier of the schema this schema derives from.", + "apiname": "BaseSchemaId" + } + ] + }, + { + "name": "SchemaOptions", + "comment": "Describes the set of options that apply to the entire schema and the way it is validated.", + "id": 2147473653, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { "path": "disallowUnschematized", "type": { "type": "bool", "storage": "sparse" } }, + { "path": "enablePropertyLevelTimestamp", "type": { "type": "bool", "storage": "sparse" } }, + { "path": "disableSystemPrefix", "type": { "type": "bool", "storage": "sparse" } }, + { + "path": "abstract", + "type": { "type": "bool", "storage": "sparse" }, + "comment": "If true then instances of this schema cannot be created directly, only through subtypes." + } + ] + }, + { + "name": "PartitionKey", + "id": 2147473654, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { "path": "path", "type": { "type": "utf8", "storage": "variable" } } + ] + }, + { + "name": "PrimarySortKey", + "id": 2147473655, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { "path": "path", "type": { "type": "utf8", "storage": "variable" } }, + { + "path": "direction", + "type": { "type": "enum", "enum": "SortDirection", "storage": "fixed", "nullable": false } + } + ] + }, + { + "name": "StaticKey", + "id": 2147473656, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { "path": "path", "type": { "type": "utf8", "storage": "variable" } } + ] + }, + { + "name": "Property", + "id": 2147473657, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { "path": "path", "type": { "type": "utf8", "storage": "variable" } }, + { "path": "comment", "type": { "type": "utf8", "storage": "sparse" } }, + { + "path": "type", + "type": { + "type": "schema", + "name": "PropertyType", + "id": 2147473658 + }, + "comment": + "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType.", + "apiname": "PropertyType" + }, + { "path": "apiname", "type": { "type": "utf8", "storage": "sparse" }, "apiname": "ApiName" }, + { "path": "allowEmpty", "type": { "type": "enum", "enum": "AllowEmptyKind" }, "allowEmpty": "both" } + ] + }, + { + "name": "PropertyType", + "id": 2147473658, + "type": "schema", + "options": { "disallowUnschematized": true, "abstract": true }, + "properties": [ + { "path": "apitype", "type": { "type": "utf8", "storage": "variable" }, "apiname": "ApiType" }, + { "path": "type", "type": { "type": "enum", "enum": "TypeKind", "storage": "fixed", "nullable": false } }, + { "path": "nullable", "type": { "type": "bool", "storage": "fixed", "nullable": false } } + ] + }, + { + "name": "PrimitivePropertyType", + "id": 2147473659, + "baseName": "PropertyType", + "baseId": 2147473658, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { "path": "length", "type": { "type": "int32", "storage": "fixed", "nullable": false } }, + { "path": "storage", "type": { "type": "enum", "enum": "StorageKind", "storage": "fixed", "nullable": false } }, + { "path": "enum", "type": { "type": "utf8", "storage": "sparse" }, "allowEmpty": "emptyAsNull" }, + { "path": "rowBufferSize", "type": { "type": "bool", "storage": "sparse" } } + ] + }, + { + "name": "ScopePropertyType", + "id": 2147473660, + "baseName": "PropertyType", + "baseId": 2147473658, + "type": "schema", + "options": { "disallowUnschematized": true, "abstract": true }, + "properties": [ + { "path": "immutable", "type": { "type": "bool", "storage": "fixed", "nullable": false } } + ] + }, + { + "name": "ArrayPropertyType", + "id": 2147473661, + "baseName": "ScopePropertyType", + "baseId": 2147473660, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "items", + "type": { + "type": "schema", + "name": "PropertyType", + "id": 2147473658 + }, + "comment": + "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType." + } + ] + }, + { + "name": "ObjectPropertyType", + "id": 2147473662, + "baseName": "ScopePropertyType", + "baseId": 2147473660, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "properties", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "Property", + "id": 2147473657, + "nullable": false + } + }, + "comment": "(Optional) List of zero or more property definitions that define the columns within the schema." + } + ] + }, + { + "name": "UdtPropertyType", + "id": 2147473663, + "baseName": "ScopePropertyType", + "baseId": 2147473660, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { "path": "name", "type": { "type": "utf8", "storage": "variable" } }, + { + "path": "id", + "type": { "type": "int32", "apitype": "SchemaId", "storage": "fixed", "nullable": false }, + "apiname": "SchemaId" + } + ] + }, + { + "name": "SetPropertyType", + "id": 2147473664, + "baseName": "ScopePropertyType", + "baseId": 2147473660, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "items", + "type": { + "type": "schema", + "name": "PropertyType", + "id": 2147473658 + }, + "comment": + "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType." + } + ] + }, + { + "name": "MapPropertyType", + "id": 2147473665, + "baseName": "ScopePropertyType", + "baseId": 2147473660, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "keys", + "type": { + "type": "schema", + "name": "PropertyType", + "id": 2147473658 + }, + "comment": + "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType." + }, + { + "path": "values", + "type": { + "type": "schema", + "name": "PropertyType", + "id": 2147473658 + }, + "comment": + "The type of the property. This field is polymorphic and may contain any defined subtype of PropertyType." + } + ] + }, + { + "name": "TuplePropertyType", + "id": 2147473666, + "baseName": "ScopePropertyType", + "baseId": 2147473660, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "items", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "PropertyType", + "id": 2147473658, + "nullable": false + } + }, + "comment": + "The type of the properties. This field is polymorphic and may contain any defined subtype of PropertyType." + } + ] + }, + { + "name": "TaggedPropertyType", + "id": 2147473667, + "baseName": "ScopePropertyType", + "baseId": 2147473660, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "items", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "PropertyType", + "id": 2147473658, + "nullable": false + } + }, + "comment": + "The type of the properties. This field is polymorphic and may contain any defined subtype of PropertyType." + } + ] + }, + { + "name": "EnumSchema", + "id": 2147473668, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "type", + "type": { "type": "enum", "enum": "TypeKind", "storage": "fixed", "nullable": false }, + "comment": "(Required) Type of the schema element." + }, + { + "path": "name", + "type": { "type": "utf8", "storage": "variable" }, + "comment": "(Optional) Name of the schema." + }, + { + "path": "comment", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "(Optional) Comment field describing the schema." + }, + { + "path": "apitype", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "Api-specific type annotations for the property.", + "apiname": "ApiType" + }, + { + "path": "values", + "allowEmpty": "both", + "type": { + "type": "array", + "items": { + "type": "schema", + "name": "EnumValue", + "id": 2147473669, + "nullable": false + } + }, + "comment": "(Optional) List of zero or more values." + } + ] + }, + { + "name": "EnumValue", + "id": 2147473669, + "type": "schema", + "options": { "disallowUnschematized": true }, + "properties": [ + { + "path": "name", + "type": { "type": "utf8", "storage": "variable" }, + "comment": "(Optional) Name of the schema." + }, + { + "path": "value", + "type": { "type": "int64", "storage": "fixed" }, + "comment": + "The numerical value of the enum value." + }, + { + "path": "comment", + "type": { "type": "utf8", "storage": "sparse" }, + "comment": "(Optional) Comment field describing the schema." + } + ] + } + ] +} diff --git a/dotnet/src/HybridRow/UnixDateTime.cs b/src/Serialization/HybridRow/UnixDateTime.cs similarity index 100% rename from dotnet/src/HybridRow/UnixDateTime.cs rename to src/Serialization/HybridRow/UnixDateTime.cs diff --git a/src/Serialization/HybridRowCLI/CSharp/CSharpNamespaceGenerator.cs b/src/Serialization/HybridRowCLI/CSharp/CSharpNamespaceGenerator.cs new file mode 100644 index 0000000..e1495db --- /dev/null +++ b/src/Serialization/HybridRowCLI/CSharp/CSharpNamespaceGenerator.cs @@ -0,0 +1,2260 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1009 // Closing parenthesis should be followed by a space. +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods +#pragma warning disable AsyncMethodsMustEndInAsync // Async method must end in Async + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.CSharp +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + + // ReSharper disable ArrangeStaticMemberQualifier + internal class CSharpNamespaceGenerator + { + private readonly Namespace ns; + private readonly LayoutResolver resolver; + + /// + /// Mapping from types to their descendants. The inner map includes all descendants within + /// the closure of a type and is marked 'true' if it is a direct descendent (child). + /// + private readonly Dictionary> hierarchy; + + public CSharpNamespaceGenerator(Namespace ns) + { + this.ns = ns; + this.resolver = new LayoutResolverNamespace(ns); + this.hierarchy = new Dictionary>(); + + // Build the type hierarchy in two steps: + // 1. Add all first level dependencies from parent to children. + // 2. Add all descendant dependencies from parent to all descendants. + foreach (Schema s in ns.Schemas) + { + if (s.BaseName == null) + { + continue; + } + + Schema parent = s.ResolveBase(ns); + Contract.Invariant(parent != null); + if (!this.hierarchy.TryGetValue(parent!, out Dictionary parentMap)) + { + parentMap = new Dictionary(); + this.hierarchy[parent] = parentMap; + } + parentMap.Add(s, true); + } + + foreach (Dictionary parentMap in this.hierarchy.Values) + { + IEnumerable children = from t in parentMap where t.Value select t.Key; + foreach (Schema child in children.ToList()) + { + AddDescendants(parentMap, child); + } + } + + void AddDescendants(Dictionary parentMap, Schema child) + { + if (!this.hierarchy.TryGetValue(child, out Dictionary childMap)) + { + return; + } + + IEnumerable grandchildren = from t in childMap where t.Value select t.Key; + foreach (Schema gc in grandchildren) + { + if (parentMap.ContainsKey(gc)) + { + continue; + } + parentMap[gc] = false; + AddDescendants(parentMap, gc); + } + } + } + + public async ValueTask GenerateNamespace(List excludes, Emit emit, bool includeDataContracts, bool includeEmbedSchema) + { + if (this.ns.Comment != null) + { + await emit.Comment(this.ns.Comment); + } + + await emit.Whitespace(); + await emit.Comment("ReSharper disable CheckNamespace"); + await emit.Comment("ReSharper disable InconsistentNaming"); + await emit.Comment("ReSharper disable RedundantEmptySwitchSection"); + await emit.Comment("ReSharper disable JoinDeclarationAndInitializer"); + await emit.Comment("ReSharper disable TooWideLocalVariableScope"); + await emit.Comment("ReSharper disable ArrangeStaticMemberQualifier"); + await emit.Comment("ReSharper disable RedundantJumpStatement"); + await emit.Comment("ReSharper disable RedundantUsingDirective"); + await using Emit.Scope s1 = await emit.Namespace(this.ns.Name); + await emit.Using("System"); + await emit.Using("System.Collections.Generic"); + await emit.Using("System.Runtime.CompilerServices"); + await emit.Using("Microsoft.Azure.Cosmos.Core"); + await emit.Using("Microsoft.Azure.Cosmos.Core.Utf8"); + await emit.Using("Microsoft.Azure.Cosmos.Serialization.HybridRow"); + await emit.Using("Microsoft.Azure.Cosmos.Serialization.HybridRow.IO"); + await emit.Using("Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts"); + await emit.Using("Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO"); + await emit.Using("Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas"); + + if (includeEmbedSchema) + { + await this.GenerateHrSchema(emit); + } + + if (includeDataContracts) + { + foreach (Schema s in this.ns.Schemas) + { + if (excludes.Contains(s.Name)) + { + continue; + } + + Console.WriteLine($"Generating Data Contracts: {s.Name}"); + await this.GenerateDataContracts(emit, s); + } + } + + foreach (Schema s in this.ns.Schemas) + { + if (excludes.Contains(s.Name)) + { + continue; + } + + Console.WriteLine($"Compiling Schema: {s.Name}"); + await this.GenerateSchema(emit, s); + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, Namespace item) + { + if (!string.IsNullOrEmpty(item.Name)) + { + await source.Block($"Name = \"{item.Name}\",\n"); + } + await source.Block($"Version = SchemaLanguageVersion.{item.Version},\n"); + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Block($"Comment = \"{item.Comment}\",\n"); + } + if (!string.IsNullOrEmpty(item.CppNamespace)) + { + await source.Block($"CppNamespace = \"{item.CppNamespace}\",\n"); + } + if (item.Enums.Count > 0) + { + await using (await source.Block("Enums = new List\n{", "},")) + { + await source.Whitespace(); + foreach (EnumSchema item2 in item.Enums) + { + await source.Whitespace("//////////////////////////////////////////////////////////////////////////////"); + await using Emit.Scope s1 = await source.Block( + @" + new EnumSchema + {", + "},"); + await source.Whitespace(); + await GenerateHrSchemaConstant(source, item2); + } + } + } + + if (item.Schemas.Count > 0) + { + await using (await source.Block("Schemas = new List\n{", "},")) + { + await source.Whitespace(); + foreach (Schema item2 in item.Schemas) + { + await source.Whitespace("//////////////////////////////////////////////////////////////////////////////"); + await using Emit.Scope s1 = await source.Block( + @" + new Schema + {", + "},"); + await source.Whitespace(); + await GenerateHrSchemaConstant(source, item2); + } + } + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, Schema item) + { + if (item.Version != SchemaLanguageVersion.Unspecified) + { + await source.Block($"Version = SchemaLanguageVersion::{item.Version},\n"); + } + if (!string.IsNullOrEmpty(item.Name)) + { + await source.Block($"Name = \"{item.Name}\",\n"); + } + if (item.Type != TypeKind.Schema) + { + await source.Block($"Type = TypeKind.{item.Type},\n"); + } + await source.Block($"SchemaId = new SchemaId({item.SchemaId}),\n"); + if (!string.IsNullOrEmpty(item.BaseName)) + { + await source.Block($"BaseName = \"{item.BaseName}\",\n"); + await source.Block($"BaseSchemaId = new SchemaId({item.BaseSchemaId}),\n"); + } + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Block($"Comment = \"{item.Comment}\",\n"); + } + if (item.Options != null) + { + await using Emit.Scope scope2 = await source.Block("Options = new SchemaOptions\n{", "},"); + await source.Whitespace(); + await GenerateHrSchemaConstant(source, item.Options); + } + + if (item.PartitionKeys.Count > 0) + { + await using (await source.Block("PartitionKeys = new List\n{", "},")) + { + await source.Whitespace(); + foreach (PartitionKey item2 in item.PartitionKeys) + { + await using Emit.Scope scope2 = await source.Block( + @" + new PartitionKey + {", + "},"); + await source.Whitespace(); + await GenerateHrSchemaConstant(source, item2); + } + } + } + + if (item.PrimaryKeys.Count > 0) + { + await using (await source.Block("PrimaryKeys = new List\n{", "},")) + { + await source.Whitespace(); + foreach (PrimarySortKey item2 in item.PrimaryKeys) + { + await using Emit.Scope scope2 = await source.Block( + @" + new PrimarySortKey + {", + "},"); + await source.Whitespace(); + await GenerateHrSchemaConstant(source, item2); + } + } + } + + if (item.StaticKeys.Count > 0) + { + await using (await source.Block("StaticKeys = new List\n{", "},")) + { + await source.Whitespace(); + foreach (StaticKey item2 in item.StaticKeys) + { + await using Emit.Scope scope2 = await source.Block( + @" + new StaticKey + {", + "},"); + await source.Whitespace(); + await GenerateHrSchemaConstant(source, item2); + } + } + } + + if (item.Properties.Count > 0) + { + await using (await source.Block("Properties = new List\n{", "},")) + { + await source.Whitespace(); + foreach (Property item2 in item.Properties) + { + await using Emit.Scope scope2 = await source.Block( + @" + new Property + {", + "},"); + await source.Whitespace(); + await GenerateHrSchemaConstant(source, item2); + } + } + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, SchemaOptions item) + { + if (item.DisallowUnschematized) + { + await source.Block("DisallowUnschematized = true,\n"); + } + if (item.EnablePropertyLevelTimestamp) + { + await source.Block("EnablePropertyLevelTimestamp = true,\n"); + } + if (item.DisableSystemPrefix) + { + await source.Block("DisableSystemPrefix = true,\n"); + } + if (item.Abstract) + { + await source.Block("Abstract = true,\n"); + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, PartitionKey item) + { + if (!string.IsNullOrEmpty(item.Path)) + { + await source.Block($"Path = \"{item.Path}\",\n"); + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, PrimarySortKey item) + { + if (!string.IsNullOrEmpty(item.Path)) + { + await source.Block($"Path = \"{item.Path}\",\n"); + } + if (item.Direction != SortDirection.Ascending) + { + await source.Block($"Direction = SortDirection.{item.Direction},\n"); + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, StaticKey item) + { + if (!string.IsNullOrEmpty(item.Path)) + { + await source.Block($"Path = \"{item.Path}\",\n"); + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, Property item) + { + if (!string.IsNullOrEmpty(item.Path)) + { + await source.Block($"Path = \"{item.Path}\",\n"); + } + if (item.AllowEmpty != AllowEmptyKind.None) + { + await source.Block($"AllowEmpty = AllowEmptyKind.{item.AllowEmpty},\n"); + } + if (item.PropertyType != null) + { + await using Emit.Scope scope3 = await source.Block(@"PropertyType = ", ""); + await GenerateHrSchemaConstant(source, scope3, item.PropertyType); + } + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Block($"Comment = \"{item.Comment}\",\n"); + } + if (!string.IsNullOrEmpty(item.ApiName)) + { + await source.Block($"ApiName = \"{item.ApiName}\",\n"); + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, Emit.Scope scope, PropertyType item) + { + switch (item) + { + case PrimitivePropertyType p: + await GenerateHrSchemaConstant(source, scope, p); + return; + case UdtPropertyType p: + { + await using Emit.Scope s2 = await source.Block("new UdtPropertyType\n{", "},", scope); + await source.Whitespace(); + await source.Block($"Name = \"{p.Name}\",\n"); + await source.Block($"SchemaId = new SchemaId({p.SchemaId}),\n"); + if (!p.Nullable) + { + await source.Block("Nullable = false,\n"); + } + if (p.Immutable) + { + await source.Block("Immutable = true,\n"); + } + + return; + } + case ArrayPropertyType p: + { + await using Emit.Scope s2 = await source.Block("new ArrayPropertyType\n{", "},", scope); + await source.Whitespace(); + await using (Emit.Scope s3 = await source.Block(@"Items = ", "")) + { + await GenerateHrSchemaConstant(source, s3, p.Items); + } + if (!p.Nullable) + { + await source.Block("Nullable = false,\n"); + } + if (p.Immutable) + { + await source.Block("Immutable = true,\n"); + } + return; + } + case MapPropertyType p: + { + await using Emit.Scope s2 = await source.Block("new MapPropertyType\n{", "},", scope); + await source.Whitespace(); + await using (Emit.Scope s3 = await source.Block(@"Keys = ", "")) + { + await GenerateHrSchemaConstant(source, s3, p.Keys); + } + await using (Emit.Scope s4 = await source.Block(@"Values = ", "")) + { + await GenerateHrSchemaConstant(source, s4, p.Values); + } + if (!p.Nullable) + { + await source.Block("Nullable = false,\n"); + } + if (p.Immutable) + { + await source.Block("Immutable = true,\n"); + } + return; + } + case TuplePropertyType p: + { + await using Emit.Scope s2 = await source.Block("new TuplePropertyType\n{", "},", scope); + await source.Whitespace(); + await using (await source.Block("Items = new List\n{", "},")) + { + await source.Whitespace(); + foreach (PropertyType item2 in p.Items) + { + await CSharpNamespaceGenerator.GenerateHrSchemaConstant(source, default, item2); + } + } + if (!p.Nullable) + { + await source.Block("Nullable = false,\n"); + } + if (p.Immutable) + { + await source.Block("Immutable = true,\n"); + } + return; + } + default: + Contract.Fail($"Not Yet Implemented: {item.GetType().Name}"); + return; + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, Emit.Scope scope, PrimitivePropertyType item) + { + await using Emit.Scope scope2 = await source.Block("new PrimitivePropertyType\n{", "},", scope); + await source.Whitespace(); + await source.Block($"Type = TypeKind.{item.Type},\n"); + if (item.Length != 0) + { + await source.Block($"Length = {item.Length},\n"); + } + if (item.Storage != StorageKind.Sparse) + { + await source.Block($"Storage = StorageKind.{item.Storage},\n"); + } + if (!string.IsNullOrEmpty(item.Enum)) + { + await source.Block($"Enum = \"{item.Enum}\",\n"); + } + if (item.RowBufferSize) + { + await source.Block("RowBufferSize = true,\n"); + } + if (!item.Nullable) + { + await source.Block($"Nullable = {item.Nullable.ToString().ToLowerInvariant()},\n"); + } + if (!string.IsNullOrEmpty(item.ApiType)) + { + await source.Block($"ApiType = \"{item.ApiType}\",\n"); + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, EnumSchema item) + { + if (!string.IsNullOrEmpty(item.Name)) + { + await source.Block($"Name = \"{item.Name}\",\n"); + } + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Block($"Comment = \"{item.Comment}\",\n"); + } + await source.Block($"Type = TypeKind.{item.Type},\n"); + if (!string.IsNullOrEmpty(item.ApiType)) + { + await source.Block($"ApiType = \"{item.ApiType}\",\n"); + } + if (item.Values.Count > 0) + { + await using (await source.Block("Values = new List\n{", "},")) + { + await source.Whitespace(); + foreach (EnumValue item2 in item.Values) + { + await using Emit.Scope s1 = await source.Block( + @" + new EnumValue + {", + "},"); + await source.Whitespace(); + await GenerateHrSchemaConstant(source, item2); + } + } + } + } + + private static async ValueTask GenerateHrSchemaConstant(Emit source, EnumValue item) + { + if (!string.IsNullOrEmpty(item.Name)) + { + await source.Block($"Name = \"{item.Name}\",\n"); + } + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Block($"Comment = \"{item.Comment}\",\n"); + } + await source.Block($"Value = {item.Value},\n"); + } + + private async ValueTask GenerateHrSchema(Emit emit) + { + string name = $"{this.ns.Name.IdentifierOnly()}HrSchema"; + await emit.Whitespace(); + await using Emit.Scope s1 = await emit.Class(Keywords.Internal | Keywords.Static, name); + + await emit.Variable( + Keywords.Public | Keywords.Static | Keywords.ReadOnly, + "Namespace", + "Namespace", + $"{name}.CreateSchema()"); + await emit.Variable( + Keywords.Public | Keywords.Static | Keywords.ReadOnly, + "LayoutResolver", + "LayoutResolver", + $"{name}.LoadSchema()"); + + await using (await emit.Method(Keywords.Private | Keywords.Static, "Namespace", "CreateSchema")) + { + await using (await emit.Block( + @" + return new Namespace + {", + "};")) + { + await emit.Whitespace(); + await GenerateHrSchemaConstant(emit, this.ns); + } + } + + await using (await emit.Method(Keywords.Private | Keywords.Static, "LayoutResolver", "LoadSchema")) + { + await emit.Statement($"return new LayoutResolverNamespace({name}.Namespace)"); + } + } + + private async ValueTask GenerateDataContracts(Emit emit, Schema s) + { + await emit.Whitespace(); + if (s.Comment != null) + { + await emit.DocComment(s.Comment); + } + + // If the type has a dotted name then use only the final identifier. + string name = s.Name.IdentifierOnly(); + Keywords flags = Keywords.Public; + flags |= (s.BaseName == null) ? Keywords.Sealed : 0; + await using Emit.Scope s1 = await emit.Class(flags, name, s.BaseName); + foreach (Property p in s.Properties) + { + await emit.AutoProperty(Keywords.Public, p.UnderlyingType(this.ns), p.AsPascal()); + } + } + + private async ValueTask GenerateSchema(Emit emit, Schema s) + { + await emit.Whitespace(); + if (s.Comment != null) + { + await emit.DocComment(s.Comment); + } + + // If the type has a dotted name then use only the final identifier. + string name = s.Name.IdentifierOnly(); + string typename = $"{name}HybridRowSerializer"; + await using Emit.Scope s1 = await emit.Struct(Keywords.Public | Keywords.ReadOnly, typename, $"IHybridRowSerializer<{name}>"); + await emit.Variable(Keywords.Public | Keywords.Const, "int", "SchemaId", s.SchemaId.ToString()); + await emit.Variable( + Keywords.Public | Keywords.Const, + "int", + "Size", + this.resolver.Resolve(s.SchemaId).Size.ToString()); + await emit.PropertyExpr(Keywords.Public, $"IEqualityComparer<{name}>", "Comparer", $"{name}Comparer.Default"); + + // Emit the names. + foreach (Property p in s.Properties) + { + await emit.Variable( + Keywords.Private | Keywords.Static | Keywords.ReadOnly, + "Utf8String", + $"{p.AsPascal()}Name", + $"Utf8String.TranscodeUtf16(\"{p.Path}\")"); + } + if (s.BaseName != null) + { + await emit.Variable( + Keywords.Private | Keywords.Static | Keywords.ReadOnly, + "Utf8String", + "__BaseName", + "Utf8String.TranscodeUtf16(\"__base\")"); + } + + // Emit the columns. + if (s.Properties.Count > 0) + { + await emit.Whitespace(); + } + foreach (Property p in s.Properties) + { + await emit.Variable( + Keywords.Private | Keywords.Static | Keywords.ReadOnly, + "LayoutColumn", + $"{p.AsPascal()}Column"); + } + if (s.BaseName != null) + { + await emit.Variable( + Keywords.Private | Keywords.Static | Keywords.ReadOnly, + "LayoutColumn", + "__BaseColumn"); + } + + // Emit the tokens. + if (s.HasSparse()) + { + await emit.Whitespace(); + foreach (Property p in s.Properties) + { + switch (p.PropertyType) + { + case PrimitivePropertyType pp when pp.Storage != StorageKind.Sparse: + break; // Don't need tokens for non-sparse primitives. + default: + await emit.Variable( + Keywords.Private | Keywords.Static | Keywords.ReadOnly, + "StringToken", + $"{p.AsPascal()}Token"); + break; + } + } + if (s.BaseName != null) + { + await emit.Variable( + Keywords.Private | Keywords.Static | Keywords.ReadOnly, + "StringToken", + "__BaseToken"); + } + } + + // Emit constructor. + await using (Emit.Scope unused = await emit.Method(Keywords.Static, null, typename)) + { + string hrSchemaName = $"{this.ns.Name.IdentifierOnly()}HrSchema"; + await emit.Variable("Layout", "layout", $"{hrSchemaName}.LayoutResolver.Resolve(new SchemaId(SchemaId))"); + await emit.Whitespace(); + + if ((s.Properties.Count > 0) || (s.BaseName != null)) + { + await emit.Variable("bool", "found"); + } + foreach (Property p in s.Properties) + { + string cname = p.AsPascal(); + await emit.Statement($"found = layout.TryFind({cname}Name, out {cname}Column)"); + await emit.Statement("Contract.Invariant(found)"); + } + if (s.BaseName != null) + { + await emit.Statement("found = layout.TryFind(__BaseName, out __BaseColumn)"); + await emit.Statement("Contract.Invariant(found)"); + } + + int i = 0; + foreach (Property p in s.Properties) + { + switch (p.PropertyType) + { + case PrimitivePropertyType pp when pp.Storage != StorageKind.Sparse: + break; // Don't need ids for non-sparse primitives. + default: + if (i++ == 0) + { + await emit.Whitespace(); + } + + string cname = p.AsPascal(); + await emit.Statement($"found = layout.Tokenizer.TryFindToken({cname}Column.Path, out {cname}Token)"); + await emit.Statement("Contract.Invariant(found)"); + break; + } + } + if (s.BaseName != null) + { + if (i == 0) + { + await emit.Whitespace(); + } + + await emit.Statement("found = layout.Tokenizer.TryFindToken(__BaseColumn.Path, out __BaseToken)"); + await emit.Statement("Contract.Invariant(found)"); + } + } + + await this.GenerateWriteRoot(emit, name, s); + await this.GenerateWriteProperties(emit, name, s); + await this.GenerateReadRoot(emit, name, s); + await this.GenerateReadProperties(emit, name, s); + await this.GenerateComparer(emit, name, s); + } + + private async Task GenerateWriteRoot(Emit emit, string name, Schema s) + { + this.hierarchy.TryGetValue(s, out Dictionary descendants); + await using (Emit.Scope unused = await emit.Method( + Keywords.Public, + "Result", + "Write", + $"ref RowBuffer row, ref RowCursor scope, bool isRoot, TypeArgumentList typeArgs, {name} value")) + { + if (descendants != null) + { + await using Emit.Scope unused2 = await emit.Control("switch (value)"); + foreach (Schema child in from d in descendants where d.Value select d.Key) + { + await emit.Block( + $@" + case {child.Name} p: + return default({child.Name}HybridRowSerializer) + .Write(ref row, ref scope, isRoot, typeArgs, p); + "); + } + await emit.Block( + @" + default: + break; + "); + } + + if (descendants != null) + { + await emit.Whitespace(); + } + if (s.Options?.Abstract ?? false) + { + await emit.Block( + @" + Contract.Fail(""Type is abstract.""); + return Result.Failure; + "); + } + else + { + await emit.Block( + @" + if (isRoot) + { + return Write(ref row, ref scope, value); + } + + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + "); + } + } + + if (descendants != null) + { + await using Emit.Scope unused1 = await emit.Method( + Keywords.Public | Keywords.Static, + "Result", + "WriteBase", + $"ref RowBuffer row, ref RowCursor scope, {name} value"); + await emit.Block( + @" + Result r = LayoutType.UDT.WriteScope(ref row, ref scope, new SchemaId(SchemaId), out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Write(ref row, ref childScope, value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + "); + } + } + + private async Task GenerateWriteProperties(Emit emit, string name, Schema s) + { + await using Emit.Scope unused = await emit.Method( + Keywords.Private | Keywords.Static, + "Result", + "Write", + $"ref RowBuffer row, ref RowCursor scope, {name} value"); + + await emit.Variable("Result", "r"); + + // Handle fixed properties first. + int i = 0; + Property rowBufferSizeProp = null; + foreach (Property p in s.Properties) + { + if (!(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage != StorageKind.Fixed)) + { + continue; + } + + // Defer RowBufferSize properties till the end. + if (pp.RowBufferSize) + { + rowBufferSizeProp = p; + continue; + } + + if (i++ != 0) + { + await emit.Whitespace(); + } + string cname = p.AsPascal(); + await using Emit.Scope unused1 = await emit.Control($"if ({NullCheck(p)})"); + await emit.Statement($"r = {p.Instance(this.ns)}.WriteFixed(ref row, ref scope, {cname}Column, {p.Cast(this.ns)}value.{cname})"); + await emit.Block( + @" + if (r != Result.Success) + { + return r; + } + "); + } + + // Handle variable properties. + foreach (Property p in s.Properties) + { + if (!(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage != StorageKind.Variable)) + { + continue; + } + + if (i++ != 0) + { + await emit.Whitespace(); + } + string cname = p.AsPascal(); + await using Emit.Scope unused1 = await emit.Control($"if ({NullCheck(p)})"); + await emit.Statement($"r = {p.Instance(this.ns)}.WriteVariable(ref row, ref scope, {cname}Column, {p.Cast(this.ns)}value.{cname})"); + await emit.Block( + @" + if (r != Result.Success) + { + return r; + } + "); + } + + // Handle sparse properties. + foreach (Property p in s.Properties) + { + if ((p.PropertyType is PrimitivePropertyType pp1) && (pp1.Storage != StorageKind.Sparse)) + { + continue; + } + + if (i++ != 0) + { + await emit.Whitespace(); + } + string cname = p.AsPascal(); + await using Emit.Scope unused1 = await emit.Control($"if ({NullCheck(p)})"); + await emit.Statement($"scope.Find(ref row, {cname}Column.Path)"); + switch (p.PropertyType) + { + case PrimitivePropertyType _: + await emit.Statement($@"r = {p.Instance(this.ns)}.WriteSparse(ref row, ref scope, {p.Cast(this.ns)}value.{cname})"); + break; + case ArrayPropertyType apt: + { + string arrayType = + (apt.Items is UdtPropertyType upt && this.hierarchy.ContainsKey(upt.Resolve(this.ns))) + ? "Array" + : "TypedArray"; + await emit.Statement( + $@"r = default({arrayType}HybridRowSerializer<{apt.Items.UnderlyingType(this.ns)}, {apt.Items.UnderlyingTypeSerializer(this.ns)}>).Write( + ref row, + ref scope, + false, + {p.AsPascal()}Column.TypeArgs, + {p.Cast(this.ns)}value.{p.AsPascal()})"); + break; + } + case MapPropertyType apt: + { + bool hasInheritance = + (apt.Keys is UdtPropertyType upt1 && this.hierarchy.ContainsKey(upt1.Resolve(this.ns))) || + (apt.Keys is UdtPropertyType upt2 && this.hierarchy.ContainsKey(upt2.Resolve(this.ns))); + Contract.Invariant(!hasInheritance, "Inheritance is not supported in maps"); + await emit.Statement( + $@"r = default(TypedMapHybridRowSerializer< + {apt.Keys.UnderlyingType(this.ns)}, {apt.Keys.UnderlyingTypeSerializer(this.ns)}, + {apt.Values.UnderlyingType(this.ns)}, {apt.Values.UnderlyingTypeSerializer(this.ns)} + >).Write( + ref row, + ref scope, + false, + {p.AsPascal()}Column.TypeArgs, + {p.Cast(this.ns)}value.{p.AsPascal()})"); + break; + } + case TuplePropertyType tpt: + { + await emit.Statement( + $@"r = default({tpt.UnderlyingTypeSerializer(this.ns)}).Write( + ref row, + ref scope, + false, + {p.AsPascal()}Column.TypeArgs, + {p.Cast(this.ns)}value.{p.AsPascal()})"); + break; + } + case UdtPropertyType _: + await emit.Statement( + $@"r = default({p.UnderlyingTypeSerializer(this.ns)}) + .Write(ref row, ref scope, false, {p.AsPascal()}Column.TypeArgs, {p.Cast(this.ns)}value.{p.AsPascal()})"); + break; + default: + throw new NotImplementedException(); + } + await emit.Block( + @" + if (r != Result.Success) + { + return r; + } + "); + } + if (s.BaseName != null) + { + if (i++ != 0) + { + await emit.Whitespace(); + } + await using Emit.Scope unused1 = await emit.Braces(); + await emit.Block( + $@" + scope.Find(ref row, __BaseColumn.Path); + r = {s.BaseName}HybridRowSerializer.WriteBase(ref row, ref scope, value); + if (r != Result.Success) + {{ + return r; + }} + "); + } + + // Emit the RowBufferSize property last (if it exists). + if (rowBufferSizeProp != null) + { + Property p = rowBufferSizeProp; + if (i != 0) + { + await emit.Whitespace(); + } + string cname = p.AsPascal(); + await emit.Comment("Emit RowBufferSize field with actual size of RowBuffer."); + await emit.Statement($"r = {p.Instance(this.ns)}.WriteFixed(ref row, ref scope, {cname}Column, row.Length)"); + await emit.Block( + @" + if (r != Result.Success) + { + return r; + } + "); + } + + await emit.Whitespace(); + await emit.Statement("return Result.Success"); + + static string NullCheck(Property p) + { + string cname = p.AsPascal(); + if ((p.AllowEmpty & AllowEmptyKind.EmptyAsNull) == 0) + { + return $"value.{cname} != default"; + } + return p.PropertyType.Type switch + { + TypeKind.Utf8 => $"!string.IsNullOrEmpty(value.{cname})", + TypeKind.Binary => $"(value.{cname} != null) && (value.{cname}.Length > 0)", + TypeKind.Array => $"(value.{cname} != null) && (value.{cname}.Count > 0)", + TypeKind.Set => $"(value.{cname} != null) && (value.{cname}.Count > 0)", + TypeKind.Map => $"(value.{cname} != null) && (value.{cname}.Count > 0)", + TypeKind.Tuple => $"(value.{cname} != null) && (value.{cname}.Count > 0)", + TypeKind.Tagged => $"(value.{cname} != null) && (value.{cname}.Count > 0)", + _ => $"value.{cname} != default", + }; + } + } + + private async Task GenerateReadRoot(Emit emit, string name, Schema s) + { + this.hierarchy.TryGetValue(s, out Dictionary descendants); + await using (Emit.Scope unused = await emit.Method( + Keywords.Public, + "Result", + "Read", + $"ref RowBuffer row, ref RowCursor scope, bool isRoot, out {name} value")) + { + if (descendants != null) + { + await emit.Block( + @" + if (!(scope.TypeArg.Type is LayoutUDT)) + { + value = default; + return Result.TypeMismatch; + } + + "); + + int i = 0; + await using Emit.Scope unused2 = await emit.Control("switch (scope.TypeArg.TypeArgs.SchemaId.Id)"); + foreach (Schema child in from d in descendants where d.Value select d.Key) + { + if (i++ != 0) + { + await emit.Whitespace(); + } + + if (this.hierarchy.TryGetValue(child, out Dictionary childMap)) + { + foreach (Schema grandchild in childMap.Keys) + { + await emit.Block( + $@" + case {grandchild.Name}HybridRowSerializer.SchemaId: + "); + } + } + + await emit.Block( + $@" + case {child.Name}HybridRowSerializer.SchemaId: + "); + await using Emit.Scope unused3 = await emit.Braces(); + await emit.Block( + $@" + Result r = default({child.Name}HybridRowSerializer) + .Read(ref row, ref scope, false, out {child.Name} fieldValue); + value = fieldValue; + return r; + "); + } + await emit.Whitespace(); + await emit.Block( + @" + default: + break; + "); + } + + if (descendants != null) + { + await emit.Whitespace(); + } + if (s.Options?.Abstract ?? false) + { + await emit.Block( + @" + Contract.Fail(""Type is abstract.""); + value = default; + return Result.Failure; + "); + } + else + { + await emit.Block( + $@" + if (isRoot) + {{ + value = new {name}(); + return Read(ref row, ref scope, ref value); + }} + + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + {{ + value = default; + return r; + }} + + value = new {name}(); + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + {{ + value = default; + return r; + }} + + scope.Skip(ref row, ref childScope); + return Result.Success; + "); + } + } + + if (descendants != null) + { + await using Emit.Scope unused1 = await emit.Method( + Keywords.Public | Keywords.Static, + "Result", + "ReadBase", + $"ref RowBuffer row, ref RowCursor scope, ref {name} value"); + await emit.Block( + @" + Result r = LayoutType.UDT.ReadScope(ref row, ref scope, out RowCursor childScope); + if (r != Result.Success) + { + return r; + } + + r = Read(ref row, ref childScope, ref value); + if (r != Result.Success) + { + return r; + } + + scope.Skip(ref row, ref childScope); + return Result.Success; + "); + } + } + + private async Task GenerateReadProperties(Emit emit, string name, Schema s) + { + await using Emit.Scope unused = await emit.Method( + Keywords.Private | Keywords.Static, + "Result", + "Read", + $"ref RowBuffer row, ref RowCursor scope, ref {name} value"); + + await emit.Variable("Result", "r"); + + // Handle fixed properties first. + int i = 0; + foreach (Property p in s.Properties) + { + if (!(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage != StorageKind.Fixed)) + { + continue; + } + + string cname = p.AsPascal(); + if (i++ != 0) + { + await emit.Whitespace(); + } + await using Emit.Scope unused1 = await emit.Braces(); + await emit.Statement( + $"r = {p.Instance(this.ns)}.ReadFixed(ref row, ref scope, {cname}Column, out {p.UnderlyingType(this.ns, true)} fieldValue)"); + await emit.Block( + $@" + switch (r) + {{ + case Result.NotFound: + break; + case Result.Success: + value.{cname} = {p.RCast(this.ns)}fieldValue; + break; + default: + return r; + }} + "); + } + + // Handle variable properties. + foreach (Property p in s.Properties) + { + if (!(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage != StorageKind.Variable)) + { + continue; + } + + string cname = p.AsPascal(); + if (i++ != 0) + { + await emit.Whitespace(); + } + await using Emit.Scope unused1 = await emit.Braces(); + await emit.Statement( + $"r = {p.Instance(this.ns)}.ReadVariable(ref row, ref scope, {cname}Column, out {p.UnderlyingType(this.ns, true)} fieldValue)"); + await emit.Block( + $@" + switch (r) + {{ + case Result.NotFound: + break; + case Result.Success: + value.{cname} = {p.RCast(this.ns)}fieldValue; + break; + default: + return r; + }} + "); + } + + if (s.HasSparse()) + { + if (i != 0) + { + i = 0; + await emit.Whitespace(); + } + await using Emit.Scope unused1 = await emit.Control("while (scope.MoveNext(ref row))"); + + // Handle sparse properties. + foreach (Property p in s.Properties) + { + if ((p.PropertyType is PrimitivePropertyType pp) && (pp.Storage != StorageKind.Sparse)) + { + continue; + } + + string cname = p.AsPascal(); + if (i++ != 0) + { + await emit.Whitespace(); + } + + await using Emit.Scope unused2 = await emit.Control($"if (scope.Token == {cname}Token.Id)"); + switch (p.PropertyType) + { + case ArrayPropertyType ap: + { + string arrayType = + (ap.Items is UdtPropertyType upt && this.hierarchy.ContainsKey(upt.Resolve(this.ns))) + ? "Array" + : "TypedArray"; + await emit.Statement( + $@"r = default({arrayType}HybridRowSerializer<{ap.Items.UnderlyingType(this.ns)}, {ap.Items.UnderlyingTypeSerializer(this.ns)}>) + .Read(ref row, ref scope, false, out {p.UnderlyingType(this.ns)} fieldValue)"); + break; + } + case MapPropertyType mp: + { + bool hasInheritance = + (mp.Keys is UdtPropertyType upt1 && this.hierarchy.ContainsKey(upt1.Resolve(this.ns))) || + (mp.Keys is UdtPropertyType upt2 && this.hierarchy.ContainsKey(upt2.Resolve(this.ns))); + Contract.Invariant(!hasInheritance, "Inheritance is not supported in maps"); + await emit.Statement( + $@"r = default(TypedMapHybridRowSerializer< + {mp.Keys.UnderlyingType(this.ns)}, {mp.Keys.UnderlyingTypeSerializer(this.ns)}, + {mp.Values.UnderlyingType(this.ns)}, {mp.Values.UnderlyingTypeSerializer(this.ns)} + >).Read(ref row, ref scope, false, out {p.UnderlyingType(this.ns)} fieldValue)"); + break; + } + case TuplePropertyType tpt: + { + string args = string.Join( + ", ", + tpt.Items.Select(x => $"{x.UnderlyingType(this.ns)}, {x.UnderlyingTypeSerializer(this.ns)}")); + await emit.Statement( + $@"r = default(TypedTupleHybridRowSerializer<{args}>) + .Read(ref row, ref scope, false, out {p.UnderlyingType(this.ns)} fieldValue)"); + break; + } + case UdtPropertyType upt: + await emit.Statement( + $@"r = default({upt.UnderlyingTypeSerializer(this.ns)}) + .Read(ref row, ref scope, false, out {p.UnderlyingType(this.ns)} fieldValue)"); + break; + default: + await emit.Statement( + $@"r = {p.Instance(this.ns)}.ReadSparse(ref row, ref scope, out {p.UnderlyingType(this.ns)} fieldValue)"); + break; + } + await emit.Block( + $@" + if (r != Result.Success) + {{ + return r; + }} + + value.{cname} = {p.RCast(this.ns)}fieldValue; + continue; + "); + } + if (s.BaseName != null) + { + if (i++ != 0) + { + await emit.Whitespace(); + } + await using Emit.Scope unused2 = await emit.Control("if (scope.Token == __BaseToken.Id)"); + await emit.Block( + $@" + {s.BaseName} baseValue = value; + r = {s.BaseName}HybridRowSerializer.ReadBase(ref row, ref scope, ref baseValue); + if (r != Result.Success) + {{ + return r; + }} + + Contract.Assert(baseValue == value); + continue; + "); + } + } + + if (i != 0) + { + await emit.Whitespace(); + } + await emit.Statement("return Result.Success"); + } + + private async Task GenerateComparer(Emit emit, string name, Schema s) + { + this.hierarchy.TryGetValue(s, out Dictionary descendants); + await emit.Whitespace(); + string typename = $"{name}Comparer"; + await using Emit.Scope s1 = await emit.Class(Keywords.Public | Keywords.Sealed, typename, $"EqualityComparer<{name}>"); + await emit.Variable(Keywords.Public | Keywords.Static | Keywords.New | Keywords.ReadOnly, typename, "Default", $"new {typename}()"); + + await using (Emit.Scope unused = await emit.Method( + Keywords.Public | Keywords.Override, + "bool", + "Equals", + $"{name} x, {name} y")) + { + await emit.Block( + @" + HybridRowSerializer.EqualityReferenceResult refCheck = HybridRowSerializer.EqualityReferenceCheck(x, y); + if (refCheck != HybridRowSerializer.EqualityReferenceResult.Unknown) + { + return refCheck == HybridRowSerializer.EqualityReferenceResult.Equal; + } + "); + + if (descendants != null) + { + await using (Emit.Scope unused2 = await emit.Control("switch (x)")) + { + foreach (Schema child in from d in descendants where d.Value select d.Key) + { + await emit.Block( + $@" + case {child.Name} p: + return default({child.Name}HybridRowSerializer) + .Comparer.Equals(p, ({child.Name})y); + "); + } + await emit.Block( + @" + default: + break; + "); + } + + if (s.Options?.Abstract ?? false) + { + await emit.Block( + @" + Contract.Fail(""Type is abstract.""); + return false; + "); + } + else + { + await emit.Block(@"return EqualsBase(x, y);"); + } + } + else + { + await this.GenerateComparerEquals(emit, s); + } + } + + if (descendants != null) + { + await emit.Whitespace(); + await emit.Block("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); + await using Emit.Scope unused = await emit.Method( + Keywords.Internal | Keywords.Static, + "bool", + "EqualsBase", + $"{name} x, {name} y"); + await this.GenerateComparerEquals(emit, s); + } + + // public override int GetHashCode(Address obj) + await using (Emit.Scope unused = await emit.Method( + Keywords.Public | Keywords.Override, + "int", + "GetHashCode", + $"{name} obj")) + { + if (descendants != null) + { + await using (Emit.Scope unused2 = await emit.Control("switch (obj)")) + { + foreach (Schema child in from d in descendants where d.Value select d.Key) + { + await emit.Block( + $@" + case {child.Name} p: + return default({child.Name}HybridRowSerializer) + .Comparer.GetHashCode(p); + "); + } + await emit.Block( + @" + default: + break; + "); + } + + if (s.Options?.Abstract ?? false) + { + await emit.Block( + @" + Contract.Fail(""Type is abstract.""); + return 0; + "); + } + else + { + await emit.Block(@"return GetHashCodeBase(x, y);"); + } + } + else + { + await this.GenerateComparerGetHashCode(emit, s); + } + } + + if (descendants != null) + { + await emit.Whitespace(); + await emit.Block("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); + await using Emit.Scope unused = await emit.Method( + Keywords.Internal | Keywords.Static, + "int", + "GetHashCodeBase", + $"{name} obj"); + await this.GenerateComparerGetHashCode(emit, s); + } + } + + private async Task GenerateComparerEquals(Emit emit, Schema s) + { + await emit.Whitespace(); + if (s.Properties.Count == 0) + { + if (s.BaseName != null) + { + await emit.Block( + $@" + return {s.BaseName}HybridRowSerializer + .{s.BaseName}Comparer.EqualsBase(x, y); + "); + } + else + { + await emit.Statement("return true"); + } + } + else if ((s.Properties.Count == 1) && (s.BaseName == null)) + { + Property p = s.Properties[0]; + await emit.Statement( + $"return default({p.UnderlyingTypeSerializer(this.ns)}).Comparer.Equals({p.Cast(this.ns)}x.{p.AsPascal()}, {p.Cast(this.ns)}y.{p.AsPascal()})"); + } + else + { + await using Emit.Scope s2 = await emit.Block("return\n", ");"); + int i = 0; + if (s.BaseName != null) + { + i++; + await emit.Block( + $@"{s.BaseName}HybridRowSerializer.{s.BaseName}Comparer.EqualsBase(x, y"); + } + foreach (Property p in s.Properties) + { + if (i++ != 0) + { + await emit.Block(") && \n", s2); + } + await emit.Block( + $"default({p.UnderlyingTypeSerializer(this.ns)}).Comparer.Equals({p.Cast(this.ns)}x.{p.AsPascal()}, {p.Cast(this.ns)}y.{p.AsPascal()}"); + } + } + } + + private async Task GenerateComparerGetHashCode(Emit emit, Schema s) + { + if (s.Properties.Count == 0) + { + if (s.BaseName != null) + { + await emit.Block( + $@" + return {s.BaseName}HybridRowSerializer + .{s.BaseName}Comparer.EqualsBase(x, y); + "); + } + else + { + await emit.Statement("return 0"); + } + } + else if ((s.Properties.Count == 1) && (s.BaseName == null)) + { + Property p = s.Properties[0]; + await emit.Statement( + $"return default({p.UnderlyingTypeSerializer(this.ns)}).Comparer.GetHashCode({p.Cast(this.ns)}obj.{p.AsPascal()})"); + } + else if (s.Properties.Count < 8) + { + await using Emit.Scope s2 = await emit.Block("return HashCode.Combine(\n", "));"); + int i = 0; + if (s.BaseName != null) + { + i++; + await emit.Block( + $@"{s.BaseName}HybridRowSerializer.{s.BaseName}Comparer.GetHashCodeBase(obj"); + } + foreach (Property p in s.Properties) + { + if (i++ != 0) + { + await emit.Block("),\n", s2); + } + await emit.Block( + $"default({p.UnderlyingTypeSerializer(this.ns)}).Comparer.GetHashCode({p.Cast(this.ns)}obj.{p.AsPascal()}"); + } + } + else + { + await emit.Statement("HashCode hash = default"); + foreach (Property p in s.Properties) + { + if (s.BaseName != null) + { + await emit.Statement( + $"hash.Add({s.BaseName}HybridRowSerializer.{s.BaseName}Comparer.GetHashCodeBase(obj))"); + } + await emit.Statement( + $"hash.Add({p.Cast(this.ns)}obj.{p.AsPascal()}, default({p.UnderlyingTypeSerializer(this.ns)}).Comparer)"); + } + await emit.Statement("return hash.ToHashCode()"); + } + } + + [Flags] + internal enum Keywords + { + None = 0, + Public = 0x1, + Private = 0x2, + Internal = 0x4, + Static = 0x8, + ReadOnly = 0x10, + Const = 0x20, + Sealed = 0x40, + New = 0x80, + Override = 0x100, + } + + internal sealed class Emit : IAsyncDisposable + { + private readonly TextWriter writer; + + /// Number of spaces indent is increased for each scope. + private readonly int step; + + /// Current indent. + private int indent; + + public Emit(Stream stm) + { + this.writer = new StreamWriter(stm, new UTF8Encoding(false, true)); + this.step = 4; + this.indent = 0; + } + + public async ValueTask Struct(Keywords keywords, string identifier, string baseIdentifier = null) + { + await this.Indent(); + await this.Modifiers(keywords); + if (baseIdentifier is null) + { + await this.writer.WriteLineAsync($"struct {identifier}"); + } + else + { + await this.writer.WriteLineAsync($"struct {identifier} : {baseIdentifier}"); + } + return await this.Braces(); + } + + public async ValueTask Class(Keywords keywords, string identifier, string baseIdentifier = null) + { + await this.Indent(); + await this.Modifiers(keywords); + if (baseIdentifier is null) + { + await this.writer.WriteLineAsync($"class {identifier}"); + } + else + { + await this.writer.WriteLineAsync($"class {identifier} : {baseIdentifier}"); + } + return await this.Braces(); + } + + public async ValueTask FileHeader() + { + await this.WriteLine("// ------------------------------------------------------------"); + await this.WriteLine("// Copyright (c) Microsoft Corporation. All rights reserved."); + await this.WriteLine("// ------------------------------------------------------------"); + await this.Whitespace(); + await this.WriteLine("// ------------------------------------------------------------"); + await this.WriteLine("// This file was generated by:"); + AssemblyName asm = Assembly.GetEntryAssembly().GetName(); + await this.WriteLine($"// {asm.Name}: {asm.Version}"); + await this.WriteLine("//"); + await this.WriteLine("// This file should not be modified directly."); + await this.WriteLine("// ------------------------------------------------------------"); + } + + public async ValueTask Whitespace(string comment = null) + { + if (comment == null) + { + await this.WriteLine(); + return; + } + + foreach (string line in comment.Split('\n')) + { + await this.WriteLine(line); + } + } + + public async ValueTask Comment(string comment = null) + { + if (comment == null) + { + await this.WriteLine(); + return; + } + + foreach (string line in comment.Split('\n')) + { + await this.WriteLine("// {0}", line); + } + } + + public async ValueTask DocComment(string comment) + { + await this.WriteLine("/// "); + foreach (string line in comment.Split('\n')) + { + await this.WriteLine("/// {0}", line); + } + await this.WriteLine("/// "); + } + + public async ValueTask Namespace(string identifier) + { + await this.WriteLine("namespace {0}", identifier); + return await this.Braces(); + } + + public ValueTask Using(string identifier) + { + return this.WriteLine("using {0};", identifier); + } + + public async ValueTask Pragma(string warning, string comment = null) + { + await this.Indent(); + + if (comment == null) + { + await this.writer.WriteLineAsync($"#pragma warning disable {warning}"); + } + else + { + await this.writer.WriteLineAsync($"#pragma warning disable {warning} // {comment}"); + } + } + + public ValueTask Variable(string typename, string identifier, string expr = null) + { + return this.Variable(CSharpNamespaceGenerator.Keywords.None, typename, identifier, expr); + } + + public async ValueTask Variable(Keywords keywords, string typename, string identifier, string expr = null) + { + await this.Indent(); + await this.Modifiers(keywords); + + if (expr == null) + { + await this.writer.WriteLineAsync($"{typename} {identifier};"); + } + else + { + await this.writer.WriteAsync($"{typename} {identifier} = "); + await this.Expr(expr); + await this.writer.WriteLineAsync(";"); + } + } + + public async ValueTask PropertyExpr(Keywords keywords, string typename, string identifier, string expr) + { + await this.Indent(); + await this.Modifiers(keywords); + await this.writer.WriteAsync($"{typename} {identifier} => "); + await this.Expr(expr); + await this.writer.WriteLineAsync(";"); + } + + public async ValueTask AutoProperty(Keywords keywords, string typename, string identifier) + { + await this.Indent(); + await this.Modifiers(keywords); + await this.writer.WriteLineAsync($"{typename} {identifier} {{ get; set; }}"); + } + + public async ValueTask Statement(string expr) + { + await this.Indent(); + await this.Expr(expr); + await this.writer.WriteLineAsync(";"); + } + + public async ValueTask Control(string expr) + { + await this.Indent(); + await this.Expr(expr); + await this.writer.WriteLineAsync(); + return await this.Braces(); + } + + public async ValueTask Block(string startBlock, string endBlock, Scope outer = null) + { + bool isNested = outer != null; + int blockStep = this.step; + if (isNested) + { + // If this is a nested block that beings with a newline, then don't + // align it with the parent (the newline indicates an explicit desire + // for the nested block to have its own indent). Otherwise a nested + // block begins on the parent's first line and should adopt its indent. + string firstLine = startBlock.Replace("\r\n", "\n").Split("\n").FirstOrDefault(); + if (firstLine != null && !string.IsNullOrWhiteSpace(firstLine)) + { + blockStep = outer.Indent; + this.indent -= blockStep; + outer.Indent = 0; + } + } + await this.Block(startBlock, isNested); + + this.indent += blockStep; + if (isNested) + { + outer.HasNest = true; + } + return new Scope(this, blockStep, endBlock, isNested); + } + + public async ValueTask Block(string block, Scope outer) + { + await this.Block(block, outer != null); + if (outer != null) + { + outer.HasNest = true; + } + } + + public ValueTask Block(string block) + { + return this.Block(block, false); + } + + public async ValueTask Method(Keywords keywords, string typename, string identifier, string parameters = null) + { + await this.Whitespace(); + await this.Indent(); + await this.Modifiers(keywords); + + if (typename != null) + { + await this.writer.WriteAsync($"{typename} "); + } + + if (parameters == null) + { + await this.writer.WriteLineAsync($"{identifier}()"); + } + else + { + await this.writer.WriteLineAsync($"{identifier}({parameters})"); + } + return await this.Braces(); + } + + public ValueTask DisposeAsync() + { + return this.writer.DisposeAsync(); + } + + public async ValueTask Braces() + { + await this.WriteLine("{"); + this.indent += this.step; + return new Scope(this, this.step, "}"); + } + + private async ValueTask Block(string block, bool isNested) + { + block = block.Replace("\r\n", "\n"); + int i = 0; + int trim = 0; + foreach (string line in block.Split("\n")) + { + // Skip leading empty lines (unless nested). + if (string.IsNullOrWhiteSpace(line) && i == 0 && !isNested) + { + continue; + } + + string trimmed; + if (i++ == 0) + { + trimmed = line.TrimStart(); + trim = line.Length - trimmed.Length; + } + else + { + await this.writer.WriteLineAsync(); + trimmed = (line.Length >= trim) ? line[trim..] : line; + } + if (!string.IsNullOrWhiteSpace(trimmed)) + { + if (!isNested || i > 1) + { + await this.Indent(); + } + await this.writer.WriteAsync(trimmed); + } + } + } + + private async ValueTask Expr(string expr) + { + expr = expr.Replace("\r\n", "\n"); + int i = 0; + this.indent += this.step; + foreach (string line in expr.Split("\n")) + { + if (i++ != 0) + { + await this.writer.WriteLineAsync(); + await this.Indent(); + } + await this.writer.WriteAsync(line.Trim()); + } + this.indent -= this.step; + } + + private ValueTask Indent() + { + return new ValueTask(this.writer.WriteAsync(new string(' ', this.indent))); + } + + private async ValueTask Modifiers(Keywords keywords) + { + if ((keywords & Keywords.Public) != 0) + { + await this.writer.WriteAsync("public "); + } + if ((keywords & Keywords.Private) != 0) + { + await this.writer.WriteAsync("private "); + } + if ((keywords & Keywords.Internal) != 0) + { + await this.writer.WriteAsync("internal "); + } + if ((keywords & Keywords.Static) != 0) + { + await this.writer.WriteAsync("static "); + } + if ((keywords & Keywords.Override) != 0) + { + await this.writer.WriteAsync("override "); + } + if ((keywords & Keywords.New) != 0) + { + await this.writer.WriteAsync("new "); + } + if ((keywords & Keywords.ReadOnly) != 0) + { + await this.writer.WriteAsync("readonly "); + } + if ((keywords & Keywords.Const) != 0) + { + await this.writer.WriteAsync("const "); + } + if ((keywords & Keywords.Sealed) != 0) + { + await this.writer.WriteAsync("sealed "); + } + } + + private async ValueTask WriteLine(string format, params object[] args) + { + await this.Indent(); + await this.writer.WriteLineAsync(string.Format(format, args)); + } + + private async ValueTask WriteLine(string format) + { + await this.Indent(); + await this.writer.WriteLineAsync(format); + } + + private ValueTask WriteLine() + { + return new ValueTask(this.writer.WriteLineAsync()); + } + + public sealed class Scope : IAsyncDisposable + { + private readonly Emit parent; + private readonly string closer; + private readonly bool isNested; + + public Scope(Emit parent, int indent, string closer, bool isNested = false) + { + this.parent = parent; + this.Indent = indent; + this.closer = closer; + this.isNested = isNested; + } + + public bool HasNest { get; set; } + + public int Indent { get; set; } + + public async ValueTask DisposeAsync() + { + this.parent.indent -= this.Indent; + if (!this.HasNest) + { + await this.parent.Indent(); + } + if (this.isNested) + { + await this.parent.writer.WriteAsync(this.closer); + } + else + { + await this.parent.writer.WriteLineAsync(this.closer); + } + } + } + } + } + + internal static class CodeGenExtensions + { + public static string AsPascal(this Property p) + { + if (!string.IsNullOrEmpty(p.ApiName)) + { + return p.ApiName; + } + + string identifier = p.Path; + return identifier[..1].ToUpperInvariant() + identifier[1..]; + } + + public static string IdentifierOnly(this string fullyQualifiedIdentifier) + { + // If the type has a dotted name then use only the final identifier. + string identifier = fullyQualifiedIdentifier; + int index = identifier.LastIndexOf('.'); + if (index != -1) + { + identifier = identifier[(index + 1)..]; + } + return identifier; + } + + public static string Instance(this Property p, Namespace ns) + { + return p.PropertyType switch + { + PrimitivePropertyType pp when pp.Type == TypeKind.Enum => + $"LayoutType.{Enum.GetName(typeof(TypeKind), pp.ResolveEnum(ns))}", + PrimitivePropertyType pp => $"LayoutType.{Enum.GetName(typeof(TypeKind), pp.Type)}", + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string Cast(this Property p, Namespace ns) + { + // Don't use nullable types for top-level nullable fields (instead use default-as-null semantics). + if (p.PropertyType.Nullable && p.Path == null) + { + return p.PropertyType switch + { + PrimitivePropertyType { Type: TypeKind.Utf8 } => "", + PrimitivePropertyType { Type: TypeKind.Binary } => "", + PrimitivePropertyType pp => $"({pp.UnderlyingType(ns, true)})", + _ => "", + }; + } + + return p.PropertyType switch + { + PrimitivePropertyType pp when !string.IsNullOrEmpty(pp.ApiType) => $"({pp.UnderlyingType(ns, true)})", + PrimitivePropertyType { Type: TypeKind.Enum } pp => $"({pp.UnderlyingType(ns, true)})", + _ => "", + }; + } + + public static string RCast(this Property p, Namespace ns) + { + return p.PropertyType switch + { + PrimitivePropertyType pp when !string.IsNullOrEmpty(pp.ApiType) => $"({pp.ApiType})", + PrimitivePropertyType { Type: TypeKind.Enum } pp => $"({pp.Enum})", + _ => "", + }; + } + + public static TypeKind ResolveEnum(this PrimitivePropertyType ep, Namespace ns) + { + EnumSchema enumSchema = ns.Enums.Find(es => es.Name == ep.Enum); + Contract.Invariant(enumSchema != null); + return enumSchema.Type; + } + + public static string UnderlyingType(this Property p, Namespace ns, bool stripNullable = false) + { + // Don't use nullable types for top-level nullable fields (instead use default-as-null semantics). + return p.PropertyType.UnderlyingType(ns, stripNullable || p.Path != null); + } + + public static string UnderlyingType(this PropertyType p, Namespace ns, bool stripNullable = false) + { + bool nullable = !stripNullable && p.Nullable; + return p switch + { + PrimitivePropertyType pp when pp.Type == TypeKind.Enum => + ns.Enums.Find(es => es.Name == (p as PrimitivePropertyType).Enum).Type.UnderlyingType(nullable), + PrimitivePropertyType pp => pp.Type.UnderlyingType(nullable), + ArrayPropertyType ap => (ap.Items is null) ? "List" : $"List<{ap.Items.UnderlyingType(ns)}>", + MapPropertyType mp when !((mp.Keys is null) || (mp.Values is null)) => + $"Dictionary<{mp.Keys.UnderlyingType(ns)}, {mp.Values.UnderlyingType(ns)}>", + TuplePropertyType ap when !(ap.Items is null) => + $"({string.Join(", ", ap.Items.Select(x => x.UnderlyingType(ns)))})", + UdtPropertyType up => up.Name.IdentifierOnly(), + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string UnderlyingType(this TypeKind type, bool nullable) + { + return type switch + { + TypeKind.Binary => "byte[]", + TypeKind.Boolean => nullable ? "bool?" : "bool", + TypeKind.DateTime => nullable ? "DateTime?" : "DateTime", + TypeKind.Decimal => nullable ? "Decimal?" : "Decimal", + TypeKind.Float128 => nullable ? "Float128?" : "Float128", + TypeKind.Float32 => nullable ? "float?" : "float", + TypeKind.Float64 => nullable ? "double?" : "double", + TypeKind.Guid => nullable ? "Guid?" : "Guid", + TypeKind.Int16 => nullable ? "short?" : "short", + TypeKind.Int32 => nullable ? "int?" : "int", + TypeKind.Int64 => nullable ? "long?" : "long", + TypeKind.Int8 => nullable ? "sbyte?" : "sbyte", + TypeKind.MongoDbObjectId => nullable ? "MongoDbObjectId?" : "MongoDbObjectId", + TypeKind.UInt16 => nullable ? "ushort?" : "ushort", + TypeKind.UInt32 => nullable ? "uint?" : "uint", + TypeKind.UInt64 => nullable ? "ulong?" : "ulong", + TypeKind.UInt8 => nullable ? "byte?" : "byte", + TypeKind.UnixDateTime => nullable ? "UnixDateTime?" : "UnixDateTime", + TypeKind.Utf8 => "string", + TypeKind.VarInt => nullable ? "long?" : "long", + TypeKind.VarUInt => nullable ? "ulong?" : "ulong", + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string UnderlyingTypeSerializer(this Property p, Namespace ns, bool stripNullable = false) + { + // Don't use nullable types for top-level nullable fields (instead use default-as-null semantics). + return p.PropertyType.UnderlyingTypeSerializer(ns, stripNullable || p.Path != null); + } + + public static string UnderlyingTypeSerializer(this PropertyType p, Namespace ns, bool stripNullable = false) + { + bool nullable = !stripNullable && p.Nullable; + return p switch + { + PrimitivePropertyType pp when pp.Type == TypeKind.Enum => + ns.Enums.Find(es => es.Name == (p as PrimitivePropertyType).Enum).Type.UnderlyingTypeSerializer(nullable), + PrimitivePropertyType pp => pp.Type.UnderlyingTypeSerializer(nullable), + ArrayPropertyType arr when !(arr.Items is null) => + $"TypedArrayHybridRowSerializer<{arr.Items.UnderlyingType(ns)}, {arr.Items.UnderlyingTypeSerializer(ns)}>", + MapPropertyType mp when !((mp.Keys is null) || (mp.Values is null)) => + $"TypedMapHybridRowSerializer<{mp.Keys.UnderlyingType(ns)}, {mp.Keys.UnderlyingTypeSerializer(ns)}, " + + $"{mp.Values.UnderlyingType(ns)}, {mp.Values.UnderlyingTypeSerializer(ns)}>", + UdtPropertyType up => $"{up.Name.IdentifierOnly()}HybridRowSerializer", + TuplePropertyType tpt => + "TypedTupleHybridRowSerializer<" + + string.Join(", ", tpt.Items.Select(x => $"{x.UnderlyingType(ns)}, {x.UnderlyingTypeSerializer(ns)}")) + + ">", + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string UnderlyingTypeSerializer(this TypeKind type, bool nullable) + { + if (nullable) + { + return $"NullableHybridRowSerializer<{type.UnderlyingType(true)}, " + + $"{type.UnderlyingType(false)}, " + + $"{type.UnderlyingTypeSerializer(false)}>"; + } + + return $"{type}HybridRowSerializer"; + } + + public static bool HasSparse(this Schema s) + { + return ( + from p in s.Properties + where !(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage == StorageKind.Sparse) + select p).Any() || + s.BaseName != null; + } + + public static Schema Resolve(this UdtPropertyType upt, Namespace ns) + { + return CodeGenExtensions.Resolve(upt.Name, upt.SchemaId, ns); + } + + public static Schema ResolveBase(this Schema s, Namespace ns) + { + return CodeGenExtensions.Resolve(s.BaseName, s.BaseSchemaId, ns); + } + + private static Schema Resolve(string name, SchemaId id, Namespace ns) + { + Schema s; + if (id == SchemaId.Invalid) + { + s = ns.Schemas.Find(q => q.Name == name); + } + else + { + s = ns.Schemas.Find(q => q.SchemaId == id); + if (s.Name != name) + { + throw new Exception($"Ambiguous schema reference: '{name}:{id}'"); + } + } + return s; + } + } +} diff --git a/dotnet/src/HybridRowCLI/CompileCommand.cs b/src/Serialization/HybridRowCLI/CompileCommand.cs similarity index 64% rename from dotnet/src/HybridRowCLI/CompileCommand.cs rename to src/Serialization/HybridRowCLI/CompileCommand.cs index e0b9976..1c7306e 100644 --- a/dotnet/src/HybridRowCLI/CompileCommand.cs +++ b/src/Serialization/HybridRowCLI/CompileCommand.cs @@ -6,20 +6,20 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI { using System; using System.Collections.Generic; - using System.IO; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; using Microsoft.Extensions.CommandLineUtils; public class CompileCommand { + private bool verbose; + private List schemas; + private CompileCommand() { } - public bool Verbose { get; set; } = false; - - public List Schemas { get; set; } = null; - public static void AddCommand(CommandLineApplication app) { app.Command( @@ -39,37 +39,30 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI command.OnExecute( () => { - CompileCommand config = new CompileCommand(); - config.Verbose = verboseOpt.HasValue(); - config.Schemas = schemasOpt.Values; + CompileCommand config = new CompileCommand + { + verbose = verboseOpt.HasValue(), + schemas = schemasOpt.Values + }; - return config.OnExecute(); + return config.OnExecuteAsync().Result; }); }); } - public int OnExecute() + private async Task OnExecuteAsync() { - foreach (string schemaFile in this.Schemas) + foreach (string schemaFile in this.schemas) { - if (this.Verbose) + (Namespace ns, LayoutResolver resolver) = await SchemaUtil.CreateResolverAsync(schemaFile, this.verbose); + + foreach (Schema s in ns.Schemas) { - Console.WriteLine($"Compiling {schemaFile}..."); - Console.WriteLine(); + Console.WriteLine($"Compiling Schema: {s.Name}"); + _ = resolver.Resolve(s.SchemaId); } - string json = File.ReadAllText(schemaFile); - Namespace n = Namespace.Parse(json); - if (this.Verbose) - { - Console.WriteLine($"Namespace: {n.Name}"); - foreach (Schema s in n.Schemas) - { - Console.WriteLine($" {s.SchemaId} Schema: {s.Name}"); - } - } - - if (this.Verbose) + if (this.verbose) { Console.WriteLine(); Console.WriteLine($"Compiling {schemaFile} complete.\n"); diff --git a/dotnet/src/HybridRowCLI/ConsoleNative.cs b/src/Serialization/HybridRowCLI/ConsoleNative.cs similarity index 94% rename from dotnet/src/HybridRowCLI/ConsoleNative.cs rename to src/Serialization/HybridRowCLI/ConsoleNative.cs index 65ba202..406e901 100644 --- a/dotnet/src/HybridRowCLI/ConsoleNative.cs +++ b/src/Serialization/HybridRowCLI/ConsoleNative.cs @@ -8,11 +8,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI { using System; + using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; public static class ConsoleNative { [Flags] + [SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Interop")] public enum ConsoleModes : uint { ENABLE_PROCESSED_INPUT = 0x0001, diff --git a/src/Serialization/HybridRowCLI/ConvertSchemaCommand.cs b/src/Serialization/HybridRowCLI/ConvertSchemaCommand.cs new file mode 100644 index 0000000..2ebccac --- /dev/null +++ b/src/Serialization/HybridRowCLI/ConvertSchemaCommand.cs @@ -0,0 +1,172 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI +{ + using System; + using System.IO; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + using Microsoft.Extensions.CommandLineUtils; + + public class ConvertSchemaCommand + { + private const int InitialCapacity = 2 * 1024 * 1024; + private bool verbose; + private string outputFile; + private string namespaceFile; + private OutputFormat format; + + private ConvertSchemaCommand() + { + } + + public static void AddCommand(CommandLineApplication app) + { + // ReSharper disable once StringLiteralTypo + app.Command( + "convertschema", + command => + { + command.Description = "Convert a hybrid row schema namespace between formats."; + command.ExtendedHelpText = "Converts a hybrid row schema between formats. " + + "Supports both JSON SDL and HR Schema as either input or output."; + command.HelpOption("-? | -h | --help"); + + CommandOption verboseOpt = command.Option("-v|--verbose", "Display verbose output. Default: false.", CommandOptionType.NoValue); + + CommandArgument inputOpt = command.Argument("input", "Input file to be converted."); + CommandArgument outputOpt = command.Argument("output", "Output file to contain the conversion."); + + // ReSharper disable twice StringLiteralTypo + CommandArgument formatOpt = command.Argument("format", "Output format: hrschema json Default: hrschema"); + + command.OnExecute( + () => + { + ConvertSchemaCommand config = new ConvertSchemaCommand + { + verbose = verboseOpt.HasValue(), + namespaceFile = inputOpt.Value.Trim(), + outputFile = outputOpt.Value.Trim(), + format = OutputFormat.HrSchema, + }; + + if (!string.IsNullOrWhiteSpace(formatOpt.Value)) + { + if (!Enum.TryParse(formatOpt.Value.Trim(), true, out config.format)) + { + throw new CommandParsingException(command, "Invalid output format"); + } + } + + return config.OnExecute(); + }); + }); + } + + private enum OutputFormat + { + HrSchema, + Json, + } + + public int OnExecute() + { + if (this.verbose) + { + Console.WriteLine($"Converting {this.namespaceFile}..."); + Console.WriteLine(); + } + + int magicNumber; + using (Stream stm = new FileStream(this.namespaceFile, FileMode.Open)) + { + // Detect if it is a text or binary file via the encoding of the magic number at the + // beginning of the HybridRow header. + magicNumber = stm.ReadByte(); + stm.Seek(-1, SeekOrigin.Current); + if (magicNumber == -1) + { + Console.Error.WriteLine($"Invalid file: {this.namespaceFile}"); + return -1; + } + } + + Namespace n; + if (magicNumber == (int)HybridRowVersion.V1) + { + byte[] buffer = File.ReadAllBytes(this.namespaceFile); + RowBuffer row = new RowBuffer(buffer.AsSpan(), HybridRowVersion.V1, SystemSchema.LayoutResolver); + Result r = Namespace.Read(ref row, out n); + if (r != Result.Success) + { + Console.Error.WriteLine($"Error {r} while reading file: {this.namespaceFile}"); + return -1; + } + } + else + { + string json = File.ReadAllText(this.namespaceFile); + n = Namespace.Parse(json); + } + + if (this.verbose) + { + Console.WriteLine($"Namespace: {n.Name}"); + foreach (Schema s in n.Schemas) + { + Console.WriteLine($" {s.SchemaId} Schema: {s.Name}"); + } + } + + if (this.verbose) + { + Console.WriteLine(); + Console.WriteLine("Writing output:"); + Console.WriteLine($" Format: {this.format}"); + Console.WriteLine($" Output: {this.outputFile}"); + } + + switch (this.format) + { + case OutputFormat.HrSchema: + { + RowBuffer row = new RowBuffer(ConvertSchemaCommand.InitialCapacity); + Layout layout = SystemSchema.LayoutResolver.Resolve((SchemaId)NamespaceHybridRowSerializer.SchemaId); + row.InitLayout(HybridRowVersion.V1, layout, SystemSchema.LayoutResolver); + Result r = n.Write(ref row); + if (r != Result.Success) + { + Console.Error.WriteLine($"Error {r} while writing file: {this.outputFile}"); + return -1; + } + + using Stream stm = new FileStream(this.outputFile, FileMode.Create); + row.WriteTo(stm); + break; + } + case OutputFormat.Json: + { + string json = Namespace.ToJson(n); + File.WriteAllText(this.outputFile, json); + break; + } + default: + Contract.Fail("Unknown output format."); + break; + } + + if (this.verbose) + { + Console.WriteLine(); + Console.WriteLine("Conversion complete.\n"); + } + + return 0; + } + } +} diff --git a/src/Serialization/HybridRowCLI/Cpp/CppNamespaceGenerator.cs b/src/Serialization/HybridRowCLI/Cpp/CppNamespaceGenerator.cs new file mode 100644 index 0000000..d4758e4 --- /dev/null +++ b/src/Serialization/HybridRowCLI/Cpp/CppNamespaceGenerator.cs @@ -0,0 +1,2038 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +#pragma warning disable CA1822 // Mark members as static +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously +#pragma warning disable CA1823 // Avoid unused private fields + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1009 // Closing parenthesis should be followed by a space. +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line +#pragma warning disable SA1118 // The parameter spans multiple lines +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods +#pragma warning disable AsyncMethodsMustEndInAsync // Async method must end in Async + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.Cpp +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + + // ReSharper disable ArrangeStaticMemberQualifier + internal class CppNamespaceGenerator + { + private readonly Namespace ns; + private readonly LayoutResolver resolver; + + /// + /// Mapping from types to their descendants. The inner map includes all descendants within + /// the closure of a type and is marked 'true' if it is a direct descendent (child). + /// + private readonly Dictionary> hierarchy; + + public CppNamespaceGenerator(Namespace ns) + { + this.ns = ns; + this.resolver = new LayoutResolverNamespace(ns); + this.hierarchy = new Dictionary>(); + + // Build the type hierarchy in two steps: + // 1. Add all first level dependencies from parent to children. + // 2. Add all descendant dependencies from parent to all descendants. + foreach (Schema s in ns.Schemas) + { + if (s.BaseName == null) + { + continue; + } + + Schema parent = s.ResolveBase(ns); + Contract.Invariant(parent != null); + if (!this.hierarchy.TryGetValue(parent!, out Dictionary parentMap)) + { + parentMap = new Dictionary(); + this.hierarchy[parent] = parentMap; + } + parentMap.Add(s, true); + } + + foreach (Dictionary parentMap in this.hierarchy.Values) + { + IEnumerable children = from t in parentMap where t.Value select t.Key; + foreach (Schema child in children.ToList()) + { + AddDescendants(parentMap, child); + } + } + + void AddDescendants(Dictionary parentMap, Schema child) + { + if (!this.hierarchy.TryGetValue(child, out Dictionary childMap)) + { + return; + } + + IEnumerable grandchildren = from t in childMap where t.Value select t.Key; + foreach (Schema gc in grandchildren) + { + if (parentMap.ContainsKey(gc)) + { + continue; + } + parentMap[gc] = false; + AddDescendants(parentMap, gc); + } + } + } + + public async ValueTask GenerateNamespace( + List excludes, + Emit header, + Emit source, + bool includeDataContracts, + bool includeEmbedSchema) + { + if (this.ns.Comment != null) + { + await header.Comment(this.ns.Comment); + } + + string cppNamespace = this.ns.CppNamespace ?? this.ns.Name.Replace(".", "::"); + await using Emit.Scope s1 = await header.Namespace(cppNamespace); + + // Emit forward references for Data Contracts. + foreach (Schema s in this.ns.Schemas) + { + if (excludes.Contains(s.Name)) + { + continue; + } + + await header.Statement($"class {s.Name}"); + } + + await source.Comment("ReSharper disable CppClangTidyCppcoreguidelinesProTypeStaticCastDowncast"); + await source.Comment("ReSharper disable CppClangTidyPerformanceMoveConstArg"); + await source.Comment("ReSharper disable CppRedundantControlFlowJump"); + await source.Comment("ReSharper disable CppClangTidyClangDiagnosticExitTimeDestructors"); + await using Emit.Scope s2 = await source.Namespace(cppNamespace); + await source.Using("std::literals"); + await source.Whitespace(); + + if (includeEmbedSchema) + { + await this.GenerateHrSchema(header, source); + } + + if (includeDataContracts) + { + foreach (Schema s in this.ns.Schemas) + { + if (excludes.Contains(s.Name)) + { + continue; + } + + Console.WriteLine($"Generating Data Contracts: {s.Name}"); + await this.GenerateDataContracts(header, s); + } + } + + foreach (Schema s in this.ns.Schemas) + { + if (excludes.Contains(s.Name)) + { + continue; + } + + Console.WriteLine($"Compiling Schema: {s.Name}"); + await this.GenerateSchema(header, source, s); + } + } + + private async ValueTask GenerateHrSchema(Emit header, Emit source) + { + string name = $"{this.ns.Name.IdentifierOnly()}HrSchema"; + await header.Whitespace(); + await using (await header.Struct(Keywords.Final, $"{name}")) + { + await header.Statement("static const cdb_hr::Namespace& GetNamespace() noexcept"); + await header.Statement("static const cdb_hr::LayoutResolver& GetLayoutResolver() noexcept"); + await header.Whitespace(); + await header.Accessor(Keywords.Private); + await header.Statement("class Literal"); + } + + await using (await source.Class(Keywords.Final, $"{name}::Literal")) + { + await source.Statement($"friend struct {name}"); + await source.Whitespace(); + await using (await source.Method(Keywords.Static | Keywords.Noexcept, "const cdb_hr::Namespace&", "GetNamespace")) + { + await source.Statement("return *s_namespace"); + } + + await using (await source.Method(Keywords.Static, "std::unique_ptr", "LoadSchema")) + { + await using (await source.Block("auto ns = cdb_core::make_unique_with([&](cdb_hr::Namespace& n)\n{", "});")) + { + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, this.ns); + } + + await source.Whitespace(); + await source.Statement("s_namespace = ns.get()"); + await source.Statement("return std::make_unique(std::move(ns))"); + } + + await source.Whitespace(); + await source.Variable( + Keywords.Inline | Keywords.Static, + "cdb_hr::Namespace*", + "s_namespace", + "nullptr"); + await source.Variable( + Keywords.Inline | Keywords.Static, + "std::unique_ptr", + "s_layoutResolver", + "LoadSchema()"); + } + + await using (await source.Method(Keywords.Noexcept, "const cdb_hr::Namespace&", $"{name}::GetNamespace")) + { + await source.Statement("return *Literal::s_namespace"); + } + + await using (await source.Method(Keywords.Noexcept, "const cdb_hr::LayoutResolver&", $"{name}::GetLayoutResolver")) + { + await source.Statement("return *Literal::s_layoutResolver"); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, Namespace item) + { + if (!string.IsNullOrEmpty(item.Name)) + { + await source.Statement(@$"n.SetName(""{item.Name}"")"); + } + await source.Statement(@$"n.SetVersion(cdb_hr::SchemaLanguageVersion::{item.Version})"); + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Statement(@$"n.SetComment(""{item.Comment}"")"); + } + if (!string.IsNullOrEmpty(item.CppNamespace)) + { + await source.Statement(@$"n.SetCppNamespace(""{item.CppNamespace}"")"); + } + foreach (EnumSchema item2 in item.Enums) + { + await source.Whitespace("//////////////////////////////////////////////////////////////////////////////"); + await using Emit.Scope s2 = await source.Block( + "n.GetEnums().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumSchema& es)\n{", + "}));"); + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, item2); + } + foreach (Schema item2 in item.Schemas) + { + await source.Whitespace("//////////////////////////////////////////////////////////////////////////////"); + await using Emit.Scope s2 = await source.Block( + "n.GetSchemas().emplace_back(cdb_core::make_unique_with([](cdb_hr::Schema& s)\n{", + "}));"); + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, item2); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, Schema item) + { + if (item.Version != SchemaLanguageVersion.Unspecified) + { + await source.Statement(@$"s.SetVersion(cdb_hr::SchemaLanguageVersion::{item.Version})"); + } + if (!string.IsNullOrEmpty(item.Name)) + { + await source.Statement(@$"s.SetName(""{item.Name}"")"); + } + if (item.Type != TypeKind.Schema) + { + await source.Statement(@$"s.SetType(cdb_hr::TypeKind::{item.Type})"); + } + await source.Statement(@$"s.SetSchemaId(cdb_hr::SchemaId{{{item.SchemaId}}})"); + if (!string.IsNullOrEmpty(item.BaseName)) + { + await source.Statement(@$"s.SetBaseName(""{item.BaseName}"")"); + await source.Statement(@$"s.SetBaseSchemaId(cdb_hr::SchemaId{{{item.BaseSchemaId}}})"); + } + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Statement(@$"s.SetComment(""{item.Comment}"")"); + } + if (item.Options != null) + { + await using Emit.Scope scope2 = await source.Block( + "s.SetOptions(cdb_core::make_unique_with([](cdb_hr::SchemaOptions& so)\n{", + "}));"); + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, item.Options); + } + + foreach (PartitionKey item2 in item.PartitionKeys) + { + await using Emit.Scope scope2 = await source.Block( + "s.GetPartitionKeys().emplace_back(cdb_core::make_unique_with([](cdb_hr::PartitionKey& pk)\n{", + "}));"); + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, item2); + } + + foreach (PrimarySortKey item2 in item.PrimaryKeys) + { + await using Emit.Scope scope2 = await source.Block( + "s.GetPrimaryKeys().emplace_back(cdb_core::make_unique_with([](cdb_hr::PrimarySortKey& psk)\n{", + "}));"); + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, item2); + } + + foreach (StaticKey item2 in item.StaticKeys) + { + await using Emit.Scope scope2 = await source.Block( + "s.GetStaticKeys().emplace_back(cdb_core::make_unique_with([](cdb_hr::StaticKey& sk)\n{", + "}));"); + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, item2); + } + + foreach (Property item2 in item.Properties) + { + await using Emit.Scope scope2 = await source.Block( + "s.GetProperties().emplace_back(cdb_core::make_unique_with([](cdb_hr::Property& p)\n{", + "}));"); + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, item2); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, SchemaOptions item) + { + if (item.DisallowUnschematized) + { + await source.Statement("so.SetDisallowUnschematized(true)"); + } + if (item.EnablePropertyLevelTimestamp) + { + await source.Statement("so.SetEnablePropertyLevelTimestamp(true)"); + } + if (item.DisableSystemPrefix) + { + await source.Statement("so.SetDisableSystemPrefix(true)"); + } + if (item.Abstract) + { + await source.Statement("so.SetAbstract(true)"); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, PartitionKey item) + { + if (!string.IsNullOrEmpty(item.Path)) + { + await source.Statement(@$"pk.SetPath(""{item.Path}"")"); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, PrimarySortKey item) + { + if (!string.IsNullOrEmpty(item.Path)) + { + await source.Statement(@$"psk.SetPath(""{item.Path}"")"); + } + if (item.Direction != SortDirection.Ascending) + { + await source.Statement(@$"psk.SetDirection(SortDirection::{item.Direction})"); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, StaticKey item) + { + if (!string.IsNullOrEmpty(item.Path)) + { + await source.Statement(@$"sk.SetPath(""{item.Path}"")"); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, Property item) + { + if (!string.IsNullOrEmpty(item.Path)) + { + await source.Statement(@$"p.SetPath(""{item.Path}"")"); + } + if (item.AllowEmpty != AllowEmptyKind.None) + { + await source.Statement(@$"p.SetAllowEmpty(AllowEmptyKind::{item.AllowEmpty})"); + } + if (item.PropertyType != null) + { + await using Emit.Scope scope3 = await source.Block(@"p.SetPropertyType(", ");"); + await this.GenerateHrSchemaConstant(source, scope3, item.PropertyType); + } + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Statement(@$"p.SetComment(""{item.Comment}"")"); + } + if (!string.IsNullOrEmpty(item.ApiName)) + { + await source.Statement(@$"p.SetApiName(""{item.ApiName}"")"); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, Emit.Scope scope, PropertyType item) + { + switch (item) + { + case PrimitivePropertyType p: + await this.GenerateHrSchemaConstant(source, scope, p); + return; + case UdtPropertyType p: + { + string nullableClause = p.Nullable ? "" : ", false"; + nullableClause = p.Immutable ? $"{p.Nullable}, true" : nullableClause; + await source.Block( + $"\nstd::make_unique(\"{p.Name}\", cdb_hr::SchemaId{{{p.SchemaId}}}{nullableClause})", + scope); + return; + } + case ArrayPropertyType p: + { + string nullableClause = p.Nullable ? "" : ", false"; + nullableClause = p.Immutable ? $"{p.Nullable}, true" : nullableClause; + await using Emit.Scope s2 = await source.Block("\nstd::make_unique(", $"{nullableClause})", scope); + await this.GenerateHrSchemaConstant(source, s2, p.Items); + return; + } + case TuplePropertyType p: + { + string nullableClause = p.Nullable ? "" : ", false"; + nullableClause = p.Immutable ? $"{p.Nullable}, true" : nullableClause; + await using Emit.Scope s2 = await source.Block("\nstd::make_unique(", $"){nullableClause})", scope); + await using (Emit.Scope s3 = await source.Block("\ncdb_hr::IHybridRowSerializer::make_unique_vector(", "", s2)) + { + int i = 0; + foreach (PropertyType item2 in p.Items) + { + await source.Whitespace(); + await source.Block(""); + await using Emit.Scope s4 = await source.Block("", (++i == p.Items.Count) ? "" : ",", s3); + await this.GenerateHrSchemaConstant(source, s4, item2); + } + } + await source.Whitespace(); + await source.Block(""); + return; + } + default: + Contract.Fail($"Not Yet Implemented: {item.GetType().Name}"); + return; + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, Emit.Scope scope, PrimitivePropertyType item) + { + await using Emit.Scope scope2 = await source.Block( + "cdb_core::make_unique_with([](cdb_hr::PrimitivePropertyType& pt)\n{", + "})", + scope, true); + await source.Whitespace(); + await source.Statement($"pt.SetType(cdb_hr::TypeKind::{item.Type})"); + if (item.Length != 0) + { + await source.Statement($"pt.SetLength({item.Length})"); + } + if (item.Storage != StorageKind.Sparse) + { + await source.Statement($"pt.SetStorage(cdb_hr::StorageKind::{item.Storage})"); + } + if (!string.IsNullOrEmpty(item.Enum)) + { + await source.Statement(@$"pt.SetEnum(""{item.Enum}"")"); + } + if (item.RowBufferSize) + { + await source.Statement(@"pt.SetRowBufferSize(true)"); + } + if (!string.IsNullOrEmpty(item.ApiType)) + { + await source.Statement(@$"pt.SetApiType(""{item.ApiType}"")"); + } + if (!item.Nullable) + { + await source.Statement(@$"pt.SetNullable({item.Nullable.ToString().ToLowerInvariant()})"); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, EnumSchema item) + { + if (!string.IsNullOrEmpty(item.Name)) + { + await source.Statement(@$"es.SetName(""{item.Name}"")"); + } + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Statement(@$"es.SetComment(""{item.Comment}"")"); + } + await source.Statement(@$"es.SetType(cdb_hr::TypeKind::{item.Type})"); + if (!string.IsNullOrEmpty(item.ApiType)) + { + await source.Statement(@$"es.SetApiType(""{item.ApiType}"")"); + } + foreach (EnumValue item2 in item.Values) + { + await using Emit.Scope scope2 = await source.Block( + "es.GetValues().emplace_back(cdb_core::make_unique_with([](cdb_hr::EnumValue& ev)\n{", + "}));"); + await source.Whitespace(); + await this.GenerateHrSchemaConstant(source, item2); + } + } + + private async ValueTask GenerateHrSchemaConstant(Emit source, EnumValue item) + { + if (!string.IsNullOrEmpty(item.Name)) + { + await source.Statement(@$"ev.SetName(""{item.Name}"")"); + } + if (!string.IsNullOrEmpty(item.Comment)) + { + await source.Statement(@$"ev.SetComment(""{item.Comment}"")"); + } + await source.Statement(@$"ev.SetValue({item.Value})"); + } + + private async ValueTask GenerateDataContracts(Emit header, Schema s) + { + await header.Whitespace(); + if (s.Comment != null) + { + await header.DocComment(s.Comment); + } + + // If the type has a dotted name then use only the final identifier. + string name = s.Name.IdentifierOnly(); + Keywords flags = Keywords.Public; + flags |= (s.BaseName == null) ? Keywords.Final : 0; + await using Emit.Scope s1 = await header.Class(flags, name, s.BaseName); + await header.Accessor(Keywords.Public); + foreach (Property p in s.Properties) + { + await header.AutoProperty(p.ConstRefType(this.ns), p.OwningType(this.ns), p.AsPascal(), p.AsField()); + } + await header.Accessor(Keywords.Private); + foreach (Property p in s.Properties) + { + await header.Variable(Keywords.None, p.OwningType(this.ns), p.AsField(), ""); + } + } + + private async ValueTask GenerateSchema(Emit header, Emit source, Schema s) + { + // If the type has a dotted name then use only the final identifier. + string name = s.Name.IdentifierOnly(); + string typename = $"{name}HybridRowSerializer"; + + /////////////////////////////////////////////////////////////// + // Generate serializer declaration. + /////////////////////////////////////////////////////////////// + await header.Whitespace(); + if (s.Comment != null) + { + await header.DocComment(s.Comment); + } + await using (await header.Struct(Keywords.Final, typename)) + { + await header.Using("value_type", name); + await header.Using("owning_type", "std::unique_ptr"); + await header.Variable(Keywords.Constexpr | Keywords.Static, "cdb_hr::SchemaId", "Id", s.SchemaId.ToString()); + await header.Variable(Keywords.Constexpr | Keywords.Static, "uint32_t", "Size", + this.resolver.Resolve(s.SchemaId).Size.ToString()); + await header.Whitespace(); + + /////////////////////////////////////////////////////////////// + // Generate serializer definition. + /////////////////////////////////////////////////////////////// + + await source.Whitespace(); + await using (await source.Class(Keywords.Final, typename + "::Literal")) + { + await source.Statement($"friend struct {typename}"); + await source.Whitespace(); + + // Emit the names. + foreach (Property p in s.Properties) + { + await source.Variable( + Keywords.Constexpr | Keywords.Static, + "std::string_view", + $"{p.AsPascal()}Name", + $"\"{p.Path}\"sv"); + } + if (s.BaseName != null) + { + await source.Variable( + Keywords.Constexpr | Keywords.Static, + "std::string_view", + "__BaseName", + "\"__base\"sv"); + } + + // Emit the columns. + if (s.Properties.Count > 0) + { + await source.Whitespace(); + await source.Variable( + Keywords.Inline | Keywords.Static, + "const cdb_hr::Layout&", + "Layout", + $"{this.ns.Name.IdentifierOnly()}HrSchema::GetLayoutResolver().Resolve(Id)"); + await source.Whitespace(); + } + foreach (Property p in s.Properties) + { + await source.Variable( + Keywords.Inline | Keywords.Static | Keywords.Const, + "const cdb_hr::LayoutColumn&", + $"{p.AsPascal()}Column", + $"cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, {p.AsPascal()}Name)"); + } + if (s.BaseName != null) + { + await source.Variable( + Keywords.Inline | Keywords.Static | Keywords.Const, + "const cdb_hr::LayoutColumn&", + "__BaseColumn", + "cdb_hr::IHybridRowSerializer::InitLayoutColumn(Layout, __BaseName)"); + } + + // Emit the tokens. + if (s.HasSparse()) + { + await source.Whitespace(); + foreach (Property p in s.Properties) + { + switch (p.PropertyType) + { + case PrimitivePropertyType pp when pp.Storage != StorageKind.Sparse: + break; // Don't need tokens for non-sparse primitives. + default: + await source.Variable( + Keywords.Inline | Keywords.Static, + "const cdb_hr::StringTokenizer::StringToken&", + $"{p.AsPascal()}Token", + $@" + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, {p.AsPascal()}Column.GetPath())"); + break; + } + } + if (s.BaseName != null) + { + await source.Variable( + Keywords.Inline | Keywords.Static, + "const cdb_hr::StringTokenizer::StringToken&", + "__BaseToken", + @" + cdb_hr::IHybridRowSerializer::InitStringToken(Layout, __BaseColumn.GetPath())"); + } + } + + // Emit the property method declarations. + if (s.Properties.Count > 0) + { + await source.Whitespace(); + } + await source.Statement( + $"static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const {name}& value) noexcept"); + await source.Statement($"static cdb_hr::Result Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, {name}& value)"); + } + + await this.GenerateWriteRoot(header, source, name, typename, s); + await this.GenerateWriteProperties(source, name, typename, s); + await this.GenerateReadRoot(header, source, name, typename, s); + await this.GenerateReadProperties(source, name, typename, s); + + await header.Whitespace(); + await header.Accessor(Keywords.Private); + await header.Statement("class Literal"); + } + + await header.Whitespace(); + await header.Statement($"static_assert(cdb_hr::is_hybridrow_serializer_v<{name}, {typename}>)"); + } + + private async Task GenerateWriteRoot(Emit header, Emit source, string name, string typename, Schema s) + { + // Emit declaration. + await header.Statement( + $@"static cdb_hr::Result Write(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, const cdb_hr::TypeArgumentList& typeArgs, + const {name}& value) noexcept"); + + this.hierarchy.TryGetValue(s, out Dictionary descendants); + await using (Emit.Scope unused = await source.Method( + Keywords.Noexcept, + "cdb_hr::Result", + $"{typename}::Write", + $@"cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot, + const cdb_hr::TypeArgumentList& typeArgs, const {name}& value")) + { + if (descendants != null) + { + int i = 0; + await using Emit.Scope unused2 = await source.Switch("value.GetRuntimeSchemaId().Id()"); + foreach (Schema child in from d in descendants where d.Value select d.Key) + { + if (i++ != 0) + { + await source.Whitespace(); + } + + if (this.hierarchy.TryGetValue(child, out Dictionary childMap)) + { + foreach (Schema grandchild in childMap.Keys) + { + await source.Block( + $@" + case {grandchild.Name}HybridRowSerializer::Id.Id(): + "); + } + } + + await source.Block( + $@" + case {child.Name}HybridRowSerializer::Id.Id(): + "); + await using Emit.Scope unused3 = await source.Braces(); + await source.Block( + $@" + return {child.Name}HybridRowSerializer::Write(row, scope, isRoot, typeArgs, + static_cast(value)); + "); + } + await source.Whitespace(); + await source.Block( + @" + default: + break; + "); + } + + if (descendants != null) + { + await source.Whitespace(); + } + if (s.Options?.Abstract ?? false) + { + await source.Block( + @" + cdb_core::Contract::Fail(""Type is abstract.""); + "); + } + else + { + await source.Block( + $@" + if (isRoot) + {{ + return {typename}::Literal::Write(row, scope, value); + }} + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + {{ + return r; + }} + + r = {typename}::Literal::Write(row, childScope, value); + if (r != cdb_hr::Result::Success) + {{ + return r; + }} + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + "); + } + } + + if (descendants != null) + { + // Emit declaration. + await header.Statement( + $"static cdb_hr::Result WriteBase(cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const {name}& value) noexcept"); + + await using Emit.Scope unused1 = await source.Method( + Keywords.Noexcept, + "cdb_hr::Result", + $"{typename}::WriteBase", + $"cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const {name}& value"); + await source.Block( + $@" + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.WriteScope(row, scope, Id); + if (r != cdb_hr::Result::Success) + {{ + return r; + }} + + r = {typename}::Literal::Write(row, childScope, value); + if (r != Result::Success) + {{ + return r; + }} + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + "); + } + } + + private async Task GenerateWriteProperties(Emit source, string name, string typename, Schema s) + { + await using Emit.Scope unused = await source.Method( + Keywords.Noexcept, + "cdb_hr::Result", + $"{typename}::Literal::Write", + $"cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, const {name}& value"); + + // Handle fixed properties first. + int i = 0; + Property rowBufferSizeProp = null; + foreach (Property p in s.Properties) + { + if (!(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage != StorageKind.Fixed)) + { + continue; + } + + // Defer RowBufferSize properties till the end. + if (pp.RowBufferSize) + { + rowBufferSizeProp = p; + continue; + } + + if (i++ != 0) + { + await source.Whitespace(); + } + string cname = p.AsPascal(); + await using Emit.Scope unused1 = await source.Control($"if ({NullCheck(p)})"); + await source.Statement( + $@"cdb_hr::Result r = {p.Instance(this.ns)}.WriteFixed( + row, scope, {cname}Column, {p.Cast(this.ns, cname)})"); + await source.Block( + @" + if (r != cdb_hr::Result::Success) + { + return r; + } + "); + } + + // Handle variable properties. + foreach (Property p in s.Properties) + { + if (!(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage != StorageKind.Variable)) + { + continue; + } + + if (i++ != 0) + { + await source.Whitespace(); + } + string cname = p.AsPascal(); + await using Emit.Scope unused1 = await source.Control($"if ({NullCheck(p)})"); + await source.Statement( + $@"cdb_hr::Result r = {p.Instance(this.ns)}.WriteVariable( + row, scope, {cname}Column, {p.Cast(this.ns, cname)})"); + await source.Block( + @" + if (r != cdb_hr::Result::Success) + { + return r; + } + "); + } + + // Handle sparse properties. + foreach (Property p in s.Properties) + { + if ((p.PropertyType is PrimitivePropertyType pp1) && (pp1.Storage != StorageKind.Sparse)) + { + continue; + } + + if (i++ != 0) + { + await source.Whitespace(); + } + string cname = p.AsPascal(); + await using Emit.Scope unused1 = await source.Control($"if ({NullCheck(p)})"); + await source.Statement($"scope.Find(row, {cname}Column.GetPath())"); + switch (p.PropertyType) + { + case PrimitivePropertyType _: + await source.Statement( + $@"cdb_hr::Result r = {p.Instance(this.ns)}.WriteSparse( + row, scope, {p.Cast(this.ns, cname)})"); + break; + case ArrayPropertyType apt: + { + string arrayType = + (apt.Items is UdtPropertyType upt && this.hierarchy.ContainsKey(upt.Resolve(this.ns))) + ? "Array" + : "TypedArray"; + await source.Statement( + $@"cdb_hr::Result r = cdb_hr::{arrayType}HybridRowSerializer<{apt.Items.UnderlyingType(this.ns)}, {apt.Items.UnderlyingTypeSerializer(this.ns)}>::Write( + row, + scope, + false, + {p.AsPascal()}Column.GetTypeArgs(), + {p.Cast(this.ns, cname)})"); + break; + } + case TuplePropertyType tpt: + { + await source.Statement( + $@"cdb_hr::Result r = cdb_hr::{tpt.UnderlyingTypeSerializer(this.ns)}::Write( + row, + scope, + false, + {p.AsPascal()}Column.GetTypeArgs(), + {p.Cast(this.ns, cname)})"); + break; + } + case UdtPropertyType _: + await source.Statement( + $@"cdb_hr::Result r = {p.UnderlyingTypeSerializer(this.ns)}::Write( + row, scope, false, {p.AsPascal()}Column.GetTypeArgs(), {p.Cast(this.ns, cname)})"); + break; + default: + throw new NotImplementedException(); + } + await source.Block( + @" + if (r != cdb_hr::Result::Success) + { + return r; + } + "); + } + if (s.BaseName != null) + { + if (i++ != 0) + { + await source.Whitespace(); + } + await using Emit.Scope unused1 = await source.Braces(); + await source.Block( + $@" + scope.Find(row, __BaseColumn.GetPath()); + cdb_hr::Result r = {s.BaseName}HybridRowSerializer::WriteBase(row, scope, value); + if (r != cdb_hr::Result::Success) + {{ + return r; + }} + "); + } + + // Emit the RowBufferSize property last (if it exists). + if (rowBufferSizeProp != null) + { + Property p = rowBufferSizeProp; + if (i != 0) + { + await source.Whitespace(); + } + string cname = p.AsPascal(); + await source.Comment("Emit RowBufferSize field with actual size of RowBuffer."); + await source.Statement( + $@"cdb_hr::Result r = {p.Instance(this.ns)}.WriteFixed( + row, scope, {cname}Column, static_cast(row.GetLength()))"); + await source.Block( + @" + if (r != cdb_hr::Result::Success) + { + return r; + } + "); + } + + await source.Whitespace(); + await source.Statement("return cdb_hr::Result::Success"); + + static string NullCheck(Property p) + { + string cname = p.AsPascal(); + if ((p.AllowEmpty & AllowEmptyKind.EmptyAsNull) == 0) + { + return $"!cdb_hr::IHybridRowSerializer::is_default(value.Get{cname}())"; + } + return $"!cdb_hr::IHybridRowSerializer::is_default_or_empty(value.Get{cname}())"; + } + } + + private async Task GenerateReadRoot(Emit header, Emit source, string name, string typename, Schema s) + { + // Emit declaration. + await header.Statement( + $@"static std::tuple> + Read(const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot)"); + + // Emit definition. + this.hierarchy.TryGetValue(s, out Dictionary descendants); + await using (Emit.Scope unused = await source.Method( + Keywords.None, + $"std::tuple>", + $"{typename}::Read", + @"const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, bool isRoot")) + { + if (descendants != null) + { + await source.Block( + $@" + if (!(scope.GetTypeArg().GetType()->IsUDT())) + {{ + return {{cdb_hr::Result::TypeMismatch, std::unique_ptr<{name}>{{}}}}; + }} + + "); + + int i = 0; + await using Emit.Scope unused2 = await source.Switch("scope.GetTypeArg().GetTypeArgs().GetSchemaId().Id()"); + foreach (Schema child in from d in descendants where d.Value select d.Key) + { + if (i++ != 0) + { + await source.Whitespace(); + } + + if (this.hierarchy.TryGetValue(child, out Dictionary childMap)) + { + foreach (Schema grandchild in childMap.Keys) + { + await source.Block( + $@" + case {grandchild.Name}HybridRowSerializer::Id.Id(): + "); + } + } + + await source.Block( + $@" + case {child.Name}HybridRowSerializer::Id.Id(): + "); + await using Emit.Scope unused3 = await source.Braces(); + await source.Block( + $@" + auto [r, fieldValue] = {child.Name}HybridRowSerializer::Read(row, scope, false); + return {{r, std::move(fieldValue)}}; + "); + } + await source.Whitespace(); + await source.Block( + @" + default: + break; + "); + } + + if (descendants != null) + { + await source.Whitespace(); + } + if (s.Options?.Abstract ?? false) + { + await source.Block( + @" + cdb_core::Contract::Fail(""Type is abstract.""); + "); + } + else + { + await source.Block( + $@" + if (isRoot) + {{ + std::unique_ptr<{name}> value = std::make_unique<{name}>(); + cdb_hr::Result r = {typename}::Literal::Read(row, scope, *value); + return {{r, std::move(value)}}; + }} + + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + {{ + return {{r, std::unique_ptr<{name}>{{}}}}; + }} + + std::unique_ptr<{name}> value = std::make_unique<{name}>(); + r = {typename}::Literal::Read(row, childScope, *value); + if (r != cdb_hr::Result::Success) + {{ + return {{r, std::unique_ptr<{name}>{{}}}}; + }} + + scope.Skip(row, childScope); + return {{cdb_hr::Result::Success, std::move(value)}}; + "); + } + } + + if (descendants != null) + { + // Emit declaration. + await header.Statement($"static cdb_hr::Result ReadBase(const RowBuffer& row, RowCursor& scope, {name}& value)"); + + await using Emit.Scope unused1 = await source.Method( + Keywords.None, + "cdb_hr::Result", + $"{typename}::ReadBase", + $"const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, {name}& value"); + await source.Block( + @$" + auto [r, childScope] = cdb_hr::LayoutLiteral::UDT.ReadScope(row, scope); + if (r != cdb_hr::Result::Success) + {{ + return r; + }} + + r = {typename}::Literal::Read(row, childScope, value); + if (r != cdb_hr::Result::Success) + {{ + return r; + }} + + scope.Skip(row, childScope); + return cdb_hr::Result::Success; + "); + } + } + + private async Task GenerateReadProperties(Emit source, string name, string typename, Schema s) + { + await using Emit.Scope unused = await source.Method( + Keywords.None, + "cdb_hr::Result", + $"{typename}::Literal::Read", + $"const cdb_hr::RowBuffer& row, cdb_hr::RowCursor& scope, {name}& value"); + + // Handle fixed properties first. + int i = 0; + foreach (Property p in s.Properties) + { + if (!(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage != StorageKind.Fixed)) + { + continue; + } + + string cname = p.AsPascal(); + if (i++ != 0) + { + await source.Whitespace(); + } + await using Emit.Scope unused1 = await source.Braces(); + await source.Statement( + $"auto [r, fieldValue] = {p.Instance(this.ns)}.ReadFixed(row, scope, {cname}Column)"); + await source.Block( + $@" + switch (r) + {{ + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.Set{cname}({p.Hoist(p.RCast(this.ns, "fieldValue"))}); + break; + default: + return r; + }} + "); + } + + // Handle variable properties. + foreach (Property p in s.Properties) + { + if (!(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage != StorageKind.Variable)) + { + continue; + } + + string cname = p.AsPascal(); + if (i++ != 0) + { + await source.Whitespace(); + } + await using Emit.Scope unused1 = await source.Braces(); + await source.Statement( + $"auto [r, fieldValue] = {p.Instance(this.ns)}.ReadVariable(row, scope, {cname}Column)"); + await source.Block( + $@" + switch (r) + {{ + case cdb_hr::Result::NotFound: + break; + case cdb_hr::Result::Success: + value.Set{cname}({p.Hoist(p.RCast(this.ns, "fieldValue"))}); + break; + default: + return r; + }} + "); + } + + if (s.HasSparse()) + { + if (i != 0) + { + i = 0; + await source.Whitespace(); + } + await using Emit.Scope unused1 = await source.Control("while (scope.MoveNext(row))"); + + // Handle sparse properties. + foreach (Property p in s.Properties) + { + if ((p.PropertyType is PrimitivePropertyType pp) && (pp.Storage != StorageKind.Sparse)) + { + continue; + } + + string cname = p.AsPascal(); + if (i++ != 0) + { + await source.Whitespace(); + } + + await using Emit.Scope unused2 = await source.Control($"if (scope.GetToken() == {cname}Token.GetId())"); + switch (p.PropertyType) + { + case ArrayPropertyType ap: + { + string arrayType = + (ap.Items is UdtPropertyType upt && this.hierarchy.ContainsKey(upt.Resolve(this.ns))) + ? "Array" + : "TypedArray"; + await source.Statement( + $@"auto [r, fieldValue] = cdb_hr::{arrayType}HybridRowSerializer<{ap.Items.UnderlyingType(this.ns)}, {ap.Items.UnderlyingTypeSerializer(this.ns)}>::Read( + row, scope, false)"); + break; + } + case TuplePropertyType tpt: + { + string args = string.Join(", ", tpt.Items.Select(x => x.UnderlyingTypeSerializer(this.ns))); + await source.Statement( + $@"auto [r, fieldValue] = cdb_hr::TypedTupleHybridRowSerializer<{args}HybridRowSerializer>::Read( + row, scope, false)"); + break; + } + case UdtPropertyType upt: + await source.Statement( + $@"auto [r, fieldValue] = {upt.UnderlyingTypeSerializer(this.ns)}::Read(row, scope, false)"); + break; + default: + await source.Statement( + $@"auto [r, fieldValue] = {p.Instance(this.ns)}.ReadSparse(row, scope)"); + break; + } + await source.Block( + $@" + if (r != cdb_hr::Result::Success) + {{ + return r; + }} + + value.Set{cname}({p.RCast(this.ns, "std::move(fieldValue)")}); + continue; + "); + } + if (s.BaseName != null) + { + if (i++ != 0) + { + await source.Whitespace(); + } + await using Emit.Scope unused2 = await source.Control("if (scope.GetToken() == __BaseToken.GetId())"); + await source.Block( + $@" + cdb_hr::Result r = {s.BaseName}HybridRowSerializer::ReadBase(row, scope, value); + if (r != cdb_hr::Result::Success) + {{ + return r; + }} + continue; + "); + } + } + + if (i != 0) + { + await source.Whitespace(); + } + await source.Statement("return cdb_hr::Result::Success"); + } + + [Flags] + internal enum Keywords + { + None = 0, + Public = 0x1, + Private = 0x2, + Internal = 0x4, + Static = 0x8, + ReadOnly = 0x10, + Const = 0x20, + Final = 0x40, + Constexpr = 0x80, + Noexcept = 0x100, + Inline = 0x200, + } + + internal sealed class Emit : IAsyncDisposable + { + private readonly TextWriter writer; + + /// Number of spaces indent is increased for each scope. + private readonly int step; + + /// Current indent. + private int indent; + + public Emit(Stream stm) + { + this.writer = new StreamWriter(stm, new UTF8Encoding(false, true)); + this.step = 2; + this.indent = 0; + } + + public async ValueTask Struct(Keywords keywords, string identifier, string baseIdentifier = null) + { + await this.Indent(); + await this.Modifiers(keywords); + string final = ((keywords & Keywords.Final) != 0) ? " final" : ""; + if (baseIdentifier is null) + { + await this.writer.WriteLineAsync($"struct {identifier}{final}"); + } + else + { + await this.writer.WriteLineAsync($"struct {identifier}{final} : {baseIdentifier}"); + } + return await this.Braces(terminated: true); + } + + public async ValueTask Class(Keywords keywords, string identifier, string baseIdentifier = null) + { + await this.Indent(); + await this.Modifiers(keywords); + string final = ((keywords & Keywords.Final) != 0) ? " final" : ""; + if (baseIdentifier is null) + { + await this.writer.WriteLineAsync($"class {identifier}{final}"); + } + else + { + await this.writer.WriteLineAsync($"class {identifier}{final} : {baseIdentifier}"); + } + return await this.Braces(terminated: true); + } + + public async ValueTask FileHeader() + { + await this.WriteLine("// ------------------------------------------------------------"); + await this.WriteLine("// Copyright (c) Microsoft Corporation. All rights reserved."); + await this.WriteLine("// ------------------------------------------------------------"); + await this.Whitespace(); + } + + public async ValueTask GeneratedComment() + { + await this.WriteLine("// ------------------------------------------------------------"); + await this.WriteLine("// This file was generated by:"); + AssemblyName asm = Assembly.GetEntryAssembly().GetName(); + await this.WriteLine($"// {asm.Name}: {asm.Version}"); + await this.WriteLine("//"); + await this.WriteLine("// This file should not be modified directly."); + await this.WriteLine("// ------------------------------------------------------------"); + } + + public async ValueTask Whitespace(string comment = null) + { + if (comment == null) + { + await this.WriteLine(); + return; + } + + foreach (string line in comment.Split('\n')) + { + await this.WriteLine(line); + } + } + + public async ValueTask Comment(string comment = null) + { + if (comment == null) + { + await this.WriteLine(); + return; + } + + foreach (string line in comment.Split('\n')) + { + await this.WriteLine("// {0}", line); + } + } + + public async ValueTask DocComment(string comment) + { + await this.WriteLine("/// "); + foreach (string line in comment.Split('\n')) + { + await this.WriteLine("/// {0}", line); + } + await this.WriteLine("/// "); + } + + public async ValueTask Namespace(string identifier) + { + await this.WriteLine("namespace {0}", identifier); + return await this.Braces(); + } + + public ValueTask Using(string identifier) + { + return this.WriteLine("using namespace {0};", identifier); + } + + public ValueTask Using(string identifier, string expr) + { + return this.WriteLine("using {0} = {1};", identifier, expr); + } + + public async ValueTask Pragma(string warning, string comment = null) + { + await this.Indent(); + + if (warning == "once") + { + await this.writer.WriteLineAsync("#pragma once"); + } + else if (comment == null) + { + await this.writer.WriteLineAsync($"#pragma warning disable {warning}"); + } + else + { + await this.writer.WriteLineAsync($"#pragma warning disable {warning} // {comment}"); + } + } + + public async ValueTask Include(string path) + { + await this.Indent(); + await this.writer.WriteLineAsync($"#include \"{path}\""); + } + + public async ValueTask Accessor(Keywords keywords) + { + this.indent -= this.step; + try + { + await this.Indent(); + switch (keywords) + { + case Keywords.Public: + await this.writer.WriteLineAsync("public:"); + break; + case Keywords.Private: + await this.writer.WriteLineAsync("private:"); + break; + default: + Contract.Fail($"Invalid accessor: {keywords}"); + break; + } + } + finally + { + this.indent += this.step; + } + } + + public ValueTask Variable(string typename, string identifier, string expr = null) + { + return this.Variable(CppNamespaceGenerator.Keywords.None, typename, identifier, expr); + } + + public async ValueTask Variable(Keywords keywords, string typename, string identifier, string expr = null) + { + await this.Indent(); + await this.Modifiers(keywords); + + if (expr == null) + { + await this.writer.WriteLineAsync($"{typename} {identifier};"); + } + else + { + if (expr.Contains('\n')) + { + await this.writer.WriteAsync($"{typename} {identifier}{{"); + await this.Expr(expr); + await this.Whitespace(); + await this.Indent(); + await this.writer.WriteLineAsync("};"); + } + else + { + await this.writer.WriteAsync($"{typename} {identifier}{{"); + await this.Expr(expr); + await this.writer.WriteLineAsync("};"); + } + } + } + + public async ValueTask AutoProperty(string constRefType, string owningType, string identifier, string field) + { + await this.Indent(); + // ReSharper disable once StringLiteralTypo + await this.writer.WriteLineAsync($"[[nodiscard]] {constRefType} Get{identifier}() const noexcept {{ return {field}; }}"); + await this.Indent(); + await this.writer.WriteLineAsync($"void Set{identifier}({owningType} value) noexcept {{ {field} = std::move(value);}}"); + } + + public async ValueTask Statement(string expr) + { + await this.Indent(); + await this.Expr(expr); + await this.writer.WriteLineAsync(";"); + } + + public async ValueTask Control(string expr) + { + await this.Indent(); + await this.Expr(expr); + await this.writer.WriteLineAsync(); + return await this.Braces(); + } + + public async ValueTask Switch(string expr) + { + await this.Indent(); + await this.Expr($"switch ({expr})"); + await this.writer.WriteLineAsync(); + await this.WriteLine("{"); + return new Scope(this, 0, "}"); + } + + public ValueTask Block(string startBlock, string endBlock) + { + return this.Block(startBlock, endBlock, null); + } + + public async ValueTask Block(string startBlock, string endBlock, Scope outer, bool adopted = false) + { + bool isNested = outer != null; + int blockStep = this.step; + if (isNested && adopted) + { + blockStep = outer.Indent; + this.indent -= blockStep; + outer.Indent = 0; + } + await this.Block(startBlock, isNested); + + this.indent += blockStep; + if (isNested) + { + outer.HasNest = true; + } + return new Scope(this, blockStep, endBlock, outer, adopted); + } + + public async ValueTask Block(string block, Scope outer) + { + await this.Block(block, outer != null); + if (outer != null) + { + outer.HasNest = true; + } + } + + public ValueTask Block(string block) + { + return this.Block(block, false); + } + + public async ValueTask Method(Keywords keywords, string typename, string identifier, string parameters = null) + { + await this.Whitespace(); + await this.Indent(); + await this.Modifiers(keywords); + + if (typename != null) + { + await this.writer.WriteAsync($"{typename} "); + } + + string constMod = ((keywords & Keywords.Const) != 0) ? " const" : ""; + string noexceptMod = ((keywords & Keywords.Noexcept) != 0) ? " noexcept" : ""; + if (parameters == null) + { + await this.writer.WriteLineAsync($"{identifier}(){constMod}{noexceptMod}"); + } + else + { + await this.writer.WriteLineAsync($"{identifier}({parameters}){constMod}{noexceptMod}"); + } + return await this.Braces(); + } + + public ValueTask DisposeAsync() + { + return this.writer.DisposeAsync(); + } + + public async ValueTask Braces(bool terminated = false) + { + await this.WriteLine("{"); + this.indent += this.step; + return new Scope(this, this.step, terminated ? "};" : "}"); + } + + private async ValueTask Block(string block, bool isNested) + { + block = block.Replace("\r\n", "\n"); + int i = 0; + int trim = 0; + foreach (string line in block.Split("\n")) + { + // Skip leading empty lines (unless nested). + if (string.IsNullOrWhiteSpace(line) && i == 0 && !isNested) + { + continue; + } + + string trimmed; + if (i++ == 0) + { + trimmed = line.TrimStart(); + trim = line.Length - trimmed.Length; + } + else + { + await this.writer.WriteLineAsync(); + trimmed = (line.Length >= trim) ? line[trim..] : line; + } + if (!string.IsNullOrWhiteSpace(trimmed)) + { + if (!isNested || i > 1) + { + await this.Indent(); + } + await this.writer.WriteAsync(trimmed); + } + } + if (i == 0) + { + await this.Indent(); + } + } + + private async ValueTask Expr(string expr) + { + expr = expr.Replace("\r\n", "\n"); + int i = 0; + this.indent += this.step; + foreach (string line in expr.Split("\n")) + { + if (i++ != 0) + { + await this.writer.WriteLineAsync(); + await this.Indent(); + } + await this.writer.WriteAsync(line.Trim()); + } + this.indent -= this.step; + } + + private ValueTask Indent() + { + return new ValueTask(this.writer.WriteAsync(new string(' ', this.indent))); + } + + private async ValueTask Modifiers(Keywords keywords) + { + if ((keywords & Keywords.Inline) != 0) + { + await this.writer.WriteAsync("inline "); + } + if ((keywords & Keywords.Constexpr) != 0) + { + await this.writer.WriteAsync("constexpr "); + } + if ((keywords & Keywords.Static) != 0) + { + await this.writer.WriteAsync("static "); + } + } + + private async ValueTask WriteLine(string format, params object[] args) + { + await this.Indent(); + await this.writer.WriteLineAsync(string.Format(format, args)); + } + + private async ValueTask WriteLine(string format) + { + await this.Indent(); + await this.writer.WriteLineAsync(format); + } + + private ValueTask WriteLine() + { + return new ValueTask(this.writer.WriteLineAsync()); + } + + public sealed class Scope : IAsyncDisposable + { + private readonly Emit parent; + private readonly string closer; + private readonly Scope outer; + private readonly bool adopted; + + public Scope(Emit parent, int indent, string closer, Scope outer = null, bool adopted = false) + { + this.parent = parent; + this.Indent = indent; + this.closer = closer; + this.outer = outer; + this.adopted = adopted; + } + + public bool HasNest { get; set; } + + public int Indent { get; set; } + + public async ValueTask DisposeAsync() + { + this.parent.indent -= this.Indent; + if (!this.HasNest) + { + await this.parent.Indent(); + } + if (this.outer != null) + { + await this.parent.writer.WriteAsync(this.closer); + } + else + { + await this.parent.writer.WriteLineAsync(this.closer); + } + if ((this.outer != null) && this.adopted) + { + this.outer.Indent = this.Indent; + this.parent.indent += this.Indent; + } + } + } + } + } + + internal static class CodeGenExtensions + { + public static string AsPascal(this Property p) + { + if (!string.IsNullOrEmpty(p.ApiName)) + { + return p.ApiName; + } + + string identifier = p.Path; + return identifier[..1].ToUpperInvariant() + identifier[1..]; + } + + public static string AsField(this Property p) + { + string identifier = p.AsPascal(); + return "m_" + identifier[..1].ToLowerInvariant() + identifier[1..]; + } + + public static string IdentifierOnly(this string fullyQualifiedIdentifier) + { + // If the type has a dotted name then use only the final identifier. + string identifier = fullyQualifiedIdentifier; + int index = identifier.LastIndexOf('.'); + if (index != -1) + { + identifier = identifier[(index + 1)..]; + } + return identifier; + } + + public static string Instance(this Property p, Namespace ns) + { + return p.PropertyType switch + { + PrimitivePropertyType pp when pp.Type == TypeKind.Enum => + $"cdb_hr::LayoutLiteral::{Enum.GetName(typeof(TypeKind), pp.ResolveEnum(ns))}", + PrimitivePropertyType pp => $"cdb_hr::LayoutLiteral::{Enum.GetName(typeof(TypeKind), pp.Type)}", + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string Cast(this Property p, Namespace ns, string cname) + { + return p.PropertyType switch + { + PrimitivePropertyType pp when !string.IsNullOrEmpty(pp.ApiType) => + $"static_cast<{p.UnderlyingType(ns)}>(cdb_hr::IHybridRowSerializer::get(value.Get{cname}()))", + PrimitivePropertyType { Type: TypeKind.Enum } => + $"static_cast<{p.UnderlyingType(ns)}>(cdb_hr::IHybridRowSerializer::get(value.Get{cname}()))", + _ => $"cdb_hr::IHybridRowSerializer::get(value.Get{cname}())", + }; + } + + public static string RCast(this Property p, Namespace ns, string fieldValue) + { + return p.PropertyType switch + { + PrimitivePropertyType pp when !string.IsNullOrEmpty(pp.ApiType) => $"static_cast<{pp.ApiType}>({fieldValue})", + PrimitivePropertyType { Type: TypeKind.Enum } pp => $"static_cast<{pp.Enum}>({fieldValue})", + _ => fieldValue, + }; + } + + public static string Hoist(this Property p, string fieldValue) + { + return p.PropertyType switch + { + PrimitivePropertyType pp when !string.IsNullOrEmpty(pp.ApiType) => fieldValue, + PrimitivePropertyType { Type: TypeKind.Utf8 } pp => $"std::string({fieldValue})", + PrimitivePropertyType { Type: TypeKind.Binary} pp => $"cdb_core::Memory({fieldValue})", + _ => fieldValue, + }; + } + + public static TypeKind ResolveEnum(this PrimitivePropertyType ep, Namespace ns) + { + EnumSchema enumSchema = ns.Enums.Find(es => es.Name == ep.Enum); + Contract.Invariant(enumSchema != null); + return enumSchema.Type; + } + + public static string OwningType(this Property p, Namespace ns, bool stripNullable = false) + { + // Don't use nullable types for top-level nullable fields (instead use default-as-null semantics). + return p.PropertyType.OwningType(ns, stripNullable || p.Path != null); + } + + public static string OwningType(this PropertyType p, Namespace ns, bool stripNullable = false) + { + return p switch + { + PrimitivePropertyType pp => pp.UnderlyingType(ns, stripNullable), + ArrayPropertyType ap when !(ap.Items is null) => $"std::vector<{ap.Items.OwningType(ns)}>", + TuplePropertyType ap when !(ap.Items is null) => + $"std::tuple<{string.Join(", ", ap.Items.Select(x => x.OwningType(ns)))}>", + UdtPropertyType up => $"std::unique_ptr<{up.UnderlyingType(ns, stripNullable)}>", + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string ConstRefType(this Property p, Namespace ns, bool stripNullable = false) + { + // Don't use nullable types for top-level nullable fields (instead use default-as-null semantics). + return p.PropertyType.ConstRefType(ns, stripNullable || p.Path != null); + } + + public static string ConstRefType(this PropertyType p, Namespace ns, bool stripNullable = false) + { + return p switch + { + PrimitivePropertyType pp when p.Type == TypeKind.Utf8 => $"const {pp.UnderlyingType(ns, stripNullable)}&", + PrimitivePropertyType pp => pp.UnderlyingType(ns, stripNullable), + ArrayPropertyType ap when !(ap.Items is null) => $"const std::vector<{ap.Items.OwningType(ns)}>&", + TuplePropertyType ap when !(ap.Items is null) => + $"const std::tuple<{string.Join(", ", ap.Items.Select(x => x.OwningType(ns)))}>&", + UdtPropertyType up => $"const std::unique_ptr<{up.UnderlyingType(ns, stripNullable)}>&", + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string UnderlyingType(this Property p, Namespace ns, bool stripNullable = false) + { + // Don't use nullable types for top-level nullable fields (instead use default-as-null semantics). + return p.PropertyType.UnderlyingType(ns, stripNullable || p.Path != null); + } + + public static string UnderlyingType(this PropertyType p, Namespace ns, bool stripNullable = false) + { + bool nullable = !stripNullable && p.Nullable; + return p switch + { + PrimitivePropertyType pp when pp.Type == TypeKind.Enum => + ns.Enums.Find(es => es.Name == (p as PrimitivePropertyType).Enum).Type.UnderlyingType(nullable), + PrimitivePropertyType pp => pp.Type.UnderlyingType(nullable), + ArrayPropertyType ap when !(ap.Items is null) => $"std::vector<{ap.Items.UnderlyingType(ns)}>", + TuplePropertyType ap when !(ap.Items is null) => + $"std::tuple<{string.Join(", ", ap.Items.Select(x => x.UnderlyingType(ns)))}>", + UdtPropertyType up => up.Name.IdentifierOnly(), + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string UnderlyingType(this TypeKind type, bool nullable) + { + return type switch + { + _ when nullable => $"std::optional<{type.UnderlyingType(false)}>", + TypeKind.Binary => "cdb_core::Memory", + TypeKind.Boolean => "bool", + TypeKind.DateTime => "cdb_hr::DateTime", + TypeKind.Decimal => "cdb_hr::Decimal", + TypeKind.Float128 => "cdb_hr::Float128", + TypeKind.Float32 => "float32_t", + TypeKind.Float64 => "float64_t", + TypeKind.Guid => "cdb_hr::Guid", + TypeKind.Int16 => "int16_t", + TypeKind.Int32 => "int32_t", + TypeKind.Int64 => "int64_t", + TypeKind.Int8 => "int8_t", + TypeKind.MongoDbObjectId => "cdb_hr::MongoDbObjectId", + TypeKind.UInt16 => "uint16_t", + TypeKind.UInt32 => "uint32_t", + TypeKind.UInt64 => "uint64_t", + TypeKind.UInt8 => "uint8_t", + TypeKind.UnixDateTime => "cdb_hr::UnixDateTime", + TypeKind.Utf8 => "std::string", + TypeKind.VarInt => "int64_t", + TypeKind.VarUInt => "uint64_t", + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string UnderlyingTypeSerializer(this Property p, Namespace ns) + { + return p.PropertyType.UnderlyingTypeSerializer(ns); + } + + public static string UnderlyingTypeSerializer(this PropertyType p, Namespace ns) + { + return p switch + { + PrimitivePropertyType pp when pp.Type == TypeKind.Enum => + ns.Enums.Find(es => es.Name == (p as PrimitivePropertyType).Enum).Type.UnderlyingTypeSerializer(p.Nullable), + PrimitivePropertyType pp => pp.Type.UnderlyingTypeSerializer(p.Nullable), + ArrayPropertyType arr when !(arr.Items is null) => + $"cdb_hr::TypedArrayHybridRowSerializer<{arr.Items.UnderlyingType(ns)}, {arr.Items.UnderlyingTypeSerializer(ns)}>", + UdtPropertyType up => $"{up.Name.IdentifierOnly()}HybridRowSerializer", + TuplePropertyType tpt => + "cdb_hr::TypedTupleHybridRowSerializer<" + + string.Join(", ", tpt.Items.Select(x => x.UnderlyingTypeSerializer(ns))) + + ">", + _ => NotImplemented(), + }; + + static string NotImplemented() + { + Contract.Fail("This should never happen"); + return ""; + } + } + + public static string UnderlyingTypeSerializer(this TypeKind type, bool nullable) + { + if (nullable) + { + return $"cdb_hr::NullableHybridRowSerializer<{type.UnderlyingType(true)}, " + + $"{type.UnderlyingType(false)}, " + + $"{type.UnderlyingTypeSerializer(false)}>"; + } + + return $"cdb_hr::{type}HybridRowSerializer"; + } + + public static bool HasSparse(this Schema s) + { + return ( + from p in s.Properties + where !(p.PropertyType is PrimitivePropertyType pp) || (pp.Storage == StorageKind.Sparse) + select p).Any() || + s.BaseName != null; + } + + public static Schema Resolve(this UdtPropertyType upt, Namespace ns) + { + return CodeGenExtensions.Resolve(upt.Name, upt.SchemaId, ns); + } + + public static Schema ResolveBase(this Schema s, Namespace ns) + { + return CodeGenExtensions.Resolve(s.BaseName, s.BaseSchemaId, ns); + } + + private static Schema Resolve(string name, SchemaId id, Namespace ns) + { + Schema s; + if (id == SchemaId.Invalid) + { + s = ns.Schemas.Find(q => q.Name == name); + } + else + { + s = ns.Schemas.Find(q => q.SchemaId == id); + if (s.Name != name) + { + throw new Exception($"Ambiguous schema reference: '{name}:{id}'"); + } + } + return s; + } + } +} diff --git a/dotnet/src/HybridRowCLI/Csv2HybridRowCommand.cs b/src/Serialization/HybridRowCLI/Csv2HybridRowCommand.cs similarity index 72% rename from dotnet/src/HybridRowCLI/Csv2HybridRowCommand.cs rename to src/Serialization/HybridRowCLI/Csv2HybridRowCommand.cs index 79306be..8bd1582 100644 --- a/dotnet/src/HybridRowCLI/Csv2HybridRowCommand.cs +++ b/src/Serialization/HybridRowCLI/Csv2HybridRowCommand.cs @@ -15,6 +15,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI 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.Extensions.CommandLineUtils; public class Csv2HybridRowCommand @@ -44,7 +45,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI "The csv2hr command accepts CSV files in the following format:\n" + "\t* First line contains column names, followed by 0 or more blank rows.\n" + "\t* All subsequent lines contain rows. A row with too few values treats \n" + - "\t all missing values as the empty string. Lines consistent entirely of \n" + + "\t all missing values as the empty string. Lines consisting entirely of \n" + "\t whitespace are omitted. Columns without a name are discarded."; command.HelpOption("-? | -h | --help"); @@ -149,7 +150,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI { case LayoutCode.Boolean: { - if (!bool.TryParse(fieldValue.AsString(), out bool value)) + if (!bool.TryParse(fieldValue.ToString(), out bool value)) { goto default; } @@ -159,7 +160,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.Int8: { - if (!sbyte.TryParse(fieldValue.AsString(), out sbyte value)) + if (!sbyte.TryParse(fieldValue.ToString(), out sbyte value)) { goto default; } @@ -169,7 +170,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.Int16: { - if (!short.TryParse(fieldValue.AsString(), out short value)) + if (!short.TryParse(fieldValue.ToString(), out short value)) { goto default; } @@ -179,7 +180,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.Int32: { - if (!int.TryParse(fieldValue.AsString(), out int value)) + if (!int.TryParse(fieldValue.ToString(), out int value)) { goto default; } @@ -189,7 +190,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.Int64: { - if (!long.TryParse(fieldValue.AsString(), out long value)) + if (!long.TryParse(fieldValue.ToString(), out long value)) { goto default; } @@ -199,7 +200,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.UInt8: { - if (!byte.TryParse(fieldValue.AsString(), out byte value)) + if (!byte.TryParse(fieldValue.ToString(), out byte value)) { goto default; } @@ -209,7 +210,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.UInt16: { - if (!ushort.TryParse(fieldValue.AsString(), out ushort value)) + if (!ushort.TryParse(fieldValue.ToString(), out ushort value)) { goto default; } @@ -219,7 +220,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.UInt32: { - if (!uint.TryParse(fieldValue.AsString(), out uint value)) + if (!uint.TryParse(fieldValue.ToString(), out uint value)) { goto default; } @@ -229,7 +230,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.UInt64: { - if (!ulong.TryParse(fieldValue.AsString(), out ulong value)) + if (!ulong.TryParse(fieldValue.ToString(), out ulong value)) { goto default; } @@ -239,7 +240,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.VarInt: { - if (!long.TryParse(fieldValue.AsString(), out long value)) + if (!long.TryParse(fieldValue.ToString(), out long value)) { goto default; } @@ -249,7 +250,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.VarUInt: { - if (!ulong.TryParse(fieldValue.AsString(), out ulong value)) + if (!ulong.TryParse(fieldValue.ToString(), out ulong value)) { goto default; } @@ -259,7 +260,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.Float32: { - if (!float.TryParse(fieldValue.AsString(), out float value)) + if (!float.TryParse(fieldValue.ToString(), out float value)) { goto default; } @@ -269,7 +270,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.Float64: { - if (!double.TryParse(fieldValue.AsString(), out double value)) + if (!double.TryParse(fieldValue.ToString(), out double value)) { goto default; } @@ -279,7 +280,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.Decimal: { - if (!decimal.TryParse(fieldValue.AsString(), out decimal value)) + if (!decimal.TryParse(fieldValue.ToString(), out decimal value)) { goto default; } @@ -294,7 +295,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal; - if (!DateTime.TryParse(fieldValue.AsString(), provider, style, out DateTime value)) + if (!DateTime.TryParse(fieldValue.ToString(), provider, style, out DateTime value)) { goto default; } @@ -304,12 +305,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.Guid: { - string s = fieldValue.AsString(); + string s = fieldValue.ToString(); // If the guid is quoted then remove the quotes. - if (s.Length > 2 && s[0] == '"' && s[s.Length - 1] == '"') + if (s.Length > 2 && s[0] == '"' && s[^1] == '"') { - s = s.Substring(1, s.Length - 2); + s = s[1..^1]; } if (!Guid.TryParse(s, out Guid value)) @@ -324,7 +325,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI { try { - byte[] newBytes = Convert.FromBase64String(fieldValue.AsString()); + byte[] newBytes = Convert.FromBase64String(fieldValue.ToString()); return writer.WriteBinary(path, newBytes.AsSpan()); } catch (Exception) @@ -342,7 +343,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.UnixDateTime: { - if (!long.TryParse(fieldValue.AsString(), out long value)) + if (!long.TryParse(fieldValue.ToString(), out long value)) { goto default; } @@ -352,7 +353,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case LayoutCode.MongoDbObjectId: { - string s = fieldValue.AsString(); + string s = fieldValue.ToString(); try { byte[] newBytes = Convert.FromBase64String(s); @@ -372,97 +373,95 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI } default: - return writer.WriteString(path, fieldValue.AsString()); + return writer.WriteString(path, fieldValue.ToString()); } } private async Task OnExecuteAsync() { - LayoutResolver resolver = await SchemaUtil.CreateResolverAsync(this.namespaceFile, this.verbose); - string sdl = string.IsNullOrWhiteSpace(this.namespaceFile) ? null : await File.ReadAllTextAsync(this.namespaceFile); + (Namespace ns, LayoutResolver resolver) = await SchemaUtil.CreateResolverAsync(this.namespaceFile, this.verbose); MemorySpanResizer resizer = new MemorySpanResizer(Csv2HybridRowCommand.InitialCapacity); - using (Stream stm = new FileStream(this.csvFile, FileMode.Open)) - using (TextReader txt = new StreamReader(stm)) - using (Stream outputStm = new FileStream(this.outputFile, FileMode.Create)) + await using Stream stm = new FileStream(this.csvFile, FileMode.Open); + using TextReader txt = new StreamReader(stm); + await using Stream outputStm = new FileStream(this.outputFile, FileMode.Create); + + // Read the CSV "schema". + string fieldNamesLine = await txt.ReadLineAsync(); + while (fieldNamesLine != null && string.IsNullOrWhiteSpace(fieldNamesLine)) { - // Read the CSV "schema". - string fieldNamesLine = await txt.ReadLineAsync(); - while (fieldNamesLine != null && string.IsNullOrWhiteSpace(fieldNamesLine)) - { - fieldNamesLine = await txt.ReadLineAsync(); - } - - if (fieldNamesLine == null) - { - await Console.Error.WriteLineAsync($"Input file contains no schema: {this.csvFile}"); - return -1; - } - - string[] fieldNames = fieldNamesLine.Split(','); - if (!(from c in fieldNames where !string.IsNullOrWhiteSpace(c) select c).Any()) - { - await Console.Error.WriteLineAsync($"All columns are ignored. Does this file have field headers?: {this.csvFile}"); - return -1; - } - - Utf8String[] paths = (from c in fieldNames select Utf8String.TranscodeUtf16(c)).ToArray(); - - SchemaId tableId = SystemSchema.EmptySchemaId; - if (!string.IsNullOrWhiteSpace(this.tableName)) - { - tableId = (resolver as LayoutResolverNamespace)?.Namespace?.Schemas?.Find(s => s.Name == this.tableName)?.SchemaId ?? - SchemaId.Invalid; - - if (tableId == SchemaId.Invalid) - { - await Console.Error.WriteLineAsync($"Error: schema {this.tableName} could not be found in {this.namespaceFile}."); - return -1; - } - } - - Layout layout = resolver.Resolve(tableId); - - long totalWritten = 0; - Segment segment = new Segment("CSV conversion from HybridRowCLI csv2hr", sdl); - Result r = await outputStm.WriteRecordIOAsync( - segment, - async index => - { - if (totalWritten >= this.limit) - { - return (Result.Success, default); - } - - string line = await txt.ReadLineAsync(); - while (line != null && string.IsNullOrWhiteSpace(line)) - { - line = await txt.ReadLineAsync(); - } - - if (line == null) - { - return (Result.Success, default); - } - - Result r2 = Csv2HybridRowCommand.ProcessLine(paths, line, resolver, layout, resizer, out ReadOnlyMemory record); - if (r2 != Result.Success) - { - return (r2, default); - } - - totalWritten++; - return (r2, record); - }); - - if (r != Result.Success) - { - Console.WriteLine($"Error while writing record: {totalWritten} to HybridRow(s): {this.outputFile}"); - return -1; - } - - Console.WriteLine($"Wrote ({totalWritten}) HybridRow(s): {this.outputFile}"); - return 0; + fieldNamesLine = await txt.ReadLineAsync(); } + + if (fieldNamesLine == null) + { + await Console.Error.WriteLineAsync($"Input file contains no schema: {this.csvFile}"); + return -1; + } + + string[] fieldNames = fieldNamesLine.Split(','); + if (!(from c in fieldNames where !string.IsNullOrWhiteSpace(c) select c).Any()) + { + await Console.Error.WriteLineAsync($"All columns are ignored. Does this file have field headers?: {this.csvFile}"); + return -1; + } + + Utf8String[] paths = (from c in fieldNames select Utf8String.TranscodeUtf16(c)).ToArray(); + + SchemaId tableId = SystemSchema.EmptySchemaId; + if (!string.IsNullOrWhiteSpace(this.tableName)) + { + tableId = (resolver as LayoutResolverNamespace)?.Namespace?.Schemas?.Find(s => s.Name == this.tableName)?.SchemaId ?? + SchemaId.Invalid; + + if (tableId == SchemaId.Invalid) + { + await Console.Error.WriteLineAsync($"Error: schema {this.tableName} could not be found in {this.namespaceFile}."); + return -1; + } + } + + Layout layout = resolver.Resolve(tableId); + + long totalWritten = 0; + Segment segment = new Segment("CSV conversion from HybridRowCLI csv2hr", ns); + Result r = await outputStm.WriteRecordIOAsync( + segment, + async index => + { + if (totalWritten >= this.limit) + { + return (Result.Success, default); + } + + string line = await txt.ReadLineAsync(); + while (line != null && string.IsNullOrWhiteSpace(line)) + { + line = await txt.ReadLineAsync(); + } + + if (line == null) + { + return (Result.Success, default); + } + + Result r2 = Csv2HybridRowCommand.ProcessLine(paths, line, resolver, layout, resizer, out ReadOnlyMemory record); + if (r2 != Result.Success) + { + return (r2, default); + } + + totalWritten++; + return (r2, record); + }); + + if (r != Result.Success) + { + Console.WriteLine($"Error while writing record: {totalWritten} to HybridRow(s): {this.outputFile}"); + return -1; + } + + Console.WriteLine($"Wrote ({totalWritten}) HybridRow(s): {this.outputFile}"); + return 0; } private struct WriterContext diff --git a/src/Serialization/HybridRowCLI/GenCSharpCommand.cs b/src/Serialization/HybridRowCLI/GenCSharpCommand.cs new file mode 100644 index 0000000..5389dae --- /dev/null +++ b/src/Serialization/HybridRowCLI/GenCSharpCommand.cs @@ -0,0 +1,130 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + using Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.CSharp; + using Microsoft.Extensions.CommandLineUtils; + + public class GenCSharpCommand + { + private bool verbose; + private bool includeDataContracts; + private bool includeEmbedSchema; + private List schemas; + private List excludes; + private string outputFile; + + private GenCSharpCommand() + { + } + + // ReSharper disable once StringLiteralTypo + public static void AddCommand(CommandLineApplication app) + { + app.Command( + "gencs", + command => + { + command.Description = "Generate csharp code from a hybrid row schema."; + command.ExtendedHelpText = "Generate code for serialization of the types in a hybrid row schema " + + "for use in read and writing instances of the schemas to rows."; + command.HelpOption("-? | -h | --help"); + + CommandOption verboseOpt = command.Option("-v|--verbose", "Display verbose output. Default: false.", CommandOptionType.NoValue); + CommandOption noDataContractCommandOptionSchemaOpt = command.Option("-d|--no-data-contracts", "Skip data contract generation. Default: false.", CommandOptionType.NoValue); + CommandOption noEmbedSchemaOpt = command.Option("-e|--no-embed", "Skip embedded schema. Default: false.", CommandOptionType.NoValue); + CommandOption excludeOpt = command.Option( + "-x|--exclude", + "Schema that should be skipped during code generation.", + CommandOptionType.MultipleValue); + + CommandOption outputOpt = command.Option( + "-o|--output", + "Output file to contain the conversion.", + CommandOptionType.SingleValue); + + CommandArgument schemasOpt = command.Argument( + "schema", + "File(s) containing the schema namespace to compile.", + arg => { arg.MultipleValues = true; }); + + command.OnExecute( + () => + { + if (!outputOpt.HasValue()) + { + throw new CommandParsingException(command, "Output file is required."); + } + + GenCSharpCommand config = new GenCSharpCommand + { + verbose = verboseOpt.HasValue(), + includeDataContracts = !noDataContractCommandOptionSchemaOpt.HasValue(), + includeEmbedSchema = !noEmbedSchemaOpt.HasValue(), + schemas = schemasOpt.Values, + excludes = excludeOpt.Values, + outputFile = outputOpt.Value().Trim(), + }; + + return config.OnExecuteAsync().AsTask().Result; + }); + }); + } + + private async ValueTask OnExecuteAsync() + { + // Ensure target directory exists. + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(this.outputFile))); + + // Create output file. + await using Stream stm = new FileStream(this.outputFile, FileMode.Create); + await using CSharpNamespaceGenerator.Emit emit = new CSharpNamespaceGenerator.Emit(stm); + await emit.FileHeader(); + await emit.Whitespace(); + await emit.Pragma("NamespaceMatchesFolderStructure", "Namespace Declarations must match folder structure."); + await emit.Pragma("CA1707", "Identifiers should not contain underscores."); + await emit.Pragma("CA1034", "Do not nest types."); + await emit.Pragma("CA2104", "Do not declare readonly mutable reference types."); + await emit.Pragma("SA1129", "Do not use default value type constructor."); + await emit.Pragma("SA1309", "Field should not begin with an underscore."); + await emit.Pragma("SA1310", "Field names should not contain underscore."); + await emit.Pragma("SA1402", "File may only contain a single type."); + await emit.Pragma("SA1414", "Tuple types in signatures should have element names."); + await emit.Pragma("SA1514", "Element documentation header should be preceded by blank line."); + await emit.Pragma("SA1516", "Elements should be separated by blank line."); + await emit.Pragma("SA1649", "File name should match first type name."); + + foreach (string schemaFile in this.schemas) + { + (Namespace ns, LayoutResolver _) = await SchemaUtil.CreateResolverAsync(schemaFile, this.verbose); + CSharpNamespaceGenerator gen = new CSharpNamespaceGenerator(ns); + await gen.GenerateNamespace( + this.excludes, + emit, + includeDataContracts: this.includeDataContracts, + includeEmbedSchema: this.includeEmbedSchema); + + if (this.verbose) + { + Console.WriteLine(); + Console.WriteLine($"Complete: {schemaFile}\n"); + } + } + + if (this.verbose) + { + Console.WriteLine(); + Console.WriteLine($"Output: {this.outputFile}\n"); + } + return 0; + } + } +} diff --git a/src/Serialization/HybridRowCLI/GenCppCommand.cs b/src/Serialization/HybridRowCLI/GenCppCommand.cs new file mode 100644 index 0000000..e89edf7 --- /dev/null +++ b/src/Serialization/HybridRowCLI/GenCppCommand.cs @@ -0,0 +1,163 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + using Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.Cpp; + using Microsoft.Extensions.CommandLineUtils; + + public class GenCppCommand + { + private bool verbose; + private bool includeDataContracts; + private bool includeEmbedSchema; + private List schemas; + private List excludes; + private string outputHeaderFile; + private string outputSourceFile; + private string dataHeaderFile; + + private GenCppCommand() + { + } + + // ReSharper disable once StringLiteralTypo + public static void AddCommand(CommandLineApplication app) + { + app.Command( + "gencpp", + command => + { + command.Description = "Generate cpp code from a hybrid row schema."; + command.ExtendedHelpText = "Generate code for serialization of the types in a hybrid row schema " + + "for use in read and writing instances of the schemas to rows."; + command.HelpOption("-? | -h | --help"); + + CommandOption verboseOpt = command.Option("-v|--verbose", "Display verbose output. Default: false.", CommandOptionType.NoValue); + CommandOption noEmbedSchemaOpt = command.Option( + "-e|--no-embed", + "Skip embedded schema. Default: false.", + CommandOptionType.NoValue); + CommandOption noDataContractCommandOptionSchemaOpt = command.Option( + "-d|--no-data-contracts", + "Skip data contract generation. Default: false.", + CommandOptionType.NoValue); + CommandOption excludeOpt = command.Option( + "-x|--exclude", + "Schema that should be skipped during code generation.", + CommandOptionType.MultipleValue); + + CommandOption outputHeaderOpt = command.Option( + "-oh|--output-header", + "Output header file to contain the conversion.", + CommandOptionType.SingleValue); + + CommandOption outputSourceOpt = command.Option( + "-os|--output-source", + "Output source file to contain the conversion.", + CommandOptionType.SingleValue); + + CommandOption inputDataFile = command.Option( + "-ih|--input-header", + "Input header file containing the data contract classes.", + CommandOptionType.SingleValue); + + CommandArgument schemasOpt = command.Argument( + "schema", + "File(s) containing the schema namespace to compile.", + arg => { arg.MultipleValues = true; }); + + command.OnExecute( + () => + { + if (!outputHeaderOpt.HasValue()) + { + throw new CommandParsingException(command, "Output header file is required."); + } + + if (!outputSourceOpt.HasValue()) + { + throw new CommandParsingException(command, "Output source file is required."); + } + + GenCppCommand config = new GenCppCommand + { + verbose = verboseOpt.HasValue(), + includeDataContracts = !noDataContractCommandOptionSchemaOpt.HasValue(), + includeEmbedSchema = !noEmbedSchemaOpt.HasValue(), + schemas = schemasOpt.Values, + excludes = excludeOpt.Values, + outputHeaderFile = outputHeaderOpt.Value().Trim(), + outputSourceFile = outputSourceOpt.Value().Trim(), + dataHeaderFile = inputDataFile.HasValue() ? inputDataFile.Value().Trim() : null + }; + + return config.OnExecuteAsync().AsTask().Result; + }); + }); + } + + private async ValueTask OnExecuteAsync() + { + // Ensure target directory exists. + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(this.outputHeaderFile))); + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(this.outputSourceFile))); + + // Create output files. + await using Stream headerStm = new FileStream(this.outputHeaderFile, FileMode.Create); + await using Stream sourceStm = new FileStream(this.outputSourceFile, FileMode.Create); + await using CppNamespaceGenerator.Emit emitHeader = new CppNamespaceGenerator.Emit(headerStm); + await using CppNamespaceGenerator.Emit emitSource = new CppNamespaceGenerator.Emit(sourceStm); + await emitHeader.FileHeader(); + await emitHeader.Pragma("once"); + await emitHeader.Whitespace(); + await emitHeader.GeneratedComment(); + await emitHeader.Whitespace(); + + await emitSource.FileHeader(); + await emitSource.Include("pch.h"); + if (this.dataHeaderFile != null) + { + await emitSource.Include(Path.GetFileName(this.dataHeaderFile)); + } + + await emitSource.Include(Path.GetFileName(this.outputHeaderFile)); + await emitSource.Whitespace(); + await emitSource.GeneratedComment(); + await emitSource.Whitespace(); + + foreach (string schemaFile in this.schemas) + { + (Namespace ns, LayoutResolver _) = await SchemaUtil.CreateResolverAsync(schemaFile, this.verbose); + CppNamespaceGenerator gen = new CppNamespaceGenerator(ns); + await gen.GenerateNamespace( + this.excludes, + emitHeader, + emitSource, + includeDataContracts: this.includeDataContracts, + includeEmbedSchema: this.includeEmbedSchema); + + if (this.verbose) + { + Console.WriteLine(); + Console.WriteLine($"Complete: {schemaFile}\n"); + } + } + + if (this.verbose) + { + Console.WriteLine(); + Console.WriteLine($"Output: {this.outputHeaderFile}\n"); + Console.WriteLine($"Output: {this.outputSourceFile}\n"); + } + return 0; + } + } +} diff --git a/dotnet/src/HybridRowCLI/HybridRowCLIProgram.cs b/src/Serialization/HybridRowCLI/HybridRowCLIProgram.cs similarity index 86% rename from dotnet/src/HybridRowCLI/HybridRowCLIProgram.cs rename to src/Serialization/HybridRowCLI/HybridRowCLIProgram.cs index d095462..89d6295 100644 --- a/dotnet/src/HybridRowCLI/HybridRowCLIProgram.cs +++ b/src/Serialization/HybridRowCLI/HybridRowCLIProgram.cs @@ -24,10 +24,14 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI command.HelpOption("-? | -h | --help"); command.VersionOption("-ver | --version", HybridRowCLIProgram.Version, HybridRowCLIProgram.LongVersion); + GenCSharpCommand.AddCommand(command); + GenCppCommand.AddCommand(command); CompileCommand.AddCommand(command); - PrintCommand.AddCommand(command); - Json2HybridRowCommand.AddCommand(command); + ConvertSchemaCommand.AddCommand(command); Csv2HybridRowCommand.AddCommand(command); + Json2HybridRowCommand.AddCommand(command); + PrintCommand.AddCommand(command); + RowRewriterHybridRowCommand.AddCommand(command); return command.Execute(args); } diff --git a/dotnet/src/HybridRowCLI/Json2HybridRowCommand.cs b/src/Serialization/HybridRowCLI/Json2HybridRowCommand.cs similarity index 75% rename from dotnet/src/HybridRowCLI/Json2HybridRowCommand.cs rename to src/Serialization/HybridRowCLI/Json2HybridRowCommand.cs index 9086c37..e5284a7 100644 --- a/dotnet/src/HybridRowCLI/Json2HybridRowCommand.cs +++ b/src/Serialization/HybridRowCLI/Json2HybridRowCommand.cs @@ -14,6 +14,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI 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.Extensions.CommandLineUtils; using Newtonsoft.Json; @@ -88,87 +89,85 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI public async Task OnExecuteAsync() { - LayoutResolver globalResolver = await SchemaUtil.CreateResolverAsync(this.NamespaceFile, this.Verbose); - string sdl = string.IsNullOrWhiteSpace(this.NamespaceFile) ? null : await File.ReadAllTextAsync(this.NamespaceFile); + (Namespace ns, LayoutResolver globalResolver) = await SchemaUtil.CreateResolverAsync(this.NamespaceFile, this.Verbose); MemorySpanResizer resizer = new MemorySpanResizer(Json2HybridRowCommand.InitialCapacity); - using (Stream stm = new FileStream(this.JsonFile, FileMode.Open)) - using (TextReader txt = new StreamReader(stm)) - using (JsonReader reader = new JsonTextReader(txt)) - { - // Turn off any special parsing conversions. Just raw JSON tokens. - reader.DateParseHandling = DateParseHandling.None; - reader.FloatParseHandling = FloatParseHandling.Double; + await using Stream stm = new FileStream(this.JsonFile, FileMode.Open); + using TextReader txt = new StreamReader(stm); + using JsonReader reader = new JsonTextReader(txt); - if (!await reader.ReadAsync()) + // Turn off any special parsing conversions. Just raw JSON tokens. + reader.DateParseHandling = DateParseHandling.None; + reader.FloatParseHandling = FloatParseHandling.Double; + + if (!await reader.ReadAsync()) + { + await Console.Error.WriteLineAsync("Error: file is empty."); + return -1; + } + + switch (reader.TokenType) + { + case JsonToken.StartObject: { - await Console.Error.WriteLineAsync("Error: file is empty."); - return -1; + JObject obj = await JObject.LoadAsync(reader); + Result r = this.ProcessObject(obj, globalResolver, resizer, out ReadOnlyMemory record); + if (r != Result.Success) + { + Console.WriteLine($"Error while writing record: 0 to HybridRow(s): {this.OutputFile}"); + return -1; + } + + // Write the output. + using (Stream outputStm = new FileStream(this.OutputFile, FileMode.Create)) + { + await outputStm.WriteAsync(record); + Console.WriteLine($"Wrote (1) HybridRow: {this.OutputFile}"); + } + + return 0; } - switch (reader.TokenType) + case JsonToken.StartArray: { - case JsonToken.StartObject: + using (Stream outputStm = new FileStream(this.OutputFile, FileMode.Create)) { - JObject obj = await JObject.LoadAsync(reader); - Result r = this.ProcessObject(obj, globalResolver, resizer, out ReadOnlyMemory record); + long totalWritten = 0; + Segment segment = new Segment("JSON conversion from HybridRowCLI json2hr", ns); + Result r = await outputStm.WriteRecordIOAsync( + segment, + async index => + { + if (!await reader.ReadAsync() || reader.TokenType == JsonToken.EndArray) + { + return (Result.Success, default); + } + + JObject obj = await JObject.LoadAsync(reader); + Result r2 = this.ProcessObject(obj, globalResolver, resizer, out ReadOnlyMemory record); + if (r2 != Result.Success) + { + return (r2, default); + } + + totalWritten++; + return (r2, record); + }); + if (r != Result.Success) { - Console.WriteLine($"Error while writing record: 0 to HybridRow(s): {this.OutputFile}"); + Console.WriteLine($"Error while writing record: {totalWritten} to HybridRow(s): {this.OutputFile}"); return -1; } - // Write the output. - using (Stream outputStm = new FileStream(this.OutputFile, FileMode.Create)) - { - await outputStm.WriteAsync(record); - Console.WriteLine($"Wrote (1) HybridRow: {this.OutputFile}"); - } - - return 0; + Console.WriteLine($"Wrote ({totalWritten}) HybridRow(s): {this.OutputFile}"); } - case JsonToken.StartArray: - { - using (Stream outputStm = new FileStream(this.OutputFile, FileMode.Create)) - { - long totalWritten = 0; - Segment segment = new Segment("JSON conversion from HybridRowCLI json2hr", sdl); - Result r = await outputStm.WriteRecordIOAsync( - segment, - async index => - { - if (!await reader.ReadAsync() || reader.TokenType == JsonToken.EndArray) - { - return (Result.Success, default); - } - - JObject obj = await JObject.LoadAsync(reader); - Result r2 = this.ProcessObject(obj, globalResolver, resizer, out ReadOnlyMemory record); - if (r2 != Result.Success) - { - return (r2, default); - } - - totalWritten++; - return (r2, record); - }); - - if (r != Result.Success) - { - Console.WriteLine($"Error while writing record: {totalWritten} to HybridRow(s): {this.OutputFile}"); - return -1; - } - - Console.WriteLine($"Wrote ({totalWritten}) HybridRow(s): {this.OutputFile}"); - } - - return 0; - } - - default: - await Console.Error.WriteLineAsync("Error: Only JSON documents with top-level Object/Array supported."); - return -1; + return 0; } + + default: + await Console.Error.WriteLineAsync("Error: Only JSON documents with top-level Object/Array supported."); + return -1; } } @@ -184,7 +183,6 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI /// Returns true if the type code indicates a linear scope (i.e. array like). /// The scope type code. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsLinearScope(LayoutCode code) { @@ -365,37 +363,24 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI double value = token.Value(); try { - switch (code) + return code switch { - case LayoutCode.Int8: - return writer.WriteInt8(path, (sbyte)value); - case LayoutCode.Int16: - return writer.WriteInt16(path, (short)value); - case LayoutCode.Int32: - return writer.WriteInt32(path, (int)value); - case LayoutCode.Int64: - return writer.WriteInt64(path, (long)value); - case LayoutCode.UInt8: - return writer.WriteUInt8(path, (byte)value); - case LayoutCode.UInt16: - return writer.WriteUInt16(path, (ushort)value); - case LayoutCode.UInt32: - return writer.WriteUInt32(path, (uint)value); - case LayoutCode.UInt64: - return writer.WriteUInt64(path, (ulong)value); - case LayoutCode.VarInt: - return writer.WriteVarInt(path, (long)value); - case LayoutCode.VarUInt: - return writer.WriteVarUInt(path, (ulong)value); - case LayoutCode.Float32: - return writer.WriteFloat32(path, (float)value); - case LayoutCode.Float64: - return writer.WriteFloat64(path, value); - case LayoutCode.Decimal: - return writer.WriteDecimal(path, (decimal)value); - case LayoutCode.Utf8: - return writer.WriteString(path, value.ToString(CultureInfo.InvariantCulture)); - } + LayoutCode.Int8 => writer.WriteInt8(path, (sbyte)value), + LayoutCode.Int16 => writer.WriteInt16(path, (short)value), + LayoutCode.Int32 => writer.WriteInt32(path, (int)value), + LayoutCode.Int64 => writer.WriteInt64(path, (long)value), + LayoutCode.UInt8 => writer.WriteUInt8(path, (byte)value), + LayoutCode.UInt16 => writer.WriteUInt16(path, (ushort)value), + LayoutCode.UInt32 => writer.WriteUInt32(path, (uint)value), + LayoutCode.UInt64 => writer.WriteUInt64(path, (ulong)value), + LayoutCode.VarInt => writer.WriteVarInt(path, (long)value), + LayoutCode.VarUInt => writer.WriteVarUInt(path, (ulong)value), + LayoutCode.Float32 => writer.WriteFloat32(path, (float)value), + LayoutCode.Float64 => writer.WriteFloat64(path, value), + LayoutCode.Decimal => writer.WriteDecimal(path, (decimal)value), + LayoutCode.Utf8 => writer.WriteString(path, value.ToString(CultureInfo.InvariantCulture)), + _ => writer.WriteFloat64(path, value), + }; } catch (OverflowException) { @@ -568,9 +553,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI string s = token.Value(); // If the guid is quoted then remove the quotes. - if (s.Length > 2 && s[0] == '"' && s[s.Length - 1] == '"') + if (s.Length > 2 && s[0] == '"' && s[^1] == '"') { - s = s.Substring(1, s.Length - 2); + s = s[1..^1]; } if (!Guid.TryParse(s, out Guid value)) @@ -646,52 +631,33 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI case JTokenType.Boolean: { bool value = token.Value(); - switch (code) + return code switch { - case LayoutCode.Int8: - return writer.WriteInt8(path, (sbyte)(value ? 1 : 0)); - case LayoutCode.Int16: - return writer.WriteInt16(path, (short)(value ? 1 : 0)); - case LayoutCode.Int32: - return writer.WriteInt32(path, value ? 1 : 0); - case LayoutCode.Int64: - return writer.WriteInt64(path, value ? 1L : 0L); - case LayoutCode.UInt8: - return writer.WriteUInt8(path, (byte)(value ? 1 : 0)); - case LayoutCode.UInt16: - return writer.WriteUInt16(path, (ushort)(value ? 1 : 0)); - case LayoutCode.UInt32: - return writer.WriteUInt32(path, (uint)(value ? 1 : 0)); - case LayoutCode.UInt64: - return writer.WriteUInt64(path, value ? 1U : 0U); - case LayoutCode.VarInt: - return writer.WriteVarInt(path, value ? 1L : 0L); - case LayoutCode.VarUInt: - return writer.WriteVarUInt(path, value ? 1U : 0U); - case LayoutCode.Float32: - return writer.WriteFloat32(path, value ? 1 : 0); - case LayoutCode.Float64: - return writer.WriteFloat64(path, value ? 1 : 0); - case LayoutCode.Decimal: - return writer.WriteDecimal(path, value ? 1 : 0); - case LayoutCode.Utf8: - return writer.WriteString(path, value ? "true" : "false"); - default: - return writer.WriteBool(path, value); - } + LayoutCode.Int8 => writer.WriteInt8(path, (sbyte)(value ? 1 : 0)), + LayoutCode.Int16 => writer.WriteInt16(path, (short)(value ? 1 : 0)), + LayoutCode.Int32 => writer.WriteInt32(path, value ? 1 : 0), + LayoutCode.Int64 => writer.WriteInt64(path, value ? 1L : 0L), + LayoutCode.UInt8 => writer.WriteUInt8(path, (byte)(value ? 1 : 0)), + LayoutCode.UInt16 => writer.WriteUInt16(path, (ushort)(value ? 1 : 0)), + LayoutCode.UInt32 => writer.WriteUInt32(path, (uint)(value ? 1 : 0)), + LayoutCode.UInt64 => writer.WriteUInt64(path, value ? 1U : 0U), + LayoutCode.VarInt => writer.WriteVarInt(path, value ? 1L : 0L), + LayoutCode.VarUInt => writer.WriteVarUInt(path, value ? 1U : 0U), + LayoutCode.Float32 => writer.WriteFloat32(path, value ? 1 : 0), + LayoutCode.Float64 => writer.WriteFloat64(path, value ? 1 : 0), + LayoutCode.Decimal => writer.WriteDecimal(path, value ? 1 : 0), + LayoutCode.Utf8 => writer.WriteString(path, value ? "true" : "false"), + _ => writer.WriteBool(path, value), + }; } case JTokenType.Null: - switch (code) + return code switch { - case LayoutCode.Null: - case LayoutCode.Invalid: - return writer.WriteNull(path); - default: - - // Any other schematized type then just don't write it. - return Result.Success; - } + LayoutCode.Null => writer.WriteNull(path), + LayoutCode.Invalid => writer.WriteNull(path), + _ => Result.Success + }; case JTokenType.Comment: return Result.Success; diff --git a/dotnet/src/HybridRowCLI/Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.csproj b/src/Serialization/HybridRowCLI/Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.csproj similarity index 60% rename from dotnet/src/HybridRowCLI/Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.csproj rename to src/Serialization/HybridRowCLI/Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.csproj index 859f8a7..1b65412 100644 --- a/dotnet/src/HybridRowCLI/Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.csproj +++ b/src/Serialization/HybridRowCLI/Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.csproj @@ -1,25 +1,27 @@  + 8.0 true true - {F7D04E9B-4257-4D7E-9AAD-C743AEDBED04} + {AFAA0AAB-F62C-400F-AFB4-22D902732721} Exe Test Microsoft.Azure.Cosmos.Serialization.HybridRowCLI Microsoft.Azure.Cosmos.Serialization.HybridRowCLI - netcoreapp2.2 + netcoreapp3.1 x64 + false + win-x64 + true + true + true - - - - - - - - + + + + @@ -27,4 +29,9 @@ - \ No newline at end of file + + + PreserveNewest + + + diff --git a/dotnet/src/HybridRowCLI/PrintCommand.cs b/src/Serialization/HybridRowCLI/PrintCommand.cs similarity index 78% rename from dotnet/src/HybridRowCLI/PrintCommand.cs rename to src/Serialization/HybridRowCLI/PrintCommand.cs index 12474df..be429fc 100644 --- a/dotnet/src/HybridRowCLI/PrintCommand.cs +++ b/src/Serialization/HybridRowCLI/PrintCommand.cs @@ -6,17 +6,18 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Core; using Microsoft.Azure.Cosmos.Serialization.HybridRow; using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; using Microsoft.Azure.Cosmos.Serialization.HybridRow.Json; 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.Extensions.CommandLineUtils; @@ -53,7 +54,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI command.ExtendedHelpText = "Convert binary serialized hybrid row value(s) to a printable string form using the given schema.\n\n" + "The print command accepts files in three formats:\n" + - "\t* A HybridRow RecordIO file containing 0 or more records (with or without embedded SDL).\n" + + "\t* A HybridRow RecordIO file containing 0 or more records (with or without embedded schema).\n" + "\t* A HybridRow binary file containing exactly 1 record. The length of the file indicates the record size.\n" + "\t* A HybridRow text file containing exactly 1 record written as a HEX text string. The length of the file\n" + "\t indicates the length of the encoding. All non-HEX characters (e.g. extra spaces, newlines, or dashes)\n" + @@ -281,66 +282,64 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI /// Any character that is not a HEX character is ignored. private static async Task> HexTextStreamToBinaryAsync(Stream stm) { - using (TextReader reader = new StreamReader(stm)) - { - string rowAsText = await reader.ReadToEndAsync(); - string trimmed = string.Concat( - from c in rowAsText - where (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') - select c); + using TextReader reader = new StreamReader(stm); + string rowAsText = await reader.ReadToEndAsync(); + string trimmed = string.Concat( + from c in rowAsText + where (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') + select c); - return ByteConverter.ToBytes(trimmed).AsMemory(); - } + return ByteConverter.ToBytes(trimmed).AsMemory(); } + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Bug in CA rule.")] private async Task OnExecuteAsync() { - LayoutResolver globalResolver = await SchemaUtil.CreateResolverAsync(this.namespaceFile, this.verbose); + (_, LayoutResolver globalResolver) = await SchemaUtil.CreateResolverAsync(this.namespaceFile, this.verbose); MemorySpanResizer resizer = new MemorySpanResizer(PrintCommand.InitialCapacity); foreach (string rowFile in this.rows) { - using (Stream stm = new FileStream(rowFile, FileMode.Open)) - { - // Detect if it is a text or binary file via the encoding of the magic number at the - // beginning of the HybridRow header. - int magicNumber = stm.ReadByte(); - stm.Seek(-1, SeekOrigin.Current); - if (magicNumber == -1) - { - continue; // empty file - } + await using Stream stm = new FileStream(rowFile, FileMode.Open); - Result r = Result.Failure; - if (magicNumber == (int)HybridRowVersion.V1) + // Detect if it is a text or binary file via the encoding of the magic number at the + // beginning of the HybridRow header. + int magicNumber = stm.ReadByte(); + stm.Seek(-1, SeekOrigin.Current); + if (magicNumber == -1) + { + continue; // empty file + } + + Result r = Result.Failure; + if (magicNumber == (int)HybridRowVersion.V1) + { + HybridRowHeader header = await PrintCommand.PeekHybridRowHeaderAsync(stm); + if (header.SchemaId == (SchemaId)SegmentHybridRowSerializer.SchemaId) { - HybridRowHeader header = await PrintCommand.PeekHybridRowHeaderAsync(stm); - if (header.SchemaId == SystemSchema.SegmentSchemaId) - { - r = await this.PrintRecordIOAsync(stm, globalResolver, resizer); - } - else - { - Memory rowAsBinary = await PrintCommand.ReadFixedAsync(stm, (int)stm.Length); - r = this.PrintOneRow(rowAsBinary, 0, globalResolver); - } + r = await this.PrintRecordIOAsync(stm, globalResolver, resizer); } - else if (char.ToUpper((char)magicNumber, CultureInfo.InvariantCulture) == HybridRowVersion.V1.ToString("X")[0]) + else { - // Convert hex text file to binary. - Memory rowAsBinary = await PrintCommand.HexTextStreamToBinaryAsync(stm); + Memory rowAsBinary = await PrintCommand.ReadFixedAsync(stm, (int)stm.Length); r = this.PrintOneRow(rowAsBinary, 0, globalResolver); } + } + else if (char.ToUpper((char)magicNumber, CultureInfo.InvariantCulture) == HybridRowVersion.V1.ToString("X")[0]) + { + // Convert hex text file to binary. + Memory rowAsBinary = await PrintCommand.HexTextStreamToBinaryAsync(stm); + r = this.PrintOneRow(rowAsBinary, 0, globalResolver); + } - if (r == Result.Canceled) - { - return 0; - } + if (r == Result.Canceled) + { + return 0; + } - if (r != Result.Success) - { - await Console.Error.WriteLineAsync($"Error reading row at {rowFile}"); - return -1; - } + if (r != Result.Success) + { + await Console.Error.WriteLineAsync($"Error reading row at {rowFile}"); + return -1; } } @@ -359,20 +358,45 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI // Read a RecordIO stream. long index = 0; Result r = await stm.ReadRecordIOAsync( - record => this.PrintOneRow(record, index++, segmentResolver), + (ReadOnlyMemory record) => this.PrintOneRow(record, index++, segmentResolver), segment => { - r = SegmentSerializer.Read(segment.Span, globalResolver, out Segment s); - if (r != Result.Success) + static Result ReadSegment(ReadOnlyMemory b, LayoutResolver r, out Segment s) { - return r; + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + RowBuffer row = new RowBuffer(MemoryMarshal.AsMemory(b).Span, HybridRowVersion.V1, r); + RowCursor root = RowCursor.Create(ref row); + return default(SegmentHybridRowSerializer).Read(ref row, ref root, true, out s); } - segmentResolver = string.IsNullOrWhiteSpace(s.SDL) ? globalResolver : SchemaUtil.LoadFromSdl(s.SDL, this.verbose, globalResolver); + Result r2 = ReadSegment(segment, globalResolver, out Segment s); + if (r2 != Result.Success) + { + return r2; + } + + segmentResolver = string.IsNullOrWhiteSpace(s.SDL) + ? globalResolver + : SchemaUtil.LoadFromSdl(s.SDL, this.verbose, globalResolver).resolver; if (this.showSchema) { - string str = string.IsNullOrWhiteSpace(s.SDL) ? "" : s.SDL; + string str; + switch (0) + { + case 0 when s.Schema != null: + str = "HrSchema Schema:\n" + Namespace.ToJson(s.Schema); + break; + case 0 when !string.IsNullOrWhiteSpace(s.SDL): + str = "JSON Schema:\n" + s.SDL; + break; + default: + str = ""; + break; + } if (!this.interactive) { Console.WriteLine(str); @@ -401,10 +425,9 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI /// /// The resolver for nested contents within the row. /// Success if the print is successful, an error code otherwise. - private Result PrintOneRow(Memory buffer, long index, LayoutResolver resolver) + private Result PrintOneRow(ReadOnlyMemory buffer, long index, LayoutResolver resolver) { - RowBuffer row = new RowBuffer(buffer.Span, HybridRowVersion.V1, resolver); - RowReader reader = new RowReader(ref row); + RowReader reader = new RowReader(buffer, HybridRowVersion.V1, resolver); Result r; string str; if (this.outputJson) @@ -421,15 +444,15 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI return r; } - Layout layout = resolver.Resolve(row.Header.SchemaId); + Layout layout = resolver.Resolve(reader.Header.SchemaId); if (!this.interactive) { - Console.WriteLine($"Schema: {layout.SchemaId} {layout.Name}, Length: {row.Length}"); + Console.WriteLine($"Schema: {layout.SchemaId} {layout.Name}, Length: {reader.Length}"); Console.WriteLine(str); } else { - if (!PrintCommand.ShowInteractive(layout, row.Length, index, str)) + if (!PrintCommand.ShowInteractive(layout, reader.Length, index, str)) { return Result.Canceled; } diff --git a/src/Serialization/HybridRowCLI/RowRewriterHybridRowCommand.cs b/src/Serialization/HybridRowCLI/RowRewriterHybridRowCommand.cs new file mode 100644 index 0000000..de22f8b --- /dev/null +++ b/src/Serialization/HybridRowCLI/RowRewriterHybridRowCommand.cs @@ -0,0 +1,167 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI +{ + using System; + using System.IO; + using System.Runtime.InteropServices; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Core; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; + using Microsoft.Extensions.CommandLineUtils; + + public class RowRewriterHybridRowCommand + { + private const int InitialCapacity = 2 * 1024 * 1024; + private bool verbose; + private string inputFile; + private string outputFile; + private string namespaceFile; + + private RowRewriterHybridRowCommand() + { + } + + public static void AddCommand(CommandLineApplication app) + { + app.Command( + "rewrite", + command => + { + command.Description = "Rewrite a HybridRow RecordIO document with a new schema."; + command.ExtendedHelpText = "Rewrite a HybridRow RecordIO document with a new schema." + + command.HelpOption("-? | -h | --help"); + + CommandOption verboseOpt = command.Option("-v|--verbose", "Display verbose output.", CommandOptionType.NoValue); + + CommandOption namespaceOpt = command.Option( + "-n|--namespace", + "File containing the schema namespace.", + CommandOptionType.SingleValue); + + CommandArgument jsonOpt = command.Argument("input", "Input file containing rows to convert."); + CommandArgument outputOpt = command.Argument("output", "Output file to contain the conversion."); + + command.OnExecute( + () => + { + RowRewriterHybridRowCommand config = new RowRewriterHybridRowCommand + { + verbose = verboseOpt.HasValue(), + namespaceFile = namespaceOpt.Value(), + inputFile = jsonOpt.Value, + outputFile = outputOpt.Value + }; + + return config.OnExecuteAsync().Result; + }); + }); + } + + private static (Result Result, Memory Block) FormatRow(ReadOnlyMemory body, MemorySpanResizer resizer) + { + Result r = RecordIOFormatter.FormatRecord(body, out RowBuffer row, resizer); + if (r != Result.Success) + { + return (r, default); + } + + return (Result.Success, resizer.Memory.Slice(0, row.Length)); + } + + private static (Result Result, Memory Metadata) FormatSegment(Segment s, MemorySpanResizer resizer) + { + Result r = RecordIOFormatter.FormatSegment(s, out RowBuffer row, resizer); + if (r != Result.Success) + { + return (r, default); + } + + return (r, resizer.Memory.Slice(0, row.Length)); + } + + private async Task OnExecuteAsync() + { + (Namespace ns, LayoutResolver globalResolver) = await SchemaUtil.CreateResolverAsync(this.namespaceFile, this.verbose); + MemorySpanResizer inResizer = new MemorySpanResizer(RowRewriterHybridRowCommand.InitialCapacity); + MemorySpanResizer outResizer = new MemorySpanResizer(RowRewriterHybridRowCommand.InitialCapacity); + await using Stream inStm = new FileStream(this.inputFile, FileMode.Open); + await using Stream outputStm = new FileStream(this.outputFile, FileMode.Create); + long totalWritten = 0; + Result r1 = await inStm.ReadRecordIOAsync( + async (record) => + { + (Result r, Memory metadata) = RowRewriterHybridRowCommand.FormatRow(record, outResizer); + if (r != Result.Success) + { + return r; + } + totalWritten++; + + // Metadata and Body memory blocks should not overlap since they are both in + // play at the same time. If they do this usually means that the same resizer + // was incorrectly used for both. Check the resizer parameter passed to + // WriteRecordIOAsync for metadata. + Contract.Assert(!metadata.Span.Overlaps(record.Span)); + + await outputStm.WriteAsync(metadata); + await outputStm.WriteAsync(record); + return Result.Success; + }, + async (segment) => + { + static Result ReadSegment(ReadOnlyMemory b, LayoutResolver r, out Segment s) + { + // TODO: remove this cost-cast when ReadOnlyRowBuffer exists. + // The cost-cast implied by MemoryMarshal.AsMemory is only safe here because: + // 1. Only READ operations are performed on the row. + // 2. The row is not allowed to escape this code. + RowBuffer row = new RowBuffer(MemoryMarshal.AsMemory(b).Span, HybridRowVersion.V1, r); + RowCursor root = RowCursor.Create(ref row); + return default(SegmentHybridRowSerializer).Read(ref row, ref root, true, out s); + } + + Result r = ReadSegment(segment, globalResolver, out Segment s); + if (r != Result.Success) + { + return r; + } + + // Apply the new schema. + if (!string.IsNullOrWhiteSpace(this.namespaceFile)) + { + s.Schema = ns; + } + else if (!string.IsNullOrWhiteSpace(s.SDL)) + { + s.Schema = Namespace.Parse(s.SDL); + } + s.SDL = null; + + (Result r3, Memory metadata) = RowRewriterHybridRowCommand.FormatSegment(s, outResizer); + if (r3 != Result.Success) + { + return r3; + } + + await outputStm.WriteAsync(metadata); + return Result.Success; + }, + inResizer); + + if (r1 != Result.Success) + { + Console.WriteLine($"Error while writing record: {totalWritten} to HybridRow(s): {this.outputFile}"); + return -1; + } + + Console.WriteLine($"Wrote ({totalWritten}) HybridRow(s): {this.outputFile}"); + return 0; + } + } +} diff --git a/dotnet/src/HybridRowCLI/SchemaUtil.cs b/src/Serialization/HybridRowCLI/SchemaUtil.cs similarity index 66% rename from dotnet/src/HybridRowCLI/SchemaUtil.cs rename to src/Serialization/HybridRowCLI/SchemaUtil.cs index 4db4b63..f7f0903 100644 --- a/dotnet/src/HybridRowCLI/SchemaUtil.cs +++ b/src/Serialization/HybridRowCLI/SchemaUtil.cs @@ -19,13 +19,13 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI /// resolver. /// /// True if verbose output should be written to stdout. - /// A resolver. - public static async Task CreateResolverAsync(string namespaceFile, bool verbose) + /// A Namespace and its resolver. + public static async Task<(Namespace ns, LayoutResolver resolver)> CreateResolverAsync(string namespaceFile, bool verbose) { - LayoutResolver globalResolver; + (Namespace ns, LayoutResolver resolver) t; if (string.IsNullOrWhiteSpace(namespaceFile)) { - globalResolver = SystemSchema.LayoutResolver; + t = (SystemSchema.GetNamespace(), SystemSchema.LayoutResolver); } else { @@ -36,37 +36,38 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowCLI } string json = await File.ReadAllTextAsync(namespaceFile); - globalResolver = SchemaUtil.LoadFromSdl(json, verbose, SystemSchema.LayoutResolver); + t = SchemaUtil.LoadFromSdl(json, verbose, SystemSchema.LayoutResolver); } - Contract.Requires(globalResolver != null); - return globalResolver; + Contract.Requires(t.resolver != null); + return t; } /// Create a HybridRow resolver for given piece of embedded Schema Definition Language (SDL). /// The SDL to parse. /// True if verbose output should be written to stdout. + /// An (optional) parent resolver for namespace chaining. /// A resolver that resolves all types in the given SDL. - public static LayoutResolver LoadFromSdl(string json, bool verbose, LayoutResolver parent = default) + public static (Namespace ns, LayoutResolver resolver) LoadFromSdl(string json, bool verbose, LayoutResolver parent = default) { - Namespace n = Namespace.Parse(json); + Namespace ns = Namespace.Parse(json); if (verbose) { - Console.WriteLine($"Namespace: {n.Name}"); - foreach (Schema s in n.Schemas) + Console.WriteLine($"Namespace: {ns.Name}"); + foreach (Schema s in ns.Schemas) { Console.WriteLine($" {s.SchemaId} Schema: {s.Name}"); } } - LayoutResolver resolver = new LayoutResolverNamespace(n, parent); + LayoutResolver resolver = new LayoutResolverNamespace(ns, parent); if (verbose) { Console.WriteLine(); - Console.WriteLine($"Loaded {n.Name}.\n"); + Console.WriteLine($"Loaded {ns.Name}.\n"); } - return resolver; + return (ns, resolver); } } } diff --git a/src/Serialization/HybridRowCLI/Sdk/Microsoft.HybridRow.Tools.props b/src/Serialization/HybridRowCLI/Sdk/Microsoft.HybridRow.Tools.props new file mode 100644 index 0000000..314b93d --- /dev/null +++ b/src/Serialization/HybridRowCLI/Sdk/Microsoft.HybridRow.Tools.props @@ -0,0 +1,23 @@ + + + + + + + + false + + + + + + + diff --git a/src/Serialization/HybridRowCLI/Sdk/Microsoft.HybridRow.Tools.targets b/src/Serialization/HybridRowCLI/Sdk/Microsoft.HybridRow.Tools.targets new file mode 100644 index 0000000..acb38d8 --- /dev/null +++ b/src/Serialization/HybridRowCLI/Sdk/Microsoft.HybridRow.Tools.targets @@ -0,0 +1,112 @@ + + + + + + ConfigCLIProps; + CreateSchemaGen; + GenerateCSharpSchema; + ConvertSchema; + $(CoreCompileDependsOn); + + + + + + + + + + + $(MSBuildThisFileDirectory)..\..\tools\Microsoft.Azure.Cosmos.Serialization.HybridRowCLI.exe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Serialization/HybridRowCLI/Sdk/_._ b/src/Serialization/HybridRowCLI/Sdk/_._ new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/src/Serialization/HybridRowCLI/Sdk/_._ @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dotnet/src/HybridRowGenerator/ByteConverter.cs b/src/Serialization/HybridRowGenerator/ByteConverter.cs similarity index 71% rename from dotnet/src/HybridRowGenerator/ByteConverter.cs rename to src/Serialization/HybridRowGenerator/ByteConverter.cs index a334caf..ff87b4e 100644 --- a/dotnet/src/HybridRowGenerator/ByteConverter.cs +++ b/src/Serialization/HybridRowGenerator/ByteConverter.cs @@ -36,12 +36,16 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator int len = bytes.Length; string result = new string((char)0, len * 2); fixed (uint* lp = ByteConverter.EncodeTable) - fixed (byte* bp = bytes) - fixed (char* rp = result) { - for (int i = 0; i < len; i++) + fixed (byte* bp = bytes) { - ((uint*)rp)[i] = lp[bp[i]]; + fixed (char* rp = result) + { + for (int i = 0; i < len; i++) + { + ((uint*)rp)[i] = lp[bp[i]]; + } + } } } @@ -56,36 +60,40 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator int len = hexChars.Length; byte[] result = new byte[len / 2]; fixed (byte* lp = ByteConverter.DecodeTable) - fixed (char* cp = hexChars) - fixed (byte* rp = result) { - for (int i = 0; i < len; i += 2) + fixed (char* cp = hexChars) { - int c1 = cp[i]; - if ((c1 < 0) || (c1 > 255)) + fixed (byte* rp = result) { - throw new Exception($"Invalid character: {c1}"); - } + for (int i = 0; i < len; i += 2) + { + int c1 = cp[i]; + if ((c1 < 0) || (c1 > 255)) + { + throw new Exception($"Invalid character: {c1}"); + } - byte b1 = lp[c1]; - if (b1 == 255) - { - throw new Exception($"Invalid character: {c1}"); - } + byte b1 = lp[c1]; + if (b1 == 255) + { + throw new Exception($"Invalid character: {c1}"); + } - int c2 = cp[i + 1]; - if ((c2 < 0) || (c2 > 255)) - { - throw new Exception($"Invalid character: {c2}"); - } + int c2 = cp[i + 1]; + if ((c2 < 0) || (c2 > 255)) + { + throw new Exception($"Invalid character: {c2}"); + } - byte b2 = lp[c2]; - if (b2 == 255) - { - throw new Exception($"Invalid character: {c2}"); - } + byte b2 = lp[c2]; + if (b2 == 255) + { + throw new Exception($"Invalid character: {c2}"); + } - rp[i / 2] = (byte)((b1 << 4) | b2); + rp[i / 2] = (byte)((b1 << 4) | b2); + } + } } } diff --git a/dotnet/src/HybridRowGenerator/CharDistribution.cs b/src/Serialization/HybridRowGenerator/CharDistribution.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/CharDistribution.cs rename to src/Serialization/HybridRowGenerator/CharDistribution.cs diff --git a/dotnet/src/HybridRowGenerator/DiagnosticConverter.cs b/src/Serialization/HybridRowGenerator/DiagnosticConverter.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/DiagnosticConverter.cs rename to src/Serialization/HybridRowGenerator/DiagnosticConverter.cs diff --git a/dotnet/src/HybridRowGenerator/DistributionType.cs b/src/Serialization/HybridRowGenerator/DistributionType.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/DistributionType.cs rename to src/Serialization/HybridRowGenerator/DistributionType.cs diff --git a/dotnet/src/HybridRowGenerator/HybridRowGeneratorConfig.cs b/src/Serialization/HybridRowGenerator/HybridRowGeneratorConfig.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/HybridRowGeneratorConfig.cs rename to src/Serialization/HybridRowGenerator/HybridRowGeneratorConfig.cs diff --git a/dotnet/src/HybridRowGenerator/HybridRowValueGenerator.cs b/src/Serialization/HybridRowGenerator/HybridRowValueGenerator.cs similarity index 98% rename from dotnet/src/HybridRowGenerator/HybridRowValueGenerator.cs rename to src/Serialization/HybridRowGenerator/HybridRowValueGenerator.cs index a3447b9..d5d13a7 100644 --- a/dotnet/src/HybridRowGenerator/HybridRowValueGenerator.cs +++ b/src/Serialization/HybridRowGenerator/HybridRowValueGenerator.cs @@ -29,12 +29,12 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator public static bool DynamicTypeArgumentEquals(LayoutResolver resolver, object left, object right, TypeArgument typeArg) { - if (object.ReferenceEquals(left, null)) + if (left is null) { - return object.ReferenceEquals(null, right); + return right is null; } - if (object.ReferenceEquals(null, right)) + if (right is null) { return false; } @@ -136,7 +136,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator return false; } - List> working = new List>(leftList); + List working = new List(leftList); foreach (List rightItem in rightList) { int i = HybridRowValueGenerator.MapContains(resolver, working, rightItem[0], typeArg); @@ -145,7 +145,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator return false; } - List leftItem = working[i]; + List leftItem = (List)working[i]; if (!HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, leftItem[1], rightItem[1], typeArg.TypeArgs[1])) { return false; @@ -523,7 +523,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator Contract.Assert(typeArg.TypeArgs.Count == 2); length = this.config.CollectionValueLength.Next(this.rand); - List> mapValue = new List>(length); + List mapValue = new List(length); for (int i = 0; i < length; i++) { object key = this.GenerateLayoutType(resolver, typeArg.TypeArgs[0]); @@ -617,11 +617,11 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator return -1; } - private static int MapContains(LayoutResolver resolver, List> map, object right, TypeArgument typeArg) + private static int MapContains(LayoutResolver resolver, List map, object right, TypeArgument typeArg) { for (int i = 0; i < map.Count; i++) { - List pair = map[i]; + List pair = (List)map[i]; Contract.Assert(pair.Count == 2); if (HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, pair[0], right, typeArg.TypeArgs[0])) { diff --git a/dotnet/src/HybridRowGenerator/IntDistribution.cs b/src/Serialization/HybridRowGenerator/IntDistribution.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/IntDistribution.cs rename to src/Serialization/HybridRowGenerator/IntDistribution.cs diff --git a/dotnet/src/HybridRowGenerator/Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj b/src/Serialization/HybridRowGenerator/Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj similarity index 66% rename from dotnet/src/HybridRowGenerator/Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj rename to src/Serialization/HybridRowGenerator/Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj index fad2748..4887110 100644 --- a/dotnet/src/HybridRowGenerator/Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj +++ b/src/Serialization/HybridRowGenerator/Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator.csproj @@ -3,18 +3,14 @@ true true - {B3F04B26-800A-4097-95CE-EACECA9ACE23} Library - Test Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator - netstandard2.0 + netstandard2.1 AnyCPU - - - + diff --git a/dotnet/src/HybridRowGenerator/RandomGenerator.cs b/src/Serialization/HybridRowGenerator/RandomGenerator.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/RandomGenerator.cs rename to src/Serialization/HybridRowGenerator/RandomGenerator.cs diff --git a/dotnet/src/HybridRowGenerator/SchemaGenerator.cs b/src/Serialization/HybridRowGenerator/SchemaGenerator.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/SchemaGenerator.cs rename to src/Serialization/HybridRowGenerator/SchemaGenerator.cs diff --git a/dotnet/src/HybridRowGenerator/StreamingRowGenerator.cs b/src/Serialization/HybridRowGenerator/StreamingRowGenerator.cs similarity index 99% rename from dotnet/src/HybridRowGenerator/StreamingRowGenerator.cs rename to src/Serialization/HybridRowGenerator/StreamingRowGenerator.cs index 553099b..171aa8c 100644 --- a/dotnet/src/HybridRowGenerator/StreamingRowGenerator.cs +++ b/src/Serialization/HybridRowGenerator/StreamingRowGenerator.cs @@ -77,7 +77,7 @@ namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator return writer.WriteNull(path); case LayoutCode.Boolean: - return writer.WriteBool(path, value == null ? default : (bool)value); + return writer.WriteBool(path, value != null && (bool)value); case LayoutCode.Int8: return writer.WriteInt8(path, value == null ? default : (sbyte)value); diff --git a/dotnet/src/HybridRowGenerator/VisitRowGenerator.cs b/src/Serialization/HybridRowGenerator/VisitRowGenerator.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/VisitRowGenerator.cs rename to src/Serialization/HybridRowGenerator/VisitRowGenerator.cs diff --git a/dotnet/src/HybridRowGenerator/WriteRowGenerator.cs b/src/Serialization/HybridRowGenerator/WriteRowGenerator.cs similarity index 100% rename from dotnet/src/HybridRowGenerator/WriteRowGenerator.cs rename to src/Serialization/HybridRowGenerator/WriteRowGenerator.cs diff --git a/dotnet/src/HybridRowStress/HybridRowStressConfig.cs b/src/Serialization/HybridRowStress/HybridRowStressConfig.cs similarity index 100% rename from dotnet/src/HybridRowStress/HybridRowStressConfig.cs rename to src/Serialization/HybridRowStress/HybridRowStressConfig.cs diff --git a/dotnet/src/HybridRowStress/HybridRowStressProgram.cs b/src/Serialization/HybridRowStress/HybridRowStressProgram.cs similarity index 100% rename from dotnet/src/HybridRowStress/HybridRowStressProgram.cs rename to src/Serialization/HybridRowStress/HybridRowStressProgram.cs diff --git a/dotnet/src/HybridRowStress/Microsoft.Azure.Cosmos.Serialization.HybridRowStress.csproj b/src/Serialization/HybridRowStress/Microsoft.Azure.Cosmos.Serialization.HybridRowStress.csproj similarity index 67% rename from dotnet/src/HybridRowStress/Microsoft.Azure.Cosmos.Serialization.HybridRowStress.csproj rename to src/Serialization/HybridRowStress/Microsoft.Azure.Cosmos.Serialization.HybridRowStress.csproj index 22e0096..4ddb673 100644 --- a/dotnet/src/HybridRowStress/Microsoft.Azure.Cosmos.Serialization.HybridRowStress.csproj +++ b/src/Serialization/HybridRowStress/Microsoft.Azure.Cosmos.Serialization.HybridRowStress.csproj @@ -3,19 +3,14 @@ true true - {2FED4096-A113-4946-85E2-36A1A94924C9} Exe - Test Microsoft.Azure.Cosmos.Serialization.HybridRowStress Microsoft.Azure.Cosmos.Serialization.HybridRowStress - netcoreapp2.2 - x64 + netcoreapp3.1 - - - - + + diff --git a/dotnet/src/HybridRowStress/StressContext.cs b/src/Serialization/HybridRowStress/StressContext.cs similarity index 100% rename from dotnet/src/HybridRowStress/StressContext.cs rename to src/Serialization/HybridRowStress/StressContext.cs diff --git a/dotnet/src/HybridRowStress/SyntaxException.cs b/src/Serialization/HybridRowStress/SyntaxException.cs similarity index 100% rename from dotnet/src/HybridRowStress/SyntaxException.cs rename to src/Serialization/HybridRowStress/SyntaxException.cs