Skip to content

Commit

Permalink
Introduce selectors (#4)
Browse files Browse the repository at this point in the history
* Fix namespaces

* Improve test coverage

* Remove UnitOfWork namespace nesting

* Add first version of selectors

* Add caching to selectors
  • Loading branch information
Enterprize1 authored Aug 29, 2024
1 parent 46e8191 commit 69146a7
Show file tree
Hide file tree
Showing 63 changed files with 1,943 additions and 113 deletions.
13 changes: 13 additions & 0 deletions src/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"verify.tool": {
"version": "0.6.0",
"commands": [
"dotnet-verify"
],
"rollForward": false
}
}
}
6 changes: 5 additions & 1 deletion src/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ bld/
[Oo]bj/
msbuild.log
msbuild.err
msbuild.wrn
msbuild.wrn

.idea

*.received.*
2 changes: 1 addition & 1 deletion src/Fluss.HotChocolate/AddExtensionMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private async IAsyncEnumerable<IQueryResult> LiveResults(IReadOnlyDictionary<str
break;
}

if (contextData[nameof(UnitOfWork)] is not UnitOfWork.UnitOfWork unitOfWork)
if (contextData[nameof(UnitOfWork)] is not UnitOfWork unitOfWork)
{
break;
}
Expand Down
1 change: 0 additions & 1 deletion src/Fluss.HotChocolate/BuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Fluss.UnitOfWork;
using HotChocolate.Execution.Configuration;
using HotChocolate.Internal;
using Microsoft.Extensions.DependencyInjection;
Expand Down
13 changes: 6 additions & 7 deletions src/Fluss.HotChocolate/UnitOfWorkParameterExpressionBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Linq.Expressions;
using System.Reflection;
using Fluss.UnitOfWork;
using HotChocolate.Internal;
using HotChocolate.Resolvers;

Expand All @@ -13,7 +12,7 @@ public class UnitOfWorkParameterExpressionBuilder : IParameterExpressionBuilder
private static readonly MethodInfo GetOrSetGlobalStateUnitOfWorkMethod =
typeof(ResolverContextExtensions).GetMethods()
.First(m => m.Name == nameof(ResolverContextExtensions.GetOrSetGlobalState))
.MakeGenericMethod(typeof(UnitOfWork.UnitOfWork));
.MakeGenericMethod(typeof(UnitOfWork));

private static readonly MethodInfo GetGlobalStateOrDefaultLongMethod =
typeof(ResolverContextExtensions).GetMethods()
Expand All @@ -24,20 +23,20 @@ public class UnitOfWorkParameterExpressionBuilder : IParameterExpressionBuilder
typeof(IPureResolverContext).GetMethods().First(
method => method.Name == nameof(IPureResolverContext.Service) &&
method.IsGenericMethod)
.MakeGenericMethod(typeof(UnitOfWork.UnitOfWork));
.MakeGenericMethod(typeof(UnitOfWork));

private static readonly MethodInfo GetValueOrDefaultMethod =
typeof(CollectionExtensions).GetMethods().First(m => m.Name == nameof(CollectionExtensions.GetValueOrDefault) && m.GetParameters().Length == 2);

private static readonly MethodInfo WithPrefilledVersionMethod =
typeof(UnitOfWork.UnitOfWork).GetMethods(BindingFlags.Instance | BindingFlags.Public)
.First(m => m.Name == nameof(UnitOfWork.UnitOfWork.WithPrefilledVersion));
typeof(UnitOfWork).GetMethods(BindingFlags.Instance | BindingFlags.Public)
.First(m => m.Name == nameof(UnitOfWork.WithPrefilledVersion));

private static readonly PropertyInfo ContextData =
typeof(IHasContextData).GetProperty(
nameof(IHasContextData.ContextData))!;

