From dd17a6cbf43969a27e04bf492463e332180e0dbc Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:53:15 -0300 Subject: [PATCH] Multirange Support (#73) --- examples/EdgeDB.Examples.FSharp/Program.fs | 1 + .../Binary/Codecs/MultiRangeCodec.cs | 58 ++++++++++ .../Binary/Protocol/IProtocolProvider.cs | 2 + .../Protocol/V1.0/V1ProtocolProvider.cs | 2 +- .../V2.0/Descriptors/DescriptorType.cs | 1 + .../V2.0/Descriptors/MultiRangeDescriptor.cs | 50 ++++++++ .../Protocol/V2.0/V2ProtocolProvider.cs | 7 ++ .../Clients/EdgeDBBinaryClient.cs | 18 +-- .../Models/DataTypes/MultiRange.cs | 108 ++++++++++++++++++ tests/EdgeDB.Tests.Integration/ClientTests.cs | 18 +++ tests/EdgeDB.Tests.Integration/DDLTests.cs | 2 +- tests/EdgeDB.Tests.Integration/DMLTests.cs | 2 +- 12 files changed, 252 insertions(+), 17 deletions(-) create mode 100644 src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs create mode 100644 src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/Descriptors/MultiRangeDescriptor.cs create mode 100644 src/EdgeDB.Net.Driver/Models/DataTypes/MultiRange.cs diff --git a/examples/EdgeDB.Examples.FSharp/Program.fs b/examples/EdgeDB.Examples.FSharp/Program.fs index 5bb2b3b4..75168875 100644 --- a/examples/EdgeDB.Examples.FSharp/Program.fs +++ b/examples/EdgeDB.Examples.FSharp/Program.fs @@ -3,6 +3,7 @@ open Serilog open Microsoft.Extensions.Hosting open Microsoft.Extensions.DependencyInjection open Microsoft.Extensions.Logging +open EdgeDB Log.Logger <- LoggerConfiguration() diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs new file mode 100644 index 00000000..e618d288 --- /dev/null +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs @@ -0,0 +1,58 @@ +using EdgeDB.Binary.Protocol.Common.Descriptors; +using EdgeDB.DataTypes; +using EdgeDB.Models.DataTypes; + +namespace EdgeDB.Binary.Codecs; + +internal sealed class MultiRangeCodec : BaseCodec>, IWrappingCodec, ICacheableCodec + where T : struct +{ + private RangeCodec _rangeCodec; + + public MultiRangeCodec(in Guid id, ICodec rangeInnerCodec, CodecMetadata? metadata) : base(in id, metadata) + { + _rangeCodec = new RangeCodec(in id, rangeInnerCodec, metadata); + } + + public override void Serialize(ref PacketWriter writer, MultiRange value, CodecContext context) + { + writer.Write(value.Length); + + for (int i = 0; i != value.Length; i++) + { + writer.WriteToWithInt32Length( + (ref PacketWriter innerWriter) => _rangeCodec.Serialize(ref innerWriter, value[i], context)); + } + } + + public override MultiRange Deserialize(ref PacketReader reader, CodecContext context) + { + var length = reader.ReadInt32(); + + var elements = new Range[length]; + + for (int i = 0; i != length; i++) + { + reader.Limit = reader.ReadInt32(); + elements[i] = _rangeCodec.Deserialize(ref reader, context); + reader.Limit = -1; + } + + return new MultiRange(elements); + } + + public ICodec InnerCodec + { + get => _rangeCodec; + set + { + if (value is not RangeCodec r) + throw new ArgumentException($"Expected a range codec, but got {value}"); + + _rangeCodec = r; + } + } + + public override string ToString() + => "multirange"; +} diff --git a/src/EdgeDB.Net.Driver/Binary/Protocol/IProtocolProvider.cs b/src/EdgeDB.Net.Driver/Binary/Protocol/IProtocolProvider.cs index 67ca3a00..c115a6bf 100644 --- a/src/EdgeDB.Net.Driver/Binary/Protocol/IProtocolProvider.cs +++ b/src/EdgeDB.Net.Driver/Binary/Protocol/IProtocolProvider.cs @@ -30,6 +30,8 @@ internal interface IProtocolProvider IReadOnlyDictionary ServerConfig { get; } + int? SuggestedPoolConcurrency { get; } + public static IProtocolProvider GetDefaultProvider(EdgeDBBinaryClient client) => (_defaultProvider ??= Providers[ProtocolVersion.EdgeDBBinaryDefaultVersion].Factory)(client); diff --git a/src/EdgeDB.Net.Driver/Binary/Protocol/V1.0/V1ProtocolProvider.cs b/src/EdgeDB.Net.Driver/Binary/Protocol/V1.0/V1ProtocolProvider.cs index 1ab16390..7cd79c51 100644 --- a/src/EdgeDB.Net.Driver/Binary/Protocol/V1.0/V1ProtocolProvider.cs +++ b/src/EdgeDB.Net.Driver/Binary/Protocol/V1.0/V1ProtocolProvider.cs @@ -21,7 +21,7 @@ public V1ProtocolProvider(EdgeDBBinaryClient client) _client = client; } - public int SuggestedPoolConcurrency { get; private set; } + public int? SuggestedPoolConcurrency { get; private set; } public ref ReadOnlyMemory ServerKey => ref _serverKey; diff --git a/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/Descriptors/DescriptorType.cs b/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/Descriptors/DescriptorType.cs index f15b8523..a72a299a 100644 --- a/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/Descriptors/DescriptorType.cs +++ b/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/Descriptors/DescriptorType.cs @@ -13,5 +13,6 @@ internal enum DescriptorType : byte Range = 9, Object = 10, Compound = 11, + MultiRange = 12, TypeAnnotationText = 127 } diff --git a/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/Descriptors/MultiRangeDescriptor.cs b/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/Descriptors/MultiRangeDescriptor.cs new file mode 100644 index 00000000..dd9b3ff3 --- /dev/null +++ b/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/Descriptors/MultiRangeDescriptor.cs @@ -0,0 +1,50 @@ +using EdgeDB.Binary.Protocol.Common.Descriptors; + +namespace EdgeDB.Binary.Protocol.V2._0.Descriptors; + +internal readonly struct MultiRangeDescriptor : ITypeDescriptor, IMetadataDescriptor +{ + public readonly Guid Id; + + public readonly string Name; + + public readonly bool IsSchemaDefined; + + public readonly ushort[] Ancestors; + + public readonly ushort Type; + public MultiRangeDescriptor(ref PacketReader reader, in Guid id) + { + Id = id; + + Name = reader.ReadString(); + IsSchemaDefined = reader.ReadBoolean(); + + var ancestorsCount = reader.ReadUInt16(); + var ancestors = new ushort[ancestorsCount]; + + for (var i = 0; i != ancestorsCount; i++) + { + ancestors[i] = reader.ReadUInt16(); + } + + Ancestors = ancestors; + + Type = reader.ReadUInt16(); + } + + + public CodecMetadata? GetMetadata(RelativeCodecDelegate relativeCodec, + RelativeDescriptorDelegate relativeDescriptor) + => new(Name, IsSchemaDefined, + IMetadataDescriptor.ConstructAncestors(Ancestors, relativeCodec, relativeDescriptor)); + + unsafe ref readonly Guid ITypeDescriptor.Id + { + get + { + fixed (Guid* ptr = &Id) + return ref *ptr; + } + } +} diff --git a/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/V2ProtocolProvider.cs b/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/V2ProtocolProvider.cs index 78fe5a29..ab71e777 100644 --- a/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/V2ProtocolProvider.cs +++ b/src/EdgeDB.Net.Driver/Binary/Protocol/V2.0/V2ProtocolProvider.cs @@ -43,6 +43,7 @@ public override ITypeDescriptor GetDescriptor(ref PacketReader reader) DescriptorType.Scalar => new ScalarTypeDescriptor(ref reader, in id), DescriptorType.Set => new SetDescriptor(ref reader, in id), DescriptorType.Tuple => new TupleTypeDescriptor(ref reader, in id), + DescriptorType.MultiRange => new MultiRangeDescriptor(ref reader, in id), _ => throw new InvalidDataException($"No descriptor found for type {type}") }; } @@ -142,6 +143,12 @@ public override ITypeDescriptor GetDescriptor(ref PacketReader reader) return new CompilableWrappingCodec(in range.Id, innerCodec, typeof(RangeCodec<>), metadata); } + case MultiRangeDescriptor multirange: + { + ref var innerCodec = ref getRelativeCodec(multirange.Type)!; + + return new CompilableWrappingCodec(in multirange.Id, innerCodec, typeof(MultiRangeCodec<>), metadata); + } case ScalarTypeDescriptor scalar: throw new MissingCodecException( $"Could not find the scalar type {scalar.Id}. Please file a bug report with your query that caused this error."); diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index 38f7656c..3aee1709 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System.Collections.Immutable; +using System.Diagnostics; using System.Reflection; using ProtocolExecuteResult = EdgeDB.Binary.Protocol.ExecuteResult; @@ -34,7 +35,9 @@ internal abstract class EdgeDBBinaryClient : BaseEdgeDBClient private Guid _stateDescriptorId; internal byte[] ServerKey; - internal int? SuggestedPoolConcurrency; + + internal int? SuggestedPoolConcurrency + => _protocolProvider.SuggestedPoolConcurrency; /// /// Creates a new binary client with the provided conection and config. @@ -92,19 +95,6 @@ internal ref Guid StateDescriptorId protected CancellationToken DisconnectCancelToken => Duplexer.DisconnectToken; - #region Events - - /// - /// Fired when the client disconnects. - /// - public event Func OnDisconnect - { - add => OnDisconnectInternal.Add(c => value()); - remove => OnDisconnectInternal.Remove(c => value()); - } - - #endregion - #region Client pool dispose /// diff --git a/src/EdgeDB.Net.Driver/Models/DataTypes/MultiRange.cs b/src/EdgeDB.Net.Driver/Models/DataTypes/MultiRange.cs new file mode 100644 index 00000000..27b0fee3 --- /dev/null +++ b/src/EdgeDB.Net.Driver/Models/DataTypes/MultiRange.cs @@ -0,0 +1,108 @@ +using EdgeDB.DataTypes; +using System.Collections; + +namespace EdgeDB.Models.DataTypes; + +/// +/// Represents the multirange type in EdgeDB. +/// +/// The inner type of the multirange. +public readonly struct MultiRange : IEnumerable> + where T : struct +{ + /// + /// Gets the length of this multirange. + /// + public int Length + => _ranges.Length; + + /// + /// Gets a element within this multirange. + /// + /// + public readonly ref Range this[int i] + => ref _ranges[i]; + + private readonly Range[] _ranges; + + /// + /// Constructs a new . + /// + /// A set of ranges to put within this multirange. + public MultiRange(HashSet> set) + { + _ranges = set.ToArray(); + } + + internal MultiRange(Range[] ranges) + { + _ranges = ranges; + } + + /// + /// Returns a hashset that represents this multirange. + /// + /// A hashset, derived from the contents of this multirange. + public HashSet> ToSet() => new(_ranges); + + #region Enumerator + private sealed class MultiRangeEnumerator : IEnumerator> + { + private readonly MultiRange _multiRange; + private int _index; + + internal MultiRangeEnumerator(in MultiRange multiRange) + { + _multiRange = multiRange; + _index = -1; + } + + public bool MoveNext() + { + var index = _index + 1; + if (index >= _multiRange.Length) + { + _index = _multiRange.Length; + return false; + } + _index = index; + return true; + } + + public void Reset() => _index = -1; + + public Range Current + { + get + { + var index = _index; + + if (index >= _multiRange.Length) + { + if (index < 0) + { + throw new InvalidOperationException("Enumeration hasn't started"); + } + else + { + throw new InvalidOperationException("The enumeration has finished"); + } + } + + return _multiRange[index]; + } + } + + public void Dispose() + { } + + object IEnumerator.Current => Current; + } + #endregion + + /// + public IEnumerator> GetEnumerator() => new MultiRangeEnumerator(in this); + + /// + IEnumerator IEnumerable.GetEnumerator() => _ranges.GetEnumerator(); +} diff --git a/tests/EdgeDB.Tests.Integration/ClientTests.cs b/tests/EdgeDB.Tests.Integration/ClientTests.cs index 74a766bf..f4a9283f 100644 --- a/tests/EdgeDB.Tests.Integration/ClientTests.cs +++ b/tests/EdgeDB.Tests.Integration/ClientTests.cs @@ -1,3 +1,5 @@ +using EdgeDB.DataTypes; +using EdgeDB.Models.DataTypes; using EdgeDB.State; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -20,6 +22,22 @@ public ClientTests() internal EdgeDBClient EdgeDB { get; set; } + [TestMethod] + public async Task TestMultiRanges() + { + var multiRange = new MultiRange(new[] + { + new Range(-40, -20), new Range(5, 10), new Range(20, 50), new Range(5000, 5001), + }); + + var result = await EdgeDB.QueryRequiredSingleAsync>("select >$arg", + new {arg = multiRange}); + + Assert.AreEqual(result.Length, multiRange.Length); + + Assert.IsTrue(multiRange.SequenceEqual(result)); + } + [TestMethod] public async Task TestNullableReturns() { diff --git a/tests/EdgeDB.Tests.Integration/DDLTests.cs b/tests/EdgeDB.Tests.Integration/DDLTests.cs index 9cd71250..774827b4 100644 --- a/tests/EdgeDB.Tests.Integration/DDLTests.cs +++ b/tests/EdgeDB.Tests.Integration/DDLTests.cs @@ -64,6 +64,6 @@ public async Task TestDDLValidConfigAndCapabilities() private class TestType { - [EdgeDBProperty("name")] public string? Name { get; } + [EdgeDBProperty("name")] public string? Name { get; set; } } } diff --git a/tests/EdgeDB.Tests.Integration/DMLTests.cs b/tests/EdgeDB.Tests.Integration/DMLTests.cs index e8b9d45e..039992b0 100644 --- a/tests/EdgeDB.Tests.Integration/DMLTests.cs +++ b/tests/EdgeDB.Tests.Integration/DMLTests.cs @@ -121,6 +121,6 @@ await _ddlClient.TransactionAsync(async transaction => private class TestType { - [EdgeDBProperty("name")] public string? Name { get; } + [EdgeDBProperty("name")] public string? Name { get; set; } } }