django/django · error · ValueError
Composite exclusion constraints using Hash indexes are not s
Error message
Composite exclusion constraints using Hash indexes are not supported.
What it means
Raised when index_type='hash' and len(expressions) > 1. A PostgreSQL hash index can only index a single column with a single operator, so multi-column (composite) exclusion with hash is impossible. Django fails fast at model load rather than producing a confusing migration error.
Source
Thrown at django/contrib/postgres/constraints.py:70
raise ValueError("The expressions must be a list of 2-tuples.")
if not isinstance(condition, (NoneType, Q)):
raise ValueError("ExclusionConstraint.condition must be a Q instance.")
if not isinstance(deferrable, (NoneType, Deferrable)):
raise ValueError(
"ExclusionConstraint.deferrable must be a Deferrable instance."
)
if not isinstance(include, (NoneType, list, tuple)):
raise ValueError("ExclusionConstraint.include must be a list or tuple.")
if index_type and index_type.lower() == "hash":
if include:
raise ValueError(
"Covering exclusion constraints using Hash indexes are not "
"supported."
)
if not expressions:
pass
elif len(expressions) > 1:
raise ValueError(
"Composite exclusion constraints using Hash indexes are not "
"supported."
)
elif expressions[0][1] != RangeOperators.EQUAL:
raise ValueError(
"Exclusion constraints using Hash indexes only support the EQUAL "
"operator."
)
self.expressions = expressions
self.index_type = index_type or "GIST"
self.condition = condition
self.deferrable = deferrable
self.include = tuple(include) if include else ()
super().__init__(
name=name,
violation_error_code=violation_error_code,
violation_error_message=violation_error_message,
)View on GitHub (pinned to b5388a3a80)
Solutions
- Reduce expressions to a single 2-tuple, or switch index_type to 'gist' (the default, which supports composites).
- For multi-column overlap detection (e.g. booking conflicts), GiST with the && operator is the standard choice.
Example fix
// before
ExclusionConstraint(name='x', expressions=[('room_id', '='), ('period', '&&')], index_type='hash')
// after
ExclusionConstraint(name='x', expressions=[('room_id', '='), ('period', '&&')], index_type='gist') Defensive patterns
Strategy: validation
Validate before calling
def validate_hash_composite(index_type, expressions):
if index_type and index_type.lower() == 'hash' and len(expressions) > 1:
raise ValueError('Hash exclusion constraints cannot be composite')
return expressions Type guard
def hash_index_allows_composite(index_type: str, expressions: list) -> bool:
return not (index_type and index_type.lower() == 'hash' and len(expressions) > 1) Try / catch
try:
ExclusionConstraint(..., index_type='hash', expressions=exprs)
except ValueError as e:
if 'Composite exclusion constraints using Hash' in str(e):
index_type = 'gist' # fall back to GIST for multi-column
else:
raise Prevention
- Use GIST (the default) for any multi-column exclusion constraint.
- Reserve hash for single-column equality exclusion, which is rare.
- Add an assertion in migration tests that composite exclusion uses GIST.
When it happens
Trigger: ExclusionConstraint(name='x', expressions=[('a', '='), ('b', '=')], index_type='hash'). Any two-or-more element expressions list with hash triggers it.
Common situations: Building a no-overlap constraint across two range columns and trying hash for speed. Assuming hash works like btree for composites.
Related errors
- Covering exclusion constraints using Hash indexes are not su
- Exclusion constraints using Hash indexes only support the EQ
- ExclusionConstraint.condition must be a Q instance.
- ExclusionConstraint.deferrable must be a Deferrable instance
- ExclusionConstraint.include must be a list or tuple.
AI-assisted analysis of django/django@b5388a3a80 (2026-08-10).
Data as JSON: /api/errors/1e0340e43a33975f.
Report an issue: GitHub.