Skip to content

Commit 69146a7

Browse files
authored
Introduce selectors (#4)
* Fix namespaces * Improve test coverage * Remove UnitOfWork namespace nesting * Add first version of selectors * Add caching to selectors
1 parent 46e8191 commit 69146a7

File tree

63 files changed

+1943
-113
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1943
-113
lines changed

src/.config/dotnet-tools.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"verify.tool": {
6+
"version": "0.6.0",
7+
"commands": [
8+
"dotnet-verify"
9+
],
10+
"rollForward": false
11+
}
12+
}
13+
}

src/.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ bld/
1111
[Oo]bj/
1212
msbuild.log
1313
msbuild.err
14-
msbuild.wrn
14+
msbuild.wrn
15+
16+
.idea
17+
18+
*.received.*

src/Fluss.HotChocolate/AddExtensionMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private async IAsyncEnumerable<IQueryResult> LiveResults(IReadOnlyDictionary<str
102102
break;
103103
}
104104

105-
if (contextData[nameof(UnitOfWork)] is not UnitOfWork.UnitOfWork unitOfWork)
105+
if (contextData[nameof(UnitOfWork)] is not UnitOfWork unitOfWork)
106106
{
107107
break;
108108
}

src/Fluss.HotChocolate/BuilderExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Fluss.UnitOfWork;
21
using HotChocolate.Execution.Configuration;
32
using HotChocolate.Internal;
43
using Microsoft.Extensions.DependencyInjection;

src/Fluss.HotChocolate/UnitOfWorkParameterExpressionBuilder.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Linq.Expressions;
22
using System.Reflection;
3-
using Fluss.UnitOfWork;
43
using HotChocolate.Internal;
54
using HotChocolate.Resolvers;
65

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

1817
private static readonly MethodInfo GetGlobalStateOrDefaultLongMethod =
1918
typeof(ResolverContextExtensions).GetMethods()
@@ -24,20 +23,20 @@ public class UnitOfWorkParameterExpressionBuilder : IParameterExpressionBuilder
2423
typeof(IPureResolverContext).GetMethods().First(
2524
method => method.Name == nameof(IPureResolverContext.Service) &&
2625
method.IsGenericMethod)
27-
.MakeGenericMethod(typeof(UnitOfWork.UnitOfWork));
26+
.MakeGenericMethod(typeof(UnitOfWork));
2827

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

3231
private static readonly MethodInfo WithPrefilledVersionMethod =
33-
typeof(UnitOfWork.UnitOfWork).GetMethods(BindingFlags.Instance | BindingFlags.Public)
34-
.First(m => m.Name == nameof(UnitOfWork.UnitOfWork.WithPrefilledVersion));
32+
typeof(UnitOfWork).GetMethods(BindingFlags.Instance | BindingFlags.Public)
33+
.First(m => m.Name == nameof(UnitOfWork.WithPrefilledVersion));
3534

3635
private static readonly PropertyInfo ContextData =
3736
typeof(IHasContextData).GetProperty(
3837
nameof(IHasContextData.ContextData))!;
3938

40-
public bool CanHandle(ParameterInfo parameter) => typeof(UnitOfWork.UnitOfWork) == parameter.ParameterType
39+
public bool CanHandle(ParameterInfo parameter) => typeof(UnitOfWork) == parameter.ParameterType
4140
|| typeof(IUnitOfWork) == parameter.ParameterType;
4241

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

6564
return Expression.Call(null, GetOrSetGlobalStateUnitOfWorkMethod, context, Expression.Constant(nameof(UnitOfWork)),
66-
Expression.Lambda<Func<string, UnitOfWork.UnitOfWork>>(
65+
Expression.Lambda<Func<string, UnitOfWork>>(
6766
getNewUnitOfWork,
6867
Expression.Parameter(typeof(string))));
6968
}

src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System.Reflection;
22
using FluentMigrator.Runner;
3-
using Fluss.Core;
4-
using Fluss.Events;
53
using Fluss.Upcasting;
64
using Microsoft.Extensions.DependencyInjection;
75
using Microsoft.Extensions.Hosting;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Fluss.Regen.Attributes;
2+
3+
public abstract class SelectorAttribute
4+
{
5+
public static string FullName => $"{Namespace}.{AttributeName}";
6+
7+
private const string Namespace = "Fluss.Regen";
8+
private const string AttributeName = "SelectorAttribute";
9+
10+
public const string AttributeSourceCode = $@"// <auto-generated/>
11+
12+
namespace {Namespace}
13+
{{
14+
[System.AttributeUsage(System.AttributeTargets.Method)]
15+
public class {AttributeName} : System.Attribute
16+
{{
17+
}}
18+
}}";
19+
}

