Django-DB-Migrations: cannot ALTER TABLE because it has pending trigger events
Asked Answered
S

10

197

I want to remove null=True from a TextField:

-    footer=models.TextField(null=True, blank=True)
+    footer=models.TextField(blank=True, default='')

I created a schema migration:

manage.py schemamigration fooapp --auto

Since some footer columns contain NULL I get this error if I run the migration:

django.db.utils.IntegrityError: column "footer" contains null values

I added this to the schema migration:

    for sender in orm['fooapp.EmailSender'].objects.filter(footer=None):
        sender.footer=''
        sender.save()

Now I get:

django.db.utils.DatabaseError: cannot ALTER TABLE "fooapp_emailsender" because it has pending trigger events

What is wrong?

Subset answered 11/10, 2012 at 11:6 Comment(2)
This question is similar: #28430433 and had answers that were more useful to me.Trexler
I got the same problem with Postgres v10 (but not with Postgres v.12). Problem solved, by adding a separate migration file.Unexperienced
S
200

Every migration is inside a transaction. In PostgreSQL you must not update the table and then alter the table schema in one transaction.

You need to split the data migration and the schema migration. First create the data migration with this code:

 for sender in orm['fooapp.EmailSender'].objects.filter(footer=None):
    sender.footer=''
    sender.save()

Then create the schema migration:

manage.py schemamigration fooapp --auto

Now you have two transactions and the migration in two steps should work.

Subset answered 11/10, 2012 at 11:6 Comment(10)
PostgreSQL probably changed its behaviour regarding such transactions, as I managed to run a migration with both data and schema changes on my dev machine (PostgreSQL 9.4) while it failed on the server (PostgreSQL 9.1).Reifel
Almost same for me. It worked flawlessly for 100+ migrations(including ~20 data migrations) until today, while adding unique together constraint along with data migration removing duplicates before it. PostgreSQL 10.0Monoatomic
If using a RunPython operation in the migration for the data migration, you just need to make sure it's the last operation. Django knows that if the RunPython operation is last, to open its own transaction.Lavernalaverne
@Lavernalaverne is this a documented feature of django?Subset
I actually don't see this anywhere, was just something I observed. docs.djangoproject.com/en/2.2/ref/migration-operations/…Lavernalaverne
@Lavernalaverne the page you linked contains the word "last" once. But the context is different. It is about the last migration, not the last operation. Still I don't understand this comment of you "Django knows that if the RunPython operation is last, to open its own transaction. "Subset
I said it was "just something I observed". I faced the same error OP faced while running a migration with approx. 12 operations, with the RunPython operation in the middle. After coming across that error, I moved the RunPython operation to be the last operation with no other changes. The migration then ran successfully.Lavernalaverne
Django's migration also support Migration.atomic = False which allows you to not have to spit the file (as mentioned in the answer).Apospory
@Apospory thanks, setting atomic to False solved my problemOrly
Is atomic False option running migration without transaction? On postgresql transaction on schema is a good thing, which helps you not using manual rollback schema in db. If answer is yes - i think this is the way to get errors on app deployment. Approach in the answer the best choice, and it is a official choice, which you can find in django documentation.Laguna
R
210

Another reason for this maybe because you try to set a column to NOT NULL when it actually already has NULL values.

Reposition answered 25/2, 2014 at 16:58 Comment(4)
To address this you can either use a data migration or manually (manage.py shell) go in and update non-compliant valuesBrickkiln
@Brickkiln How do you do that?Straus
@Straus If you aren't too picky, you can just update the null values at the django shell. If you're looking for something more formal and testable, it depends on what versions you're using. If you use south, see: south.readthedocs.org/en/latest/tutorial/part3.html and if you use django's migrations, see the "data migrations" section here: docs.djangoproject.com/en/1.8/topics/migrationsBrickkiln
I'm a huge fan of Django but obviously error messages like this - that are COMPLETE red herrings - just goes to show the complexity of such platforms!Anthropopathy
S
200

Every migration is inside a transaction. In PostgreSQL you must not update the table and then alter the table schema in one transaction.

