summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHasan Ramezani <hasan.r67@gmail.com>2019-09-28 02:46:18 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-10-17 12:50:53 +0200
commitef4beafa2ce58a81a56ffd48a20a6153126b611a (patch)
treeea90b74c6307f51accc0f5d13d5e69d1e96b4b64
parent6f82df69efa372fb4bddf272fff577850a09f1dc (diff)
Refs #28816 -- Prevented silencing data loss when decreasing CharField.max_length for ArrayField.base_field on PostgreSQL.
-rw-r--r--django/db/backends/postgresql/schema.py18
-rw-r--r--tests/schema/tests.py48
2 files changed, 64 insertions, 2 deletions
diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py
index 6a65d12415..cf90bb8f81 100644
--- a/django/db/backends/postgresql/schema.py
+++ b/django/db/backends/postgresql/schema.py
@@ -47,6 +47,13 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
field.db_type(self.connection),
)
+ def _field_base_data_types(self, field):
+ # Yield base data types for array fields.
+ if field.base_field.get_internal_type() == 'ArrayField':
+ yield from self._field_base_data_types(field.base_field)
+ else:
+ yield self._field_data_type(field.base_field)
+
def _create_like_index_sql(self, model, field):
"""
Return the statement to create an index with varchar operator pattern
@@ -72,8 +79,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
def _alter_column_type_sql(self, model, old_field, new_field, new_type):
self.sql_alter_column_type = 'ALTER COLUMN %(column)s TYPE %(type)s'
# Cast when data type changed.
- if self._field_data_type(old_field) != self._field_data_type(new_field):
- self.sql_alter_column_type += ' USING %(column)s::%(type)s'
+ using_sql = ' USING %(column)s::%(type)s'
+ new_internal_type = new_field.get_internal_type()
+ old_internal_type = old_field.get_internal_type()
+ if new_internal_type == 'ArrayField' and new_internal_type == old_internal_type:
+ # Compare base data types for array fields.
+ if list(self._field_base_data_types(old_field)) != list(self._field_base_data_types(new_field)):
+ self.sql_alter_column_type += using_sql
+ elif self._field_data_type(old_field) != self._field_data_type(new_field):
+ self.sql_alter_column_type += using_sql
# Make ALTER TYPE with SERIAL make sense.
table = strip_quotes(model._meta.db_table)
serial_fields_map = {'bigserial': 'bigint', 'serial': 'integer', 'smallserial': 'smallint'}
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index 08a77000b2..9a2195d1d5 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -895,6 +895,54 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(Foo, old_field, new_field, strict=True)
+ @isolate_apps('schema')
+ @unittest.skipUnless(connection.vendor == 'postgresql', 'PostgreSQL specific')
+ def test_alter_array_field_decrease_base_field_length(self):
+ from django.contrib.postgres.fields import ArrayField
+
+ class ArrayModel(Model):
+ field = ArrayField(CharField(max_length=16))
+
+ class Meta:
+ app_label = 'schema'
+
+ with connection.schema_editor() as editor:
+ editor.create_model(ArrayModel)
+ self.isolated_local_models = [ArrayModel]
+ ArrayModel.objects.create(field=['x' * 16])
+ old_field = ArrayModel._meta.get_field('field')
+ new_field = ArrayField(CharField(max_length=15))
+ new_field.set_attributes_from_name('field')
+ new_field.model = ArrayModel
+ with connection.schema_editor() as editor:
+ msg = 'value too long for type character varying(15)'
+ with self.assertRaisesMessage(DataError, msg):
+ editor.alter_field(ArrayModel, old_field, new_field, strict=True)
+
+ @isolate_apps('schema')
+ @unittest.skipUnless(connection.vendor == 'postgresql', 'PostgreSQL specific')
+ def test_alter_array_field_decrease_nested_base_field_length(self):
+ from django.contrib.postgres.fields import ArrayField
+
+ class ArrayModel(Model):
+ field = ArrayField(ArrayField(CharField(max_length=16)))
+
+ class Meta:
+ app_label = 'schema'
+
+ with connection.schema_editor() as editor:
+ editor.create_model(ArrayModel)
+ self.isolated_local_models = [ArrayModel]
+ ArrayModel.objects.create(field=[['x' * 16]])
+ old_field = ArrayModel._meta.get_field('field')
+ new_field = ArrayField(ArrayField(CharField(max_length=15)))
+ new_field.set_attributes_from_name('field')
+ new_field.model = ArrayModel
+ with connection.schema_editor() as editor:
+ msg = 'value too long for type character varying(15)'
+ with self.assertRaisesMessage(DataError, msg):
+ editor.alter_field(ArrayModel, old_field, new_field, strict=True)
+
def test_alter_textfield_to_null(self):
"""
#24307 - Should skip an alter statement on databases with