You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2026/05/26 20:56:48 UTC
(allura) 32/33: [#8607] solr: strip local-params syntax
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch db/8607
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 058d5b94c7e09cf90117698e2d65850c41ae5549
Author: Dave Brondsema <da...@brondsema.net>
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 foo@bar.com 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):