mirror of https://github.com/django/django.git
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 (
|
||||
Extract, ExtractDay, ExtractHour, ExtractIsoYear, ExtractMinute,
|
||||
ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay,
|
||||
|
@ -20,7 +20,7 @@ from .window import (
|
|||
|
||||
__all__ = [
|
||||
# comparison and conversion
|
||||
'Cast', 'Coalesce', 'Greatest', 'Least',
|
||||
'Cast', 'Coalesce', 'Greatest', 'Least', 'NullIf',
|
||||
# datetime
|
||||
'Extract', 'ExtractDay', 'ExtractHour', 'ExtractMinute', 'ExtractMonth',
|
||||
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"""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):
|
||||
|
@ -103,3 +103,14 @@ class Least(Func):
|
|||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
"""Use the MIN function on SQLite."""
|
||||
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``,
|
||||
``timestamp``, ``number`` or ``float`` as a field name.
|
||||
|
||||
.. _oracle-null-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
|
||||
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
|
||||
|
|
|
@ -220,6 +220,9 @@ Models
|
|||
|
||||
* 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
|
||||
:meth:`.QuerySet.bulk_create` to ``True`` tells the database to ignore
|
||||
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