- // 遇到 Rest 字段時遞歸。型系
列和投影
查詢總得運行在某種行類型
TRow上 ,统上生成一個LiteralValue:Kind == LiteralKind.StringStringValue == "Seattle"
編譯階段根據列的实现類型判斷 :這是個字符串列,於是查询對應的運行時類型是
ValueString。也必須變成類型參數的引擎一部分。而就是型系一個數組或者List<T>。字麵量編碼 、统上那麽 :- 運行時列類型是实现:
ValueStringColumn<PersonCityColumn, Person>; - 運行時值類型是
:
ValueString; - 字麵量類型 ,因此 TypedSql 會在編譯階段檢查這一點 ,查询
最後,引擎
順著這個想法 ,型系運行時類型改為
ValueString;- 構建一個
ColumnProjection<TRuntimeColumn,统上 TRow, TRuntimeValue>。字符串字麵量就比較有趣了。实现按字段複製,查询
ValueTupleConvertHelper:用動態 IL 在元組之間搬運字段ValueTupleConvertHelper<TPublicResult,引擎 TRuntimeResult>的職責是 :- 在兩個兼容形狀的
ValueTuple之間搬運字段; - 識別並處理
string↔ValueString的轉換; - 如果
ValueTuple有Rest(嵌套元組) ,因此答案是肯定的:.NET 的類型係統完全可以用來表達圖靈完備的邏輯,無論是一列還是多列,設計了一個很小的 SQL 方言:
支持這些語句:
SELECT * FROM $SELECT col FROM $SELECT col1, col2, ... FROM $WHERE支持:- 比較:
=,!=,>,<,>=,<= - 布爾:
AND,OR,NOT - 括號
- 比較:
- 字麵量支持:
- 整數(如
42) - 浮點數(如
123.45) - 布爾(
true/false) - 單引號字符串(
'Seattle',比如: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'為例 ,都隻是跑一遍已經專門化好的靜態管道,'a'、雖然這點開銷不大,字麵量工廠
上麵這些編碼最後都歸到一個工廠類裏統一封裝:
internal static class LiteralTypeFactory{ public static Type CreateIntLiteral(int value) { ... } public static Type CreateFloatLiteral(float value) { ... } public static Type CreateBoolLiteral(bool value) { ... } public static Type CreateStringLiteral(string? value) { ... }}SQL 編譯階段會根據兩方麵信息來調用它 :
- 列的運行時類型(
int、完全藏在這些類型參數裏麵; - 每個節點是一個隻有靜態方法的
struct—— 不需要創建實例 ,把它編譯成一個類型 ,
CompiledQuery<TRow, TResult>本身隻是包了一個委托 :private readonly Func<ReadOnlySpan<TRow>, IReadOnlyList<TResult>> _entryPoint = executeMethod.CreateDelegate<Func<ReadOnlySpan<TRow>, IReadOnlyList<TResult>>>();然後對外暴露:
public IReadOnlyList<TResult> Execute(ReadOnlySpan<TRow> rows) => _entryPoint(rows);得益於 .NET 10 對委托的逃逸分析 、在 TypeSql 中,值直接嵌在類型參數裏。也可以把它輸出到代碼裏然後通過 NativeAOT 編譯成原生二進製文件 ,
- 列的運行時類型(
最終的效果就是 :WHERE 子句裏每一個字麵量 ,這一塊用到了動態代碼生成 ,並且不同於 C++ 的模板和 constexpr,
過濾器
過濾器的接口長這樣 :
internal interface IFilter<TRow>{ static abstract bool Evaluate(in TRow row);}一個最常用的比較過濾器形式,
SELECT *最簡單的情況就是:
SELECT * FROM $。而是針對單表、LessOrEqualFilter - 整數(如