Skip to content

Instantly share code, notes, and snippets.

@MarkusH
Created April 14, 2015 19:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MarkusH/9084e4279880fc686ec1 to your computer and use it in GitHub Desktop.
Save MarkusH/9084e4279880fc686ec1 to your computer and use it in GitHub Desktop.
Postgresql function based indexes in Django (based on 825bb0ab08cec353edcd2b9aea651bfe9392ef97)
diff --git a/django/db/backends/postgresql_psycopg2/schema.py b/django/db/backends/postgresql_psycopg2/schema.py
index 8340059..692866e 100644
--- a/django/db/backends/postgresql_psycopg2/schema.py
+++ b/django/db/backends/postgresql_psycopg2/schema.py
@@ -1,6 +1,21 @@
import psycopg2
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
+from django.db.models.expressions import Func
+from django.db.models.sql.compiler import SQLCompiler
+
+
+class SQLFuncCompiler(SQLCompiler):
+
+ def __init__(self, connection):
+ super(SQLFuncCompiler, self).__init__(None, connection, None)
+
+ def quote_name_unless_alias(self, name):
+ if name in self.quote_cache:
+ return self.quote_cache[name]
+ r = self.connection.ops.quote_name(name)
+ self.quote_cache[name] = r
+ return r
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
@@ -19,6 +34,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
if not model._meta.managed or model._meta.proxy or model._meta.swapped:
return output
+ compiler = SQLFuncCompiler(self.connection)
+
for field in model._meta.local_fields:
db_type = field.db_type(connection=self.connection)
if db_type is not None and (field.db_index or field.unique):
@@ -32,6 +49,16 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
elif db_type.startswith('text'):
output.append(self._create_index_sql(
model, [field], suffix='_like', sql=self.sql_create_text_index))
+ if isinstance(field.db_index, Func):
+ columns, params = field.db_index.as_sql(compiler, self.connection)
+ suffix = "_func"
+ output.append(self.sql_create_index % {
+ "table": self.quote_name(model._meta.db_table),
+ "name": self.quote_name(self._create_index_name(model, columns, suffix=suffix)),
+ "columns": columns,
+ "extra": '',
+ })
+
return output
def _alter_column_type_sql(self, table, column, type):
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index b305330..8a75d44 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -278,10 +278,11 @@ class Field(RegisterLookupMixin):
return []
def _check_db_index(self):
- if self.db_index not in (None, True, False):
+ from django.db.models.expressions import Func # circular import
+ if self.db_index not in (None, True, False) and not isinstance(self.db_index, Func):
return [
checks.Error(
- "'db_index' must be None, True or False.",
+ "'db_index' must be None, True or False or a database function instance.",
hint=None,
obj=self,
id='fields.E006',
django=# \d c_project
Table "public.c_project"
Column | Type | Modifiers
-------------+--------------------------+--------------------------------------------------------
id | integer | not null default nextval('c_project_id_seq'::regclass)
name | character varying(100) | not null
description | character varying(500) | not null
createdBy | character varying(50) | not null
createdOn | timestamp with time zone | not null
modifiedBy | character varying(50) |
modifiedOn | timestamp with time zone | not null
Indexes:
"c_project_pkey" PRIMARY KEY, btree (id)
"c_project_67daf92c" btree (description)
"c_project_L_a3a6aa61_func" btree (lower(name::text))
"c_project_U_ee9b75f9_func" btree (upper(description::text))
"c_project_b068931c" btree (name)
"c_project_description_107f2cb7_like" btree (description varchar_pattern_ops)
"c_project_name_d268d16e_like" btree (name varchar_pattern_ops)
Referenced by:
TABLE "c_projectuser" CONSTRAINT "c_projectuser_project_id_221391ba_fk_c_project_id" FOREIGN KEY (project_id) REFERENCES c_project(id) DEFERRABLE INITIALLY DEFERRED
from django.db import models
from django.db.models.expressions import Ref
from django.db.models.functions import Lower, Upper
class Project(models.Model):
name = models.CharField(max_length=100, db_index=Lower(Ref('name', None)))
description = models.CharField(max_length=500, db_index=Upper(Ref('description', None)))
createdBy = models.CharField(max_length=50)
createdOn = models.DateTimeField(auto_now_add=True)
modifiedBy = models.CharField(max_length=50, null=True, blank=True)
modifiedOn = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
In [1]: from django.db import connection
In [2]: from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
In [3]: from c.models import Project
In [4]: schema_editor = DatabaseSchemaEditor(connection, True)
In [5]: schema_editor._model_indexes_sql(Project)
Out[5]:
['CREATE INDEX "c_project_b068931c" ON "c_project" ("name")',
'CREATE INDEX "c_project_67daf92c" ON "c_project" ("description")',
'CREATE INDEX "c_project_name_d268d16e_like" ON "c_project" ("name" varchar_pattern_ops)',
'CREATE INDEX "c_project_L_a3a6aa61_func" ON "c_project" (LOWER("name"))',
'CREATE INDEX "c_project_description_107f2cb7_like" ON "c_project" ("description" varchar_pattern_ops)',
'CREATE INDEX "c_project_U_ee9b75f9_func" ON "c_project" (UPPER("description"))']
In [6]: with connection.cursor() as cursor:
...: for q in schema_editor._model_indexes_sql(Project):
...: cursor.execute(q)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment