Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
1ae736f
Give back the pages a dropped table actually owns
ChrisJollyAU Sep 12, 2026
939fae9
Match ACE's narrow-row shapes: the fixed-region floor and the 255-row…
ChrisJollyAU Sep 12, 2026
aac3cc0
Identify the long-value chain stamp, and write and check it
ChrisJollyAU Sep 12, 2026
d9433ba
Fix ALTER COLUMN on an indexed column, and carry calculated columns t…
ChrisJollyAU Sep 12, 2026
e345bba
Identify page type 0x09, and give packed long values back
ChrisJollyAU Sep 12, 2026
b5147a6
Refuse the writes ACE does not accept
ChrisJollyAU Sep 12, 2026
e04fcb4
Clear the minor version byte on a format raise, and identify 0x6A
ChrisJollyAU Sep 13, 2026
5695194
Tidy the format spec down to what the format is
ChrisJollyAU Sep 14, 2026
cc118d3
Remove dead code found by code coverage
ChrisJollyAU Sep 14, 2026
b925c6d
Skip the format versions the installed ACE cannot open
ChrisJollyAU Sep 14, 2026
f676bf9
Serialize LibRed.Core.AccessTests through an ACE collection
ChrisJollyAU Sep 14, 2026
e5b9041
Allocate through page 0's global maps and release freed pages at close
ChrisJollyAU Sep 15, 2026
5d765a0
Translate Math.Max and Math.Min to GREATEST and LEAST in extended mode
ChrisJollyAU Sep 15, 2026
7528739
Accept CLUSTERED and NONCLUSTERED in constraint clauses, as ACE does
ChrisJollyAU Sep 15, 2026
f6bbb67
Resolve REFERENCES to the parent's primary key and accept a trailing …
ChrisJollyAU Sep 15, 2026
fa8076d
Match ACE's operator semantics in the LibRed evaluator
ChrisJollyAU Sep 16, 2026
dc1c909
Match ACE's built-in function semantics in the LibRed evaluator
ChrisJollyAU Sep 16, 2026
d876138
Return pooled connections under the key they are looked up by
ChrisJollyAU Sep 17, 2026
66146d4
Write a time with a plain space before AM/PM on every platform
ChrisJollyAU Sep 17, 2026
0249021
Report why a statement was refused in the references and identity par…
ChrisJollyAU Sep 17, 2026
b1ff6c0
Give the lock manager tests their own threads
ChrisJollyAU Sep 17, 2026
d006028
Expect a plain space before AM/PM in the ampersand time test
ChrisJollyAU Sep 17, 2026
0f39c2a
Skip the BIGINT identity parity case on an ACE without BIGINT
ChrisJollyAU Sep 17, 2026
a5d585a
Return every value in its column's declared type, and type bitwise re…
ChrisJollyAU Sep 17, 2026
60661a2
Match ACE's multi-table UPDATE and DELETE
ChrisJollyAU Sep 17, 2026
2373693
Read ORDER BY n as the nth output column, as ACE does
ChrisJollyAU Sep 17, 2026
fe7a4f7
Read CCur and CDec arguments exactly
ChrisJollyAU Sep 17, 2026
ef5e32c
Add windowed aggregates, NTILE, PERCENT_RANK, CUME_DIST, LAG and LEAD
ChrisJollyAU Sep 17, 2026
68233b5
Complete the standard's window functions, frames and ordered-set aggr…
ChrisJollyAU Sep 18, 2026
ce99d6b
Trim the ideographic space as ACE does, and add DATALENGTH
ChrisJollyAU Sep 18, 2026
8913b1b
Keep DATETIME2's sub-millisecond ticks through comparison and DateTim…
ChrisJollyAU Sep 18, 2026
8e6fe87
Release abandoned DAO and OLE DB objects while no test is inside ACE
ChrisJollyAU Sep 18, 2026
4b6ac73
Drop the answered DAO probes and keep the surveys out of ordinary runs
ChrisJollyAU Sep 18, 2026
73730ed
Declare DATEDIFF('ms') as the Int64 it returns
ChrisJollyAU Sep 18, 2026
6ca3387
Keep Currency results to four places and in range, and let MOD reach …
ChrisJollyAU Sep 18, 2026
300cae0
Add CLngLng, VBA's LongLong conversion
ChrisJollyAU Sep 18, 2026
c408953
Type parameters by their values, and move a date by a time-span param…
ChrisJollyAU Sep 18, 2026
8b0447f
Declare Round, Abs, Int, Fix and Sgn, and keep a written decimal a De…
ChrisJollyAU Sep 18, 2026
daac75b
Translate Math.Floor to INT and Math.Ceiling to -INT(-x)
ChrisJollyAU Sep 19, 2026
95d680d
Fix Math.Atan2, Asin and Acos translations
ChrisJollyAU Sep 19, 2026
79e1a37
Add the standard SQL math functions
ChrisJollyAU Sep 19, 2026
fd5b97c
Reorganise the function catalog into Access and extended functions
ChrisJollyAU Sep 19, 2026
e230ca3
Translate Math to the standard math functions in extended mode
ChrisJollyAU Sep 19, 2026
2d68ab0
Bump version to 11.0.0-alpha.3
ChrisJollyAU Sep 19, 2026
2fa5a05
Lay out and rewrite table definition chains as ACE does
ChrisJollyAU Sep 19, 2026
70e709b
Drop memo and OLE columns
ChrisJollyAU Sep 19, 2026
47703ca
Rewrite a parent's definition as ACE does when a relationship is added
ChrisJollyAU Sep 19, 2026
87d6b62
Record each relationship as its own catalog object
ChrisJollyAU Sep 19, 2026
fc4b7ad
Keep index statistics as ACE does, and refuse OLE columns in indexes
ChrisJollyAU Sep 19, 2026
4c55dbe
Let the arity test allow LOG's base argument
ChrisJollyAU Sep 19, 2026
d721e3b
Leave the OS cache to write committed pages, as ACE does
ChrisJollyAU Sep 19, 2026
4a0e2a5
Keep the file's length in the shared page cache
ChrisJollyAU Sep 19, 2026
091e9fa
Read the cached file length without the page-cache lock
ChrisJollyAU Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ It is kept in sync with `CLAUDE.md`; if you change guidance in one, check whethe

