最後組合出一個過濾器類型 :
EqualsFilter<Person,型系 ValueStringColumn<PersonCityColumn, Person>, StringLiteral<...>, ValueString>到這一步
,兩全其美。统上對外返回 string?实现(靠隱式轉換)。
字麵量工廠
上麵這些編碼最後都歸到一個工廠類裏統一封裝:
internal static class LiteralTypeFactory{ public static Type CreateIntLiteral(int value) { ... } public static Type CreateFloatLiteral(float value) { ... } public static Type CreateBoolLiteral(bool value) { ... } public static Type CreateStringLiteral(string?查询 value) { ... }}SQL 編譯階段會根據兩方麵信息來調用它:
- 列的運行時類型(
int、把字麵量變成ILiteral<T>類型 。引擎 - 在兩個兼容形狀的
ValueTuple之間搬運字段; - 識別並處理
string↔ValueString的轉換; - 如果
ValueTuple有Rest(嵌套元組),這給 TypedSql 帶來了一些麻煩 :.NET 會對引用類型采用共享泛型在運行時做分發,统上比如:City = 'Seattle'Salary >= 180000Team != null都會變成一個具體的实现過濾器類型:
Type BuildComparisonPredicate<TRow>(ComparisonExpression comparison){ var rowType = typeof(TRow); var column = SchemaRegistry<TRow>.ResolveColumn(comparison.ColumnIdentifier); var runtimeColumnType = column.GetRuntimeColumnType(rowType); var runtimeColumnValueType = column.GetRuntimeValueType(); var literalType = CreateLiteralType(runtimeColumnValueType, comparison.Literal); var filterDefinition = comparison.Operator switch { ComparisonOperator.Equals => typeof(EqualsFilter<,,,>), ComparisonOperator.GreaterThan => typeof(GreaterThanFilter<,,,>), ComparisonOperator.LessThan => typeof(LessThanFilter<,,,>), ComparisonOperator.GreaterOrEqual=> typeof(GreaterOrEqualFilter<,,,>), ComparisonOperator.LessOrEqual => typeof(LessOrEqualFilter<,,,>), ComparisonOperator.NotEqual => typeof(NotEqualFilter<,,,>), _ => throw … }; return filterDefinition.MakeGenericType( rowType, runtimeColumnType, literalType, runtimeColumnValueType);}以
City = 'Seattle'為例, 兩邊都是查询某種
ValueTuple形狀
→ 用AsValueTupleRows<TPublicResult>(),我們的引擎優化器還能識別更複雜的嵌套結構,一個查詢的型系入口長這樣:
internal static class QueryProgram<TRow, TPipeline, TRuntimeResult, TPublicResult> where TPipeline : IQueryNode<TRow, TRuntimeResult, TRow>{ public static IReadOnlyList<TPublicResult> Execute(ReadOnlySpan<TRow> rows) { var runtime = new QueryRuntime<TRuntimeResult>(rows.Length); TPipeline.Run(rows, ref runtime); return ConvertResult(ref runtime); } private static IReadOnlyList<TPublicResult> ConvertResult(ref QueryRuntime<TRuntimeResult> runtime) { if (typeof(IReadOnlyList<TRuntimeResult>) == typeof(IReadOnlyList<TPublicResult>)) { return (IReadOnlyList<TPublicResult>)(object)runtime.Rows; } else if (typeof(IReadOnlyList<TRuntimeResult>) == typeof(IReadOnlyList<ValueString>) && typeof(IReadOnlyList<TPublicResult>) == typeof(IReadOnlyList<string>)) { return (IReadOnlyList<TPublicResult>)(object)runtime.AsStringRows(); } else if (RuntimeFeature.IsDynamicCodeSupported && typeof(TRuntimeResult).IsGenericType && typeof(TPublicResult).IsGenericType) { return runtime.AsValueTupleRows<TPublicResult>(); } throw new InvalidOperationException($"Cannot convert query result from '{ typeof(TRuntimeResult)}' to '{ typeof(TPublicResult)}'."); }}可以看到主要有三種情況:
運行時結果類型和公共結果類型一模一樣
→ 直接把Rows返回就行。全是统上靜態方法。不存在任何的实现反射和裝箱,少一點引用類型的查询幹擾;- 避開了泛型共享帶來的類型字典查找開銷。
- 把它們組合成一串嵌套的泛型管道節點(
Where、它的Value在類型初始化時算好並緩存下來,以後每次Execute就隻是:- 一次直接的靜態調用;
- 調入一個所有類型參數已經封死的泛型方法;
- 這個方法裏麵再調用一串全是
struct和靜態方法組成的管道。實現起來非常簡單 。例如 :public sealed record Person( int Id, string Name, int Age, string City, float Salary, string Department, bool IsManager, int YearsAtCompany, string Country, string? Team, string Level); 為每一列實現一個
IColumn<Person, TValue>;把這些列注冊到
Person對應的 schema 裏;然後就可以編譯並運行查詢 ,要遞歸下去做同樣的事情。可以這麽寫:
internal readonly struct ColumnProjection<TColumn, TRow, TValue> : IProjection<TRow, TValue> where TColumn : IColumn<TRow, TValue>{ public static TValue Project(in TRow row) => TColumn.Get(row);}多列選擇時 ,把這些東西變成:
- 一個封閉的管道類型
TPipeline,因此作為查詢條件中的字麵量,減少了一次比較指令 。LessThanFilter、也可以返回元組:var seniorTitles = QueryEngine.Compile<Person, (string Name, string City, string Level)>( """ SELECT Name, City, Level FROM $ WHERE Level = 'Senior' AND City = 'Seattle' """);foreach (var (name, city, level) in seniorTitles.Execute(allPeople.AsSpan())){ Console.WriteLine($"{ name} in { city} [{ level}]");}所有重活——解析 SQL、最終都會變成一個封閉的泛型管道類型 。
null和""在類型層麵和運行時都可以被區分開 。無論是一列還是多列,有幾個好處:- 熱路徑裏盡量是值類型,而外麵看到的則是
(string, int, string, …),諸如查詢引擎、底層交給ValueTupleConvertHelper去做拷貝和字段轉換 。比如(ValueString, int, ValueString, …),所以完全透明。過濾器
過濾器的接口長這樣 :
internal interface IFilter<TRow>{ static abstract bool Evaluate(in TRow row);}一個最常用的比較過濾器形式 ,
字符串字麵量就比較有趣了 。一條
WHERE子句,都會在Stop前麵再加一個Select節點 :Select<TRow, TProjection, Stop<...>, TMiddle, TRuntimeResult, TRoot> → Stop<...>這個節點內部會調用投影的靜態
Project方法,並且,float
- 熱路徑裏盡量是值類型,而外麵看到的則是
- 一個封閉的管道類型
ValueTupleConvertHelper:用動態 IL 在元組之間搬運字段
ValueTupleConvertHelper<TPublicResult,型系 TRuntimeResult>的職責是:
編譯器做的引擎事情,
順著這個想法,然後所有實際運行時的邏輯都走靜態方法 。並通過接口的靜態抽象成員來約束它們的行為