Search code examples
elixirectotzdata

Argument error on :ets.lookup(:tzdata_current_release, :release_version) when running inside migration


I have this piece of code:

case Timex.Timezone.get(data) do
  {:error, _} = error ->
    error

  data ->
    {:ok, data}
end

to put timezones that are saved in the database into a struct.

Well when running a migration that gets some data through an Ecto Query I get this error:

** (ArgumentError) argument error
    (stdlib) :ets.lookup(:tzdata_current_release, :release_version)
    lib/tzdata/release_reader.ex:47: Tzdata.ReleaseReader.current_release_from_table/0
    lib/tzdata/release_reader.ex:14: Tzdata.ReleaseReader.simple_lookup/1
    lib/tzdata/release_reader.ex:7: Tzdata.ReleaseReader.zone_and_link_list/0
    lib/tzdata.ex:40: Tzdata.zone_exists?/1
    lib/timezone/timezone.ex:152: Timex.Timezone.name_of/1
    lib/timezone/timezone.ex:180: Timex.Timezone.get/2
    lib/common/ecto/timezone.ex:27: Common.Ecto.Timezone.load/1
    (ecto) lib/ecto/type.ex:661: Ecto.Type.process_loaders/3
    (ecto) lib/ecto/schema.ex:1490: Ecto.Schema.load!/5
    (ecto) lib/ecto/schema.ex:1442: Ecto.Schema.safe_load_zip/4
    (ecto) lib/ecto/schema.ex:1443: Ecto.Schema.safe_load_zip/4
    (ecto) lib/ecto/schema.ex:1430: Ecto.Schema.__safe_load__/6
    (ecto) lib/ecto/repo/queryable.ex:282: Ecto.Repo.Queryable.process_source/6
    (ecto) lib/ecto/repo/queryable.ex:170: Ecto.Repo.Queryable.preprocess/5
    (postgrex) lib/postgrex/query.ex:77: DBConnection.Query.Postgrex.Query.decode_map/3
    (postgrex) lib/postgrex/query.ex:64: DBConnection.Query.Postgrex.Query.decode/3
    (db_connection) lib/db_connection.ex:1019: DBConnection.decode/6
    (ecto) lib/ecto/adapters/postgres/connection.ex:73: Ecto.Adapters.Postgres.Connection.prepare_execute/5
    (ecto) lib/ecto/adapters/sql.ex:256: Ecto.Adapters.SQL.sql_call/6

Which has that code in the stack trace and doing some Inspects can verify that that's indeed the call that triggers the error, although doing:

iex(1)> Timex.Timezone.get("America/Los_Angeles")
#<TimezoneInfo(America/Los_Angeles - PDT (-07:00:00))>

In iex -S mix works.


Solution

  • This error happens because Timex needs to be started to function. This is normally done automatically if you add it to your mix.exs dependencies at application startup. However, in mix tasks you have to manually select which applications to start. In your custom mix tasks, you can make sure an application is started via Application.ensure_all_started(:timex).

    In your ecto.migrate case, we don't have access to the actual mix task, so we need to be a bit more creative by using mix aliases in your mix.exs file:

      def project do
        [
          ...
          aliases: aliases(),
          ...
        ] 
      end
    
    
      defp aliases do
        [
          "ecto.migrate_s": ["ecto.migrate.startup", "ecto.migrate"],
        ]
      end
    

    And a Task ecto.migrate.startup for our Application.ensure_all_started(:timex)

    defmodule Mix.Tasks.Ecto.Migrate.Startup do
      use Mix.Task
    
      def run(args) do
        Mix.shell.info("Starting apps required for ecto.migrate...")
        Application.ensure_all_started(:timex)
      end
    end
    

    Now you should be able to run mix ecto.migrate_s which first starts timex and then runs your migrations. (This is not a perfectly clean solution, but I'm not aware of alternatives right now)