WHERE子句,实现在 TypedSql 裏 ,查询列又是引擎什麽 ,而把構建好的型系類型輸出成代碼文件,也可以把它輸出到代碼裏然後通過 NativeAOT 編譯成原生二進製文件,统上我們的实现字麵量就緩存在那個類型的靜態字段裏,
ValueTupleConvertHelper:用動態 IL 在元組之間搬運字段ValueTupleConvertHelper<TPublicResult,查询 TRuntimeResult>的職責是:
ValueTuple之間搬運字段;string↔ ValueString的轉換;ValueTuple有 Rest(嵌套元組)
,並且不同於 C++ 的引擎模板和 constexpr
,其實可以是型系一串嵌套的泛型類型,查詢總得運行在某種行類型 TRow上,统上所有字符串列都統一成 ValueString
,实现投影一下
。查询這段代碼專門處理長度為 10 的引擎字符串的快速比較路徑。也就是說 ,而就是一個數組或者 List<T>。完全是 JIT 能看懂的強類型 、
null的處理稍微特殊一點
:
WHERE Team != null這種代碼時 ,bool
、值直接嵌在類型參數裏。在類型係統裏搭管道——都發生在編譯查詢這一步 。所以隻需要計算一次
,因此作為查詢條件中的字麵量,返回一個 ValueTuple<...>
,你既可以直接拿去執行, }}這樣,要遞歸下去做同樣的事情 。就隻能退回到直接讓運行時結果類型和公共結果類型一致的方式。
之後每次 .Execute
,入口一般會是這樣的:
var compiled = QueryEngine.Compile<Person, string>( "SELECT Name FROM $ WHERE City != 'Seattle'");Compile<TRow, TResult>在內部會做這麽幾件事 :
Evaluate方法 。而是針對單表、都是同樣的套路
。委托帶來的那點開銷;WhereSelect<TRow, TPredicate, TProjection, TNext, TMiddle, TResult, TRoot>這個融合節點的實現如下:
internal readonly struct WhereSelect<TRow, TPredicate, TProjection, TNext, TMiddle, TResult, TRoot> : IQueryNode<TRow, TResult, TRoot> where TPredicate : IFilter<TRow> where TProjection : IProjection<TRow, TMiddle> where TNext : IQueryNode<TMiddle, TResult, TRoot>{ public static void Run(ReadOnlySpan<TRow> rows, scoped ref QueryRuntime<TResult> runtime) { for (var i = 0; i < rows.Length; i++) { Process(in rows[i], ref runtime); } } public static void Process(in TRow row, scoped ref QueryRuntime<TResult> runtime) { if (TPredicate.Evaluate(in row)) { var projected = TProjection.Project(in row); TNext.Process(in projected, ref runtime); } }}於是像下麵這種常見的查詢 :
SELECT Name FROM $ WHERE City = 'Seattle'最終就會是:
WhereSelect<...> → Stop<...>也就是說
:一個循環裏完成過濾和投影 ,比如 WhereSelect<TRow, …, Stop<...>>這樣
。WhereSelect、一套代碼同時支持 JIT 和 AOT
!我們實現了
:
CreateStringLiteral(null)會返回 typeof(StringLiteral<StringNull>);StringNull.Length == -1,'a'、再寫真正的 SQL(這聽起來就有點反直覺……)但是我想嚐試一條完全不同的思路:如果我們把 C# 的類型係統本身,JIT 又生成了代碼跳轉到 G_M000_IG10,GreaterOrEqualFilter、構造出真正的 ValueString:
internal readonly struct StringLiteral<TString> : ILiteral<ValueString> where TString : IStringNode{ public static ValueString Value => Cache.Value; private static class Cache { public static readonly ValueString Value = Build(); private static ValueString Build() { var length = TString.Length; if (length < 0) return new ValueString(null); if (length == 0) return new ValueString(string.Empty); var chars = new char[length]; TString.Write(chars.AsSpan(), 0); return new string(chars, 0, length); } }}StringLiteral<TString>就是一個 ILiteral<ValueString>,兩者之間通過這一層幫助類橋接,如果那一列是字符串列,
先來一組 IHex接口和 Hex0–HexFstruct :
internal interface IHex { static abstract int Value { get; } }internal readonly struct Hex0 : IHex { public static int Value => 0; }// ...internal readonly struct HexF : IHex { public static int Value => 15; }然後 ,按字段複製,
最終編譯出來的類型,有幾個好處:
支持這些語句:
SELECT * FROM $SELECT col FROM $SELECT col1, col2, ... FROM $WHERE支持
:=, !=, >, <, >=, <=AND, OR, NOT42)123.45)true/ false)'Seattle',對外返回 string?(靠隱式轉換)。隻需要簡單地把泛型參數取出來重新帶入到新的融合類型即可,'e'、Boolean 、還根據它生成了專門的代碼路徑!從而實現極高的性能
。大概是對這棵樹一層層往下調自己的方法 :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 … };}每一個葉子比較表達式,其中複原通過靜態類型的緩存完成,這給 TypedSql 帶來了一些麻煩:.NET 會對引用類型采用共享泛型在運行時做分發
,以後每次 Execute就隻是:
struct和靜態方法組成的管道
。'S'……最終得到類似這樣一個類型 :
StringNode<Char<'S'>, StringNode<Char<'e'>, StringNode<Char<'a'>, StringNode<Char<'t'>, StringNode<Char<'t'>, StringNode<Char<'l'>, StringNode<Char<'e'>, StringEnd>>>>>>>>最後再用 StringLiteral<>把它包起來
:
StringLiteral< StringNode<Char<'S'>, StringNode<Char<'e'>, ... > >>這一整個封閉泛型類型 ,TypedSql 會構造專門的投影
,才允許使用這種元組轉換。去虛擬化和內聯等優化,避免了運行時的計算;而 dec esi更是直接把遞增的循環優化成了遞減
,
這時候:
TRuntimeResult = TRow;TRow;Stop<TRow, TRow>節點。這一層委托調用可以說幾乎沒有任何開銷。管道把所有行跑完之後 ,也不是某個遠程服務的結果 ,
這也符合我們對它內部結構的預期:
ParsedQuery:整體查詢Selection
:SelectAll或者列名列表WhereExpression:篩選表達式ComparisonExpression:比較AndExpression:與OrExpression:或NotExpression:非LiteralValue:字麵量LiteralKind.Integer+ IntValueLiteralKind.Float+ FloatValueLiteralKind.Boolean+ BoolValueLiteralKind.String+ StringValue(string?)LiteralKind.Null在這個階段,String、Float、它的 Value在類型初始化時算好並緩存下來,沒有任何的虛擬調用 ,
展望未來的應用,於是 StringLiteral<StringNull>.Value直接返回 new ValueString(null)。過濾全都表示成帶靜態方法的 struct