# Changelog

What changed in Socigy.OpenSource.DB. Modular-monolith and multi-project fixes — `required` members, flowing Npgsql/Bcl dependencies, a `contextName` for lowercase databases, and `excludeDbDefaults` on every insert path — in 0.3.4; Binary COPY bulk insert, scalar/affected/DTO procedure returns, database-first scaffolding, and Transit data-key envelope encryption with per-column profiles and OpenBao support in 0.3.3; runtime-named typed tables ([TableType] and DynamicTable) in 0.3.2; the database-context bulk insert plus scalar and aggregate API in 0.3.1; and 0.3.0's field encryption, rotating credentials, and HashiCorp Vault package.

## v0.3.4 (unreleased)

Integration fixes found wiring the library into a modular monolith (model project + API + Host).

### Added

- **`contextName` config.** A new optional `socigy.json` field that decouples the generated C# identifiers from the physical database name — a lowercase `databaseName` like `identity` can now produce `IIdentityDb` / `AddIdentityDb()` while the connection-string key and physical database stay `identity`. Without it, the identifier is derived from `databaseName` (the first letter is upper-cased, so a lowercase name no longer trips `CS8981`).
- **`excludeDbDefaults` on every insert path.** The context `InsertAsync` / `InsertMultipleAsync`, the static `Table.InsertMultipleAsync`, and `BulkCopy.InsertMultipleCopyAsync` now take `excludeDbDefaults: true` to omit `[Default]` columns so the server default applies — previously only the fluent `Insert().ExcludeAutoFields()` could do this, so a `[Default]` column left unset on those paths was silently written as the CLR default.

### Changed

- **`required` members on `[Table]` models are supported.** Generated constructors emit `[SetsRequiredMembers]` (when the consumer targets a framework that has the attribute), so `public required string Email { get; set; }` no longer breaks the builders' `new()` constraint (`CS9040`).
- **Dependencies now flow to consumers.** The package multi-targets `netstandard2.0;net8.0`; on `net8.0`+ it declares `Npgsql` and `Microsoft.Bcl.AsyncInterfaces` as normal dependencies, so the generated code compiles and migrations run without adding either package by hand (previously `CS0246` for `NpgsqlCommand`/`NpgsqlDbType`, and a runtime `FileNotFoundException` for `Microsoft.Bcl.AsyncInterfaces`).

### Fixed

- **Generator no longer crashes in projects without `socigy.json`.** The analyzer flows transitively to consumer projects (e.g. an API or Host that references a model project); without a `socigy.json` it now emits nothing instead of throwing `CS8785` (`ArgumentNullException`), so the modular-monolith layout works without stripping the generator.
- **Lowercase `databaseName` no longer produces invalid C# type names.** A Postgres-conventional `databaseName: "identity"` generated an all-lowercase `partial class identity`, tripping `CS8981` under `TreatWarningsAsErrors`. Generated identifiers are now valid regardless of the database name's casing.
- **Multi-table migrations are named after all their tables.** A single migration that created `users` and `outbox` was auto-named `…_Addoutbox_…`; it is now `…_AddUsersAndOutbox_…` (and `…AndNMore` beyond two).

## v0.3.3 (22 June 2026)

### Added

