summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorIan Foote <python@ian.feete.org>2017-10-11 22:55:52 +0530
committerTim Graham <timograham@gmail.com>2018-06-29 17:00:28 -0400
commit38cada7c94f5f73d2d47a0a730ea5d71d266fa2c (patch)
treef7cfb69df62a07b0cda4b3c0d1c4a2f6ea5780bf /django
parentb4cba4ed625ce7c88675616b3bbb237c28a926d1 (diff)
Fixed #28077 -- Added support for PostgreSQL opclasses in Index.
Thanks Vinay Karanam for the initial patch.
Diffstat (limited to 'django')
-rw-r--r--django/db/backends/base/schema.py7
-rw-r--r--django/db/backends/ddl_references.py18
-rw-r--r--django/db/backends/postgresql/schema.py12
-rw-r--r--django/db/models/indexes.py13
4 files changed, 42 insertions, 8 deletions
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index a722e497c3..ec2cf0e5c7 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -907,7 +907,7 @@ class BaseDatabaseSchemaEditor:
return ''
def _create_index_sql(self, model, fields, *, name=None, suffix='', using='',
- db_tablespace=None, col_suffixes=(), sql=None):
+ db_tablespace=None, col_suffixes=(), sql=None, opclasses=()):
"""
Return the SQL statement to create the index for one or several fields.
`sql` can be specified if the syntax differs from the standard (GIS
@@ -929,10 +929,13 @@ class BaseDatabaseSchemaEditor:
table=Table(table, self.quote_name),
name=IndexName(table, columns, suffix, create_index_name),
using=using,
- columns=Columns(table, columns, self.quote_name, col_suffixes=col_suffixes),
+ columns=self._index_columns(table, columns, col_suffixes, opclasses),
extra=tablespace_sql,
)
+ def _index_columns(self, table, columns, col_suffixes, opclasses):
+ return Columns(table, columns, self.quote_name, col_suffixes=col_suffixes)
+
def _model_indexes_sql(self, model):
"""
Return a list of all index SQL statements (field indexes,
diff --git a/django/db/backends/ddl_references.py b/django/db/backends/ddl_references.py
index b894d58793..d71f6169ea 100644
--- a/django/db/backends/ddl_references.py
+++ b/django/db/backends/ddl_references.py
@@ -103,6 +103,24 @@ class IndexName(TableColumns):
return self.create_index_name(self.table, self.columns, self.suffix)
+class IndexColumns(Columns):
+ def __init__(self, table, columns, quote_name, col_suffixes=(), opclasses=()):
+ self.opclasses = opclasses
+ super().__init__(table, columns, quote_name, col_suffixes)
+
+ def __str__(self):
+ def col_str(column, idx):
+ try:
+ col = self.quote_name(column) + self.col_suffixes[idx]
+ except IndexError:
+ col = self.quote_name(column)
+ # Index.__init__() guarantees that self.opclasses is the same
+ # length as self.columns.
+ return '{} {}'.format(col, self.opclasses[idx])
+
+ return ', '.join(col_str(column, idx) for idx, column in enumerate(self.columns))
+
+
class ForeignKeyName(TableColumns):
"""Hold a reference to a foreign key name."""
diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py
index 18388cc523..feaddfab52 100644
--- a/django/db/backends/postgresql/schema.py
+++ b/django/db/backends/postgresql/schema.py
@@ -1,6 +1,7 @@
import psycopg2
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
+from django.db.backends.ddl_references import IndexColumns
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
@@ -12,8 +13,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
sql_set_sequence_max = "SELECT setval('%(sequence)s', MAX(%(column)s)) FROM %(table)s"
sql_create_index = "CREATE INDEX %(name)s ON %(table)s%(using)s (%(columns)s)%(extra)s"
- sql_create_varchar_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s varchar_pattern_ops)%(extra)s"
- sql_create_text_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s text_pattern_ops)%(extra)s"
sql_delete_index = "DROP INDEX IF EXISTS %(name)s"
# Setting the constraint to IMMEDIATE runs any deferred checks to allow
@@ -49,9 +48,9 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
if '[' in db_type:
return None
if db_type.startswith('varchar'):
- return self._create_index_sql(model, [field], suffix='_like', sql=self.sql_create_varchar_index)
+ return self._create_index_sql(model, [field], suffix='_like', opclasses=['varchar_pattern_ops'])
elif db_type.startswith('text'):
- return self._create_index_sql(model, [field], suffix='_like', sql=self.sql_create_text_index)
+ return self._create_index_sql(model, [field], suffix='_like', opclasses=['text_pattern_ops'])
return None
def _alter_column_type_sql(self, model, old_field, new_field, new_type):
@@ -132,3 +131,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
if old_field.unique and not (new_field.db_index or new_field.unique):
index_to_remove = self._create_index_name(model._meta.db_table, [old_field.column], suffix='_like')
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_to_remove))
+
+ def _index_columns(self, table, columns, col_suffixes, opclasses):
+ if opclasses:
+ return IndexColumns(table, columns, self.quote_name, col_suffixes=col_suffixes, opclasses=opclasses)
+ return super()._index_columns(table, columns, col_suffixes, opclasses)
diff --git a/django/db/models/indexes.py b/django/db/models/indexes.py
index 9bfb9e0558..c378b13a5c 100644
--- a/django/db/models/indexes.py
+++ b/django/db/models/indexes.py
@@ -12,9 +12,15 @@ class Index:
# cross-database compatibility with Oracle)
max_name_length = 30
- def __init__(self, *, fields=(), name=None, db_tablespace=None):
+ def __init__(self, *, fields=(), name=None, db_tablespace=None, opclasses=()):
+ if opclasses and not name:
+ raise ValueError('An index must be named to use opclasses.')
if not isinstance(fields, (list, tuple)):
raise ValueError('Index.fields must be a list or tuple.')
+ if not isinstance(opclasses, (list, tuple)):
+ raise ValueError('Index.opclasses must be a list or tuple.')
+ if opclasses and len(fields) != len(opclasses):
+ raise ValueError('Index.fields and Index.opclasses must have the same number of elements.')
if not fields:
raise ValueError('At least one field is required to define an index.')
self.fields = list(fields)
@@ -31,6 +37,7 @@ class Index:
if errors:
raise ValueError(errors)
self.db_tablespace = db_tablespace
+ self.opclasses = opclasses
def check_name(self):
errors = []
@@ -49,7 +56,7 @@ class Index:
col_suffixes = [order[1] for order in self.fields_orders]
return schema_editor._create_index_sql(
model, fields, name=self.name, using=using, db_tablespace=self.db_tablespace,
- col_suffixes=col_suffixes,
+ col_suffixes=col_suffixes, opclasses=self.opclasses,
)
def remove_sql(self, model, schema_editor):
@@ -65,6 +72,8 @@ class Index:
kwargs = {'fields': self.fields, 'name': self.name}
if self.db_tablespace is not None:
kwargs['db_tablespace'] = self.db_tablespace
+ if self.opclasses:
+ kwargs['opclasses'] = self.opclasses
return (path, (), kwargs)
def clone(self):