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 + +"""