Fixed #30916 -- Added support for functional unique constraints.

Thanks Ian Foote and Mariusz Felisiak for reviews.
This commit is contained in:
Hannes Ljungberg 2021-02-06 20:45:54 +01:00 committed by Mariusz Felisiak
parent 19ce1d493a
commit 3aa545281e
15 changed files with 779 additions and 38 deletions

View file

@ -391,6 +391,8 @@ Models
* **models.W042**: Auto-created primary key used when not defining a primary
key type, by default ``django.db.models.AutoField``.
* **models.W043**: ``<database>`` does not support indexes on expressions.
* **models.W044**: ``<database>`` does not support unique constraints on
expressions.
Security
--------

View file

@ -183,10 +183,10 @@ available from the ``django.contrib.postgres.indexes`` module.
.. class:: OpClass(expression, name)
An ``OpClass()`` expression represents the ``expression`` with a custom
`operator class`_ that can be used to define functional indexes. To use it,
you need to add ``'django.contrib.postgres'`` in your
:setting:`INSTALLED_APPS`. Set the ``name`` parameter to the name of the
`operator class`_.
`operator class`_ that can be used to define functional indexes or unique
constraints. To use it, you need to add ``'django.contrib.postgres'`` in
your :setting:`INSTALLED_APPS`. Set the ``name`` parameter to the name of
the `operator class`_.
For example::
@ -197,4 +197,18 @@ available from the ``django.contrib.postgres.indexes`` module.
creates an index on ``Lower('username')`` using ``varchar_pattern_ops``.
Another example::
UniqueConstraint(
OpClass(Upper('description'), name='text_pattern_ops'),
name='upper_description_unique',
)
creates a unique constraint on ``Upper('description')`` using
``text_pattern_ops``.
.. versionchanged:: 4.0
Support for functional unique constraints was added.
.. _operator class: https://www.postgresql.org/docs/current/indexes-opclass.html

View file

@ -69,10 +69,30 @@ constraint.
``UniqueConstraint``
====================
.. class:: UniqueConstraint(*, fields, name, condition=None, deferrable=None, include=None, opclasses=())
.. class:: UniqueConstraint(*expressions, fields=(), name=None, condition=None, deferrable=None, include=None, opclasses=())
Creates a unique constraint in the database.
``expressions``
---------------
.. attribute:: UniqueConstraint.expressions
.. versionadded:: 4.0
Positional argument ``*expressions`` allows creating functional unique
constraints on expressions and database functions.
For example::
UniqueConstraint(Lower('name').desc(), 'category', name='unique_lower_name_category')
creates a unique constraint on the lowercased value of the ``name`` field in
descending order and the ``category`` field in the default ascending order.
Functional unique constraints have the same database restrictions as
:attr:`Index.expressions`.
``fields``
----------

View file

@ -28,6 +28,36 @@ The Django 3.2.x series is the last to support Python 3.6 and 3.7.
What's new in Django 4.0
========================
Functional unique constraints
-----------------------------
The new :attr:`*expressions <django.db.models.UniqueConstraint.expressions>`
positional argument of
:class:`UniqueConstraint() <django.db.models.UniqueConstraint>` enables
creating functional unique constraints on expressions and database functions.
For example::
from django.db import models
from django.db.models import UniqueConstraint
from django.db.models.functions import Lower
class MyModel(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
class Meta:
indexes = [
UniqueConstraint(
Lower('first_name'),
Lower('last_name').desc(),
name='first_last_name_unique',
),
]
Functional unique constraints are added to models using the
:attr:`Meta.constraints <django.db.models.Options.constraints>` option.
Minor features
--------------