123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- ========================================
- 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``
- =======================
- .. class:: ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, violation_error_code=None, violation_error_message=None)
- 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.
- Exclusion constraints are checked during the :ref:`model validation
- <validating-objects>`.
- ``name``
- --------
- .. attribute:: ExclusionConstraint.name
- See :attr:`.BaseConstraint.name`.
- ``expressions``
- ---------------
- .. attribute:: ExclusionConstraint.expressions
- An iterable of 2-tuples. The first element is an expression or string. The
- second element is an SQL operator represented as a string. To avoid typos, you
- 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.
- 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
- ``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`.
- ``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>`_.
- ``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`).
- ``include`` is supported for GiST indexes. PostgreSQL 14+ also supports
- ``include`` for SP-GiST indexes.
- ``violation_error_code``
- ------------------------
- .. attribute:: ExclusionConstraint.violation_error_code
- The error code used when ``ValidationError`` is raised during
- :ref:`model validation <validating-objects>`. Defaults to ``None``.
- ``violation_error_message``
- ---------------------------
- The error message used when ``ValidationError`` is raised during
- :ref:`model validation <validating-objects>`. Defaults to
- :attr:`.BaseConstraint.violation_error_message`.
- 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
- 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",
- expressions=[
- (
- TsTzRange("start", "end", RangeBoundary()),
- RangeOperators.OVERLAPS,
- ),
- ("room", RangeOperators.EQUAL),
- ],
- condition=Q(cancelled=False),
- ),
- ]
|