Skip to content

Commit bf79fa6

Browse files
committed
[IMP] util.explode_query_range: replace problematic parallel_filter formatting
The usage of `str.format` to inject the parallel filter used to explode queries is not robust to the presence of other curly braces. Examples: 1. `JSON` strings (typically to leverage their mapping capabilities): see 79f3d71, where a query had to be modified to accomodate that. 2. Hardcoded sets of curly braces: ```python >>> "UPDATE t SET c = '{usage as literal characters}' WHERE {parallel_filter}".format(parallel_filter="…") Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'usage as literal characters' ``` Which can be (unelegantly) solved adding even more braces, leveraging one side-effect of `.format`: ```python >>> "UPDATE t SET c = '{{usage as literal characters}}' WHERE {parallel_filter}".format(parallel_filter="…") "UPDATE t SET c = '{usage as literal characters}' WHERE …" ``` 3. Hardcoded curly braces (AFAICT no way to solve this): ```python >>> "UPDATE t SET c = 'this is an open curly brace = {' WHERE {parallel_filter}".format(parallel_filter="…") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: unexpected '{' in field name ``` ```python >>> "UPDATE t SET c = 'this is a close brace = }' WHERE {parallel_filter}".format(parallel_filter="…") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: Single '}' encountered in format string ```
1 parent 79f3d71 commit bf79fa6

File tree

2 files changed

+27
-1
lines changed

2 files changed

+27
-1
lines changed

src/base/tests/test_util.py

+26
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,32 @@ def _get_cr(self):
659659
self.addCleanup(cr.close)
660660
return cr
661661

662+
def test_explode_format_parallel_filter(self):
663+
cr = self._get_cr()
664+
665+
cr.execute("SELECT MIN(id) FROM res_users")
666+
min_id = cr.fetchone()[0]
667+
668+
q1 = "SELECT '{} {0} {x} {x:*^30d} {x.a.b} {x[0]}, {x[1]!s:*^30} {{x}}' FROM res_users"
669+
q2 = "SELECT '{} {0} {x} {x:*^30d} {x.a.b} {x[0]}, {x[1]!s:*^30} {{x}}' FROM res_users WHERE {parallel_filter}"
670+
expected_out = q1 + f" WHERE res_users.id BETWEEN {min_id} AND {min_id}"
671+
672+
out1 = util.explode_query_range(
673+
cr,
674+
q1,
675+
table="res_users",
676+
bucket_size=1,
677+
)[0]
678+
self.assertEqual(out1, expected_out)
679+
680+
out2 = util.explode_query_range(
681+
cr,
682+
q2,
683+
table="res_users",
684+
bucket_size=1,
685+
)[0]
686+
self.assertEqual(out2, expected_out)
687+
662688
def test_explode_mult_filters(self):
663689
cr = self._get_cr()
664690
queries = util.explode_query_range(

src/util/pg.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ def explode_query_range(cr, query, table, alias=None, bucket_size=10000, prefix=
300300
return [query.format(parallel_filter=parallel_filter)]
301301

302302
parallel_filter = "{alias}.id BETWEEN %(lower-bound)s AND %(upper-bound)s".format(alias=alias)
303-
query = query.replace("%", "%%").format(parallel_filter=parallel_filter)
303+
query = query.replace("%", "%%").replace("{parallel_filter}", parallel_filter)
304304
return [
305305
cr.mogrify(query, {"lower-bound": ids[i], "upper-bound": ids[i + 1] - 1}).decode() for i in range(len(ids) - 1)
306306
]

0 commit comments

Comments
 (0)