Opened 6 days ago
Last modified 16 hours ago
#36598 assigned Bug
Django migrations is unable to find remove constraints from non-public schema for Postgres
Reported by: | Salaah Amin | Owned by: | Salaah Amin |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 4.2 |
Severity: | Normal | Keywords: | postgres, db, orm |
Cc: | Salaah Amin | Triage Stage: | Unreviewed |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
I have a table in another schema (analytics) which is created and managed by Django. The following is a snippet of the table:
class Meta: db_table = '"analytics"."occupancy"' indexes = [models.Index(fields=("business_id", "-dt"))] constraints = [ models.CheckConstraint( check=models.Q(occupancy_tables__gte=0.0) & models.Q(occupancy_tables___lte=1.0), name="occupancy_tables_between_0_and_1", ), models.CheckConstraint( check=models.Q(occupancy_covers__gte=0.0) & models.Q(occupancy_covers__lte=1.0), name="occupancy_covers_between_0_and_1", ), ]
If i create a new migration that results in these constraints being removed, it breaks.
Here is the traceback:
Traceback (most recent call last): File "..../manage.py", line 25, in <module> main() File "..../manage.py", line 21, in main execute_from_command_line(sys.argv) File "....django/core/management/__init__.py", line 442, in execute_from_command_line utility.execute() File "....django/core/management/__init__.py", line 436, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "....django/core/management/base.py", line 412, in run_from_argv self.execute(*args, **cmd_options) File "....django/core/management/base.py", line 458, in execute output = self.handle(*args, **options) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "....django/core/management/base.py", line 106, in wrapper res = handle_func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "....django/core/management/commands/migrate.py", line 356, in handle post_migrate_state = executor.migrate( ^^^^^^^^^^^^^^^^^ File "....django/db/migrations/executor.py", line 135, in migrate state = self._migrate_all_forwards( ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "....django/db/migrations/executor.py", line 167, in _migrate_all_forwards state = self.apply_migration( ^^^^^^^^^^^^^^^^^^^^^ File "....django/db/migrations/executor.py", line 252, in apply_migration state = migration.apply(state, schema_editor) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "....django/db/migrations/migration.py", line 132, in apply operation.database_forwards( File "....django/db/migrations/operations/models.py", line 659, in database_forwards alter_together( File "....django/db/backends/base/schema.py", line 554, in alter_unique_together self._delete_composed_index( File "....django/db/backends/base/schema.py", line 611, in _delete_composed_index raise ValueError( ValueError: Found wrong number (0) of constraints for "analytics"."occupancy"(business_id, dt)
Taking a look at the code, the problem seems to be caused by `django.lib.backends.postgresql.introspection.DatabaseIntrospection.get_constraints.
The query adds "pg_catalog.pg_table_is_visible(cl.oid)". When I ran that query in pgadmin, removing that condition, the missing constraints appeared.
Looking at the postgres docs, pg_table_is_visible searches the search_path. However, I have added the schema to the search path in the db setup in settings.py:
DATABASES = { ... "OPTIONS": { "options": "-c search_path=public,analytics", }, }
Not sure if this is a misconfiguration on my part (couldn't find anything in Django docs), or if this is a bug/missing feature.
I am able to migrate ok when creating new tables, just deleting constraints seem to be a problem.
Note: I am currently using Django version 4.2. But looking at the source code, this (potential) issue may also be in v5.2.
Change History (7)
comment:1 by , 6 days ago
Type: | Uncategorized β Bug |
---|
comment:2 by , 28 hours ago
comment:3 by , 25 hours ago
Hi Tanishq,
I don't think that would work.
In fact, I ran two queries:
SELECT c.conname, array( SELECT attname FROM unnest(c.conkey) WITH ORDINALITY cols(colid, arridx) JOIN pg_attribute AS ca ON cols.colid = ca.attnum WHERE ca.attrelid = c.conrelid ORDER BY cols.arridx ), c.contype, (SELECT fkc.relname || '.' || fka.attname FROM pg_attribute AS fka JOIN pg_class AS fkc ON fka.attrelid = fkc.oid WHERE fka.attrelid = c.confrelid AND fka.attnum = c.confkey[1]), cl.reloptions FROM pg_constraint AS c JOIN pg_class AS cl ON c.conrelid = cl.oid WHERE cl.relname like '%occupancy' AND pg_catalog.pg_table_is_visible(cl.oid);
Here I kept the pg_table_is_visible constraint in and it didn't return anything.
However, it works when I run:
SELECT c.conname, array( SELECT attname FROM unnest(c.conkey) WITH ORDINALITY cols(colid, arridx) JOIN pg_attribute AS ca ON cols.colid = ca.attnum WHERE ca.attrelid = c.conrelid ORDER BY cols.arridx ), c.contype, (SELECT fkc.relname || '.' || fka.attname FROM pg_attribute AS fka JOIN pg_class AS fkc ON fka.attrelid = fkc.oid WHERE fka.attrelid = c.confrelid AND fka.attnum = c.confkey[1]), cl.reloptions FROM pg_constraint AS c JOIN pg_class AS cl ON c.conrelid = cl.oid WHERE cl.relname like '%occupancy';
So the issue really is pg_table_is_visible.
comment:4 by , 24 hours ago
I think I found the issue.
When I run:
select pg_catalog.pg_table_is_visible(<the model oid>)
I get false.
When I run:
show search_path
I get a few things, but I notice analytics isn't there.
If I update my search_path using set search_path to ...
, and then run select pg_catalog.pg_table_is_visible(<the model oid>)
I get true.
Now, I'm not entirely sure if there are any consequences to adding all schemas to the search path. But I assume it should be fine to add it just before running migrations so that it only affects the user's session when they are running the actual migration.
If you agree to this solution, let me know (and mark the ticket accordingly), and I'd be happy to apply the fix.
comment:5 by , 16 hours ago
Hi Salaah,
I agree with your finding that the issue is due to pg_table_is_visible
filtering out theanalytics
schema, and your suggestion to adjust the search_path
during migrations makes sense. Dynamically setting search_path
(e.g., SET search_path
TO public,analytics
) in the migration session should resolve the constraint visibility problem without affecting other operations, as long as itβs scoped to the migration context.
So, I say go ahead with the solution.
comment:6 by , 16 hours ago
Has patch: | set |
---|
comment:7 by , 16 hours ago
Owner: | set to |
---|---|
Status: | new β assigned |
Hi Salaah,
Iβve looked into the problem with migrations failing to remove constraints from the
analytics.occupancy
table in a non-public schema, and it seems relatedto
django.db.backends.postgresql.introspection.DatabaseIntrospection.get_constraints
not detecting constraints due to thepg_catalog.pg_table_is_visible(cl.oid)
filter, despite thesearch_path=public,analytics
setting inDATABASES["OPTIONS"]
.TheValueError: Found wrong number (0) of constraints
suggests that the introspection query isnβt picking up schema-qualified constraints during removal operations. This could be a bug or a missing feature, as creation works fine. A potential fix might involve modifyingget_constraints
to either:Use the schema from
db_table
(e.g., "analytics"."occupancy
") explicitly in the query, or Adjust the SQL to include the schema in theJOIN
condition, bypassing the visibility filter when the schema is specified.