Fixed #27935 -- Fixed crash with BrinIndex name > 30 characters.

This commit is contained in:
Mads Jensen 2017-03-17 19:01:25 +01:00 committed by Tim Graham
parent 93eca976c1
commit 82bb4e684f
3 changed files with 30 additions and 7 deletions

View File

@ -1,4 +1,6 @@
from django.db import connection
from django.db.models import Index from django.db.models import Index
from django.utils.functional import cached_property
__all__ = ['BrinIndex', 'GinIndex'] __all__ = ['BrinIndex', 'GinIndex']
@ -34,6 +36,14 @@ class BrinIndex(Index):
schema_editor.quote_value(self.pages_per_range)) + parameters['extra'] schema_editor.quote_value(self.pages_per_range)) + parameters['extra']
return parameters 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): class GinIndex(Index):
suffix = 'gin' suffix = 'gin'

View File

@ -4,12 +4,12 @@ from django.utils.encoding import force_bytes
__all__ = ['Index'] __all__ = ['Index']
# The max length of the names of the indexes (restricted to 30 due to Oracle)
MAX_NAME_LENGTH = 30
class Index: class Index:
suffix = 'idx' 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): def __init__(self, fields=[], name=None):
if not isinstance(fields, list): if not isinstance(fields, list):
@ -25,8 +25,8 @@ class Index:
self.name = name or '' self.name = name or ''
if self.name: if self.name:
errors = self.check_name() errors = self.check_name()
if len(self.name) > MAX_NAME_LENGTH: if len(self.name) > self.max_name_length:
errors.append('Index names cannot be longer than %s characters.' % MAX_NAME_LENGTH) errors.append('Index names cannot be longer than %s characters.' % self.max_name_length)
if errors: if errors:
raise ValueError(errors) raise ValueError(errors)
@ -100,13 +100,15 @@ class Index:
(('-%s' if order else '%s') % column_name) (('-%s' if order else '%s') % column_name)
for column_name, (field_name, order) in zip(column_names, self.fields_orders) 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] hash_data = [table_name] + column_names_with_order + [self.suffix]
self.name = '%s_%s_%s' % ( self.name = '%s_%s_%s' % (
table_name[:11], table_name[:11],
column_names[0][:7], column_names[0][:7],
'%s_%s' % (self._hash_generator(*hash_data), self.suffix), '%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 ' 'Index too long for multiple database support. Is self.suffix '
'longer than 3 characters?' 'longer than 3 characters?'
) )

View File

@ -3,7 +3,7 @@ from django.db import connection
from django.test import skipUnlessDBFeature from django.test import skipUnlessDBFeature
from . import PostgreSQLTestCase from . import PostgreSQLTestCase
from .models import CharFieldModel, IntegerArrayModel from .models import CharFieldModel, DateTimeArrayModel, IntegerArrayModel
@skipUnlessDBFeature('has_brin_index_support') @skipUnlessDBFeature('has_brin_index_support')
@ -23,6 +23,17 @@ class BrinIndexTests(PostgreSQLTestCase):
index_with_page_range = BrinIndex(fields=['title'], pages_per_range=16) index_with_page_range = BrinIndex(fields=['title'], pages_per_range=16)
self.assertNotEqual(index, index_with_page_range) 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): def test_deconstruction(self):
index = BrinIndex(fields=['title'], name='test_title_brin') index = BrinIndex(fields=['title'], name='test_title_brin')
path, args, kwargs = index.deconstruct() path, args, kwargs = index.deconstruct()