2019-07-12 11:08:00 +00:00
|
|
|
========================================
|
|
|
|
PostgreSQL specific database constraints
|
|
|
|
========================================
|
|
|
|
|
|
|
|
.. module:: django.contrib.postgres.constraints
|
|
|
|
:synopsis: PostgreSQL specific database constraint
|
|
|
|
|
|
|
|
PostgreSQL supports additional data integrity constraints available from the
|
|
|
|
``django.contrib.postgres.constraints`` module. They are added in the model
|
|
|
|
:attr:`Meta.constraints <django.db.models.Options.constraints>` option.
|
|
|
|
|
|
|
|
``ExclusionConstraint``
|
|
|
|
=======================
|
|
|
|
|
2023-02-14 20:06:45 +00:00
|
|
|
.. class:: ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, violation_error_code=None, violation_error_message=None)
|
2019-07-12 11:08:00 +00:00
|
|
|
|
|
|
|
Creates an exclusion constraint in the database. Internally, PostgreSQL
|
|
|
|
implements exclusion constraints using indexes. The default index type is
|
|
|
|
`GiST <https://www.postgresql.org/docs/current/gist.html>`_. To use them,
|
|
|
|
you need to activate the `btree_gist extension
|
|
|
|
<https://www.postgresql.org/docs/current/btree-gist.html>`_ on PostgreSQL.
|
|
|
|
You can install it using the
|
|
|
|
:class:`~django.contrib.postgres.operations.BtreeGistExtension` migration
|
|
|
|
operation.
|
|
|
|
|
|
|
|
If you attempt to insert a new row that conflicts with an existing row, an
|
|
|
|
:exc:`~django.db.IntegrityError` is raised. Similarly, when update
|
|
|
|
conflicts with an existing row.
|
|
|
|
|
2022-01-31 15:04:13 +00:00
|
|
|
Exclusion constraints are checked during the :ref:`model validation
|
|
|
|
<validating-objects>`.
|
|
|
|
|
2019-07-12 11:08:00 +00:00
|
|
|
``name``
|
|
|
|
--------
|
|
|
|
|
|
|
|
.. attribute:: ExclusionConstraint.name
|
|
|
|
|
2022-03-16 15:05:12 +00:00
|
|
|
See :attr:`.BaseConstraint.name`.
|
2019-07-12 11:08:00 +00:00
|
|
|
|
|
|
|
``expressions``
|
|
|
|
---------------
|
|
|
|
|
|
|
|
.. attribute:: ExclusionConstraint.expressions
|
|
|
|
|
|
|
|
An iterable of 2-tuples. The first element is an expression or string. The
|
2020-05-06 04:35:26 +00:00
|
|
|
second element is an SQL operator represented as a string. To avoid typos, you
|
2019-07-12 11:08:00 +00:00
|
|
|
may use :class:`~django.contrib.postgres.fields.RangeOperators` which maps the
|
|
|
|
operators with strings. For example::
|
|
|
|
|
|
|
|
expressions = [
|
|
|
|
("timespan", RangeOperators.ADJACENT_TO),
|
|
|
|
(F("room"), RangeOperators.EQUAL),
|
|
|
|
]
|
|
|
|
|
|
|
|
.. admonition:: Restrictions on operators.
|
|
|
|
|
|
|
|
Only commutative operators can be used in exclusion constraints.
|
|
|
|
|
2021-12-08 20:28:08 +00:00
|
|
|
The :class:`OpClass() <django.contrib.postgres.indexes.OpClass>` expression can
|
|
|
|
be used to specify a custom `operator class`_ for the constraint expressions.
|
|
|
|
For example::
|
|
|
|
|
|
|
|
expressions = [
|
|
|
|
(OpClass("circle", name="circle_ops"), RangeOperators.OVERLAPS),
|
|
|
|
]
|
|
|
|
|
|
|
|
creates an exclusion constraint on ``circle`` using ``circle_ops``.
|
|
|
|
|
|
|
|
.. _operator class: https://www.postgresql.org/docs/current/indexes-opclass.html
|
|
|
|
|
2019-07-12 11:08:00 +00:00
|
|
|
``index_type``
|
|
|
|
--------------
|
|
|
|
|
|
|
|
.. attribute:: ExclusionConstraint.index_type
|
|
|
|
|
|
|
|
The index type of the constraint. Accepted values are ``GIST`` or ``SPGIST``.
|
|
|
|
Matching is case insensitive. If not provided, the default index type is
|
|
|
|
``GIST``.
|
|
|
|
|
|
|
|
``condition``
|
|
|
|
-------------
|
|
|
|
|
|
|
|
.. attribute:: ExclusionConstraint.condition
|
|
|
|
|
|
|
|
A :class:`~django.db.models.Q` object that specifies the condition to restrict
|
|
|
|
a constraint to a subset of rows. For example,
|
|
|
|
``condition=Q(cancelled=False)``.
|
|
|
|
|
|
|
|
These conditions have the same database restrictions as
|
|
|
|
:attr:`django.db.models.Index.condition`.
|
|
|
|
|
2020-04-12 10:43:16 +00:00
|
|
|
``deferrable``
|
|
|
|
--------------
|
|
|
|
|
|
|
|
.. attribute:: ExclusionConstraint.deferrable
|
|
|
|
|
|
|
|
Set this parameter to create a deferrable exclusion constraint. Accepted values
|
|
|
|
are ``Deferrable.DEFERRED`` or ``Deferrable.IMMEDIATE``. For example::
|
|
|
|
|
|
|
|
from django.contrib.postgres.constraints import ExclusionConstraint
|
|
|
|
from django.contrib.postgres.fields import RangeOperators
|
|
|
|
from django.db.models import Deferrable
|
|
|
|
|
|
|
|
|
|
|
|
ExclusionConstraint(
|
|
|
|
name="exclude_overlapping_deferred",
|
|
|
|
expressions=[
|
|
|
|
("timespan", RangeOperators.OVERLAPS),
|
|
|
|
],
|
|
|
|
deferrable=Deferrable.DEFERRED,
|
|
|
|
)
|
|
|
|
|
|
|
|
By default constraints are not deferred. A deferred constraint will not be
|
|
|
|
enforced until the end of the transaction. An immediate constraint will be
|
|
|
|
enforced immediately after every command.
|
|
|
|
|
|
|
|
.. warning::
|
|
|
|
|
|
|
|
Deferred exclusion constraints may lead to a `performance penalty
|
|
|
|
<https://www.postgresql.org/docs/current/sql-createtable.html#id-1.9.3.85.9.4>`_.
|
|
|
|
|
2020-06-11 19:05:38 +00:00
|
|
|
``include``
|
|
|
|
-----------
|
|
|
|
|
|
|
|
.. attribute:: ExclusionConstraint.include
|
|
|
|
|
|
|
|
A list or tuple of the names of the fields to be included in the covering
|
|
|
|
exclusion constraint as non-key columns. This allows index-only scans to be
|
|
|
|
used for queries that select only included fields
|
|
|
|
(:attr:`~ExclusionConstraint.include`) and filter only by indexed fields
|
|
|
|
(:attr:`~ExclusionConstraint.expressions`).
|
|
|
|
|
2022-05-19 07:26:48 +00:00
|
|
|
``include`` is supported for GiST indexes. PostgreSQL 14+ also supports
|
|
|
|
``include`` for SP-GiST indexes.
|
2021-05-28 22:56:23 +00:00
|
|
|
|
2023-02-14 20:06:45 +00:00
|
|
|
``violation_error_code``
|
|
|
|
------------------------
|
|
|
|
|
|
|
|
.. versionadded:: 5.0
|
|
|
|
|
|
|
|
.. attribute:: ExclusionConstraint.violation_error_code
|
|
|
|
|
|
|
|
The error code used when ``ValidationError`` is raised during
|
|
|
|
:ref:`model validation <validating-objects>`. Defaults to ``None``.
|
|
|
|
|
2022-01-31 15:04:13 +00:00
|
|
|
``violation_error_message``
|
|
|
|
---------------------------
|
|
|
|
|
|
|
|
The error message used when ``ValidationError`` is raised during
|
|
|
|
:ref:`model validation <validating-objects>`. Defaults to
|
|
|
|
:attr:`.BaseConstraint.violation_error_message`.
|
|
|
|
|
2019-07-12 11:08:00 +00:00
|
|
|
Examples
|
|
|
|
--------
|
|
|
|
|
|
|
|
The following example restricts overlapping reservations in the same room, not
|
|
|
|
taking canceled reservations into account::
|
|
|
|
|
|
|
|
from django.contrib.postgres.constraints import ExclusionConstraint
|
|
|
|
from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators
|
|
|
|
from django.db import models
|
|
|
|
from django.db.models import Q
|
|
|
|
|
2023-02-28 19:53:28 +00:00
|
|
|
|
2019-07-12 11:08:00 +00:00
|
|
|
class Room(models.Model):
|
|
|
|
number = models.IntegerField()
|
|
|
|
|
|
|
|
|
|
|
|
class Reservation(models.Model):
|
|
|
|
room = models.ForeignKey("Room", on_delete=models.CASCADE)
|
|
|
|
timespan = DateTimeRangeField()
|
|
|
|
cancelled = models.BooleanField(default=False)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
constraints = [
|
|
|
|
ExclusionConstraint(
|
|
|
|
name="exclude_overlapping_reservations",
|
|
|
|
expressions=[
|
|
|
|
("timespan", RangeOperators.OVERLAPS),
|
|
|
|
("room", RangeOperators.EQUAL),
|
|
|
|
],
|
|
|
|
condition=Q(cancelled=False),
|
|
|
|
),
|
|
|
|
]
|
|
|
|
|
|
|
|
In case your model defines a range using two fields, instead of the native
|
|
|
|
PostgreSQL range types, you should write an expression that uses the equivalent
|
|
|
|
function (e.g. ``TsTzRange()``), and use the delimiters for the field. Most
|
|
|
|
often, the delimiters will be ``'[)'``, meaning that the lower bound is
|
|
|
|
inclusive and the upper bound is exclusive. You may use the
|
|
|
|
:class:`~django.contrib.postgres.fields.RangeBoundary` that provides an
|
|
|
|
expression mapping for the `range boundaries <https://www.postgresql.org/docs/
|
|
|
|
current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. For example::
|
|
|
|
|
|
|
|
from django.contrib.postgres.constraints import ExclusionConstraint
|
|
|
|
from django.contrib.postgres.fields import (
|
|
|
|
DateTimeRangeField,
|
|
|
|
RangeBoundary,
|
|
|
|
RangeOperators,
|
|
|
|
)
|
|
|
|
from django.db import models
|
|
|
|
from django.db.models import Func, Q
|
|
|
|
|
|
|
|
|
|
|
|
class TsTzRange(Func):
|
|
|
|
function = "TSTZRANGE"
|
|
|
|
output_field = DateTimeRangeField()
|
|
|
|
|
|
|
|
|
|
|
|
class Reservation(models.Model):
|
|
|
|
room = models.ForeignKey("Room", on_delete=models.CASCADE)
|
|
|
|
start = models.DateTimeField()
|
|
|
|
end = models.DateTimeField()
|
|
|
|
cancelled = models.BooleanField(default=False)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
constraints = [
|
|
|
|
ExclusionConstraint(
|
|
|
|
name="exclude_overlapping_reservations",
|
2022-08-26 14:10:27 +00:00
|
|
|
expressions=[
|
2019-07-12 11:08:00 +00:00
|
|
|
(
|
|
|
|
TsTzRange("start", "end", RangeBoundary()),
|
|
|
|
RangeOperators.OVERLAPS,
|
|
|
|
),
|
|
|
|
("room", RangeOperators.EQUAL),
|
2022-08-26 14:10:27 +00:00
|
|
|
],
|
2019-07-12 11:08:00 +00:00
|
|
|
condition=Q(cancelled=False),
|
|
|
|
),
|
|
|
|
]
|