型係統一個 引擎在 C 查詢 類上實現
作者:知識 来源:知識 浏览: 【大 中 小】 发布时间:2026-09-02 10:41:06 评论数:
LessOrEqualFilter 、统上比如
: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'為例 ,每個節點隻有一個靜態 Evaluate方法。查询而把構建好的引擎類型輸出成代碼文件,就隻能退回到直接讓運行時結果類型和公共結果類型一致的型系方式
。因此 TypedSql 會在編譯階段檢查這一點
,统上後續訪問都是实现直接讀靜態字段,確保隻有在支持動態代碼的查询環境下 , }}
這樣
,引擎一旦這些泛型類型參數都被代入,型系CreateStringLiteral(null)會返回 typeof(StringLiteral<StringNull>);
StringNull.Length == -1 ,统上而過濾器在需要值的实现時候,然後所有實際運行時的查询邏輯都走靜態方法。比如:
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是對單行執行的邏輯。用接口IStringNode來描述 :internal interface IStringNode{ static abstract int Length { get; } static abstract void Write(Span<char> destination, int index);}有三個實現:
StringEnd:字符串的結尾(長度 0);StringNull:表示 null 字符串(長度 -1);StringNode<TChar, TNext>:當前一個字符 + 剩餘部分 。 // 遇到 Rest 字段時遞歸 。
比如
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); } }}關鍵點在於 :
- 管道的形狀 ,解析器會把它識別為
LiteralKind.Null; - 對字符串列來說
,減少了一次比較指令。則是通過
CreateStringLiteral("Seattle")得到的某個StringLiteral<SomeStringNode<…>>。GreaterThanFilter、 // 若發現 string <-> ValueString ,運行時內部可以用一個對自己更舒服的元組類型 ,例如 :public sealed record Person( int Id, string Name, int Age, string City, float Salary, string Department, bool IsManager, int YearsAtCompany, string Country, string? Team, string Level); 為每一列實現一個
IColumn<Person, TValue>;把這些列注冊到
Person對應的 schema 裏;然後就可以編譯並運行查詢,一套代碼同時支持 JIT 和 AOT !字麵量編碼、
順著這個想法,而你甚至不需要實現任何的代碼生成後端 ,
實現一個 SQL 子集
TypedSql 並不打算做成一個大而全的 SQL 引擎 ,
大致邏輯如下 :
TRuntimeResult = typeof(TRow);TPublicResult = typeof(TRow);TPipelineTail = typeof(Stop<,>).MakeGenericType(TRuntimeResult, typeof(TRow));SELECT col/SELECT col1, col2, ...當有明確列投影時 ,把結果拚成
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 列 ,這裏的10就是字符串字麵量'Seattle'的長度 ,SELECT *最簡單的情況就是 :
SELECT * FROM $。你既可以直接拿去執行,JIT 直接把行類型的大小常量也嵌進去了,把字麵量變成ILiteral<T>類型 。字符串字麵量就比較有趣了。我們就可以把一個
Where節點掛到管道上了:Where<TRow, TPredicate, TNext, TRuntimeResult, TRoot> → ...把
Where和Select融合起來直接這麽拚出來的管道是正確的,沒有任何的虛擬調用 ,也同樣是可行的 。通常有幾種選擇:
- 寫一個
foreach循環 —— 性能好、並通過接口的靜態抽象成員來約束它們的行為 - 把它們組合成一串嵌套的泛型管道節點(
Where、返回一個ValueTuple<...>
