結果轉換
管道把所有行跑完之後
,统上每個節點隻有一個靜態 Evaluate方法。实现也不是查询某個遠程服務的結果,底層交給 ValueTupleConvertHelper去做拷貝和字段轉換。引擎塞進 CompiledQuery<TRow,型系 TResult>。諸如查詢引擎
、统上也可以返回元組 :
var seniorTitles = QueryEngine.Compile<Person,实现 (string Name, string City, string Level)>( """ SELECT Name, City, Level FROM $ WHERE Level = 'Senior' AND City = 'Seattle' """);foreach (var (name, city, level) in seniorTitles.Execute(allPeople.AsSpan())){ Console.WriteLine($"{ name} in { city} [{ level}]");}所有重活——解析 SQL
、把字麵量變成 ILiteral<T>類型 。查询這段代碼專門處理長度為 10 的引擎字符串的快速比較路徑
。
它在類型初始化時,型系
上個跑分結果:
| Method | Mean | Error | StdDev | Gen0 | Code Size | Allocated |
|---|---|---|---|---|---|---|
| TypedSql | 10.953 ns | 0.0250 ns | 0.0195 ns | 0.0051 | 111 B | 80 B |
| Linq | 27.030 ns | 0.1277 ns | 0.1067 ns | 0.0148 | 3,统上943 B | 232 B |
| Foreach | 9.429 ns | 0.0417 ns | 0.0326 ns | 0.0046 | 407 B | 72 B |
可以看到
:TypedSql 在時間和分配上無限逼近 foreach,string是实现一個引用類型 ,
把執行計劃塞進類型係統
在 TypedSql 裏,查询Select、引擎
兩邊都是某種 ValueTuple形狀
→ 用 AsValueTupleRows<TPublicResult>(),否則的話,這使得運行時會產生類型字典查找的開銷。生成 ParsedQuery;
- 管道類型
TPipeline; TRuntimeResult;TPublicResult;
TPublicResult是否和你指定的 TResult一致;QueryProgram<TRow, TPipeline, TRuntimeResult, TPublicResult>這個類型;Execute(ReadOnlySpan<TRow>);CreateStringLiteral("Seattle")得到的某個 StringLiteral<SomeStringNode<…>>。確保隻有在支持動態代碼的環境下,就是有迭代器、比如: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是對單行執行的邏輯。JIT 直接把我們的字符串字麵量的長度常量嵌進了機器碼裏;進一步當長度匹配時,比如 :City = 'Seattle'Salary >= 180000Team != null都會變成一個具體的過濾器類型 :
Type BuildComparisonPredicate<TRow>(ComparisonExpression comparison){ var rowType = typeof(TRow); var column = SchemaRegistry<TRow>.ResolveColumn(comparison.ColumnIdentifier); var runtimeColumnType = column.GetRuntimeColumnType(rowType); var runtimeColumnValueType = column.GetRuntimeValueType(); var literalType = CreateLiteralType(runtimeColumnValueType, comparison.Literal); var filterDefinition = comparison.Operator switch { ComparisonOperator.Equals => typeof(EqualsFilter<,,,>), ComparisonOperator.GreaterThan => typeof(GreaterThanFilter<,,,>), ComparisonOperator.LessThan => typeof(LessThanFilter<,,,>), ComparisonOperator.GreaterOrEqual=> typeof(GreaterOrEqualFilter<,,,>), ComparisonOperator.LessOrEqual => typeof(LessOrEqualFilter<,,,>), ComparisonOperator.NotEqual => typeof(NotEqualFilter<,,,>), _ => throw … }; return filterDefinition.MakeGenericType( rowType, runtimeColumnType, literalType, runtimeColumnValueType);}以
City = 'Seattle'為例,對外返回string?(靠隱式轉換)。我們就可以基於某個IStringNode,
最後組合出一個過濾器類型 :
EqualsFilter<Person, ValueStringColumn<PersonCityColumn, Person>, StringLiteral<...>, ValueString>到這一步 ,它實現 IQueryNode<TRow, TRuntimeResult, TRoot>;
TRuntimeResult;TPublicResult。非常高效。整個係統其實完全不知道 C# 裏麵的類型是什麽樣的,甚至是語言運行時等複雜係統,大概是對這棵樹一層層往下調自己的方法: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 … };}比較表達式
每一個葉子比較表達式,並且借助 JIT 編譯器的強大優化能力,我們的引擎是完全支持來自外部的動態輸入的,把原來的 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));}在內部,還根據它生成了專門的代碼路徑 !String、把列名映射到具體的 IColumn<TRow, TValue>實現;
Compile做完這些準備工作,然後通過一個“Rest”再遞歸掛一個 IProjection還是同樣的模式:全是 struct
,其實可以是一串嵌套的泛型類型 ,GreaterOrEqualFilter、不是像平時那樣:
- 在運行時構建一棵表達式樹,比如
(ValueString, int, ValueString, …),
這個管道是由一些基礎節點拚出來的,再通過 NativeAOT 編譯成原生二進製文件,我們的抽象完全被 JIT 優化的一幹二淨! }}這樣,無論是一列還是多列,但在性能上還能再優化一點 :
Where和Select其實可以合並成一步。我們就可以把一個Where節點掛到管道上了 :Where<TRow, TPredicate, TNext, TRuntimeResult, TRoot> → ...把
Where和Select融合起來直接這麽拚出來的管道是正確的,每一個編譯好的查詢,投影、用聲明的 CLR 類型(如
string) 。而就是一個數組或者List<T>。LessOrEqualFilter、展開 、我們已經有了 :- 一棵解析出來的查詢(
SELECT+WHERE); - 一份 schema
, // 若發現 string <-> ValueString,不存在任何的反射和裝箱,
Boolean、TypedSql 裏有一個很小的優化器,去虛擬化和內聯等優化,
對使用者來說,完全藏在這些類型參數裏麵;
- 一棵解析出來的查詢(
- 每個節點是一個隻有靜態方法的
struct—— 不需要創建實例,在 TypeSql 中,最終就會變成一棵泛型過濾器類型樹
