ash-project / ash_sqlite Goto Github PK
View Code? Open in Web Editor NEWThe SQLite data layer for Ash Framework.
Home Page: https://hexdocs.pm/ash_sqlite
License: MIT License
The SQLite data layer for Ash Framework.
Home Page: https://hexdocs.pm/ash_sqlite
License: MIT License
First of all, thank you for all the hard work on Ash. I am impressed and enjoying the 3.0 release candidates!
Describe the bug
The update action on a resource updates all records in the resource's table, rather than the specific record that is intended to be updated. This also causes a CaseClauseError
when the return of the sqlite update statement is received.
To Reproduce
There is a unit test at the end of this issue that I believe demonstrates the issue.
The basic steps are:
Consider a simple resource:
defmodule DemoAshSqlite.Example.Thing do
use Ash.Resource,
domain: DemoAshSqlite.Example,
data_layer: AshSqlite.DataLayer
sqlite do
table "things"
repo DemoAshSqlite.Repo
end
attributes do
uuid_primary_key :id
attribute :subject, :string do
allow_nil? false
public? true
end
end
actions do
defaults [:read, :create, update: :*]
create :new do
accept [:subject]
end
update :edit do
accept [:subject]
end
end
end
Then create some sample data in an iex
shell
# Create a single record
{:ok, thing1} =
DemoAshSqlite.Example.Thing
|> Ash.Changeset.for_create(:new, %{subject: "First thing"})
|> Ash.create()
# This record can be updated
Ash.Changeset.for_update(thing1, :edit, %{subject: "First thing after update"}) |> Ash.update!()
# Create a second record
{:ok, thing2} =
DemoAshSqlite.Example.Thing
|> Ash.Changeset.for_create(:new, %{subject: "Second thing"})
|> Ash.create()
# Both records can be read and have different subjects
Ash.read!(DemoAshSqlite.Example.Thing)
Attempting to update a record once there is more than one record will cause an error as there is not a case match for when more than one record is returned
Line 1387 in 843c0ff
where
.
iex(6)> Ash.Changeset.for_update(thing2, :edit, %{subject: "Second thing after update"}) |> Ash.update!()
23:47:34.176 [debug] QUERY OK source="things" db=0.6ms idle=1338.1ms
UPDATE "things" AS t0 SET "subject" = ? RETURNING "id" ["Second thing after update"]
** (Ash.Error.Unknown) Unknown Error
* ** (CaseClauseError) no case clause matching: {2, [#DemoAshSqlite.Example.Thing<__meta__: #Ecto.Schema.Metadata<:loaded, "things">, id: "5b8f496d-00cf-43e7-bfa5-54ef0f8eb3a2", subject: nil, aggregates: %{}, calculations: %{}, ...>, #DemoAshSqlite.Example.Thing<__meta__: #Ecto.Schema.Metadata<:loaded, "things">, id: "727c16ee-592a-4fdc-b07f-0592946c6a75", subject: nil, aggregates: %{}, calculations: %{}, ...>]}
(ash_sqlite 0.1.2-rc.0) lib/data_layer.ex:1388: AshSqlite.DataLayer.update/2
(ash 3.0.0-rc.45) lib/ash/actions/update/update.ex:459: anonymous fn/5 in Ash.Actions.Update.commit/3
(ash 3.0.0-rc.45) lib/ash/changeset/changeset.ex:3337: Ash.Changeset.run_around_actions/2
(ash 3.0.0-rc.45) lib/ash/changeset/changeset.ex:3057: anonymous fn/2 in Ash.Changeset.transaction_hooks/2
(ash 3.0.0-rc.45) lib/ash/changeset/changeset.ex:2982: Ash.Changeset.with_hooks/3
(ash 3.0.0-rc.45) lib/ash/actions/update/update.ex:379: Ash.Actions.Update.commit/3
(ash 3.0.0-rc.45) lib/ash/actions/update/update.ex:261: Ash.Actions.Update.do_run/4
(ash 3.0.0-rc.45) lib/ash/actions/update/update.ex:220: Ash.Actions.Update.run/4
(elixir 1.16.2) lib/process.ex:860: Process.info/2
(ash 3.0.0-rc.45) lib/ash/error/unknown.ex:3: Ash.Error.Unknown."exception (overridable 2)"/1
(ash 3.0.0-rc.45) /Users/bes/code/demo_ash_sqlite/deps/splode/lib/splode.ex:211: Ash.Error.to_class/2
(ash 3.0.0-rc.45) lib/ash/error/error.ex:66: Ash.Error.to_error_class/2
(ash 3.0.0-rc.45) lib/ash/actions/update/update.ex:291: Ash.Actions.Update.do_run/4
(ash 3.0.0-rc.45) lib/ash/actions/update/update.ex:220: Ash.Actions.Update.run/4
(ash 3.0.0-rc.45) lib/ash.ex:2423: Ash.update/3
iex:6: (file)
And both records are in the database get updated to have the same subject:
iex(7)> Ash.read!(DemoAshSqlite.Example.Thing)
23:49:14.557 [debug] QUERY OK source="things" db=0.4ms idle=1719.6ms
SELECT t0."id", t0."subject" FROM "things" AS t0 []
[
#DemoAshSqlite.Example.Thing<
__meta__: #Ecto.Schema.Metadata<:loaded, "things">,
id: "5b8f496d-00cf-43e7-bfa5-54ef0f8eb3a2",
subject: "Second thing after update",
aggregates: %{},
calculations: %{},
...
>,
#DemoAshSqlite.Example.Thing<
__meta__: #Ecto.Schema.Metadata<:loaded, "things">,
id: "727c16ee-592a-4fdc-b07f-0592946c6a75",
subject: "Second thing after update",
aggregates: %{},
calculations: %{},
...
>
]
Expected behavior
It is expected that only the record being updated would get updated, and that there would be no error.
** Runtime
{:ash, "~> 3.0.0-rc.45"}
{:ash_sqlite, "~> 0.1.2-rc.0"}
Additional context
Here is a unit test that will fail with ash_sqlite
, but pass for ash_postgres
(assuming one changes AshSqlite
to AshPostgres
). Please feel free to use and/or modify it if it is useful.
# test/update_test.exs
defmodule AshSqlite.Test.UpdateTest do
use AshSqlite.RepoCase, async: false
alias AshSqlite.Test.Post
require Ash.Query
test "updating a record when multiple records are in the table will only update the desired record" do
# This test is here because of a previous bug in update that caused
# all records in the table to be updated.
id_1 = Ash.UUID.generate()
id_2 = Ash.UUID.generate()
new_post_1 =
Post
|> Ash.Changeset.for_create(:create, %{
id: id_1,
title: "new_post_1"
})
|> Ash.create!()
_new_post_2 =
Post
|> Ash.Changeset.for_create(:create, %{
id: id_2,
title: "new_post_2"
})
|> Ash.create!()
{:ok, updated_post_1} =
new_post_1
|> Ash.Changeset.for_update(:update, %{
title: "new_post_1_updated"
})
|> Ash.update()
# It is deliberate that post 2 is re-fetched from the db after the
# update to post 1. This ensure that post 2 was not updated.
post_2 = Ash.get!(Post, id_2)
assert updated_post_1.id == id_1
assert updated_post_1.title == "new_post_1_updated"
assert post_2.id == id_2
assert post_2.title == "new_post_2"
end
end
What are the types that can be stored in Sqlite? I've tried to store Maps and Atoms but for example when I have a :map
attribute I get the following when I try to create a Resource:
** (Ash.Error.Unknown) Unknown Error
* ** (FunctionClauseError) no function clause matching in Ecto.Type.process_dumpers/3
I have an application where I want to persist requests to some API. I cannot assume I know the schema because it is subject to change - the simplest way would be to have a :map
attribute on my resource that represents an API call.
Resource
with fields that cannot be persisted in SqliteEcto changed their internal representation from a 3-tuple to a 2-tuple with this commit.
As mentioned here in the original issue, Ash currently relies on this internal representation in some places in the codebase.
This issue tracks that this gets handled before before the Ecto dependency is updated.
Given that the application could override the dependency, we should either support both formats when comparing or rely on functions in the Ecto.Type
/Ecto.ParameterizedType
modules to do so (possibly adding some more upstream if something is missing).
For creating, using Ecto.ParameterizedType.init/2
should be enough.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.