1. ========================================
    
  2. PostgreSQL specific database constraints
    
  3. ========================================
    
  4. 
    
  5. .. module:: django.contrib.postgres.constraints
    
  6.    :synopsis: PostgreSQL specific database constraint
    
  7. 
    
  8. PostgreSQL supports additional data integrity constraints available from the
    
  9. ``django.contrib.postgres.constraints`` module. They are added in the model
    
  10. :attr:`Meta.constraints <django.db.models.Options.constraints>` option.
    
  11. 
    
  12. ``ExclusionConstraint``
    
  13. =======================
    
  14. 
    
  15. .. class:: ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, opclasses=(), violation_error_message=None)
    
  16. 
    
  17.     Creates an exclusion constraint in the database. Internally, PostgreSQL
    
  18.     implements exclusion constraints using indexes. The default index type is
    
  19.     `GiST <https://www.postgresql.org/docs/current/gist.html>`_. To use them,
    
  20.     you need to activate the `btree_gist extension
    
  21.     <https://www.postgresql.org/docs/current/btree-gist.html>`_ on PostgreSQL.
    
  22.     You can install it using the
    
  23.     :class:`~django.contrib.postgres.operations.BtreeGistExtension` migration
    
  24.     operation.
    
  25. 
    
  26.     If you attempt to insert a new row that conflicts with an existing row, an
    
  27.     :exc:`~django.db.IntegrityError` is raised. Similarly, when update
    
  28.     conflicts with an existing row.
    
  29. 
    
  30.     Exclusion constraints are checked during the :ref:`model validation
    
  31.     <validating-objects>`.
    
  32. 
    
  33.     .. versionchanged:: 4.1
    
  34. 
    
  35.         In older versions, exclusion constraints were not checked during model
    
  36.         validation.
    
  37. 
    
  38. ``name``
    
  39. --------
    
  40. 
    
  41. .. attribute:: ExclusionConstraint.name
    
  42. 
    
  43. See :attr:`.BaseConstraint.name`.
    
  44. 
    
  45. ``expressions``
    
  46. ---------------
    
  47. 
    
  48. .. attribute:: ExclusionConstraint.expressions
    
  49. 
    
  50. An iterable of 2-tuples. The first element is an expression or string. The
    
  51. second element is an SQL operator represented as a string. To avoid typos, you
    
  52. may use :class:`~django.contrib.postgres.fields.RangeOperators` which maps the
    
  53. operators with strings. For example::
    
  54. 
    
  55.     expressions=[
    
  56.         ('timespan', RangeOperators.ADJACENT_TO),
    
  57.         (F('room'), RangeOperators.EQUAL),
    
  58.     ]
    
  59. 
    
  60. .. admonition:: Restrictions on operators.
    
  61. 
    
  62.     Only commutative operators can be used in exclusion constraints.
    
  63. 
    
  64. The :class:`OpClass() <django.contrib.postgres.indexes.OpClass>` expression can
    
  65. be used to specify a custom `operator class`_ for the constraint expressions.
    
  66. For example::
    
  67. 
    
  68.     expressions=[
    
  69.         (OpClass('circle', name='circle_ops'), RangeOperators.OVERLAPS),
    
  70.     ]
    
  71. 
    
  72. creates an exclusion constraint on ``circle`` using ``circle_ops``.
    
  73. 
    
  74. .. versionchanged:: 4.1
    
  75. 
    
  76.     Support for the ``OpClass()`` expression was added.
    
  77. 
    
  78. .. _operator class: https://www.postgresql.org/docs/current/indexes-opclass.html
    
  79. 
    
  80. ``index_type``
    
  81. --------------
    
  82. 
    
  83. .. attribute:: ExclusionConstraint.index_type
    
  84. 
    
  85. The index type of the constraint. Accepted values are ``GIST`` or ``SPGIST``.
    
  86. Matching is case insensitive. If not provided, the default index type is
    
  87. ``GIST``.
    
  88. 
    
  89. ``condition``
    
  90. -------------
    
  91. 
    
  92. .. attribute:: ExclusionConstraint.condition
    
  93. 
    
  94. A :class:`~django.db.models.Q` object that specifies the condition to restrict
    
  95. a constraint to a subset of rows. For example,
    
  96. ``condition=Q(cancelled=False)``.
    
  97. 
    
  98. These conditions have the same database restrictions as
    
  99. :attr:`django.db.models.Index.condition`.
    
  100. 
    
  101. ``deferrable``
    
  102. --------------
    
  103. 
    
  104. .. attribute:: ExclusionConstraint.deferrable
    
  105. 
    
  106. Set this parameter to create a deferrable exclusion constraint. Accepted values
    
  107. are ``Deferrable.DEFERRED`` or ``Deferrable.IMMEDIATE``. For example::
    
  108. 
    
  109.     from django.contrib.postgres.constraints import ExclusionConstraint
    
  110.     from django.contrib.postgres.fields import RangeOperators
    
  111.     from django.db.models import Deferrable
    
  112. 
    
  113. 
    
  114.     ExclusionConstraint(
    
  115.         name='exclude_overlapping_deferred',
    
  116.         expressions=[
    
  117.             ('timespan', RangeOperators.OVERLAPS),
    
  118.         ],
    
  119.         deferrable=Deferrable.DEFERRED,
    
  120.     )
    
  121. 
    
  122. By default constraints are not deferred. A deferred constraint will not be
    
  123. enforced until the end of the transaction. An immediate constraint will be
    
  124. enforced immediately after every command.
    
  125. 
    
  126. .. warning::
    
  127. 
    
  128.     Deferred exclusion constraints may lead to a `performance penalty
    
  129.     <https://www.postgresql.org/docs/current/sql-createtable.html#id-1.9.3.85.9.4>`_.
    
  130. 
    
  131. ``include``
    
  132. -----------
    
  133. 
    
  134. .. attribute:: ExclusionConstraint.include
    
  135. 
    
  136. A list or tuple of the names of the fields to be included in the covering
    
  137. exclusion constraint as non-key columns. This allows index-only scans to be
    
  138. used for queries that select only included fields
    
  139. (:attr:`~ExclusionConstraint.include`) and filter only by indexed fields
    
  140. (:attr:`~ExclusionConstraint.expressions`).
    
  141. 
    
  142. ``include`` is supported for GiST indexes on PostgreSQL 12+ and SP-GiST
    
  143. indexes on PostgreSQL 14+.
    
  144. 
    
  145. .. versionchanged:: 4.1
    
  146. 
    
  147.     Support for covering exclusion constraints using SP-GiST indexes on
    
  148.     PostgreSQL 14+ was added.
    
  149. 
    
  150. ``opclasses``
    
  151. -------------
    
  152. 
    
  153. .. attribute:: ExclusionConstraint.opclasses
    
  154. 
    
  155. The names of the `PostgreSQL operator classes
    
  156. <https://www.postgresql.org/docs/current/indexes-opclass.html>`_ to use for
    
  157. this constraint. If you require a custom operator class, you must provide one
    
  158. for each expression in the constraint.
    
  159. 
    
  160. For example::
    
  161. 
    
  162.     ExclusionConstraint(
    
  163.         name='exclude_overlapping_opclasses',
    
  164.         expressions=[('circle', RangeOperators.OVERLAPS)],
    
  165.         opclasses=['circle_ops'],
    
  166.     )
    
  167. 
    
  168. creates an exclusion constraint on ``circle`` using ``circle_ops``.
    
  169. 
    
  170. .. deprecated:: 4.1
    
  171. 
    
  172.     The ``opclasses`` parameter is deprecated in favor of using
    
  173.     :class:`OpClass() <django.contrib.postgres.indexes.OpClass>` in
    
  174.     :attr:`~ExclusionConstraint.expressions`.
    
  175. 
    
  176. ``violation_error_message``
    
  177. ---------------------------
    
  178. 
    
  179. .. versionadded:: 4.1
    
  180. 
    
  181. The error message used when ``ValidationError`` is raised during
    
  182. :ref:`model validation <validating-objects>`. Defaults to
    
  183. :attr:`.BaseConstraint.violation_error_message`.
    
  184. 
    
  185. Examples
    
  186. --------
    
  187. 
    
  188. The following example restricts overlapping reservations in the same room, not
    
  189. taking canceled reservations into account::
    
  190. 
    
  191.     from django.contrib.postgres.constraints import ExclusionConstraint
    
  192.     from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators
    
  193.     from django.db import models
    
  194.     from django.db.models import Q
    
  195. 
    
  196.     class Room(models.Model):
    
  197.         number = models.IntegerField()
    
  198. 
    
  199. 
    
  200.     class Reservation(models.Model):
    
  201.         room = models.ForeignKey('Room', on_delete=models.CASCADE)
    
  202.         timespan = DateTimeRangeField()
    
  203.         cancelled = models.BooleanField(default=False)
    
  204. 
    
  205.         class Meta:
    
  206.             constraints = [
    
  207.                 ExclusionConstraint(
    
  208.                     name='exclude_overlapping_reservations',
    
  209.                     expressions=[
    
  210.                         ('timespan', RangeOperators.OVERLAPS),
    
  211.                         ('room', RangeOperators.EQUAL),
    
  212.                     ],
    
  213.                     condition=Q(cancelled=False),
    
  214.                 ),
    
  215.             ]
    
  216. 
    
  217. In case your model defines a range using two fields, instead of the native
    
  218. PostgreSQL range types, you should write an expression that uses the equivalent
    
  219. function (e.g. ``TsTzRange()``), and use the delimiters for the field. Most
    
  220. often, the delimiters will be ``'[)'``, meaning that the lower bound is
    
  221. inclusive and the upper bound is exclusive. You may use the
    
  222. :class:`~django.contrib.postgres.fields.RangeBoundary` that provides an
    
  223. expression mapping for the `range boundaries <https://www.postgresql.org/docs/
    
  224. current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. For example::
    
  225. 
    
  226.     from django.contrib.postgres.constraints import ExclusionConstraint
    
  227.     from django.contrib.postgres.fields import (
    
  228.         DateTimeRangeField,
    
  229.         RangeBoundary,
    
  230.         RangeOperators,
    
  231.     )
    
  232.     from django.db import models
    
  233.     from django.db.models import Func, Q
    
  234. 
    
  235. 
    
  236.     class TsTzRange(Func):
    
  237.         function = 'TSTZRANGE'
    
  238.         output_field = DateTimeRangeField()
    
  239. 
    
  240. 
    
  241.     class Reservation(models.Model):
    
  242.         room = models.ForeignKey('Room', on_delete=models.CASCADE)
    
  243.         start = models.DateTimeField()
    
  244.         end = models.DateTimeField()
    
  245.         cancelled = models.BooleanField(default=False)
    
  246. 
    
  247.         class Meta:
    
  248.             constraints = [
    
  249.                 ExclusionConstraint(
    
  250.                     name='exclude_overlapping_reservations',
    
  251.                     expressions=(
    
  252.                         (TsTzRange('start', 'end', RangeBoundary()), RangeOperators.OVERLAPS),
    
  253.                         ('room', RangeOperators.EQUAL),
    
  254.                     ),
    
  255.                     condition=Q(cancelled=False),
    
  256.                 ),
    
  257.             ]