Skip to content

improvement: support the Duration form of ago, from_now, datetime_add… - #245

Merged
zachdaniel merged 1 commit into
ash-project:mainfrom
matt-beanland:improvement/duration-expressions
Aug 14, 2026
Merged

improvement: support the Duration form of ago, from_now, datetime_add…#245
zachdaniel merged 1 commit into
ash-project:mainfrom
matt-beanland:improvement/duration-expressions

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

Tests will be contributed separately in ash_postgres as PR, will need this to release before it merges.

Summary

Partially delivers ash-project/ash_postgres#553 (Duration)
Requires ash PR ash-project/ash#2844 (for from_now/1)
Enables an ash_postgres test PR

Ash.Query.Function.Ago, FromNow, DateTimeAdd and DateAdd each accept a
Duration as well as an integer/interval-name pair, and evaluate it in Elixir. The Duration form is handled in ash for data layers that evaluate expressions using Ash Runtime, but wasn't handled by ash_sql raising Unsupported expression for SQL backed data layers.

This adds a clause per function, passing the duration as an interval parameter. Ecto
has a native :duration type and Postgres accepts a %Duration{} directly, so there
is no interval-name string building and multi-unit durations work.

Verified against PostgreSQL 19 with all four functions in both forms, including a
multi-unit and a negative duration.

… and date_add

`Ash.Query.Function.Ago`, `FromNow`, `DateTimeAdd` and `DateAdd` each accept a
`Duration` as well as an integer/interval-name pair, and evaluate it in Elixir, but
only the interval form was rendered here. The Duration form reached
`default_dynamic_expr/6`, matched nothing, and raised `Unsupported expression`, so it
worked on data layers that evaluate expressions at runtime and failed on every
SQL-backed one.

Adds a clause per function, rendering the duration as an interval parameter. Ecto has
a native `:duration` type and Postgres accepts a `%Duration{}` directly, so no
interval-name string building is needed, and multi-unit durations work.

The datetime operand is cast, as Ecto's own `datetime_add/3` does with
`type_unless_typed`; without it Postgres resolves `? - ?::interval` as interval
arithmetic. `date_add` casts back to `::date` for the same reason Ecto's does.

`from_now/1` is uncallable until the corresponding ash change releases, so its clause
is untestable downstream until then.
@zachdaniel
zachdaniel merged commit 44cd01e into ash-project:main Aug 14, 2026
25 of 26 checks passed
@zachdaniel

Copy link
Copy Markdown
Contributor

🚀 Thank you for your contribution! 🚀

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