Skip to content

Commit 67b7310

Browse files
committed
fix: escape % in MySQL StrWrapper to prevent conflict with %s parameter placeholders
When LIKE filter functions (contains, startswith, endswith, etc.) are used in update() operations, the % wildcards in the LIKE pattern conflict with MySQL driver's %s parameter substitution, causing 'not enough arguments for format string' errors. Fix by doubling % to %% in StrWrapper.get_value_sql() so that after the MySQL driver's parameter formatting, the %% becomes the correct single %. Fixes #1225
1 parent 314273c commit 67b7310

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

tests/test_sql.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from tests.testmodels import CharPkModel, Drink, Event, IntFields
5+
from tests.testmodels import CharPkModel, Drink, Event, IntFields, Tournament
66
from tortoise import connections
77
from tortoise.backends.psycopg.client import PsycopgClient
88
from tortoise.expressions import F
@@ -287,3 +287,27 @@ def test_m2m_filter_two_relations_same_target_produces_aliased_joins(sql_context
287287
assert '"drink_topping"' in sql
288288
assert '"drink__flavors"' in sql
289289
assert '"drink__toppings"' in sql
290+
291+
292+
def test_update_with_like_filter(sql_context):
293+
"""LIKE wildcards (%) in update queries must not conflict with %s parameter placeholders."""
294+
db, dialect, is_psycopg = sql_context
295+
sql = Tournament.filter(name__contains="test").update(desc="updated").sql()
296+
if dialect == "mysql":
297+
expected = "UPDATE `tournament` SET `desc`=%s WHERE CAST(`name` AS CHAR) LIKE '%%test%%'"
298+
elif dialect == "postgres":
299+
if is_psycopg:
300+
expected = (
301+
'UPDATE "tournament" SET "desc"=%s'
302+
" WHERE CAST(\"name\" AS VARCHAR) LIKE '%test%' ESCAPE '\\'"
303+
)
304+
else:
305+
expected = (
306+
'UPDATE "tournament" SET "desc"=$1'
307+
" WHERE CAST(\"name\" AS VARCHAR) LIKE '%test%' ESCAPE '\\'"
308+
)
309+
else:
310+
expected = (
311+
'UPDATE "tournament" SET "desc"=? WHERE CAST("name" AS VARCHAR) LIKE ? ESCAPE \'\\\''
312+
)
313+
assert sql == expected

tortoise/backends/mysql/executor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class StrWrapper(ValueWrapper):
4646
def get_value_sql(self, ctx: SqlContext) -> str:
4747
quote_char = ctx.secondary_quote_char or ""
4848
value = self.value.replace(quote_char, quote_char * 2)
49+
# Escape % as %% so it survives MySQL driver's %s parameter substitution
50+
value = value.replace("%", "%%")
4951
return format_quotes(value, quote_char)
5052

5153

0 commit comments

Comments
 (0)