]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: fix handling of queries with %% in ClientCursor
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 3 Oct 2022 23:28:07 +0000 (00:28 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Tue, 4 Oct 2022 16:23:10 +0000 (17:23 +0100)
Close #399.

docs/news.rst
psycopg/psycopg/_queries.py
tests/test_client_cursor.py
tests/test_client_cursor_async.py

index 430455d3924c8c95ab45c248d737332ca5b7481d..63a020f414579ec6b6dd98c1c5fe2979d148908a 100644 (file)
@@ -17,6 +17,8 @@ Psycopg 3.1.3 (unreleased)
   prematurely (:ticket:`#382`).
 - Fix regression introduced in 3.1 with different named tuples mangling rules
   for non-ascii attribute names (:ticket:`#386`).
+- Fix handling of queries with escaped percent signs (``%%``) in `ClientCursor`
+  (:ticket:`#399`).
 
 
 Current release
index f2b916d7ae3c56d9b41d2b4702fa5c38a65b7c85..2a7554c30cc2da6c36c4df7b4af264a631156796 100644 (file)
@@ -202,7 +202,7 @@ def _query2pg_client(
     """
     Convert Python query and params into a template to perform client-side binding
     """
-    parts = _split_query(query, encoding)
+    parts = _split_query(query, encoding, collapse_double_percent=False)
     order: Optional[List[str]] = None
     chunks: List[bytes] = []
 
@@ -294,7 +294,9 @@ _re_placeholder = re.compile(
 )
 
 
-def _split_query(query: bytes, encoding: str = "ascii") -> List[QueryPart]:
+def _split_query(
+    query: bytes, encoding: str = "ascii", collapse_double_percent: bool = True
+) -> List[QueryPart]:
     parts: List[Tuple[bytes, Optional[Match[bytes]]]] = []
     cur = 0
 
@@ -323,9 +325,11 @@ def _split_query(query: bytes, encoding: str = "ascii") -> List[QueryPart]:
 
         ph = m.group(0)
         if ph == b"%%":
-            # unescape '%%' to '%' and merge the parts
+            # unescape '%%' to '%' if necessary, then merge the parts
+            if collapse_double_percent:
+                ph = b"%"
             pre1, m1 = parts[i + 1]
-            parts[i + 1] = (pre + b"%" + pre1, m1)
+            parts[i + 1] = (pre + ph + pre1, m1)
             del parts[i]
             continue
 
index 20900c4d9eea35a7216838aa88c92f98f0fb021d..21fea8c4ffd61f6dfe58aa6a0ffa7169bcd996d8 100644 (file)
@@ -816,6 +816,10 @@ def test_leak(conn_cls, dsn, faker, fetch, row_factory):
         ("select 'hello'", (), "select 'hello'"),
         ("select %s, %s", ([1, dt.date(2020, 1, 1)],), "select 1, '2020-01-01'::date"),
         ("select %(foo)s, %(foo)s", ({"foo": "x"},), "select 'x', 'x'"),
+        ("select %%", (), "select %%"),
+        ("select %%, %s", (["a"],), "select %, 'a'"),
+        ("select %%, %(foo)s", ({"foo": "x"},), "select %, 'x'"),
+        ("select %%s, %(foo)s", ({"foo": "x"},), "select %s, 'x'"),
     ],
 )
 def test_mogrify(conn, query, params, want):
index d3a074f229ebb51817e703237987c3c2d110ea12..63e9c3cc9acbe627e34b8ade05ee52504d51c47b 100644 (file)
@@ -688,6 +688,10 @@ async def test_leak(aconn_cls, dsn, faker, fetch, row_factory):
         ("select 'hello'", (), "select 'hello'"),
         ("select %s, %s", ([1, dt.date(2020, 1, 1)],), "select 1, '2020-01-01'::date"),
         ("select %(foo)s, %(foo)s", ({"foo": "x"},), "select 'x', 'x'"),
+        ("select %%", (), "select %%"),
+        ("select %%, %s", (["a"],), "select %, 'a'"),
+        ("select %%, %(foo)s", ({"foo": "x"},), "select %, 'x'"),
+        ("select %%s, %(foo)s", ({"foo": "x"},), "select %s, 'x'"),
     ],
 )
 async def test_mogrify(aconn, query, params, want):