Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ Here's a quick summary about how this library performs with a quick example of m

| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|---------------------------- |-----------:|---------:|---------:|-------:|-------:|----------:|
| Marshall_AWS_Reflection | 2,467.8 ns | 48.87 ns | 68.51 ns | 0.5112 | 0.0038 | 6450 B |
| Marshall_Source_Generated | 234.0 ns | 2.92 ns | 2.73 ns | 0.1421 | 0.0010 | 1784 B |
| Unmarshall_AWS_Reflection | 2,397.0 ns | 36.13 ns | 30.17 ns | 0.5188 | 0.0038 | 6544 B |
| Unmarshall_Source_Generated | 131.6 ns | 0.82 ns | 0.77 ns | 0.0126 | - | 160 B |
| Marshall_AWS_Reflection | 5,351.9 ns | 74.57 ns | 69.75 ns | 0.8545 | - | 10875 B |
| Marshall_Source_Generated | 488.9 ns | 8.21 ns | 7.68 ns | 0.2375 | 0.0019 | 2984 B |
| Unmarshall_AWS_Reflection | 5,447.3 ns | 68.30 ns | 63.89 ns | 0.8545 | - | 10922 B |
| Unmarshall_Source_Generated | 923.6 ns | 10.92 ns | 10.21 ns | 0.0610 | - | 768 B |

## Features:

Expand Down
6 changes: 5 additions & 1 deletion src/DynamoDBGenerator.SourceGenerator/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public static class Marshaller

