You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fixed BCP datetime decode truncation (0.3–0.9s value error). Mimicked datetime SqlClient rounding. Nested-loop join optimized as hash join, protected against regression with new observability mechanics visible from internal tests.
Copy file name to clipboardExpand all lines: CLAUDE.md
+3-4Lines changed: 3 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -103,9 +103,7 @@ Six scopes, one home each. **Add new state to whichever class matches its true s
103
103
The `*.Tests` and `*.Tests.EFCore` suites are the authoritative behavior contract. Notes below cover only probe-confirmed quirks, deviations from SQL Server, and non-obvious implementation rules. Per-feature deep-dives live under `docs/claude/` (see [Feature reference](#feature-reference) for the trigger-phrased index); short cross-cutting sections stay inline below.
104
104
105
105
### JOINs / APPLY
106
-
INNER / bare JOIN / LEFT [OUTER] / RIGHT [OUTER] / FULL [OUTER] / CROSS / CROSS APPLY / OUTER APPLY. Multi-table chains compose left-to-right. ON-predicate UNKNOWN excludes. APPLY = lateral form (right side re-executed per outer row, no ON clause). Comma-separated FROM (ANSI-89 syntax — `FROM a, b WHERE a.id = b.id`) parses as a sequence of explicit-join chains spliced with `JoinKind.Cross` joins; each comma starts a fresh chain via the same `ParseExplicitJoinChain` helper the JOIN-keyword loop calls, so any explicit JOINs *within* a chain bind before the cross-splice.
107
-
108
-
`JoinDriver` is a fold over `joins[]`: leftmost rowset → wrap with each join's operator → final enumerator (`Selection.Execution.Joins.cs`). INNER / CROSS / LEFT / CROSS APPLY / OUTER APPLY stream one upstream tuple at a time; RIGHT / FULL materialize `sources[level].Rows` into a list and track a `matched[]` bitmap across the entire upstream iteration so unmatched right rows can be emitted (with all prior slots NULL-filled) after upstream is exhausted. **RIGHT / FULL with a derived-table right side** materialize the lateral plan once via the enclosing-scope `outerResolver` (not the joined-tuple resolver), so non-correlated and outer-correlated derived tables work; lateral correlation to the left side is rejected because the derived-table parse doesn't wire the left-source snapshot resolver — left-side references raise Msg 207 ("Invalid column name") at runtime when `Reference.Run` hits the null outer resolver. Real SQL Server raises Msg 4104 at bind time for the same shape; different code, same end state. EF Core 10's LINQ `LeftJoin` / `RightJoin` operators translate to LEFT / RIGHT JOIN respectively and route through this pipeline; .NET 10 LINQ doesn't expose a `FullJoin` operator, so FULL OUTER JOIN is reachable only via raw SQL.
106
+
INNER / bare JOIN / LEFT [OUTER] / RIGHT [OUTER] / FULL [OUTER] / CROSS / CROSS APPLY / OUTER APPLY, multi-table chains (compose left-to-right), and ANSI-89 comma-FROM. ON-predicate UNKNOWN excludes. Equi-joins (`a.col = b.col`) take an O(L+R) hash path; non-equi / CROSS / APPLY / lateral-right fall back to streaming nested loops. EF Core's LINQ `LeftJoin` / `RightJoin` route through this pipeline (no LINQ `FullJoin`, so FULL is raw-SQL-only). See [`docs/claude/joins.md`](docs/claude/joins.md).
109
107
110
108
### Subqueries
111
109
`EXISTS` / `NOT EXISTS` (multi-column inner allowed); `expr [NOT] IN (SELECT ...)` (single inner column, Msg 116); scalar `(SELECT col FROM ...)` (single column, single-row Msg 512 per outer row, empty → typed NULL); `expr <op> {ANY|SOME|ALL} (SELECT col FROM ...)` quantified comparison with all six operators (`=` `<>` `<` `<=` `>` `>=`) plus T-SQL synonyms `!=` `!<` `!>`, predicate-only (SELECT-list usage raises Msg 102 at the operator, matching real SQL Server's grammar restriction); SOME aliases ANY. Empty inner: ALL vacuously true, ANY vacuously false (both independent of LHS NULL); non-empty inner with NULL on either side of any per-row compare taints to UNKNOWN per three-valued logic. All forms work correlated and non-correlated, arbitrary nesting depth. `UNION` / `UNION ALL` / `INTERSECT` / `EXCEPT` are legal inside every subquery context — derived tables in FROM, EXISTS, IN, ANY/ALL inners, scalar `(SELECT ...)`, CTE bodies — because subquery parsers route through `Selection.Parse` → `ParseQueryExpression`, which already drives the set-op chain. EF Core 7+'s TPC inheritance emit shape (UNION ALL of selects from each concrete table wrapped in a derived table) ships end-to-end through this path.
Comparison (Msg 402), ORDER BY/DISTINCT (Msg 306), and aggregates (Msg 8117 from MAX/MIN) all enforced.
140
138
141
139
### `SimulatedDbDataReader`
142
-
Full `DbDataReader` contract. Typed accessors read `SqlValue` directly via the cursor's indexer and unwrap via `As*` (no boxing); NULL on a typed accessor → `SqlNullValueException` matching SqlClient. `GetDateTime` covers Date/DateTime/SmallDateTime/DateTime2 (Date surfaces at midnight, `Kind=Unspecified`); `GetDecimal` covers Decimal/Numeric/Money/SmallMoney; `GetFieldValue<T>` short-circuits EF's `DateOnly`-over-`Date` and `TimeOnly`-over-`Time`. `GetOrdinal(name)` is two-pass linear scan (case-sensitive then case-insensitive — SqlClient's documented precedence). `HasRows` is a sticky bit. `GetChar(int)` always raises `InvalidCastException` (matches SqlClient).
140
+
Full `DbDataReader` contract. Typed accessors read `SqlValue` directly via the cursor's indexer and unwrap via `As*` (no boxing); NULL on a typed accessor → `SqlNullValueException` matching SqlClient. `GetDateTime` covers Date/DateTime/SmallDateTime/DateTime2 (Date surfaces at midnight, `Kind=Unspecified`). A **`datetime` value rounds to whole milliseconds at the ADO.NET boundary** (`DateTimeSqlType.RoundToClientMilliseconds`, applied in `GetDateTime` and `SqlValue.ToObject` — the latter also covers `GetValue` / `GetFieldValue` / output-parameter writeback) to match SqlClient's `.000`/`.003`/`.007` materialization; the value stays at full 1/300-second resolution for engine-internal comparison / arithmetic / re-encode, so only the client-facing surface rounds. `GetDecimal` covers Decimal/Numeric/Money/SmallMoney; `GetFieldValue<T>` short-circuits EF's `DateOnly`-over-`Date` and `TimeOnly`-over-`Time`. `GetOrdinal(name)` is two-pass linear scan (case-sensitive then case-insensitive — SqlClient's documented precedence). `HasRows` is a sticky bit. `GetChar(int)` always raises `InvalidCastException` (matches SqlClient).
143
141
144
142
### Feature reference
145
143
@@ -149,6 +147,7 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
0 commit comments