public bool CanHandle(ParameterInfo parameter) => typeof(UnitOfWork.UnitOfWork) == parameter.ParameterType
public bool CanHandle(ParameterInfo parameter) => typeof(UnitOfWork) == parameter.ParameterType
|| typeof(IUnitOfWork) == parameter.ParameterType;

/*
Expand All @@ -63,7 +62,7 @@ public Expression Build(ParameterExpressionBuilderContext builderContext)
Expression.Constant(PrefillUnitOfWorkVersion)));

return Expression.Call(null, GetOrSetGlobalStateUnitOfWorkMethod, context, Expression.Constant(nameof(UnitOfWork)),
Expression.Lambda<Func<string, UnitOfWork.UnitOfWork>>(
Expression.Lambda<Func<string, UnitOfWork>>(
getNewUnitOfWork,
Expression.Parameter(typeof(string))));
}
Expand Down
2 changes: 0 additions & 2 deletions src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System.Reflection;
using FluentMigrator.Runner;
using Fluss.Core;
using Fluss.Events;
using Fluss.Upcasting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down
19 changes: 19 additions & 0 deletions src/Fluss.Regen/Attributes/SelectorAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Fluss.Regen.Attributes;

public abstract class SelectorAttribute
{
public static string FullName => $"{Namespace}.{AttributeName}";

private const string Namespace = "Fluss.Regen";
private const string AttributeName = "SelectorAttribute";

public const string AttributeSourceCode = $@"// <auto-generated/>
namespace {Namespace}
{{
[System.AttributeUsage(System.AttributeTargets.Method)]
public class {AttributeName} : System.Attribute
{{
}}
}}";
}
27 changes: 27 additions & 0 deletions src/Fluss.Regen/Fluss.Regen.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>

<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>

<RootNamespace>Fluss.Regen</RootNamespace>
<PackageId>Fluss.Regen</PackageId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
</ItemGroup>


</Project>
181 changes: 181 additions & 0 deletions src/Fluss.Regen/Generators/SelectorSyntaxGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Fluss.Regen.Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Fluss.Regen.Generators;

public sealed class SelectorSyntaxGenerator : IDisposable
{
private StringBuilder _sb;
private CodeWriter _writer;
private bool _disposed;

public SelectorSyntaxGenerator()
{
_sb = StringBuilderPool.Get();
_writer = new CodeWriter(_sb);
}

public void WriteHeader()
{
_writer.WriteFileHeader();
_writer.WriteLine();
_writer.WriteIndentedLine("namespace {0}", "Fluss");
_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();
}

public void WriteClassHeader()
{
_writer.WriteIndentedLine("public static class UnitOfWorkSelectors");
_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();
_writer.WriteIndentedLine("private static global::Microsoft.Extensions.Caching.Memory.MemoryCache _cache = new (new global::Microsoft.Extensions.Caching.Memory.MemoryCacheOptions { SizeLimit = 1024 });");
}

public void WriteEndNamespace()
{
_writer.WriteIndentedLine("""
private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList<global::Fluss.UnitOfWorkRecordingProxy.EventListenerTypeWithKeyAndVersion>? EventListeners);
private static async ValueTask<bool> MatchesEventListenerState(IUnitOfWork unitOfWork, CacheEntryValue value) {
foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty<global::Fluss.UnitOfWorkRecordingProxy.EventListenerTypeWithKeyAndVersion>()) {
if (!(await eventListenerData.IsStillUpToDate(unitOfWork))) {
return false;
}
}
return true;
}
""");

_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
_writer.WriteLine();
}

public void WriteMethodSignatureStart(string methodName, ITypeSymbol returnType, bool noParameters)
{
_writer.WriteLine();
_writer.WriteIndentedLine(
"public static async global::{0}<{1}> Select{2}(this global::Fluss.IUnitOfWork unitOfWork{3}",
typeof(ValueTask).FullName,
returnType.ToFullyQualified(),
methodName,
noParameters ? "" : ", ");
_writer.IncreaseIndent();
}

public void WriteMethodSignatureParameter(ITypeSymbol parameterType, string parameterName, bool isLast)
{
_writer.WriteIndentedLine(
"{0} {1}{2}",
parameterType.ToFullyQualified(),
parameterName,
isLast ? "" : ","
);
}

public void WriteMethodSignatureEnd()
{
_writer.DecreaseIndent();
_writer.WriteIndentedLine(")");
_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();
}

public void WriteRecordingUnitOfWork()
{
_writer.WriteIndentedLine("var recordingUnitOfWork = new global::Fluss.UnitOfWorkRecordingProxy(unitOfWork);");
}

public void WriteKeyStart(string containingType, string methodName, bool noParameters)
{
_writer.WriteIndentedLine("var key = (");
_writer.IncreaseIndent();
_writer.WriteIndentedLine("\"{0}.{1}\"{2}", containingType, methodName, noParameters ? "" : ",");
}

public void WriteKeyParameter(string parameterName, bool isLast)
{
_writer.WriteIndentedLine("{0}{1}", parameterName, isLast ? "" : ",");
}

public void WriteKeyEnd()
{
_writer.DecreaseIndent();
_writer.WriteIndentedLine(");");
_writer.WriteLine();
}

public void WriteMethodCacheHit(ITypeSymbol returnType)
{
_writer.WriteIndented("if (_cache.TryGetValue(key, out var result) && result is CacheEntryValue entryValue && await MatchesEventListenerState(unitOfWork, entryValue)) ");
using (_writer.WriteBraces())
{
_writer.WriteIndentedLine("return ({0})entryValue.Value;", returnType.ToFullyQualified());
}
_writer.WriteLine();
}

public void WriteMethodCall(string containingType, string methodName, bool isAsync)
{
_writer.WriteIndentedLine("result = {0}global::{1}.{2}(", isAsync ? "await " : "", containingType, methodName);
_writer.IncreaseIndent();
}

public void WriteMethodCallParameter(string parameterName, bool isLast)
{
_writer.WriteIndentedLine("{0}{1}", parameterName, isLast ? "" : ",");
}

public void WriteMethodCallEnd(bool isAsync)
{
_writer.DecreaseIndent();
_writer.WriteIndentedLine("){0};", isAsync ? ".ConfigureAwait(false)" : "");
_writer.WriteLine();
}

public void WriteMethodCacheMiss(ITypeSymbol returnType)
{
_writer.WriteIndented("using (var entry = _cache.CreateEntry(key)) ");

using (_writer.WriteBraces())
{
_writer.WriteIndentedLine("entry.Value = new CacheEntryValue(result, recordingUnitOfWork.GetRecordedListeners());");
_writer.WriteIndentedLine("entry.Size = 1;");
}

_writer.WriteLine();
_writer.WriteIndentedLine("return ({0})result;", returnType.ToFullyQualified());
}

public void WriteMethodEnd()
{
_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
}

public override string ToString()
=> _sb.ToString();

public SourceText ToSourceText()
=> SourceText.From(ToString(), Encoding.UTF8);

public void Dispose()
{
if (_disposed)
{
return;
}

StringBuilderPool.Return(_sb);
_sb = default!;
_writer = default!;
_disposed = true;
}
}
21 changes: 21 additions & 0 deletions src/Fluss.Regen/Generators/StringBuilderPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Text;
using System.Threading;

namespace Fluss.Regen.Generators;

public static class StringBuilderPool
{
private static StringBuilder? _stringBuilder;

public static StringBuilder Get()
{
var stringBuilder = Interlocked.Exchange(ref _stringBuilder, null);
return stringBuilder ?? new StringBuilder();
}

public static void Return(StringBuilder stringBuilder)
{
stringBuilder.Clear();
Interlocked.CompareExchange(ref _stringBuilder, stringBuilder, null);
}
}
Loading

0 comments on commit 69146a7

Please sign in to comment.