public static class AttributeValueUtilityFactory
{
private const string ClassName = "MarshallHelper";
public const string ClassName = "MarshallHelper";
public const string ToAttributeValue = $"{ClassName}.ToAttributeValue";
public const string Null = $"{ClassName}.Null";
public const string ToList = $"{ClassName}.ToList";
Expand All @@ -93,6 +93,10 @@ public static class AttributeValueUtilityFactory
public const string ToDictionary = $"{ClassName}.ToDictionary";
public const string ToLookup = $"{ClassName}.ToLookup";
public const string FromLookup = $"{ClassName}.FromLookup";
public const string FromStringSet = $"{ClassName}.FromStringSet";
public const string FromNullableStringSet = $"{ClassName}.FromNullableStringSet";
public const string FromNumberSet = $"{ClassName}.FromNumberSet";
public const string FromNullableNumberSet = $"{ClassName}.FromNullableNumberSet";
}
public static class ExceptionHelper
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,15 @@ SingleGeneric.SupportedType.Set when singleGeneric.T.TypeSymbol.SpecialType is S
.CreateScope(
$"if ({ParamReference} is null)"
.CreateScope(singleGeneric.ReturnNullOrThrow(DataMember))
.Append($"return new {Constants.AWSSDK_DynamoDBv2.AttributeValue} {{ SS = new List<{(singleGeneric.T.IsSupposedToBeNull ? "string?" : "string")}>({(singleGeneric.T.IsSupposedToBeNull ? ParamReference : $"{ParamReference}.Select((y,i) => y ?? throw {ExceptionHelper.NullExceptionMethod}($\"{{{DataMember}}}[UNKNOWN]\"))")})}};")
.Append($"return {(singleGeneric.T.IsSupposedToBeNull ? AttributeValueUtilityFactory.FromNullableStringSet : AttributeValueUtilityFactory.FromStringSet)}({ParamReference}, {DataMember});")
)
.ToConversion(singleGeneric.T),
SingleGeneric.SupportedType.Set when singleGeneric.T.IsNumeric
=> signature
.CreateScope(
$"if ({ParamReference} is null)"
.CreateScope(singleGeneric.ReturnNullOrThrow(DataMember))
.Append($"return new {Constants.AWSSDK_DynamoDBv2.AttributeValue} {{ NS = new List<string>({ParamReference}.Select(y => y.ToString())) }};")
.Append($"return {(singleGeneric.T.IsSupposedToBeNull ? AttributeValueUtilityFactory.FromNullableNumberSet : AttributeValueUtilityFactory.FromNumberSet)}({ParamReference}, {DataMember});")
)
.ToConversion(singleGeneric.T),
SingleGeneric.SupportedType.Set => throw new ArgumentException("Only string and integers are supported for sets", UncoveredConversionException(singleGeneric, nameof(CreateMethod))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ private static CodeFactory CreateMethod(TypeIdentifier typeIdentifier, Func<ITyp
.CreateScope(
$"if ({Value} is null || {Value}.NS is null)"
.CreateScope(singleGeneric.ReturnNullOrThrow(DataMember))
.Append($"return new {(singleGeneric.TypeSymbol.TypeKind is TypeKind.Interface ? $"HashSet<{singleGeneric.T.UnannotatedString}>" : null)}({Value}.NS.Select(y => {singleGeneric.T.UnannotatedString}.Parse(y)));")
.Append($"return {AttributeValueUtilityFactory.ClassName}.{(singleGeneric.T.IsSupposedToBeNull ? $"ToNullableNumber{typeIdentifier.TypeSymbol.Name}" : $"ToNumber{typeIdentifier.TypeSymbol.Name}")}<{singleGeneric.T.UnannotatedString}>({Value}.NS, {DataMember});")
)
.ToConversion(singleGeneric),
SingleGeneric.SupportedType.Set => throw new ArgumentException("Only string and integers are supported for sets", UncoveredConversionException(singleGeneric, nameof(CreateMethod))),
Expand Down
13 changes: 11 additions & 2 deletions src/DynamoDBGenerator.SourceGenerator/Types/TypeIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ protected TypeIdentifier(ITypeSymbol typeSymbol)
var (annotated, original) = Representation(typeSymbol);
UnannotatedString = original;
AnnotatedString = annotated;
IsNumeric = IsNumericMethod(typeSymbol);
CanBeNull = typeSymbol is { IsReferenceType: true } or { IsValueType: true, OriginalDefinition.SpecialType: SpecialType.System_Nullable_T };
IsNumeric = IsNumericMethod(typeSymbol);
}

/// <summary>
Expand All @@ -54,7 +54,12 @@ private static (string annotated, string original) Representation(ITypeSymbol ty
return RepresentationDictionary.GetOrAdd(typeSymbol, x =>
{
var displayString = x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
return RepresentationDictionary[typeSymbol] = (ToString(typeSymbol, displayString), displayString);

var unAnnotated = x.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T
? displayString.Substring(0, displayString.Length - 1)
: displayString;

return RepresentationDictionary[typeSymbol] = (ToString(typeSymbol, displayString), unAnnotated);
});

static string ToString(ITypeSymbol x, string displayString)
Expand Down Expand Up @@ -118,6 +123,9 @@ static string ExceptionMessage(ISymbol typeSymbol)

private static bool IsNumericMethod(ITypeSymbol typeSymbol)
{
if (typeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
typeSymbol = ((INamedTypeSymbol)typeSymbol).TypeArguments[0];

return typeSymbol.SpecialType
is SpecialType.System_Int16
or SpecialType.System_Byte
Expand All @@ -130,6 +138,7 @@ or SpecialType.System_UInt64
or SpecialType.System_Decimal
or SpecialType.System_Double
or SpecialType.System_Single;

}

public string ReturnNullOrThrow(string dataMember)
Expand Down
226 changes: 216 additions & 10 deletions src/DynamoDBGenerator/Internal/MarshallHelper.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Amazon.DynamoDBv2.Model;
using static System.Runtime.InteropServices.CollectionsMarshal;

Expand All @@ -22,9 +23,12 @@ public static class MarshallHelper
[return: NotNullIfNotNull(nameof(dict))]
public static AttributeValue? ToAttributeValue(
[NotNullIfNotNull(nameof(dict))] Dictionary<string, AttributeValue>? dict
) => dict is null
? null
: new AttributeValue { M = dict };
)
{
return dict is null
? null
: new AttributeValue { M = dict };
}

public static AttributeValue FromDictionary<T, TArgument>(
IEnumerable<KeyValuePair<string, T>> dictionary,
Expand All @@ -45,6 +49,203 @@ public static AttributeValue FromDictionary<T, TArgument>(
return new AttributeValue { M = elements };
}

public static AttributeValue FromNullableNumberSet<T>(IEnumerable<T?> numbers, string? _)
where T : struct, INumber<T>
{
if (numbers.TryGetNonEnumeratedCount(out var count) is false)
return new AttributeValue
{
NS = numbers.Select(number => number?.ToString()).ToList()
};

if (count is 0)
return new AttributeValue { NS = [] };

var list = new List<string?>(count);
list.AddRange(numbers.Select(number => number?.ToString()));

return new AttributeValue { NS = list };
}

public static AttributeValue FromNumberSet<T>(IEnumerable<T> numbers, string? dataMember)
where T : struct, INumber<T>
{
if (numbers.TryGetNonEnumeratedCount(out var count) is false)
{
var noCapacity = new List<string>();

foreach (var number in numbers)
{
var @string = number.ToString();
if (string.IsNullOrEmpty(@string))
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");

noCapacity.Add(@string);
}

return new AttributeValue { NS = noCapacity };
}

if (count is 0)
return new AttributeValue { NS = [] };

var list = new List<string>(count);

foreach (var number in numbers)
{
var @string = number.ToString();
if (string.IsNullOrEmpty(@string))
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");

list.Add(@string);
}

return new AttributeValue { NS = list };
}

public static AttributeValue FromNullableStringSet(IEnumerable<string?> strings, string? _)
{
if (strings.TryGetNonEnumeratedCount(out var count) is false)
return new AttributeValue { SS = strings.ToList() };

if (count is 0)
return new AttributeValue { SS = [] };

var list = new List<string?>(count);
list.AddRange(strings);

return new AttributeValue { SS = list };
}

public static AttributeValue FromStringSet(IEnumerable<string> strings, string? dataMember)
{
if (strings.TryGetNonEnumeratedCount(out var count) is false)
{
var list = new List<string>();
foreach (var @string in strings)
{
if (@string is null)
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");
list.Add(@string);
}

return new AttributeValue { SS = list };
}
else
{
if (count is 0)
return new AttributeValue { SS = [] };

var list = new List<string>(count);

foreach (var @string in strings)
list.Add(@string ?? throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]"));

return new AttributeValue { SS = list };
}
}

private static TSet ToNumberSet<TNumber, TSet>(
List<string> numbers,
Func<int, TSet> factory,
string? dataMember
)
where TSet : ICollection<TNumber>
where TNumber : struct, INumber<TNumber>
{
var span = AsSpan(numbers);
var set = factory(span.Length);

foreach (var number in span)
{
if (number is null)
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");

set.Add(TNumber.Parse(number, null));
}

return set;
}

private static TSet ToNullableNumberSet<TNumber, TSet>(
List<string?> numbers,
Func<int, TSet> factory,
string? _
)
where TSet : ICollection<TNumber?>
where TNumber : struct, INumber<TNumber>
{
var span = AsSpan(numbers);
var set = factory(span.Length);

foreach (var number in span)
{
if (number is null)
set.Add(null);
else
set.Add(TNumber.Parse(number, null));
}

return set;
}

public static ISet<TNumber> ToNumberISet<TNumber>(List<string> ns, string? dataMember)
where TNumber : struct, INumber<TNumber>
{
return ToNumberSet<TNumber, HashSet<TNumber>>(ns, i => new HashSet<TNumber>(i), dataMember);
}

public static IReadOnlySet<TNumber> ToNumberIReadOnlySet<TNumber>(List<string> ns, string? dataMember)
where TNumber : struct, INumber<TNumber>
{
return ToNumberSet<TNumber, HashSet<TNumber>>(ns, i => new HashSet<TNumber>(i), dataMember);
}

public static HashSet<TNumber> ToNumberHashSet<TNumber>(List<string> ns, string? dataMember)
where TNumber : struct, INumber<TNumber>
{
return ToNumberSet<TNumber, HashSet<TNumber>>(ns, i => new HashSet<TNumber>(i), dataMember);
}

public static SortedSet<TNumber> ToNumberSortedSet<TNumber>(List<string> ns, string? dataMember)
where TNumber : struct, INumber<TNumber>
{
var span = AsSpan(ns);
var set = new SortedSet<TNumber>();
foreach (var se in span)
{
if (se is null)
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");

set.Add(TNumber.Parse(se, null));
}

return set;
}

public static ISet<TNumber?> ToNullableNumberISet<TNumber>(List<string?> ns, string? dataMember)
where TNumber : struct, INumber<TNumber>
{
return ToNullableNumberSet<TNumber, HashSet<TNumber?>>(ns, i => new HashSet<TNumber?>(i), dataMember);
}

public static IReadOnlySet<TNumber?> ToNullableNumberIReadOnlySet<TNumber>(List<string?> ns, string? dataMember)
where TNumber : struct, INumber<TNumber>
{
return ToNullableNumberSet<TNumber, HashSet<TNumber?>>(ns, i => new HashSet<TNumber?>(i), dataMember);
}

public static HashSet<TNumber?> ToNullableNumberHashSet<TNumber>(List<string?> ns, string? dataMember)
where TNumber : struct, INumber<TNumber>
{
return ToNullableNumberSet<TNumber, HashSet<TNumber?>>(ns, i => new HashSet<TNumber?>(i), dataMember);
}

public static SortedSet<TNumber?> ToNullableNumberSortedSet<TNumber>(List<string?> ns, string? _)
where TNumber : struct, INumber<TNumber>
{
return new SortedSet<TNumber?>(ns.Select(x => x is null ? (TNumber?)null : TNumber.Parse(x, null)));
}

public static ILookup<string, T> ToLookup<T, TArgument>(
Dictionary<string, AttributeValue> dictionary,
Expand Down Expand Up @@ -134,15 +335,20 @@ public static AttributeValue FromEnumerable<T, TArgument>(
string? dataMember,
Func<T, TArgument, string?, AttributeValue> resultSelector)
{
var attributeValues = enumerable.TryGetNonEnumeratedCount(out var count)
? new List<AttributeValue>(count)
: new List<AttributeValue>();
if (enumerable.TryGetNonEnumeratedCount(out var count) is false)
return new AttributeValue
{
L = [..enumerable.Select((element, i) => resultSelector(element, argument, $"{dataMember}[{i}]"))]
};

if (count == 0)
return new AttributeValue { L = [] };

var list = new List<AttributeValue>(count);
foreach (var (element, i) in enumerable.Select((x, y) => (x, y)))
attributeValues.Add(resultSelector(element, argument, $"{dataMember}[{i}]"));
list.Add(resultSelector(element, argument, $"{dataMember}[{i}]"));


return new AttributeValue { L = attributeValues };
return new AttributeValue { L = list };
}

public static List<TResult> ToList<TResult, TArgument>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class MarshallBenchmark
private readonly PersonEntity _singleElement;
private readonly Dictionary<string, AttributeValue> _attributeValues;


public MarshallBenchmark()
{
var fixture = new Fixture();
Expand Down Expand Up @@ -62,4 +63,4 @@ public PersonEntity Unmarshall_Source_Generated()
{
return PersonEntity.PersonEntityMarshaller.Unmarshall(_attributeValues);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Amazon.DynamoDBv2.DataModel;
using DynamoDBGenerator.Attributes;
namespace DynamoDBGenerator.SourceGenerator.Benchmarks.Models;

namespace DynamoDBGenerator.SourceGenerator.Benchmarks.Models;

[DynamoDBMarshaller(EntityType = typeof(PersonEntity))]
public partial record PersonEntity
Expand All @@ -11,5 +11,11 @@ public partial record PersonEntity

public string Firstname { get; set; } = null!;
public string Lastname { get; set; } = null!;
public DateTime BirthDate { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime InsertedAt { get; set; }
public DateTime? DeletedAt { get; set; }
public Address Address { get; set; } = null!;
}
public HashSet<string> StringSet { get; set; }
public HashSet<int> IntSet { get; set; }
}
Loading