Commit d0974170 authored by Shai Berger's avatar Shai Berger
Browse files

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.
parent 7c0b72a8
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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

+42 −24
Original line number Diff line number Diff line
@@ -757,9 +757,22 @@ class FormatStylePlaceholderCursor(object):
        self.cursor.arraysize = 100

    def _format_params(self, 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):
        # 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):
@@ -768,9 +781,13 @@ class FormatStylePlaceholderCursor(object):
            self.setinputsizes(*sizes)        

    def _param_generator(self, 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,
+1 −0
Original line number Diff line number Diff line
@@ -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):
+4 −1
Original line number Diff line number Diff line
@@ -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]
+8 −0
Original line number Diff line number Diff line
@@ -623,6 +623,14 @@ If you're getting this error, you can solve it by:
SQLite does not support the ``SELECT ... FOR UPDATE`` syntax. Calling it will
have no effect.

"pyformat" parameter style in raw queries not supported
-------------------------------------------------------

For most backends, raw queries (``Manager.raw()`` or ``cursor.execute()``)
can use the "pyformat" parameter style, where placeholders in the query
are given as ``'%(name)s'`` and the parameters are passed as a dictionary
rather than a list. SQLite does not support this.

.. _sqlite-connection-queries:

Parameters not quoted in ``connection.queries``
Loading