Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/submission/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,14 @@ def postgres_search(
# distinct fields to match order_by fields
inner_sql = self.stringify_queryset(queryset)

# stringify_queryset() returns SQL with every parameter already
# interpolated via cursor.mogrify(), so any '%' left in the string is a
# literal (e.g. from a search term like "50%" or a LIKE pattern). Article
# .objects.raw() defaults params to () rather than None, so it re-runs
# %-formatting on this SQL and chokes on those literal '%'. Double them so
# they survive that pass unchanged. (#5348)
Comment on lines +894 to +899
inner_sql = inner_sql.replace("%", "%%")

if "relevance" in sort:
# Relevance is not a field but an annotation
return Article.objects.raw(
Expand Down
36 changes: 36 additions & 0 deletions src/submission/tests/test_article_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,39 @@ def test_article_search_title(self):
result = [a for a in queryset]

self.assertEqual(result, [article])

@override_settings(ENABLE_FULL_TEXT_SEARCH=True)
def test_article_search_term_containing_percent(self):
"""A search term containing '%' must not raise (#5348).

stringify_queryset() mogrifies the term into the raw SQL, so a literal
'%' from the term reaches Article.objects.raw(), which re-runs
%-formatting on the string and previously raised. Evaluating the search
should now complete instead of erroring.
"""
from django.db import connection

if connection.vendor == "sqlite":
# The bug is in postgres_search()'s raw() query; sqlite falls back to
# mysql_search() and never hits the affected code path.
return
Comment on lines +198 to +201

models.Article.objects.create(
journal=self.journal_one,
title="Save 50% on warp-drive systems",
date_published=FROZEN_DATETIME_2020,
stage=models.STAGE_PUBLISHED,
)

# Mysql can't search at all without FULLTEXT indexes installed
call_command("generate_search_indexes")

search_filters = {"title": True}
try:
# Forces the RawQuerySet to execute; before the fix this raised
# TypeError ("not enough arguments for format string").
result = list(models.Article.objects.search("50%", search_filters))
except TypeError as exc:
self.fail(f"search with a '%' term raised (regression of #5348): {exc}")

self.assertIsInstance(result, list)
Loading