Skip to content

Commit 779bf2c

Browse files
committed
Make constraint queries schema-aware for multi-tenancy
Prior to this change, the ConstraintQueries class queried pg_catalog.pg_constraint without filtering by PostgreSQL schema. This caused incorrect behavior in multi-tenant environments using django-tenants, where each tenant has its own schema. The queries would detect constraints from other tenants' schemas, leading to false positives (e.g., ConstraintAlreadyExists errors when creating a second tenant). This change adds INNER JOINs with pg_catalog.pg_class and pg_catalog.pg_namespace to filter constraints by current_schema(). This ensures that constraint checks only look at the current tenant's schema, not across all schemas in the database. Affected queries: - CHECK_EXISTING_CONSTRAINT - CHECK_CONSTRAINT_IS_VALID - CHECK_CONSTRAINT_IS_NOT_VALID Related issue: #92
1 parent 3ee4d35 commit 779bf2c

File tree

3 files changed

+342
-187
lines changed

3 files changed

+342
-187
lines changed

src/django_pg_migration_tools/operations.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,34 @@ class IndexQueries:
5353

5454
class ConstraintQueries:
5555
CHECK_EXISTING_CONSTRAINT = dedent("""
56-
SELECT conname
57-
FROM pg_catalog.pg_constraint
58-
WHERE conname = {constraint_name};
56+
SELECT con.conname
57+
FROM pg_catalog.pg_constraint con
58+
INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid
59+
INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = rel.relnamespace
60+
WHERE con.conname = {constraint_name}
61+
AND nsp.nspname = current_schema();
5962
""")
6063

6164
CHECK_CONSTRAINT_IS_VALID = dedent("""
6265
SELECT 1
63-
FROM pg_catalog.pg_constraint
66+
FROM pg_catalog.pg_constraint con
67+
INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid
68+
INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = rel.relnamespace
6469
WHERE
65-
conname = {constraint_name}
66-
AND convalidated IS TRUE;
70+
con.conname = {constraint_name}
71+
AND con.convalidated IS TRUE
72+
AND nsp.nspname = current_schema();
6773
""")
6874

6975
CHECK_CONSTRAINT_IS_NOT_VALID = dedent("""
7076
SELECT 1
71-
FROM pg_catalog.pg_constraint
77+
FROM pg_catalog.pg_constraint con
78+
INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid
79+
INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = rel.relnamespace
7280
WHERE
73-
conname = {constraint_name}
74-
AND convalidated IS FALSE;
81+
con.conname = {constraint_name}
82+
AND con.convalidated IS FALSE
83+
AND nsp.nspname = current_schema();
7584
""")
7685

7786
ALTER_TABLE_CONSTRAINT_NOT_NULL_NOT_VALID = dedent("""

tests/django_pg_migration_tools/test_multitenancy.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,6 @@ def setup_schemas(self):
6060
cursor.execute("DROP SCHEMA IF EXISTS tenant1 CASCADE")
6161
cursor.execute("DROP SCHEMA IF EXISTS tenant2 CASCADE")
6262

63-
@pytest.mark.xfail(
64-
reason="Constraint queries not yet schema-aware", raises=AssertionError
65-
)
6663
def test_check_existing_constraint_only_sees_current_schema(self):
6764
"""
6865
Test that CHECK_EXISTING_CONSTRAINT only detects constraints in the
@@ -99,9 +96,6 @@ def test_check_existing_constraint_only_sees_current_schema(self):
9996
"Constraint should NOT be visible in tenant2"
10097
)
10198

102-
@pytest.mark.xfail(
103-
reason="Constraint queries not yet schema-aware", raises=AssertionError
104-
)
10599
def test_check_constraint_is_valid_only_sees_current_schema(self):
106100
"""
107101
Test that CHECK_CONSTRAINT_IS_VALID only detects valid constraints
@@ -140,9 +134,6 @@ def test_check_constraint_is_valid_only_sees_current_schema(self):
140134
"Valid constraint should NOT be visible in tenant2"
141135
)
142136

143-
@pytest.mark.xfail(
144-
reason="Constraint queries not yet schema-aware", raises=AssertionError
145-
)
146137
def test_check_constraint_is_not_valid_only_sees_current_schema(self):
147138
"""
148139
Test that CHECK_CONSTRAINT_IS_NOT_VALID only detects NOT VALID
@@ -190,10 +181,6 @@ def test_check_constraint_is_not_valid_only_sees_current_schema(self):
190181
"NOT VALID constraint should NOT be visible in tenant2"
191182
)
192183

193-
@pytest.mark.xfail(
194-
reason="Constraint queries not yet schema-aware",
195-
raises=operations.ConstraintAlreadyExists,
196-
)
197184
def test_unique_constraint_creation_in_second_schema(self):
198185
"""
199186
Test that SaferAddUniqueConstraint can create a constraint in a second
@@ -286,9 +273,6 @@ def test_unique_constraint_creation_in_second_schema(self):
286273
# Reset to public schema
287274
cursor.execute("SET search_path TO public")
288275

289-
@pytest.mark.xfail(
290-
reason="Constraint queries not yet schema-aware", raises=AssertionError
291-
)
292276
def test_check_constraint_creation_in_second_schema(self):
293277
"""
294278
Test that SaferAddCheckConstraint can create a constraint in a second

0 commit comments

Comments
 (0)