E5E1 Make grammar immutable by sebastienros · Pull Request #469 · EntityGraphQL/EntityGraphQL · GitHub
[go: up one dir, main page]

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
13 changes: 10 additions & 3 deletions src/Benchmarks/Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@
<LangVersion>13.0</LangVersion>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
<PackageReference Include="BenchmarkDotNet" Version="0.15.2" />

<!-- Fixes package downgrade warnings (NU1608) -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.14.0" />

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
<ProjectReference Include="../EntityGraphQL/EntityGraphQL.csproj" />

<None Update="./DataLoader/moviedata.json" CopyToOutputDirectory="PreserveNewest" />
Expand Down
29 changes: 16 additions & 13 deletions src/Benchmarks/EqlBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,34 +34,37 @@ namespace Benchmarks;
/// | ComplexExpression | Default | 3 | 1 | 3 | 32.84 us | 23.009 us | 1.261 us |
/// | ComplexWithMethodCallExpression | Default | 3 | 1 | 3 | 53.69 us | 9.651 us | 0.529 us |
/// </summary>
[ShortRunJob]
[ShortRunJob, MemoryDiagnoser]
public class EqlBenchmarks : BaseBenchmark
{
private Movie _data = new(Guid.NewGuid(), "foo", 3, new DateTime(2021, 1, 1), new Person(Guid.NewGuid(), "Jimmy", "Rum", new DateTime(1978, 2, 4), []), [], new MovieGenre("Action"));
private Expression _context = Expression.Parameter(typeof(Movie), "m");

[Benchmark]
public void SimpleExpression()
public object SimpleExpression()
{
var expressionStr = "name == \"foo\"";
var data = new Movie(Guid.NewGuid(), "foo", 3, new DateTime(2021, 1, 1), new Person(Guid.NewGuid(), "Jimmy", "Rum", new DateTime(1978, 2, 4), []), [], new MovieGenre("Action"));
var context = Expression.Parameter(data.GetType(), "m");
var expression = EntityQueryCompiler.CompileWith(expressionStr, context, Schema, new QueryRequestContext(null, null), new ExecutionOptions());
var expression = EntityQueryCompiler.CompileWith(expressionStr, _context, Schema, new QueryRequestContext(null, null), new ExecutionOptions());

return expression;
}

[Benchmark]
public void ComplexExpression()
public object ComplexExpression()
{
var expressionStr = "name == \"foo\" && director.name == \"Jimmy\" && director.dob > \"1978-02-04\" && genre.name == \"Action\" && rating > 3";
var data = new Movie(Guid.NewGuid(), "foo", 3, new DateTime(2021, 1, 1), new Person(Guid.NewGuid(), "Jimmy", "Rum", new DateTime(1978, 2, 4), []), [], new MovieGenre("Action"));
var context = Expression.Parameter(data.GetType(), "m");
var expression = EntityQueryCompiler.CompileWith(expressionStr, context, Schema, new QueryRequestContext(null, null), new ExecutionOptions());
var expression = EntityQueryCompiler.CompileWith(expressionStr, _context, Schema, new QueryRequestContext(null, null), new ExecutionOptions());

return expression;
}

[Benchmark]
public void ComplexWithMethodCallExpression()
public object ComplexWithMethodCallExpression()
{
var expressionStr =
"name.contains(\"fo\") && director.name.toLower().startsWith(\"ji\") && director.dob > \"1978-01-01\" && actors.orderBy(name).first().name.startsWith(\"bob\") && rating > 3";
var data = new Movie(Guid.NewGuid(), "foo", 3, new DateTime(2021, 1, 1), new Person(Guid.NewGuid(), "Jimmy", "Rum", new DateTime(1978, 2, 4), []), [], new MovieGenre("Action"));
var context = Expression.Parameter(data.GetType(), "m");
var expression = EntityQueryCompiler.CompileWith(expressionStr, context, Schema, new QueryRequestContext(null, null), new ExecutionOptions());
var expression = EntityQueryCompiler.CompileWith(expressionStr, _context, Schema, new QueryRequestContext(null, null), new ExecutionOptions());

return expression;
}
}
2 changes: 1 addition & 1 deletion src/Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Program
{
static void Main(string[] args)
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new DebugInProcessConfig());
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

// var bench = new CompileStagesBenchmarks();
// for (int i = 0; i < 1; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ ExecutionOptions executionOptions
)
{
var compileContext = new CompileContext(executionOptions, null, requestContext);
var expressionParser = new EntityQueryParser(context, schemaProvider, requestContext, methodProvider, compileContext);
var expression = expressionParser.Parse(query);
var expression = EntityQueryParser.Instance.Parse(query, context, schemaProvider, requestContext, methodProvider, compileContext);
return expression;
}
}
50 changes: 34 additions & 16 deletions src/EntityGraphQL/Compiler/EntityQuery/Grammar/EntityQueryParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace EntityGraphQL.Compiler.EntityQuery.Grammar;

public sealed class EntityQueryParser
{
public static readonly EntityQueryParser Instance;
private const string MultiplyChar = "*";
private const string DivideChar = "/";
private const string ModChar = "%";
Expand Down Expand Up @@ -73,12 +74,8 @@ public sealed class EntityQueryParser

private static readonly Parser<IExpression> strExp = SkipWhiteSpace(new StringLiteral(StringLiteralQuotes.SingleOrDouble))
.Then<IExpression>(static s => new EqlExpression(Expression.Constant(s.ToString())));
private readonly Expression? context;
private readonly ISchemaProvider? schema;
private readonly QueryRequestContext requestContext;
private readonly IMethodProvider methodProvider;

public EntityQueryParser(Expression? context, ISchemaProvider? schema, QueryRequestContext requestContext, IMethodProvider methodProvider, CompileContext compileContext)
private EntityQueryParser()
{
// The Deferred helper creates a parser that can be referenced by others before it is defined
var expression = Deferred<IExpression>();
Expand All @@ -89,16 +86,16 @@ public EntityQueryParser(Expression? context, ISchemaProvider? schema, QueryRequ
var callArgs = openParen.And(Separated(comma, expression)).And(closeParen).Then(static x => x.Item2);
var emptyCallArgs = openParen.And(closeParen).Then(static x => new List<IExpression>() as IReadOnlyList<IExpression>);

var identifier = SkipWhiteSpace(new Identifier()).And(Not(emptyCallArgs)).Then<IExpression>(x => new IdentityExpression(x.Item1.ToString()!, compileContext));
var identifier = SkipWhiteSpace(new Identifier()).And(Not(emptyCallArgs)).Then<IExpression>(static (c, x) => new IdentityExpression(x.Item1.ToString()!, ((EntityQueryParseContext)c).CompileContext));

var constArray = openArray
.And(Separated(comma, expression))
.And(closeArray)
.Then<IExpression>(x => new EqlExpression(Expression.NewArrayInit(x.Item2[0].Type, x.Item2.Select(e => e.Compile(context, schema, requestContext, methodProvider)))));
.Then<IExpression>(static (c, x) => new EqlExpression(Expression.NewArrayInit(x.Item2[0].Type, x.Item2.Select(e => e.Compile(((EntityQueryParseContext)c).Context, ((EntityQueryParseContext)c).Schema, ((EntityQueryParseContext)c).RequestContext, ((EntityQueryParseContext)c).MethodProvider)))));

var call = SkipWhiteSpace(new Identifier()).And(callArgs.Or(emptyCallArgs)).Then<IExpression>(static x => new CallExpression(x.Item1!.ToString()!, x.Item2));

var callPath = Separated(dot, OneOf(call, constArray, identifier)).Then<IExpression>(p => new CallPath(p, compileContext));
var callPath = Separated(dot, OneOf(call, constArray, identifier)).Then<IExpression>(static (c, p) => new CallPath(p, ((EntityQueryParseContext)c).CompileContext));

var nullExp = Terms.Text("null").AndSkip(Not(identifier)).Then<IExpression>(static _ => new EqlExpression(Expression.Constant(null)));
var trueExp = Terms.Text("true").AndSkip(Not(identifier)).Then<IExpression>(static _ => new EqlExpression(Expression.Constant(true)));
Expand All @@ -110,7 +107,7 @@ public EntityQueryParser(Expression? context, ISchemaProvider? schema, QueryRequ
// The Recursive helper allows to create parsers that depend on themselves.
// ( "-" ) unary | primary;
var unary = Recursive<IExpression>(
(u) => minus.And(u).Then<IExpression>(x => new EqlExpression(Expression.Negate(x.Item2.Compile(context, schema, requestContext, methodProvider)))).Or(primary)
(u) => minus.And(u).Then<IExpression>(static (c, x) => new EqlExpression(Expression.Negate(x.Item2.Compile(((EntityQueryParseContext)c).Context, ((EntityQueryParseContext)c).Schema, ((EntityQueryParseContext)c).RequestContext, ((EntityQueryParseContext)c).MethodProvider)))).Or(primary)
);

// factor => unary ( ( "*" | "/" | ... ) unary )* ;
Expand Down Expand Up @@ -155,10 +152,11 @@ public EntityQueryParser(Expression? context, ISchemaProvider? schema, QueryRequ
expression.Parser = conditional.Or(logicalBinary);

grammar = expression;
this.context = context;
this.schema = schema;
this.requestContext = requestContext;
this.methodProvider = methodProvider;
}

static EntityQueryParser()
{
Instance = new EntityQueryParser();
}

private static IExpression HandleBinary((IExpression, IReadOnlyList<(string, IExpression)>) x)
Expand Down Expand Up @@ -198,9 +196,29 @@ private static IExpression HandleBinary((IExpression, IReadOnlyList<(string, IEx
return binaryExp;
}

public Expression Parse(string query)
private sealed class EntityQueryParseContext : ParseContext
{
public EntityQueryParseContext(string query, Expression? context, ISchemaProvider? schema, QueryRequestContext requestContext, IMethodProvider methodProvider, CompileContext compileContext) : base(new Parlot.Scanner(query))
{
Context = context;
Schema = schema;
RequestContext = requestContext;
MethodProvider = methodProvider;
CompileContext = compileContext;
}

public Expression? Context { get; }
public ISchemaProvider? Schema { get; }
public QueryRequestContext RequestContext { get; }
public IMethodProvider MethodProvider { get; }
public CompileContext CompileContext { get; }
}

public Expression Parse(string query, Expression? context, ISchemaProvider? schema, QueryRequestContext requestContext, IMethodProvider methodProvider, CompileContext compileContext)
{
var result = grammar.Parse(query) ?? throw new EntityGraphQLCompilerException("Failed to parse query");
return result.Compile(context, schema, requestContext, methodProvider);
var parseContext = new EntityQueryParseContext(query, context, schema, requestContext, methodProvider, compileContext);

var result = grammar.Parse(parseContext) ?? throw new EntityGraphQLCompilerException("Failed to parse query");
return result.Compile(parseContext.Context, parseContext.Schema, parseContext.RequestContext, parseContext.MethodProvider);
}
}
2 changes: 1 addition & 1 deletion src/EntityGraphQL/EntityGraphQL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Parlot" Version="1.3.5" />
<PackageReference Include="Parlot" Version="1.4.2" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="System.Runtime.Caching" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,14 +331,15 @@ public void CompilesEnumSimple()
{
var schema = SchemaBuilder.FromObject<TestSchema>();
var param = Expression.Parameter(typeof(Person));
var expressionParser = new EntityQueryParser(

var exp = EntityQueryParser.Instance.Parse("gender == Female",
param,
schema,
new QueryRequestContext(null, null),
new DefaultMethodProvider(),
new CompileContext(executionOptions, null, new QueryRequestContext(null, null))
);
var exp = expressionParser.Parse("gender == Female");
);

var res = (bool?)Expression.Lambda(exp, param).Compile().DynamicInvoke(new Person { Gender = Gender.Female });
Assert.NotNull(res);
Assert.True(res);
Expand Down
0