- **Binary COPY bulk insert.** `BulkCopy.InsertMultipleCopyAsync(rows, conn)` (and `DynamicTable<T>.InsertMultipleCopyAsync`) load large batches via PostgreSQL's binary `COPY ... FROM STDIN (FORMAT BINARY)` — much faster than the parameterized multi-row insert and not bound by the 65,535-parameter limit. Values flow through the same per-column pipeline, so `[Encrypted]`, JSON, and value-convertor columns are handled identically; `NULL`s are written as SQL `NULL`. COPY cannot use `RETURNING`, so database-generated values are not propagated back. See [Bulk COPY](/database/0.3.4/querying/writing/bulk-copy).
- **Scalar procedure returns.** `-- @returns scalar: T` generates a `Task<T>` for single-value queries (`COUNT`, `MAX`, `EXISTS`, …), supporting primitives, `string`, `Guid`, date/time types, and their nullable forms. `NULL`/empty results map to `default(T)`; numeric widening (e.g. `COUNT`'s `bigint` → `int`) is handled. See [Procedure mapping](/database/0.3.4/advanced/procedure-mapping#scalar-returns).
- **Affected-row procedure returns.** `-- @returns affected` generates a `Task<int>` returning the number of rows a write affected, instead of the default `Task<bool>`.
- **DTO procedure returns.** `-- @returns:` can now name a plain POCO or record (not just a `[Table]` type); the generator emits an AOT-safe, by-name materializer for it — ideal for projections and report shapes.
- **Database-first scaffolding.** New CLI commands `scaffold schema` (live database → `structure.json`) and `scaffold classes` (database or `structure.json` → annotated `[Table]` C# classes), reusing the existing schema model so the result round-trips with `generate`. See [Database-first scaffolding](/database/0.3.4/migration/db-first).
- **Source-linked debug symbols.** Packages ship with embedded PDBs and SourceLink, plus deterministic CI builds, so you can step into library source from your debugger.
- **Public-API surface tracking.** The Core project now tracks its public API (`PublicAPI.*.txt`) to guard against accidental breaking changes ahead of 1.0.

### Changed

- **New generator diagnostics** `SCGDB019`–`SCGDB022` validate the new procedure return directives (unsupported scalar type, conflicting/`malformed` `@returns`, and DTOs that cannot be mapped). See [Generator diagnostics](/database/0.3.4/advanced/generator-diagnostics).

### Fixed

- **Migration generation on Linux/headless builds.** When no interactive name prompt is available, the generated migration's file and class name were derived from the multi-line schema-diff summary, producing an invalid name (embedded newlines and `:`). Headless builds now use a clean, deterministic `{timestamp}_{prefix}_{hash}` identifier.
- **Package metadata** now points at the correct repository (`github.com/Socigy-org/Socigy.OpenSource.DB`).

### Security

- **Versioned keyring envelope encryption.** `KeyringFieldEncryptor` encrypts under a current key while still decrypting values written under earlier keys (the key id is embedded in the ciphertext), so keys can be rotated without rewriting existing rows.
- **Named encryption profiles.** Register multiple encryptors and route individual columns with `[Encrypted(Profile = "…")]`; reads are lock-free. Useful for mixing a fast local encryptor with a Vault-backed one for the most sensitive columns.
- **HashiCorp Vault / OpenBao Transit.** New Transit-backed encryption modes (data-key envelope and direct EaaS) for the `Socigy.OpenSource.DB.HashiCorp` package, alongside the existing KV-v2 key and rotating database credentials. Verified against both HashiCorp Vault and the API-compatible [OpenBao](https://openbao.org/) fork.
- **Offline re-encryption.** A batched `FieldReencryptor` upgrades existing rows (generated, dynamic, and `[TableType]` tables) to the current key version/profile.

## v0.3.2 (6 June 2026)

### Added

- **Dynamic (runtime-named) tables.** New `[TableType]` attribute: declare a typed column shape once and bind the **table name at runtime** via `WithTableName(...)`, returning the typed entity (NativeAOT-safe). Full CRUD + aggregates, both standalone (`WithConnection`/`WithTransaction`) and through a context (`db.DynamicTable<T>(name)`). See [Dynamic tables](/database/0.3.4/dynamic-tables/declaring).
- **Custom (undeclared) columns.** `WithCustomColumns(...)` captures extra runtime columns into each row, `TryGetCustomValue<T>(...)` reads them, and `DB.CustomField<T>("name")` filters on them inside a normal predicate. `MapTypeAsync(name, conn)` auto-discovers a table's extra columns once and caches the schema.
- **Runtime table lifecycle.** `InstantiateAsync()` (CREATE TABLE from the declared shape), `DeleteInstanceAsync()` (DROP TABLE), and `InstanceExistsAsync()`. `[TableType]` tables live outside the migration history, so the type manages its own DDL.
- **Extended join builders.** Joins now support **3 and 4 tables** (chain `.Join<T3>(…).Join<T4>(…)`), **`OrderBy`/`OrderByDesc`**, **client-side projection** (`.Select((a,b,…)=>…)` → a typed result), and **aggregates** (`CountAsync`/`SumAsync`/`AvgAsync`/`MinAsync`/`MaxAsync`). See [Joins](/database/0.3.4/querying/reading/joins).

### Fixed

- **Outer joins now return `null` for an unmatched side** (was a zeroed default instance). Join tuple elements are nullable, so you can distinguish "no match" from a real row of defaults.
- **`Query(pred).Join<…>(…)` now filters the driving table.** The driving predicate was previously dropped.
- **Migrations apply atomically.** Each migration's schema change and its `_scg_migrations` row now commit (or roll back) in a single transaction, so a crash can no longer leave the schema changed-but-unrecorded or recorded-but-not-changed. See [Applying migrations](/database/0.3.4/migration/applying).
- **Migration order follows the `PreviousId` chain, not id sorting.** Ids are minute-granularity timestamps; two migrations created in the same minute (or any non-sortable id) could previously apply out of order. A broken or forked chain now fails loudly.
- **Rollback-aware version detection.** The current version is computed from the full history honoring `is_rollback`, so a rolled-back migration is no longer reported as current.
- **Deterministic constraint names.** Column-less constraints (e.g. raw CHECKs) no longer get a random `Guid` name, so regenerated migrations are reproducible.

### Changed

- **Destructive and lossy migration statements are flagged.** Generated migrations prefix data-losing operations with `-- [SOCIGY:DESTRUCTIVE]` (table/column drops) or `-- [SOCIGY:LOSSY]` (narrowing/unsafe type casts), and the CLI lists them at generation time. See [Schema generation](/database/0.3.4/migration/schema-generation).

### Security

- **Field encryption: associated-data binding (automatic).** Generated code now binds every encrypted value to its `table:column` (authenticated into the HMAC, not stored), so a value cannot be relocated to a different column/row and still decrypt. `IFieldEncryptor`/`FieldCrypto` also expose the optional `associatedData` for custom use. See [Encrypted columns](/database/0.3.4/defining-models/encrypted-columns).
- **Field encryption: key zeroing & portable format.** `AesFieldEncryptor` implements `IDisposable` (zeroes key material) and now encodes values in a fixed little-endian byte order so ciphertext is portable across architectures.
- **Vault: auth token is kept alive.** A background service renews the Vault token (renew-self, or AppRole relogin at max TTL) so long-running apps no longer fail once the initial token expires.
- **Vault: credential renewal tracks the real lease TTL** (renews at ~2/3 of the lease) instead of a fixed interval that could outlast it.
- **Vault: connection strings are built with `DbConnectionStringBuilder`**, so leased passwords containing `;`, `=`, quotes or spaces are escaped correctly.
- **Vault: a warning is logged** when the Vault address uses plaintext HTTP to a non-loopback host.

## v0.3.1 (5 June 2026)

### Added

- **`InsertMultipleAsync` on the database context.** `I{Table}Set` now exposes `InsertMultipleAsync(entities, includeAutoFields, ct)`, batching a whole collection into multi-row `INSERT`s within the unit-of-work scope. See [Database context → Table-set methods](/database/0.3.4/core-concepts/database-context).
- **Auto-field control on context inserts.** `InsertAsync` and `InsertMultipleAsync` take `includeAutoFields` (default `false`); pass `true` to also write auto-increment columns (supply your own values), the context equivalent of `WithAllFields()`. Backed by a new `GetInsertPlan(bool includeAutoIncrement)`.
- **Projecting `ForEachAsync<TResult>`.** Streams matching rows, projects each through the callback, and returns the results (materialized inside the scope), so you can transform rows without a lazy enumerable escaping the connection.
- **Scalar & aggregate queries.** `CountAsync` (a real `SELECT COUNT(*)`, replacing the previous client-side drain), plus `SumAsync`/`AvgAsync`/`MinAsync`/`MaxAsync` and a single-value `ScalarAsync<T>`, on both the query builder and the database context, parameterized via the existing WHERE translation. See [Aggregates & scalars](/database/0.3.4/querying/reading/aggregates).

### Fixed

- CI now builds, packs, and publishes the optional `Socigy.OpenSource.DB.HashiCorp` package independently of the main package (each is version-checked separately, so a re-run still ships one when the other is already published).
