mirror of
https://github.com/django/django.git
synced 2025-11-07 07:15:35 +00:00
Support 'pyformat' style parameters in raw queries, Refs #10070
Add support for Oracle, fix an issue with the repr of RawQuerySet, add tests and documentations. Also added a 'supports_paramstyle_pyformat' database feature, True by default, False for SQLite. Thanks Donald Stufft for review of documentation.
This commit is contained in:
@@ -613,6 +613,11 @@ class BaseDatabaseFeatures(object):
|
||||
# when autocommit is disabled? http://bugs.python.org/issue8145#msg109965
|
||||
autocommits_when_autocommit_is_off = False
|
||||
|
||||
# Does the backend support 'pyformat' style ("... %(name)s ...", {'name': value})
|
||||
# parameter passing? Note this can be provided by the backend even if not
|
||||
# supported by the Python driver
|
||||
supports_paramstyle_pyformat = True
|
||||
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
|
||||
@@ -757,20 +757,37 @@ class FormatStylePlaceholderCursor(object):
|
||||
self.cursor.arraysize = 100
|
||||
|
||||
def _format_params(self, params):
|
||||
return tuple([OracleParam(p, self, True) for p in params])
|
||||
try:
|
||||
return dict((k,OracleParam(v, self, True)) for k,v in params.items())
|
||||
except AttributeError:
|
||||
return tuple([OracleParam(p, self, True) for p in params])
|
||||
|
||||
def _guess_input_sizes(self, params_list):
|
||||
sizes = [None] * len(params_list[0])
|
||||
for params in params_list:
|
||||
for i, value in enumerate(params):
|
||||
if value.input_size:
|
||||
sizes[i] = value.input_size
|
||||
self.setinputsizes(*sizes)
|
||||
# Try dict handling; if that fails, treat as sequence
|
||||
if hasattr(params_list[0], 'keys'):
|
||||
sizes = {}
|
||||
for params in params_list:
|
||||
for k, value in params.items():
|
||||
if value.input_size:
|
||||
sizes[k] = value.input_size
|
||||
self.setinputsizes(**sizes)
|
||||
else:
|
||||
# It's not a list of dicts; it's a list of sequences
|
||||
sizes = [None] * len(params_list[0])
|
||||
for params in params_list:
|
||||
for i, value in enumerate(params):
|
||||
if value.input_size:
|
||||
sizes[i] = value.input_size
|
||||
self.setinputsizes(*sizes)
|
||||
|
||||
def _param_generator(self, params):
|
||||
return [p.force_bytes for p in params]
|
||||
# Try dict handling; if that fails, treat as sequence
|
||||
if hasattr(params, 'items'):
|
||||
return dict((k, v.force_bytes) for k,v in params.items())
|
||||
else:
|
||||
return [p.force_bytes for p in params]
|
||||
|
||||
def execute(self, query, params=None):
|
||||
def _fix_for_params(self, query, params):
|
||||
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
|
||||
# it does want a trailing ';' but not a trailing '/'. However, these
|
||||
# characters must be included in the original query in case the query
|
||||
@@ -780,10 +797,18 @@ class FormatStylePlaceholderCursor(object):
|
||||
if params is None:
|
||||
params = []
|
||||
query = convert_unicode(query, self.charset)
|
||||
elif hasattr(params, 'keys'):
|
||||
# Handle params as dict
|
||||
args = dict((k, ":%s"%k) for k in params.keys())
|
||||
query = convert_unicode(query % args, self.charset)
|
||||
else:
|
||||
params = self._format_params(params)
|
||||
# Handle params as sequence
|
||||
args = [(':arg%d' % i) for i in range(len(params))]
|
||||
query = convert_unicode(query % tuple(args), self.charset)
|
||||
return query, self._format_params(params)
|
||||
|
||||
def execute(self, query, params=None):
|
||||
query, params = self._fix_for_params(query, params)
|
||||
self._guess_input_sizes([params])
|
||||
try:
|
||||
return self.cursor.execute(query, self._param_generator(params))
|
||||
@@ -794,22 +819,15 @@ class FormatStylePlaceholderCursor(object):
|
||||
raise
|
||||
|
||||
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):
|
||||
if not params:
|
||||
# No params given, nothing to do
|
||||
return None
|
||||
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
|
||||
# it does want a trailing ';' but not a trailing '/'. However, these
|
||||
# characters must be included in the original query in case the query
|
||||
# is being passed to SQL*Plus.
|
||||
if query.endswith(';') or query.endswith('/'):
|
||||
query = query[:-1]
|
||||
query = convert_unicode(query % tuple(args), self.charset)
|
||||
formatted = [self._format_params(i) for i in params]
|
||||
# uniform treatment for sequences and iterables
|
||||
params_iter = iter(params)
|
||||
query, firstparams = self._fix_for_params(query, next(params_iter))
|
||||
# we build a list of formatted params; as we're going to traverse it
|
||||
# more than once, we can't make it lazy by using a generator
|
||||
formatted = [firstparams]+[self._format_params(p) for p in params_iter]
|
||||
self._guess_input_sizes(formatted)
|
||||
try:
|
||||
return self.cursor.executemany(query,
|
||||
|
||||
@@ -101,6 +101,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
has_bulk_insert = True
|
||||
can_combine_inserts_with_and_without_auto_increment_pk = False
|
||||
autocommits_when_autocommit_is_off = True
|
||||
supports_paramstyle_pyformat = False
|
||||
|
||||
@cached_property
|
||||
def uses_savepoints(self):
|
||||
|
||||
@@ -1445,7 +1445,10 @@ class RawQuerySet(object):
|
||||
yield instance
|
||||
|
||||
def __repr__(self):
|
||||
return "<RawQuerySet: %r>" % (self.raw_query % tuple(self.params))
|
||||
text = self.raw_query
|
||||
if self.params:
|
||||
text = text % (self.params if hasattr(self.params, 'keys') else tuple(self.params))
|
||||
return "<RawQuerySet: %r>" % text
|
||||
|
||||
def __getitem__(self, k):
|
||||
return list(self)[k]
|
||||
|
||||
Reference in New Issue
Block a user