mirror of https://github.com/django/django.git
Proof-of-concept fix for #16731
Implemented only for SQLite and PostgreSQL, and only for startswith and istartswith lookups.
This commit is contained in:
parent
193cd097ca
commit
1016159f34
|
@ -677,6 +677,9 @@ class BaseDatabaseFeatures(object):
|
|||
# What kind of error does the backend throw when accessing closed cursor?
|
||||
closed_cursor_error_class = ProgrammingError
|
||||
|
||||
# Does 'a' LIKE 'A' match?
|
||||
has_case_insensitive_like = True
|
||||
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||
supports_combined_alters = True
|
||||
nulls_order_largest = True
|
||||
closed_cursor_error_class = InterfaceError
|
||||
has_case_insensitive_like = False
|
||||
|
||||
|
||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||
|
@ -83,6 +84,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
'iendswith': 'LIKE UPPER(%s)',
|
||||
}
|
||||
|
||||
pattern_ops = {
|
||||
'startswith': "LIKE %s || '%%%%'",
|
||||
'istartswith': "LIKE UPPER(%s) || '%%%%'",
|
||||
}
|
||||
|
||||
Database = Database
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -334,6 +334,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
'iendswith': "LIKE %s ESCAPE '\\'",
|
||||
}
|
||||
|
||||
pattern_ops = {
|
||||
'startswith': "LIKE %s || '%%%%'",
|
||||
'istartswith': "LIKE UPPER(%s) || '%%%%'",
|
||||
}
|
||||
|
||||
Database = Database
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -158,12 +158,30 @@ class In(BuiltinLookup):
|
|||
default_lookups['in'] = In
|
||||
|
||||
|
||||
class StartsWith(BuiltinLookup):
|
||||
class PatternLookup(BuiltinLookup):
|
||||
def get_rhs_op(self, connection, rhs):
|
||||
# Assume we are in startswith. We need to produce SQL like:
|
||||
# col LIKE %s, ['thevalue%']
|
||||
# For python values we can (and should) do that directly in Python,
|
||||
# but if the value is for example reference to other column, then
|
||||
# we need to add the % pattern match to the lookup by something like
|
||||
# col LIKE othercol || '%%'
|
||||
# So, for Python values we don't need any special pattern, but for
|
||||
# SQL reference values we need the correct pattern added.
|
||||
value = self.rhs
|
||||
if (hasattr(value, 'get_compiler') or hasattr(value, 'as_sql')
|
||||
or hasattr(value, '_as_sql')):
|
||||
return connection.pattern_ops[self.lookup_name] % rhs
|
||||
else:
|
||||
return super(PatternLookup, self).get_rhs_op(connection, rhs)
|
||||
|
||||
|
||||
class StartsWith(PatternLookup):
|
||||
lookup_name = 'startswith'
|
||||
default_lookups['startswith'] = StartsWith
|
||||
|
||||
|
||||
class IStartsWith(BuiltinLookup):
|
||||
class IStartsWith(PatternLookup):
|
||||
lookup_name = 'istartswith'
|
||||
default_lookups['istartswith'] = IStartsWith
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||
from django.core.exceptions import FieldError
|
||||
from django.db.models import F
|
||||
from django.db import transaction
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, skipIfDBFeature
|
||||
from django.utils import six
|
||||
|
||||
from .models import Company, Employee
|
||||
|
@ -231,6 +231,18 @@ class ExpressionsTests(TestCase):
|
|||
queryset = Employee.objects.filter(firstname__iexact=F('lastname'))
|
||||
self.assertQuerysetEqual(queryset, ["<Employee: Test test>"])
|
||||
|
||||
@skipIfDBFeature('has_case_insensitive_like')
|
||||
def test_ticket_16731_startswith_lookup(self):
|
||||
Employee.objects.create(firstname="John", lastname="Doe")
|
||||
e2 = Employee.objects.create(firstname="Jack", lastname="Jackson")
|
||||
e3 = Employee.objects.create(firstname="Jack", lastname="jackson")
|
||||
self.assertQuerysetEqual(
|
||||
Employee.objects.filter(lastname__startswith=F('firstname')),
|
||||
[e2], lambda x: x)
|
||||
self.assertQuerysetEqual(
|
||||
Employee.objects.filter(lastname__istartswith=F('firstname')).order_by('pk'),
|
||||
[e2, e3], lambda x: x)
|
||||
|
||||
def test_ticket_18375_join_reuse(self):
|
||||
# Test that reverse multijoin F() references and the lookup target
|
||||
# the same join. Pre #18375 the F() join was generated first, and the
|
||||
|
|
Loading…
Reference in New Issue