EntityFrameworkCore.Jet is an EF Core provider for Microsoft Jet/ACE databases (Microsoft Access `.mdb`/`.accdb` files). The **Jet** provider runs **Windows only** and bridges EF Core to the Access database engine via either ODBC or OLE DB. Alongside it, **LibRed** (also in this repo, on `master`) is a from-scratch managed engine that reads/writes the file format directly and is **cross-platform** — see the LibRed section below.

Current version: `11.0.0-alpha.1` (`Version.props`) targeting EF Core 11 and `net11.0`; `global.json` pins the 11.0.100 RC1 SDK with `rollForward: latestFeature`. The test projects use **xunit v3**.
Current version: `11.0.0-alpha.3` (`Version.props`) targeting EF Core 11 and `net11.0`; `global.json` pins the 11.0.100 RC1 SDK with `rollForward: latestFeature`. The test projects use **xunit v3**.

### Which layer am I touching?

Expand Down Expand Up @@ -211,7 +211,9 @@ contract.
> underneath us. It did: dotnet/runtime#130566 (.NET 11 preview 7) dropped `Convert.ToDecimal`'s 15-significant-digit
> rounding, which came from OA's own `VarDecFromR8` and had been stable since the 1990s. That turned
> `SUM(ROUND(UnitPrice, 2))` into `58.600000000000001421085471520`. The fix was to own it:
> `src/EFCore.Jet.Data/JetDecimalConverter.cs`. **When a long-stable conversion suddenly misbehaves with no code
> `src/Shared/Data/JetDecimalConverter.cs` (compiled into both EFCore.Jet.Data and LibRed.Core; every LibRed
> assembly uses the Core copy, and any double/float that can reach a decimal goes through its `ToDecimal`, never
> `Convert.ToDecimal` or a cast). **When a long-stable conversion suddenly misbehaves with no code
> change on our side, suspect the runtime's OA-era compatibility behaviour before suspecting the provider.**

## LibRed — Native Managed Engine
Expand Down
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

EntityFrameworkCore.Jet is an EF Core provider for Microsoft Jet/ACE databases (Microsoft Access `.mdb`/`.accdb` files). The **Jet** provider runs **Windows only** and bridges EF Core to the Access database engine via either ODBC or OLE DB. Alongside it, **LibRed** (also in this repo, on `master`) is a from-scratch managed engine that reads/writes the file format directly and is **cross-platform** — see the LibRed section below.

Current version: `11.0.0-alpha.1` (`Version.props`) targeting EF Core 11 and `net11.0`; `global.json` pins the 11.0.100 RC1 SDK with `rollForward: latestFeature`. The test projects use **xunit v3**.
Current version: `11.0.0-alpha.3` (`Version.props`) targeting EF Core 11 and `net11.0`; `global.json` pins the 11.0.100 RC1 SDK with `rollForward: latestFeature`. The test projects use **xunit v3**.

### Which layer am I touching?

