From f37df2ab9c07886a1ca92a7c84c3c7823379701b Mon Sep 17 00:00:00 2001 From: Bilal Tonga Date: Fri, 5 Jun 2026 16:20:27 +0200 Subject: [PATCH] Fix named parameter regex to allow non-identifier characters and add test --- src/crate/client/cursor.py | 2 +- tests/client/test_cursor.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/crate/client/cursor.py b/src/crate/client/cursor.py index e0c4bf85..f07d3197 100644 --- a/src/crate/client/cursor.py +++ b/src/crate/client/cursor.py @@ -26,7 +26,7 @@ from .converter import Converter, DataType from .exceptions import ProgrammingError -_NAMED_PARAM_RE = re.compile(r"%\((\w+)\)s") +_NAMED_PARAM_RE = re.compile(r"%\(([^)]+)\)s") def _convert_named_to_positional( diff --git a/tests/client/test_cursor.py b/tests/client/test_cursor.py index ab429ac8..dcb71774 100644 --- a/tests/client/test_cursor.py +++ b/tests/client/test_cursor.py @@ -565,6 +565,24 @@ def test_execute_with_named_params_missing(mocked_connection): mocked_connection.client.sql.assert_not_called() +def test_execute_with_named_params_non_identifier_keys(mocked_connection): + """ + Verify that %(name)s placeholders whose name contains characters outside + [a-zA-Z0-9_] are still converted to positional $N markers. + + """ + cursor = mocked_connection.cursor() + + cursor.execute( + "UPDATE characters SET data['x'] = %(data['x'])s WHERE name = %(name)s", + {"data['x']": 42, "name": "Berlin"}, + ) + sql, args, _ = mocked_connection.client.sql.call_args[0] + assert "%" not in sql + assert sql == "UPDATE characters SET data['x'] = $1 WHERE name = $2" + assert args == [42, "Berlin"] + + def test_cursor_close(mocked_connection): """ Verify that a cursor is not closed if not specifically closed.