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 избегать)

Вернуться на верх