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 datetime
|
||||||
import decimal
|
import decimal
|
||||||
import warnings
|
import warnings
|
||||||
from itertools import chain
|
from collections.abc import Mapping
|
||||||
|
from itertools import chain, tee
|
||||||
from sqlite3 import dbapi2 as Database
|
from sqlite3 import dbapi2 as Database
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
@ -357,20 +358,40 @@ FORMAT_QMARK_REGEX = _lazy_re_compile(r"(?<!%)%s")
|
||||||
|
|
||||||
class SQLiteCursorWrapper(Database.Cursor):
|
class SQLiteCursorWrapper(Database.Cursor):
|
||||||
"""
|
"""
|
||||||
Django uses "format" style placeholders, but sqlite3 uses "qmark" style.
|
Django uses the "format" and "pyformat" styles, but Python's sqlite3 module
|
||||||
This fixes it -- but note that if you want to use a literal "%s" in a query,
|
supports neither of these styles.
|
||||||
you'll need to use "%%s".
|
|
||||||
|
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):
|
def execute(self, query, params=None):
|
||||||
if params is None:
|
if params is None:
|
||||||
return Database.Cursor.execute(self, query)
|
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)
|
return Database.Cursor.execute(self, query, params)
|
||||||
|
|
||||||
def executemany(self, query, param_list):
|
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)
|
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("%%", "%")
|
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
|
atomic_transactions = False
|
||||||
can_rollback_ddl = True
|
can_rollback_ddl = True
|
||||||
can_create_inline_fk = False
|
can_create_inline_fk = False
|
||||||
supports_paramstyle_pyformat = False
|
|
||||||
requires_literal_defaults = True
|
requires_literal_defaults = True
|
||||||
can_clone_databases = True
|
can_clone_databases = True
|
||||||
supports_temporal_subtraction = 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
|
SQLite does not support the ``SELECT ... FOR UPDATE`` syntax. Calling it will
|
||||||
have no effect.
|
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:
|
.. _sqlite-isolation:
|
||||||
|
|
||||||
Isolation when using ``QuerySet.iterator()``
|
Isolation when using ``QuerySet.iterator()``
|
||||||
|
|
Loading…
Reference in New Issue