Skip to content

Commit

Permalink
[4.2.x] Fixed #34932 -- Restored varchar_pattern_ops/text_pattern_ops…
Browse files Browse the repository at this point in the history
… index creation when deterministic collaction is set.

Regression in f3f9d03 (4.2) and
8ed25d6 (5.0).

Backport of 34b4117 from main.
  • Loading branch information
knyghty authored and felixxm committed Oct 30, 2023
1 parent 6161299 commit 109f39a
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 1 deletion.
1 change: 1 addition & 0 deletions django/db/backends/postgresql/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_covering_indexes = True
can_rename_index = True
test_collations = {
"deterministic": "C",
"non_default": "sv-x-icu",
"swedish_ci": "sv-x-icu",
}
Expand Down
16 changes: 15 additions & 1 deletion django/db/backends/postgresql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ def _create_like_index_sql(self, model, field):
return None
# Non-deterministic collations on Postgresql don't support indexes
# 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
if db_type.startswith("varchar"):
return self._create_index_sql(
Expand Down Expand Up @@ -372,3 +373,16 @@ def _create_index_sql(
include=include,
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
4 changes: 4 additions & 0 deletions docs/releases/4.2.7.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ Bugfixes
* Fixed a regression in Django 4.2 that caused a crash of
``QuerySet.aggregate()`` with aggregates referencing expressions containing
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`).
101 changes: 101 additions & 0 deletions tests/schema/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@ def get_constraints_for_column(self, model, column_name):
constraints_for_column.append(name)
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(
self,
schema_editor,
Expand Down Expand Up @@ -1378,6 +1390,95 @@ class Meta:
)
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):
"""
#24307 - Should skip an alter statement on databases with
Expand Down

0 comments on commit 109f39a

Please sign in to comment.