-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed #26709 -- Added class-based indexes.
Added the AddIndex and RemoveIndex operations to use them in migrations. Thanks markush, mjtamlyn, timgraham, and charettes for review and advice.
- Loading branch information
Showing
18 changed files
with
473 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,15 @@ | ||
from .fields import AddField, AlterField, RemoveField, RenameField | ||
from .models import ( | ||
AlterIndexTogether, AlterModelManagers, AlterModelOptions, AlterModelTable, | ||
AlterOrderWithRespectTo, AlterUniqueTogether, CreateModel, DeleteModel, | ||
RenameModel, | ||
AddIndex, AlterIndexTogether, AlterModelManagers, AlterModelOptions, | ||
AlterModelTable, AlterOrderWithRespectTo, AlterUniqueTogether, CreateModel, | ||
DeleteModel, RemoveIndex, RenameModel, | ||
) | ||
from .special import RunPython, RunSQL, SeparateDatabaseAndState | ||
|
||
__all__ = [ | ||
'CreateModel', 'DeleteModel', 'AlterModelTable', 'AlterUniqueTogether', | ||
'RenameModel', 'AlterIndexTogether', 'AlterModelOptions', | ||
'AddField', 'RemoveField', 'AlterField', 'RenameField', | ||
'RenameModel', 'AlterIndexTogether', 'AlterModelOptions', 'AddIndex', | ||
'RemoveIndex', 'AddField', 'RemoveField', 'AlterField', 'RenameField', | ||
'SeparateDatabaseAndState', 'RunSQL', 'RunPython', | ||
'AlterOrderWithRespectTo', 'AlterModelManagers', | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
from __future__ import unicode_literals | ||
|
||
import hashlib | ||
|
||
from django.utils.encoding import force_bytes | ||
from django.utils.functional import cached_property | ||
|
||
__all__ = ['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' | ||
|
||
def __init__(self, fields=[], name=None): | ||
if not fields: | ||
raise ValueError('At least one field is required to define an index.') | ||
self.fields = fields | ||
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 errors: | ||
raise ValueError(errors) | ||
|
||
@cached_property | ||
def name(self): | ||
if not self._name: | ||
self._name = self.get_name() | ||
self.check_name() | ||
return self._name | ||
|
||
def check_name(self): | ||
errors = [] | ||
# Name can't start with an underscore on Oracle; prepend D if needed. | ||
if self._name[0] == '_': | ||
errors.append('Index names cannot start with an underscore (_).') | ||
self._name = 'D%s' % self._name[1:] | ||
# Name can't start with a number on Oracle; prepend D if needed. | ||
elif self._name[0].isdigit(): | ||
errors.append('Index names cannot start with a number (0-9).') | ||
self._name = 'D%s' % self._name[1:] | ||
return errors | ||
|
||
def create_sql(self, schema_editor): | ||
fields = [self.model._meta.get_field(field) for field in self.fields] | ||
tablespace_sql = schema_editor._get_index_tablespace_sql(self.model, fields) | ||
columns = [field.column for field in fields] | ||
|
||
quote_name = schema_editor.quote_name | ||
return schema_editor.sql_create_index % { | ||
'table': quote_name(self.model._meta.db_table), | ||
'name': quote_name(self.name), | ||
'columns': ', '.join(quote_name(column) for column in columns), | ||
'extra': tablespace_sql, | ||
} | ||
|
||
def remove_sql(self, schema_editor): | ||
quote_name = schema_editor.quote_name | ||
return schema_editor.sql_delete_index % { | ||
'table': quote_name(self.model._meta.db_table), | ||
'name': quote_name(self.name), | ||
} | ||
|
||
def deconstruct(self): | ||
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) | ||
path = path.replace('django.db.models.indexes', 'django.db.models') | ||
return (path, (), {'fields': self.fields}) | ||
|
||
@staticmethod | ||
def _hash_generator(*args): | ||
""" | ||
Generate a 32-bit digest of a set of arguments that can be used to | ||
shorten identifying names. | ||
""" | ||
h = hashlib.md5() | ||
for arg in args: | ||
h.update(force_bytes(arg)) | ||
return h.hexdigest()[:6] | ||
|
||
def get_name(self): | ||
""" | ||
Generate a unique name for the index. | ||
The name is divided into 3 parts - table name (12 chars), field name | ||
(8 chars) and unique hash + suffix (10 chars). Each part is made to | ||
fit its size by truncating the excess length. | ||
""" | ||
table_name = self.model._meta.db_table | ||
column_names = [self.model._meta.get_field(field).column for field in self.fields] | ||
hash_data = [table_name] + column_names + [self.suffix] | ||
index_name = '%s_%s_%s' % ( | ||
table_name[:11], | ||
column_names[0][:7], | ||
'%s_%s' % (self._hash_generator(*hash_data), self.suffix), | ||
) | ||
assert len(index_name) <= 30, ( | ||
'Index too long for multiple database support. Is self.suffix ' | ||
'longer than 3 characters?' | ||
) | ||
return index_name | ||
|
||
def __repr__(self): | ||
return "<%s: fields='%s'>" % (self.__class__.__name__, ', '.join(self.fields)) | ||
|
||
def __eq__(self, other): | ||
return (self.__class__ == other.__class__) and (self.deconstruct() == other.deconstruct()) | ||
|
||
def __ne__(self, other): | ||
return not (self == other) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.