Skip to content

Commit

Permalink
[IMP] util.explode_query_range: replace problematic parallel_filter
Browse files Browse the repository at this point in the history
… 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
```

In order to adapt to this commit, some queries had to be updated.
See odoo/upgrade#6571
  • Loading branch information
Pirols committed Sep 26, 2024
1 parent 79f3d71 commit 96a474e
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 1 deletion.
26 changes: 26 additions & 0 deletions src/base/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,32 @@ def _get_cr(self):
self.addCleanup(cr.close)
return cr

def test_explode_format_parallel_filter(self):
cr = self._get_cr()

cr.execute("SELECT MIN(id) FROM res_users")
min_id = cr.fetchone()[0]

q1 = "SELECT '{} {0} {x} {x:*^30d} {x.a.b} {x[0]}, {x[1]!s:*^30} {{x}}' FROM res_users"
q2 = "SELECT '{} {0} {x} {x:*^30d} {x.a.b} {x[0]}, {x[1]!s:*^30} {{x}}' FROM res_users WHERE {parallel_filter}"
expected_out = q1 + f" WHERE res_users.id BETWEEN {min_id} AND {min_id}"

out1 = util.explode_query_range(
cr,
q1,
table="res_users",
bucket_size=1,
)[0]
self.assertEqual(out1, expected_out)

out2 = util.explode_query_range(
cr,
q2,
table="res_users",
bucket_size=1,
)[0]
self.assertEqual(out2, expected_out)

def test_explode_mult_filters(self):
cr = self._get_cr()
queries = util.explode_query_range(
Expand Down
2 changes: 1 addition & 1 deletion src/util/pg.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def explode_query_range(cr, query, table, alias=None, bucket_size=10000, prefix=
return [query.format(parallel_filter=parallel_filter)]

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

0 comments on commit 96a474e

Please sign in to comment.