Skip to content

Commit

Permalink
Multirange Support (#73)
Browse files Browse the repository at this point in the history
  • Loading branch information
quinchs authored Nov 1, 2023
1 parent 8ae1b69 commit dd17a6c
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 17 deletions.
1 change: 1 addition & 0 deletions examples/EdgeDB.Examples.FSharp/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ open Serilog
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Logging
open EdgeDB

Log.Logger <-
LoggerConfiguration()
Expand Down
58 changes: 58 additions & 0 deletions src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs
Original file line number Diff line number Diff line change
@@ -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<T> : BaseCodec<MultiRange<T>>, IWrappingCodec, ICacheableCodec
where T : struct
{
private RangeCodec<T> _rangeCodec;

public MultiRangeCodec(in Guid id, ICodec<T> rangeInnerCodec, CodecMetadata? metadata) : base(in id, metadata)
{
_rangeCodec = new RangeCodec<T>(in id, rangeInnerCodec, metadata);
}

public override void Serialize(ref PacketWriter writer, MultiRange<T> 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<T> Deserialize(ref PacketReader reader, CodecContext context)
{
var length = reader.ReadInt32();

var elements = new Range<T>[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<T>(elements);
}

public ICodec InnerCodec
{
get => _rangeCodec;
set
{
if (value is not RangeCodec<T> r)
throw new ArgumentException($"Expected a range codec, but got {value}");

_rangeCodec = r;
}
}

public override string ToString()
=> "multirange";
}
2 changes: 2 additions & 0 deletions src/EdgeDB.Net.Driver/Binary/Protocol/IProtocolProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ internal interface IProtocolProvider

IReadOnlyDictionary<string, object?> ServerConfig { get; }

int? SuggestedPoolConcurrency { get; }

public static IProtocolProvider GetDefaultProvider(EdgeDBBinaryClient client)
=> (_defaultProvider ??= Providers[ProtocolVersion.EdgeDBBinaryDefaultVersion].Factory)(client);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> ServerKey
=> ref _serverKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ internal enum DescriptorType : byte
Range = 9,
Object = 10,
Compound = 11,
MultiRange = 12,
TypeAnnotationText = 127
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
};
}
Expand Down Expand Up @@ -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.");
Expand Down
18 changes: 4 additions & 14 deletions src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -34,7 +35,9 @@ internal abstract class EdgeDBBinaryClient : BaseEdgeDBClient
private Guid _stateDescriptorId;

internal byte[] ServerKey;
internal int? SuggestedPoolConcurrency;

internal int? SuggestedPoolConcurrency
=> _protocolProvider.SuggestedPoolConcurrency;

/// <summary>
/// Creates a new binary client with the provided conection and config.
Expand Down Expand Up @@ -92,19 +95,6 @@ internal ref Guid StateDescriptorId
protected CancellationToken DisconnectCancelToken
=> Duplexer.DisconnectToken;

#region Events

/// <summary>
/// Fired when the client disconnects.
/// </summary>
public event Func<ValueTask> OnDisconnect
{
add => OnDisconnectInternal.Add(c => value());
remove => OnDisconnectInternal.Remove(c => value());
}

#endregion

#region Client pool dispose

/// <remarks />
Expand Down
108 changes: 108 additions & 0 deletions src/EdgeDB.Net.Driver/Models/DataTypes/MultiRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using EdgeDB.DataTypes;
using System.Collections;

namespace EdgeDB.Models.DataTypes;

/// <summary>
/// Represents the <c>multirange</c> type in EdgeDB.
/// </summary>
/// <typeparam name="T">The inner type of the multirange.</typeparam>
public readonly struct MultiRange<T> : IEnumerable<Range<T>>
where T : struct
{
/// <summary>
/// Gets the length of this multirange.
/// </summary>
public int Length
=> _ranges.Length;

/// <summary>
/// Gets a <see cref="Range{T}"/> element within this multirange.
/// </summary>
/// <param name="i"></param>
public readonly ref Range<T> this[int i]
=> ref _ranges[i];

private readonly Range<T>[] _ranges;

/// <summary>
/// Constructs a new <see cref="MultiRange{T}"/>.
/// </summary>
/// <param name="set">A set of ranges to put within this multirange.</param>
public MultiRange(HashSet<Range<T>> set)
{
_ranges = set.ToArray();
}

internal MultiRange(Range<T>[] ranges)
{
_ranges = ranges;
}

/// <summary>
/// Returns a hashset that represents this multirange.
/// </summary>
/// <returns>A hashset, derived from the contents of this multirange.</returns>
public HashSet<Range<T>> ToSet() => new(_ranges);

#region Enumerator
private sealed class MultiRangeEnumerator : IEnumerator<Range<T>>
{
private readonly MultiRange<T> _multiRange;
private int _index;

internal MultiRangeEnumerator(in MultiRange<T> 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<T> 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

/// <inheritdoc />
public IEnumerator<Range<T>> GetEnumerator() => new MultiRangeEnumerator(in this);

/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => _ranges.GetEnumerator();
}
18 changes: 18 additions & 0 deletions tests/EdgeDB.Tests.Integration/ClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using EdgeDB.DataTypes;
using EdgeDB.Models.DataTypes;
using EdgeDB.State;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
Expand All @@ -20,6 +22,22 @@ public ClientTests()

internal EdgeDBClient EdgeDB { get; set; }

[TestMethod]
public async Task TestMultiRanges()
{
var multiRange = new MultiRange<int>(new[]
{
new Range<int>(-40, -20), new Range<int>(5, 10), new Range<int>(20, 50), new Range<int>(5000, 5001),
});

var result = await EdgeDB.QueryRequiredSingleAsync<MultiRange<int>>("select <multirange<int32>>$arg",
new {arg = multiRange});

Assert.AreEqual(result.Length, multiRange.Length);

Assert.IsTrue(multiRange.SequenceEqual(result));
}

[TestMethod]
public async Task TestNullableReturns()
{
Expand Down
2 changes: 1 addition & 1 deletion tests/EdgeDB.Tests.Integration/DDLTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ public async Task TestDDLValidConfigAndCapabilities()

private class TestType
{
[EdgeDBProperty("name")] public string? Name { get; }
[EdgeDBProperty("name")] public string? Name { get; set; }
}
}
2 changes: 1 addition & 1 deletion tests/EdgeDB.Tests.Integration/DMLTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}

0 comments on commit dd17a6c

Please sign in to comment.