Commit 7a3b486c authored by Josh Smeaton's avatar Josh Smeaton Committed by Tim Graham
Browse files

[1.9.x] Fixed #25517 -- Made Concat function idempotent on SQLite.

Backport of 6c95b134 from master
parent 9039ff60
Loading
Loading
Loading
Loading
+9 −6
Original line number Diff line number Diff line
@@ -42,10 +42,10 @@ class ConcatPair(Func):
        super(ConcatPair, self).__init__(left, right, **extra)

    def as_sqlite(self, compiler, connection):
        self.arg_joiner = ' || '
        self.template = '%(expressions)s'
        self.coalesce()
        return super(ConcatPair, self).as_sql(compiler, connection)
        coalesced = self.coalesce()
        coalesced.arg_joiner = ' || '
        coalesced.template = '%(expressions)s'
        return super(ConcatPair, coalesced).as_sql(compiler, connection)

    def as_mysql(self, compiler, connection):
        # Use CONCAT_WS with an empty separator so that NULLs are ignored.
@@ -55,9 +55,12 @@ class ConcatPair(Func):

    def coalesce(self):
        # null on either side results in null for expression, wrap with coalesce
        c = self.copy()
        expressions = [
            Coalesce(expression, Value('')) for expression in self.get_source_expressions()]
        self.set_source_expressions(expressions)
            Coalesce(expression, Value('')) for expression in c.get_source_expressions()
        ]
        c.set_source_expressions(expressions)
        return c


class Concat(Func):
+2 −0
Original line number Diff line number Diff line
@@ -23,3 +23,5 @@ Bugfixes
  have their reverse relations disabled (:ticket:`25545`).

* Allowed filtering over a ``RawSQL`` annotation (:ticket:`25506`).

* Made the ``Concat`` database function idempotent on SQLite (:ticket:`25517`).
+15 −1
Original line number Diff line number Diff line
@@ -7,7 +7,8 @@ from django.db import connection
from django.db.models import CharField, TextField, Value as V
from django.db.models.expressions import RawSQL
from django.db.models.functions import (
    Coalesce, Concat, Greatest, Least, Length, Lower, Now, Substr, Upper,
    Coalesce, Concat, ConcatPair, Greatest, Least, Length, Lower, Now, Substr,
    Upper,
)
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.utils import six, timezone
@@ -353,6 +354,19 @@ class FunctionTests(TestCase):
        expected = article.title + ' - ' + article.text
        self.assertEqual(expected.upper(), article.title_text)

    @skipUnless(connection.vendor == 'sqlite', "sqlite specific implementation detail.")
    def test_concat_coalesce_idempotent(self):
        pair = ConcatPair(V('a'), V('b'))
        # Check nodes counts
        self.assertEqual(len(list(pair.flatten())), 3)
        self.assertEqual(len(list(pair.coalesce().flatten())), 7)  # + 2 Coalesce + 2 Value()
        self.assertEqual(len(list(pair.flatten())), 3)

    def test_concat_sql_generation_idempotency(self):
        qs = Article.objects.annotate(description=Concat('title', V(': '), 'summary'))
        # Multiple compilations should not alter the generated query.
        self.assertEqual(str(qs.query), str(qs.all().query))

    def test_lower(self):
        Author.objects.create(name='John Smith', alias='smithj')
        Author.objects.create(name='Rhonda')