ash-project / ash-project/ash

Support destroy and update actions on resources that have no primary key

Open
#1,660 2 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Elixir
Stars
2.5k
Forks
426
Avg merge
23h 26m
Merged PRs (30d)
46

Description

Right now, ash does not allow defining `update` and `destroy` actions on resources without a PK:
```elixir
# require_primary_key? false and update :some_update in resource causes the following compile time error

actions -> update -> some_update:
AshPostgres.DataLayer does not support update actions on this resource.
This is either because of a limitation in the data layer, or specific configuration of the resource.
```

Some databases, e.g. timescaledb extension, do not allow having a PK on a table. Instead of PK, some timescaledb users utilize a unique index based on `time` + other columns (e.g. `id, device_id, time`).

While it's possible to define a generic action to workaround this issue, it would cause us to loose action type semantics and other instruments ash provides for `update`/`destroy` actions.

As @zachdaniel noted before https://discord.com/channels/711271361523351632/711271361523351636/1317093457982001225, perhaps it makes sense to

> 1. Allow manual destroy and update actions to bypass that check
> 2. Use the first identity to destroy/update if present, and change that check
> 3. Ensure the first identity is always selected if no primary key exists.

@zachdaniel afaiu the first point implies that ash will allow defining **manual** `update` & `destroy` actions *even if there is no identity/unique index*. I believe it makes sense to bypass the check **and** not require defining any `identities` because
a) both destroy and update actions might not be limited to a single row and
b) some resources might have their own way for identifying a specific entry they need to update/destroy that is not known for data layer or ash, but rather enforced on a higher level (app, device, transport protocol, etc.). I can imagine a scenario where someone would like to have no unique indices to improve data ingestion performance on some table. They might or might not enforce uniqueness in business logic instead, but *regardless of their decision*, it makes sense to allow defining manual `destroy` / `update` actions in Ash.Resources without any identity.

Short example with comments that might be useful:

```elixir
attributes do
# not a PK
attribute :id, :integer do
generated? false
primary_key? false
public? true
end

# in timescale, tables have to have some timstamp for chunking/partitioning
attribute :time, AshPostgres.TimestamptzUsec do
allow_nil? false
public? true
constraints precision: :microsecond, timezone: :utc
end
end

relationships do
belongs_to :device, Device do
allow_nil? false
public? true
end
end

resource do
require_primary_key? false
end

identities do
# in our use case we tolerate having this unique index, but at some point for specific hypertables,
# we might consider dropping this index whatsoever for ingestion performance
identity :unique_id_device_id_time, [:id, :device_id, :time]
end

actions do
defaults [:read]

destroy :expunge_old do
# ... destroy multiple entries
end

destroy :delete_specific do
# ... destroy specific resource, based on
# identity or with manual actions based on our own logic unknown to ash & data layer
end

update :reset_time do
# ... same but for update
end

create :create do
# create works fine
accept :*
# allow upserts
upsert? true
# use ON CONFLICT (:id, :device_id, time)
upsert_identity :unique_id_device_id_time
# force DO NOTHING
upsert_fields []
end
end
```

Contributor guide

Open the contributing guide

Research direction

Start by tracing the AshPostgres.DataLayer check that rejects update and destroy actions when a resource has no primary key, then review the resource action and identity behavior described in the issue. Done means manual actions can support the stated no-primary-key cases while preserving the relevant update and destroy semantics, with the chosen identity behavior made explicit.

Written by the indexing model from the issue text.

Assessment

Tech stack
elixir, postgres
Domain
backend-api-design, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.