Skip to content

Commit de25c73

Browse files
committed
Add AGENTS.md, refactor postprocessor, skip collation tests
Added AGENTS.md with project and agent guidance. Refactored JetLiftOrderByPostprocessor.Visit for clarity. Removed unused using in JetDataReader.cs. Marked collation-related migration tests as skipped and updated expected SQL output, since Jet does not support collation.
1 parent 10e11b3 commit de25c73

4 files changed

Lines changed: 112 additions & 13 deletions

File tree

AGENTS.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
4+
5+
## What This Is
6+
7+
EntityFrameworkCore.Jet is an EF Core provider for Microsoft Jet/ACE databases (Microsoft Access `.mdb`/`.accdb` files). It runs **Windows only** and bridges EF Core to the Access database engine via either ODBC or OLE DB.
8+
9+
Current version: `10.0.x` targeting EF Core 10 and `net10.0`.
10+
11+
## Build
12+
13+
```powershell
14+
dotnet build EFCore.Jet.sln
15+
```
16+
17+
Assemblies are **strong-name signed** using `Key.snk`. `TreatWarningsAsErrors=True` is set globally — fix all warnings.
18+
19+
### Local EFCore Repository (optional)
20+
21+
To develop against a local EF Core build instead of NuGet packages, copy `Development.props.sample` to `Development.props` and set `LocalEFCoreRepository` to your EF Core checkout. That local build must be compiled with `AssemblyVersion=10.0.0.0` to avoid binding conflicts.
22+
23+
## Tests
24+
25+
Tests require a real Microsoft Access driver installed (ODBC or OLE DB) and an actual `.accdb` file — no mocks. The connection string is configured via:
26+
- `test/EFCore.Jet.FunctionalTests/config.json` (OLE DB example present)
27+
- `test/EFCore.Jet.Tests/config.json` (bare filename; picks up default provider)
28+
- Or env var `EFCoreJet_DefaultConnection`
29+
30+
**Run all tests** (requires x86 or x64 matching your driver bitness):
31+
32+
```powershell
33+
dotnet test EFCore.Jet.sln --configuration Debug
34+
```
35+
36+
**Run a single test class:**
37+
38+
```powershell
39+
dotnet test test\EFCore.Jet.FunctionalTests\EFCore.Jet.FunctionalTests.csproj --filter "FullyQualifiedName~NorthwindQueryJetTest"
40+
```
41+
42+
**Run a single test method:**
43+
44+
```powershell
45+
dotnet test test\EFCore.Jet.FunctionalTests\EFCore.Jet.FunctionalTests.csproj --filter "FullyQualifiedName=EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindQueryJetTest.Where_simple"
46+
```
47+
48+
Tests run in **fixed order by default** (`FIXED_TEST_ORDER` compile constant). All tests lock culture to `en-US` via a module initializer.
49+
50+
Tests that require features Jet doesn't support are marked `[Fact(Skip = "Unsupported by JET: ...")]` — see `SkipMessages.txt` for the catalog of known unsupported patterns.
51+
52+
## Project Structure
53+
54+
```
55+
src/
56+
EFCore.Jet.Data/ ADO.NET driver — JetConnection, JetCommand, JetDataReader,
57+
schema management, DUAL table simulation, connection pooling
58+
EFCore.Jet/ EF Core provider — query pipeline, migrations, scaffolding,
59+
type mappings, value generation, conventions
60+
EFCore.Jet.Odbc/ Provider factory for ODBC data access
61+
EFCore.Jet.OleDb/ Provider factory for OLE DB data access
62+
Shared/ Shared source files compiled into multiple src projects
63+
64+
test/
65+
EFCore.Jet.Data.Tests/ Unit tests for the ADO.NET driver layer
66+
EFCore.Jet.FunctionalTests/ EF Core specification tests (adapted from EF Core's own suite)
67+
EFCore.Jet.Tests/ Additional functional tests
68+
EFCore.Jet.IntegrationTests/ Integration scenario tests
69+
JetProviderExceptionTests/ Exception-path tests
70+
Shared/ Shared test infrastructure (xunit framework customizations,
71+
test orderers, conditional test attributes)
72+
```
73+
74+
## Architecture: Two-Layer Design
75+
76+
**Layer 1 — `EFCore.Jet.Data`** wraps the raw ODBC/OLE DB driver:
77+
- `JetConnection` detects whether the connection string is ODBC or OLE DB and delegates to the appropriate inner `DbConnection`.
78+
- `JetCommand` rewrites SQL at runtime: handles `SELECT SKIP`, emulates `@@ROWCOUNT`, rewrites `TOP @param`, parses `IF NOT EXISTS ... THEN ...` syntax, and intercepts stored-procedure creation.
79+
- `JetConfiguration` holds global settings: `TimeSpanOffset` (Jet has no TimeSpan; dates are offset from 1899-12-30), `CustomDualTableName`, `IntegerNullValue`, `UseConnectionPooling`.
80+
- Schema operations (create/drop database, list tables) have three implementations: ADOX, DAO, and Precise, selected based on available COM libraries.
81+
82+
**Layer 2 — `EFCore.Jet`** is the EF Core provider:
83+
- `JetServiceCollectionExtensions.AddEntityFrameworkJet()` registers all provider services.
84+
- `JetQuerySqlGenerator` extends `QuerySqlGenerator` to produce Jet-compatible SQL — converts `CAST` to Jet VBA functions (`CBOOL`, `CINT`, `CLNG`, etc.), handles boolean/numeric null semantics.
85+
- `JetQueryTranslationPostprocessor` applies Jet-specific query rewrites in order: skip/take transformation → base postprocessing → optional millisecond support → ORDER BY lifting. `JetSkipTakePostprocessor` emulates `SKIP`/`OFFSET` since Jet only supports `SELECT TOP n`.
86+
- `JetMigrationsSqlGenerator` generates DDL for Access (no `ALTER COLUMN`, limited constraint support).
87+
- `JetHistoryRepository` implements migration locking via a `__EFMigrationsLock` table with `LockReleaseBehavior.Explicit`.
88+
- `JetRelationalConnection` creates an "empty" (masterless) connection for database creation/drop operations.
89+
90+
## Key Jet SQL Constraints
91+
92+
These shape much of the query pipeline complexity:
93+
- No `OFFSET` — emulated via subquery or `TOP`+skip in the data layer
94+
- `SELECT TOP n` only supports a literal integer, not a parameter (rewritten at command level)
95+
- Subqueries in `SELECT` list are limited; scalar subqueries only work in `FROM`
96+
- No parallel transactions (OLE DB)
97+
- No millisecond precision in `DateTime`
98+
- `CROSS JOIN` and mixed `JOIN`/comma syntax must be ordered correctly
99+
- Booleans stored as `-1`/`0` (numeric), not `TRUE`/`FALSE`
100+
- `GUID` support is indirect
101+
- No `rowversion`, no `DateTimeOffset`, no nullable `BIT`
102+
103+
## Versioning
104+
105+
`Version.props` owns `VersionPrefix` and `PreReleaseVersionLabel`. Bump `VersionPrefix` after each release. Valid labels: `alpha`, `beta`, `silver`, `preview`, `rc`, `rtm`, `servicing`. CI sets `OfficialVersion`, `ContinuousIntegrationTimestamp`, and `BuildSha` automatically.

