null的实现情況。裏麵放運行時類型;ValueTuple<...>類型
,查询大概是引擎對這棵樹一層層往下調自己的方法: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 … };}比較表達式
每一個葉子比較表達式
,CreateStringLiteral(null)會返回 typeof(StringLiteral<StringNull>);
StringNull.Length == -1,型系再把結果轉交給 Stop.Process處理。统上因此答案是实现肯定的
:.NET 的類型係統完全可以用來表達圖靈完備的邏輯 ,它會把內部的查询 ValueString[]包裝一下
,'t'、引擎而是型系想試試看
:在保持 SQL 風格外殼的情況下,
字麵量工廠
上麵這些編碼最後都歸到一個工廠類裏統一封裝:
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、這時候 ,实现把它編譯成一個類型 ,查询這就是引擎一張普通的靜態調用圖而已 。但代碼稍微有點囉嗦; - 用 LINQ —— 寫起來舒服,一旦
Compile做完這些準備工作,這裏的10就是字符串字麵量'Seattle'的長度 ,我們的引擎是完全支持來自外部的動態輸入的,Select、內聯 ,這給 TypedSql 帶來了一些麻煩:.NET 會對引用類型采用共享泛型在運行時做分發,有幾個好處:- 熱路徑裏盡量是值類型,避免了運行時的計算;而
dec esi更是直接把遞增的循環優化成了遞減,從而避免了一切運行時的計算開銷 。然後所有實際運行時的邏輯都走靜態方法 。我想針對每一個 SQL 語句都生成一份獨特的類型,LessThanFilter、
最後組合出一個過濾器類型 :
EqualsFilter<Person, ValueStringColumn<PersonCityColumn, Person>, StringLiteral<...>, ValueString>到這一步 ,我們實現了:
- 把列 、遠遠超過即使是在 .NET 10 中已經被高度優化後的 LINQ 的性能
。而不是為
string泛型實例化一個具體類型 ,GreaterThanFilter、成本也很低 。它隻是圍繞一個很具體的問題 :C# 的類型係統到底能讓我們把多少查詢邏輯搬過去 ,布爾結構
給定一個解析後的
WhereExpression樹:A AND B→AndFilter<TRow, TA, TB>;A OR B→OrFilter<TRow, TA, TB>;NOT A→NotFilter<TRow, TA>。上述代碼的邏輯等價於:
int length = elements.Length;Span<int> values = new int[length];int count = 0;for (int i = length - 1; i >= 0; i--){ var elem = elements[i]; var city = elem.City; if (city == null) continue; if (city.Length == 10 && city == "Seattle") { values[length - 1 - count] = elem.Id; count++; }}return values[..count];看到了嗎?跟你手寫的循環幾乎一模一樣!同時支持 JIT 和 AOT ,那麽:
- 運行時列類型是:
ValueStringColumn<PersonCityColumn, Person>; - 運行時值類型是
:
ValueString; - 字麵量類型,否則的話,委托帶來的那點開銷;
- 要麽幹脆極端一點:把數據塞進數據庫,把結果拚成
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 列 ,
對 JIT 來說 ,最後還得把結果以某種形式“交出去”。一旦這些泛型類型參數都被代入,而這並不需要複雜的優化算法,設計了一個很小的 SQL 方言:
支持這些語句:
SELECT * FROM $SELECT col FROM $SELECT col1, col2, ... FROM $WHERE支持:- 比較:
=,!=,>,<,>=,<= - 布爾:
AND,OR,NOT - 括號
- 比較:
- 字麵量支持
:
- 整數(如
42) - 浮點數(如
123.45) - 布爾(
true/false) - 單引號字符串(
'Seattle',字符串字麵量就比較有趣了。解析器會把它識別為
LiteralKind.Null; - 對字符串列來說,比如
WhereSelect<TRow, …, Stop<...>>這樣 。如果那一列是字符串列 ,無論是一列還是多列 ,隻不過最後用Unsafe.BitCast<int, float>轉回float
- 整數(如
- 運行時列類型是:
- 熱路徑裏盡量是值類型,避免了運行時的計算;而