Expand Down Expand Up @@ -223,7 +223,9 @@ contract.
> underneath us. It did: dotnet/runtime#130566 (.NET 11 preview 7) dropped `Convert.ToDecimal`'s 15-significant-digit
> rounding, which came from OA's own `VarDecFromR8` and had been stable since the 1990s. That turned
> `SUM(ROUND(UnitPrice, 2))` into `58.600000000000001421085471520`. The fix was to own it:
> `src/EFCore.Jet.Data/JetDecimalConverter.cs`. **When a long-stable conversion suddenly misbehaves with no code
> `src/Shared/Data/JetDecimalConverter.cs` (compiled into both EFCore.Jet.Data and LibRed.Core; every LibRed
> assembly uses the Core copy, and any double/float that can reach a decimal goes through its `ToDecimal`, never
> `Convert.ToDecimal` or a cast). **When a long-stable conversion suddenly misbehaves with no code
> change on our side, suspect the runtime's OA-era compatibility behaviour before suspecting the provider.**

## LibRed — Native Managed Engine
Expand Down
2 changes: 1 addition & 1 deletion Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
-->
<VersionPrefix>11.0.0</VersionPrefix>
<PreReleaseVersionLabel>alpha</PreReleaseVersionLabel>
<PreReleaseVersionIteration>2</PreReleaseVersionIteration>
<PreReleaseVersionIteration>3</PreReleaseVersionIteration>

<!--
The following properties will automatically be set by CI builds when appropriate:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ public class JetMathTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMe
{
return method.Name switch
{
// Arccos(X) = Atn(-X / Sqr(-X * X + 1)) + 2 * Atn(1)
// Arccos(X) = 2 * Atn(-X / (Sqr(-X * X + 1) + 1)) + 2 * Atn(1)
nameof(Math.Acos) => _sqlExpressionFactory.Add(
_sqlExpressionFactory.Constant(Math.Atan(1) * 2),
_sqlExpressionFactory.Function(
_sqlExpressionFactory.Multiply(_sqlExpressionFactory.Function(
"ATN",
[
_sqlExpressionFactory.Divide(
_sqlExpressionFactory.Negate(arguments[0]),
Translate(
_sqlExpressionFactory.Add(Translate(
null,
method.DeclaringType == typeof(MathF)
? typeof(MathF).GetRuntimeMethod(nameof(MathF.Sqrt), [typeof(float)])!
Expand All @@ -147,20 +147,20 @@ public class JetMathTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMe
)
],
logger
)!
)!, _sqlExpressionFactory.Constant(1d))
)
],
true,
[true],
method.ReturnType)),
method.ReturnType), _sqlExpressionFactory.Constant(2d))),

// Arcsin(X) = Atn(X / Sqr(-X * X + 1))
nameof(Math.Asin) => _sqlExpressionFactory.Function(
// Arcsin(X) = 2 * Atn(X / (Sqr(-X * X + 1) + 1))
nameof(Math.Asin) => _sqlExpressionFactory.Multiply(_sqlExpressionFactory.Function(
"ATN",
[
_sqlExpressionFactory.Divide(
arguments[0],
Translate(
_sqlExpressionFactory.Add(Translate(
null,
method.DeclaringType == typeof(MathF)
? typeof(MathF).GetRuntimeMethod(nameof(MathF.Sqrt), [typeof(float)])!
Expand All @@ -177,12 +177,12 @@ public class JetMathTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMe
)
],
logger
)!
)!, _sqlExpressionFactory.Constant(1d))
)
],
true,
[true],
method.ReturnType),
method.ReturnType), _sqlExpressionFactory.Constant(2d)),

// Logn(x) = Log(x) / Log(n)
nameof(Math.Log10) => _sqlExpressionFactory.Divide(
Expand All @@ -196,10 +196,12 @@ public class JetMathTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMe
_sqlExpressionFactory.Function("LOG", [arguments[1]], true, [true], method.ReturnType)
),

nameof(Math.Floor) => CreateFix(arguments, method.ReturnType),
nameof(Math.Ceiling) => CreateCeiling(arguments, method.ReturnType),
nameof(Math.Floor) => CreateFloor(arguments[0], method.ReturnType),
nameof(Math.Ceiling) => CreateCeiling(arguments[0], method.ReturnType),

