Giter Site home page Giter Site logo

nebo15 / ecto_mnesia Goto Github PK

View Code? Open in Web Editor NEW
242.0 22.0 41.0 321 KB

Ecto adapter for Mnesia Erlang term database.

Home Page: https://hex.pm/packages/ecto_mnesia

License: MIT License

Shell 0.07% Elixir 99.93%
mnesia ecto erlang adapter ecto-adapter hex package elixir elixir-lang

ecto_mnesia's Introduction

Ecto adapter for Mnesia Erlang term database

Hex.pm Downloads Latest Version License Build Status Coverage Status Ebert

Ecto 2.X adapter for Mnesia Erlang term database. In most cases it can be used as drop-in replacement for other adapters.

Supported features:

  • Compatible Ecto.Repo API.
  • Automatically converts Ecto.Query structs to Erlang match_spec.
  • Emulated query.select and query.order_bys, select .. in [..]. (Emulating is slow for any large dataset, O(n * log n).)
  • Auto-generated (via sequence table) :id primary keys.
  • Migrations and database setup via Ecto.Migrations.
  • Transactions.
  • Secondary indexes.

Planned features:

  • Native primary key and unique index constraints.
  • Custom primary keys.
  • Other transactional contexts.

Not supported features (create issue and vote if you need them):

  • Type casting. Mnesia can store any data in any field, including strings, numbers, atoms, tuples, floats or even PID's. All types in your migrations will be silently ignored.
  • Mnesia clustering and auto-clustering.
  • Lookups in json fields.
  • Schemaless queries.
  • Composite primary keys.
  • Unique/all other constraints (including associations).
  • JOINs.
  • min, max, avg and other aggregation functions.
  • Intervals.

In general. This adapter is still not passing all Ecto integration tests and in active development. But it already can be helpful in simple use-cases.

Why Mnesia?

We have a production task that needs low read-latency database and our data fits in RAM, so Mnesia is the best choice: it's part of OTP, shares same space as our app does, work fast in RAM and supports transactions (it's critical for fintech projects).

Why do we need an adapter? We don't want to lock us to any specific database, since requirements can change. Ecto allows to switch databases by simply modifying the config, and we might want to go back to Postres or another DB.

Clustering and using Mnesia for your project

If you use Mnesia - you either get a distributed system from day one or a single node with low availability. Very few people really want any of that options. Specifically Mnesia it's neither an AP, nor a CP database; requires you to handle network partitions (split brains) manually; has much less documentation available compared to a more common databases (like PostgreSQL).

Please, pick your tools wisely and think through how you would use them in production.

Mnesia configuration from config.exs

config :ecto_mnesia,
  host: {:system, :atom, "MNESIA_HOST", Kernel.node()},
  storage_type: {:system, :atom, "MNESIA_STORAGE_TYPE", :disc_copies}

config :mnesia,
  dir: 'priv/data/mnesia' # Make sure this directory exists

Notice that {:system, [TYPE], ENV_NAME, default_value} tuples can be replaced with any raw values.

They tell adapter to read configuration from environment in run-time, so you will be able to set MNESIA_HOST and MNESIA_STORAGE_TYPE environment variables, which is very useful when you releasing app in production and don't want to rebuild all code on each config change.

If you want to know more how this tool works take look at Confex package.

Storage Types

  • :disc_copies - store data in both RAM and on disc. Recommended value for most cases.
  • :ram_copies - store data only in RAM. Data will be lost on node restart. Useful when working with large datasets that don't need to be persisted.
  • :disc_only_copies - store data only on disc. This will limit database size to 2GB and affect adapter performance.

Table Types (Engines)

In migrations you can select which kind of table you want to use:

create_if_not_exists table(:my_table, engine: :set) do
  # ...
end

Supported types:

  • :set - expected your records to have at least one unique primary key that should be in first column.
  • :ordered_set - default type. Same as :set, but Mnesia will store data in a table will be ordered by primary key.
  • :bag - expected all records to be unique, but no primary key is required. (Internally, it will use first field as a primary key).
Ordered Set Performance

Ordered set comes in a cost of increased complexity of write operations:

Set

Operation Average Worst Case
Space O(n) O(n)
Search O(1) O(n)
Insert O(1) O(n)
Delete O(1) O(n)

Ordered Set

Operation Average Worst Case
Space O(n) O(n)
Search O(log n) O(n)
Insert O(log n) O(n)
Delete O(log n) O(n)

Installation

It is available in Hex, the package can be installed as:

  1. Add ecto_mnesia to your list of dependencies in mix.exs:
def deps do
  [{:ecto_mnesia, "~> 0.9.0"}]
end
  1. Ensure ecto_mnesia is started before your application:
def application do
  [applications: [:ecto_mnesia]]
end
  1. Use EctoMnesia.Adapter as your Ecto.Repo adapter:
