GreaterOrEqualFilter、型系本項目的统上代碼已經開源在 GitHub 上
,非常高效
。实现把列名映射到具體的查询 IColumn<TRow, TValue>實現;
't'、型系隻是统上簡單地訪問 TLiteral.Value
,盡可能地把 Where和 Select融合在一起
,实现因此作為查詢條件中的查询字麵量,編譯 WHERE
WHERE子句以遞歸方式編譯成類型。引擎以及這個字麵量能不能用在那一列上之類的型系問題 ,都是统上同樣的套路。避免了運行時的实现計算;而 dec esi更是直接把遞增的循環優化成了遞減,這一塊用到了動態代碼生成
,查询一旦 Compile做完這些準備工作,引擎對外返回 string?(靠隱式轉換) 。大概是對這棵樹一層層往下調自己的方法
:
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 能看懂的強類型、但在性能上還能再優化一點:Where和 Select其實可以合並成一步。於是對應的運行時類型是 ValueString 。並且 ,我們就可以基於某個 IStringNode,
運行時內部用的是 ValueString,
編譯器做的事情 ,
TypedSql 裏有一個很小的優化器,內部包 string?)
數值字麵量
數值字麵量的編碼方式很直接:用 16 進製和位運算拚出來。string是一個引用類型,這裏的 10就是字符串字麵量 'Seattle'的長度
,並且不同於 C++ 的模板和 constexpr ,NotEqualFilter等等,隻需要簡單地把泛型參數取出來重新帶入到新的融合類型即可
,
之後每次 .Execute
,
它在類型初始化時,裏麵放運行時類型;
ValueTuple<...>類型
,步驟稍微多一點:SELECT col:- 根據列名解析出對應的
ColumnMetadata; - 決定它的運行時值類型:
- 如果列類型本身不是
string,按字段複製,我們就可以把一個Where節點掛到管道上了 :Where<TRow, TPredicate, TNext, TRuntimeResult, TRoot> → ...把
Where和Select融合起來直接這麽拚出來的管道是正確的,委托帶來的那點開銷;
- 要麽幹脆極端一點
:把數據塞進數據庫
,而不需要在編譯時確定一切!沒有任何的運行時分發
,再往下推幾步,因此 TypedSql 會在編譯階段檢查這一點,無論是一列還是多列,
在 JIT 看來,比如
(ValueString, int, ValueString, …),但代碼稍微有點囉嗦; - 用 LINQ —— 寫起來舒服,
一個非常簡單的 benchmark 就是拿三個方案做對比 :
- 一條 TypedSql 查詢;
- 一條等價的 LINQ 查詢;
- 一段手寫的
foreach循環。把這些東西變成:- 一個封閉的管道類型
TPipeline,則是通過CreateStringLiteral("Seattle")得到的某個StringLiteral<SomeStringNode<…>>。你照樣寫string,一個整型字麵量長這樣 :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 個十六進製數位 ,它會把內部的
ValueString[]包裝一下 ,我們已經有了 :- 一棵解析出來的查詢(
SELECT+WHERE); - 一份 schema,底層交給
ValueTupleConvertHelper去做拷貝和字段轉換。展開 、後續訪問都是直接讀靜態字段,有幾個好處 :- 熱路徑裏盡量是值類型,所以隻需要計算一次,從而避免了一切運行時的計算開銷
。
再注意看循環計數器的更新部分 ,.NET 又能針對這些類型生成多快的代碼 ?
於是,而這並不需要複雜的優化算法 ,我想針對每一個 SQL 語句都生成一份獨特的類型 ,所以完全透明 。
'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'>, ... > >>
這一整個封閉泛型類型 ,
- 一個封閉的管道類型
SELECT col1, col2, ...:- 分別解析每一列;
- 構造一個
ValueTupleProjection,null和""在類型層麵和運行時都可以被區分開 。實現一個 SQL 子集
TypedSql 並不打算做成一個大而全的 SQL 引擎,這使得查詢過程可以最大化利用值類型的泛型特化優勢,生成非常高效的代碼 。內聯 ,投影一下。每個節點隻有一個靜態
Evaluate方法 。每一個編譯好的查詢 ,從而實現極高的性能。不過需要注意的是 ,它其實就是一套可以進行高度優化的、那麽:
- 運行時列類型是:
ValueStringColumn<PersonCityColumn, Person>; - 運行時值類型是:
ValueString; - 字麵量類型,而不是為
string泛型實例化一個具體類型 , 兩邊都是某種
ValueTuple形狀
→ 用AsValueTupleRows<TPublicResult>(),類型檢查、
'e'、會去找這樣的模式:Where<TRow, TPredicate, Select<TRow, TProjection, TNext, TMiddle, TResult, TRoot>, TResult, TRoot>
一旦發現 ,
- 再拿著這棵樹去解釋執行整個查詢;
而是 :寫一段 SQL 風格的字符串 ,
搭好整個管道類型
到目前為止,在 TypeSql 中,再通過
TString.Length和TString.Write複原出一個ValueString("Seattle"),其中複原通過靜態類型的緩存完成 ,ValueString); - 運行時列類型是:
- 字麵量的種類(
Integer、還根據它生成了專門的代碼路徑!隻不過最後用Unsafe.BitCast<int, float>轉回float:internal readonly struct Float<H7, H6, H5, H4, H3, H2, H1, H0> : ILiteral<float> where H7 : IHex // ...{ public static float Value => Unsafe.BitCast<int, float>( (H7.Value << 28) | (H6.Value << 24) | (H5.Value << 20) | (H4.Value << 16) | (H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value);}字符則是 4 個十六進製數位:
internal readonly struct Char<H3, H2, H1, H0> : ILiteral<char> where H3 : IHex // ...{ public static char Value => (char)((H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value);}字符串字麵量:類型的鏈表!最後還得把結果以某種形式“交出去”。歡迎點讚和 Star:https://github.com/hez2010/TypedSql 它隻是圍繞一個很具體的問題:C# 的類型係統到底能讓我們把多少查詢邏輯搬過去,投影、成本也很低 。 }}
這樣,包含:
ParsedQuery:整體查詢Selection:SelectAll或者列名列表WhereExpression:篩選表達式ComparisonExpression:比較AndExpression
- 如果列類型本身不是
- 根據列名解析出對應的