Skip to content

Instantly share code, notes, and snippets.

@GeyseR
Last active November 17, 2018 11:45
Show Gist options
  • Select an option

  • Save GeyseR/363b4b9d2d1cd8c91d51ebfe9369a7ac to your computer and use it in GitHub Desktop.

Select an option

Save GeyseR/363b4b9d2d1cd8c91d51ebfe9369a7ac to your computer and use it in GitHub Desktop.
Char field IExact Index
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