Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: docs: Add documentation to the compiled query subsystem #2928

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
88 changes: 84 additions & 4 deletions src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,51 @@

public class CompiledQueryPlan : ICommandBuilder
{
/// <summary>
/// Placeholder in the compiled query for parameters
/// </summary>
public const string ParameterPlaceholder = "^";

/// <summary>
/// The type for which this <see cref="CompiledQueryPlan"/> is constructed
/// </summary>
public Type QueryType { get; }

/// <summary>
/// The output type that should be produced by this <see cref="CompiledQueryPlan"/>
/// </summary>
public Type OutputType { get; }
public const string ParameterPlaceholder = "^";

/// <summary>
/// Member of the <see cref="QueryType"/> that holds <see cref="QueryStatistics"/> if any
/// </summary>
public MemberInfo? StatisticsMember { get; set; }

Check warning on line 39 in src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 39 in src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 39 in src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 39 in src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

/// <summary>
/// Members of the <see cref="QueryType"/> that can be used to include additional data
/// </summary>
public List<MemberInfo> IncludeMembers { get; } = new();

/// <summary>
/// Members of the <see cref="QueryType"/> that are invalid as parameters
/// </summary>
public List<MemberInfo> InvalidMembers { get; } = new();

/// <summary>
/// Members of the <see cref="QueryType"/> that are usable as parameters
/// </summary>
public List<IQueryMember> QueryMembers { get; } = new();
public List<MemberInfo> IncludeMembers { get; } = new();
internal List<IIncludePlan> IncludePlans { get; } = new();

private readonly List<CommandPlan> _commands = new();
public IQueryHandler HandlerPrototype { get; set; }
public MemberInfo? StatisticsMember { get; set; }


/// <summary>
/// Create a new <see cref="CompiledQueryPlan"/> for the given <paramref name="queryType"/> which produces <paramref name="outputType"/> as output.
/// </summary>
/// <param name="queryType">The type of the query</param>
/// <param name="outputType">The produced type</param>
public CompiledQueryPlan(Type queryType, Type outputType)
{
QueryType = queryType;
Expand All @@ -41,6 +73,24 @@

#region finding members on query type

/// <summary>
/// This function's purpose is to sort all the members on the <see cref="QueryType"/> into categories
/// </summary>
/// <remarks>
/// The possible categories are:
/// <list type="number">
/// <item> Statistic member, only one </item>
/// <item> Include members, which can be filled by fetching additional data during the same operation </item>
/// <item> Invalid members, which are at this point in time all nullable fields as well as all types for which no
/// <see cref="QueryCompiler.Finders"/> instance exists </item>
/// <item> Query members, which are the ones that can actually be used by the compiled query as parameters </item>
/// </list>
/// </remarks>
/// <seealso cref="StatisticsMember"/>
/// <seealso cref="IncludeMembers"/>
/// <seealso cref="InvalidMembers"/>
/// <seealso cref="QueryMembers"/>
/// TODO: Possibly throw on duplicate QueryStatistics?
private void sortMembers()
{
foreach (var member in findMembers())
Expand Down Expand Up @@ -84,6 +134,10 @@
}
}

/// <summary>
/// Iterates over all <see langword="public"/> fields and properties on <see cref="QueryType"/>
/// </summary>
/// <returns>An enumerable that iterates over all fields and properties</returns>
private IEnumerable<MemberInfo> findMembers()
{
foreach (var field in QueryType.GetFields(BindingFlags.Instance | BindingFlags.Public)
Expand Down Expand Up @@ -212,7 +266,12 @@

#endregion

public QueryStatistics GetStatisticsIfAny(object query)
/// <summary>
/// Returns the <see cref="StatisticsMember"/>'s content of <paramref name="query"/> if any
/// </summary>
/// <param name="query">The query instance</param>
/// <returns>A <see cref="QueryStatistics"/> instance if the <see cref="QueryType"/> has a <see cref="StatisticsMember"/>, null otherwise </returns>
public QueryStatistics? GetStatisticsIfAny(object query)

Check warning on line 274 in src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 274 in src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 274 in src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 274 in src/Marten/Internal/CompiledQueries/CompiledQueryPlan.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
if (StatisticsMember is PropertyInfo p)
{
Expand All @@ -227,6 +286,15 @@
return null;
}

/// <summary>
/// Tries to create a template from the <paramref name="query"/>, making sure that all <see cref="QueryMembers"/>
/// have unique values
/// </summary>
/// <param name="query">The query to create a template from</param>
/// <typeparam name="TDoc">Document input type to the <see cref="ICompiledQuery{TDoc,TOut}"/></typeparam>
/// <typeparam name="TOut">Output type of the <see cref="ICompiledQuery{TDoc,TOut}"/></typeparam>
/// <returns>An instance of the same type as <paramref name="query"/> with unique values, can be identical to <paramref name="query"/></returns>
/// <exception cref="InvalidCompiledQueryException">Thrown when a template with unique values could not be created</exception>
public ICompiledQuery<TDoc, TOut> CreateQueryTemplate<TDoc, TOut>(ICompiledQuery<TDoc, TOut> query)
{
foreach (var parameter in QueryMembers) parameter.StoreValue(query);
Expand All @@ -246,6 +314,13 @@
}
}

/// <summary>
/// Tries to create a unique template instance of <paramref name="type"/>
/// </summary>
/// <param name="type">The type for which to create a unique template instance</param>
/// <returns>A template instance with unique values for <see cref="QueryMembers"/></returns>
/// <exception cref="InvalidOperationException">Thrown if an instance of the type cannot be constructed</exception>
/// <exception cref="InvalidCompiledQueryException">Thrown if unique values cannot be assigned to the created instance</exception>
public object TryCreateUniqueTemplate(Type type)
{
var constructor = type.GetConstructors().MaxBy(x => x.GetParameters().Count());
Expand Down Expand Up @@ -283,6 +358,11 @@
type.FullNameInCode());
}

/// <summary>
/// Checks whether the <paramref name="query"/> has only unique values by applying all <see cref="QueryCompiler.Finders"/>
/// </summary>
/// <param name="query">The query object to check</param>
/// <returns>True if all values are unique, false otherwise</returns>
private bool areAllMemberValuesUnique(object query)
{
return QueryCompiler.Finders.All(x => x.AreValuesUnique(query, this));
Expand Down
8 changes: 8 additions & 0 deletions src/Marten/Internal/CompiledQueries/IParameterFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ namespace Marten.Internal.CompiledQueries;
internal interface IParameterFinder
{
bool Matches(Type memberType);

/// <summary>
/// Checks whether all values that can be found on <paramref name="query"/> by this <see cref="IParameterFinder"/>
/// have unique values when compared amongst each other
/// </summary>
/// <param name="query">The query object to check</param>
/// <param name="plan">The query plan built for <paramref name="query"/></param>
/// <returns>True if all values are unique, false otherwise</returns>
bool AreValuesUnique(object query, CompiledQueryPlan plan);
Queue<object> UniqueValueQueue(Type type);
}
12 changes: 12 additions & 0 deletions src/Marten/Internal/CompiledQueries/IQueryMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@ public interface IQueryMember
MemberInfo Member { get; }
bool CanWrite();

/// <summary>
/// Stores the value of the member represented by this <see cref="IQueryMember"/> as currently set on <paramref name="query"/>
/// </summary>
/// <param name="query">The object from which to read the value</param>
void StoreValue(object query);

bool TryMatch(NpgsqlParameter parameter, StoreOptions options, ICompiledQueryAwareFilter[] filters,
out ICompiledQueryAwareFilter filter);

/// <summary>
/// Tries to write a unique value retrieved from <see cref="valueSource"/> into the member of <paramref name="query"/>
/// </summary>
/// <remarks>
/// This should also update any stored value previously stored by <see cref="StoreValue"/>
/// </remarks>
/// <param name="valueSource">A source for unique values</param>
/// <param name="query">The object to write to</param>
void TryWriteValue(UniqueValueSource valueSource, object query);
object GetValueAsObject(object query);
}
53 changes: 49 additions & 4 deletions src/Marten/Internal/CompiledQueries/QueryCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@

internal class QueryCompiler
{
/// <summary>
/// A list of <see cref="IParameterFinder"/>
/// </summary>
internal static readonly IList<IParameterFinder> Finders = new List<IParameterFinder> { new EnumParameterFinder() };

/// <summary>
/// Query compiler static constructor that sets up unique value sources for
/// </summary>
static QueryCompiler()
{
forType(count =>
Expand Down Expand Up @@ -113,9 +119,15 @@
});
}

private static void forType<T>(Func<int, T[]> uniqueValues)
/// <summary>
/// Creates a new <see cref="SimpleParameterFinder{T}"/> using <paramref name="uniqueValueGenerator"/>
/// as a unique value source and adds it to <see cref="Finders"/>
/// </summary>
/// <param name="uniqueValueGenerator">Function that generates unique values</param>
/// <typeparam name="T">The type for the new <see cref="SimpleParameterFinder{T}"/> </typeparam>
private static void forType<T>(Func<int, T[]> uniqueValueGenerator)
{
var finder = new SimpleParameterFinder<T>(uniqueValues);
var finder = new SimpleParameterFinder<T>(uniqueValueGenerator);
Finders.Add(finder);
}

Expand All @@ -135,6 +147,16 @@
return builder.BuildPlan(session, queryType, storeOptions);
}

/// <summary>
/// Try to build a <see cref="CompiledQueryPlan"/> for the <paramref name="query"/>
/// </summary>
/// <param name="session">The <see cref="QuerySession"/> to build the command for</param>
/// <param name="query">The actual <see cref="ICompiledQuery{TDoc,TOut}"/> instance</param>
/// <typeparam name="TDoc">The input type of <see cref="ICompiledQuery{TDoc,TOut}"/></typeparam>
/// <typeparam name="TOut">The output type of <see cref="ICompiledQuery{TDoc,TOut}"/></typeparam>
/// <returns>A compiled query plan</returns>
/// <exception cref="InvalidCompiledQueryException">Thrown if the <paramref name="query"/> passed is invalid somehow</exception>
/// TODO: Document exceptions
public static CompiledQueryPlan BuildQueryPlan<TDoc, TOut>(QuerySession session, ICompiledQuery<TDoc, TOut> query)
{
eliminateStringNulls(query);
Expand All @@ -161,9 +183,20 @@
return plan;
}

/// <summary>
/// Uses the <see cref="ICompiledQuery{TDoc,TOut}"/> template <paramref name="queryTemplate"/> to build a template
/// database command. Also matches parameters against a provided <see cref="CompiledQueryPlan"/>.
/// </summary>
/// <param name="session">The session this command should be built for</param>
/// <param name="queryTemplate">The template to build the command from</param>
/// <param name="statistics">Where if any QueryStatistics should be saved</param>
/// <param name="queryPlan">The query plan to match parameters against</param>
/// <typeparam name="TDoc">The input type of the <see cref="ICompiledQuery{TDoc,TOut}"/></typeparam>
/// <typeparam name="TOut">The output type of the <see cref="ICompiledQuery{TDoc,TOut}"/></typeparam>
/// <returns>A <see cref="LinqQueryParser"/> built from the template containing the template command</returns>
internal static LinqQueryParser BuildDatabaseCommand<TDoc, TOut>(QuerySession session,
ICompiledQuery<TDoc, TOut> queryTemplate,
QueryStatistics statistics,
QueryStatistics? statistics,

Check warning on line 199 in src/Marten/Internal/CompiledQueries/QueryCompiler.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 199 in src/Marten/Internal/CompiledQueries/QueryCompiler.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
CompiledQueryPlan queryPlan)
{
Expression expression = queryTemplate.QueryIs();
Expand All @@ -187,6 +220,11 @@
return parser;
}

/// <summary>
/// Sets all writable public string properties and fields that are <see langword="null"/> to <see cref="string.Empty"/>
/// </summary>
/// <remarks>This also sets all public static string properties and fields</remarks>
/// <param name="query">The query instance</param>
private static void eliminateStringNulls(object query)
{
var type = query.GetType();
Expand All @@ -211,11 +249,18 @@
}


/// <summary>
/// Throws if <paramref name="plan"/> has any invalid members
/// </summary>
/// <param name="plan">The plan to check</param>
/// <param name="type">Unused</param>
/// <exception cref="InvalidCompiledQueryException">Thrown if any members on the <see cref="CompiledQueryPlan"/> are invalid </exception>
/// TODO: Should this actually throw if the members are unused during the actual query?
private static void assertValidityOfQueryType(CompiledQueryPlan plan, Type type)
{
if (plan.InvalidMembers.Any())
{
var members = plan.InvalidMembers.Select(x => $"{x.GetRawMemberType().NameInCode()} {x.Name}")
var members = plan.InvalidMembers.Select(x => $"{x.GetRawMemberType()?.NameInCode()} {x.Name}")
.Join(", ");
var message = $"Members {members} cannot be used as parameters to a compiled query";

Expand Down
5 changes: 5 additions & 0 deletions src/Marten/Internal/CompiledQueries/QueryMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ namespace Marten.Internal.CompiledQueries;
internal interface IQueryMember<T>: IQueryMember
{
T Value { get; }
/// <summary>
/// Returns the current value of the QueryMember represented by this instance from <paramref name="query"/>
/// </summary>
/// <param name="query">The object from which to read the value</param>
/// <returns>The value if any</returns>
T GetValue(object query);
void SetValue(object query, T value);
}
Expand Down
4 changes: 4 additions & 0 deletions src/Marten/Internal/CompiledQueries/SimpleParameterFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public bool Matches(Type memberType)
return memberType == DotNetType;
}

/// <summary>
/// Does a quick sweep over all members this <see cref="SimpleParameterFinder{T}"/> can handle
/// and checks for uniqueness
/// </summary>
public bool AreValuesUnique(object query, CompiledQueryPlan plan)
{
var members = findMembers(plan);
Expand Down
14 changes: 14 additions & 0 deletions src/Marten/Internal/CompiledQueries/UniqueValueSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@

namespace Marten.Internal.CompiledQueries;

/// <summary>
/// Creates sequences of unique values for different <see cref="Type"/>s
/// </summary>
public class UniqueValueSource
{
private readonly Dictionary<Type, Queue<object>> _values = new();

/// <summary>
/// Returns the next unique value for the <see cref="Type"/> <paramref name="type"/>
/// </summary>
/// <param name="type">The type for which to get a unique value</param>
/// <returns>The next unique value in the sequence</returns>
/// <exception cref="InvalidOperationException">If there is no source for the <see cref="Type"/> <paramref name="type"/></exception>
public object GetValue(Type type)
{
if (_values.TryGetValue(type, out var queue))
Expand All @@ -24,6 +33,11 @@ public object GetValue(Type type)
return queue.Dequeue();
}

/// <summary>
/// Returns an array of unique values that satisfy the constructor arguments of <paramref name="constructor"/>
/// </summary>
/// <param name="constructor">The constructor for which to create arguments</param>
/// <returns>An array of unique valued arguments</returns>
public object[] ArgsFor(ConstructorInfo constructor)
{
return constructor.GetParameters().Select(parameter => GetValue(parameter.ParameterType))
Expand Down
7 changes: 7 additions & 0 deletions src/Marten/Linq/ICompiledQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace Marten.Linq;
/// </summary>
public interface IQueryPlanning
{
/// <summary>
/// Called to give the implementor a chance to set unique values for their publicly visible members and fields
/// </summary>
void SetUniqueValuesForQueryPlanning();
}

Expand All @@ -25,6 +28,10 @@ public interface IQueryPlanning

public interface ICompiledQuery<TDoc, TOut>
{
/// <summary>
/// Called to retrieve the query itself
/// </summary>
/// <returns>An expression that defines the query</returns>
Expression<Func<IMartenQueryable<TDoc>, TOut>> QueryIs();
}

Expand Down
Loading