1
0
mirror of https://github.com/django/django.git synced 2025-10-24 14:16:09 +00:00

Fixed #31777 -- Added support for database collations to Char/TextFields.

Thanks Simon Charette and Mariusz Felisiak for reviews.
This commit is contained in:
Tom Carrick
2020-07-18 13:17:39 +02:00
committed by Mariusz Felisiak
parent ba6b32e5ef
commit e387f191f7
25 changed files with 544 additions and 30 deletions

View File

@@ -185,6 +185,14 @@ class SchemaTests(TransactionTestCase):
counts['indexes'] += 1
return counts
def get_column_collation(self, table, column):
with connection.cursor() as cursor:
return next(
f.collation
for f in connection.introspection.get_table_description(cursor, table)
if f.name == column
)
def assertIndexOrder(self, table, index, order):
constraints = self.get_constraints(table)
self.assertIn(index, constraints)
@@ -3224,3 +3232,147 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor(atomic=True) as editor:
editor.alter_db_table(Foo, Foo._meta.db_table, 'renamed_table')
Foo._meta.db_table = 'renamed_table'
@isolate_apps('schema')
@skipUnlessDBFeature('supports_collation_on_charfield')
def test_db_collation_charfield(self):
collation = connection.features.test_collations['non_default']
class Foo(Model):
field = CharField(max_length=255, db_collation=collation)
class Meta:
app_label = 'schema'
self.isolated_local_models = [Foo]
with connection.schema_editor() as editor:
editor.create_model(Foo)
self.assertEqual(
self.get_column_collation(Foo._meta.db_table, 'field'),
collation,
)
@isolate_apps('schema')
@skipUnlessDBFeature('supports_collation_on_textfield')
def test_db_collation_textfield(self):
collation = connection.features.test_collations['non_default']
class Foo(Model):
field = TextField(db_collation=collation)
class Meta:
app_label = 'schema'
self.isolated_local_models = [Foo]
with connection.schema_editor() as editor:
editor.create_model(Foo)
self.assertEqual(
self.get_column_collation(Foo._meta.db_table, 'field'),
collation,
)
@skipUnlessDBFeature('supports_collation_on_charfield')
def test_add_field_db_collation(self):
with connection.schema_editor() as editor:
editor.create_model(Author)
collation = connection.features.test_collations['non_default']
new_field = CharField(max_length=255, db_collation=collation)
new_field.set_attributes_from_name('alias')
with connection.schema_editor() as editor:
editor.add_field(Author, new_field)
columns = self.column_classes(Author)
self.assertEqual(
columns['alias'][0],
connection.features.introspected_field_types['CharField'],
)
self.assertEqual(columns['alias'][1][8], collation)
@skipUnlessDBFeature('supports_collation_on_charfield')
def test_alter_field_db_collation(self):
with connection.schema_editor() as editor:
editor.create_model(Author)
collation = connection.features.test_collations['non_default']
old_field = Author._meta.get_field('name')
new_field = CharField(max_length=255, db_collation=collation)
new_field.set_attributes_from_name('name')
new_field.model = Author
with connection.schema_editor() as editor:
editor.alter_field(Author, old_field, new_field, strict=True)
self.assertEqual(
self.get_column_collation(Author._meta.db_table, 'name'),
collation,
)
with connection.schema_editor() as editor:
editor.alter_field(Author, new_field, old_field, strict=True)
self.assertIsNone(self.get_column_collation(Author._meta.db_table, 'name'))
@skipUnlessDBFeature('supports_collation_on_charfield')
def test_alter_field_type_and_db_collation(self):
with connection.schema_editor() as editor:
editor.create_model(Note)
collation = connection.features.test_collations['non_default']
old_field = Note._meta.get_field('info')
new_field = CharField(max_length=255, db_collation=collation)
new_field.set_attributes_from_name('info')
new_field.model = Note
with connection.schema_editor() as editor:
editor.alter_field(Note, old_field, new_field, strict=True)
columns = self.column_classes(Note)
self.assertEqual(
columns['info'][0],
connection.features.introspected_field_types['CharField'],
)
self.assertEqual(columns['info'][1][8], collation)
with connection.schema_editor() as editor:
editor.alter_field(Note, new_field, old_field, strict=True)
columns = self.column_classes(Note)
self.assertEqual(columns['info'][0], 'TextField')
self.assertIsNone(columns['info'][1][8])
@skipUnlessDBFeature(
'supports_collation_on_charfield',
'supports_non_deterministic_collations',
)
def test_ci_cs_db_collation(self):
cs_collation = connection.features.test_collations.get('cs')
ci_collation = connection.features.test_collations.get('ci')
try:
if connection.vendor == 'mysql':
cs_collation = 'latin1_general_cs'
elif connection.vendor == 'postgresql':
cs_collation = 'en-x-icu'
with connection.cursor() as cursor:
cursor.execute(
"CREATE COLLATION IF NOT EXISTS case_insensitive "
"(provider = icu, locale = 'und-u-ks-level2', "
"deterministic = false)"
)
ci_collation = 'case_insensitive'
# Create the table.
with connection.schema_editor() as editor:
editor.create_model(Author)
# Case-insensitive collation.
old_field = Author._meta.get_field('name')
new_field_ci = CharField(max_length=255, db_collation=ci_collation)
new_field_ci.set_attributes_from_name('name')
new_field_ci.model = Author
with connection.schema_editor() as editor:
editor.alter_field(Author, old_field, new_field_ci, strict=True)
Author.objects.create(name='ANDREW')
self.assertIs(Author.objects.filter(name='Andrew').exists(), True)
# Case-sensitive collation.
new_field_cs = CharField(max_length=255, db_collation=cs_collation)
new_field_cs.set_attributes_from_name('name')
new_field_cs.model = Author
with connection.schema_editor() as editor:
editor.alter_field(Author, new_field_ci, new_field_cs, strict=True)
self.assertIs(Author.objects.filter(name='Andrew').exists(), False)
finally:
if connection.vendor == 'postgresql':
with connection.cursor() as cursor:
cursor.execute('DROP COLLATION IF EXISTS case_insensitive')