summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Kelly <ian.g.kelly@gmail.com>2010-09-16 19:53:41 +0000
committerIan Kelly <ian.g.kelly@gmail.com>2010-09-16 19:53:41 +0000
commit320c46999c44efdc31be9cc80d3a2d1bf5186f18 (patch)
tree77c242a2be11e589678485456c60a7657e12f599
parent763bcf8472e61ba09cc739086bb181822501efa4 (diff)
Fixed #14244: Allow lists of more than 1000 items to be used with the 'in' lookup in Oracle, by breaking them up into groups of 1000 items and ORing them together. Thanks to rlynch for the report and initial patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@13859 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/db/backends/__init__.py7
-rw-r--r--django/db/backends/oracle/base.py3
-rw-r--r--django/db/models/sql/where.py21
-rw-r--r--tests/regressiontests/queries/models.py20
4 files changed, 49 insertions, 2 deletions
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index fe2c7c451b..75f40cd36f 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -233,6 +233,13 @@ class BaseDatabaseOperations(object):
"""
return "%s"
+ def max_in_list_size(self):
+ """
+ Returns the maximum number of items that can be passed in a single 'IN'
+ list condition, or None if the backend does not impose a limit.
+ """
+ return None
+
def max_name_length(self):
"""
Returns the maximum length of table and column names, or None if there
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 0cf26c406d..a175c3933a 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -178,6 +178,9 @@ WHEN (new.%(col_name)s IS NULL)
return "UPPER(%s)"
return "%s"
+ def max_in_list_size(self):
+ return 1000
+
def max_name_length(self):
return 30
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 72b2d4cc82..2427a528fb 100644
--- a/django/db/models/sql/where.py
+++ b/django/db/models/sql/where.py
@@ -2,6 +2,7 @@
Code to manage the creation and SQL rendering of 'where' constraints.
"""
import datetime
+from itertools import repeat
from django.utils import tree
from django.db.models.fields import Field
@@ -178,8 +179,24 @@ class WhereNode(tree.Node):
raise EmptyResultSet
if extra:
return ('%s IN %s' % (field_sql, extra), params)
- return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(params))),
- params)
+ max_in_list_size = connection.ops.max_in_list_size()
+ if max_in_list_size and len(params) > max_in_list_size:
+ # Break up the params list into an OR of manageable chunks.
+ in_clause_elements = ['(']
+ for offset in xrange(0, len(params), max_in_list_size):
+ if offset > 0:
+ in_clause_elements.append(' OR ')
+ in_clause_elements.append('%s IN (' % field_sql)
+ group_size = min(len(params) - offset, max_in_list_size)
+ param_group = ', '.join(repeat('%s', group_size))
+ in_clause_elements.append(param_group)
+ in_clause_elements.append(')')
+ in_clause_elements.append(')')
+ return ''.join(in_clause_elements), params
+ else:
+ return ('%s IN (%s)' % (field_sql,
+ ', '.join(repeat('%s', len(params)))),
+ params)
elif lookup_type in ('range', 'year'):
return ('%s BETWEEN %%s and %%s' % field_sql, params)
elif lookup_type in ('month', 'day', 'week_day'):
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index f3f0ad8857..f22cfb3af3 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -1339,3 +1339,23 @@ Using an empty generator expression as the rvalue for an "__in" lookup is legal
[]
"""
+
+# Sqlite 3 does not support passing in more than 1000 parameters except by
+# changing a parameter at compilation time.
+if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != "django.db.backends.sqlite3":
+ __test__["API_TESTS"] += """
+Bug #14244: Test that the "in" lookup works with lists of 1000 items or more.
+>>> Number.objects.all().delete()
+>>> numbers = range(2500)
+>>> for num in numbers:
+... _ = Number.objects.create(num=num)
+>>> Number.objects.filter(num__in=numbers[:1000]).count()
+1000
+>>> Number.objects.filter(num__in=numbers[:1001]).count()
+1001
+>>> Number.objects.filter(num__in=numbers[:2000]).count()
+2000
+>>> Number.objects.filter(num__in=numbers).count()
+2500
+
+"""