mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Fixed #10070 -- Added support for pyformat style parameters on SQLite.
Co-authored-by: Nick Pope <nick@nickpope.me.uk>
This commit is contained in:
committed by
Mariusz Felisiak
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):
|
||||
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}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user