summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Smeaton <josh.smeaton@gmail.com>2015-10-14 10:07:42 +1100
committerTim Graham <timograham@gmail.com>2015-10-17 15:56:00 -0400
commit7a3b486ccd4aba0a9cf69475895a9b89ae2438b3 (patch)
tree660d03aef418f5be82f9a40b984a984178ee4d15
parent9039ff60e3f9848338d3fbc6957f9e25e9edc22b (diff)
[1.9.x] Fixed #25517 -- Made Concat function idempotent on SQLite.
Backport of 6c95b134e9b2d5641c123551c080305e90e6a89d from master
-rw-r--r--django/db/models/functions.py15
-rw-r--r--docs/releases/1.8.6.txt2
-rw-r--r--tests/db_functions/tests.py16
3 files changed, 26 insertions, 7 deletions
diff --git a/django/db/models/functions.py b/django/db/models/functions.py
index ac24aa7bdc..ffa3e36084 100644
--- a/django/db/models/functions.py
+++ b/django/db/models/functions.py
@@ -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):
diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt
index 44380cded5..178d714f01 100644
--- a/docs/releases/1.8.6.txt
+++ b/docs/releases/1.8.6.txt
@@ -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`).
diff --git a/tests/db_functions/tests.py b/tests/db_functions/tests.py
index 00aac82a7b..c18a4b25af 100644
--- a/tests/db_functions/tests.py
+++ b/tests/db_functions/tests.py
@@ -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')