Skip to content

Commit 487db71

Browse files
committed
Add UniqueIndex for materialized views
`unique=True` on a model field in a materialized view will generate a `ALTER TABLE` operation. This will not work, but `CREATE UNIQUE INDEX` will.
1 parent 3aac310 commit 487db71

File tree

4 files changed

+82
-1
lines changed

4 files changed

+82
-1
lines changed

docs/source/indexes.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@
33
Indexes
44
=======
55

6+
.. _unique_index_page:
7+
8+
Unique Index
9+
-----------------------------
10+
The :class:`~psqlextra.indexes.UniqueIndex` lets you create a unique index. Normally Django only allows you to create unique indexes by specifying ``unique=True`` on the model field.
11+
12+
Although it can be used on any Django model, it is most useful on views and materialized views where ``unique=True`` does not work.
13+
14+
.. code-block:: python
15+
16+
from django.db import models
17+
from psqlextra.indexes import UniqueIndex
18+
19+
class Model(models.Model):
20+
class Meta:
21+
indexes = [
22+
UniqueIndex(fields=['name']),
23+
]
24+
25+
name = models.CharField(max_length=255)
26+
27+
Model.objects.create(name='henk')
28+
Model.objects.create(name='Henk') # raises IntegrityError
29+
30+
631
.. _conditional_unique_index_page:
732

833
Conditional Unique Index

psqlextra/indexes/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
from .case_insensitive_unique_index import CaseInsensitiveUniqueIndex
22
from .conditional_unique_index import ConditionalUniqueIndex
3+
from .unique_index import UniqueIndex
34

4-
__all__ = ["ConditionalUniqueIndex", "CaseInsensitiveUniqueIndex"]
5+
__all__ = [
6+
"UniqueIndex",
7+
"ConditionalUniqueIndex",
8+
"CaseInsensitiveUniqueIndex",
9+
]

psqlextra/indexes/unique_index.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import django
2+
3+
from django.db.models.indexes import Index
4+
5+
6+
class UniqueIndex(Index):
7+
def create_sql(self, *args, **kwargs):
8+
if django.VERSION >= (2, 0):
9+
statement = super().create_sql(*args, **kwargs)
10+
statement.template = self._rewrite_sql(statement.template)
11+
return statement
12+
13+
sql = super().create_sql(*args, **kwargs)
14+
return self._rewrite_sql(sql)
15+
16+
@staticmethod
17+
def _rewrite_sql(sql: str) -> str:
18+
return sql.replace("CREATE INDEX", "CREATE UNIQUE INDEX")

tests/test_unique_index.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from django.db import models
2+
from django.db.migrations import AddIndex, CreateModel
3+
4+
from psqlextra.indexes import UniqueIndex
5+
6+
from .migrations import apply_migration, filtered_schema_editor
7+
8+
9+
def test_unique_index_migrations():
10+
index = UniqueIndex(fields=["name", "other_name"], name="index1")
11+
12+
ops = [
13+
CreateModel(
14+
name="mymodel",
15+
fields=[
16+
("name", models.TextField()),
17+
("other_name", models.TextField()),
18+
],
19+
options={
20+
# "indexes": [index],
21+
},
22+
),
23+
AddIndex(model_name="mymodel", index=index),
24+
]
25+
26+
with filtered_schema_editor("CREATE UNIQUE INDEX") as calls:
27+
apply_migration(ops)
28+
29+
calls = [call[0] for _, call, _ in calls["CREATE UNIQUE INDEX"]]
30+
31+
db_table = "tests_mymodel"
32+
query = 'CREATE UNIQUE INDEX "index1" ON "{0}" ("name", "other_name")'
33+
assert str(calls[0]) == query.format(db_table)

0 commit comments

Comments
 (0)