mirror of
https://github.com/django/django.git
synced 2025-06-05 11:39:13 +00:00
[4.2.x] Fixed #34932 -- Restored varchar_pattern_ops/text_pattern_ops index creation when deterministic collaction is set.
Regression in f3f9d03edf17ccfa17263c7efa0b1350d1ac9278 (4.2) and 8ed25d65ea7546fafd808086fa07e7e5bb5428fc (5.0). Backport of 34b411762b50883d768d7b67e0a158ec39da8b09 from main.
This commit is contained in:
parent
61612990d8
commit
109f39a38b
@ -72,6 +72,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||||||
supports_covering_indexes = True
|
supports_covering_indexes = True
|
||||||
can_rename_index = True
|
can_rename_index = True
|
||||||
test_collations = {
|
test_collations = {
|
||||||
|
"deterministic": "C",
|
||||||
"non_default": "sv-x-icu",
|
"non_default": "sv-x-icu",
|
||||||
"swedish_ci": "sv-x-icu",
|
"swedish_ci": "sv-x-icu",
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|||||||
return None
|
return None
|
||||||
# Non-deterministic collations on Postgresql don't support indexes
|
# Non-deterministic collations on Postgresql don't support indexes
|
||||||
# for operator classes varchar_pattern_ops/text_pattern_ops.
|
# for operator classes varchar_pattern_ops/text_pattern_ops.
|
||||||
if getattr(field, "db_collation", None):
|
collation_name = getattr(field, "db_collation", None)
|
||||||
|
if collation_name and not self._is_collation_deterministic(collation_name):
|
||||||
return None
|
return None
|
||||||
if db_type.startswith("varchar"):
|
if db_type.startswith("varchar"):
|
||||||
return self._create_index_sql(
|
return self._create_index_sql(
|
||||||
@ -372,3 +373,16 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|||||||
include=include,
|
include=include,
|
||||||
expressions=expressions,
|
expressions=expressions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _is_collation_deterministic(self, collation_name):
|
||||||
|
with self.connection.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT collisdeterministic
|
||||||
|
FROM pg_collation
|
||||||
|
WHERE collname = %s
|
||||||
|
""",
|
||||||
|
[collation_name],
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
return row[0] if row else None
|
||||||
|
@ -13,3 +13,7 @@ Bugfixes
|
|||||||
* Fixed a regression in Django 4.2 that caused a crash of
|
* Fixed a regression in Django 4.2 that caused a crash of
|
||||||
``QuerySet.aggregate()`` with aggregates referencing expressions containing
|
``QuerySet.aggregate()`` with aggregates referencing expressions containing
|
||||||
subqueries (:ticket:`34798`).
|
subqueries (:ticket:`34798`).
|
||||||
|
|
||||||
|
* Restored, following a regression in Django 4.2, creating
|
||||||
|
``varchar/text_pattern_ops`` indexes on ``CharField`` and ``TextField`` with
|
||||||
|
deterministic collations on PostgreSQL (:ticket:`34932`).
|
||||||
|
@ -227,6 +227,18 @@ class SchemaTests(TransactionTestCase):
|
|||||||
constraints_for_column.append(name)
|
constraints_for_column.append(name)
|
||||||
return sorted(constraints_for_column)
|
return sorted(constraints_for_column)
|
||||||
|
|
||||||
|
def get_constraint_opclasses(self, constraint_name):
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
sql = """
|
||||||
|
SELECT opcname
|
||||||
|
FROM pg_opclass AS oc
|
||||||
|
JOIN pg_index as i on oc.oid = ANY(i.indclass)
|
||||||
|
JOIN pg_class as c on c.oid = i.indexrelid
|
||||||
|
WHERE c.relname = %s
|
||||||
|
"""
|
||||||
|
cursor.execute(sql, [constraint_name])
|
||||||
|
return [row[0] for row in cursor.fetchall()]
|
||||||
|
|
||||||
def check_added_field_default(
|
def check_added_field_default(
|
||||||
self,
|
self,
|
||||||
schema_editor,
|
schema_editor,
|
||||||
@ -1378,6 +1390,95 @@ class SchemaTests(TransactionTestCase):
|
|||||||
)
|
)
|
||||||
self.assertIn("field", self.get_uniques(CiCharModel._meta.db_table))
|
self.assertIn("field", self.get_uniques(CiCharModel._meta.db_table))
|
||||||
|
|
||||||
|
@isolate_apps("schema")
|
||||||
|
@unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
|
||||||
|
@skipUnlessDBFeature("supports_collation_on_charfield")
|
||||||
|
def test_unique_with_deterministic_collation_charfield(self):
|
||||||
|
deterministic_collation = connection.features.test_collations.get(
|
||||||
|
"deterministic"
|
||||||
|
)
|
||||||
|
if not deterministic_collation:
|
||||||
|
self.skipTest("This backend does not support deterministic collations.")
|
||||||
|
|
||||||
|
class CharModel(Model):
|
||||||
|
field = CharField(db_collation=deterministic_collation, unique=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = "schema"
|
||||||
|
|
||||||
|
# Create the table.
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.create_model(CharModel)
|
||||||
|
self.isolated_local_models = [CharModel]
|
||||||
|
constraints = self.get_constraints_for_column(
|
||||||
|
CharModel, CharModel._meta.get_field("field").column
|
||||||
|
)
|
||||||
|
self.assertIn("schema_charmodel_field_8b338dea_like", constraints)
|
||||||
|
self.assertIn(
|
||||||
|
"varchar_pattern_ops",
|
||||||
|
self.get_constraint_opclasses("schema_charmodel_field_8b338dea_like"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.get_column_collation(CharModel._meta.db_table, "field"),
|
||||||
|
deterministic_collation,
|
||||||
|
)
|
||||||
|
self.assertIn("field", self.get_uniques(CharModel._meta.db_table))
|
||||||
|
|
||||||
|
@isolate_apps("schema")
|
||||||
|
@unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
|
||||||
|
@skipUnlessDBFeature("supports_collation_on_charfield")
|
||||||
|
def test_relation_to_deterministic_collation_charfield(self):
|
||||||
|
deterministic_collation = connection.features.test_collations.get(
|
||||||
|
"deterministic"
|
||||||
|
)
|
||||||
|
if not deterministic_collation:
|
||||||
|
self.skipTest("This backend does not support deterministic collations.")
|
||||||
|
|
||||||
|
class CharModel(Model):
|
||||||
|
field = CharField(db_collation=deterministic_collation, unique=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = "schema"
|
||||||
|
|
||||||
|
class RelationModel(Model):
|
||||||
|
field = OneToOneField(CharModel, CASCADE, to_field="field")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = "schema"
|
||||||
|
|
||||||
|
# Create the table.
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.create_model(CharModel)
|
||||||
|
editor.create_model(RelationModel)
|
||||||
|
self.isolated_local_models = [CharModel, RelationModel]
|
||||||
|
constraints = self.get_constraints_for_column(
|
||||||
|
CharModel, CharModel._meta.get_field("field").column
|
||||||
|
)
|
||||||
|
self.assertIn("schema_charmodel_field_8b338dea_like", constraints)
|
||||||
|
self.assertIn(
|
||||||
|
"varchar_pattern_ops",
|
||||||
|
self.get_constraint_opclasses("schema_charmodel_field_8b338dea_like"),
|
||||||
|
)
|
||||||
|
rel_constraints = self.get_constraints_for_column(
|
||||||
|
RelationModel, RelationModel._meta.get_field("field").column
|
||||||
|
)
|
||||||
|
self.assertIn("schema_relationmodel_field_id_395fbb08_like", rel_constraints)
|
||||||
|
self.assertIn(
|
||||||
|
"varchar_pattern_ops",
|
||||||
|
self.get_constraint_opclasses(
|
||||||
|
"schema_relationmodel_field_id_395fbb08_like"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.get_column_collation(RelationModel._meta.db_table, "field_id"),
|
||||||
|
deterministic_collation,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.get_column_collation(CharModel._meta.db_table, "field"),
|
||||||
|
deterministic_collation,
|
||||||
|
)
|
||||||
|
self.assertIn("field_id", self.get_uniques(RelationModel._meta.db_table))
|
||||||
|
|
||||||
def test_alter_textfield_to_null(self):
|
def test_alter_textfield_to_null(self):
|
||||||
"""
|
"""
|
||||||
#24307 - Should skip an alter statement on databases with
|
#24307 - Should skip an alter statement on databases with
|
||||||
|
Loading…
x
Reference in New Issue
Block a user