mirror of https://github.com/django/django.git
Fixed #10070 -- Added support for pyformat style parameters on SQLite.
Co-authored-by: Nick Pope <nick@nickpope.me.uk>
This commit is contained in:
parent
7b94847e38
commit
8e6ea1d153
|
@ -4,7 +4,8 @@ SQLite backend for the sqlite3 module in the standard library.
|
|||
import datetime
|
||||
import decimal
|
||||
import warnings
|
||||
from itertools import chain
|
||||
from collections.abc import Mapping
|
||||
from itertools import chain, tee
|
||||
from sqlite3 import dbapi2 as Database
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
@ -357,20 +358,40 @@ FORMAT_QMARK_REGEX = _lazy_re_compile(r"(?<!%)%s")
|
|||
|
||||
class SQLiteCursorWrapper(Database.Cursor):
|
||||
"""
|
||||
Django uses "format" style placeholders, but sqlite3 uses "qmark" style.
|
||||
This fixes it -- but note that if you want to use a literal "%s" in a query,
|
||||
you'll need to use "%%s".
|
||||
Django uses the "format" and "pyformat" styles, but Python's sqlite3 module
|
||||
supports neither of these styles.
|
||||
|
||||
This wrapper performs the following conversions:
|
||||
|
||||
- "format" style to "qmark" style
|
||||
- "pyformat" style to "named" style
|
||||
|
||||
In both cases, if you want to use a literal "%s", you'll need to use "%%s".
|
||||
"""
|
||||
|
||||
def execute(self, query, params=None):
|
||||
if params is None:
|
||||
return Database.Cursor.execute(self, query)
|
||||
query = self.convert_query(query)
|
||||
# Extract names if params is a mapping, i.e. "pyformat" style is used.
|
||||
param_names = list(params) if isinstance(params, Mapping) else None
|
||||
query = self.convert_query(query, param_names=param_names)
|
||||
return Database.Cursor.execute(self, query, params)
|
||||
|
||||
def executemany(self, query, param_list):
|
||||
query = self.convert_query(query)
|
||||
# Extract names if params is a mapping, i.e. "pyformat" style is used.
|
||||
# Peek carefully as a generator can be passed instead of a list/tuple.
|
||||
peekable, param_list = tee(iter(param_list))
|
||||
if (params := next(peekable, None)) and isinstance(params, Mapping):
|
||||
param_names = list(params)
|
||||
else:
|
||||
param_names = None
|
||||
query = self.convert_query(query, param_names=param_names)
|
||||
return Database.Cursor.executemany(self, query, param_list)
|
||||
|
||||
def convert_query(self, query):
|
||||
def convert_query(self, query, *, param_names=None):
|
||||
if param_names is None:
|
||||
# Convert from "format" style to "qmark" style.
|
||||
return FORMAT_QMARK_REGEX.sub("?", query).replace("%%", "%")
|
||||
else:
|
||||
# Convert from "pyformat" style to "named" style.
|
||||
return query % {name: f":{name}" for name in param_names}
|
||||
|
|
|
@ -18,7 +18,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||
atomic_transactions = False
|
||||
can_rollback_ddl = True
|
||||
can_create_inline_fk = False
|
||||
supports_paramstyle_pyformat = False
|
||||
requires_literal_defaults = True
|
||||
can_clone_databases = True
|
||||
supports_temporal_subtraction = True
|
||||
|
|
|
@ -819,14 +819,6 @@ 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-isolation:
|
||||
|
||||
Isolation when using ``QuerySet.iterator()``
|
||||
|
|
Loading…
Reference in New Issue