nameof(Math.Atan2) => _sqlExpressionFactory.Function(
// Atan2(Y, X) = IIf(X = 0, Sgn(Y) * Pi / 2, Atn(Y / X) + IIf(X < 0, IIf(Y < 0, -Pi, Pi), 0))
nameof(Math.Atan2) => _sqlExpressionFactory.Case([new CaseWhenClause(_sqlExpressionFactory.Equal(arguments[1], _sqlExpressionFactory.Constant(0d)), _sqlExpressionFactory.Multiply(_sqlExpressionFactory.Function("SGN", [arguments[0]], true, [true], method.ReturnType), _sqlExpressionFactory.Constant(Math.PI / 2)))],
_sqlExpressionFactory.Add(_sqlExpressionFactory.Function(
"ATN",
[
_sqlExpressionFactory.Divide(
Expand All @@ -209,7 +211,7 @@ public class JetMathTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMe
],
true,
[true],
method.ReturnType),
method.ReturnType), _sqlExpressionFactory.Case([new CaseWhenClause(_sqlExpressionFactory.LessThan(arguments[1], _sqlExpressionFactory.Constant(0d)), _sqlExpressionFactory.Case([new CaseWhenClause(_sqlExpressionFactory.LessThan(arguments[0], _sqlExpressionFactory.Constant(0d)), _sqlExpressionFactory.Constant(-Math.PI))], _sqlExpressionFactory.Constant(Math.PI)))], _sqlExpressionFactory.Constant(0d)))),

nameof(double.DegreesToRadians) => _sqlExpressionFactory.Multiply(arguments[0], _sqlExpressionFactory.Divide(_sqlExpressionFactory.Constant(Math.PI), _sqlExpressionFactory.Constant(180))),

Expand Down Expand Up @@ -274,40 +276,25 @@ public class JetMathTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMe
return null;
}

private SqlExpression CreateCeiling(IReadOnlyList<SqlExpression> arguments, Type methodReturnType)
{
SqlFunctionExpression fixExpression = (SqlFunctionExpression)CreateFix(arguments, methodReturnType);
var addoneexp = _sqlExpressionFactory.Add(fixExpression, _sqlExpressionFactory.Constant(1));
return _sqlExpressionFactory.Case(
[
new CaseWhenClause(
_sqlExpressionFactory.Equal(
fixExpression,
arguments[0]),
fixExpression)
],
addoneexp);
}

private SqlExpression CreateFix(IReadOnlyList<SqlExpression> arguments, Type methodReturnType)
{
var typeMapping = arguments.Count == 1
? ExpressionExtensions.InferTypeMapping(arguments[0])
: ExpressionExtensions.InferTypeMapping(arguments[0], arguments[1]);
// Math.Floor rounds towards negative infinity, which is Access's INT - not FIX, which rounds towards zero
// (that is Math.Truncate) and so gave Floor(-1.5) as -1. Math.Ceiling is the mirror image, -INT(-x): the old
// IIF(FIX(x) = x, FIX(x), FIX(x) + 1) was one too high for any negative non-integer, and +1 for anything
// between -1 and 0. Verified against ACE and LibRed over -2.7, -1.5, -1, -0.5, 0, 1.5 and 2, Double and
// Currency; both keep the argument's type, and -INT(-x) keeps .NET's -0 for Math.Ceiling(-0.5).
private SqlExpression CreateFloor(SqlExpression argument, Type methodReturnType)
=> CreateInt(argument, methodReturnType);

var newArguments = new SqlExpression[arguments.Count];
newArguments[0] = _sqlExpressionFactory.ApplyTypeMapping(arguments[0], typeMapping);

if (arguments.Count == 2)
{
newArguments[1] = _sqlExpressionFactory.ApplyTypeMapping(arguments[1], typeMapping);
}
private SqlExpression CreateCeiling(SqlExpression argument, Type methodReturnType)
=> _sqlExpressionFactory.Negate(CreateInt(_sqlExpressionFactory.Negate(argument), methodReturnType));

private SqlExpression CreateInt(SqlExpression argument, Type methodReturnType)
{
var typeMapping = ExpressionExtensions.InferTypeMapping(argument);
return _sqlExpressionFactory.Function(
"FIX",
newArguments,
"INT",
[_sqlExpressionFactory.ApplyTypeMapping(argument, typeMapping)],
nullable: true,
argumentsPropagateNullability: newArguments.Select(_ => true).ToArray(),
argumentsPropagateNullability: [true],
methodReturnType,
typeMapping);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ namespace EntityFrameworkCore.Jet.Query.Internal;
/// alone: <c>d == default</c> must keep matching the epoch a default was stored as.
/// </para>
/// <para>
/// This has to run in <c>JetParameterBasedSqlProcessor</c> rather than in the compile-time
/// <c>JetDateTimeExpressionVisitor</c>, because the value normally arrives as a parameter and query
/// This has to run in <c>JetParameterBasedSqlProcessor</c> rather than at query compilation, because
/// the value normally arrives as a parameter and query
/// compilation is cached across invocations, so no value is available there. Nor can it be deferred to
/// a value-independent clamp in the emitted SQL: by then the DateTime type mapping has already replaced
/// 0001-01-01 with the epoch, so the clamp would test the substituted value and never fire.
Expand Down
Loading
Loading