Skip to content

improvement: decode an interval as a Duration by default - #815

Open
matt-beanland wants to merge 1 commit into
ash-project:mainfrom
matt-beanland:improvement/duration-interval-types
Open

improvement: decode an interval as a Duration by default#815
matt-beanland wants to merge 1 commit into
ash-project:mainfrom
matt-beanland:improvement/duration-interval-types

Conversation

@matt-beanland

Copy link
Copy Markdown
Contributor

Contributor checklist

Leave anything that you believe does not apply unchecked.

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

Summary

Part of #553 (doesn't close it because operators are outstanding)

A :duration attribute cannot be read on Postgres. The value encodes into the interval column fine, but the RETURNING row loads back as a %Postgrex.Interval{}, which Ash.Type.Duration refuses, then the create fails with an Ash.Error.Unknown and the insert is rolled back, leaving nothing stored.

postgrex can already decode an interval as a Duration, but interval_decode_type is only settable at Postgrex.Types.define/3, never in repo configuration, and ash_postgres shipped no types module. This adds AshPostgres.PostgrexTypes and applies it in AshPostgres.Repo.init/2 with Keyword.put_new, so a repo defining its own module for other extensions keeps it.

The migration generator needs no change: a :duration attribute already generates add(:field, :duration), which Ecto renders as interval. In Postgres INTERVAL and INTERVAL(6) are identical with microsecond precision.

This changes interval decoding for every repo, not only :duration attributes: raw queries and any custom interval-mapped type will receive a Duration rather than a %Postgrex.Interval{}. There is no Ash :duration data to migrate, since loading one currently fails.

Best paired with ash#2851 — without it, a units-constrained duration is readable but not re-writable once persisted.

Tested with Postgres 19.


# `interval_decode_type` can only be set at `Postgrex.Types.define/3`, never in repo
# configuration, so an `interval` column needs this module to load as a `Duration`.
Postgrex.Types.define(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could theoretically break any custom code that a user has. In other cases, we require the user to do this in their own application so that they have a single authoritative source of truth. I think we will have to do that here as well otherwise risk anyone who has custom interval handling code that is also using AshPostgres.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed. The default is gone, and a repo's types: is never touched.

Now:

  • The error message teaches - A %Postgrex.Interval{} reaching Ash.Type.Duration now appends
    the Postgrex.Types.define/3 call and the repo config to the message, instead of a cast
    error naming neither.

  • The tests show how - AshPostgres.TestRepo configures its own types module exactly as
    the docs instruct, so the documented path is the one CI exercises rather than only
    describes.

  • The solution is pinned - Tests assert an interval loads as a Duration, that
    months and days survive separately, and that a repo configuring its own types: keeps it.

(GitHub has followed this comment onto the test-support module — the file it was on is
deleted.)

@matt-beanland
matt-beanland force-pushed the improvement/duration-interval-types branch from 7611cb5 to db6bca7 Compare August 16, 2026 22:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants