Last active
November 17, 2018 11:45
-
-
Save GeyseR/363b4b9d2d1cd8c91d51ebfe9369a7ac to your computer and use it in GitHub Desktop.
Char field IExact Index
This file contains hidden or 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
| from django.db.backends.ddl_references import ( | |
| Columns, IndexName, Statement, Table, | |
| ) | |
| from django.db.backends.utils import split_identifier | |
| from django.db import models | |
| class UpperColumns(Columns): | |
| def __init__(self, *args, optclass='', **kwargs): | |
| self.optclass = optclass | |
| super().__init__(*args, **kwargs) | |
| def __str__(self): | |
| def col_str(column, idx): | |
| col_name = f'UPPER({self.quote_name(column)}) {self.optclass}' | |
| if len(self.col_suffixes) > idx: | |
| col_name = f'{col_name} {self.col_suffixes[idx]}' | |
| return col_name | |
| return ', '.join( | |
| col_str(column, idx) for idx, column in enumerate(self.columns) | |
| ) | |
| class PostgresIExactCharIndex(models.Index): | |
| """ | |
| This index class should be used with `iexact` filter in django with | |
| CharFields only, as it use `varchar_pattern_ops` for building the index. | |
| See https://www.postgresql.org/docs/current/indexes-opclass.html | |
| """ | |
| suffix = 'iexact' | |
| # Allow an index name longer than 30 characters since this index can only be | |
| # used on PostgreSQL, and the Django default 30 character limit for | |
| # cross-database compatibility isn't applicable. | |
| # The "iexact" suffix is 3 letters longer than the default "idx". | |
| max_name_length = 33 | |
| sql_create_index = \ | |
| 'CREATE%(unique)s INDEX %(name)s ON %(table)s (%(columns)s)' | |
| def __init__(self, *, unique=False, **kwargs): | |
| self.unique = unique | |
| super().__init__(**kwargs) | |
| def create_sql(self, model, schema_editor, using=''): | |
| fields = [ | |
| model._meta.get_field(field_name) for field_name, _ in | |
| self.fields_orders | |
| ] | |
| col_suffixes = [order[1] for order in self.fields_orders] | |
| columns = [field.column for field in fields] | |
| table = model._meta.db_table | |
| name = self.name | |
| def create_index_name(*args, **kwargs): | |
| nonlocal name | |
| if name is None: | |
| name = schema_editor._create_index_name(*args, **kwargs) | |
| return schema_editor.quote_name(name) | |
| return Statement( | |
| self.sql_create_index, | |
| table=Table(table, schema_editor.quote_name), | |
| name=IndexName(table, columns, '', create_index_name), | |
| using=using, | |
| columns=UpperColumns( | |
| table, | |
| columns, | |
| schema_editor.quote_name, | |
| optclass='varchar_pattern_ops', | |
| col_suffixes=col_suffixes, | |
| ), | |
| unique=' UNIQUE' if self.unique else '', | |
| ) | |
| def deconstruct(self): | |
| path, _, kwargs = super().deconstruct() | |
| return path, _, {**kwargs, 'unique': self.unique} | |
| def set_name_with_model(self, model): | |
| """ | |
| Generate a unique name for the index. | |
| We would like to only override "hash_data = ...", but the entire method | |
| must be duplicated for that. | |
| """ | |
| _, table_name = split_identifier(model._meta.db_table) | |
| column_names = [ | |
| model._meta.get_field(field_name).column | |
| for field_name, order | |
| in self.fields_orders | |
| ] | |
| column_names_with_order = [ | |
| (('-%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_hash_extra_data() | |
| ) | |
| 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) <= self.max_name_length, ( | |
| 'Index too long for multiple database support. Is self.suffix ' | |
| 'longer than 3 characters?' | |
| ) | |
| self.check_name() | |
| def name_hash_extra_data(self): | |
| return [str(self.unique)] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment