summaryrefslogtreecommitdiff
path: root/django/db/backends
diff options
context:
space:
mode:
Diffstat (limited to 'django/db/backends')
-rw-r--r--django/db/backends/base/features.py5
-rw-r--r--django/db/backends/base/schema.py35
-rw-r--r--django/db/backends/postgresql/features.py7
3 files changed, 43 insertions, 4 deletions
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index 11dd079110..79abad82cf 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -27,6 +27,11 @@ class BaseDatabaseFeatures:
# Does the backend allow inserting duplicate rows when a unique_together
# constraint exists and some fields are nullable but not all of them?
supports_partially_nullable_unique_constraints = True
+
+ # Does the backend supports specifying whether NULL values should be
+ # considered distinct in unique constraints?
+ supports_nulls_distinct_unique_constraints = False
+
# Does the backend support initially deferrable unique constraints?
supports_deferrable_unique_constraints = False
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index 9329ee0971..5fe26d068f 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -129,7 +129,7 @@ class BaseDatabaseSchemaEditor:
)
sql_create_unique_index = (
"CREATE UNIQUE INDEX %(name)s ON %(table)s "
- "(%(columns)s)%(include)s%(condition)s"
+ "(%(columns)s)%(include)s%(condition)s%(nulls_distinct)s"
)
sql_rename_index = "ALTER INDEX %(old_name)s RENAME TO %(new_name)s"
sql_delete_index = "DROP INDEX %(name)s"
@@ -1675,12 +1675,20 @@ class BaseDatabaseSchemaEditor:
if deferrable == Deferrable.IMMEDIATE:
return " DEFERRABLE INITIALLY IMMEDIATE"
+ def _unique_index_nulls_distinct_sql(self, nulls_distinct):
+ if nulls_distinct is False:
+ return " NULLS NOT DISTINCT"
+ elif nulls_distinct is True:
+ return " NULLS DISTINCT"
+ return ""
+
def _unique_supported(
self,
condition=None,
deferrable=None,
include=None,
expressions=None,
+ nulls_distinct=None,
):
return (
(not condition or self.connection.features.supports_partial_indexes)
@@ -1692,6 +1700,10 @@ class BaseDatabaseSchemaEditor:
and (
not expressions or self.connection.features.supports_expression_indexes
)
+ and (
+ nulls_distinct is None
+ or self.connection.features.supports_nulls_distinct_unique_constraints
+ )
)
def _unique_sql(
@@ -1704,17 +1716,26 @@ class BaseDatabaseSchemaEditor:
include=None,
opclasses=None,
expressions=None,
+ nulls_distinct=None,
):
if not self._unique_supported(
condition=condition,
deferrable=deferrable,
include=include,
expressions=expressions,
+ nulls_distinct=nulls_distinct,
):
return None
- if condition or include or opclasses or expressions:
- # Databases support conditional, covering, and functional unique
- # constraints via a unique index.
+
+ if (
+ condition
+ or include
+ or opclasses
+ or expressions
+ or nulls_distinct is not None
+ ):
+ # Databases support conditional, covering, functional unique,
+ # and nulls distinct constraints via a unique index.
sql = self._create_unique_sql(
model,
fields,
@@ -1723,6 +1744,7 @@ class BaseDatabaseSchemaEditor:
include=include,
opclasses=opclasses,
expressions=expressions,
+ nulls_distinct=nulls_distinct,
)
if sql:
self.deferred_sql.append(sql)
@@ -1746,12 +1768,14 @@ class BaseDatabaseSchemaEditor:
include=None,
opclasses=None,
expressions=None,
+ nulls_distinct=None,
):
if not self._unique_supported(
condition=condition,
deferrable=deferrable,
include=include,
expressions=expressions,
+ nulls_distinct=nulls_distinct,
):
return None
@@ -1782,6 +1806,7 @@ class BaseDatabaseSchemaEditor:
condition=self._index_condition_sql(condition),
deferrable=self._deferrable_constraint_sql(deferrable),
include=self._index_include_sql(model, include),
+ nulls_distinct=self._unique_index_nulls_distinct_sql(nulls_distinct),
)
def _unique_constraint_name(self, table, columns, quote=True):
@@ -1804,12 +1829,14 @@ class BaseDatabaseSchemaEditor:
include=None,
opclasses=None,
expressions=None,
+ nulls_distinct=None,
):
if not self._unique_supported(
condition=condition,
deferrable=deferrable,
include=include,
expressions=expressions,
+ nulls_distinct=nulls_distinct,
):
return None
if condition or include or opclasses or expressions:
diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py
index 29b6a4f6c5..12dbc71743 100644
--- a/django/db/backends/postgresql/features.py
+++ b/django/db/backends/postgresql/features.py
@@ -132,6 +132,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
def is_postgresql_14(self):
return self.connection.pg_version >= 140000
+ @cached_property
+ def is_postgresql_15(self):
+ return self.connection.pg_version >= 150000
+
has_bit_xor = property(operator.attrgetter("is_postgresql_14"))
supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14"))
supports_unlimited_charfield = True
+ supports_nulls_distinct_unique_constraints = property(
+ operator.attrgetter("is_postgresql_15")
+ )