All notable changes to ex_tempo_sql are documented here, following Keep a Changelog.
[0.2.0] — 2026-08-03
Added
- Every public function now carries an
### Examplessection, and every example that can run without a database runs as a doctest on each test build. The library previously had none, so its documentation was never checked against its code.
Fixed
Recurring intervals stored in a
tempo_rangeortempo_multirangecolumn now load back intact. The meta format could not tell an open-ended endpoint from an absent one, so a value likeR5/2022-01-01/P1Mreturned withto: :undefinedand could no longer be rendered at all.Documented the storage contract accurately for week and day-of-year endpoints. Ordinal dates and full week dates are resolved to calendar dates while parsing and store normally; only a value still carrying those tokens — such as a bare
~o"2026-W10"— is refused.
Changed
The composite
metaformat is now version 2, which writes".."for an explicitly open-ended endpoint. Version 1 rows keep decoding exactly as before, so no migration is required, but a recurring interval written by 0.1.0 should be rewritten to regain its endpoint.Requires
ex_tempo ~> 1.1 and >= 1.1.1. The previous~> 0.16constraint could not resolve alongside Tempo 1.x, so the two libraries could not be used together. The 1.1.1 floor matters on its own: before it, casting a list whose members are not intervals raised rather than returning:error, so bad changeset input crashed instead of failing validation.Repository links point at the
elixir-tempoorganisation, and the README links the storage-contract guide on HexDocs rather than a GitHub blob.Requires Erlang/OTP 27 or later, inherited from
ex_tempo— OTP 26 is no longer supported and has been dropped from CI.
[0.1.0] — 2026-07-05
Highlights
Tempo SQL persists Tempo intervals and interval sets as PostgreSQL range types, wiring the half-open
[from, to)convention through totstzrange/tstzmultirangenatively.Ecto types.
Tempo.Ecto.Intervalstores a%Tempo.Interval{}aststzrange;Tempo.Ecto.IntervalSetstores a%Tempo.IntervalSet{}aststzmultirange(PG 14+);Tempo.Ecto.Tempomaterialises a bare%Tempo{}implicit span before delegating to the interval type.Storage contract. Each type is explicit about what it refuses to store: recurrence rules, qualifications, non-Gregorian calendars, multi-valued token slots, and ordinal/week-date endpoints all return
:errorfromdump/1rather than silently losing information. See the "Storage contract" section of the README.Migration helpers.
Tempo.SQL.Migrationexposesadd_interval/2,add_interval_set/2, andcreate_interval_index/3. Range queries require GiST indexes for speed; the helper handles that.Query API.
Tempo.Ecto.QueryAPIwraps Postgres range operators (@>,&&,-|-,<<,>>) under Allen's interval-algebra names —contains,overlaps,meets,strictly_before,strictly_after.Round-trip note. The plain-range types (
Tempo.Ecto.Interval,.IntervalSet,.Tempo) are lossy on metadata — qualifications, recurrence rules, calendars, zone identifiers, and the implicit-vs-explicit-span distinction are dropped on store. Partial resolution is recoverable via the:resolutionfield option. For full round-trip fidelity, use the composite types below.Composite
tempo_range/tempo_multirangetypes.Tempo.Ecto.TempoRangeandTempo.Ecto.TempoMultirangepersist the full Tempo shape byte-for-byte via a PostgreSQL composite type pairing atstzrange/tstzmultirangewith ajsonbmeta column. One-time migration creates the types (Tempo.SQL.Migration.create_tempo_types/0); field helpers (add_tempo_range/2,add_tempo_multirange/2) declare the columns;Tempo.Ecto.QueryAPI.Compositeprovides auto-unwrapping query macros. Uses Erlang's built-in:json(OTP 27+) via theTempo.SQL.PostgresTypesmodule — no Jason dependency.