diff --git a/product/ERP5/Document/ContributionPredicate.py b/product/ERP5/Document/ContributionPredicate.py
index f0fad0b5f80a795ae8ae0dc035ed8a59b742f47b..e96c8b42b20654723593e80b124f4ede83bdec03 100644
--- a/product/ERP5/Document/ContributionPredicate.py
+++ b/product/ERP5/Document/ContributionPredicate.py
@@ -126,3 +126,6 @@ class ContributionPredicate(Predicate, XMLObject):
 
   def asSQLExpression(self):
     raise NotImplementedError, 'ContributionPredicate does not support asSQLExpression.'
+
+  def asQuery(self, *args, **kw):
+    raise NotImplementedError('ContributionPredicate does not support asQuery.')
diff --git a/product/ERP5/interfaces/predicate.py b/product/ERP5/interfaces/predicate.py
index 0825dd231f46e76a223f5519ec34e1982e27edda..9d220dec537f687c871e3ce83dfed21de8370b4f 100644
--- a/product/ERP5/interfaces/predicate.py
+++ b/product/ERP5/interfaces/predicate.py
@@ -68,3 +68,14 @@ class IPredicate(Interface):
     implement test, results obtained through asSQLExpression
     must be additionnaly tested by invoking test().
     """
+
+  def asQuery():
+    """
+    A Predicate can be rendered as a set of catalog conditions. This
+    can be useful to create reporting trees based on the
+    ZSQLCatalog. This condition set is however partial since
+    python scripts which are used by the test method of the predicate
+    cannot be converted to catalog conditions. If a python script is defined to
+    implement test, results obtained through asQuery must be additionnaly
+    tested by invoking test().
+    """
diff --git a/product/ERP5Type/Core/Predicate.py b/product/ERP5Type/Core/Predicate.py
index ba1fe0b48778351a37a77a97b98d2e1c86c4d067..0ba83f13f7e9bc4b68b445ad878080e896d5dffc 100644
--- a/product/ERP5Type/Core/Predicate.py
+++ b/product/ERP5Type/Core/Predicate.py
@@ -27,6 +27,7 @@
 #
 ##############################################################################
 
+import itertools
 from types import MethodType
 import zope.interface
 from warnings import warn
@@ -40,7 +41,7 @@ from Products.ERP5Type.XMLObject import XMLObject
 from Products.ERP5Type.Utils import convertToUpperCase
 from Products.ERP5Type.Cache import readOnlyTransactionCache
 from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
-from Products.ZSQLCatalog.SQLCatalog import SQLQuery
+from Products.ZSQLCatalog.SQLCatalog import SQLQuery, SimpleQuery, ComplexQuery
 from Products.ERP5Type.Globals import PersistentMapping
 from Products.ERP5Type.UnrestrictedMethod import unrestricted_apply
 from Products.CMFCore.Expression import Expression
@@ -348,19 +349,100 @@ class Predicate(XMLObject):
   security.declareProtected( Permissions.AccessContentsInformation, 'asSqlJoinExpression' )
   asSqlJoinExpression = asSQLJoinExpression
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'asQuery')
+  def asQuery(self, strict_membership=False):
+    """
+    A Predicate can be rendered as a set of catalog conditions. This
+    can be useful to create reporting trees based on the
+    ZSQLCatalog. This condition set is however partial since
+    python scripts which are used by the test method of the predicate
+    cannot be converted to catalog conditions. If a python script is defined to
+    implement test, results obtained through asQuery must be additionnaly
+    tested by invoking test().
+    """
+    portal_catalog = self.getPortalObject().portal_catalog
+    buildSingleQuery = portal_catalog.getSQLCatalog().buildSingleQuery
+    getCategoryParameterDict = portal_catalog.getCategoryParameterDict
+    def filterCategoryList(base_category_set, category_list):
+      return [
+        x for x in category_list
+        if x.split('/', 1)[0] in base_category_set
+      ]
+    next_join_counter = itertools.count().next
+    def buildSeparateJoinQuery(name, value):
+      query = buildSingleQuery(name, value)
+      suffix = str(next_join_counter())
+      # XXX: using a deprecated API and accessing properties which are not part
+      # of the API. Of course this will never break !
+      query.setTableAliasList([
+        (x, x + suffix) for x in query.search_key.table_list
+      ])
+      return query
+
+    query_list = []
+    append = query_list.append
+
+    for buildQuery, getBaseCategorySet, getCategoryList in (
+      ( # Single-membership criterion
+        lambda name, value: buildSingleQuery(name, value),
+        self.getMembershipCriterionBaseCategoryList,
+        self.getMembershipCriterionCategoryList,
+      ),
+      ( # Multi-membership criterion
+        buildSeparateJoinQuery,
+        self.getMultimembershipCriterionBaseCategoryList,
+        self.getMembershipCriterionCategoryList,
+      ),
+    ):
+      append(
+        getCategoryParameterDict(
+          filterCategoryList(getBaseCategorySet(), getCategoryList()),
+          strict_membership=strict_membership,
+          onMissing=lambda category: False,
+        ),
+      )
+
+    # Value criterion
+    for criterion in self.getCriterionList():
+      if not criterion.min and not criterion.max:
+        append(buildSingleQuery(criterion.property, criterion.identity))
+        continue
+      if criterion.min:
+        append(SimpleQuery(
+          comparison_operator='>=',
+          **{criterion.property: criterion.min}
+        ))
+      if criterion.max:
+        append(SimpleQuery(
+          comparison_operator='<=',
+          **{criterion.property: criterion.max}
+        ))
+
+    if query_list:
+      return ComplexQuery(query_list, logical_operator='AND')
+    elif not getattr(self, 'isEmptyCriterionValid', lambda: True)():
+      # By catalog definition, no object has uid 0, so this condition forces an
+      # empty result.
+      return SimpleQuery(uid=0)
+    return SimpleQuery(uid=0, comparison_operator='>')
+
   security.declareProtected(Permissions.AccessContentsInformation, 'searchResults')
   def searchResults(self, **kw):
     """
     """
     return self.getPortalObject().portal_catalog.searchResults(
-      build_sql_query_method=self.buildSQLQuery, **kw)
+      predicate_internal_query=self.asQuery(),
+      **kw
+    )
 
   security.declareProtected(Permissions.AccessContentsInformation, 'countResults')
   def countResults(self, REQUEST=None, used=None, **kw):
     """
     """
     return self.getPortalObject().portal_catalog.countResults(
-      build_sql_query_method=self.buildSQLQuery, **kw)
+      predicate_internal_query=self.asQuery(),
+      **kw
+    )
 
   security.declareProtected( Permissions.AccessContentsInformation, 'getCriterionList' )
   def getCriterionList(self, **kw):