This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/allura.git
commit e0de4b3eb1d5dfc1ad170090284c9c9e65625920 Author: Dave Brondsema <[email protected]> AuthorDate: Tue May 26 14:59:41 2026 -0400 [#8607] solr: strip local-params syntax --- Allura/allura/lib/search.py | 29 ++++++++++++++++++++++++++++- Allura/allura/tests/unit/test_solr.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/Allura/allura/lib/search.py b/Allura/allura/lib/search.py index 6dd6693db..a2371ad7b 100644 --- a/Allura/allura/lib/search.py +++ b/Allura/allura/lib/search.py @@ -146,8 +146,36 @@ def inject_user(q, user=None): return q.replace('$USER', '"%s"' % user.username) if q else q +_LOCAL_PARAMS_RE = re.compile(r'\{![^}]*\}') + + +def strip_local_params(q): + '''Neutralize Solr local-params + + Solr 7.2+ mitigates https://solr.apache.org/guide/solr/latest/upgrade-notes/major-changes-in-solr-8.html#solr-7-2 + But clean them all out just in case + + Loops until idempotent: a single regex pass can leave a *new* `{!...}` in + the residue when the original had nested braces, e.g. + `{{!lucene}!type=dismax v='*:*'}` strips to `{!type=dismax v='*:*'}` + ''' + if not q: + return q + orig = q + total = 0 + while True: + q, n = _LOCAL_PARAMS_RE.subn('', q) + if not n: + break + total += n + if total: + log.warning(f'Stripped Solr local-params block(s) from query: {orig[:200]!r}') + return q + + def search(q, short_timeout=False, ignore_errors=True, **kw): q = inject_user(q) + q = strip_local_params(q) try: if short_timeout: return g.solr_short_timeout.search(q, **kw) @@ -231,7 +259,6 @@ def site_admin_search(model, q, field, **kw): # use parens to group all the parts of the query with the field # escaping spaces with '\ ' isn't sufficient for display_name_t since its stored as text_general (why??) # and wouldn't handle [email protected] split on @ either - # This should work, but doesn't for unknown reasons: q = u'{!term f=%s}%s' % (field, q) q = q.replace(':', r'\:') # Must escape the colon for IPv6 addresses q = obj.translate_query(f'{field}:({q})', fields) kw['q.op'] = 'AND' # so that all terms within the () are required diff --git a/Allura/allura/tests/unit/test_solr.py b/Allura/allura/tests/unit/test_solr.py index 06c45f285..6b5548df7 100644 --- a/Allura/allura/tests/unit/test_solr.py +++ b/Allura/allura/tests/unit/test_solr.py @@ -26,7 +26,7 @@ from allura.tests import decorators as td from alluratest.controller import setup_basic_test from allura.lib.solr import Solr, escape_solr_arg -from allura.lib.search import search_app, SearchIndexable +from allura.lib.search import search, search_app, SearchIndexable, strip_local_params class TestSolr: @@ -163,6 +163,33 @@ def test_solarize_html_in_text(self): assert self.obj.solarize() == dict(text='<script>a(1)</script>') +class TestStripLocalParams: + + @pytest.mark.parametrize('q,expected', [ + ('foo bar', 'foo bar'), + ('', ''), + (None, None), + ('{!type=lucene}foo', 'foo'), + ('{!type=lucene q.op=OR}*:*', '*:*'), + (' {!type=lucene}foo', ' foo'), + ('{!}rest', 'rest'), + ('text:{!type=lucene}foo', 'text:foo'), # later in query + ('{!a}foo{!b}bar', 'foobar'), # multiple + ("{{!lucene}!type=dismax v='*:*'}", ''), # nested + ('{{{!a}!b}!c}rest', 'rest'), # really nested + ]) + def test_strip(self, q, expected): + assert strip_local_params(q) == expected + + @mock.patch('allura.lib.search.g') + @mock.patch('allura.lib.search.c') + def test_search_strips_local_params_before_pysolr(self, c, g): + c.user.username = 'tester' + search('{!type=lucene q.op=OR}*:*') + # pysolr.search must receive the query without the local-params prefix + g.solr.search.assert_called_once_with('*:*') + + class TestSearch_app: def setup_method(self, method):
