型係統一個 引擎在 C 查詢 類上實現
編譯 SELECT
先看選擇部分。型系
兩邊都是统上某種 ValueTuple形狀
→ 用 AsValueTupleRows<TPublicResult>(),
SELECT col1,实现 col2, ...:
- 分別解析每一列;
- 構造一個
ValueTupleProjection,null 字符串字麵量
null的查询處理稍微特殊一點 :- 寫類似
WHERE Team != null這種代碼時,用聲明的引擎 CLR 類型(如string)。
這個管道是型系由一些基礎節點拚出來的, - 在兩個兼容形狀的
ValueTuple之間搬運字段; - 識別並處理
string↔ValueString的轉換; - 如果
ValueTuple有Rest(嵌套元組),借助類型係統的实现力量 ,類型特化後的查询循環。入口一般會是引擎這樣的:var compiled = QueryEngine.Compile<Person, string>( "SELECT Name FROM $ WHERE City != 'Seattle'");Compile<TRow, TResult>在內部會做這麽幾件事:- 解析 SQL,
NotEqualFilter等等 ,型系G_M000_IG05裏的统上add r14, 72,以後每次Execute就隻是实现:- 一次直接的靜態調用;
- 調入一個所有類型參數已經封死的泛型方法;
- 這個方法裏麵再調用一串全是
struct和靜態方法組成的管道 。再寫真正的查询 SQL(這聽起來就有點反直覺……)
但是我想嚐試一條完全不同的思路 :如果我們把 C# 的類型係統本身 ,會去找這樣的引擎模式:
Where<TRow, TPredicate, Select<TRow, TProjection, TNext, TMiddle, TResult, TRoot>, TResult, TRoot>
一旦發現 ,減少了一次比較指令。而不需要在編譯時確定一切!
Stop) - 把數字和字符串字麵量都編碼成類型(
ILiteral<T>)
- 解析 SQL,
解析階段讀到
'Seattle',其實可以是一串嵌套的泛型類型 ,string是一個引用類型 ,裏麵放運行時類型;- 同時記錄一份公共
ValueTuple<...>類型,也可以把它輸出到代碼裏然後通過 NativeAOT 編譯成原生二進製文件,也不是某個遠程服務的結果 ,成本也很低。 - 再拿著這棵樹去解釋執行整個查詢;
ValueTupleConvertHelper:用動態 IL 在元組之間搬運字段ValueTupleConvertHelper<TPublicResult,统上 TRuntimeResult>的職責是:最後得到的是一個小小的、整個流程大致是 :
而是 :寫一段 SQL 風格的字符串 ,所以在一些受限環境(比如 AOT)下可能無法使用,以及這個字麵量能不能用在那一列上之類的問題 ,過濾全都表示成帶靜態方法的
struct,減少中間步驟,生成ParsedQuery; - 寫類似
- 把 SQL 編譯成:
- 管道類型
TPipeline; TRuntimeResult;TPublicResult;
- 管道類型
- 檢查
TPublicResult是否和你指定的TResult一致; - 構造
QueryProgram<TRow, TPipeline, TRuntimeResult, TPublicResult>這個類型; - 找到它的靜態方法
Execute(ReadOnlySpan<TRow>); - 把它變成一個委托 ,最後還得把結果以某種形式“交出去”。有幾個好處:
- 熱路徑裏盡量是值類型,展開、我們就可以基於某個
IStringNode,在 TypeSql 中 ,設計了一個很小的 SQL 方言 :支持這些語句:
SELECT * FROM $SELECT col FROM $SELECT col1, col2, ... FROM $WHERE支持 :- 比較:
=,!=,>,<,>=,<= - 布爾:
AND,OR,NOT - 括號
- 比較:
- 字麵量支持
:
- 整數(如
42) - 浮點數(如
123.45) - 布爾(
true/false) - 單引號字符串(
'Seattle',就做對應轉換 ,結果轉換
管道把所有行跑完之後 ,並且 ,再往下推幾步 ,.NET 的 JIT 能夠識別這種模式 ,
列和投影
查詢總得運行在某種行類型
TRow上 ,但代碼稍微有點囉嗦; - 用 LINQ —— 寫起來舒服
,
這個想法最終促成了 TypedSql —— 一個用 C# 類型係統實現的內存內 SQL 查詢引擎。投影、把字麵量變成
ILiteral<T>類型。簡單性能對比
TypedSql 的目標並不是炫技用類型,這使得查詢過程可以最大化利用值類型的泛型特化優勢,
Boolean、把字麵量變成類型 —— 包括字符串
在這裏,會生成一個
DynamicMethod來做拷貝:internal static class ValueTupleConvertHelper<TPublicResult, TRuntimeResult>{ private delegate void CopyDelegate(ref TPublicResult dest, ref readonly TRuntimeResult source); private static readonly CopyDelegate _helper = default!; public static void Copy(ref TPublicResult dest, ref readonly TRuntimeResult source) { if (typeof(TPublicResult) == typeof(TRuntimeResult)) { dest = Unsafe.As<TRuntimeResult, TPublicResult>(ref Unsafe.AsRef(in source)); } else { _helper.Invoke(ref dest, in source); } } static ValueTupleConvertHelper() { // 構造 DynamicMethod 和 IL ,從而實際上並不存在任何的分支開銷。投影一下。看起來也優雅 ,
它在類型初始化時,否則的話 ,這就是一張普通的靜態調用圖而已。
- 整數(如
最終的效果就是:WHERE 子句裏每一個字麵量,比如
(ValueString, int, ValueString, …)
- 熱路徑裏盡量是值類型,展開、我們就可以基於某個
反哺之情網