Reputation: 27022
I have an extra
filter in Django with a where
clause, but the table name is dynamic.
filtered_queryset = queryset.extra(
where=[
f'({table_name}.modified_on, {table_name}.id) > (%s, %s)',
],
params=(after_ts, after_id),
)
How can I best avoid the f-string to make really sure it's not open to SQL injection?
I would like the SQL to have the tuple comparison, rather than multiple >
and >=
with AND
. From previous testing, it seemed more likely to use multi-column indexes.
(This is part of some custom pagination code, where the cursor is essentially a tuple of a datetime and id)
Upvotes: 0
Views: 419
Reputation: 27022
As in this answer, extra
can be avoided by using annotate
, and the table name is taken from the Queryset's model internally, which avoids having to worry about escaping any identifiers:
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')
(Apparently in Django 3.2+ alias
can be used instead of extra
, and the output_field
hack avoided)
Upvotes: 0
Reputation: 562631
First, validate your table_name
variable against a list of known table names.
Also, use double-quotes as identifier delimiters, which allows table_name
to be an SQL reserved keyword, or contain spaces or punctuation. These are all legal in SQL, as long as you use identifier delimiters.
f'("{table_name}".modified_on, "{table_name}".id) > (%s, %s)',
See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
However, if your table name contains a literal "
character, this could cause trouble, because the "
symbol would be interpreted as the terminating identifer delimiter. So it's up to you to filter your table_name
, replacing a literal "
with ""
.
Upvotes: 1
Reputation: 6858
Why don't you write it as a filter?
filtered_queryset = queryset.filter(
Q(modified_on__gt=after_ts) |
Q(Q(modified_on__gte=after_ts) & Q(id__gt=after_id))
)
PS: It's a bit unclear what your query is trying to do, I think it's this, but maybe you want to filter on something else.
Upvotes: 2