You need to split the data migration and the schema migration. First create the data migration with this code:

 for sender in orm['fooapp.EmailSender'].objects.filter(footer=None):
    sender.footer=''
    sender.save()

Then create the schema migration:

manage.py schemamigration fooapp --auto

Now you have two transactions and the migration in two steps should work.

Subset answered 11/10, 2012 at 11:6 Comment(10)
PostgreSQL probably changed its behaviour regarding such transactions, as I managed to run a migration with both data and schema changes on my dev machine (PostgreSQL 9.4) while it failed on the server (PostgreSQL 9.1).Reifel
Almost same for me. It worked flawlessly for 100+ migrations(including ~20 data migrations) until today, while adding unique together constraint along with data migration removing duplicates before it. PostgreSQL 10.0Monoatomic
If using a RunPython operation in the migration for the data migration, you just need to make sure it's the last operation. Django knows that if the RunPython operation is last, to open its own transaction.Lavernalaverne
@Lavernalaverne is this a documented feature of django?Subset
I actually don't see this anywhere, was just something I observed. docs.djangoproject.com/en/2.2/ref/migration-operations/…Lavernalaverne
@Lavernalaverne the page you linked contains the word "last" once. But the context is different. It is about the last migration, not the last operation. Still I don't understand this comment of you "Django knows that if the RunPython operation is last, to open its own transaction. "Subset
I said it was "just something I observed". I faced the same error OP faced while running a migration with approx. 12 operations, with the RunPython operation in the middle. After coming across that error, I moved the RunPython operation to be the last operation with no other changes. The migration then ran successfully.Lavernalaverne
Django's migration also support Migration.atomic = False which allows you to not have to spit the file (as mentioned in the answer).Apospory
@Apospory thanks, setting atomic to False solved my problemOrly
Is atomic False option running migration without transaction? On postgresql transaction on schema is a good thing, which helps you not using manual rollback schema in db. If answer is yes - i think this is the way to get errors on app deployment. Approach in the answer the best choice, and it is a official choice, which you can find in django documentation.Laguna
U
38

At the operations I put SET CONSTRAINTS:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RunPython(migration_func),
    migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'),
]
Unmoral answered 24/3, 2020 at 10:41 Comment(3)
Better to use SeparateDatabaseAndStateCircumvallate
@Circumvallate +++ thank you! The original answer is a kind of dirty hack.Molybdic
Thanks! Found my way here for a different reason, but this solved the same named issue.Salgado
C
28

If you are adding a non-nullable field, you need to do it in two migrations:

  1. AddField and RunPython to populate it
  2. AlterField to change the field to be non-nullable

Explanation

On PostgreSQL and SQLite, this problem can occur if you have a sufficiently complex RunPython command combined with schema alterations in the same migration. For example, if you are adding a non-nullable field, the typical migration steps for this is:

  1. AddField to add the field as nullable
  2. RunRython to populate it
  3. AlterField to change the field to be non-nullable

On SQLite and Postgres, this can cause problems because the whole thing is being done in one transaction.
The Django docs have a specific warning about this:

On databases that do support DDL transactions (SQLite and PostgreSQL), RunPython operations do not have any transactions automatically added besides the transactions created for each migration. Thus, on PostgreSQL, for example, you should avoid combining schema changes and RunPython operations in the same migration or you may hit errors like OperationalError: cannot ALTER TABLE "mytable" because it has pending trigger events.

If this is the case, the solution is to separate your migration into multiple migrations. In general, the way to split is to have a first migration containing the steps up through the run_python command and the second migration containing all the ones after it. Thus, in the case described above, the pattern would be the AddField and RunPython in one migration, and the AlterField in a second.

Circumbendibus answered 28/10, 2020 at 3:46 Comment(1)
Thank you for pasting the link and quote from the Django docs!Infinitesimal
G
11

Have just hit this problem. You can also use db.start_transaction() and db.commit_transaction() in the schema migration to separate data changes from schema changes. Probably not so clean as to have a separate data migration but in my case I would need schema, data, and then another schema migration so I decided to do it all at once.

