Skip to content

Commit

Permalink
Command properties (DapperLib#32)
Browse files Browse the repository at this point in the history
* - EstimatedRowCountState => AdditionalCommandState
- add (and parse) CommandProperty<DbCommand>

* emit the necessary

* restrict to known/supported types in [CommandProperty<T>]

* minor layout
  • Loading branch information
mgravell authored Aug 14, 2023
1 parent d563d38 commit 80d5203
Show file tree
Hide file tree
Showing 20 changed files with 1,305 additions and 140 deletions.
1 change: 1 addition & 0 deletions src/Dapper.AOT.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ DAP029 | Library | Info | Diagnostics
DAP030 | Library | Error | Diagnostics
DAP031 | Library | Error | Diagnostics
DAP032 | Library | Error | Diagnostics
DAP033 | Library | Warning | Diagnostics
DAP100 | Library | Error | Diagnostics
DAP101 | Library | Error | Diagnostics
DAP102 | Library | Error | Diagnostics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ private static bool TryWriteMultiExecImplementation(
string map, bool cache,
ImmutableArray<IParameterSymbol> methodParameters,
CommandFactoryState factories,
string? fixedSql)
string? fixedSql,
AdditionalCommandState? additionalCommandState)
{
if (!HasAny(flags, OperationFlags.Execute))
{
Expand Down Expand Up @@ -90,7 +91,7 @@ void WriteBatchCommandArguments(ITypeSymbol elementType)
// commandFactory
if (HasAny(flags, OperationFlags.HasParameters))
{
var index = factories.GetIndex(elementType, map, cache, out var subIndex);
var index = factories.GetIndex(elementType, map, cache, additionalCommandState, out var subIndex);
sb.Append("CommandFactory").Append(index).Append(".Instance").Append(subIndex);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void WriteSingleImplementation(
in CommandFactoryState factories,
in RowReaderState readers,
string? fixedSql,
in EstimatedRowCountState estimatedRowCount)
AdditionalCommandState? additionalCommandState)
{
sb.Append("return ");
if (HasAll(flags, OperationFlags.Async | OperationFlags.Query | OperationFlags.Buffered))
Expand Down Expand Up @@ -54,7 +54,7 @@ void WriteSingleImplementation(
sb.Append(", ").Append(Forward(methodParameters, "commandTimeout")).Append(HasParam(methodParameters, "commandTimeout") ? ".GetValueOrDefault()" : "").Append(", ");
if (HasAny(flags, OperationFlags.HasParameters))
{
var index = factories.GetIndex(parameterType!, map, cache, out var subIndex);
var index = factories.GetIndex(parameterType!, map, cache, additionalCommandState, out var subIndex);
sb.Append("CommandFactory").Append(index).Append(".Instance").Append(subIndex);
}
else
Expand Down Expand Up @@ -189,15 +189,15 @@ static bool IsInbuilt(ITypeSymbol? type, out string? helper)
{
sb.NewLine().Append("#error not supported: ").Append(method.Name).NewLine();
}
if (HasAny(flags, OperationFlags.Query) && estimatedRowCount.HasValue)
if (HasAny(flags, OperationFlags.Query) && additionalCommandState is { HasEstimatedRowCount: true })
{
if (estimatedRowCount.MemberName is null)
if (additionalCommandState.EstimatedRowCountMemberName is null)
{
sb.Append(", rowCountHint: ").Append(estimatedRowCount.Count);
sb.Append(", rowCountHint: ").Append(additionalCommandState.EstimatedRowCount);
}
else if (parameterType is not null && !parameterType.IsAnonymousType)
{
sb.Append(", rowCountHint: ((").Append(parameterType).Append(")param!).").Append(estimatedRowCount.MemberName);
sb.Append(", rowCountHint: ((").Append(parameterType).Append(")param!).").Append(additionalCommandState.EstimatedRowCountMemberName);
}
}
if (isAsync && HasParam(methodParameters, "cancellationToken"))
Expand Down
195 changes: 132 additions & 63 deletions src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/Dapper.AOT.Analyzers/CodeAnalysis/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ internal static readonly DiagnosticDescriptor
"The [EstimatedRowCount] parameters are invalid; no parameter should be supplied", Category.Library, DiagnosticSeverity.Error, true),
MemberRowCountHintDuplicated = new("DAP032", "Member-level row-count hint duplicated",
"Only a single member should be marked [EstimatedRowCount]", Category.Library, DiagnosticSeverity.Error, true),
CommandPropertyNotFound = new("DAP033", "Command property not found",
"Command property {0}.{1} was not found or was not valid", Category.Library, DiagnosticSeverity.Warning, true),

// TypeAccessor
TypeAccessorCollectionTypeNotAllowed = new("DAP100", "TypeAccessors does not allow collection types",
Expand Down
223 changes: 223 additions & 0 deletions src/Dapper.AOT.Analyzers/Internal/AdditionalCommandState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
using Dapper.CodeAnalysis;
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Immutable;
using System.Linq;

namespace Dapper.Internal;

internal readonly struct CommandProperty : IEquatable<CommandProperty>
{
public readonly INamedTypeSymbol CommandType;
public readonly string Name;
public readonly object Value;
public readonly Location? Location;

public CommandProperty(INamedTypeSymbol commandType, string name, object value, Location? location)
{
CommandType = commandType;
Name = name;
Value = value;
Location = location;
}

public override string ToString() => $"{CommandType.Name}.{Name}={Value}";

public override int GetHashCode() => SymbolEqualityComparer.Default.GetHashCode(CommandType) & Name.GetHashCode() ^ (Value?.GetHashCode() ?? 0) ^ (Location?.GetHashCode() ?? 0);

public override bool Equals(object obj) => obj is CommandProperty other && Equals(in other);

bool IEquatable<CommandProperty>.Equals(CommandProperty other) => Equals(in other);
public bool Equals(in CommandProperty other)
=> SymbolEqualityComparer.Default.Equals(CommandType, other.CommandType) && string.Equals(Name, other.Name) && Equals(Value, other.Value) && Equals(Location, other.Location);
}

internal sealed class AdditionalCommandState : IEquatable<AdditionalCommandState>
{
public readonly int EstimatedRowCount;
public readonly string? EstimatedRowCountMemberName;
public readonly ImmutableArray<CommandProperty> CommandProperties;

public bool HasEstimatedRowCount => EstimatedRowCount > 0 || EstimatedRowCountMemberName is not null;

public bool HasCommandProperties => !CommandProperties.IsDefaultOrEmpty;

public static AdditionalCommandState? Parse(ISymbol? target, ITypeSymbol? parameterType, ref object? diagnostics)
{
if (target is null) return null;

var inherited = target is IAssemblySymbol ? null : Parse(target.ContainingSymbol, parameterType, ref diagnostics);
var local = ReadFor(target, parameterType, ref diagnostics);
if (inherited is null) return local;
if (local is null) return inherited;
return Combine(inherited, local);
}

private static AdditionalCommandState? ReadFor(ISymbol target, ITypeSymbol? parameterType, ref object? diagnostics)
{
var attribs = target.GetAttributes();
if (attribs.IsDefaultOrEmpty) return null;

int cmdPropsCount = 0;
int estimatedRowCount = 0;
string? estimatedRowCountMember = null;
if (parameterType is not null)
{
foreach (var member in Inspection.GetMembers(parameterType))
{
if (member.IsEstimatedRowCount)
{
if (estimatedRowCountMember is not null)
{
Diagnostics.Add(ref diagnostics, Diagnostic.Create(Diagnostics.MemberRowCountHintDuplicated, member.GetLocation()));
}
estimatedRowCountMember = member.Member.Name;
}
}
}
var location = target.Locations.FirstOrDefault();
foreach (var attrib in attribs)
{
if (Inspection.IsDapperAttribute(attrib))
{
switch (attrib.AttributeClass!.Name)
{
case Types.EstimatedRowCountAttribute:
if (attrib.ConstructorArguments.Length == 1 && attrib.ConstructorArguments[0].Value is int i
&& i > 0)
{
if (estimatedRowCountMember is null)
{
estimatedRowCount = i;
}
else
{
Diagnostics.Add(ref diagnostics, Diagnostic.Create(Diagnostics.MethodRowCountHintRedundant, location, estimatedRowCountMember));
}
}
else
{
Diagnostics.Add(ref diagnostics, Diagnostic.Create(Diagnostics.MethodRowCountHintInvalid, location));
}
break;
case Types.CommandPropertyAttribute:
cmdPropsCount++;
break;
}
}
}

ImmutableArray<CommandProperty> cmdProps;
if (cmdPropsCount != 0)
{
var builder = ImmutableArray.CreateBuilder<CommandProperty>(cmdPropsCount);
foreach (var attrib in attribs)
{
if (Inspection.IsDapperAttribute(attrib) && attrib.AttributeClass!.Name == Types.CommandPropertyAttribute
&& attrib.AttributeClass.Arity == 1
&& attrib.AttributeClass.TypeArguments[0] is INamedTypeSymbol cmdType
&& attrib.ConstructorArguments.Length == 2
&& attrib.ConstructorArguments[0].Value is string name
&& attrib.ConstructorArguments[1].Value is object value)
{
builder.Add(new(cmdType, name, value, location));
}
}
cmdProps = builder.ToImmutable();
}
else
{
cmdProps = ImmutableArray<CommandProperty>.Empty;
}


return cmdProps.IsDefaultOrEmpty && estimatedRowCount <= 0 && estimatedRowCountMember is null
? null : new(estimatedRowCount, estimatedRowCountMember, cmdProps);
}

private static AdditionalCommandState Combine(AdditionalCommandState inherited, AdditionalCommandState overrides)
{
if (inherited is null) return overrides;
if (overrides is null) return inherited;

var count = inherited.EstimatedRowCount;
var countMember = inherited.EstimatedRowCountMemberName;

if (overrides.EstimatedRowCountMemberName is not null)
{
count = 0;
countMember = overrides.EstimatedRowCountMemberName;
}
else if (overrides.EstimatedRowCount > 0)
{
count = overrides.EstimatedRowCount;
countMember = null;
}

return new(count, countMember, Concat(inherited.CommandProperties, overrides.CommandProperties));
}

static ImmutableArray<CommandProperty> Concat(ImmutableArray<CommandProperty> x, ImmutableArray<CommandProperty> y)
{
if (x.IsDefaultOrEmpty) return y;
if (y.IsDefaultOrEmpty) return x;
var builder = ImmutableArray.CreateBuilder<CommandProperty>(x.Length + y.Length);
builder.AddRange(x);
builder.AddRange(y);
return builder.ToImmutable();
}

private AdditionalCommandState(int estimatedRowCount, string? estimatedRowCountMemberName, ImmutableArray<CommandProperty> commandProperties)
{
EstimatedRowCount = estimatedRowCount;
EstimatedRowCountMemberName = estimatedRowCountMemberName;
CommandProperties = commandProperties;
}


public override bool Equals(object obj) => obj is AdditionalCommandState other && Equals(in other);

bool IEquatable<AdditionalCommandState>.Equals(AdditionalCommandState other) => Equals(in other);

public bool Equals(in AdditionalCommandState other)
=> EstimatedRowCount == other.EstimatedRowCount && EstimatedRowCountMemberName == other.EstimatedRowCountMemberName
&& ((CommandProperties.IsDefaultOrEmpty && other.CommandProperties.IsDefaultOrEmpty) || Equals(CommandProperties, other.CommandProperties));

private static bool Equals(in ImmutableArray<CommandProperty> x, in ImmutableArray<CommandProperty> y)
{
if (x.IsDefaultOrEmpty)
{
return y.IsDefaultOrEmpty;
}
if (y.IsDefaultOrEmpty || x.Length != y.Length)
{
return false;
}
var ySpan = y.AsSpan();
int index = 0;
foreach (ref readonly CommandProperty xVal in x.AsSpan())
{
if (!xVal.Equals(in ySpan[index++]))
{
return false;
}
}
return true;
}

static int GetHashCode(in ImmutableArray<CommandProperty> x)
{
if (x.IsDefaultOrEmpty) return 0;

var value = 0;
foreach (ref readonly CommandProperty xVal in x.AsSpan())
{
value = (value * -42) + xVal.GetHashCode();
}
return value;
}

public override int GetHashCode()
=> (EstimatedRowCount + (EstimatedRowCountMemberName is null ? 0 : EstimatedRowCountMemberName.GetHashCode()))
^ (CommandProperties.IsDefaultOrEmpty ? 0 : GetHashCode(in CommandProperties));
}
20 changes: 11 additions & 9 deletions src/Dapper.AOT.Analyzers/Internal/CommandFactoryState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

namespace Dapper.Internal;

internal readonly struct CommandFactoryState : IEnumerable<(ITypeSymbol Type, string Map, int Index, int CacheCount)>
internal readonly struct CommandFactoryState : IEnumerable<(ITypeSymbol Type, string Map, int Index, int CacheCount, AdditionalCommandState? AdditionalCommandState)>
{

public CommandFactoryState(Compilation compilation) => systemObject = compilation.GetSpecialType(SpecialType.System_Object);
private readonly ITypeSymbol systemObject;
private readonly Dictionary<(ITypeSymbol Type, string Map, bool Cached), (int Index, int CacheCount)> parameterTypes = new(ParameterTypeMapComparer.Instance);
private readonly Dictionary<(ITypeSymbol Type, string Map, bool Cached, AdditionalCommandState? AdditionalCommandState), (int Index, int CacheCount)> parameterTypes = new(ParameterTypeMapComparer.Instance);

public int Count()
{
Expand All @@ -24,22 +24,22 @@ public int Count()
return total;
}

public IEnumerator<(ITypeSymbol Type, string Map, int Index, int CacheCount)> GetEnumerator()
public IEnumerator<(ITypeSymbol Type, string Map, int Index, int CacheCount, AdditionalCommandState? AdditionalCommandState)> GetEnumerator()
{
// retain discovery order
return parameterTypes.OrderBy(x => x.Value.Index).Select(x => (x.Key.Type, x.Key.Map, x.Value.Index, x.Value.CacheCount)).GetEnumerator();
return parameterTypes.OrderBy(x => x.Value.Index).Select(x => (x.Key.Type, x.Key.Map, x.Value.Index, x.Value.CacheCount, x.Key.AdditionalCommandState)).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public int GetIndex(ITypeSymbol type, string map, bool cache, out int? subIndex)
public int GetIndex(ITypeSymbol type, string map, bool cache, AdditionalCommandState? additionalCommandState, out int? subIndex)
{
if (string.IsNullOrWhiteSpace(map) && type.IsReferenceType)
{
// just use object if there's nothing to map
type = systemObject;
}
var key = (type!, map, cache);
var key = (type!, map, cache, additionalCommandState);
int index;
if (parameterTypes.TryGetValue(key, out var value))
{
Expand All @@ -63,19 +63,21 @@ public int GetIndex(ITypeSymbol type, string map, bool cache, out int? subIndex)
return index;
}

private sealed class ParameterTypeMapComparer : IEqualityComparer<(ITypeSymbol Type, string Map, bool Cached)>
private sealed class ParameterTypeMapComparer : IEqualityComparer<(ITypeSymbol Type, string Map, bool Cached, AdditionalCommandState? AdditionalCommandState)>
{
public static readonly ParameterTypeMapComparer Instance = new();
private ParameterTypeMapComparer() { }

public bool Equals((ITypeSymbol Type, string Map, bool Cached) x, (ITypeSymbol Type, string Map, bool Cached) y)
public bool Equals((ITypeSymbol Type, string Map, bool Cached, AdditionalCommandState? AdditionalCommandState) x, (ITypeSymbol Type, string Map, bool Cached, AdditionalCommandState? AdditionalCommandState) y)
=> StringComparer.InvariantCultureIgnoreCase.Equals(x.Map, y.Map)
&& SymbolEqualityComparer.Default.Equals(x.Type, y.Type)
&& Equals(x.AdditionalCommandState, y.AdditionalCommandState)
&& x.Cached == y.Cached;

public int GetHashCode((ITypeSymbol Type, string Map, bool Cached) obj)
public int GetHashCode((ITypeSymbol Type, string Map, bool Cached, AdditionalCommandState? AdditionalCommandState) obj)
=> (StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Map)
^ SymbolEqualityComparer.Default.GetHashCode(obj.Type))
^ (obj.AdditionalCommandState is null ? 0 : obj.AdditionalCommandState.GetHashCode())
* (obj.Cached ? -1 : 1);

}
Expand Down
33 changes: 0 additions & 33 deletions src/Dapper.AOT.Analyzers/Internal/EstimatedRowCountState.cs

This file was deleted.

Loading

0 comments on commit 80d5203

Please sign in to comment.