andreh

andreh

How to set PRAGMA foreign_keys=OFF in Ecto migration?

Hello,

Using SQLite, in order to remove a NOT NULL constraint on a column, I’m trying to follow this procedure: https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes

The problem is, we must execute PRAGMA foreign_keys=OFF before the transaction, because SQLite’s documentation says:

PRAGMA foreign_keys = boolean ;
This pragma is a no-op within a transaction; foreign key constraint enforcement may only be enabled or disabled when there is no pending BEGIN or SAVEPOINT.

I’ve tried this approach:

defmodule Brinjel.Repo.Migrations.RelaxNotNullConstraintForOwnerId do
  use Ecto.Migration

  @disable_ddl_transaction true

  def change do
    execute("PRAGMA foreign_keys=OFF;")

    execute("PRAGMA busy_timeout=5000;")

    execute("BEGIN TRANSACTION")

    create table(:new_farms, primary_key: false, options: "STRICT") do
      add :farm_id, :bigserial, primary_key: true
      add :name, :text, null: false
      add :slug, :text, null: false
      add :default_provider_id, :integer
      add :locked, :boolean, null: false, default: false
      add :trial_expiry_date, :date, null: false

      add :owner_id, references(:users)

      timestamps()
    end

    execute """
      INSERT INTO new_farms(farm_id, name, slug, default_provider_id, locked, trial_expiry_date, owner_id, inserted_at, updated_at)
      SELECT farm_id, name, slug, default_provider_id, locked, trial_expiry_date, owner_id, inserted_at, updated_at 
      FROM farms;
    """

    flush()

    drop table("farms")

    rename table("new_farms"), to: table("farms")

    create unique_index(:farms, [:slug])

    execute("COMMIT")

    execute("PRAGMA foreign_keys=ON")
  end
end

and unfortunately got this error:

17:20:26.716 [info] execute "PRAGMA foreign_keys=OFF;"

17:20:26.716 [info] execute "PRAGMA busy_timeout=5000;"

17:20:26.716 [info] execute "BEGIN TRANSACTION"

17:20:26.716 [info] create table new_farms

17:20:26.720 [info] execute "  INSERT INTO new_farms(farm_id, name, slug, default_provider_id, locked, trial_expiry_date, owner_id, inserted_at, updated_at)\n  SELECT farm_id, name, slug, default_provider_id, locked, trial_expiry_date, owner_id, inserted_at, updated_at \n  FROM farms;\n"

17:20:26.721 [info] drop table farms
** (Exqlite.Error) Database busy
DROP TABLE "farms"
    (ecto_sql 3.11.3) lib/ecto/adapters/sql.ex:1054: Ecto.Adapters.SQL.raise_sql_call_error/1
    (elixir 1.16.1) lib/enum.ex:1700: Enum."-map/2-lists^map/1-1-"/2
    (ecto_sql 3.11.3) lib/ecto/adapters/sql.ex:1161: Ecto.Adapters.SQL.execute_ddl/4
    (ecto_sql 3.11.3) lib/ecto/migration/runner.ex:348: Ecto.Migration.Runner.log_and_execute_ddl/3
    (elixir 1.16.1) lib/enum.ex:1700: Enum."-map/2-lists^map/1-1-"/2
    (stdlib 5.2) timer.erl:270: :timer.tc/2
    (ecto_sql 3.11.3) lib/ecto/migration/runner.ex:25: Ecto.Migration.Runner.run/8
    (ecto_sql 3.11.3) lib/ecto/migrator.ex:365: Ecto.Migrator.attempt/8

Any idea why dropping the table produces a “Database busy” error?

Marked As Solved

andreh

andreh

Sorry, I just saw your answers! I’ve switched to PostgreSQL long ago. Too many hours have been wasted on this issue, and it was generally too frustrating to migrate data using SQLite. I also had scaling issues with my one-db-per-tenant architecture. Sure, I now have to deal with Postgres updates, but it’s still really fast because I use a socket connection on the same server.

I still like SQLite for small projects, but Postgres is now my default database for larger ones.

Also Liked

dimitarvp

dimitarvp

I’ll get you guys sorted soon enough. My SQLite library is nearly done and I’m moving to Ecto integration very soon, migrations included.

a-nassim

a-nassim

I cannot explain the “Database busy” error

But I had a similar issue which I could work around with a dynamic repo Ecto.Repo — Ecto v3.14.0

I only tried this approach with up and down functions

Adapted to your snippet, this would look like this

defmodule Brinjel.Repo.Migrations.RelaxNotNullConstraintForOwnerId do
  use Ecto.Migration

  import Ecto.Query

  @disable_ddl_transaction true

  def up do
    repo().start_link(name: :migration, foreign_keys: :off)
    repo().put_dynamic_repo(:migration)

    repo().transaction(fn ->
      create table(:new_farms, primary_key: false, options: "STRICT") do
        add(:farm_id, :bigserial, primary_key: true)
        add(:name, :text, null: false)
        add(:slug, :text, null: false)
        add(:default_provider_id, :integer)
        add(:locked, :boolean, null: false, default: false)
        add(:trial_expiry_date, :date, null: false)

        add(:owner_id, references(:users))

        timestamps()
      end

      execute("""
        INSERT INTO new_farms(farm_id, name, slug, default_provider_id, locked, trial_expiry_date, owner_id, inserted_at, updated_at)
        SELECT farm_id, name, slug, default_provider_id, locked, trial_expiry_date, owner_id, inserted_at, updated_at 
        FROM farms;
      """)

      drop(table("farms"))

      rename(table("new_farms"), to: table("farms"))

      create(unique_index(:farms, [:slug]))

      execute(fn ->
        count = repo().one(from(fragment("pragma_foreign_key_check()"), select: count()))

        if count > 0 do
          raise "Foreign key check failed"
        end
      end)

      flush()
    end)
  end

  def down do
    # reverse
  end
end

Where Next?

Popular in Questions Top

beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> somethi...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54250 245
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

We're in Beta

About us Mission Statement