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):
catalogged_object_list = []
append = catalogged_object_list.append
filter = filter_dict[method_name]
type_set = frozenset(filter['type']) or None
expression = filter['expression_instance']
expression_cache_key_list = filter.get('expression_cache_key', '').split()
for object in object_list:
# We will check if there is an filter on this # We will check if there is an filter on this
# method, if so we may not call this zsqlMethod # method, if so we may not call this zsqlMethod
# for this object # for this object
if type_set is not None and object.getPortalType() not in type_set: expression = None
continue try:
elif expression is not None: filter = filter_dict[method_name]
if filter['filtered']:
if filter.get('type'):
expression = Expression('python: context.getPortalType() in '
+ repr(tuple(filter['type'])))
LOG('SQLCatalog', WARNING,
"Convert deprecated type filter for %r into %r expression"
% (method_name, expression.text))
filter['type'] = ()
filter['expression'] = expression.text
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
try:
cache_key = (object.getProperty(key, None) for key in expression_cache_key_list)
# ZZZ - we could find a way to compute this once only # ZZZ - we could find a way to compute this once only
cache_key = (method_name, tuple(cache_key)) cache_key = tuple(object.getProperty(key) for key
result = expression_result_cache[cache_key] in expression_cache_key_list)
compute_result = 0
except KeyError:
cache_result = 1
compute_result = 1
else:
cache_result = 0
compute_result = 1
if compute_result:
try: try:
econtext = econtext_cache[object.uid] if expression_result_cache[cache_key]:
append(object)
continue
except KeyError: except KeyError:
econtext = self.getExpressionContext(object) pass
econtext_cache[object.uid] = econtext if old_context:
result = expression(self.getExpressionContext(object))
else:
econtext.setLocal('context', object)
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]
if expression == "":
self.filter_dict[id]['expression'] = ""
self.filter_dict[id]['expression_instance'] = None
else:
expr_instance = Expression(expression)
self.filter_dict[id]['expression'] = expression self.filter_dict[id]['expression'] = expression
self.filter_dict[id]['expression_instance'] = expr_instance if expression:
self.filter_dict[id]['expression_instance'] = Expression(expression)
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