diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 105730c69c1..f1ee987ba8d 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -670,6 +670,9 @@ class FormatStylePlaceholderCursor(object): raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] def executemany(self, query, params=None): + # cx_Oracle doesn't support iterators, convert them to lists + if params is not None and not isinstance(params, (list, tuple)): + params = list(params) try: args = [(':arg%d' % i) for i in range(len(params[0]))] except (IndexError, TypeError): diff --git a/django/db/backends/util.py b/django/db/backends/util.py index 3ba1cb00cd8..32e0f4f7023 100644 --- a/django/db/backends/util.py +++ b/django/db/backends/util.py @@ -47,7 +47,7 @@ class CursorDebugWrapper(CursorWrapper): 'time': "%.3f" % duration, }) logger.debug('(%.3f) %s; args=%s' % (duration, sql, params), - extra={'duration':duration, 'sql':sql, 'params':params} + extra={'duration': duration, 'sql': sql, 'params': params} ) def executemany(self, sql, param_list): @@ -58,12 +58,16 @@ class CursorDebugWrapper(CursorWrapper): finally: stop = time() duration = stop - start + try: + times = len(param_list) + except TypeError: # param_list could be an iterator + times = '?' self.db.queries.append({ - 'sql': '%s times: %s' % (len(param_list), sql), + 'sql': '%s times: %s' % (times, sql), 'time': "%.3f" % duration, }) logger.debug('(%.3f) %s; args=%s' % (duration, sql, param_list), - extra={'duration':duration, 'sql':sql, 'params':param_list} + extra={'duration': duration, 'sql': sql, 'params': param_list} ) diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 436da8c2d03..cfb662ee804 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -14,6 +14,7 @@ from django.db.backends.signals import connection_created from django.db.backends.postgresql_psycopg2 import version as pg_version from django.db.utils import ConnectionHandler, DatabaseError, load_backend from django.test import TestCase, skipUnlessDBFeature, TransactionTestCase +from django.test.utils import override_settings from django.utils import unittest from . import models @@ -308,23 +309,43 @@ class EscapingChecks(TestCase): class BackendTestCase(TestCase): + + def create_squares_with_executemany(self, args): + cursor = connection.cursor() + opts = models.Square._meta + tbl = connection.introspection.table_name_converter(opts.db_table) + f1 = connection.ops.quote_name(opts.get_field('root').column) + f2 = connection.ops.quote_name(opts.get_field('square').column) + query = 'INSERT INTO %s (%s, %s) VALUES (%%s, %%s)' % (tbl, f1, f2) + cursor.executemany(query, args) + def test_cursor_executemany(self): #4896: Test cursor.executemany - cursor = connection.cursor() - qn = connection.ops.quote_name - opts = models.Square._meta - f1, f2 = opts.get_field('root'), opts.get_field('square') - query = ('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)' - % (connection.introspection.table_name_converter(opts.db_table), qn(f1.column), qn(f2.column))) - cursor.executemany(query, [(i, i**2) for i in range(-5, 6)]) + args = [(i, i**2) for i in range(-5, 6)] + self.create_squares_with_executemany(args) self.assertEqual(models.Square.objects.count(), 11) for i in range(-5, 6): square = models.Square.objects.get(root=i) self.assertEqual(square.square, i**2) + def test_cursor_executemany_with_empty_params_list(self): #4765: executemany with params=[] does nothing - cursor.executemany(query, []) - self.assertEqual(models.Square.objects.count(), 11) + args = [] + self.create_squares_with_executemany(args) + self.assertEqual(models.Square.objects.count(), 0) + + def test_cursor_executemany_with_iterator(self): + #10320: executemany accepts iterators + args = iter((i, i**2) for i in range(-3, 2)) + self.create_squares_with_executemany(args) + self.assertEqual(models.Square.objects.count(), 5) + + args = iter((i, i**2) for i in range(3, 7)) + with override_settings(DEBUG=True): + # same test for DebugCursorWrapper + self.create_squares_with_executemany(args) + self.assertEqual(models.Square.objects.count(), 9) + def test_unicode_fetches(self): #6254: fetchone, fetchmany, fetchall return strings as unicode objects