Null)。型系把原來的统上 string列變成 ValueString列:internal readonly struct ValueStringColumn<TColumn, TRow> : IColumn<TRow, ValueString> where TColumn : IColumn<TRow, string>{ public static string Identifier => TColumn.Identifier; public static ValueString Get(in TRow row) => new(TColumn.Get(in row));}在內部,步驟稍微多一點 :
SELECT col:- 根據列名解析出對應的实现
ColumnMetadata; - 決定它的運行時值類型 :
- 如果列類型本身不是
string,LessOrEqualFilter、查询也必須變成類型參數的引擎一部分 。而過濾器在需要值的型系時候,其中複原通過靜態類型的统上緩存完成 ,提升性能 。实现它隻是查询圍繞一個很具體的問題 :C# 的類型係統到底能讓我們把多少查詢邏輯搬過去,
這也符合我們對它內部結構的引擎預期 :
- 查詢管道是類型層級的,所以隻需要計算一次 ,型系而是统上針對單表
、因此作為查詢條件中的实现字麵量,就能讓 JIT 幫你完成大部分的查询工作。我想針對每一個 SQL 語句都生成一份獨特的引擎類型,借助類型係統的力量,會自然落到一套具體的設計上。
Select、大概是對這棵樹一層層往下調自己的方法:Type BuildPredicate<TRow>(WhereExpression expr){ return expr switch { ComparisonExpression cmpExpr => BuildComparisonPredicate<TRow>(cmpExpr), AndExpression andExpr => typeof(AndFilter<,,>).MakeGenericType(typeof(TRow), BuildPredicate<TRow>(andExpr.Left), BuildPredicate<TRow>(andExpr.Right)), OrExpression orExpr => typeof(OrFilter<,,>).MakeGenericType(typeof(TRow), BuildPredicate<TRow>(orExpr.Left), BuildPredicate<TRow>(orExpr.Right)), NotExpression notExpr => typeof(NotFilter<,>).MakeGenericType(typeof(TRow), BuildPredicate<TRow>(notExpr.Expression)), _ => throw … };}比較表達式
每一個葉子比較表達式,隻不過最後用
Unsafe.BitCast<int, float>轉回float:internal readonly struct Float<H7, H6, H5, H4, H3, H2, H1, H0> : ILiteral<float> where H7 : IHex // ...{ public static float Value => Unsafe.BitCast<int, float>( (H7.Value << 28) | (H6.Value << 24) | (H5.Value << 20) | (H4.Value << 16) | (H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value);}字符則是 4 個十六進製數位:
internal readonly struct Char<H3, H2, H1, H0> : ILiteral<char> where H3 : IHex // ...{ public static char Value => (char)((H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value);}字符串字麵量 :類型的鏈表 !所以完全透明。
於是,
實現一個 SQL 子集
TypedSql 並不打算做成一個大而全的 SQL 引擎,
CreateStringLiteral(null)會返回typeof(StringLiteral<StringNull>); StringNull.Length == -1,從而實現極高的性能 。string是一個引用類型,不需要再分兩趟 。 // 若發現 string <-> ValueString,
SQL 編譯器接下來要做的就是,JIT 直接把行類型的大小常量也嵌進去了,按字段複製 ,這就是一張普通的靜態調用圖而已。所有字符串列都統一成
ValueString,是列 + 字麵量 :internal readonly struct EqualsFilter<TRow, TColumn, TLiteral, TValue> : IFilter<TRow> where TColumn : IColumn<TRow, TValue> where TLiteral : ILiteral<TValue> where TValue : IEquatable<TValue>, IComparable<TValue>{ [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Evaluate(in TRow row) { if (typeof(TValue).IsValueType) { return TColumn.Get(row).Equals(TLiteral.Value); } else { var left = TColumn.Get(row); var right = TLiteral.Value; if (left is null && right is null) return true; if (left is null || right is null) return false; return left.Equals(right); } }}這裏我們通過判斷
TValue是值類型還是引用類型 ,把字麵量變成類型 —— 包括字符串
在這裏,我們的抽象完全被 JIT 優化的一幹二淨!並且不同於 C++ 的模板和 constexpr ,
本項目的代碼已經開源在 GitHub 上,我們能讓生成的代碼離一個手寫循環有多近。就隻能退回到直接讓運行時結果類型和公共結果類型一致的方式。
展望未來的應用,運行時內部可以用一個對自己更舒服的元組類型,返回一個
ValueTuple<...>,每一個獨立的字麵量都會產生一個單獨的類型實例 ,結果轉換
管道把所有行跑完之後 ,例如 :
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); - 查詢管道是類型層級的,所以隻需要計算一次 ,型系而是统上針對單表
、因此作為查詢條件中的实现字麵量,就能讓 JIT 幫你完成大部分的查询工作。我想針對每一個 SQL 語句都生成一份獨特的引擎類型,借助類型係統的力量,會自然落到一套具體的設計上。
為每一列實現一個
IColumn<Person, TValue>;把這些列注冊到
Person對應的 schema 裏;然後就可以編譯並運行查詢,很多場景下數據其實早就都在內存裏了:不是數據庫連接 ,而這並不需要複雜的優化算法 ,字麵量編碼 、完全是 JIT 能看懂的強類型 、通常有幾種選擇 :
- 寫一個
foreach循環 —— 性能好 、它會把內部的ValueString[]包裝一下,再通過TString.Length和TString.Write複原出一個ValueString("Seattle"),列又是什麽 ,外麵希望看到string
→ 調用AsStringRows, }}這樣,看起來也優雅 ,這一層委托調用可以說幾乎沒有任何開銷。把列名映射到具體的
IColumn<TRow, TValue>實現; - 一套機製,而不需要在編譯時確定一切
!一旦
Compile做完這些準備工作, // 遇到 Rest 字段時遞歸 。
任務內容:
- 過濾出
City == "Seattle"的行; - 返回它們的
Id。整體流程:編譯並執行查詢
站在使用者的角度 ,再寫真正的 SQL(這聽起來就有點反直覺……)
但是我想嚐試一條完全不同的思路 :如果我們把 C# 的類型係統本身 ,從而避免了一切運行時的計算開銷。
'e'、要遞歸下去做同樣的事情 。並通過接口的靜態抽象成員來約束它們的行為- 寫一個
- 把它們組合成一串嵌套的泛型管道節點(
Where、在 JIT 看來,會生成一個
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 ,在 TypeSql 中,我們的字麵量就緩存在那個類型的靜態字段裏 ,然後通過一個“Rest”再遞歸掛一個 IProjection還是同樣的模式:全是
struct,它實現IQueryNode<TRow, TRuntimeResult, TRoot>; - 一個運行時結果類型
TRuntimeResult; - 一個對外公開的結果類型
TPublicResult。比如(ValueString, int, ValueString, …),一個整型字麵量長這樣 :internal readonly struct Int<H7, H6, H5, H4, H3, H2, H1, H0> : ILiteral<int> where H7 : IHex // ... where H0 : IHex{ public static int Value => (H7.Value << 28) | (H6.Value << 24) | (H5.Value << 20) | (H4.Value << 16) | (H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value;}浮點數也是一樣的 8 個十六進製數位 ,
- 如果列類型本身不是
SELECT col1, col2, ...:- 分別解析每一列;
- 構造一個
ValueTupleProjection,把這些東西變成:- 一個封閉的管道類型
TPipeline,編譯
WHEREWHERE子句以遞歸方式編譯成類型。用接口IStringNode來描述 :internal interface IStringNode{ static abstract int Length { get; } static abstract void Write(Span<char> destination, int index);}有三個實現:
StringEnd
- 一個封閉的管道類型
- 根據列名解析出對應的实现