Refs #28643 -- Added NullIf database function.
Thanks Nick Pope, Mariusz Felisiak, and Tim Graham for reviews.
This commit is contained in:
parent
217f4456d8
commit
4b9d72210f
|
@ -1,4 +1,4 @@
|
||||||
from .comparison import Cast, Coalesce, Greatest, Least
|
from .comparison import Cast, Coalesce, Greatest, Least, NullIf
|
||||||
from .datetime import (
|
from .datetime import (
|
||||||
Extract, ExtractDay, ExtractHour, ExtractIsoYear, ExtractMinute,
|
Extract, ExtractDay, ExtractHour, ExtractIsoYear, ExtractMinute,
|
||||||
ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay,
|
ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay,
|
||||||
|
@ -20,7 +20,7 @@ from .window import (
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# comparison and conversion
|
# comparison and conversion
|
||||||
'Cast', 'Coalesce', 'Greatest', 'Least',
|
'Cast', 'Coalesce', 'Greatest', 'Least', 'NullIf',
|
||||||
# datetime
|
# datetime
|
||||||
'Extract', 'ExtractDay', 'ExtractHour', 'ExtractMinute', 'ExtractMonth',
|
'Extract', 'ExtractDay', 'ExtractHour', 'ExtractMinute', 'ExtractMonth',
|
||||||
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""Database functions that do comparisons or type conversions."""
|
"""Database functions that do comparisons or type conversions."""
|
||||||
from django.db.models.expressions import Func
|
from django.db.models.expressions import Func, Value
|
||||||
|
|
||||||
|
|
||||||
class Cast(Func):
|
class Cast(Func):
|
||||||
|
@ -103,3 +103,14 @@ class Least(Func):
|
||||||
def as_sqlite(self, compiler, connection, **extra_context):
|
def as_sqlite(self, compiler, connection, **extra_context):
|
||||||
"""Use the MIN function on SQLite."""
|
"""Use the MIN function on SQLite."""
|
||||||
return super().as_sqlite(compiler, connection, function='MIN', **extra_context)
|
return super().as_sqlite(compiler, connection, function='MIN', **extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class NullIf(Func):
|
||||||
|
function = 'NULLIF'
|
||||||
|
arity = 2
|
||||||
|
|
||||||
|
def as_oracle(self, compiler, connection, **extra_context):
|
||||||
|
expression1 = self.get_source_expressions()[0]
|
||||||
|
if isinstance(expression1, Value) and expression1.value is None:
|
||||||
|
raise ValueError('Oracle does not allow Value(None) for expression1.')
|
||||||
|
return super().as_sql(compiler, connection, **extra_context)
|
||||||
|
|
|
@ -899,6 +899,8 @@ occur when an Oracle datatype is used as a column name. In
|
||||||
particular, take care to avoid using the names ``date``,
|
particular, take care to avoid using the names ``date``,
|
||||||
``timestamp``, ``number`` or ``float`` as a field name.
|
``timestamp``, ``number`` or ``float`` as a field name.
|
||||||
|
|
||||||
|
.. _oracle-null-empty-strings:
|
||||||
|
|
||||||
NULL and empty strings
|
NULL and empty strings
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,25 @@ will result in a database error.
|
||||||
The PostgreSQL behavior can be emulated using ``Coalesce`` if you know
|
The PostgreSQL behavior can be emulated using ``Coalesce`` if you know
|
||||||
a sensible maximum value to provide as a default.
|
a sensible maximum value to provide as a default.
|
||||||
|
|
||||||
|
``NullIf``
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. class:: NullIf(expression1, expression2)
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
|
Accepts two expressions and returns ``None`` if they are equal, otherwise
|
||||||
|
returns ``expression1``.
|
||||||
|
|
||||||
|
.. admonition:: Caveats on Oracle
|
||||||
|
|
||||||
|
Due to an :ref:`Oracle convention<oracle-null-empty-strings>`, this
|
||||||
|
function returns the empty string instead of ``None`` when the expressions
|
||||||
|
are of type :class:`~django.db.models.CharField`.
|
||||||
|
|
||||||
|
Passing ``Value(None)`` to ``expression1`` is prohibited on Oracle since
|
||||||
|
Oracle doesn't accept ``NULL`` as the first argument.
|
||||||
|
|
||||||
.. _date-functions:
|
.. _date-functions:
|
||||||
|
|
||||||
Date functions
|
Date functions
|
||||||
|
|
|
@ -220,6 +220,9 @@ Models
|
||||||
|
|
||||||
* Added many :ref:`math database functions <math-functions>`.
|
* Added many :ref:`math database functions <math-functions>`.
|
||||||
|
|
||||||
|
* The new :class:`~django.db.models.functions.NullIf` database function
|
||||||
|
returns ``None`` if the two expressions are equal.
|
||||||
|
|
||||||
* Setting the new ``ignore_conflicts`` parameter of
|
* Setting the new ``ignore_conflicts`` parameter of
|
||||||
:meth:`.QuerySet.bulk_create` to ``True`` tells the database to ignore
|
:meth:`.QuerySet.bulk_create` to ``True`` tells the database to ignore
|
||||||
failure to insert rows that fail uniqueness constraints or other checks.
|
failure to insert rows that fail uniqueness constraints or other checks.
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
from unittest import skipUnless
|
||||||
|
|
||||||
|
from django.db import connection
|
||||||
|
from django.db.models import Value
|
||||||
|
from django.db.models.functions import NullIf
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from ..models import Author
|
||||||
|
|
||||||
|
|
||||||
|
class NullIfTests(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
Author.objects.create(name='John Smith', alias='smithj')
|
||||||
|
Author.objects.create(name='Rhonda', alias='Rhonda')
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
authors = Author.objects.annotate(nullif=NullIf('alias', 'name')).values_list('nullif')
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
authors, [
|
||||||
|
('smithj',),
|
||||||
|
('' if connection.features.interprets_empty_strings_as_nulls else None,)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_null_argument(self):
|
||||||
|
authors = Author.objects.annotate(nullif=NullIf('name', Value(None))).values_list('nullif')
|
||||||
|
self.assertSequenceEqual(authors, [('John Smith',), ('Rhonda',)])
|
||||||
|
|
||||||
|
def test_too_few_args(self):
|
||||||
|
msg = "'NullIf' takes exactly 2 arguments (1 given)"
|
||||||
|
with self.assertRaisesMessage(TypeError, msg):
|
||||||
|
NullIf('name')
|
||||||
|
|
||||||
|
@skipUnless(connection.vendor == 'oracle', 'Oracle specific test for NULL-literal')
|
||||||
|
def test_null_literal(self):
|
||||||
|
msg = 'Oracle does not allow Value(None) for expression1.'
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
list(Author.objects.annotate(nullif=NullIf(Value(None), 'name')).values_list('nullif'))
|
Loading…
Reference in New Issue