summaryrefslogtreecommitdiff
path: root/django/db/backends/sqlite3/base.py
diff options
context:
space:
mode:
authorRyan Cheley <rcheley@gmail.com>2022-10-30 10:44:33 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-11-08 12:26:39 +0100
commit8e6ea1d153a852b83eaa4807301b143df1647a44 (patch)
treea9c9fbe24c3da01fbd0247f67ffdb36491cafea3 /django/db/backends/sqlite3/base.py
parent7b94847e384b1a8c05a7d4c8778958c0290bdf9a (diff)
Fixed #10070 -- Added support for pyformat style parameters on SQLite.
Co-authored-by: Nick Pope <nick@nickpope.me.uk>
Diffstat (limited to 'django/db/backends/sqlite3/base.py')
-rw-r--r--django/db/backends/sqlite3/base.py37
1 files changed, 29 insertions, 8 deletions
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index e541a6cbb0..840444e3c0 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -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):
- return FORMAT_QMARK_REGEX.sub("?", query).replace("%%", "%")
+ 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}