diff --git a/product/CMFCategory/CategoryTool.py b/product/CMFCategory/CategoryTool.py
index 90759ed9a272556bd9a25e12a4ffc96b9d981c0a..0c1a472591467676955e1e3ad295a78fccc79847 100644
--- a/product/CMFCategory/CategoryTool.py
+++ b/product/CMFCategory/CategoryTool.py
@@ -1372,11 +1372,14 @@ class CategoryTool( UniqueObject, Folder, Base ):
 
       portal_catalog = context.getPortalObject().portal_catalog
       def search(category_list, portal_type, strict_membership):
-        catalog_kw = portal_catalog.getCategoryParameterDict(
-          category_list=category_list,
-          strict_membership=strict_membership,
-        )
-        inner_join_list = catalog_kw.keys()
+        inner_join_list = []
+        catalog_kw = {
+          'query': portal_catalog.getCategoryParameterDict(
+            category_list=category_list,
+            strict_membership=strict_membership,
+            onJoin=inner_join_list.append,
+          ),
+        }
         if portal_type is not None:
           catalog_kw['portal_type'] = portal_type
         return portal_catalog.unrestrictedSearchResults(
diff --git a/product/ERP5/Tool/DomainTool.py b/product/ERP5/Tool/DomainTool.py
index 9b9f061d4117a2a599e44e91dcf9efd45a256b4e..7ed2ea48d13e99caea59db3a8c981f76a2dd3b4e 100644
--- a/product/ERP5/Tool/DomainTool.py
+++ b/product/ERP5/Tool/DomainTool.py
@@ -183,24 +183,29 @@ class DomainTool(BaseTool):
             # BBB: ValueError would seem more appropriate here, but original code
             # was raising TypeError - and this is explicitely tested for.
             raise TypeError('Unknown category: %r' % (category, ))
-          for join_category_list, join_list, predicate_has_no_condition_value in (
+          def onInnerJoin(column_name):
+            inner_join_list.append(column_name)
             # Base category is part of preferred predicate categories, predicates
             # which ignore it are indexed with category_uid=0.
-            (inner_join_category_list, inner_join_list, 0),
+            return SimpleQuery(**{column_name: 0})
+          query_list.append(portal_catalog.getCategoryParameterDict(
+            inner_join_category_list,
+            category_table='predicate_category',
+            onMissing=onMissing,
+            onJoin=onInnerJoin,
+          ))
+          def onLeftJoin(column_name):
+            left_join_list.append(column_name)
             # Base category is not part of preferred predicate categories,
             # predicates which ignore it get no predicate_category row inserted
             # for it, so an SQL NULL appears, translating to None.
-            (left_join_category_list, left_join_list, None),
-          ):
-            category_parameter_kw = portal_catalog.getCategoryParameterDict(
-              join_category_list,
-              category_table='predicate_category',
-              onMissing=onMissing,
-            )
-            for relation, uid_set in category_parameter_kw.iteritems():
-              join_list.append(relation)
-              uid_set.add(predicate_has_no_condition_value)
-            kw.update(category_parameter_kw)
+            return SimpleQuery(**{column_name: None})
+          query_list.append(portal_catalog.getCategoryParameterDict(
+            left_join_category_list,
+            category_table='predicate_category',
+            onMissing=onMissing,
+            onJoin=onLeftJoin,
+          ))
         else:
           # No category to match against, so predicates expecting any relation
           # would not apply, so we can exclude these.
diff --git a/product/ERP5Catalog/CatalogTool.py b/product/ERP5Catalog/CatalogTool.py
index 5e0ed78d7d6756060931218fa404cade0a68f34d..85fa4d38f699372fdd27f1484a733edae4d26ac7 100644
--- a/product/ERP5Catalog/CatalogTool.py
+++ b/product/ERP5Catalog/CatalogTool.py
@@ -79,6 +79,7 @@ DYNAMIC_RELATED_KEY_FLAG_LIST = (
   ('strict', DYNAMIC_RELATED_KEY_FLAG_STRICT),
   ('predicate', DYNAMIC_RELATED_KEY_FLAG_PREDICATE),
 )
+EMPTY_SET = ()
 
 class IndexableObjectWrapper(object):
     __security_parameter_cache = None
@@ -1036,12 +1037,11 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject):
       return related_key_list
 
     security.declarePublic('getCategoryValueDictParameterDict')
-    def getCategoryValueDictParameterDict(self, base_category_dict, category_table='category', strict_membership=True, forward=True):
+    def getCategoryValueDictParameterDict(self, base_category_dict, category_table='category', strict_membership=True, forward=True, onJoin=lambda x: None):
       """
       From a mapping from base category ids to lists of documents, produce a
-      catalog keyword argument dictionary testing (strict or not, forward or
-      reverse relation) membership to these documents with their respective
-      base categories.
+      query tree testing (strict or not, forward or reverse relation)
+      membership to these documents with their respective base categories.
 
       base_category_dict (dict with base category ids as keys and document sets
       as values)
@@ -1054,9 +1054,17 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject):
       forward (bool)
         Whether document being looked up bears the relation (true) or is its
         target (false).
-
-      Return a dictionnary whose keys are catalog parameter names and values
-      are sets of uids.
+      onJoin(column_name) -> None or query
+        Called for each generated query which imply a join. Specifically, this
+        will not be called for "parent" relation, as it does not involve a
+        join.
+        Receives pseudo-column name of the relation as argument.
+        If return value is not None, it must be a query tree, OR-ed with
+        existing conditions for given pseudo-column.
+        This last form should very rarely be needed (ex: when joining with
+        predicate_category table as it contains non-standard uid values).
+
+      Return a query tree.
       """
       flag_list = []
       if category_table == 'predicate_category':
@@ -1067,23 +1075,36 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject):
         flag_list.append('strict')
       prefix = ('_'.join(flag_list) + '__') if flag_list else ''
       suffix = ('' if forward else '__related') + '__uid'
-      parent_uid_set = base_category_dict.pop('parent', None)
-      result = {
-        prefix + base_category_id + suffix: {
-          document.getUid() for document in document_set
-        }
-        for base_category_id, document_set in base_category_dict.iteritems()
-      }
-      if parent_uid_set is not None:
-        result['parent_uid'] = parent_uid_set
-      return result
+      parent_document_set = base_category_dict.pop('parent', None)
+      query_list = []
+      for base_category_id, document_set in base_category_dict.iteritems():
+        column = prefix + base_category_id + suffix
+        category_query = SimpleQuery(**{
+          column: {document.getUid() for document in document_set},
+        })
+        extra_query = onJoin(column)
+        if extra_query is not None:
+          category_query = ComplexQuery(
+            category_query,
+            extra_query,
+            logical_operator='OR',
+          )
+        query_list.append(category_query)
+      if parent_document_set is not None:
+        query_list.append(SimpleQuery(
+          parent_uid={
+            document.getUid()
+            for document in parent_document_set
+          },
+        ))
+      return ComplexQuery(query_list)
 
     security.declarePublic('getCategoryParameterDict')
     def getCategoryParameterDict(self, category_list, onMissing=lambda category: True, **kw):
       """
-      From a list of categories, produce a catalog keyword argument dictionary
-      testing (strict or not, forward or reverse relation) membership to these
-      categories.
+      From a list of categories, produce a query tree testing (strict or not,
+      forward or reverse relation) membership to these documents with their
+      respective base categories.
 
       category_list (list of category relative urls with their base categories)
       onMissing (callable)