Loading django/db/models/functions.py +9 −6 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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): Loading docs/releases/1.8.6.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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`). tests/db_functions/tests.py +15 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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') Loading Loading
django/db/models/functions.py +9 −6 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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): Loading
docs/releases/1.8.6.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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`).
tests/db_functions/tests.py +15 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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') Loading