Skip to content

Commit

Permalink
Initial support of catalog caching with plone.memoize
Browse files Browse the repository at this point in the history
  • Loading branch information
andbag committed Apr 3, 2018
1 parent 2a21568 commit 7ca9701
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 15 deletions.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
'zope.interface',
'zope.schema',
'zope.testing',
'plone.memoize',
],
include_package_data=True,
zip_safe=False,
Expand Down
7 changes: 7 additions & 0 deletions src/Products/PluginIndexes/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,10 @@ def make_query(query):

def getIndexNames():
""" returns index names that are optimized by index """


class IIndexCounter(Interface):
""" Invalidation helper API for pluggable indexes"""

def getCounter():
"""Return a counter which is increased on index changes"""
40 changes: 25 additions & 15 deletions src/Products/ZCatalog/Catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from Missing import MV
from Persistence import Persistent
from ZTUtils.Lazy import LazyMap, LazyCat, LazyValues
from plone.memoize import ram

from Products.PluginIndexes.interfaces import (
ILimitedResultIndex,
Expand All @@ -38,6 +39,7 @@
from Products.PluginIndexes.util import safe_callable
from Products.ZCatalog.CatalogBrains import AbstractCatalogBrain, NoBrainer
from Products.ZCatalog.plan import CatalogPlan
from Products.ZCatalog.cache import _apply_query_plan_cachekey
from Products.ZCatalog.ProgressHandler import ZLogHandler
from Products.ZCatalog.query import IndexQuery

Expand Down Expand Up @@ -594,6 +596,28 @@ def _search_index(self, cr, index_id, query, rs):

return rs

@ram.cache(_apply_query_plan_cachekey)
def _apply_query_plan(self, cr, query):

plan = cr.plan()
if not plan:
plan = self._sorted_search_indexes(query)

rs = None # result set
for index_id in plan:
# The actual core loop over all indices.
if index_id not in self.indexes:
# We can have bogus keys or the plan can contain
# index names that have been removed in the
# meantime.
continue

rs = self._search_index(cr, index_id, query, rs)
if not rs:
break

return rs

def search(self, query,
sort_index=None, reverse=False, limit=None, merge=True):
"""Iterate through the indexes, applying the query to each one. If
Expand All @@ -619,21 +643,7 @@ def search(self, query,
cr = self.getCatalogPlan(query)
cr.start()

plan = cr.plan()
if not plan:
plan = self._sorted_search_indexes(query)

rs = None # result set
for index_id in plan:
# The actual core loop over all indices.
if index_id not in self.indexes:
# We can have bogus keys or the plan can contain index names
# that have been removed in the meantime.
continue

rs = self._search_index(cr, index_id, query, rs)
if not rs:
break
rs = self._apply_query_plan(cr, query)

if not rs:
# None of the indexes found anything to do with the query.
Expand Down
116 changes: 116 additions & 0 deletions src/Products/ZCatalog/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
##############################################################################
#
# Copyright (c) 2010 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

from Acquisition import aq_base
from Acquisition import aq_parent

from DateTime import DateTime
from plone.memoize.volatile import DontCache
from Products.PluginIndexes.interfaces import IIndexCounter
from Products.PluginIndexes.interfaces import IDateRangeIndex
from Products.PluginIndexes.interfaces import IDateIndex


class CatalogCacheKey(object):
def __init__(self, catalog, query=None):

self.catalog = catalog
self.cid = self.get_id()
self.query = query
self.key = self.make_key(query)

def get_id(self):
parent = aq_parent(self.catalog)
path = getattr(aq_base(parent), 'getPhysicalPath', None)
if path is None:
path = ('', 'NonPersistentCatalog')
else:
path = tuple(parent.getPhysicalPath())
return path

def make_key(self, query):

if query is None:
return None

catalog = self.catalog

key = []
for name, value in query.items():
if name in catalog.indexes:
index = catalog.getIndex(name)
if IIndexCounter.providedBy(index):
counter = index.getCounter()
else:
# cache key invalidation cannot be supported if
# any index of query cannot be tested for changes
return None
else:
# return None if query has a nonexistent index key
return None

if isinstance(value, dict):
kvl = []
for k, v in value.items():
v = self._convert_datum(index, v)
v.append((k, v))
value = sorted(kvl)

else:
value = self._convert_datum(index, value)

key.append((name, counter, value))

key = tuple(sorted(key))
cache_key = '%s-%s' % (self.cid, hash(key))
return cache_key

def _convert_datum(self, index, value):

def convert_datetime(dt):

if IDateRangeIndex.providedBy(index):
term = index._convertDateTime(dt)
elif IDateIndex.providedBy(index):
term = index._convert(dt)
else:
term = value

return term

if isinstance(value, (list, tuple)):
res = []
for v in value:
if isinstance(v, DateTime):
v = convert_datetime(v)
res.append(v)
res.sort()

elif isinstance(value, DateTime):
res = convert_datetime(value)

else:
res = (value,)

if not isinstance(res, tuple):
res = tuple(res)

return res


# plone memoize cache key
def _apply_query_plan_cachekey(method, catalog, plan, query):
cc = CatalogCacheKey(catalog, query)
if cc.key is None:
raise DontCache
return cc.key

0 comments on commit 7ca9701

Please sign in to comment.