src/EFCore.Jet.Data/JetDataReader.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Data.Common;
44
using System.Globalization;
55
using System.IO;
6-
using System.Runtime.InteropServices.JavaScript;
76
using System.Text;
87
using System.Threading;
98

src/EFCore.Jet/Query/Internal/JetLiftOrderByPostprocessor.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,10 @@ public virtual Expression Process(Expression expression)
3838
switch (expression)
3939
{
4040
case ShapedQueryExpression shapedQueryExpression:
41-
shapedQueryExpression = shapedQueryExpression
42-
.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression))
43-
.UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression));
41+
return shapedQueryExpression.Update(
42+
(SelectExpression)Visit(shapedQueryExpression.QueryExpression),
43+
Visit(shapedQueryExpression.ShaperExpression));
4444

45-
return shapedQueryExpression.UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression));
4645
case RelationalSplitCollectionShaperExpression relationalSplitCollectionShaperExpression:
4746
var newSelect = Visit(relationalSplitCollectionShaperExpression.SelectExpression);
4847
var newInner = Visit(relationalSplitCollectionShaperExpression.InnerShaper);

test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -640,24 +640,20 @@ public override async Task Add_column_with_comment()
640640
""");
641641
}
642642

643+
[ConditionalFact(Skip = "Jet does not support collation")]
643644
public override async Task Add_column_with_collation()
644645
{
645646
await base.Add_column_with_collation();
646647

647-
AssertSql(
648-
"""
649-
ALTER TABLE [People] ADD [Name] nvarchar(max) COLLATE German_PhoneBook_CI_AS NULL;
650-
""");
648+
AssertSql("");
651649
}
652650

651+
[ConditionalFact(Skip = "Jet does not support collation")]
653652
public override async Task Add_column_computed_with_collation(bool stored)
654653
{
655654
await base.Add_column_computed_with_collation(stored);
656655

657-
AssertSql(
658-
stored
659-
? """ALTER TABLE [People] ADD [Name] AS 'hello' COLLATE German_PhoneBook_CI_AS PERSISTED;"""
660-
: """ALTER TABLE [People] ADD [Name] AS 'hello' COLLATE German_PhoneBook_CI_AS;""");
656+
AssertSql("");
661657
}
662658

663659
public override async Task Add_column_shared()

0 commit comments

Comments
 (0)