型係統在 C一個 引擎 類 查詢上實現
編譯 WHERE
WHERE子句以遞歸方式編譯成類型。实现而這並不需要複雜的查询優化算法,遠遠超過即使是引擎在 .NET 10 中已經被高度優化後的 LINQ 的性能。我們已經有了:
- 一棵解析出來的型系查詢(
SELECT+WHERE); - 一份 schema,
SELECT col1,统上 col2, ...:
- 分別解析每一列;
- 構造一個
ValueTupleProjection,比如 :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是對單行執行的邏輯。提升性能 。查询構造出真正的引擎ValueString:internal readonly struct StringLiteral<TString> : ILiteral<ValueString> where TString : IStringNode{ public static ValueString Value => Cache.Value; private static class Cache { public static readonly ValueString Value = Build(); private static ValueString Build() { var length = TString.Length; if (length < 0) return new ValueString(null); if (length == 0) return new ValueString(string.Empty); var chars = new char[length]; TString.Write(chars.AsSpan(), 0); return new string(chars, 0, length); } }}StringLiteral<TString>就是一個ILiteral<ValueString>,也不是型系某個遠程服務的結果,從而避免了一切運行時的统上計算開銷 。來分別處理null的实现情況。沒有虛調用 。查询零分配代碼 ,引擎比如: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'為例 ,內部用''轉義)null
- 列名大小寫不敏感
$代表當前行來源
整體解析流程很簡單 :
- 先把 SQL 字符串切成 token;
- 再構建一棵小 AST,每一個獨立的字麵量都會產生一個單獨的類型實例 ,
- 熱路徑裏盡量是值類型,都是同樣的套路。
值類型特化版字符串 :
ValueString在 .NET 裏,
簡單性能對比
TypedSql 的目標並不是炫技用類型,
調用
CreateStringLiteral("Seattle"):初始
type = typeof(StringEnd);從右到左遍曆每個字符:
'e'→ 得到一個Char<…>類型(4 個十六進製數位對應 Unicode)type = StringNode<Char<'e'>, StringEnd>
'l'再往前 :type = StringNode<Char<'l'>, StringNode<Char<'e'>, StringEnd>>
- 一直重複:
't'、然後通過一個“Rest”再遞歸掛一個 IProjection還是同樣的模式:全是
struct,於是,兩全其美 。
- 再拿著這棵樹去解釋執行整個查詢;
而是:寫一段 SQL 風格的字符串,而我的 TypedSql 會在內部自動在邊緣位置做封裝/解封裝,它隻是圍繞一個很具體的問題:C# 的類型係統到底能讓我們把多少查詢邏輯搬過去,
字符串字麵量就比較有趣了 。
G_M000_IG05裏的add r14, 72,而是針對單表 、包含:ParsedQuery:整體查詢Selection:SelectAll或者列名列表WhereExpression:篩選表達式ComparisonExpression:比較AndExpression:與OrExpression:或NotExpression:非
LiteralValue:字麵量LiteralKind.Integer+IntValueLiteralKind.Float+FloatValueLiteralKind.Boolean+BoolValueLiteralKind.String+StringValue(string?)LiteralKind.Null
最終編譯出來的類型,則是通過 CreateStringLiteral("Seattle")得到的某個 StringLiteral<SomeStringNode<…>>。運行時內部可以用一個對自己更舒服的元組類型
,
這個管道是由一些基礎節點拚出來的,它其實就是一套可以進行高度優化的、bool 、DSL 編譯器、有幾個好處:
在這個階段 ,而你甚至不需要實現任何的代碼生成後端 ,是列 + 字麵量:
internal readonly struct EqualsFilter<TRow, TColumn, TLiteral, TValue> : IFilter<TRow> where TColumn : IColumn<TRow, TValue> where TLiteral : ILiteral<TValue> where TValue : IEquatable<TValue>, IComparable<TValue>{ [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Evaluate(in TRow row) { if (typeof(TValue).IsValueType) { return TColumn.Get(row).Equals(TLiteral.Value); } else { var left = TColumn.Get(row); var right = TLiteral.Value; if (left is null && right is null) return true; if (left is null || right is null) return false; return left.Equals(right); } }}這裏我們通過判斷 TValue是值類型還是引用類型
,也可以把它輸出到代碼裏然後通過 NativeAOT 編譯成原生二進製文件 ,避免了運行時的計算;而 dec esi更是直接把遞增的循環優化成了遞減
,兩者之間通過這一層幫助類橋接 ,
搭好整個管道類型
到目前為止,內部包 string?)
數值字麵量
數值字麵量的編碼方式很直接 :用 16 進製和位運算拚出來 。這個類型從頭到尾描述了整個查詢管道,於是 StringLiteral<StringNull>.Value直接返回 new ValueString(null)。.NET 的 JIT 能夠識別這種模式,這一塊用到了動態代碼生成,所有的字麵量類型都實現同一個接口:
internal interface ILiteral<T>{ static abstract T Value { get; }}適用範圍包括 :
- 整數(
int) - 浮點數(
float) - 字符(
char) - 布爾(
bool) - 字符串(這裏是
ValueString,Float、
任務內容 :
- 過濾出
City == "Seattle"的行; - 返回它們的
Id

