Current section

Files

Jump to
active_memory CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to ActiveMemory are documented here. Versions follow
[Semantic Versioning](https://semver.org); while the package is pre-1.0 a minor
bump may carry a behavior change, and those are called out below.
## 0.8.0
Ecto compatibility. A `Table` can now be typed or defined as an Ecto schema, and
the read API covers what `Ecto.Repo` offers.
### Added
- `field/3` in an `attributes` block takes an optional Ecto type —
`field :name, :string` — defaulting to `:any`. Types are not enforced on write;
they power `Ecto.Changeset` casting and validation. Available as
`__attributes__(:types)`.
- A `Table` can skip `attributes` entirely and define an Ecto `embedded_schema`.
All table metadata is derived from the schema, so the module is a real Ecto
schema and every changeset function works on it.
- Autogenerated schema fields are honored on write: an `:id`/`:binary_id` primary
key, a custom key such as `@primary_key {:uuid, Ecto.UUID, autogenerate: true}`,
and `timestamps()`.
- `write/1` accepts an `Ecto.Changeset` as well as a struct, like
`c:Ecto.Repo.insert/2`. An invalid changeset returns `{:error, changeset}` with its
`action` set to `:insert`, which is what a Phoenix form needs to render errors.
- `get/1`, `get!/1`, `get_by/1`, `get_by!/1`, `one!/1`, `reload/1` and `reload!/1`.
The bang variants raise `ActiveMemory.NotFoundError`.
- `count/1`, which asks the table for its size rather than copying records out, so
it is O(1). On a `ttl` table it takes `sweep: true` to exclude expired but
unswept records.
- `exists?/2`.
- `:order_by`, `:limit` and `:offset` on `all/1` and `select/2`. Sorting happens
after reading, and uses a value's own `compare/2` when it has one, so `Decimal`
and the calendar types order correctly rather than by Erlang term order.
- `ActiveMemory.NotFoundError` and `ActiveMemory.MultipleResultsError`.
- `__attributes__(:primary_key)`, the table's first field — what ETS and Mnesia
key a record on.
### Changed
- **`one/1` now raises `ActiveMemory.MultipleResultsError`** when a query matches
more than one record, instead of returning `{:error, :more_than_one_result}`.
This matches `c:Ecto.Repo.one/2`. `withdraw/1` raises there too, since deleting an
arbitrary one of several matches would be wrong. Callers matching on
`{:error, :more_than_one_result}` need updating.
- An Ecto schema table that declares a primary key somewhere other than its first
field, or a composite primary key, raises when the table is created. An in memory
table keys a record on its first field, so neither can be honored, and the
previous behavior would have read the wrong field.
### Fixed
- An Ecto schema table with an autogenerating primary key of a custom type
(`@primary_key {:uuid, Ecto.UUID, autogenerate: true}`) generated no key, so
every record was written under a `nil` key and collapsed onto one entry.
- Reconciling a recovered Mnesia table's options could crash the store's startup:
an option change Mnesia refused raised out of `init/1`, and a table naming more
than one copy type (`ram_copies` plus `disc_copies`, for instance) crashed the
reconciliation even when the configuration was valid. A refused change is now
logged as a warning and skipped, so the store starts and the table keeps its
current setting. A copy configuration that is wrong in the code itself — the same
node under two copy types — raises `ArgumentError`, since it would fail on every
boot.
- Compile warnings from the test dependency `local_cluster` (deprecated `:slave`
usage, charlist syntax) are gone with the upgrade to 2.1.
### Documentation
- A "Deleting a record" section explaining that `delete/1` matches a record in
full, that a stale struct therefore removes nothing while still returning `:ok`,
and that `withdraw/1` is the query based alternative.
- A `ttl` on an Ecto schema table needs an explicit `field :expires_at, :integer`;
the `attributes` DSL still adds it for you.
- `Store` and `ActiveRepo` documentation now describes the same behavior for the
operations they share.
- A "Testing" section covering which test modules can run `async: true` — a module
owning its own table and store runs concurrently today — and which need
`async: false`, namely modules sharing the application's store, since a table's
module name is its physical table name.
- The `majority` option, Mnesia's quorum writes, is documented as the mitigation for
partition behavior rather than one line in a list of options.
## 0.7.4
### Fixed
- `use ActiveMemory.Store` and `use ActiveMemory.ActiveRepo` read the table's `ttl`
while the caller's module compiled, making every table a compile time dependency
of its store. Tooling that compiles a store's file without the table module
loaded — a language server, for one — raised `UndefinedFunctionError` on code
that compiles and runs correctly. The lookup happens at runtime now.
## 0.7.3
### Fixed
- Removed an unreachable `ttl` clause that newer Elixir type checkers flagged in
any project defining a store on a table without a `ttl`.
- Removed unreachable clauses in the ETS adapter's `select/2`.
## 0.7.2
### Changed
- `delete_all/0` returns `:ok` on success. The ETS adapter had returned the raw
`true` from `:ets.delete_all_objects/1` and the Mnesia adapter `{:atomic, :ok}`,
neither matching the documented `:ok | {:error, any()}` spec, so
`:ok = Store.delete_all()` raised a `MatchError`.
## 0.7.1 and earlier
See the [commit history](https://github.com/SullysMustyRuby/active_memory/commits/main)
for releases before this changelog was kept. Notable additions were record expiry
(`ttl`), `ActiveMemory.ActiveRepo` for managing several tables from one process,
`ActiveMemory.TableHeir` so an ETS table survives its store crashing, and making
`withdraw/1` a single atomic operation.