Django extra + where: как экранировать идентификаторы
У меня есть фильтр extra
в Django с предложением where
, но имя таблицы является динамическим.
filtered_queryset = queryset.extra(
where=[
f'({table_name}.modified_on, {table_name}.id) > (%s, %s)',
],
params=(after_ts, after_id),
)
Как я могу лучше всего избежать f-строки, чтобы быть действительно уверенным, что она не открыта для SQL-инъекций?
Почему бы вам не написать это как фильтр?
filtered_queryset = queryset.filter(
Q(modified_on__gt=after_ts) |
Q(Q(modified_on__gte=after_ts) & Q(id__gt=after_id))
)
PS: Немного непонятно, что пытается сделать ваш запрос, я думаю, что это вот это, но, возможно, вы хотите отфильтровать по чему-то другому.
Сначала проверьте вашу переменную table_name
на соответствие списку известных имен таблиц.
Кроме того, используйте двойные кавычки в качестве разделителей идентификаторов, что позволяет table_name
быть зарезервированным ключевым словом SQL, содержать пробелы или знаки препинания. Все это законно в SQL, если вы используете разделители идентификаторов.
f'("{table_name}".modified_on, "{table_name}".id) > (%s, %s)',
Смотрите https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
Однако, если имя вашей таблицы содержит литеральный символ "
, это может вызвать проблемы, поскольку символ "
будет интерпретирован как завершающий разделитель идентификатора. Поэтому вам придется фильтровать table_name
, заменяя литерал "
на ""
.
Как и в этом ответе, extra
можно избежать, используя annotate
, а имя таблицы берется из внутренней модели Queryset, что позволяет не беспокоиться об экранировании каких-либо идентификаторов:
from django.db.models import F, Func, TextField
col_a_col_b = Func(F('col_a'), F('col_b'), function='ROW', output_type=TextField())
col_a_col_b_from = Func(col_a_value, col_b_value, function='ROW')
filtered_queryset = queryset
.annotate(col_a_col_b=col_a_col_b)
.filter(col_a_col_b__gt=col_a_col_b_from)
.order_by('col_a', 'col_b')
(По всей видимости, в Django 3.2+ alias
можно использовать вместо extra
, а хак output_field
избегать)