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
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -77,7 +77,7 @@ Heavy-hitters someone might assume work but don't. Source and `git log` are the
77
77
-`decimal` / `numeric` values are backed by .NET `decimal`, so values requiring more than 28 significant digits aren't modeled (the type declarations up through `decimal(38, *)` are accepted so storage byte-width still matches SQL Server). `float` text formatting uses .NET's `G15`/`G7` conventions rather than SQL Server's exact `1e+015`-style scientific layout.
78
78
-`CONVERT` / `TRY_CONVERT` style-code coverage. Only styles `0`, `120`, and `121` are wired up for date-like sources targeting a character string (the EF Core code-generation defaults). Other style numbers raise Msg 281; money / float / binary style codes and `CONVERT(date, str, 103)`-style date-parsing styles aren't modeled.
79
79
- Boolean combinators in WHERE / MERGE-ON / CHECK predicates: `AND` / `OR` / `NOT`, parenthesized groupings, `IS [NOT] NULL`, and `[NOT] IN (literal, literal, ...)` all parse with standard SQL precedence (AND > OR; NOT highest). The pattern `where (arith_expr) cmp rhs` (parens-around-arithmetic as the left side of a comparison) is the one shape the simulator's parser doesn't accept; SQL Server does. `BooleanExpression.Run` returns `bool?` (three-valued: true / false / UNKNOWN); WHERE / MERGE-ON treat UNKNOWN as exclude, CHECK treats UNKNOWN as pass — matching SQL Server. `IS NULL` definitively resolves UNKNOWN to true/false (it's the canonical way to test nullability without falling through tri-state).
80
-
- Subqueries: `EXISTS (SELECT ...)`, `[NOT] EXISTS`, `expr [NOT] IN (SELECT ...)` parse as boolean atoms in WHERE / HAVING / CHECK; both correlated and non-correlated forms work, with arbitrary outer-scope nesting depth. EXISTS counts rows only (multi-column inner SELECT allowed); `IN (SELECT ...)` requires exactly one inner column (Msg 116) and follows the same NULL semantics as the literal-list IN (NULL row in the subquery → UNKNOWN unless a non-NULL match wins first). Column resolution honors qualifiers (`alias.col` / `tableName.col`) so a correlated reference to an outer column with the same name as an inner column works correctly. The non-correlated case re-executes the inner SELECT per outer row (no result caching yet — fidelity over performance for now). `Selection.Parse` returns a deferred plan; `Selection.Execute(outerResolver)` materializes results, so the same plan re-runs against different outer rows. **Not modeled**: scalar subqueries (`WHERE col = (SELECT ...)` and projection-slot subqueries — these need cardinality enforcement / Msg 512), `ANY` / `SOME` / `ALL` quantifiers, `UNION` / `UNION ALL` inside a subquery body. Derived tables in FROM (already supported) don't see outer scope (no APPLY / lateral).
80
+
- Subqueries: `EXISTS (SELECT ...)` / `[NOT] EXISTS` and `expr [NOT] IN (SELECT ...)` parse as boolean atoms in WHERE / HAVING / CHECK; scalar subqueries `(SELECT col FROM ...)` parse anywhere an expression is allowed (projection, WHERE comparison, arithmetic operand). All forms work both correlated and non-correlated, with arbitrary outer-scope nesting depth. EXISTS counts rows only (multi-column inner allowed); `IN (SELECT ...)` and scalar subqueries require exactly one inner column (Msg 116). Scalar subqueries also enforce single-row cardinality at runtime (Msg 512, fired per outer row for correlated cases); empty result → NULL of the inner's projected type. NULL semantics in `IN (SELECT ...)` mirror the literal-list IN (NULL row → UNKNOWN unless a non-NULL match wins first). Column resolution honors qualifiers (`alias.col` / `tableName.col`) so a correlated reference to an outer column with the same name as an inner column works correctly. The inner plan re-executes per outer row (no result caching yet — fidelity over performance for now). `Selection.Parse` returns a deferred plan; `Selection.Execute(outerResolver)` materializes results, so the same plan re-runs against different outer rows. **Not modeled**: `ANY` / `SOME` / `ALL` quantifiers, `UNION` / `UNION ALL` inside a subquery body, row-constructor `IN ((1,2), (3,4))`. Derived tables in FROM (already supported) don't see outer scope (no APPLY / lateral).
81
81
-`LIKE``COLLATE` override. The default collation (case-insensitive, Latin1_General-shaped) is what every `LIKE` runs under; explicit `COLLATE` clauses on the predicate aren't parsed yet.
82
82
- Cross-category `Promote` for integer ↔ string. Only CAST works for that pair.
83
83
- EF Core compatibility: the `SqlServerSimulator.EFCore` adapter (`UseSqlServerSimulator(...)`) covers the seven `SqlParameter`-downcast pairs — `DateOnly → date`, `DateTime → date`, `DateTime → smalldatetime`, `TimeOnly → time(N)`, `TimeSpan → time(N)`, `decimal → money`, `decimal → smallmoney`. Without the adapter (plain `UseSqlServer`), those mappings still throw at SaveChanges as before. The MAX-string family (default-mapped `string` → `nvarchar(max)`, `[Column(TypeName = "varchar(max)")]`, `[Column(TypeName = "varbinary(max)")]`) flows through plain `UseSqlServer` without needing the adapter. Other type pairs the SqlServer provider supports but aren't modeled here (e.g. `hierarchyid`, `geography`, `geometry`, `rowversion`) are not bridged.
varmatched=ReadIntColumn(connection.CreateCommand("select x from t where x = (select max(x) from t)"));
327
+
CollectionAssert.AreEqual(newint?[]{15},matched);
328
+
}
329
+
330
+
[TestMethod]
331
+
publicvoidScalar_NullValueInRow_PropagatesNull()
332
+
{
333
+
// Single-row inner with a NULL column value flows through normally.
334
+
AreEqual(DBNull.Value,newSimulation().ExecuteScalar("select (select cast(null as int)) + 5"));
335
+
}
336
+
337
+
[TestMethod]
338
+
publicvoidScalar_MultiRow_RaisesMsg512()
339
+
{
340
+
varsimulation=newSimulation();
341
+
_=simulation.ExecuteNonQuery("create table t (x int)");
342
+
_=simulation.ExecuteNonQuery("insert into t values (1), (2)");
343
+
344
+
varex=Throws<DbException>(()=>_=simulation.ExecuteScalar("select (select x from t)"));
345
+
AreEqual("512",ex.Data["HelpLink.EvtID"]);
346
+
AreEqual("Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.",ex.Message);
Copy file name to clipboardExpand all lines: SqlServerSimulator/Parser/Expression.cs
+29-1Lines changed: 29 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -71,7 +71,7 @@ public static Expression Parse(ParserContext context)
71
71
// surrounding loop hands the call shape off to ResolveBuiltIn.
72
72
ReservedKeyword{Keyword:Keyword.Left or Keyword.Right or Keyword.Convert or Keyword.Try_Convert or Keyword.Coalesce}reserved=>newReference(reserved.ToString()),
new("Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.",512,16,1);
279
+
271
280
internalstaticSimulatedSqlExceptionMissingEndCommentMark()=>new("Missing end comment mark '*/'.",113,15,1);
272
281
273
282
internalstaticSimulatedSqlExceptionMustDeclareScalarVariable(stringname)=>new($"Must declare the scalar variable \"@{name}\".",137,15,2);
0 commit comments