Przeglądaj źródła

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.
Simon Charette 4 lat temu
rodzic
commit
42e8cf47c7

+ 5 - 1
django/db/backends/base/schema.py

@@ -360,6 +360,8 @@ class BaseDatabaseSchemaEditor:
             not self.connection.features.supports_expression_indexes
         ):
             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)
 
     def remove_index(self, model, index):
@@ -375,7 +377,9 @@ class BaseDatabaseSchemaEditor:
         """Add a constraint to a model."""
         sql = constraint.create_sql(model, self)
         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):
         """Remove a constraint from a model."""

+ 14 - 0
tests/migrations/test_operations.py

@@ -2145,6 +2145,7 @@ class OperationTests(OperationTestBase):
                 fields=[
                     ('id', models.AutoField(primary_key=True)),
                     ('name', models.CharField(max_length=100)),
+                    ('surname', models.CharField(max_length=100, default='')),
                     ('rebate', models.CharField(max_length=100)),
                 ],
             ),
@@ -2178,6 +2179,19 @@ class OperationTests(OperationTestBase):
             Author.objects.create(name='Albert', rebate='10$')
         author = Author.objects.create(name='Albert', rebate='10%')
         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')
     def test_add_or_constraint(self):