[3.2.x] Fixed #32369 -- Fixed adding check constraints with pattern lookups and expressions as rhs.

This disables interpolation of constraint creation statements. Since
Constraint.create_sql interpolates its parameters instead of deferring
this responsibility to the backend connection it must disable
connection level parameters interpolation.

Backport of 42e8cf47c7 from master
This commit is contained in:
Simon Charette 2021-01-25 23:32:55 -05:00 committed by Mariusz Felisiak
parent 5d9374b9fb
commit 9607e3a0cc
2 changed files with 19 additions and 1 deletions

View File

@ -360,6 +360,8 @@ class BaseDatabaseSchemaEditor:
not self.connection.features.supports_expression_indexes not self.connection.features.supports_expression_indexes
): ):
return None return None
# Index.create_sql returns interpolated SQL which makes params=None a
# necessity to avoid escaping attempts on execution.
self.execute(index.create_sql(model, self), params=None) self.execute(index.create_sql(model, self), params=None)
def remove_index(self, model, index): def remove_index(self, model, index):
@ -375,7 +377,9 @@ class BaseDatabaseSchemaEditor:
"""Add a constraint to a model.""" """Add a constraint to a model."""
sql = constraint.create_sql(model, self) sql = constraint.create_sql(model, self)
if sql: if sql:
self.execute(sql) # Constraint.create_sql returns interpolated SQL which makes
# params=None a necessity to avoid escaping attempts on execution.
self.execute(sql, params=None)
def remove_constraint(self, model, constraint): def remove_constraint(self, model, constraint):
"""Remove a constraint from a model.""" """Remove a constraint from a model."""

View File

@ -2145,6 +2145,7 @@ class OperationTests(OperationTestBase):
fields=[ fields=[
('id', models.AutoField(primary_key=True)), ('id', models.AutoField(primary_key=True)),
('name', models.CharField(max_length=100)), ('name', models.CharField(max_length=100)),
('surname', models.CharField(max_length=100, default='')),
('rebate', models.CharField(max_length=100)), ('rebate', models.CharField(max_length=100)),
], ],
), ),
@ -2178,6 +2179,19 @@ class OperationTests(OperationTestBase):
Author.objects.create(name='Albert', rebate='10$') Author.objects.create(name='Albert', rebate='10$')
author = Author.objects.create(name='Albert', rebate='10%') author = Author.objects.create(name='Albert', rebate='10%')
self.assertEqual(Author.objects.get(), author) self.assertEqual(Author.objects.get(), author)
# Right-hand-side baked "%" literals should not be used for parameters
# interpolation.
check = ~models.Q(surname__startswith=models.F('name'))
constraint = models.CheckConstraint(check=check, name='name_constraint_rhs')
operation = migrations.AddConstraint('Author', constraint)
from_state = to_state
to_state = from_state.clone()
operation.state_forwards(app_label, to_state)
with connection.schema_editor() as editor:
operation.database_forwards(app_label, editor, from_state, to_state)
Author = to_state.apps.get_model(app_label, 'Author')
with self.assertRaises(IntegrityError), transaction.atomic():
Author.objects.create(name='Albert', surname='Alberto')
@skipUnlessDBFeature('supports_table_check_constraints') @skipUnlessDBFeature('supports_table_check_constraints')
def test_add_or_constraint(self): def test_add_or_constraint(self):