#34009: migrations.RunPython runs queries against wrong database
------------------------------------------+------------------------
Reporter: Marcel Moreaux | Owner: nobody
Type: Uncategorized | Status: new
Component: Uncategorized | Version: 4.1
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
------------------------------------------+------------------------
In an application with multiple databases, `RunPython` in migrations
appears to always run queries on the `default` database, instead of the
currently selected database, resulting in database errors. This can be
observed with `manage.py migrate --database`, but the problem is also
triggered by `manage.py test`:
{{{
(venv) [marcelm@carbon foo .]% ./manage.py test
Found 1 test(s).
Creating test database for alias 'default'...
Creating test database for alias 'extra'...
Traceback (most recent call last):
File "/home/marcelm/meh/venv/lib/python3.9/site-
packages/django/db/backends/utils.py", line 89, in _execute
return self.cursor.execute(sql, params)
File "/home/marcelm/meh/venv/lib/python3.9/site-
packages/django/db/backends/sqlite3/base.py", line 357, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: table bar_bar has no column named name
}}}
== The relevant migration:
{{{
from django.db import migrations
# BUG: This always uses the default database, even when the migrations are
# run to set up the extra database.
def add_bar(apps, schema_editor):
print('RunPython starts here')
Bar = apps.get_model('bar', 'Bar')
Bar.objects.create(name='hello')
print('RunPython ends here')
class Migration(migrations.Migration):
dependencies = [ ('bar', '0001_initial'), ]
operations = [
migrations.RunPython(add_bar,
reverse_code=migrations.RunPython.noop),
]
}}}
(migration 0001 adds the `name` field)
== More debug info
Adding debug code to print the DB queries and the database on which
they're run confirms this. Selected output:
{{{
% QDEBUG=1 ./manage.py test -v3
Creating test database for alias 'default'
('file:memorydb_default?mode=memory&cache=shared')...
Found 1 test(s).
CONNECTING TO {'database':
'file:memorydb_default?mode=memory&cache=shared', 'detect_types': 3,
'check_same_thread': False, 'uri': True}
QUERY AGAINST file:memorydb_default?mode=memory&cache=shared:
SELECT name, type FROM sqlite_master
WHERE type in ('table', 'view') AND NOT name='sqlite_sequence'
ORDER BY name
[...]
Creating test database for alias 'extra'
('file:memorydb_extra?mode=memory&cache=shared')...
CONNECTING TO {'database': 'file:memorydb_extra?mode=memory&cache=shared',
'detect_types': 3, 'check_same_thread': False, 'uri': True}
QUERY AGAINST file:memorydb_extra?mode=memory&cache=shared:
SELECT name, type FROM sqlite_master
WHERE type in ('table', 'view') AND NOT name='sqlite_sequence'
ORDER BY name
[...]
Applying bar.0002_add_bar...QUERY AGAINST
file:memorydb_extra?mode=memory&cache=shared: PRAGMA foreign_keys = OFF
QUERY AGAINST file:memorydb_extra?mode=memory&cache=shared: PRAGMA
foreign_keys
QUERY AGAINST file:memorydb_extra?mode=memory&cache=shared: BEGIN
RunPython starts here
QUERY AGAINST file:memorydb_default?mode=memory&cache=shared: INSERT INTO
"bar_bar" ("name") VALUES (%s)
QUERY AGAINST file:memorydb_extra?mode=memory&cache=shared: PRAGMA
foreign_key_check
QUERY AGAINST file:memorydb_extra?mode=memory&cache=shared: PRAGMA
foreign_keys = ON
Traceback (most recent call last):
File "/home/marcelm/meh/venv/lib/python3.9/site-
packages/django/db/backends/utils.py", line 89, in _execute
return self.cursor.execute(sql, params)
File "/home/marcelm/meh/foo/foo/settings.py", line 70, in wrapped
return func(self, query, params)
File "/home/marcelm/meh/venv/lib/python3.9/site-
packages/django/db/backends/sqlite3/base.py", line 357, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: table bar_bar has no column named name
}}}
(note `QUERY AGAINST file:memorydb_default` just after `RunPython starts
here`)
Similar problems can be observed by running `manage.py migrate --database
extra`.
== Reproducing
I attached the full output of both `QDEBUG=1 manage.py test -v3` and
`QDEBUG=1 manage.py migrate --database extra`. Also attached is a tar
archive of a minimal project that reproduces the bug. I tested on Django
3.2.15, 4.1.1, and the `main` branch on github.
To reproduce, extract the minimal reproducible example somewhere
appropriate and follow these steps:
{{{
tar xvzf foo.tgz
virtualenv venv
. venv/bin/activate
pip install Django
cd foo
QDEBUG=1 ./manage.py test
QDEBUG=1 ./manage.py migrate --database=extra
}}}
`QDEBUG` triggers some monkey patching in `settings.py` to print database
queries. Queries on `default` are printed in magenta, queries on `extra`
are printed in cyan, and all other queries are printed in yellow. This
makes the problematic queries easy to spot.
--
Ticket URL: <https://code.djangoproject.com/ticket/34009>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/010701833bd3c305-9e640242-63ee-4b4a-aec9-9d0b89cecf0e-000000%40eu-central-1.amazonses.com.