From 0b93a992e50ebe455e46d261cb6a741bba1ad76e Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Fri, 17 Mar 2017 19:01:25 +0100 Subject: [PATCH] [1.11.x] Fixed #27935 -- Fixed crash with BrinIndex name > 30 characters. Backport of 82bb4e684f9d0e5939cb0596c249954df0888e2d from master --- django/contrib/postgres/indexes.py | 10 ++++++++++ django/db/models/indexes.py | 14 ++++++++------ tests/postgres_tests/test_indexes.py | 13 ++++++++++++- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/django/contrib/postgres/indexes.py b/django/contrib/postgres/indexes.py index 185ea7435c4..785f1d80205 100644 --- a/django/contrib/postgres/indexes.py +++ b/django/contrib/postgres/indexes.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals +from django.db import connection from django.db.models import Index +from django.utils.functional import cached_property __all__ = ['BrinIndex', 'GinIndex'] @@ -36,6 +38,14 @@ class BrinIndex(Index): schema_editor.quote_value(self.pages_per_range)) + parameters['extra'] return parameters + @cached_property + def max_name_length(self): + # Allow an index name longer than 30 characters since the suffix + # is 4 characters (usual limit is 3). Since this index can only be + # used on PostgreSQL, the 30 character limit for cross-database + # compatibility isn't applicable. + return connection.ops.max_name_length() + class GinIndex(Index): suffix = 'gin' diff --git a/django/db/models/indexes.py b/django/db/models/indexes.py index b8280130814..1ef5a4ab50d 100644 --- a/django/db/models/indexes.py +++ b/django/db/models/indexes.py @@ -6,12 +6,12 @@ from django.utils.encoding import force_bytes __all__ = [str('Index')] -# The max length of the names of the indexes (restricted to 30 due to Oracle) -MAX_NAME_LENGTH = 30 - class Index(object): suffix = 'idx' + # The max length of the name of the index (restricted to 30 for + # cross-database compatibility with Oracle) + max_name_length = 30 def __init__(self, fields=[], name=None): if not isinstance(fields, list): @@ -27,8 +27,8 @@ class Index(object): self.name = name or '' if self.name: errors = self.check_name() - if len(self.name) > MAX_NAME_LENGTH: - errors.append('Index names cannot be longer than %s characters.' % MAX_NAME_LENGTH) + if len(self.name) > self.max_name_length: + errors.append('Index names cannot be longer than %s characters.' % self.max_name_length) if errors: raise ValueError(errors) @@ -102,13 +102,15 @@ class Index(object): (('-%s' if order else '%s') % column_name) for column_name, (field_name, order) in zip(column_names, self.fields_orders) ] + # The length of the parts of the name is based on the default max + # length of 30 characters. hash_data = [table_name] + column_names_with_order + [self.suffix] self.name = '%s_%s_%s' % ( table_name[:11], column_names[0][:7], '%s_%s' % (self._hash_generator(*hash_data), self.suffix), ) - assert len(self.name) <= MAX_NAME_LENGTH, ( + assert len(self.name) <= self.max_name_length, ( 'Index too long for multiple database support. Is self.suffix ' 'longer than 3 characters?' ) diff --git a/tests/postgres_tests/test_indexes.py b/tests/postgres_tests/test_indexes.py index db615612003..e26d96f0034 100644 --- a/tests/postgres_tests/test_indexes.py +++ b/tests/postgres_tests/test_indexes.py @@ -3,7 +3,7 @@ from django.db import connection from django.test import skipUnlessDBFeature from . import PostgreSQLTestCase -from .models import CharFieldModel, IntegerArrayModel +from .models import CharFieldModel, DateTimeArrayModel, IntegerArrayModel @skipUnlessDBFeature('has_brin_index_support') @@ -23,6 +23,17 @@ class BrinIndexTests(PostgreSQLTestCase): index_with_page_range = BrinIndex(fields=['title'], pages_per_range=16) self.assertNotEqual(index, index_with_page_range) + def test_name_auto_generation(self): + """ + A name longer than 30 characters (since len(BrinIndex.suffix) is 4 + rather than usual limit of 3) is okay for PostgreSQL. For this test, + the name of the field ('datetimes') must be at least 7 characters to + generate a name longer than 30 characters. + """ + index = BrinIndex(fields=['datetimes']) + index.set_name_with_model(DateTimeArrayModel) + self.assertEqual(index.name, 'postgres_te_datetim_abf104_brin') + def test_deconstruction(self): index = BrinIndex(fields=['title'], name='test_title_brin') path, args, kwargs = index.deconstruct()