Skip to content

Conversation

stevebrambilla
Copy link
Contributor

@stevebrambilla stevebrambilla commented Sep 25, 2025

We use the Citus extension for sharding our Postgres database, which adds a requirement that any functions used within CASE or COALESCE expressions must be IMMUTABLE. Currently this means that we can't use error expressions at all because ash_raise_error is currently marked as STABLE.

The current ash_raise_error could technically be IMMUTABLE as it meets all of the requirements. However, the postgres planner will constant-fold the function call, making all expressions that use it immediately raise an exception. To prevent this we need to make the function call dependent on the row so it can't be constant-folded.

This PR adds a new function, ash_raise_error_immutable, that is marked IMMUTABLE and accepts a third argument that is simply ignored. This works together with a change in AshSql that adds a row-dependent argument, preventing the caching.

It's opt-in only via an AshPostgres.Repo callback, which stores a value in the repo config. AshSql looks for that config value when building the query and decides whether to call the existing ash_raise_error function, or the new ash_raise_error_immutable.

Related AshSql PR: ash-project/ash_sql#175


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

This improves support for Citus (distributed Postgres) where it only
supports immutable functions within CASE statements. A row-dependent
token is passed in as an ignored argument to prevent the query planner
from constant-folding the ash_raise_error_immutable call.
ash_sql will look for the :immutable_expr_error? key and, if true, use
the new ash_raise_error_immutable function instead of ash_raise_error.

def drop(2) do
"""
#{ash_raise_error()}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Btw, drop(2) used to call ash_raise_error(false), where the false arg removed the 'ash_error" prefix. It looks like that may have gotten lost unintentionally over a couple of commits:

Copy link
Contributor

Choose a reason for hiding this comment

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

😓 good catch.

@zachdaniel
Copy link
Contributor

So I like this, but I also hesitate to define this function automatically in everyone's databases when its only need by a few. My suggestion would be to create a new "extension" (not an Ash extension, just one of these postgres extension modules) that users can optionally add to define this function.

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