Skip to content

Commit

Permalink
Fixed serialization of composite Ids when setting outputNewIdFormat: …
Browse files Browse the repository at this point in the history
…false (#7426)
  • Loading branch information
tobias-tengler authored Sep 3, 2024
1 parent 6145bc2 commit a88692f
Show file tree
Hide file tree
Showing 7 changed files with 569 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static IRequestExecutorBuilder AddDefaultNodeIdSerializer(
builder.Services.AddSingleton<INodeIdValueSerializer, Int16NodeIdValueSerializer>();
builder.Services.AddSingleton<INodeIdValueSerializer, Int32NodeIdValueSerializer>();
builder.Services.AddSingleton<INodeIdValueSerializer, Int64NodeIdValueSerializer>();
builder.Services.AddSingleton<INodeIdValueSerializer, GuidNodeIdValueSerializer>();
builder.Services.AddSingleton<INodeIdValueSerializer>(new GuidNodeIdValueSerializer(compress: outputNewIdFormat));
}

builder.Services.TryAddSingleton<INodeIdSerializer>(sp =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ public string Format(string typeName, object internalId)
throw new ArgumentNullException(nameof(internalId));
}

if (!outputNewIdFormat)
{
return LegacyNodeIdSerializer.FormatInternal(typeName, internalId);
}

var runtimeType = internalId.GetType();
var serializer = TryResolveSerializer(runtimeType);

Expand All @@ -59,13 +54,15 @@ public string Format(string typeName, object internalId)
return Format(
_names.GetOrAdd(typeName, static n => _utf8.GetBytes(n)),
internalId,
serializer);
serializer,
outputNewIdFormat);
}

private static unsafe string Format(
ReadOnlySpan<byte> typeName,
object internalId,
INodeIdValueSerializer serializer)
INodeIdValueSerializer serializer,
bool outputNewIdFormat)
{
var minLength = typeName.Length + 128;
byte[]? rentedBuffer = null;
Expand All @@ -74,10 +71,8 @@ private static unsafe string Format(
: rentedBuffer = ArrayPool<byte>.Shared.Rent(minLength);
var capacity = span.Length;

typeName.CopyTo(span);
var valueSpan = span.Slice(typeName.Length);
valueSpan[0] = _delimiter;
valueSpan = valueSpan.Slice(1);
var valueSpan = WriteIdHeader(span, typeName, internalId, outputNewIdFormat);

var result = serializer.Format(valueSpan, internalId, out var written);

while (result == NodeIdFormatterResult.BufferTooSmall)
Expand All @@ -94,16 +89,15 @@ private static unsafe string Format(

rentedBuffer = newBuffer;

typeName.CopyTo(span);
valueSpan = span.Slice(typeName.Length);
valueSpan[0] = _delimiter;
valueSpan = valueSpan.Slice(1);
valueSpan = WriteIdHeader(span, typeName, internalId, outputNewIdFormat);

result = serializer.Format(valueSpan, internalId, out written);
}

if (result == NodeIdFormatterResult.Success)
{
var dataLength = typeName.Length + 1 + written;
var delimiterLength = outputNewIdFormat ? 1 : 2;
var dataLength = typeName.Length + delimiterLength + written;

while (Base64.EncodeToUtf8InPlace(span, dataLength, out written) == OperationStatus.DestinationTooSmall)
{
Expand Down Expand Up @@ -132,6 +126,27 @@ private static unsafe string Format(
throw new NodeIdInvalidFormatException(internalId);
}

private static Span<byte> WriteIdHeader(
Span<byte> span,
ReadOnlySpan<byte> typeName,
object value,
bool outputNewIdFormat)
{
typeName.CopyTo(span);

var valueSpan = span.Slice(typeName.Length);

if (outputNewIdFormat)
{
valueSpan[0] = _delimiter;
return valueSpan.Slice(1);
}

valueSpan[0] = _legacyDelimiter;
valueSpan[1] = LegacyNodeIdSerializer.GetLegacyValueCode(value);
return valueSpan.Slice(2);
}

public NodeId Parse(string formattedId, INodeIdRuntimeTypeLookup runtimeTypeLookup)
{
if (formattedId is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,27 @@ public NodeId Parse(string formattedId, INodeIdRuntimeTypeLookup runtimeTypeLook
return Parse(formattedId);
}

public static byte GetLegacyValueCode(object value)
{
switch (value)
{
case Guid g:
return Guid;

case short s:
return Short;

case int i:
return Int;

case long l:
return Long;

default:
return Default;
}
}

private static NodeId Parse(string formattedId)
{
if (formattedId is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ internal OptimizedNodeIdSerializer(
{
#if NET8_0_OR_GREATER
_stringSerializerMap =
boundSerializers.ToFrozenDictionary(t => t.TypeName, t => new Serializer(t.TypeName, t.Serializer));
boundSerializers.ToFrozenDictionary(t => t.TypeName, t => new Serializer(t.TypeName, t.Serializer, outputNewIdFormat));
#else
_stringSerializerMap =
boundSerializers.ToDictionary(t => t.TypeName, t => new Serializer(t.TypeName, t.Serializer));
boundSerializers.ToDictionary(t => t.TypeName, t => new Serializer(t.TypeName, t.Serializer, outputNewIdFormat));
#endif
_serializers = allSerializers;
_spanSerializerMap = new SpanSerializerMap();
Expand All @@ -67,11 +67,6 @@ public string Format(string typeName, object internalId)
throw new ArgumentNullException(nameof(internalId));
}

if (!_outputNewIdFormat)
{
return LegacyNodeIdSerializer.FormatInternal(typeName, internalId);
}

if (!_stringSerializerMap.TryGetValue(typeName, out var serializer))
{
throw new NodeIdMissingSerializerException(typeName);
Expand Down Expand Up @@ -185,7 +180,7 @@ public unsafe NodeId Parse(string formattedId, Type runtimeType)
{
if (!_spanSerializerMap.TryGetValue(typeName, out serializer))
{
serializer = new Serializer(ToString(typeName), valueSerializer);
serializer = new Serializer(ToString(typeName), valueSerializer, _outputNewIdFormat);
_spanSerializerMap.Add(serializer.FormattedTypeName, serializer);
}
}
Expand Down Expand Up @@ -283,7 +278,7 @@ private static void Clear(byte[]? rentedBuffer = null)
}
}

private sealed class Serializer(string typeName, INodeIdValueSerializer valueSerializer)
private sealed class Serializer(string typeName, INodeIdValueSerializer valueSerializer, bool outputNewIdFormat)
{
private readonly byte[] _formattedTypeName = _utf8.GetBytes(typeName);

Expand All @@ -302,10 +297,8 @@ public unsafe string Format(object value)
: rentedBuffer = ArrayPool<byte>.Shared.Rent(minLength);
var capacity = span.Length;

_formattedTypeName.CopyTo(span);
var valueSpan = span.Slice(_formattedTypeName.Length);
valueSpan[0] = _delimiter;
valueSpan = valueSpan.Slice(1);
var valueSpan = WriteIdHeader(span, _formattedTypeName, value, outputNewIdFormat);

var result = valueSerializer.Format(valueSpan, value, out var written);

while (result == NodeIdFormatterResult.BufferTooSmall)
Expand All @@ -322,16 +315,15 @@ public unsafe string Format(object value)

rentedBuffer = newBuffer;

_formattedTypeName.CopyTo(span);
valueSpan = span.Slice(_formattedTypeName.Length);
valueSpan[0] = _delimiter;
valueSpan = valueSpan.Slice(1);
valueSpan = WriteIdHeader(span, _formattedTypeName, value, outputNewIdFormat);

result = valueSerializer.Format(valueSpan, value, out written);
}

if (result == NodeIdFormatterResult.Success)
{
var dataLength = _formattedTypeName.Length + 1 + written;
var delimiterLength = outputNewIdFormat ? 1 : 2;
var dataLength = _formattedTypeName.Length + delimiterLength + written;

while (Base64.EncodeToUtf8InPlace(span, dataLength, out written) == OperationStatus.DestinationTooSmall)
{
Expand Down Expand Up @@ -362,6 +354,27 @@ public unsafe string Format(object value)

public NodeId Parse(ReadOnlySpan<byte> formattedValue)
=> ParseValue(valueSerializer, typeName, formattedValue);

private static Span<byte> WriteIdHeader(
Span<byte> span,
ReadOnlySpan<byte> typeName,
object value,
bool outputNewIdFormat)
{
typeName.CopyTo(span);

var valueSpan = span.Slice(typeName.Length);

if (outputNewIdFormat)
{
valueSpan[0] = _delimiter;
return valueSpan.Slice(1);
}

valueSpan[0] = _legacyDelimiter;
valueSpan[1] = LegacyNodeIdSerializer.GetLegacyValueCode(value);
return valueSpan.Slice(2);
}
}

// we keep the initial bucket size small to reduce memory overhead since we usually will build the map
Expand Down
Loading

0 comments on commit a88692f

Please sign in to comment.