Mnesia execution not stable inside mix task

hi, i’m trying to “init” or “setup” default mnesia database for my application with mix task, here my mix task

defmodule Mix.Tasks.Database do
  use Mix.Task

  @shortdoc "Init Database"
  def run(_) do
    :net_kernel.start([:danilla@master, :shortnames])

    :mnesia.start()

    :mnesia.create_schema([node()])

    IO.puts("Creating tables ...")

    case :mnesia.create_table(Users,
           attributes: [:username, :password, :tfa_key, :tfa_used],
           disc_copies: [node()]
         ) do
      {:atomic, :ok} ->
        IO.puts("Users table created successfully !")

      {:aborted, {:already_exists, Users}} ->
        IO.puts("Users table already exist !")

      {:aborted, {:bad_type, Users, :disc_copies, :danilla@master}} ->
        IO.puts("Error, disc_copies failed")

      _ ->
        IO.puts("Unknown Error !")
    end

    case :mnesia.create_table(Secrets,
           attributes: [:id, :type, :value],
           disc_copies: [node()]
         ) do
      {:atomic, :ok} ->
        IO.puts("Secrets table created successfully !")

      {:aborted, {:already_exists, Secrets}} ->
        IO.puts("Secrets table already exist !")

      {:aborted, {:bad_type, Secrets, :disc_copies, :danilla@master}} ->
        IO.puts("Error, disc_copies failed")

      _ ->
        IO.puts("Unknown Error !")
    end

    # in iex `:mnesia.dirty_read({Users, "admin"})` return []
    case :mnesia.dirty_read({Users, "admin"}) do
      {:aborted, {:no_exists, [Users, "admin"]}} ->
        tfa_secret = random_string(12)
        case :mnesia.dirty_write({Users, "admin", "$2b$06$YChCKP6dY3R1zysxIRC8peB2rxqTT0b15r.K1PxLyCGVjdu6AuGku", tfa_secret, false}) do
          :ok ->
            IO.puts("Default user has been created !")
          _ ->
            IO.puts("Unknown Error !")
        end
      _ ->
        IO.puts ("Unknown Error !")
    end

    :mnesia.stop()
  end

  def random_string(length) do
    :crypto.strong_rand_bytes(length) |> Base.url_encode64 |> binary_part(0, length)
  end
end

what i need is to create :

  1. a mnesia that save the data to disk
  2. same error that like execution in iex

some error that i found :

Creating tables ...        
Error, disc_copies failed  
Error, disc_copies failed
** (exit) {:aborted, {:no_exists, [Users, "admin"]}}               
    (mnesia) mnesia.erl:355: :mnesia.abort/1                       
    lib/mix/tasks/database.ex:48: Mix.Tasks.Database.run/1         
    (mix) lib/mix/task.ex:331: Mix.Task.run_task/3                 
    (mix) lib/mix/cli.ex:79: Mix.CLI.run_task/2                    
    (elixir) lib/code.ex:767: Code.require_file/2                  

note:

i just try to run mix database again, then the result is :

Creating tables ...                              
Users table created successfully !               
Secrets table created successfully !             
Uknown Error ! (i think it's come from record creation)

lol weird

    # before i invoke :mnesia.schema() i got `{:no_exists, Users}`
    :mnesia.schema()

    check_record = fn ->
      :mnesia.read({Users, "admin"})
    end

    case {type, result} = :mnesia.transaction(check_record) do
      {:atomic, []} ->
        tfa_secret = random_string(12)

        case :mnesia.dirty_write(
               {Users, "admin", "$2b$06$YChCKP6dY3R1zysxIRC8peB2rxqTT0b15r.K1PxLyCGVjdu6AuGku",
                tfa_secret, false}
             ) do
          :ok ->
            IO.puts("Default user has been created !")

          _ ->
            IO.puts("Unknown Error !")
        end

      _ ->
        IO.inspect(result)
        IO.puts("Unknown Error !")
    end

    :mnesia.stop()
  end

  def random_string(length) do
    :crypto.strong_rand_bytes(length) |> Base.url_encode64() |> binary_part(0, length)
  end
erere@lime:/path/to/elixir/danilla$ iex --name danilla@master                                    
Erlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]      
                                                                                                
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)                     
iex(danilla@master)1> :mnesia.start()                                                           
:ok                                                                                             
iex(danilla@master)2> :mnesia.dirty_read({Users, "admin"})                                      
[                                                                                               
  {Users, "admin",                                                                              
   "$2b$06$YChCKP6dY3R1zysxIRC8peB2rxqTT0b15r.K1PxLyCGVjdu6AuGku",                              
   "m3wuzfIdO8C2", false}                                                                       
]

weird, why i must invoke that :mnesia.schema()

:mnesia tables are loaded asynchronously by default, you may need to use http://erlang.org/doc/man/mnesia.html#wait_for_tables-2

1 Like

idk why but :

  1. i remove Mnesia.danilla@master folder
  2. i run mix database again, but i got disc_copies failed
  3. i remove :mnesia.start() (in L8), and run it again, got {:aborted, {:node_not_running, :danilla@master}} (of course)
  4. i add :mnesia.start() back, and run mix database again and all working perfectly

so i call :mnesia.create_schema([node()]) before :mnesia.start() and it’s working

Yes. Mnesia needs a schema for it to run. If it can’t find a schema then it will create a volatile one and no data will be saved on disk. You should only create the schema once. The problem here is that if you want to keep data between runs then you must create the schema once first, then every time you start erlang/elixir just do :mnesia.start(). All the data saved on disk will be kept in files in the schema directory.

4 Likes