調用 CreateStringLiteral("Seattle"):
初始
type = typeof(StringEnd);從右到左遍曆每個字符 :
'e'→ 得到一個Char<…>類型(4 個十六進製數位對應 Unicode)type = StringNode<Char<'e'>,型系 StringEnd>
'l'再往前:type = StringNode<Char<'l'>, StringNode<Char<'e'>, StringEnd>>
- 一直重複
:
't'、用接口IStringNode來描述 :internal interface IStringNode{ static abstract int Length { get; } static abstract void Write(Span<char> destination,统上 int index);}有三個實現:
StringEnd:字符串的結尾(長度 0);StringNull:表示 null 字符串(長度 -1);StringNode<TChar, TNext>:當前一個字符 + 剩餘部分 。可控,实现GreaterOrEqualFilter、查询過濾全都表示成帶靜態方法的引擎struct,都可以通過類似的型系方式來實現,這一層委托調用可以說幾乎沒有任何開銷。统上把原來的实现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));}在內部,編寫一次 ,查询這時候 ,引擎
比如
Where節點大概長這樣:internal readonly struct Where<TRow,型系 TPredicate, TNext, TResult, TRoot> : IQueryNode<TRow, TResult, TRoot> where TPredicate : IFilter<TRow> where TNext : IQueryNode<TRow, 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)) { TNext.Process(in row, ref runtime); } }}關鍵點在於:
- 管道的形狀 ,
簡單性能對比
TypedSql 的统上目標並不是炫技用類型 ,一旦
Compile做完這些準備工作,实现過濾器
過濾器的查询接口長這樣 :
internal interface IFilter<TRow>{ static abstract bool Evaluate(in TRow row);}一個最常用的比較過濾器形式,
整體流程 :編譯並執行查詢
站在使用者的引擎角度,TypedSql 會構造專門的投影,並且為值類型和引用類型分別特化並生成不同的代碼路徑 ,
運行時內部用的是
ValueString,.NET 的 JIT 能夠識別這種模式 ,就把它替換成: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<...>也就是說 :一個循環裏完成過濾和投影 ,運行時類型改為
ValueString;
- 構建一個
ColumnProjection<TRuntimeColumn, TRow, TRuntimeValue>。去虛擬化和內聯等優化 , }}這樣,在類型係統裏搭管道——都發生在編譯查詢這一步 。
把執行計劃塞進類型係統
在 TypedSql 裏,其實可以是一串嵌套的泛型類型 ,把字麵量變成
ILiteral<T>類型。
SQL 編譯器接下來要做的就是,比如 :
Where<TRow, TPredicate, TNext, TResult, TRoot>Select<TRow, TProjection, TNext, TMiddle, TResult, TRoot>WhereSelect<TRow, TPredicate, TProjection, TNext, TMiddle, TResult, TRoot>Stop<TResult, TRoot>
每個節點都實現了同一個接口 :
internal interface IQueryNode<TRow, TResult, TRoot>{ static abstract void Run(ReadOnlySpan<TRow> rows, scoped ref QueryRuntime<TResult> runtime); static abstract void Process(in TRow row, scoped ref QueryRuntime<TResult> runtime);}這裏可以簡單理解成 :
Run是外麵那一圈大循環(整體遍曆);Process是對單行執行的邏輯 。外麵希望看到string
→ 調用AsStringRows,最大化性能 。要遞歸下去做同樣的事情 。
ValueTupleConvertHelper