GithubHelp home page GithubHelp logo

ash-project / ash_sqlite Goto Github PK

View Code? Open in Web Editor NEW
14.0 6.0 8.0 342 KB

The SQLite data layer for Ash Framework.

Home Page: https://hexdocs.pm/ash_sqlite

License: MIT License

Elixir 100.00%
ash data-layer elixir sqlite

ash_sqlite's People

Contributors

barnabasj avatar dependabot[bot] avatar jimsynz avatar joelpaulkoch avatar legrec14 avatar zachdaniel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ash_sqlite's Issues

Update action on a resource updates all records in the table and fails with `CaseClauseError`

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:

  1. Starting with an empty table for a resource
  2. Create two records
  3. Update one record and note that it fails
  4. Read all records for the resource and note they have all been updated

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

case result do
. Also note the SQL does not include a 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

  • Elixir 1.16.2
  • Erlang 26.2.4
  • macOS Sonoma 14.4.1
  • {: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

Allowed datatypes

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

Why I need Maps

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.

What would be helpful

  • make it possible to store structs/maps
    I believe this might be tricky, but maybe a workaround is possible? I mean in principle it should be doable because we can always use some kind of adapter to store maps as strings in JSON and then save/load them so that the Resource definition actually has Maps
  • throw an error when someone tries to define a Resource with fields that cannot be persisted in Sqlite

Handle the new Ecto parameterized type format

Ecto 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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.