src/Fluss.Regen/Fluss.Regen.csproj

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>latest</LangVersion>
8+
9+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
10+
<IsRoslynComponent>true</IsRoslynComponent>
11+
12+
<RootNamespace>Fluss.Regen</RootNamespace>
13+
<PackageId>Fluss.Regen</PackageId>
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
18+
<PrivateAssets>all</PrivateAssets>
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
</PackageReference>
21+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
22+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
23+
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
24+
</ItemGroup>
25+
26+
27+
</Project>
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using System;
2+
using System.Text;
3+
using System.Threading.Tasks;
4+
using Fluss.Regen.Helpers;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.Text;
7+
8+
namespace Fluss.Regen.Generators;
9+
10+
public sealed class SelectorSyntaxGenerator : IDisposable
11+
{
12+
private StringBuilder _sb;
13+
private CodeWriter _writer;
14+
private bool _disposed;
15+
16+
public SelectorSyntaxGenerator()
17+
{
18+
_sb = StringBuilderPool.Get();
19+
_writer = new CodeWriter(_sb);
20+
}
21+
22+
public void WriteHeader()
23+
{
24+
_writer.WriteFileHeader();
25+
_writer.WriteLine();
26+
_writer.WriteIndentedLine("namespace {0}", "Fluss");
27+
_writer.WriteIndentedLine("{");
28+
_writer.IncreaseIndent();
29+
}
30+
31+
public void WriteClassHeader()
32+
{
33+
_writer.WriteIndentedLine("public static class UnitOfWorkSelectors");
34+
_writer.WriteIndentedLine("{");
35+
_writer.IncreaseIndent();
36+
_writer.WriteIndentedLine("private static global::Microsoft.Extensions.Caching.Memory.MemoryCache _cache = new (new global::Microsoft.Extensions.Caching.Memory.MemoryCacheOptions { SizeLimit = 1024 });");
37+
}
38+
39+
public void WriteEndNamespace()
40+
{
41+
_writer.WriteIndentedLine("""
42+
private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList<global::Fluss.UnitOfWorkRecordingProxy.EventListenerTypeWithKeyAndVersion>? EventListeners);
43+
private static async ValueTask<bool> MatchesEventListenerState(IUnitOfWork unitOfWork, CacheEntryValue value) {
44+
foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty<global::Fluss.UnitOfWorkRecordingProxy.EventListenerTypeWithKeyAndVersion>()) {
45+
if (!(await eventListenerData.IsStillUpToDate(unitOfWork))) {
46+
return false;
47+
}
48+
}
49+
50+
return true;
51+
}
52+
""");
53+
54+
_writer.DecreaseIndent();
55+
_writer.WriteIndentedLine("}");
56+
_writer.DecreaseIndent();
57+
_writer.WriteIndentedLine("}");
58+
_writer.WriteLine();
59+
}
60+
61+
public void WriteMethodSignatureStart(string methodName, ITypeSymbol returnType, bool noParameters)
62+
{
63+
_writer.WriteLine();
64+
_writer.WriteIndentedLine(
65+
"public static async global::{0}<{1}> Select{2}(this global::Fluss.IUnitOfWork unitOfWork{3}",
66+
typeof(ValueTask).FullName,
67+
returnType.ToFullyQualified(),
68+
methodName,
69+
noParameters ? "" : ", ");
70+
_writer.IncreaseIndent();
71+
}
72+
73+
public void WriteMethodSignatureParameter(ITypeSymbol parameterType, string parameterName, bool isLast)
74+
{
75+
_writer.WriteIndentedLine(
76+
"{0} {1}{2}",
77+
parameterType.ToFullyQualified(),
78+
parameterName,
79+
isLast ? "" : ","
80+
);
81+
}
82+
83+
public void WriteMethodSignatureEnd()
84+
{
85+
_writer.DecreaseIndent();
86+
_writer.WriteIndentedLine(")");
87+
_writer.WriteIndentedLine("{");
88+
_writer.IncreaseIndent();
89+
}
90+
91+
public void WriteRecordingUnitOfWork()
92+
{
93+
_writer.WriteIndentedLine("var recordingUnitOfWork = new global::Fluss.UnitOfWorkRecordingProxy(unitOfWork);");
94+
}
95+
96+
public void WriteKeyStart(string containingType, string methodName, bool noParameters)
97+
{
98+
_writer.WriteIndentedLine("var key = (");
99+
_writer.IncreaseIndent();
100+
_writer.WriteIndentedLine("\"{0}.{1}\"{2}", containingType, methodName, noParameters ? "" : ",");
101+
}
102+
103+
public void WriteKeyParameter(string parameterName, bool isLast)
104+
{
105+
_writer.WriteIndentedLine("{0}{1}", parameterName, isLast ? "" : ",");
106+
}
107+
108+
public void WriteKeyEnd()
109+
{
110+
_writer.DecreaseIndent();
111+
_writer.WriteIndentedLine(");");
112+
_writer.WriteLine();
113+
}
114+
115+
public void WriteMethodCacheHit(ITypeSymbol returnType)
116+
{
117+
_writer.WriteIndented("if (_cache.TryGetValue(key, out var result) && result is CacheEntryValue entryValue && await MatchesEventListenerState(unitOfWork, entryValue)) ");
118+
using (_writer.WriteBraces())
119+
{
120+
_writer.WriteIndentedLine("return ({0})entryValue.Value;", returnType.ToFullyQualified());
121+
}
122+
_writer.WriteLine();
123+
}
124+
125+
public void WriteMethodCall(string containingType, string methodName, bool isAsync)
126+
{
127+
_writer.WriteIndentedLine("result = {0}global::{1}.{2}(", isAsync ? "await " : "", containingType, methodName);
128+
_writer.IncreaseIndent();
129+
}
130+
131+
public void WriteMethodCallParameter(string parameterName, bool isLast)
132+
{
133+
_writer.WriteIndentedLine("{0}{1}", parameterName, isLast ? "" : ",");
134+
}
135+
136+
public void WriteMethodCallEnd(bool isAsync)
137+
{
138+
_writer.DecreaseIndent();
139+
_writer.WriteIndentedLine("){0};", isAsync ? ".ConfigureAwait(false)" : "");
140+
_writer.WriteLine();
141+
}
142+
143+
public void WriteMethodCacheMiss(ITypeSymbol returnType)
144+
{
145+
_writer.WriteIndented("using (var entry = _cache.CreateEntry(key)) ");
146+
147+
using (_writer.WriteBraces())
148+
{
149+
_writer.WriteIndentedLine("entry.Value = new CacheEntryValue(result, recordingUnitOfWork.GetRecordedListeners());");
150+
_writer.WriteIndentedLine("entry.Size = 1;");
151+
}
152+
153+
_writer.WriteLine();
154+
_writer.WriteIndentedLine("return ({0})result;", returnType.ToFullyQualified());
155+
}
156+
157+
public void WriteMethodEnd()
158+
{
159+
_writer.DecreaseIndent();
160+
_writer.WriteIndentedLine("}");
161+
}
162+
163+
public override string ToString()
164+
=> _sb.ToString();
165+
166+
public SourceText ToSourceText()
167+
=> SourceText.From(ToString(), Encoding.UTF8);
168+
169+
public void Dispose()
170+
{
171+
if (_disposed)
172+
{
173+
return;
174+
}
175+
176+
StringBuilderPool.Return(_sb);
177+
_sb = default!;
178+
_writer = default!;
179+
_disposed = true;
180+
}
181+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Text;
2+
using System.Threading;
3+
4+
namespace Fluss.Regen.Generators;
5+
6+
public static class StringBuilderPool
7+
{
8+
private static StringBuilder? _stringBuilder;
9+
10+
public static StringBuilder Get()
11+
{
12+
var stringBuilder = Interlocked.Exchange(ref _stringBuilder, null);
13+
return stringBuilder ?? new StringBuilder();
14+
}
15+
16+
public static void Return(StringBuilder stringBuilder)
17+
{
18+
stringBuilder.Clear();
19+
Interlocked.CompareExchange(ref _stringBuilder, stringBuilder, null);
20+
}
21+
}

0 commit comments

Comments
 (0)