config :my_app, MyRepo,
  adapter: EctoMnesia.Adapter
  1. Optionally set custom Mnesia data dir (don't forget to create it):
config :mnesia, :dir, 'priv/data/mnesia'

The docs can be found at https://hexdocs.pm/ecto_mnesia.

Thanks

We want to thank meh for his Amnesia package that helped a lot in initial Mnesia investigations. Some pieces of code was copied from his repo.

Also big thanks to josevalim for Elixir, Ecto and active help while this adapter was developed.

ecto_mnesia's People

Contributors

alfert avatar andrewdryga avatar anuragjain67 avatar beardedeagle avatar jdoig avatar jonnystorm avatar mathieul avatar maxnordlund avatar michaelkschmidt avatar minijackson avatar nirev avatar qqwy avatar sztheory avatar veelenga avatar

Stargazers

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

Watchers

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

ecto_mnesia's Issues

Heading in the direction of a query engine

I've been tinkering with a similar concept recently (coyly called MatchQL). I set out to build an abstract MatchQL.Adapter with concrete rudimentary ETS, DETS, and Mnesia implementations. After a lot of research, I realized there just isn't enough manual locking capability to build a meaningful transaction in ETS or DETS, which invalidated a lot of the project, so I gave those up and was left with Mnesia.

At that point there isn't enough to differentiate that package from this one (that I referenced heavily during my own attempts), so I thought I'd shelve some thoughts I had around the package here before retiring the effort.

You've already polyfilled some SQL features for Mnesia:

adapter emulates query.select and query.order_bys behaviours

You've also expressed interest in supporting joins. From my own tinkering, that's much less trivial and would require some substantial rearchitecting of how queries are run, since you need to start dissecting an Ecto.Query into several :mensia.select calls, and properly recombining their results.

Once you have that figured out, though, you can polyfill several other SQL capabilities, since you've effectively built your own query planner and are halfway to a full-fledged SQL query engine that just happens to use Mnesia:

  • GROUP BY
  • SUM and ilk
  • HAVING
  • DISTINCT
  • UNION and ilk
  • OFFSET
  • LIMIT

I was wondering if you'd be interested in taking this project in that direction.


What you call a Ecto.Mnesia.Record.Context I'd termed a MatchQL.Query, representing a query against a single Mnesia table. Of course, only simple Ecto.Querys can translate into this limited form:

  • The match head interprets the table schema and converts the key-value table.property stuff into positional references
  • The match condition represents a WHERE clause
  • The match body filters out what's desired for the SELECT statement

(I fit these into a separate MatchQL.Spec struct independent of an explicit table reference.)

After execution, you apply the ORDER BY in post production. (MatchQL.Query was pretty much just the table reference, a MatchQL.Spec, and some order clauses.

The low hanging fruits are the other post-production clauses. You could throw DISTINCT, OFFSET, and LIMIT into there pretty easily.

Slightly harder are the GROUP BY, aggregates, and HAVING clauses. They have to come before the SELECT but after the actual table query.

They can be handled by:

  • Applying the non-virtual fields from the SELECT to a MatchQL.Query and getting the results
  • Inject aggregates into the result tuples where referenced by reducing and mapping over the results
  • Reducing over the new result tuples to GROUP BY criteria
  • Create a MatchQL.Spec out of HAVING just as you did WHERE but apply it to the raw result list instead of a Mnesia table

Joins are much more tricky, since you have to introspect the Ecto.Query to generate each individual MatchQL.Query, produce a topological sort to determine dependency order, and thread resolved variables in-between them. Only after the FROM+JOIN pipeline can the GROUP BY pipeline start.

Once you've done that, subqueries are mostly a recursive case of the topological-dependency logic, and UNIONs are just special-case post-processors that fully perform two queries and bang the result sets together, ordering them in the process.

Instead of trying to convert an Ecto.Query into a single-table MatchQL.Query, I was trying to introduce a higher-level MatchQL.Query.Plan data structure as the conversion target, that when executed would ideally work through all these phases using Flow. About then I noticed ecto_mnesia had done all the work I had done to date, only better.


I don't know if you have any interest in trying to build a full-fledged SQL query engine on top of Mnesia but I suspect implementing JOINs will force you to get most of the way there.

The main goal of my project was to become intimate with the Ecto, ETS, and Mnesia low-level APIs, which I feel I have succeeded at. The insight into how one would have to implement a query engine has already proved useful at work, especially in finally understanding (vs just remembering) why and when one has to use HAVING, which is a plus.

If you are interested in taking ecto_mnesia this direction, it would be cool to know in case I decide to pursue the concept further and contribute. If not, it's nice to know that my research notes for MatchQL got read by someone before getting scrapped. :)

Updating a field to `nil` is ignored

I'm unable to set a column to nil in an update. I've tracked it down to this line.

Is this desirable?

Since Repo.update only works with a changeset, it seems appropriate to allow columns to be set to nil if they are included in the changes key.

Create database folder if not exists

in my dev.exs I have:

config :mnesia,
  dir: 'priv/data/mnesia/dev' # Make sure this directory exists

But I don't created the dir mnesia/dev

Executing ecto.create I got:

04:40:59.240 [info]  ==> Setting Mnesia schema table copy type

04:40:59.251 [info]  ==> Ensuring Mnesia schema exists

04:40:59.302 [error] create_schema failed with reason {'Cannot create Mnesia dir', '/home/marcos/xxx/xxx/apps/xxx/priv/data/mnesia/dev', :enoent}
** (Mix) The database for xxx.Repo couldn't be created: :unknown

When I created the DIR /priv/data/mnesia/dev the commands works fine!

Cannot create mnesia dir

Hello,

Using latest version 0.6.5, I'm unable to run mix ecto.create, using the example project (from another issue): https://github.com/AndrewDryga/saturn

** (CaseClauseError) no case clause matching: {:error, {'Cannot create Mnesia dir', '/Users/fritz/PP/saturn/priv/mnesia/development', :enoent}}
    lib/ecto_mnesia/storage.ex:45: Ecto.Mnesia.Storage.storage_up/1
    lib/mix/tasks/ecto.create.ex:36: anonymous fn/3 in Mix.Tasks.Ecto.Create.run/1
    (elixir) lib/enum.ex:651: Enum."-each/2-lists^foreach/1-0-"/2
    (elixir) lib/enum.ex:651: Enum.each/2
    (mix) lib/mix/task.ex:296: Mix.Task.run_task/3
    (mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2
    (elixir) lib/code.ex:363: Code.require_file/2

If I create the dir myself everything works, didn't had this with version 0.6.1

Why am I getting `:no_exists on :id_seq`

test/models/user_test.exs:42
 ** (exit) {:aborted, {:no_exists, :id_seq}}
 stacktrace:
   (mnesia) mnesia.erl:318: :mnesia.abort/1
   (mnesia) mnesia.erl:1610: :mnesia.do_dirty_update_counter/4
   (ecto_mnesia) lib/ecto_mnesia/adapter.ex:239: anonymous fn/2 in Ecto.Mnesia.Adapter.put_new_pk/3
   (elixir) lib/keyword.ex:242: Keyword.get_and_update/4
   (ecto_mnesia) lib/ecto_mnesia/adapter.ex:238: Ecto.Mnesia.Adapter.put_new_pk/3
   (ecto_mnesia) lib/ecto_mnesia/adapter.ex:222: Ecto.Mnesia.Adapter.do_insert/4
   (ecto) lib/ecto/repo/schema.ex:459: Ecto.Repo.Schema.apply/4
   (ecto) lib/ecto/repo/schema.ex:198: anonymous fn/11 in Ecto.Repo.Schema.do_insert/4
   test/models/user_test.exs:43: (test)

my migration is (I've tried :set as well)

create_if_not_exists table(:users) do
  add :email, :string
  add :password_hash, :string

  timestamps()
end

I see an id_seq table in mnesia, but it is empty. Does it need to be seeded with something or did I do something else wrong? Thanks for the help.

Support types other than basic

Since Mnesia is not limited by types of data it stores, would be good idea to provide more basic types like atom, pid, tuple etc.
If I understand it correctly, right now I have to define my own Lib.Type.Atom module which is ok but I think would be nice to have out of the box with the Mnesia adapter.

Custom primary keys, it works?

Hello,

I see the readme stating that custom primary keys is still a work in progress but I managed to get it to work, my fear is that this is wrong and its not yet fully supported. Whats the current state of it?

Schema

  @primary_key {:uuid, :string, []}
  @derive {Phoenix.Param, key: :uuid}

  schema "device_configs" do
    field :environment, :string, default: "staging"
    field :configs, :map, default: %{}
    timestamps()
  end

Migration

    create_if_not_exists table(:device_configs, primary_key: false) do
      add :uuid, :string, primary_key: true
      add :environment, :string, default: "staging"
      add :configs, :map, default: %{}

      timestamps()
    end
DevicesEx.Repo.insert(%DevicesEx.DeviceConfig{uuid: "ACR001"})
{:ok,
 %DevicesEx.DeviceConfig{__meta__: #Ecto.Schema.Metadata<:loaded, "device_configs">,
  configs: %{}, environment: "staging",
  inserted_at: ~N[2016-12-18 15:50:21.840696],
  updated_at: ~N[2016-12-18 15:50:21.847592], uuid: "ACR001"}}
iex(5)> DevicesEx.Repo.all(query)
[debug] Selecting by match specification `[{{:device_configs, :"$1", :"$2", :"$3", :"$4", :"$5"}, [], [[:"$1", :"$2", :"$3", :"$4", :"$5"]]}]` with limit `nil`
[%DevicesEx.DeviceConfig{__meta__: #Ecto.Schema.Metadata<:built, "device_configs">,
  configs: %{}, environment: "staging",
  inserted_at: {{2016, 12, 18}, {15, 50, 21, 840696}},
  updated_at: {{2016, 12, 18}, {15, 50, 21, 847592}}, uuid: "ACR001"}]
iex(6)> DevicesEx.Repo.insert(%DevicesEx.DeviceConfig{uuid: "ACR001"})
** (CaseClauseError) no case clause matching: {:error, :already_exists}
    (ecto) lib/ecto/repo/schema.ex:459: Ecto.Repo.Schema.apply/4
    (ecto) lib/ecto/repo/schema.ex:198: anonymous fn/11 in Ecto.Repo.Schema.do_insert/4

Repository creation fails

I am using ecto_mnesia 0.7.1, ecto 2.1.4, Elixir 1.4.2, with the following configuration:

config :ecto_mnesia, host: node(), storage_type: :disc_copies
config :mnesia, dir: "priv/my_app/mnesia"
config :my_app, :ecto_repos, [MyApp.Repo]
config :my_app, MyApp.Repo, adapter: Ecto.Adapters.Mnesia

When I run mix ecto.create, the dir defined above (priv/my_app/mnesia) is created, and then the following occurs:

==> Setting Mnesia schema table copy type
==> Ensuring Mnesia schema exists
** (CaseClauseError) no case clause matching: {:error, {:EXIT, :function_clause}}
    lib/ecto_mnesia/storage.ex:46: Ecto.Mnesia.Storage.storage_up/1
    lib/mix/tasks/ecto.create.ex:36: anonymous fn/3 in Mix.Tasks.Ecto.Create.run/1
    (elixir) lib/enum.ex:645: Enum."-each/2-lists^foreach/1-0-"/2
    (elixir) lib/enum.ex:645: Enum.each/2
    (mix) lib/mix/task.ex:294: Mix.Task.run_task/3
    (mix) lib/mix/project.ex:313: Mix.Project.in_project/4
    (elixir) lib/file.ex:1162: File.cd!/2
    (mix) lib/mix/task.ex:394: anonymous fn/4 in Mix.Task.recur/1

In the course of investigating the problem, I started looking at the storage_up and conf functions in storage.ex, since that's where the error was occurring. Strangely, if I manually call :mnesia.create_schema [node()] in iex (which is the same thing that storage_up is doing when it falls over), it results in {:error, {:nonode@nohost, {:already_exists, :nonode@nohost}}}, which the case in storage_up expects.

If I add a call to :mnesia.start after change_table_copy_type but before create_schema in storage_up, it seems to work. I'm not sure if this is a flaw in ecto_mnesia or if I somehow need to start mnesia myself or configure something to do it for me, and I haven't been able to find any guidance online.

While probably unrelated to my issue, I also noticed that the :ecto_mnesia configuration I had set was simply being ignored. The defaults were being used all of the time, as the config parameter being passed into the conf function was [otp_app: :my_app, repo: MyApp.Repo, adapter: Ecto.Adapters.Mnesia], not the [host: ..., storage_type: ...] that it appears to be expecting. I'm not making it a separate issue, just in case all of this is the result of some misconfiguration or something.

Mnesia dir changes based upon context in umbrella project

Using Ecto 2.1.4, ecto_mnesia 0.9.0, and Elixir 19, there appears to be inconsistent behavior with respect to the path to mnesia's disc copies in umbrella projects.

During the execution of ecto.create and ecto.migrate, the mnesia files for the app under the umbrella are created relative to the app, as one might expect:

# run from the root of the umbrella, with config :mnesia, dir: 'priv/thingy/data' in config.exs
$ mix ecto.create

18:40:42.973 [info]  ==> Setting Mnesia schema table copy type

18:40:42.984 [info]  ==> Ensuring Mnesia schema exists
==> under_umbrella
The database for Thingy has been created
$ mix ecto.migrate

18:40:46.983 [debug] Selecting all records by match specification `[{{:schema_migrations, :"$1", :"$2"}, [], [[:"$1"]]}]` with limit nil

18:40:47.005 [info]  == Running Thingy.Migrations.MakeThingies.change/0 forward

18:40:47.005 [info]  create table if not exists thingies

18:40:47.024 [info]  == Migrated in 0.0s
$ ls apps/under_umbrella/priv/thingy/data/
DECISION_TAB.LOG	LATEST.LOG		id_seq.DCD		schema.DAT		schema_migrations.DCD	schema_migrations.DCL	schema_migrations.TMP	thingies.DCD

However, when the umbrella project is run, mnesia appears to be looking at the configured path relative to the root of the umbrella project, not relative to the app:

# run from the root of the umbrella, with config :mnesia, dir: 'priv/thingy/data' in config.exs
$ iex -S mix
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :mnesia.info
---> Processes holding locks <--- 
---> Processes waiting for locks <--- 
---> Participant transactions <--- 
---> Coordinator transactions <---
---> Uncertain transactions <--- 
---> Active tables <--- 
schema         : with 1        records occupying 421      words of mem
===> System info in version "4.14.3", debug level = none <===
opt_disc. Directory "/mnesia_paths/priv/thingy/data" is NOT used.
use fallback at restart = false
running db nodes   = [nonode@nohost]
stopped db nodes   = [] 
master node tables = []
remote             = []
ram_copies         = [schema]
disc_copies        = []
disc_only_copies   = []
[{nonode@nohost,ram_copies}] = [schema]
2 transactions committed, 0 aborted, 0 restarted, 0 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
:ok

You'll notice that it's looking for the mnesia files in /priv/thingy/data, not /apps/under_umbrella/priv/thingy/data, and so things don't work. If you run the ecto.create and ecto.migrate commands with the aforementioned configuration, and then change config.exs to use apps/under_umbrella/priv/thingy/data before running iex, things work as expected:

# run from the root of the umbrella, with config :mnesia, dir: 'apps/under_umbrella/priv/thingy/data' in config.exs
$ iex -S mix
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :mnesia.info
---> Processes holding locks <--- 
---> Processes waiting for locks <--- 
---> Participant transactions <--- 
---> Coordinator transactions <---
---> Uncertain transactions <--- 
---> Active tables <--- 
schema_migrations: with 1        records occupying 115      words of mem
schema         : with 4        records occupying 774      words of mem
thingies       : with 0        records occupying 304      words of mem
id_seq         : with 0        records occupying 304      words of mem
===> System info in version "4.14.3", debug level = none <===
opt_disc. Directory "/mnesia_paths/apps/under_umbrella/priv/thingy/data" is used.
use fallback at restart = false
running db nodes   = [nonode@nohost]
stopped db nodes   = [] 
master node tables = []
remote             = []
ram_copies         = []
disc_copies        = [id_seq,schema,schema_migrations,thingies]
disc_only_copies   = []
[{nonode@nohost,disc_copies}] = [id_seq,thingies,schema,schema_migrations]
2 transactions committed, 0 aborted, 0 restarted, 0 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
:ok

However, I don't know if there is a way to change the configuration based on context, though I doubt this is intentional behavior. I've created a repo that demonstrates the issue for you here.

Please let me know if you need anything else.

Custom configuration in config.exs in phoenix project ignored

In a phoenix app created from scratch I added into config.ex

config :ecto_mnesia,
  dir:  "priv/mnesia",
  storage_type: :disc_copies

However after running mix ecto.create I noticed that the mnesia files have been created in priv/data/mnesia which is default config I guess. My custom configuration seems to have been ignored.

Add support for column renaming in ecto migrations

** (FunctionClauseError) no function clause matching in Ecto.Mnesia.Storage.Migrator.execute/3
    lib/ecto_mnesia/storage/migrator.ex:12: Ecto.Mnesia.Storage.Migrator.execute(Aelita2.Repo, {:rename, %Ecto.Migration.Table{comment: nil, engine: nil, name: :batches, options: nil, prefix: nil, primary_key: true}, :project, :project_id}, [timeout: :infinity, log: false])

Getting :cyclic abort when using Repo.Transaction in case of multiple process.

If multiple process calling reserve function then it's throwing
Erlang error: {:cyclic, :nonode@nohost, {:reservationinfo, 5}, :write, :write, {:tid, 38, #PID<0.627.0>}}

   def reserve(owner_id, coordinates, type) do
      Repo.transaction(
        fn ->
          searchresults = Enum.map(coordinates, fn(x) -> {x, Repo.get_by(__MODULE__, coordinate: x)} end)
          reservation_result = List.foldl(searchresults, [],
               fn({coordinate, reservationinfo}, reservation_result) ->
                      Repo.update(reservationinfo, %{owner_id: owner_id, type: type}
               end
          )
      )
     end
    )

Solution
Root Cause - We should not do catch Mnesia.Transaction. Referring - StackOverflow (https://stackoverflow.com/a/8105191/2000121).

We are doing - https://github.com/Nebo15/ecto_mnesia/blob/master/lib/ecto_mnesia/table.ex#L194
When I commented that catch it's working now.

Migration failing with no case clause matching: {:aborted, {:bad_type, :id_seq....

When trying to migrate this simple migration via mix ecto.migrate, it failing with:

** (CaseClauseError) no case clause matching: {:aborted, {:bad_type, :id_seq, :disc_copies, :nonode@nohost}}
    lib/ecto_mnesia/storage/migrator.ex:134: Ecto.Mnesia.Storage.Migrator.do_create_table/4
    lib/ecto_mnesia/storage/migrator.ex:26: Ecto.Mnesia.Storage.Migrator.execute/3
    (ecto) lib/ecto/migrator.ex:43: Ecto.Migrator.migrated_versions/2
    (ecto) lib/ecto/migrator.ex:142: Ecto.Migrator.run/4
    (ecto) lib/mix/tasks/ecto.migrate.ex:84: anonymous fn/4 in Mix.Tasks.Ecto.Migrate.run/2
    (elixir) lib/enum.ex:651: Enum."-each/2-lists^foreach/1-0-"/2
    (elixir) lib/enum.ex:651: Enum.each/2
    (mix) lib/mix/task.ex:296: Mix.Task.run_task/3
    (mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2
    (elixir) lib/code.ex:363: Code.require_file/2

Unique index , associations and :in query clausal

I am using Mnesia adapter but it does not support:
Unique/all other constraints (including associations) and :in query clausule

Let me give you a vote to take importance to development this features.
Regards
Jesus Enrique Aldana Sanchez

Field `:id` does not exist in table

I'm planning to use ecto_mnesia for a project as a message store, but I can't get it to work.

 create_if_not_exists table(:messages, engine: :set) do
 # ...
config :ecto_mnesia,
  host: Kernel.node(),
  storage_type: :ram_copies
iex(3)> MnesiaRepo.all(Message)
** (ArgumentError) Field `:id` does not exist in table `:messages`
    (ecto_mnesia) lib/ecto_mnesia/record/context.ex:91: EctoMnesia.Record.Context.find_field_placeholder!/2
         (elixir) lib/enum.ex:1229: Enum."-map/2-lists^map/1-0-"/2
    (ecto_mnesia) lib/ecto_mnesia/record/context/match_spec.ex:17: EctoMnesia.Record.Context.MatchSpec.update/2
    (ecto_mnesia) lib/ecto_mnesia/planner.ex:57: EctoMnesia.Planner.execute/6
           (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
           (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4

Any thoughts?

Thank you!

Record does not get deleted in on_exit callback

Within a ExUnit test, I use an on_exit callback to cleanup the database after testing:

IO.inspect(My.Account |> My.Repo.all) # shows a record
My.Account
|> Ecto.Query.where(my_id: ^my_id)
|> My.Repo.delete_all
IO.inspect(My.Account |> My.Repo.all) # shows []

After testing, I run iex - S mixand then My.Account |> My.Repo.all, which still shows the same record. Is this result to be expected? Why doesn't the record get deleted? Am I missing anything?

My best guess is that the delete does not get synced to disk. If this is the case, is there an idiomatic way to force the sync?

Thanks!

Ecto.Schema.Metadata state is built instead of loaded when retrieving a record

When retrieving a saved record from Mnesia the record's state is built instead of loaded as one would expect

iex> TestRepo.get(SellOffer, 5)
%SellOffer{__meta__: #Ecto.Schema.Metadata<:built, "sell_offer">, age: nil,
 application: nil, book_value: nil, booked_at: nil, dividable: nil, dpc: nil,
 dpd: nil, ended_at: nil, guaranteed: nil, id: 5, income: nil,
 inserted_at: {{2016, 12, 20}, {14, 6, 1, 189118}}, loan_changes: nil,
 loan_currency: nil, loan_duration: nil, loan_id: nil, loan_oap: nil,
 loan_product_type: nil, loan_risk_class: nil, loan_risk_subclass: nil,
 loan_status: nil, max_shared_apr: nil, min_price_rate: nil, status: nil,
 trader_id: nil, updated_at: {{2016, 12, 20}, {14, 6, 1, 189123}}}

This is quite problematic when you want to call insert_or_update, it will always insert instead of calling update. A typical example (from the docs)

result =
  case MyRepo.get(Post, id) do
    nil  -> %Post{id: id} # Post not found, we build one
    post -> post          # Post exists, let's use it
  end
  |> Post.changeset(changes)
  |> MyRepo.insert_or_update

case result do
  {:ok, struct}       -> # Inserted or updated with success
  {:error, changeset} -> # Something went wrong
end

When inserting or deleting a record the returned element is loaded or deleted.

Support variable interpolation

Hello,

Variable interpolation in a where clause is not working.

%Protocol.UndefinedError{description: "", protocol: Enumerable, value: {:^, [], [0]}}
  1) test where where `in` strings (Ecto.Mnesia.Context.MatchSpecTest)
     test/unit/match_spec_test.exs:162
     ** (Protocol.UndefinedError) protocol Enumerable not implemented for {:^, [], [0]}
     stacktrace:
       (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
       (elixir) lib/enum.ex:116: Enumerable.reduce/3
       (elixir) lib/enum.ex:1636: Enum.reduce/3
       (elixir) lib/enum.ex:1188: Enum.map/2
       (ecto_mnesia) lib/ecto_mnesia/record/context/match_spec.ex:84: Ecto.Mnesia.Record.Context.MatchSpec.match_condition/3
       (ecto_mnesia) lib/ecto_mnesia/record/context/match_spec.ex:66: Ecto.Mnesia.Record.Context.MatchSpec.match_conditions/4
       (ecto_mnesia) lib/ecto_mnesia/record/context/match_spec.ex:22: Ecto.Mnesia.Record.Context.MatchSpec.update/2
       test/unit/match_spec_test.exs:21: Ecto.Mnesia.Context.MatchSpecTest.ms/1
       test/unit/match_spec_test.exs:166: (test)

The test I'm currently using

test "where `in` strings" do
   variable = ["ok", "canceled"]
   query = from so in SellOffer, where: so.status in ^variable
   assert [{_, [{:or, {:==, @status_field_id, "ok"}, {:==, @status_field_id, "canceled"}}], _}] = ms(query)
end

I'm willing to contribute but a point in the good direction might help, currently I see a difference in the query param passed to def update in match_spec.

Ecto.Query<from s in SellOffer, where: s.status in ["ok", "canceled"]

vs

#Ecto.Query<from s in SellOffer, where: s.status in ^["ok", "canceled"]>

[error] Could not retrieve migrated versions.

First, thanks for this driver/lib, it's great to get mensia with ecto.

For this issue, sometimes I run my test suite and I get this error. If I just keep rerunning my test suite it will eventually run without the error, not sure what the problem is.

[error] Could not retrieve migrated versions. This error usually happens due to the following:

  * The database does not exist
  * The "schema_migrations" table, which Ecto uses for managing
    migrations, was defined by another library

To fix the first issue, run "mix ecto.create".

To address the second, you can run "mix ecto.drop" followed by
"mix ecto.create". Alternatively you may configure Ecto to use
another table for managing migrations:

    config :foo, Foo.Repo,
      migration_source: "some_other_table_for_schema_migrations"

The full error report is shown below.

** (RuntimeError) Schema :schema_migrations does not exist
    lib/ecto_mnesia/table.ex:165: EctoMnesia.Table.transaction/2
    lib/ecto_mnesia/planner.ex:62: EctoMnesia.Planner.execute/6
    (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
    (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4
    (ecto) lib/ecto/migrator.ex:284: Ecto.Migrator.verbose_schema_migration/3
    (ecto) lib/ecto/migrator.ex:155: Ecto.Migrator.run/4
    (ecto) lib/mix/tasks/ecto.migrate.ex:84: anonymous fn/4 in Mix.Tasks.Ecto.Migrate.run/2
    (elixir) lib/enum.ex:675: Enum."-each/2-lists^foreach/1-0-"/2

Sandbox mode for tests

We need a way to emulate transaction trap that will work with Ecto.Adapters.SQL.Sandbox.

Relates to #15.

order_by: and limit: seem to be EXTREMELY slow

Unless I am mistaken (and please tell me if I am; it would make me very happy ^.^), the ordering and limiting behaviour that EctoMnesia uses is emulated in a highly non-scalable way:

  • First, everything is selected from the database
  • Then, everything is sorted to adhere to the order_by statement (using Enum.sort and the like)
  • Then, we chop off the tail, to adhere to the limit.

This means that any and all selection procedures, regardless of how few results we want to return run in O(n * log n), which is extremely unacceptable for all but the smallest datasets (like ones that will never grow).

I am referring to locations like:

https://github.com/Nebo15/ecto_mnesia/blob/master/lib/ecto_mnesia/planner.ex#L43


Proposal:

  1. Since this is a very significant limitation of this adapter vs. other adapters, make this clear in the README.
  2. It might also be possible to display warnings when people do try to use order_by: and limit: statements in their code, to tell them that it is really slow.
  3. If we were to wrap qlc, we might be able to have proper support for order_by and limit, although I am not enough of an expert on qlc to be entirely certain.

mix ecto.* commands won't pickup the repo

If I run:

$ mix ecto.create
$

I get no output. The command silently exists.

On the contrary, If the repo is specified explicitly, there's some meaningful output:

$ mix ecto.create -r MyApp.Repo
==> Ensuring Mnesia data directory exists: priv/data/mnesia
==> Ensuring Mnesia schema exists
The database for Marketplace.Repo has already been created
$

Same is valid for mix ecto.migrate command.

** (RuntimeError) Schema :id_seq does not exist

=INFO REPORT==== 13-Mar-2017::20:23:11 ===
application: logger
exited: stopped
type: temporary
** (Mix) Could not start application crypto_exchange: CryptoExchange.Application.start(:normal, []) returned an error: shutdown: failed to start child: CryptoExchange.CryptoCrawler
** (EXIT) an exception was raised:
** (RuntimeError) Schema :id_seq does not exist
(ecto_mnesia) lib/ecto_mnesia/table.ex:152: Ecto.Mnesia.Table.transaction/2
(ecto_mnesia) lib/ecto_mnesia/adapter.ex:250: Ecto.Mnesia.Adapter.transaction/3
(elixir) lib/enum.ex:645: Enum."-each/2-lists^foreach/1-0-"/2
(elixir) lib/enum.ex:645: Enum.each/2
(crypto_exchange) lib/crypto_exchange/crypto_crawler.ex:15: CryptoExchange.CryptoCrawler.init/1
(stdlib) gen_server.erl:328: :gen_server.init_it/6
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
PS C:\Users\Fabi\Desktop\Dev\web\crypto_exchange_umbrella> ** (RuntimeErro

my schemas:
defmodule CryptoExchange.Currencies do
@moduledoc false
use Ecto.Schema

schema "currencies" do
field :long_name, :string
has_many :currencies_data, CryptoExchange.CurrenciesData
field :short_name, :string
end
end
defmodule CryptoExchange.CurrenciesData do
@moduledoc false
use Ecto.Schema
@primary_key false
schema "currencies_data" do
belongs_to :base, CryptoExchange.Currencies
belongs_to :target, CryptoExchange.Currencies
field :timestamp, :integer
field :price, :float
timestamps
end
end

any idea?

Support for "rename table"

There should be a possibility to rename tables in migrations, like with Postgres or Mysql:
rename table(:old), to: table(:new)

Preload fail because the foreign key is used, instead of the primary key

I have the following schemas:

defmodule AnimeApi.Anime do
  use Ecto.Schema

  schema "animes" do
    field :title, :string
    field :url, :string
    has_many :themes, AnimeApi.Theme
  end
end

defmodule AnimeApi.Theme do
  use Ecto.Schema

  schema "themes" do
    field :title, :string
    field :type, :string
    belongs_to :anime, AnimeApi.Anime
    has_many :videos, AnimeApi.Video # Ignore this
  end
end

When I try to preload the themes of an Anime it fails:

iex(1)> alias AnimeApi.{Anime, Repo, Theme}
[AnimeApi.Anime, AnimeApi.Repo, AnimeApi.Theme]
iex(2)> Repo.get!(Anime, 1) |> Repo.preload(:themes)
** (FunctionClauseError) no function clause matching in EctoMnesia.Record.Ordering.sort/3

    The following arguments were given to EctoMnesia.Record.Ordering.sort/3:  

         # 1
        [
          %AnimeApi.Theme{
            __meta__: #Ecto.Schema.Metadata<:loaded, "themes">,
            anime: #Ecto.Association.NotLoaded<association :anime is not loaded>,
            anime_id: 1,
            id: 1,
            title: "ED \"Sayonara Solitaire\"",
            type: "ED",
            videos: #Ecto.Association.NotLoaded<association :videos is not loaded>
          },
          1
        ]

        # 2
        [
          %AnimeApi.Theme{
            __meta__: #Ecto.Schema.Metadata<:loaded, "themes">,
            anime: #Ecto.Association.NotLoaded<association :anime is not loaded>,
            anime_id: 1,
            id: 2,
            title: "OP \"Tsubasa wa Pleasure Line\"",
            type: "OP",
            videos: #Ecto.Association.NotLoaded<association :videos is not loaded>
          },
          1
        ]

        # 3
        [
          %Ecto.Query.QueryExpr{
            expr: [asc: {{:., [], [{:&, [], [0]}, :anime_id]}, [], []}],
            file: "/d/Programming/Elixir/anime_api/deps/ecto/lib/ecto/repo/preloader.ex",
            line: 165,
            params: nil
          }
        ]

    Attempted function clauses (showing 1 out of 1):

        defp sort([left], [right], ordering)

    (ecto_mnesia) lib/ecto_mnesia/record/ordering.ex:19: EctoMnesia.Record.Ordering.sort/3
    (stdlib) lists.erl:969: :lists.sort/2
    (ecto_mnesia) lib/ecto_mnesia/planner.ex:73: EctoMnesia.Planner.execute/6
    (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
    (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4
    (elixir) lib/enum.ex:1314: Enum."-map/2-lists^map/1-0-"/2

I think the error lies in the expression expr: [asc: {{:., [], [{:&, [], [0]}, :anime_id]}, [], []}], which should have use :id instead.

Sorry if this is the wrong place for posting this issue

^value in table.array query leads to an exception

in operator doesn't work as expected in the following code:

tag = "elixir"
query = from p in Post, where: ^tag in p.tags, order_by: [desc: p.id]
posts = Repo.all(query)

When run process is terminated with the following exception:

** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Enumerable not implemented for {{:., [], [{:&, [], [0]}, :tags]}, [], []}
        (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
        (elixir) lib/enum.ex:116: Enumerable.reduce/3
        (elixir) lib/enum.ex:1776: Enum.map/2
        (ecto_mnesia) lib/ecto_mnesia/record/context/match_spec.ex:89: Ecto.Mnesia.Record.Context.MatchSpec.condition_expression/3
        (ecto_mnesia) lib/ecto_mnesia/record/context/match_spec.ex:63: Ecto.Mnesia.Record.Context.MatchSpec.match_conditions/4
        (ecto_mnesia) lib/ecto_mnesia/record/context/match_spec.ex:19: Ecto.Mnesia.Record.Context.MatchSpec.update/2
        (ecto_mnesia) lib/ecto_mnesia/adapter.ex:58: Ecto.Mnesia.Adapter.execute/6
        (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
        (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4
...
...

relevant schema part is

field :tags, {:array, :string}

Any advice? Thanks!

Problem with queries including timestamps

Expected behaviour

Queries like

from(m in Message, where: m.inserted_at < ^sent_before_datetime, order_by: [desc: :inserted_at], limit: 10)
|> Repo.all()

run without problem, and result in a list of ten message structs based on the given sent_before_datetime.

Actual Behaviour

We get errors like this one:

[debug] Selecting all records by match specification `[{{:message, :"$1", :"$2", :"$3", :"$4", :"$5", :"$6"}, [{:<, :"$5", {{2018, 6, 12}, {16, 13, 26, 211971}}}], [[:"$1", :"$2", :"$3", :"$4", :"$5", :"$6"]]}]` with limit nil
** (ArgumentError) argument error: [:message, [{{:message, :"$1", :"$2", :"$3", :"$4", :"$5", :"$6"}, [{:<, :"$5", {{2018, 6, 12}, {16, 13, 26, 211971}}}], [[:"$1", :"$2", :"$3", :"$4", :"$5", :"$6"]]}]]
    (mnesia) mnesia.erl:492: :mnesia.wrap_trans/6
    (ecto_mnesia) lib/ecto_mnesia/table.ex:203: EctoMnesia.Table.do_activity/2
    (ecto_mnesia) lib/ecto_mnesia/table.ex:193: EctoMnesia.Table.activity/2
    (ecto_mnesia) lib/ecto_mnesia/table.ex:172: EctoMnesia.Table.transaction/2
    (ecto_mnesia) lib/ecto_mnesia/planner.ex:71: EctoMnesia.Planner.execute/6
    (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
    (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4

What is going on:

Timestamps are converted into the Erlang format when stored in Mnesia (the nested two-tuple format, c.f. NaiveDateTime.utc_now() |> NaiveDateTime.to_erl)

Inside match specs, tuples are to be treated specially, however, and need to be nested inside an extra pair of curly braces to properly work (otherwise they are considered part of the match specification rather than a value).

An example of a proper match spec would be the result of this:

iex> :ets.fun2ms(fn {:message, a, b, c, d, e, f} = x when e < {{2018, 6, 12}, {15, 10, 1, 991038}} -> x end)
[
  {{:message, :"$1", :"$2", :"$3", :"$4", :"$5", :"$6"},
   [{:<, :"$5", {{{{2018, 6, 12}}, {{15, 10, 1, 991038}}}}}], [:"$_"]}
]

As you can see,

{{2018, 6, 12}, {15, 10, 1, 991038}}

is transformed into

{{{{2018, 6, 12}}, {{15, 10, 1, 991038}}}}

, so both the outer and inner layer of tuples get an extra layer of {}.

Field :id does not exist

Its suppose to create a ID automatically on migration right?

Error:

iex(7)> Tools.Model.Repo.all(Tools.Model.Embedded.Schemas.Feed)
** (ArgumentError) Field `:id` does not exist in table `:feed`
    (ecto_mnesia) lib/ecto_mnesia/record/context.ex:91: EctoMnesia.Record.Context.find_field_placeholder!/2
         (elixir) lib/enum.ex:1229: Enum."-map/2-lists^map/1-0-"/2
    (ecto_mnesia) lib/ecto_mnesia/record/context/match_spec.ex:17: EctoMnesia.Record.Context.MatchSpec.update/2
    (ecto_mnesia) lib/ecto_mnesia/planner.ex:57: EctoMnesia.Planner.execute/6
           (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
           (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4
iex(7)> 

My Schema:

defmodule Tools.Model.Embedded.Schemas.Feed do
  use Ecto.Schema

  schema "feed" do
    field :name, :string
    field :csv_origin_path, :string
    field :feed, :map
    field :is_valid, :boolean

    timestamps
  end
end

Migration file:

defmodule Tools.Model.Repo.Migrations.Feeds do
  use Ecto.Migration

  def change do
    create_if_not_exists table(:feeds, engine: :ordered_set) do
       add :name, :string
       add :csv_origin_path, :string
       add :feed, :map
       add :is_valid, :boolean

       timestamps()
    end
  end
end

Error on migration containing `references(...)` field type

The README states:

Type casting. Mnesia can store any data in any field, including strings, numbers, atoms, tuples, floats or even PID's. All types in your migrations will be silently ignored.

I just tried to migrate a Postgres-app over to Ecto_Mnesia, and it was unable to run my migrations because I had references("table_name") fields in there. Since this is also 'just a type', I think better behaviour would be to ignore the result from this as well.

Limit does not support expressions

With SQL adapters like postgres you can write something like this:

limit = 42
MyRepo.all(from item in Item, limit: ^limit)

In ecto_mnesia, this produces an error like this:

** (FunctionClauseError) no function clause matching in Enum.take/2
 stacktrace:
   (elixir) lib/enum.ex:2334: Enum.take([[%SellOffer{__meta__: #Ecto.Schema.Metadata<:loaded, "sell_offer">, age: 11, application: nil, book_value: nil, booked_at: nil, dividable: nil, dpc: nil, dpd: nil, ended_at: nil, guaranteed: nil, id: 1, income: nil, inserted_at: ~N[2017-07-02 02:32:52.743812], loan_changes: nil, loan_currency: nil, loan_duration: nil, loan_id: "hello", loan_oap: nil, loan_product_type: nil, loan_risk_class: nil, loan_risk_subclass: nil, loan_status: nil, max_shared_apr: nil, min_price_rate: nil, status: nil, trader_id: nil, updated_at: ~N[2017-07-02 02:32:52.743819]}], [%SellOffer{__meta__: #Ecto.Schema.Metadata<:loaded, "sell_offer">, age: 21, application: nil, book_value: nil, booked_at: nil, dividable: nil, dpc: nil, dpd: nil, ended_at: nil, guaranteed: nil, id: 3, income: nil, inserted_at: ~N[2017-07-02 02:32:52.744109], loan_changes: nil, loan_currency: nil, loan_duration: nil, loan_id: "world", loan_oap: nil, loan_product_type: nil, loan_risk_class: nil, loan_risk_subclass: nil, loan_status: nil, max_shared_apr: nil, min_price_rate: nil, status: nil, trader_id: nil, updated_at: ~N[2017-07-02 02:32:52.744113]}], [%SellOffer{__meta__: #Ecto.Schema.Metadata<:loaded, "sell_offer">, age: 15, application: nil, book_value: nil, booked_at: nil, dividable: nil, dpc: nil, dpd: nil, ended_at: nil, guaranteed: nil, id: 2, income: nil, inserted_at: ~N[2017-07-02 02:32:52.743973], loan_changes: nil, loan_currency: nil, loan_duration: nil, loan_id: "hello", loan_oap: nil, loan_product_type: nil, loan_risk_class: nil, loan_risk_subclass: nil, loan_status: nil, max_shared_apr: nil, min_price_rate: nil, status: nil, trader_id: nil, updated_at: ~N[2017-07-02 02:32:52.743977]}]], {:^, [], [0]})
   (ecto_mnesia) lib/ecto_mnesia/planner.ex:65: EctoMnesia.Planner.execute/6
   (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
   (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4
   test/unit/adapter_test.exs:378: (test)

Limit works fine if you give it a literal like this:

    MyRepo.all(from item in Item, limit: 42)

It's only a problem if you use a ^ expression

Expressions do seem to work for order_by, just not limit, e.g. this works fine:

field = :name
MyRepo.all(from item in Item, order_by: [desc: ^field])

Here are some tests that demonstrate the issue:

troygnichols@ae0a038

"Schema :users does not exist" while the DB has been created and migrated

Hello ๐Ÿ‘‹,

I am having some trouble when deploying a Phoenix project using ecto_mnesia.

My config is the following:

# config.exs
use Mix.Config

config :repository, Repository, adapter: EctoMnesia.Adapter

config :ecto_mnesia,
  host: :habitibot,
  storage_type: :disc_copies

config :repository, ecto_repos: [Repository]

import_config "#{Mix.env()}.exs"

# dev.exs
use Mix.Config

config :mnesia, :dir, '/tmp/mnesia'

# prod.exs
use Mix.Config

config :mnesia, :dir, '/var/lib/mnesia'

When I am testing the app everything works great, but trying to deploy fails with an error stating that the :users table is missing:

# I am building the things in an Ubuntu 16.04 container to match my deploy server
root@20e6d5e295d8:/app# cd /app/ \
                        && MIX_ENV=prod mix release \
                        && _build/prod/rel/habitibot/bin/habitibot migrate \
                        && PORT=8000 _build/prod/rel/habitibot/bin/habitibot foreground

# Building release
==> Assembling release..
==> Building release habitibot:0.1.1 using environment prod
==> Including ERTS 9.3 from /usr/lib/erlang/erts-9.3
==> Packaging release..
==> Release successfully built!
    You can run it in one of the following ways:
      Interactive: _build/prod/rel/habitibot/bin/habitibot console
      Foreground: _build/prod/rel/habitibot/bin/habitibot foreground
      Daemon: _build/prod/rel/habitibot/bin/habitibot start

# Running the migration
Loading repository..
Starting dependencies..
:habitibot
Starting repos..
==> Migrate all the repos
[Repository]
==> Run migration
14:45:52.205 [info] ==> Setting Mnesia schema table copy type
14:45:52.293 [info] ==> Ensuring Mnesia schema exists
Running migrations for repository
14:45:52.529 [info] == Running Repository.Migrations.CreateUser.change/0 forward
14:45:52.529 [info] create table if not exists users
14:45:52.541 [info] == Migrated in 0.0s
14:45:52.589 [info] == Running Repository.Migrations.AddIndexInUser.change/0 forward
14:45:52.590 [info] create index users_user_id_quest_bot_index
14:45:52.604 [info] == Migrated in 0.0s
Success!

# Starting the server
14:48:22.417 [info] Running HabitibotWeb.Endpoint with Cowboy using http://:::8000
14:48:24.830 [info] GET /

#...Doing stuff that will hit the users table...
14:49:17.554 [error] #PID<0.1647.0> running HabitibotWeb.Endpoint terminated
Server: localhost:8000 (http)
Request: POST /session
** (exit) an exception was raised:
    ** (RuntimeError) Schema :users does not exist
        (ecto_mnesia) lib/ecto_mnesia/table.ex:165: EctoMnesia.Table.transaction/2
        (ecto_mnesia) lib/ecto_mnesia/planner.ex:62: EctoMnesia.Planner.execute/6
        (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
        (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4
        (ecto) lib/ecto/repo/queryable.ex:68: Ecto.Repo.Queryable.one/4
        (habitibot_web) lib/habitibot_web/controllers/session_controller.ex:67: HabitibotWeb.SessionController.check_user_exist_locally/1
        (habitibot_web) lib/habitibot_web/controllers/session_controller.ex:17: HabitibotWeb.SessionController.create/2
        (habitibot_web) lib/habitibot_web/controllers/session_controller.ex:1: HabitibotWeb.SessionController.action/2

If I start a console and check the mnesia info I get:

iex([email protected])1> :mnesia.info
---> Processes holding locks <--- 
---> Processes waiting for locks <--- 
---> Participant transactions <--- 
---> Coordinator transactions <---
---> Uncertain transactions <--- 
---> Active tables <--- 
schema         : with 4        records occupying 812      words of mem
===> System info in version "4.15.3", debug level = none <===
opt_disc. Directory "/var/lib/mnesia" is used.
use fallback at restart = false
running DB nodes   = ['[email protected]']
stopped DB nodes   = [nonode@nohost] 
master node tables = []
remote             = [id_seq,schema_migrations,users]
ram_copies         = [schema]
disc_copies        = []
disc_only_copies   = []
[] = [id_seq,users,schema_migrations]
[{'[email protected]',ram_copies}] = [schema]
2 transactions committed, 0 aborted, 0 restarted, 0 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
:ok

But some info looks weird here:

  • There is no disc_copies when my config only define disc copies.
  • Why is there a stopped DB node, while I only defined one host using the :habitibot atom?

You can access the repository here if you want to try directly. The migration code can be found here, and it is based on the one proposed in the Distillery doc.

Any clue what could be happening here?

'preload' results in 'Complex :in queries is not supported' error

When using an Ecto.Query.preload(:relation_name) in a pipeline with a query, the following error is thrown:

Complex :in queries is not supported by the Mnesia adapter.

This error is not indicative of what is happening here, so:

a) we need a better error.
b) maybe it is possible to support this feature after all?

Discuss the importance of Ecto migrations for ecto_mnesia

Hi @AndrewDryga,

Apologies if this is not the correct place for this but I would like to ask how important is migrations for ecto_mnesia. We are discussing the possibility of creating ecto_sql, to keep Ecto core small, and I am wondering if migrations should be moved to ecto_sql or be kept in the main Ecto repo.

I am thinking that for databases like Mnesia and Mongo, running user scripts as migrations, is more productive than hiding it behind a DSL but I would love your feedback on it.

Streams: EctoMnesia does not support Ecto streams, and the custom EctoMnesia.Table.Stream uses records rather than structs

I wanted to use MyApp.Repo.stream to upgrade some rows in my tables to a format with new constraints (in this case: Giving all of them a UUID, since all newly-inserted models will have this UUID).

So MyApp.Repo.stream will raise an error, stating that EctoMnesia.Table.Stream.new should be used instead.

However:

  1. This does not support queries, but only low-level table names.
  2. This gives results in record-format, rather than as schema structs. AFAIK, there is no way that EctoMnesia exposes turning records to schema structs. I ended up performing an additional MyRepo.get!(NameOfModel, elem(record, 1)), but this is obviously a non-performant and hackish 'solution'.

Migrate more :mnesia calls into the adapter

Hey,

I migrated from Amnesia into ecto_mnesia because I felt that it's more convenient for a Phoenix project.

So far as I looked at the code all the queries are based on matchers. Since mnesia has some of it's own mechanisms for some calls like :mnesia.read, :mnesia.index_read, etc.

At the moment I use those calls from the native Erlang module instead of Repo.get & Repo.get_by.

What's Your opinion on the matter?

I'm eager to contribute on this part if you can point me in the right direction, feel free to contact me.
Thanks!

Failing to order by a non unique field

First of all thank you for sharing ecto_mnesia! Using Mnesia via Ecto sounds a bit like magic ๐Ÿ‘.

I am running into a problem when ordering by a field with non unique values.

For instance:

# Example schema
defmodule Activity.Example do
  use Ecto.Schema
  import Ecto.Query, only: [from: 2]

  schema "examples" do
    field :name, :string
  end

  def recent(scope \\ __MODULE__) do
    from(e in scope, order_by: [desc: :name])
  end
end
# in iex
iex(3)> Ecto.Changeset.cast(%Activity.Example{}, %{name: "one"}, [:name]) |> Activity.Repo.insert()
{:ok,
 %Activity.Example{__meta__: #Ecto.Schema.Metadata<:loaded, "examples">, id: 1,
  name: "one"}}
iex(4)> Ecto.Changeset.cast(%Activity.Example{}, %{name: "two"}, [:name]) |> Activity.Repo.insert()
{:ok,
 %Activity.Example{__meta__: #Ecto.Schema.Metadata<:loaded, "examples">, id: 2,
  name: "two"}}
iex(5)> Ecto.Changeset.cast(%Activity.Example{}, %{name: "one"}, [:name]) |> Activity.Repo.insert()
{:ok,
 %Activity.Example{__meta__: #Ecto.Schema.Metadata<:loaded, "examples">, id: 3,
  name: "one"}}
iex(6)> Activity.Example.recent |> Activity.Repo.all
[debug] Selecting all records by match specification `[{{:examples, :"$1", :"$2"}, [], [[:"$1", :"$2"]]}]` with limit nil
** (FunctionClauseError) no function clause matching in EctoMnesia.Record.Ordering.cmp/3
    (ecto_mnesia) lib/ecto_mnesia/record/ordering.ex:30: EctoMnesia.Record.Ordering.cmp(%Activity.Example{__meta__: #Ecto.Schema.Metadata<:loaded, "examples">, id: 1, name: "one"}, %Activity.Example{__meta__: #Ecto.Schema.Metadata<:loaded, "examples">, id: 3, name: "one"}, [])
    (ecto_mnesia) lib/ecto_mnesia/record/ordering.ex:22: EctoMnesia.Record.Ordering.sort/3
         (stdlib) lists.erl:969: :lists.sort/2
    (ecto_mnesia) lib/ecto_mnesia/planner.ex:64: EctoMnesia.Planner.execute/6
           (ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
           (ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4

Looking at EctoMnesia.Record.Ordering.cmp it seems that the case where left and right are still equal after applying all join expressions is not handled.

I've fixed the problem for me by appending a defp cmp(_, _, []), do: :gt function declaration to EctoMnesia.Record.Ordering. But being a rookie in Mnesia I could obviously be missing something.

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.