Commit 9abc60d8 authored by Julien Muchembled's avatar Julien Muchembled

ZSQLCatalog: speed up _catalogObjectList

* Follow-up of [17630]:
  - Finish implementation of expression cache keys, including BT support.
  - Really deprecate portal type filtering.
* Filter expressions shall use 'context' to refer to the object. All other
  variables (here, container, isDelivery, etc) are deprecated.
  Remove now useless expression context cache.
* Compute the list of objects to catalog only once per filter expression.

Optimisations will be really effective when erp5_mysql_innodb is updated.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@32324 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 532a963a
...@@ -101,6 +101,7 @@ catalog_method_list = ('_is_catalog_list_method_archive', ...@@ -101,6 +101,7 @@ catalog_method_list = ('_is_catalog_list_method_archive',
catalog_method_filter_list = ('_filter_expression_archive', catalog_method_filter_list = ('_filter_expression_archive',
'_filter_expression_instance_archive', '_filter_expression_instance_archive',
'_filter_expression_cache_key_archive',
'_filter_type_archive',) '_filter_type_archive',)
INSTALLED_BT_FOR_DIFF = 'installed_bt_for_diff' INSTALLED_BT_FOR_DIFF = 'installed_bt_for_diff'
...@@ -2150,9 +2151,8 @@ class CatalogMethodTemplateItem(ObjectTemplateItem): ...@@ -2150,9 +2151,8 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
self._method_properties = PersistentMapping() self._method_properties = PersistentMapping()
self._is_filtered_archive = PersistentMapping() self._is_filtered_archive = PersistentMapping()
self._filter_expression_archive = PersistentMapping() for method in catalog_method_filter_list:
self._filter_expression_instance_archive = PersistentMapping() setattr(self, method, PersistentMapping())
self._filter_type_archive = PersistentMapping()
def _extractMethodProperties(self, catalog, method_id): def _extractMethodProperties(self, catalog, method_id):
"""Extracts properties for a given method in the catalog. """Extracts properties for a given method in the catalog.
...@@ -2184,20 +2184,14 @@ class CatalogMethodTemplateItem(ObjectTemplateItem): ...@@ -2184,20 +2184,14 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
method_id = obj.id method_id = obj.id
self._method_properties[method_id] = self._extractMethodProperties( self._method_properties[method_id] = self._extractMethodProperties(
catalog, method_id) catalog, method_id)
self._is_filtered_archive[method_id] = 0 filter = catalog.filter_dict.get(method_id, {})
if catalog.filter_dict.has_key(method_id): self._is_filtered_archive[method_id] = filter.get('filtered', 0)
if catalog.filter_dict[method_id]['filtered']: for method in catalog_method_filter_list:
self._is_filtered_archive[method_id] = \ property = method[8:-8]
catalog.filter_dict[method_id]['filtered'] if property in filter:
self._filter_expression_archive[method_id] = \ getattr(self, method)[method_id] = filter[property]
catalog.filter_dict[method_id]['expression']
self._filter_expression_instance_archive[method_id] = \
catalog.filter_dict[method_id]['expression_instance']
self._filter_type_archive[method_id] = \
catalog.filter_dict[method_id]['type']
def generateXml(self, path): def generateXml(self, path):
catalog = _getCatalogValue(self)
obj = self._objects[path] obj = self._objects[path]
method_id = obj.id method_id = obj.id
xml_data = '<catalog_method>' xml_data = '<catalog_method>'
...@@ -2207,23 +2201,18 @@ class CatalogMethodTemplateItem(ObjectTemplateItem): ...@@ -2207,23 +2201,18 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
xml_data += '\n <value>%s</value>' %(value,) xml_data += '\n <value>%s</value>' %(value,)
xml_data += '\n </item>' xml_data += '\n </item>'
if catalog.filter_dict.has_key(method_id): if self._is_filtered_archive.get(method_id):
if catalog.filter_dict[method_id]['filtered']:
xml_data += '\n <item key="_is_filtered_archive" type="int">' xml_data += '\n <item key="_is_filtered_archive" type="int">'
xml_data += '\n <value>1</value>' xml_data += '\n <value>1</value>'
xml_data += '\n </item>' xml_data += '\n </item>'
for method in catalog_method_filter_list: for method in catalog_method_filter_list:
try:
value = getattr(self, method, '')[method_id]
except KeyError:
# the method has no key
continue
if method != '_filter_expression_instance_archive': if method != '_filter_expression_instance_archive':
if type(value) in (type(''), type(u'')): value = getattr(self, method, {}).get(method_id)
if isinstance(value, basestring):
xml_data += '\n <item key="%s" type="str">' %(method,) xml_data += '\n <item key="%s" type="str">' %(method,)
xml_data += '\n <value>%s</value>' %(str(value)) xml_data += '\n <value>%s</value>' %(str(value))
xml_data += '\n </item>' xml_data += '\n </item>'
elif type(value) in (type(()), type([])): elif value:
xml_data += '\n <item key="%s" type="tuple">'%(method) xml_data += '\n <item key="%s" type="tuple">'%(method)
for item in value: for item in value:
xml_data += '\n <value>%s</value>' %(str(item)) xml_data += '\n <value>%s</value>' %(str(item))
...@@ -2323,12 +2312,14 @@ class CatalogMethodTemplateItem(ObjectTemplateItem): ...@@ -2323,12 +2312,14 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
expr_instance = None expr_instance = None
else: else:
expr_instance = self._filter_expression_instance_archive[method_id] expr_instance = self._filter_expression_instance_archive[method_id]
filter_type = self._filter_type_archive[method_id]
catalog.filter_dict[method_id] = PersistentMapping() catalog.filter_dict[method_id] = PersistentMapping()
catalog.filter_dict[method_id]['filtered'] = 1 catalog.filter_dict[method_id]['filtered'] = 1
catalog.filter_dict[method_id]['expression'] = expression catalog.filter_dict[method_id]['expression'] = expression
catalog.filter_dict[method_id]['expression_instance'] = expr_instance catalog.filter_dict[method_id]['expression_instance'] = expr_instance
catalog.filter_dict[method_id]['type'] = filter_type catalog.filter_dict[method_id]['expression_cache_key'] = \
self._filter_expression_cache_key_archive.get(method_id, ())
catalog.filter_dict[method_id]['type'] = \
self._filter_type_archive.get(method_id, ())
elif method_id in catalog.filter_dict.keys(): elif method_id in catalog.filter_dict.keys():
catalog.filter_dict[method_id]['filtered'] = 0 catalog.filter_dict[method_id]['filtered'] = 0
...@@ -2420,21 +2411,16 @@ class CatalogMethodTemplateItem(ObjectTemplateItem): ...@@ -2420,21 +2411,16 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
value = str(method.getElementsByTagName('value')[0].childNodes[0].data) value = str(method.getElementsByTagName('value')[0].childNodes[0].data)
else: else:
value = '' value = ''
key = str(key)
elif key_type == "int": elif key_type == "int":
value = int(method.getElementsByTagName('value')[0].childNodes[0].data) value = int(method.getElementsByTagName('value')[0].childNodes[0].data)
key = str(key)
elif key_type == "tuple": elif key_type == "tuple":
value = [] value = tuple(item.childNodes[0].data
value_list = method.getElementsByTagName('value') for item in method.getElementsByTagName('value'))
for item in value_list:
value.append(item.childNodes[0].data)
else: else:
LOG('BusinessTemplate import CatalogMethod, type unknown', 0, key_type) LOG('BusinessTemplate import CatalogMethod, type unknown', 0, key_type)
continue continue
if key in catalog_method_list or key in catalog_method_filter_list: if key in catalog_method_list or key in catalog_method_filter_list:
dict = getattr(self, key, {}) getattr(self, key)[id] = value
dict[id] = value
else: else:
# new style key # new style key
self._method_properties.setdefault(id, PersistentMapping())[key] = 1 self._method_properties.setdefault(id, PersistentMapping())[key] = 1
......
...@@ -1381,12 +1381,13 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor): ...@@ -1381,12 +1381,13 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
sql_uncatalog_object.sort() sql_uncatalog_object.sort()
catalog.sql_uncatalog_object = tuple(sql_uncatalog_object) catalog.sql_uncatalog_object = tuple(sql_uncatalog_object)
# set filter for this method # set filter for this method
expression = 'python: isMovement' expression = 'python: context.isPredicate()'
expr_instance = Expression(expression) expr_instance = Expression(expression)
catalog.filter_dict[method_id] = PersistentMapping() catalog.filter_dict[method_id] = PersistentMapping()
catalog.filter_dict[method_id]['filtered'] = 1 catalog.filter_dict[method_id]['filtered'] = 1
catalog.filter_dict[method_id]['expression'] = expression catalog.filter_dict[method_id]['expression'] = expression
catalog.filter_dict[method_id]['expression_instance'] = expr_instance catalog.filter_dict[method_id]['expression_instance'] = expr_instance
catalog.filter_dict[method_id]['expression_cache_key'] = 'portal_type',
catalog.filter_dict[method_id]['type'] = [] catalog.filter_dict[method_id]['type'] = []
...@@ -1410,12 +1411,13 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor): ...@@ -1410,12 +1411,13 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
sql_uncatalog_object.sort() sql_uncatalog_object.sort()
catalog.sql_uncatalog_object = tuple(sql_uncatalog_object) catalog.sql_uncatalog_object = tuple(sql_uncatalog_object)
# set filter for this method # set filter for this method
expression = 'python: isDelivery' expression = 'python: context.isDelivery()'
expr_instance = Expression(expression) expr_instance = Expression(expression)
catalog.filter_dict[method_id] = PersistentMapping() catalog.filter_dict[method_id] = PersistentMapping()
catalog.filter_dict[method_id]['filtered'] = 1 catalog.filter_dict[method_id]['filtered'] = 1
catalog.filter_dict[method_id]['expression'] = expression catalog.filter_dict[method_id]['expression'] = expression
catalog.filter_dict[method_id]['expression_instance'] = expr_instance catalog.filter_dict[method_id]['expression_instance'] = expr_instance
catalog.filter_dict[method_id]['expression_cache_key'] = 'portal_type',
catalog.filter_dict[method_id]['type'] = [] catalog.filter_dict[method_id]['type'] = []
def stepCreateNewCatalogMethod(self, sequence=None, sequence_list=None, **kw): def stepCreateNewCatalogMethod(self, sequence=None, sequence_list=None, **kw):
...@@ -1494,15 +1496,15 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor): ...@@ -1494,15 +1496,15 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
self.failUnless(catalog is not None) self.failUnless(catalog is not None)
method_id = sequence.get('zsql_method_id', None) method_id = sequence.get('zsql_method_id', None)
zsql_method = catalog._getOb(method_id, None) zsql_method = catalog._getOb(method_id, None)
self.failUnless(zsql_method is not None) self.assertNotEqual(zsql_method, None)
# check catalog properties # check catalog properties
self.failUnless(method_id in catalog.sql_uncatalog_object) self.failUnless(method_id in catalog.sql_uncatalog_object)
# check filter # check filter
self.failUnless(method_id in catalog.filter_dict.keys())
filter_dict = catalog.filter_dict[method_id] filter_dict = catalog.filter_dict[method_id]
self.assertEqual(filter_dict['filtered'], 1) self.assertEqual(filter_dict['filtered'], 1)
self.assertEqual(filter_dict['expression'], 'python: isMovement') self.assertEqual(filter_dict['expression'], 'python: context.isPredicate()')
self.assertEqual(filter_dict['type'], []) self.assertEqual(filter_dict['expression_cache_key'], ('portal_type',))
self.assertEqual(filter_dict['type'], ())
def stepCheckUpdatedCatalogMethodExists(self, sequence=None, sequence_list=None, **kw): def stepCheckUpdatedCatalogMethodExists(self, sequence=None, sequence_list=None, **kw):
""" """
...@@ -1513,15 +1515,15 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor): ...@@ -1513,15 +1515,15 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
self.failUnless(catalog is not None) self.failUnless(catalog is not None)
method_id = sequence.get('zsql_method_id', None) method_id = sequence.get('zsql_method_id', None)
zsql_method = catalog._getOb(method_id, None) zsql_method = catalog._getOb(method_id, None)
self.failUnless(zsql_method is not None) self.assertNotEqual(zsql_method, None)
# check catalog properties # check catalog properties
self.failUnless(method_id in catalog.sql_uncatalog_object) self.failUnless(method_id in catalog.sql_uncatalog_object)
# check filter # check filter
self.failUnless(method_id in catalog.filter_dict.keys())
filter_dict = catalog.filter_dict[method_id] filter_dict = catalog.filter_dict[method_id]
self.assertEqual(filter_dict['filtered'], 1) self.assertEqual(filter_dict['filtered'], 1)
self.assertEqual(filter_dict['expression'], 'python: isDelivery') self.assertEqual(filter_dict['expression'], 'python: context.isDelivery()')
self.assertEqual(filter_dict['type'], []) self.assertEqual(filter_dict['expression_cache_key'], ('portal_type',))
self.assertEqual(filter_dict['type'], ())
def stepCheckCatalogMethodRemoved(self, sequence=None, sequence_list=None, **kw): def stepCheckCatalogMethodRemoved(self, sequence=None, sequence_list=None, **kw):
""" """
......
...@@ -36,6 +36,8 @@ import sys ...@@ -36,6 +36,8 @@ import sys
import urllib import urllib
import string import string
import pprint import pprint
import re
import warnings
from cStringIO import StringIO from cStringIO import StringIO
from xml.dom.minidom import parse from xml.dom.minidom import parse
from xml.sax.saxutils import escape, quoteattr from xml.sax.saxutils import escape, quoteattr
...@@ -62,6 +64,7 @@ try: ...@@ -62,6 +64,7 @@ try:
from Products.CMFCore.Expression import Expression from Products.CMFCore.Expression import Expression
from Products.PageTemplates.Expressions import getEngine from Products.PageTemplates.Expressions import getEngine
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
new_context_search = re.compile(r'\bcontext\b').search
withCMF = 1 withCMF = 1
except ImportError: except ImportError:
withCMF = 0 withCMF = 0
...@@ -1368,8 +1371,7 @@ class Catalog(Folder, ...@@ -1368,8 +1371,7 @@ class Catalog(Folder,
if method_id_list is None: if method_id_list is None:
method_id_list = self.sql_catalog_object_list method_id_list = self.sql_catalog_object_list
econtext_cache = {} econtext = getEngine().getContext()
expression_result_cache = {}
argument_cache = {} argument_cache = {}
try: try:
...@@ -1377,56 +1379,71 @@ class Catalog(Folder, ...@@ -1377,56 +1379,71 @@ class Catalog(Folder,
enableReadOnlyTransactionCache(self) enableReadOnlyTransactionCache(self)
filter_dict = self.filter_dict filter_dict = self.filter_dict
isMethodFiltered = self.isMethodFiltered catalogged_object_list_cache = {}
for method_name in method_id_list: for method_name in method_id_list:
if isMethodFiltered(method_name): # We will check if there is an filter on this
catalogged_object_list = [] # method, if so we may not call this zsqlMethod
append = catalogged_object_list.append # for this object
expression = None
try:
filter = filter_dict[method_name] filter = filter_dict[method_name]
type_set = frozenset(filter['type']) or None if filter['filtered']:
expression = filter['expression_instance'] if filter.get('type'):
expression_cache_key_list = filter.get('expression_cache_key', '').split() expression = Expression('python: context.getPortalType() in '
for object in object_list: + repr(tuple(filter['type'])))
# We will check if there is an filter on this LOG('SQLCatalog', WARNING,
# method, if so we may not call this zsqlMethod "Convert deprecated type filter for %r into %r expression"
# for this object % (method_name, expression.text))
if type_set is not None and object.getPortalType() not in type_set: filter['type'] = ()
continue filter['expression'] = expression.text
elif expression is not None: filter['expression_instance'] = expression
else:
expression = filter['expression_instance']
except KeyError:
pass
if expression is None:
catalogged_object_list = object_list
else:
text = expression.text
catalogged_object_list = catalogged_object_list_cache.get(text)
if catalogged_object_list is None:
catalogged_object_list_cache[text] = catalogged_object_list = []
append = catalogged_object_list.append
old_context = new_context_search(text) is None
if old_context:
warnings.warn("Filter expression for %r (%r): using variables"
" other than 'context' is deprecated and slower."
% (method_name, text), DeprecationWarning)
expression_cache_key_list = filter.get('expression_cache_key', ())
expression_result_cache = {}
for object in object_list:
if expression_cache_key_list: if expression_cache_key_list:
# We try to save results of expressions by portal_type # Expressions are slow to evaluate because they are executed
# or by anyother key which can prevent us from evaluating # in restricted environment. So we try to save results of
# expressions. This cache is built each time we reindex # expressions by portal_type or any other key.
# This cache is built each time we reindex
# objects but we could also use over multiple transactions # objects but we could also use over multiple transactions
# if this can improve performance significantly # if this can improve performance significantly
# ZZZ - we could find a way to compute this once only
cache_key = tuple(object.getProperty(key) for key
in expression_cache_key_list)
try: try:
cache_key = (object.getProperty(key, None) for key in expression_cache_key_list) if expression_result_cache[cache_key]:
# ZZZ - we could find a way to compute this once only append(object)
cache_key = (method_name, tuple(cache_key)) continue
result = expression_result_cache[cache_key]
compute_result = 0
except KeyError: except KeyError:
cache_result = 1 pass
compute_result = 1 if old_context:
result = expression(self.getExpressionContext(object))
else: else:
cache_result = 0 econtext.setLocal('context', object)
compute_result = 1
if compute_result:
try:
econtext = econtext_cache[object.uid]
except KeyError:
econtext = self.getExpressionContext(object)
econtext_cache[object.uid] = econtext
result = expression(econtext) result = expression(econtext)
if cache_result: if expression_cache_key_list:
expression_result_cache[cache_key] = result expression_result_cache[cache_key] = result
if not result: if result:
continue append(object)
append(object)
else:
catalogged_object_list = object_list
if len(catalogged_object_list) == 0: if not catalogged_object_list:
continue continue
#LOG('catalogObjectList', 0, 'method_name = %s' % (method_name,)) #LOG('catalogObjectList', 0, 'method_name = %s' % (method_name,))
...@@ -2325,27 +2342,17 @@ class Catalog(Folder, ...@@ -2325,27 +2342,17 @@ class Catalog(Folder,
# We will first look if the filter is activated # We will first look if the filter is activated
if not self.filter_dict.has_key(id): if not self.filter_dict.has_key(id):
self.filter_dict[id] = PersistentMapping() self.filter_dict[id] = PersistentMapping()
self.filter_dict[id]['filtered'] = 0
self.filter_dict[id]['type'] = []
self.filter_dict[id]['expression'] = ""
self.filter_dict[id]['expression_cache_key'] = "portal_type"
if REQUEST.has_key('%s_box' % id): if REQUEST.has_key('%s_box' % id):
self.filter_dict[id]['filtered'] = 1 self.filter_dict[id]['filtered'] = 1
else: else:
self.filter_dict[id]['filtered'] = 0 self.filter_dict[id]['filtered'] = 0
if REQUEST.has_key('%s_expression' % id): expression = REQUEST.get('%s_expression' % id, '').strip()
expression = REQUEST['%s_expression' % id] self.filter_dict[id]['expression'] = expression
if expression == "": if expression:
self.filter_dict[id]['expression'] = "" self.filter_dict[id]['expression_instance'] = Expression(expression)
self.filter_dict[id]['expression_instance'] = None
else:
expr_instance = Expression(expression)
self.filter_dict[id]['expression'] = expression
self.filter_dict[id]['expression_instance'] = expr_instance
else: else:
self.filter_dict[id]['expression'] = ""
self.filter_dict[id]['expression_instance'] = None self.filter_dict[id]['expression_instance'] = None
if REQUEST.has_key('%s_type' % id): if REQUEST.has_key('%s_type' % id):
...@@ -2356,14 +2363,8 @@ class Catalog(Folder, ...@@ -2356,14 +2363,8 @@ class Catalog(Folder,
else: else:
self.filter_dict[id]['type'] = [] self.filter_dict[id]['type'] = []
if REQUEST.has_key('%s_expression_cache_key' % id): self.filter_dict[id]['expression_cache_key'] = \
expression_cache_key = REQUEST['%s_expression_cache_key' % id] tuple(sorted(REQUEST.get('%s_expression_cache_key' % id, '').split()))
if expression_cache_key == "":
self.filter_dict[id]['expression_cache_key'] = expression_cache_key
else:
self.filter_dict[id]['expression_cache_key'] = ""
else:
self.filter_dict[id]['expression_cache_key'] = ""
if RESPONSE and URL1: if RESPONSE and URL1:
RESPONSE.redirect(URL1 + '/manage_catalogFilter?manage_tabs_message=Filter%20Changed') RESPONSE.redirect(URL1 + '/manage_catalogFilter?manage_tabs_message=Filter%20Changed')
...@@ -2406,7 +2407,7 @@ class Catalog(Folder, ...@@ -2406,7 +2407,7 @@ class Catalog(Folder,
self.filter_dict = PersistentMapping() self.filter_dict = PersistentMapping()
return "" return ""
try: try:
return self.filter_dict[method_name]['expression_cache_key'] return ' '.join(self.filter_dict[method_name]['expression_cache_key'])
except KeyError: except KeyError:
return "" return ""
return "" return ""
...@@ -2426,6 +2427,7 @@ class Catalog(Folder, ...@@ -2426,6 +2427,7 @@ class Catalog(Folder,
def isPortalTypeSelected(self, method_name, portal_type): def isPortalTypeSelected(self, method_name, portal_type):
""" Returns true if the portal type is selected for this method. """ Returns true if the portal type is selected for this method.
XXX deprecated
""" """
if withCMF: if withCMF:
if getattr(aq_base(self), 'filter_dict', None) is None: if getattr(aq_base(self), 'filter_dict', None) is None:
...@@ -2440,6 +2442,7 @@ class Catalog(Folder, ...@@ -2440,6 +2442,7 @@ class Catalog(Folder,
def getFilteredPortalTypeList(self, method_name): def getFilteredPortalTypeList(self, method_name):
""" Returns the list of portal types which define """ Returns the list of portal types which define
the filter. the filter.
XXX deprecated
""" """
if withCMF: if withCMF:
if getattr(aq_base(self), 'filter_dict', None) is None: if getattr(aq_base(self), 'filter_dict', None) is None:
...@@ -2469,12 +2472,12 @@ class Catalog(Folder, ...@@ -2469,12 +2472,12 @@ class Catalog(Folder,
def getExpressionContext(self, ob): def getExpressionContext(self, ob):
''' '''
An expression context provides names for TALES expressions. An expression context provides names for TALES expressions.
XXX deprecated
''' '''
if withCMF: if withCMF:
data = { data = {
'here': ob, 'here': ob,
'container': aq_parent(aq_inner(ob)), 'container': aq_parent(aq_inner(ob)),
'nothing': None,
#'root': ob.getPhysicalRoot(), #'root': ob.getPhysicalRoot(),
#'request': getattr( ob, 'REQUEST', None ), #'request': getattr( ob, 'REQUEST', None ),
#'modules': SecureModuleImporter, #'modules': SecureModuleImporter,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment