{"record":{"id":"1e0340e43a33975f","repo":"django/django","slug":"composite-exclusion-constraints-using-hash-indexes","errorCode":null,"errorMessage":"Composite exclusion constraints using Hash indexes are not supported.","messagePattern":"Composite exclusion constraints using Hash indexes are not supported\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"django/contrib/postgres/constraints.py","lineNumber":70,"sourceCode":"            raise ValueError(\"The expressions must be a list of 2-tuples.\")\n        if not isinstance(condition, (NoneType, Q)):\n            raise ValueError(\"ExclusionConstraint.condition must be a Q instance.\")\n        if not isinstance(deferrable, (NoneType, Deferrable)):\n            raise ValueError(\n                \"ExclusionConstraint.deferrable must be a Deferrable instance.\"\n            )\n        if not isinstance(include, (NoneType, list, tuple)):\n            raise ValueError(\"ExclusionConstraint.include must be a list or tuple.\")\n        if index_type and index_type.lower() == \"hash\":\n            if include:\n                raise ValueError(\n                    \"Covering exclusion constraints using Hash indexes are not \"\n                    \"supported.\"\n                )\n            if not expressions:\n                pass\n            elif len(expressions) > 1:\n                raise ValueError(\n                    \"Composite exclusion constraints using Hash indexes are not \"\n                    \"supported.\"\n                )\n            elif expressions[0][1] != RangeOperators.EQUAL:\n                raise ValueError(\n                    \"Exclusion constraints using Hash indexes only support the EQUAL \"\n                    \"operator.\"\n                )\n        self.expressions = expressions\n        self.index_type = index_type or \"GIST\"\n        self.condition = condition\n        self.deferrable = deferrable\n        self.include = tuple(include) if include else ()\n        super().__init__(\n            name=name,\n            violation_error_code=violation_error_code,\n            violation_error_message=violation_error_message,\n        )","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/django/django/blob/b5388a3a80cafcce2e34196d8e81cf5b48eb33bb/django/contrib/postgres/constraints.py#L52-L88","documentation":"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.","triggerScenarios":"ExclusionConstraint(name='x', expressions=[('a', '='), ('b', '=')], index_type='hash'). Any two-or-more element expressions list with hash triggers it.","commonSituations":"Building a no-overlap constraint across two range columns and trying hash for speed. Assuming hash works like btree for composites.","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."],"exampleFix":"// before\nExclusionConstraint(name='x', expressions=[('room_id', '='), ('period', '&&')], index_type='hash')\n// after\nExclusionConstraint(name='x', expressions=[('room_id', '='), ('period', '&&')], index_type='gist')","handlingStrategy":"validation","validationCode":"def validate_hash_composite(index_type, expressions):\n    if index_type and index_type.lower() == 'hash' and len(expressions) > 1:\n        raise ValueError('Hash exclusion constraints cannot be composite')\n    return expressions","typeGuard":"def hash_index_allows_composite(index_type: str, expressions: list) -> bool:\n    return not (index_type and index_type.lower() == 'hash' and len(expressions) > 1)","tryCatchPattern":"try:\n    ExclusionConstraint(..., index_type='hash', expressions=exprs)\nexcept ValueError as e:\n    if 'Composite exclusion constraints using Hash' in str(e):\n        index_type = 'gist'  # fall back to GIST for multi-column\n    else:\n        raise","preventionTips":["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."],"tags":["postgres","exclusion-constraint","hash-index","composite-index","validation"],"backgroundTag":null,"analyzedSha":"b5388a3a80cafcce2e34196d8e81cf5b48eb33bb","analyzedAt":"2026-08-10T17:37:52.993Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}