Gaudet answered 15/4, 2013 at 13:24 Comment(2)
The problem with this solution is this: What happens if your migration fails after db.commit_transaction()? I prefere to use three migrations, if you need this: schema-mig, data-mig, schema-mig.Subset
See: django.readthedocs.io/en/latest/ref/migration-operations.html On databases that do support DDL transactions (SQLite and PostgreSQL), RunPython operations do not have any transactions automatically added besides the transactions created for each migration. Thus, on PostgreSQL, for example, you should avoid combining schema changes and RunPython operations in the same migration or you may hit errors like OperationalError: cannot ALTER TABLE "mytable" because it has pending trigger events.Glacier
O
6

@Zags answer is correct, this error usually comes when you mix schema alteration and data manipulation in the same migration, which correspond to a unique transaction by default.

Another solution to solve that issue is to manually set atomic = False to the Migration class. As a result, no transaction is created to run operations, each one is run sequentially and immediatly applied to the database.

class Migration(migrations.Migration):
    atomic = False

    dependencies = [
        ('application', '00XX_previous_migration'),
    ]

    operations = [
        migrations.AddField(
            model_name='foo',
            name='bar',
            field=models.CharField(max_length=255),
        ),
        RunPython(migrate_data, reverse_code=reverse_migrate_data, atomic=True),
        migrations.RemoveField(
            model_name='foo',
            name='baz',
        ),
    ]

Tip: If the RunPython operation perform multiple save(), you can call it with atomic=True to create a transaction for the whole operation.

Ojibwa answered 30/8, 2023 at 7:25 Comment(2)
Worked for me. Thank you for sharing!Labionasal
This solution worked well for me as well! Thank you for sharing!Andalusia
W
3

In my case I've got

  1. AddField
  2. RunPython
  3. RemoveField

Then I just moved the last RemoveFied to the new migration file, that fixed the problem

Wellbeing answered 10/11, 2021 at 16:19 Comment(0)
S
2

You are altering the column schema. That footer column can no longer contain a blank value. There are most likely blank values already stored in the DB for that column. Django is going to update those blank rows in your DB from blank to the now default value with the migrate command. Django tries to update the rows where footer column has a blank value and change the schema at the same time it seems (I'm not sure).

The problem is you can't alter the same column schema you are trying to update the values for at the same time.

One solution would be to delete the migrations file updating the schema. Then, run a script to update all those values to your default value. Then re-run the migration to update the schema. This way, the update is already done. Django migration is only altering the schema.

Sst answered 23/4, 2020 at 20:31 Comment(1)
Running some script is not really an option for me. I have several instances of the database and the continuous deployment process just calls "manage.py migrate". This question is already valid answers which work fine.Subset
L
0

step 1)the solution is to remove the latest migration from the migration folder and remove the latest added fields in models.

step 2)then again makemigration and migrate

step 3)At the last add the field again that has been removed in the first step

step 4)then again makemigration and migrate

Problem solved

Lath answered 11/3, 2021 at 5:7 Comment(0)
C
0

The likely cause of this is rooted in your model having check constraints on it via the Meta.constraints option. Check constraints (in Postgres) are implemented as triggers that execute at the end of a transaction. By default, a Django migration file encloses all migrations operations from a file in a single transaction. This means that other ALTER TABLE operations may happen before your triggers fire, a situation that Postgres can't handle.

In order to get around this issue, you can do like another answer suggested:

operations = [
    migrations.RemoveField(...). # Do your normal table operations
    migrations.RunPython(migration_func),
    # Evaluate the check constraints
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RemoveField(...). # Run the rest of your table operations
]

Running 'SET CONSTRAINTS ALL IMMEDIATE;' after the data migration ensures that all of those check constraints that normally wait until the end of the transaction will fire, meaning there are no more pending trigger events before the next ALTER TABLE statements.

FYI - This setting is effective for only the Django migration runner in that transaction. It won't affect other database sessions or any subsequent migration files.

Unlike the other answer linked, I'd avoid running an additional migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'), otherwise you may alter the behavior of other triggers in subsequent steps that should run immediately (i.e. application triggers that track history, etc, that aren't check constraints).

Just wanted to add some more clarity here on exactly why this is happening since this is an older question with many different answers.

Cyprinid answered 6/6, 2023 at 0:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.