diff options
| author | Ryan Cheley <rcheley@gmail.com> | 2022-10-30 10:44:33 -0700 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-11-08 12:26:39 +0100 |
| commit | 8e6ea1d153a852b83eaa4807301b143df1647a44 (patch) | |
| tree | a9c9fbe24c3da01fbd0247f67ffdb36491cafea3 /django/db/backends/sqlite3 | |
| parent | 7b94847e384b1a8c05a7d4c8778958c0290bdf9a (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')
| -rw-r--r-- | django/db/backends/sqlite3/base.py | 37 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/features.py | 1 |
2 files changed, 29 insertions, 9 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} diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py index 9f6de827dd..1f7c6c012f 100644 --- a/django/db/backends/sqlite3/features.py +++ b/django/db/backends/sqlite3/features.py @@ -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 |
