# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## [2.0.0] - 2026-05-10
The 2.0 line introduces pluggable backends and multi-repo support. The
intermediate 2.1-2.5 dev tags were rolled into this release; nothing
shipped to Hex during that window. See
[MIGRATION-2.0.md](MIGRATION-2.0.md) for upgrade notes.
### Added
- **Pluggable backends.** Pick a backend package and point kura at it
with `{backend, ...}`. Kura auto-populates `dialect`, `pool_module`,
and `driver_module` from the aggregator at app start.
- `kura_pool` behaviour - connection-pool contract.
- `kura_capabilities` behaviour - backend feature flags
(e.g. `advisory_locks`, `returning`, `arrays`).
- `kura_dialect` behaviour - SQL dialect contract, with optional
`column_type/1` and `format_default/1` callbacks for
backend-specific DDL.
- `kura_driver` behaviour - query/transaction driver contract.
- `kura_pool_ets` - in-memory pool for tests that don't need a real DB.
- **Multi-repo configuration.** Run two or more repos in the same app,
each with its own backend, dialect, and pool. Configure with a map of
maps under `{repos, ...}`:
```erlang
{kura, [
{repos, #{
primary => #{
backend => kura_backend_postgres,
host => "primary.example.com",
database => "main",
pool_size => 10
},
analytics => #{
backend => kura_backend_sqlite,
database => <<":memory:">>
}
}}
]}
```
Kura starts each repo's pool on app start. The dialect resolves
per-repo, so `primary` emits Postgres SQL while `analytics` emits
SQLite SQL. The query cache is keyed per repo so dialects never share
entries.
- `kura_app:pool_config/1` returns the config map for a specific repo.
- `kura_types:cast(boolean, 0|1)` so SQLite (booleans stored as INTEGER)
round-trips cleanly.
### Changed
- `kura_query_compiler:to_sql/2`, `to_sql_cached/2`, `to_sql_from/3`,
`insert/4`, `insert/5`, `update/5`, `delete/4`, `update_all/3`,
`delete_all/2`, `insert_all/4`, `insert_all/5`, and `dialect/1`
take `RepoMod` as the first argument so they can resolve the
per-repo dialect. Internal callers (`kura_repo_worker`,
`kura_stream`, `kura_migrator`) thread the repo through automatically.
- `kura_query_cache` keys are tuples `{RepoMod, QueryHash}`. The cache
ETS table is now owned by a supervised gen_server so it survives any
caller exiting.
- `kura_db:query/3` and the rest of the query path route through
`kura_pool` / `kura_driver`, so backends are swappable.
- `kura_app:pool_config/0` is dialect-aware. PG-shaped defaults
(port 5432, user/database `"postgres"`, decode_opts) only apply when
`dialect` is `kura_dialect_pg`. Other dialects get a pass-through
map of user-set kura env keys.
- `kura_app:configure_pg_types/0` only runs when `dialect` is
`kura_dialect_pg`.
- `kura_repo:read_config/1` returns a pass-through map of all kura env
keys (minus bookkeeping). Previously hardcoded the PG-shaped subset
and silently dropped `pool_module`, `driver_module`, `backend`.
- `kura_migrator` skips `pg_advisory_xact_lock` when the configured
pool doesn't declare the `advisory_locks` capability. The migration
transaction alone serializes runs on backends that lack advisory
locks (SQLite, etc.).
- Single-repo legacy config (flat `kura` env keys) and the per-app form
(`application:get_env(OtpApp, RepoMod)`) keep working unchanged. The
global `application:get_env(kura, dialect)` is consulted as a fallback
when a repo's config map omits `dialect`.
### Removed (BREAKING)
- `kura_pool_pgo`, `kura_driver_pgo` no longer ship in `kura`. Install
[`kura_postgres`](https://github.com/Taure/kura_postgres) for Postgres
or [`kura_sqlite`](https://github.com/Taure/kura_sqlite) for SQLite.
`pgo` is no longer a runtime dependency of `kura`. `kura_dialect_pg`
remains in core as the default ANSI-ish SQL emitter that other
dialects delegate to.
- `kura_query_compiler:dialect/0` no longer defaults to `kura_dialect_pg`.
Use `dialect/1` and configure the dialect per repo (recommended via
`{backend, ...}`), or set the global `application:set_env(kura,
dialect, Module)` for legacy single-dialect setups.
- `kura_db:get_pool_module/1` and `get_driver_module/1` error with
`{no_pool_module_configured, _}` / `{no_driver_module_configured, _}`
when not configured. Set `{backend, ...}` (recommended) or
`pool_module`/`driver_module` explicitly.
## [1.8.0] - 2026-03-06
### Changed
- **Breaking**: `kura_repo` behaviour now requires `otp_app/0` callback instead of `config/0` - database configuration is read from application environment via `application:get_env(OtpApp, RepoModule)`
- Optional `init/1` callback on `kura_repo` - modify config at runtime (read secrets from env vars, files, external services)
- `kura_repo:config/1` function reads config from app env, then calls `init/1` if defined
- All internal `get_pool/1` calls now use `kura_repo:config/1` instead of `RepoMod:config()`
## [1.7.0] - 2026-03-06
### Added
- `telemetry` library integration - every query emits a `[kura, repo, query]` telemetry event with measurements (`duration`, `duration_us`) and metadata (`query`, `params`, `repo`, `result`, `num_rows`, `source`)
- `build_telemetry_metadata/4` and `extract_source/1` exported from `kura_repo_worker` for testing
- `source` field in telemetry metadata - table name extracted from the SQL query
### Changed
- Legacy `{kura, [{log, ...}]}` config still works but now runs alongside telemetry events (not instead of)
## [1.6.0] - 2026-03-06
### Added
- Ecto-style index support - `indexes/0` optional callback on `kura_schema` for declaring indexes at the schema level
- Map-based `{create_index, Table, Cols, Opts}` migration operation with auto-generated index names (`{table}_{cols}_index`)
- `kura_migration:index_name/2` helper for generating Ecto-style index names
- Unique indexes declared via `indexes/0` auto-register changeset constraints - no manual `unique_constraint/3` calls needed
- New types: `index_def()`, `index_opts_map()` in `kura_migration`
## [1.5.0] - 2026-03-05
### Added
- Table-level constraints in migrations - `{create_table, Table, Columns, Constraints}` 4-tuple variant supporting `{unique, [atom()]}` and `{check, binary()}` inline constraints
- `constraints/0` optional callback on `kura_schema` behaviour - declares table-level constraints on the schema
- Auto-registration of schema constraints on `kura_changeset:cast/4` - duplicate inserts get friendly error messages without manual `unique_constraint/3` calls
## [1.4.0] - 2026-03-05
### Changed
- **Breaking:** Minimum OTP version is now OTP 28 (sigil syntax, ELP fixes)
- Binary string literals converted to sigil syntax (`~"..."`) across changeset modules
- Eliminated unnecessary intermediate list and tuple allocations in association casting
## [1.3.2] - 2026-03-05
### Fixed
- Dialyzer warnings in `get_field/2,3` - `maps:find/2` returns `{ok, V} | error`, not map patterns
- Atom exhaustion risk in enum `load/2` - now validates against declared values list instead of `binary_to_atom`
- Atom exhaustion risk in `kura_repo_worker` error handlers - NOT NULL and constraint errors fall back to `base` atom for unknown columns
- CI running resilience tests that require docker compose control - explicitly list test modules
### Added
- Production readiness test suites: `kura_stress_tests` (7 concurrency tests), `kura_bench_tests` (6 throughput benchmarks), `kura_migration_stress_tests` (4 migration safety tests), `kura_resilience_tests` (4 failure/recovery tests)
- Makefile targets: `test-stress`, `test-bench`, `test-migration-stress`, `test-resilience`, `test-production`
## [1.3.1] - 2026-03-04
### Fixed
- Advisory lock query wrapped to avoid pgo void type decode error (`pg_advisory_xact_lock` returns void)
## [1.3.0] - 2026-03-03
### Changed
- Primary key derived from field flag instead of separate callback
## [1.2.0] - 2026-03-01
### Added
- Changeset validators: `validate_exclusion`, `validate_subset`, `traverse_errors`, `prepare_changes`, `optimistic_lock`
- Query extensions: subqueries in WHERE, CTEs, UNION/INTERSECT/EXCEPT, window functions (`select_expr`), query scopes
- `kura_sandbox` - test transaction rollback for isolated concurrent tests
- `kura_stream` - server-side cursor batching for large result sets
- `insert_all` with RETURNING support
### Changed
- `to_sql_from/2` refactored as keystone for all query compilation
## [1.1.0] - 2026-02-27
### Added
- Foreign key constraints in migrations - `references`, `on_delete`, `on_update` fields on `#kura_column{}` generate inline `REFERENCES "table"("col") ON DELETE CASCADE` etc.
- `kura_changeset:validate_confirmation/2,3` - validates a field has a matching `<field>_confirmation` in params (for password/email confirmation flows)
- `kura_repo_worker:exists/2` - checks if any record matches a query, returns `{ok, true | false}`
- `kura_repo_worker:reload/3` - re-fetches a record from the database by its primary key
## [1.0.4] - 2026-02-26
### Fixed
- ExDoc build failure - `doc/architecture.md` was wiped by edoc before ex_doc could read it; moved to `guides/architecture.md`
- Removed `doc` from hex include_files (generated output, not source)
### Added
- ExDoc build step in CI to catch documentation errors early
## [1.0.3] - 2026-02-26
### Added
- Rolling deployment safety warnings - detect dangerous migration operations (`drop_column`, `rename_column`, `modify_column`, `drop_table`, `add_column NOT NULL` without default) and log warnings before execution
- Optional `safe/0` callback on `kura_migration` behaviour to suppress warnings when the expand-contract pattern has been followed
- Rolling deployments guide (`guides/rolling_deployments.md`) documenting the expand-contract pattern with step-by-step examples
## [1.0.2] - 2026-02-24
### Added
- `kura_preloader` module - extracted preload logic from `kura_repo_worker` into a dedicated module
- `kura_changeset_assoc` module - extracted `cast_assoc`, `put_assoc`, and association coercion from `kura_changeset`
- Comprehensive integration tests (356 total) covering all query operators, aggregates, joins, bulk ops, multi pipelines, on_conflict upserts, constraint errors, type round-trips, and all association/preload types
### Fixed
- Constraint error handling (`unique_constraint`, `foreign_key_constraint`, `check_constraint`) now correctly unwraps pgo `{pgsql_error, Map}` tuples
- `put_assoc` for many_to_many now preserves primary key in changeset data
## [1.0.1] - 2026-02-22
### Added
- Embedded schemas guide (`guides/embedded-schemas.md`)
- Architecture doc linked in hexdocs extras
- Logo displayed in hexdocs
- Navigation groups: Getting Started, Guides, Reference
### Changed
- Hidden internal modules (`kura_app`, `kura_sup`) from docs
## [1.0.0] - 2026-02-22
### Changed
- Consolidated CI and release into a single GitHub Actions workflow
- Updated README features list to reflect all capabilities added since 0.2.0
- Fixed stale `log_queries` config reference in README (now `log`)
- Fixed stale migration discovery docs in README (module-based since 0.3.0)
## [0.5.0] - 2026-02-14
### Added
- Embedded schemas (`embeds_one`, `embeds_many`) stored as JSONB with `cast_embed/2,3`
- Many-to-many associations with `join_through` / `join_keys`, preloading, and persistence via `cast_assoc` / `put_assoc`
- Schemaless changesets - `cast/4` accepts a types map for validation-only workflows
- `Makefile` for local test lifecycle management (`make test` with Docker Compose)
- Schemaless changesets guide (`guides/schemaless-changesets.md`)
## [0.4.1] - 2026-02-14
### Added
- Hex documentation guides for enums, telemetry, and cast_assoc
## [0.4.0] - 2026-02-14
### Added
- Enum type (`{enum, [atom()]}`) - stored as `VARCHAR(255)`, cast/dump/load between atoms and binaries
- Query telemetry via `sys.config` - `{kura, [{log, true | {M,F}}]}` with timing on every query
- `kura_changeset:cast_assoc/2,3` - nested changeset casting for `has_many` and `has_one` associations
- `kura_changeset:put_assoc/3` - directly attach maps or pre-built changesets as association changes
- `assoc_changes` field on `#kura_changeset{}` record
- Nested insert/update - automatic transaction wrapping with FK propagation when `assoc_changes` present
- `kura_repo_worker:build_log_event/5` and `default_logger/0` exported for testability
- Architecture documentation (`guides/architecture.md`)
- Feature guides: enums, telemetry, cast_assoc (`guides/`)
### Changed
- Telemetry reads from `application:get_env(kura, log)` instead of repo `config/0` map
- Removed `log_queries` application env default (replaced by `log`)
- `kura_repo_worker:insert/2` and `update/2` refactored to support nested association persistence
### Removed
- `maybe_log/2` internal function (replaced by centralized `emit_log/5`)
## [0.3.0] - 2026-02-14
### Added
- Module-based migration discovery (no filesystem scanning)
- Xref checks in CI
- rebar3_kura plugin reference in README
### Removed
- `migration_paths` configuration (migrations now discovered automatically from compiled modules)
## [0.2.0] - 2025-02-14
### Added
- Constraint declarations (`unique_constraint`, `foreign_key_constraint`, `check_constraint`) with automatic PostgreSQL error mapping
- `kura_multi` for atomic transaction pipelines with insert/update/delete/run steps
- Association support (`belongs_to`, `has_one`, `has_many`) with preloading
- Nested preloads
- Standalone preloading via `kura_repo_worker:preload/4`
- Query preloads via `kura_query:preload/2`
- Upsert support (`insert/3` with `on_conflict` option)
- Bulk operations: `insert_all/3`, `update_all/3`, `delete_all/2`
- `kura_changeset:validate_change/3` for custom validation functions
- `kura_changeset:apply_action/2`
- Module documentation (`-moduledoc` / `-doc`) for all public modules
- Usage guides: Getting Started, Changesets, Query Builder, Associations, Multi, Migrations
- CI/CD with GitHub Actions
- CHANGELOG
## [0.1.0] - 2025-01-15
### Added
- `kura_schema` behaviour for defining database-backed schemas
- `kura_changeset` for casting, validation, and change tracking
- `kura_query` composable query builder
- `kura_query_compiler` for parameterized SQL generation
- `kura_repo` behaviour and `kura_repo_worker` for CRUD operations
- `kura_types` type system (id, integer, float, string, text, boolean, date, utc_datetime, uuid, jsonb, arrays)
- `kura_migration` behaviour and `kura_migrator` for DDL migrations
- Automatic timestamps (`inserted_at`, `updated_at`)
- Query logging via application env