# Changelog

What changed in Socigy.OpenSource.DB. 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.2 (unreleased)

### 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.2/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.2/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.2/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.2/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.2/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.2/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.2/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).
