Raise Ecto.StaleEntryError in MyApp.Repo.delete/2

Hey, I found this article:

I really liked this idea and I have applied to application I’m working on.

Migrations are already working for me really well. MyApp.Repo.delete/2 also works (it do what it should), but at end it raises extra error (after soft delete was triggered):

[debug] QUERY OK db=6.0ms queue=1.6ms
DELETE FROM "my_schema_view" WHERE "id" = $1 [1]
** (Ecto.StaleEntryError) attempted to delete a stale struct:

%MyApp.MyContext.MySchema{__meta__: #Ecto.Schema.Metadata<:loaded, "my_schema_view">, …}

    (ecto) lib/ecto/repo/schema.ex:667: Ecto.Repo.Schema.apply/4
    (ecto) lib/ecto/repo/schema.ex:442: anonymous fn/10 in Ecto.Repo.Schema.do_delete/3

As said the weird behaviour is that it raises after everything goes well. Also following documentation it should not raise - instead it should return {:error, Ecto.Changeset.t()}.

I know why it’s called stale, but as said it’s expected and also it works really well (I hit trigger without problem) and I have just this one not needed error in console. The question is how to fix it?

I’m not sure if my special case is good to fill a bug, but on the other side it raises when it should not … What do you think about it?

1 Like

Are you using the stale_error_field option to report the error in the changeset: https://hexdocs.pm/ecto/Ecto.Repo.html#c:delete/2-options ?


I missed that, because I was looking at spec only and saw that it always returns {:ok, result} or {:error, changeset} and did not even though that it could be changed in such way. I think that in docs there should be no_return also, right? Anyway it’s working as expected now, thanks!

1 Like

Hi, I’m reaching here one year later.

I’m trying to use the stale_error_field option without success: the Ecto.StaleEntryError still rises.

I don’t correctly understand what should be set as a field and what should it do.

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app
  defoverridable update: 2, update!: 2

  def update(struct, opts) do
    super(struct, Keyword.merge(opts, [stale_error_field: :id]))
  def update!(struct, opts) do
    super(struct, Keyword.merge(opts, [stale_error_field: :id]))

Can you help?


@augnustin Yeah, it was something about year ago, so I do not remember correctly … However if you are looking for examples then even before forum/google you should check test folder :smiley:

Feel free to experiment over there and apply updated code to your project.

Here are lines of tests for stale_error_field option:

Of course put_in(my_schema.__meta__.context… is only for tests (normally it’s set somewhere in ecto code) and you should not work on __meta__ key.

@Eiji I am actually running into this now with the exqlite library I made.

{:ok, user} = TestRepo.insert(%User{name: "John"}, [])
{:ok, _} = TestRepo.delete(user)

Results in

     ** (Ecto.StaleEntryError) attempted to delete a stale struct:

     %Exqlite.Integration.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, accounts: 
     #Ecto.Association.NotLoaded<association :accounts is not loaded>, 
     id: 5, inserted_at: ~N[2021-03-06 14:27:46], 
     name: "John", updated_at: ~N[2021-03-06 14:27:46]}

You return incorrect data for delete query …

Here you can check a pattern match where ecto_sql thinks that the return gives stale:

If I hardcode this:

  def query(conn, sql, params, options) do
    query = %Exqlite.Query{statement: IO.iodata_to_binary(sql)}

    case DBConnection.prepare_execute(conn, query, params, options) do
      {:ok, _, result} ->
        if sql |> List.first() |> String.starts_with?("DELETE") do
          {:ok, %{result | num_rows: 1, rows: nil}}
          {:ok, result}

      other ->

then everything works …

Thanks a ton. I got a test I made to recreate the issue. Will incorporate your fix.

This is not fix - it’s just a simple workaround. You need to return a proper Exqlite.Result. I would also try delete_all repo callback.

1 Like

Of course, but thank you for pointing me in the right direction.