布爾結構給定一個解析後的型系 WhereExpression樹 : A AND B→ AndFilter<TRow, TA, TB>;A OR B→ OrFilter<TRow, TA, TB>;NOT A→ NotFilter<TRow, TA> 。於是统上我選擇把字符串包在一個小的值類型裏: internal readonly struct ValueString(string? value) : IEquatable<ValueString>, IComparable<ValueString>{ public readonly string? Value = value; public int CompareTo(ValueString other) => string.Compare(Value, other.Value, StringComparison.Ordinal); public bool Equals(ValueString other) { return string.Equals(Value, other.Value, StringComparison.Ordinal); } public override string? ToString() => Value; public static implicit operator ValueString(string value) => new(value); public static implicit operator string?(ValueString value) => value.Value;}
再配一個適配器,
最後組合出一個過濾器類型
: EqualsFilter<Person,实现 ValueStringColumn<PersonCityColumn, Person>, StringLiteral<...>, ValueString>
到這一步,再通過 NativeAOT 編譯成原生二進製文件,查询最終就會變成一棵泛型過濾器類型樹,引擎它會把內部的型系 ValueString[]包裝一下 , 實現一個 SQL 子集TypedSql 並不打算做成一個大而全的统上 SQL 引擎, 編譯器做的实现事情,.NET 的查询 JIT 能夠識別這種模式, 列和投影查詢總得運行在某種行類型 TRow上,引擎我們就可以把一個 Where節點掛到管道上了
: Where<TRow,型系 TPredicate, TNext, TRuntimeResult, TRoot> → ...
把 Where和 Select融合起來直接這麽拚出來的管道是正確的,NotEqualFilter等等,统上把結果拚成 ValueTuple: internal readonly struct ValueTupleProjection<TRow,实现 TColumn1, TValue1> : IProjection<TRow, ValueTuple<TValue1>> where TColumn1 : IColumn<TRow, TValue1>{ public static ValueTuple<TValue1> Project(in TRow row) => new(TColumn1.Get(row));}// … 一直到 7 列,它其實就是查询一套可以進行高度優化的、把字麵量變成 ILiteral<T>類型 。引擎Null)
。把這些東西變成 :- 一個封閉的管道類型
TPipeline ,整個係統其實完全不知道 C# 裏麵的類型是什麽樣的
,都會在 Stop前麵再加一個 Select節點:Select<TRow, TProjection, Stop<...>, TMiddle, TRuntimeResult, TRoot> → Stop<...>
這個節點內部會調用投影的靜態 Project方法, 整體流程 :編譯並執行查詢站在使用者的角度,而我的 TypedSql 會在內部自動在邊緣位置做封裝/解封裝,'a'、同時支持 JIT 和 AOT,這使得查詢過程可以最大化利用值類型的泛型特化優勢, 調用 CreateStringLiteral("Seattle"): 初始 type = typeof(StringEnd); 從右到左遍曆每個字符 : 'e'→ 得到一個 Char<…>類型(4 個十六進製數位對應 Unicode)type = StringNode<Char<'e'>, StringEnd>
'l'再往前
:type = StringNode<Char<'l'>, StringNode<Char<'e'>, StringEnd>>
- 一直重複:
't' 、CreateStringLiteral(null)會返回 typeof(StringLiteral<StringNull>); StringNull.Length == -1,否則的話
,因此作為查詢條件中的字麵量,CompiledQuery<TRow, TResult>本身隻是包了一個委托:
private readonly Func<ReadOnlySpan<TRow>, IReadOnlyList<TResult>> _entryPoint = executeMethod.CreateDelegate<Func<ReadOnlySpan<TRow>, IReadOnlyList<TResult>>>();
然後對外暴露: public IReadOnlyList<TResult> Execute(ReadOnlySpan<TRow> rows) => _entryPoint(rows);
得益於 .NET 10 對委托的逃逸分析 、 每一列會實現這樣一個接口
: internal interface IColumn<TRow, TValue>{ static abstract string Identifier { get; } static abstract TValue Get(in TRow row);}
舉個簡單的例子: internal readonly struct PersonNameColumn : IColumn<Person, string>{ public static string Identifier => "Name"; public static string Get(in Person row) => row.Name;}
而投影(SELECT後麵那部分)則實現: internal interface IProjection<TRow, TResult>{ static abstract TResult Project(in TRow row);}
將選出某一列本身做成一個投影,而你甚至不需要實現任何的代碼生成後端 ,你照樣寫 string,.NET 又能針對這些類型生成多快的代碼
? 於是 ,裏麵放運行時類型; - 同時記錄一份公共
ValueTuple<...>類型
,然後通過一個“Rest”再遞歸掛一個 IProjection
還是同樣的模式
:全是 struct ,就能讓 JIT 幫你完成大部分的工作
。運行時內部可以用一個對自己更舒服的元組類型, 過濾器過濾器的接口長這樣: internal interface IFilter<TRow>{ static abstract bool Evaluate(in TRow row);}
一個最常用的比較過濾器形式
,列又是什麽 ,JIT 直接把我們的字符串字麵量的長度常量嵌進了機器碼裏;進一步當長度匹配時 ,這使得運行時會產生類型字典查找的開銷 。其實可以是一串嵌套的泛型類型
,我想針對每一個 SQL 語句都生成一份獨特的類型,也不是某個遠程服務的結果,看起來很像 SQL 的內存查詢引擎;而在 JIT 眼裏
,減少了一次比較指令。再寫真正的 SQL(這聽起來就有點反直覺……) 但是我想嚐試一條完全不同的思路:如果我們把 C# 的類型係統本身 ,會生成一個 DynamicMethod來做拷貝 : internal static class ValueTupleConvertHelper<TPublicResult, TRuntimeResult>{ private delegate void CopyDelegate(ref TPublicResult dest, ref readonly TRuntimeResult source); private static readonly CopyDelegate _helper = default!; public static void Copy(ref TPublicResult dest, ref readonly TRuntimeResult source) { if (typeof(TPublicResult) == typeof(TRuntimeResult)) { dest = Unsafe.As<TRuntimeResult, TPublicResult>(ref Unsafe.AsRef(in source)); } else { _helper.Invoke(ref dest, in source); } } static ValueTupleConvertHelper() { // 構造 DynamicMethod 和 IL,會自然落到一套具體的設計上
。遠遠超過即使是在 .NET 10 中已經被高度優化後的 LINQ 的性能。一旦 Compile做完這些準備工作,從而在保持靈活性的同時
,在 TypeSql 中 ,使用和性能測試快速上手和很多輕量級查詢庫類似, // 遇到 Rest 字段時遞歸
。 最終編譯出來的類型
,並且借助 JIT 編譯器的強大優化能力
, 最後, 邏輯運算也是在類型層麵組合的: internal readonly struct AndFilter<TRow, TLeft, TRight> : IFilter<TRow> where TLeft : IFilter<TRow> where TRight : IFilter<TRow>{ public static bool Evaluate(in TRow row) => TLeft.Evaluate(in row) && TRight.Evaluate(in row);}internal readonly struct OrFilter<TRow, TLeft, TRight> : IFilter<TRow> where TLeft : IFilter<TRow> where TRight : IFilter<TRow>{ public static bool Evaluate(in TRow row) => TLeft.Evaluate(in row) || TRight.Evaluate(in row);}internal readonly struct NotFilter<TRow, TPredicate> : IFilter<TRow> where TPredicate : IFilter<TRow>{ public static bool Evaluate(in TRow row) => !TPredicate.Evaluate(in row);}
所以,null和 ""在類型層麵和運行時都可以被區分開
。也可以把它輸出到代碼裏然後通過 NativeAOT 編譯成原生二進製文件
,我隻是想過濾一下、最終都會變成一個封閉的泛型管道類型。 展望未來的應用 , 任務內容: - 過濾出
City == "Seattle"的行; - 返回它們的
Id 。可以這麽寫: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);}
多列選擇時
, 而過濾器在需要值的時候 ,諸如查詢引擎
、再往下推幾步
,當成查詢計劃會怎樣
? 也就是說,
它在類型初始化時 ,G_M000_IG05裏的 add r14, 72,沒有任何的虛擬調用
,TypedSql 會構造專門的投影,例如: 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 裏; 然後就可以編譯並運行查詢 ,少一點引用類型的幹擾; 避開了泛型共享帶來的類型字典查找開銷 。隻是簡單地訪問 TLiteral.Value
|