|
| 1 | +# DirectSQL Copilot Instructions |
| 2 | + |
| 3 | +## Build and test |
| 4 | + |
| 5 | +- Build the solution from the repo root with `dotnet build --configuration Release`. |
| 6 | +- Run the full test suite with `dotnet test --configuration Release`. |
| 7 | +- Run a single MSTest with `dotnet test TestSqlLiteDatabase/TestSqlLiteDatabase.csproj --filter FullyQualifiedName~TestSqlLiteDatabase.QueryTest.TestQuery`. |
| 8 | +- The projects target `net8.0`. CI uses .NET 8.0.100, and `global.json` allows rolling forward from 7.0.100 to a newer major SDK. |
| 9 | +- `DirectSQL/DirectSQL.csproj` has `GeneratePackageOnBuild=true`, so builds also produce a NuGet package in `DirectSQL/bin/<Configuration>/`. |
| 10 | +- In the current dependency set, SQLite tests fail on macOS before assertions run because `System.Data.SQLite.Core` cannot load `SQLite.Interop.dll`. |
| 11 | + |
| 12 | +## High-level architecture |
| 13 | + |
| 14 | +- `DirectSQL/Database.cs` is the core of the library. It owns connection lifecycle (`Process`, `ProcessAsync`), transaction wrappers, parameter creation/conversion, and the provider-agnostic SQL helpers (`ExecuteNonQuery`, `ExecuteScalar`, `Query`, `LoadSqlResult`, and the `FormattableString` overloads). |
| 15 | +- Each database-specific entry point is a thin subclass of `Database<...>` under `DirectSQL/SqlLite`, `DirectSQL/SqlServer`, `DirectSQL/npgsql`, `DirectSQL/MySql`, `DirectSQL/DB2`, and `DirectSQL/Odbc`. These classes mostly capture a connection string and implement `CreateConnection()`. |
| 16 | +- `DirectSQL/SqlResult.cs` wraps the active `IDataReader` and exposes the current row as `dynamic`, tuple arrays, typed projections, and enumerable loaders. This is the main abstraction for reading query results. |
| 17 | +- The normal execution flow is: instantiate a concrete provider database -> enter `Process(...)` / `ProcessAsync(...)` to get an open connection -> call static query helpers on the provider type -> let `Database.cs` build the command and parameters -> read rows through `SqlResult`. |
| 18 | +- `LoadSqlResult*` convenience APIs are thin wrappers over `Query(...)` that materialize the cursor into `dynamic[]`; they do not introduce a separate query pipeline. |
| 19 | +- Error handling is centralized in `Database.Transaction(...)` and `Database.TransactionAsync(...)`: exceptions from the callback are wrapped in `DatabaseException`, while the low-level command and reader behavior remains straight ADO.NET. |
| 20 | +- `TestSqlLiteDatabase` is the only test project and doubles as the main executable specification of library behavior. Most usage patterns in the README are mirrored there. |
| 21 | + |
| 22 | +## Key conventions |
| 23 | + |
| 24 | +- Treat the concrete `*Database` class as the entry point for connection management, then use static methods on that same concrete type inside the callback. The common pattern is `db.Process((conn, tran) => { SqlLiteDatabase.Query(...); })`, not instance methods for query execution. |
| 25 | +- Transaction callbacks are manual-commit. `Process((conn, tran) => ...)` and `Database.Transaction(...)` provide a transaction object, but the library does not commit for you; tests that need persistence call `tran.Commit()` explicitly and use `tran.Rollback()` explicitly for rollback scenarios. The library only rolls back automatically when it catches an exception and rethrows `DatabaseException`. |
| 26 | +- `SqlResult` is cursor-based. Call `result.Next()` before reading `ResultValues` or `ResultTuples`; attempting to read row values before the first `Next()` or after the cursor is exhausted raises the underlying reader error. `ResultValues` and `ResultTuples` are cached for the current row and reset on the next `Next()`. |
| 27 | +- Prefer the built-in parameter helpers over handwritten command setup. The codebase uses tuple parameters like `new (string, object)[] { ("@val1", value) }` and `CreateParameter(...)` when an explicit `DbType` is needed. |
| 28 | +- Prefer the `FormattableString` helpers when authoring inline SQL in code. `ExecuteFormattableNonQuery`, `ExecuteFormattableScalar`, `QueryFormattable`, and `LoadFormattableSqlResult` rewrite interpolated values into generated bound parameters like `@0`, `@1`, and tests assert against that rewritten SQL. |
| 29 | +- The async APIs are wrappers around synchronous ADO.NET work via `Task.Run`, not provider-native async I/O. Keep that in mind when changing behavior or adding new async surfaces. |
0 commit comments