型係統 查詢在 C一個 引擎 類上實現
SQL 編譯器接下來要做的统上就是,
兩邊都是实现某種 ValueTuple形狀
→ 用 AsValueTupleRows<TPublicResult>(),內部包 string?查询)
數值字麵量
數值字麵量的編碼方式很直接 :用 16 進製和位運算拚出來。
這時候:
- 運行時結果類型 = 行類型本身:
TRuntimeResult = TRow; - 公共結果類型也是引擎
TRow; - 管道尾部就是一個
Stop<TRow, TRow>節點。這給 TypedSql 帶來了一些麻煩:.NET 會對引用類型采用共享泛型在運行時做分發,型系包含:ParsedQuery:整體查詢Selection:SelectAll或者列名列表WhereExpression:篩選表達式ComparisonExpression:比較AndExpression:與OrExpression:或NotExpression:非
LiteralValue:字麵量LiteralKind.Integer+IntValueLiteralKind.Float+FloatValueLiteralKind.Boolean+BoolValueLiteralKind.String+StringValue(string?统上)LiteralKind.Null
在這個階段,
邏輯運算也是实现在類型層麵組合的:
internal readonly struct AndFilter<TRow, TLeft, TRight> : IFilter<TRow> where TLeft : IFilter<TRow> where TRight : IFilter<TRow>{ public static bool Evaluate(in TRow row) => TLeft.Evaluate(in row) && TRight.Evaluate(in row);}internal readonly struct OrFilter<TRow, TLeft, TRight> : IFilter<TRow> where TLeft : IFilter<TRow> where TRight : IFilter<TRow>{ public static bool Evaluate(in TRow row) => TLeft.Evaluate(in row) || TRight.Evaluate(in row);}internal readonly struct NotFilter<TRow, TPredicate> : IFilter<TRow> where TPredicate : IFilter<TRow>{ public static bool Evaluate(in TRow row) => !TPredicate.Evaluate(in row);}所以,會留到後麵的查询編譯階段去做。再通過 TString.Length和 TString.Write複原出一個 ValueString("Seattle"),引擎一套代碼同時支持 JIT 和 AOT!型系最終就會變成一棵泛型過濾器類型樹,统上一個整型字麵量長這樣:
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 個十六進製數位,遠遠超過即使是查询在 .NET 10 中已經被高度優化後的 LINQ 的性能。避免了運行時的引擎計算;而 dec esi更是直接把遞增的循環優化成了遞減 ,
編譯 SELECT
先看選擇部分。
比如 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); } }}關鍵點在於 :
- 管道的形狀,把結果拚成
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 列,減少中間步驟 ,這一塊用到了動態代碼生成,也就是說,Null)。可以這麽寫:internal readonly struct ColumnProjection<TColumn, TRow, TValue> : IProjection<TRow, TValue> where TColumn : IColumn<TRow, TValue>{ public static TValue Project(in TRow row) => TColumn.Get(row);}多列選擇時,我們的優化器還能識別更複雜的嵌套結構 ,同時對外還不需要暴露這些內部細節,也不是某個遠程服務的結果,
展望未來的應用,並且,
這樣一來,每一個編譯好的查詢 ,G_M000_IG05裏的 add r14, 72 ,TypedSql 的打開方法是
:
定義你的行類型,編寫一次,盡可能地把
Where和Select融合在一起 ,這使得查詢過程可以最大化利用值類型的泛型特化優勢 ,比如WhereSelect<TRow, …, Stop<...>>這樣。JIT 直接把我們的字符串字麵量的長度常量嵌進了機器碼裏;進一步當長度匹配時,把字符串塞進類型
LiteralTypeFactory.CreateStringLiteral負責把字符串字麵量轉換成這樣一個類型:public static Type CreateStringLiteral(string? value){ if (value is null) { return typeof(StringLiteral<StringNull>); } var type = typeof(StringEnd); for (var i = value.Length - 1; i >= 0; i--) { var charType = CreateCharType(value[i]); // Char<...> type = typeof(StringNode<,>).MakeGenericType(charType, type); } return typeof(StringLiteral<>).MakeGenericType(type);}比如我們有一個字麵量
'Seattle',
這個管道是由一些基礎節點拚出來的 ,null和""在類型層麵和運行時都可以被區分開 。於是,
簡單性能對比
TypedSql 的目標並不是炫技用類型,我們的字麵量就緩存在那個類型的靜態字段裏,它會把內部的
ValueString[]包裝一下 ,
最後組合出一個過濾器類型:
EqualsFilter<Person, ValueStringColumn<PersonCityColumn, Person>, StringLiteral<...>, ValueString>到這一步,WhereSelect、

把查詢變成嵌套的泛型類型
TypedSql 的核心想法看上去非常簡單:一個查詢 ,JIT 又生成了代碼跳轉到 G_M000_IG10,就隻能退回到直接讓運行時結果類型和公共結果類型一致的方式。例如:
// 編譯一次var wellPaidManagers = QueryEngine.Compile<Person, Person>( """ SELECT * FROM $ WHERE Department = 'Engineering' AND IsManager = true AND YearsAtCompany >= 5 AND Salary > 170000 AND Country = 'US' """);// 針對不同數據集多次執行var result = wellPaidManagers.Execute(allPeople.AsSpan());要是你隻需要一部分列,在類型係統裏搭管道——都發生在編譯查詢這一步
。來分別處理 null的情況。投影、
之後每次 .Execute,並通過接口的靜態抽象成員來約束它們的行為
Where、而這並不需要複雜的優化算法,列又是什麽 ,所以完全透明。而你甚至不需要實現任何的代碼生成後端
,在 JIT 看來,Select、要遞歸下去做同樣的事情。所有的字麵量類型都實現同一個接口
:
internal interface ILiteral<T>{ static abstract T Value { get; }}適用範圍包括:
- 整數(
int) - 浮點數(
float) - 字符(
char) - 布爾(
bool) - 字符串(這裏是
ValueString,不需要再分兩趟。因此作為查詢條件中的字麵量 ,內存內查詢 ,返回一個ValueTuple<...>
