mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #25412 -- Fixed missing PostgreSQL index on Char/TextField when using AlterField.
Thanks to Emanuele Palazzetti for the help.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							cf546e11ac
						
					
				
				
					commit
					3a36c80795
				
			| @@ -23,25 +23,33 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): | ||||
|             return output | ||||
|  | ||||
|         for field in model._meta.local_fields: | ||||
|             db_type = field.db_type(connection=self.connection) | ||||
|             if db_type is not None and (field.db_index or field.unique): | ||||
|                 # Fields with database column types of `varchar` and `text` need | ||||
|                 # a second index that specifies their operator class, which is | ||||
|                 # needed when performing correct LIKE queries outside the | ||||
|                 # C locale. See #12234. | ||||
|                 # | ||||
|                 # The same doesn't apply to array fields such as varchar[size] | ||||
|                 # and text[size], so skip them. | ||||
|                 if '[' in db_type: | ||||
|                     continue | ||||
|                 if db_type.startswith('varchar'): | ||||
|                     output.append(self._create_index_sql( | ||||
|                         model, [field], suffix='_like', sql=self.sql_create_varchar_index)) | ||||
|                 elif db_type.startswith('text'): | ||||
|                     output.append(self._create_index_sql( | ||||
|                         model, [field], suffix='_like', sql=self.sql_create_text_index)) | ||||
|             like_index_statement = self._create_like_index_sql(model, field) | ||||
|             if like_index_statement is not None: | ||||
|                 output.append(like_index_statement) | ||||
|         return output | ||||
|  | ||||
|     def _create_like_index_sql(self, model, field): | ||||
|         """ | ||||
|         Return the statement to create an index with varchar operator pattern | ||||
|         when the column type is 'varchar' or 'text', otherwise return None. | ||||
|         """ | ||||
|         db_type = field.db_type(connection=self.connection) | ||||
|         if db_type is not None and (field.db_index or field.unique): | ||||
|             # Fields with database column types of `varchar` and `text` need | ||||
|             # a second index that specifies their operator class, which is | ||||
|             # needed when performing correct LIKE queries outside the | ||||
|             # C locale. See #12234. | ||||
|             # | ||||
|             # The same doesn't apply to array fields such as varchar[size] | ||||
|             # and text[size], so skip them. | ||||
|             if '[' in db_type: | ||||
|                 return None | ||||
|             if db_type.startswith('varchar'): | ||||
|                 return self._create_index_sql(model, [field], suffix='_like', sql=self.sql_create_varchar_index) | ||||
|             elif db_type.startswith('text'): | ||||
|                 return self._create_index_sql(model, [field], suffix='_like', sql=self.sql_create_text_index) | ||||
|         return None | ||||
|  | ||||
|     def _alter_column_type_sql(self, table, old_field, new_field, new_type): | ||||
|         """ | ||||
|         Makes ALTER TYPE with SERIAL make sense. | ||||
| @@ -94,3 +102,23 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): | ||||
|             return super(DatabaseSchemaEditor, self)._alter_column_type_sql( | ||||
|                 table, old_field, new_field, new_type | ||||
|             ) | ||||
|  | ||||
|     def _alter_field(self, model, old_field, new_field, old_type, new_type, | ||||
|                      old_db_params, new_db_params, strict=False): | ||||
|         super(DatabaseSchemaEditor, self)._alter_field( | ||||
|             model, old_field, new_field, old_type, new_type, old_db_params, | ||||
|             new_db_params, strict, | ||||
|         ) | ||||
|         # Added an index? Create any PostgreSQL-specific indexes. | ||||
|         if ((not old_field.db_index and new_field.db_index) or (not old_field.unique and new_field.unique)): | ||||
|             like_index_statement = self._create_like_index_sql(model, new_field) | ||||
|             if like_index_statement is not None: | ||||
|                 self.execute(like_index_statement) | ||||
|  | ||||
|         # Removed an index? Drop any PostgreSQL-specific indexes. | ||||
|         if ((not new_field.db_index and old_field.db_index) or (not new_field.unique and old_field.unique)): | ||||
|             index_to_remove = self._create_index_name(model, [old_field.column], suffix='_like') | ||||
|             index_names = self._constraint_names(model, [old_field.column], index=True) | ||||
|             for index_name in index_names: | ||||
|                 if index_name == index_to_remove: | ||||
|                     self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name)) | ||||
|   | ||||
| @@ -30,3 +30,7 @@ Bugfixes | ||||
|  | ||||
| * Fixed a state bug when migrating a ``SeparateDatabaseAndState`` operation | ||||
|   backwards (:ticket:`25896`). | ||||
|  | ||||
| * Fixed missing ``varchar/text_pattern_ops`` index on ``CharField`` and | ||||
|   ``TextField`` respectively when using ``AlterField`` on PostgreSQL | ||||
|   (:ticket:`25412`). | ||||
|   | ||||
| @@ -22,3 +22,7 @@ Bugfixes | ||||
|  | ||||
| * Fixed a regression in ``CommonMiddleware`` causing ``If-None-Match`` checks | ||||
|   to always return HTTP 200 (:ticket:`25900`). | ||||
|  | ||||
| * Fixed missing ``varchar/text_pattern_ops`` index on ``CharField`` and | ||||
|   ``TextField`` respectively when using ``AlterField`` on PostgreSQL | ||||
|   (:ticket:`25412`). | ||||
|   | ||||
| @@ -1707,3 +1707,71 @@ class SchemaTests(TransactionTestCase): | ||||
|         new_field.set_attributes_from_name("info") | ||||
|         with connection.schema_editor() as editor: | ||||
|             editor.add_field(Author, new_field) | ||||
|  | ||||
|     @unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL specific") | ||||
|     def test_alter_field_add_index_to_charfield(self): | ||||
|         # Create the table | ||||
|         with connection.schema_editor() as editor: | ||||
|             editor.create_model(Author) | ||||
|         # Ensure the table is there and has no index | ||||
|         self.assertNotIn('name', self.get_indexes(Author._meta.db_table)) | ||||
|         # Alter to add the index | ||||
|         old_field = Author._meta.get_field('name') | ||||
|         new_field = CharField(max_length=255, db_index=True) | ||||
|         new_field.set_attributes_from_name('name') | ||||
|         with connection.schema_editor() as editor: | ||||
|             editor.alter_field(Author, old_field, new_field, strict=True) | ||||
|         # Check that all the constraints are there | ||||
|         constraints = self.get_constraints(Author._meta.db_table) | ||||
|         name_indexes = [] | ||||
|         for name, details in constraints.items(): | ||||
|             if details['columns'] == ['name']: | ||||
|                 name_indexes.append(name) | ||||
|         self.assertEqual(2, len(name_indexes), 'Indexes are missing for name column') | ||||
|         # Check that one of the indexes ends with `_like` | ||||
|         like_index = [x for x in name_indexes if x.endswith('_like')] | ||||
|         self.assertEqual(1, len(like_index), 'Index with the operator class is missing for the name column') | ||||
|         # Remove the index | ||||
|         with connection.schema_editor() as editor: | ||||
|             editor.alter_field(Author, new_field, old_field, strict=True) | ||||
|         # Ensure the name constraints where dropped | ||||
|         constraints = self.get_constraints(Author._meta.db_table) | ||||
|         name_indexes = [] | ||||
|         for details in constraints.values(): | ||||
|             if details['columns'] == ['name']: | ||||
|                 name_indexes.append(details) | ||||
|         self.assertEqual(0, len(name_indexes), 'Indexes were not dropped for the name column') | ||||
|  | ||||
|     @unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL specific") | ||||
|     def test_alter_field_add_index_to_textfield(self): | ||||
|         # Create the table | ||||
|         with connection.schema_editor() as editor: | ||||
|             editor.create_model(Note) | ||||
|         # Ensure the table is there and has no index | ||||
|         self.assertNotIn('info', self.get_indexes(Note._meta.db_table)) | ||||
|         # Alter to add the index | ||||
|         old_field = Note._meta.get_field('info') | ||||
|         new_field = TextField(db_index=True) | ||||
|         new_field.set_attributes_from_name('info') | ||||
|         with connection.schema_editor() as editor: | ||||
|             editor.alter_field(Note, old_field, new_field, strict=True) | ||||
|         # Check that all the constraints are there | ||||
|         constraints = self.get_constraints(Note._meta.db_table) | ||||
|         info_indexes = [] | ||||
|         for name, details in constraints.items(): | ||||
|             if details['columns'] == ['info']: | ||||
|                 info_indexes.append(name) | ||||
|         self.assertEqual(2, len(info_indexes), 'Indexes are missing for info column') | ||||
|         # Check that one of the indexes ends with `_like` | ||||
|         like_index = [x for x in info_indexes if x.endswith('_like')] | ||||
|         self.assertEqual(1, len(like_index), 'Index with the operator class is missing for the info column') | ||||
|         # Remove the index | ||||
|         with connection.schema_editor() as editor: | ||||
|             editor.alter_field(Note, new_field, old_field, strict=True) | ||||
|         # Ensure the info constraints where dropped | ||||
|         constraints = self.get_constraints(Note._meta.db_table) | ||||
|         info_indexes = [] | ||||
|         for details in constraints.values(): | ||||
|             if details['columns'] == ['info']: | ||||
|                 info_indexes.append(details) | ||||
|         self.assertEqual(0, len(info_indexes), 'Indexes were not dropped for the info column') | ||||
|   | ||||
		Reference in New Issue
	
	Block a user