Commit dda0b10f authored by Hanno Schlichting's avatar Hanno Schlichting

Added a new BooleanIndex to the standard PluginIndexes, sponsored by Jarn AS...

Added a new BooleanIndex to the standard PluginIndexes, sponsored by Jarn AS and based on work in the Products.BooleanIndex distribution.
parent 4c844795
......@@ -19,6 +19,8 @@ Bugs Fixed
Features Added
++++++++++++++
- Added a new BooleanIndex to the standard PluginIndexes.
- Update to Zope Toolkit 1.0c1.
- Add ability to define extra zopectl commands via setuptools entrypoints.
......
##############################################################################
#
# Copyright (c) 2002 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 logging import getLogger
from App.special_dtml import DTMLFile
from BTrees.IIBTree import IIBTree, IITreeSet, IISet
from BTrees.IIBTree import union, intersection, difference
import BTrees.Length
from ZODB.POSException import ConflictError
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.common.UnIndex import UnIndex
_marker = object()
LOG = getLogger('BooleanIndex.UnIndex')
class BooleanIndex(UnIndex):
"""Index for booleans
self._index = set([documentId1, documentId2])
self._unindex = {documentId:[True/False]}
False doesn't have actual entries in _index.
"""
meta_type = "BooleanIndex"
manage_options= (
{'label': 'Settings',
'action': 'manage_main'},
{'label': 'Browse',
'action': 'manage_browse'},
)
query_options = ["query"]
manage = manage_main = DTMLFile('dtml/manageBooleanIndex', globals())
manage_main._setName('manage_main')
manage_browse = DTMLFile('../dtml/browseIndex', globals())
def clear(self):
self._length = BTrees.Length.Length()
self._index = IITreeSet()
self._unindex = IIBTree()
def insertForwardIndexEntry(self, entry, documentId):
"""If True, insert directly into treeset
"""
if entry:
self._index.insert(documentId)
self._length.change(1)
def removeForwardIndexEntry(self, entry, documentId):
"""Take the entry provided and remove any reference to documentId
in its entry in the index.
"""
try:
if entry:
self._index.remove(documentId)
self._length.change(-1)
except ConflictError:
raise
except Exception:
LOG.exception('%s: unindex_object could not remove '
'documentId %s from index %s. This '
'should not happen.' % (self.__class__.__name__,
str(documentId), str(self.id)))
def _index_object(self, documentId, obj, threshold=None, attr=''):
""" index and object 'obj' with integer id 'documentId'"""
returnStatus = 0
# First we need to see if there's anything interesting to look at
datum = self._get_object_datum(obj, attr)
# Make it boolean, int as an optimization
datum = int(bool(datum))
# We don't want to do anything that we don't have to here, so we'll
# check to see if the new and existing information is the same.
oldDatum = self._unindex.get(documentId, _marker)
if datum != oldDatum:
if oldDatum is not _marker:
self.removeForwardIndexEntry(oldDatum, documentId)
if datum is _marker:
try:
del self._unindex[documentId]
except ConflictError:
raise
except Exception:
LOG.error('Should not happen: oldDatum was there, now '
'its not, for document with id %s' %
documentId)
if datum is not _marker:
if datum:
self.insertForwardIndexEntry(datum, documentId)
self._unindex[documentId] = datum
returnStatus = 1
return returnStatus
def _apply_index(self, request, resultset=None):
record = parseIndexRequest(request, self.id, self.query_options)
if record.keys is None:
return None
index = self._index
for key in record.keys:
if key:
# If True, check index
return (intersection(index, resultset), (self.id, ))
else:
# Otherwise, remove from resultset or _unindex
if resultset is None:
return (union(difference(self._unindex, index), IISet([])),
(self.id, ))
else:
return (difference(resultset, index), (self.id, ))
return (IISet(), (self.id, ))
def indexSize(self):
"""Return distinct values, as an optimization we always claim 2."""
return 2
def items(self):
items = []
for v, k in self._unindex.items():
if isinstance(v, int):
v = IISet((v, ))
items.append((k, v))
return items
manage_addBooleanIndexForm = DTMLFile('dtml/addBooleanIndex', globals())
def manage_addBooleanIndex(self, id, extra=None,
REQUEST=None, RESPONSE=None, URL3=None):
"""Add a boolean index"""
return self.manage_addIndex(id, 'BooleanIndex', extra=extra, \
REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3)
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _, form_title='Add BooleanIndex')">
<p class="form-help">
<strong>Boolean Indexes</strong> can be used for keeping track of
whether objects fulfills a certain contract, like isFolderish
</p>
<form action="manage_addBooleanIndex" method="post" enctype="multipart/form-data">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Indexed attributes
</div>
</td>
<td align="left" valign="top">
<input type="text" name="extra.indexed_attrs:record:string" size="40" />
<em>attribute1,attribute2,...</em> or leave empty
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">
Type
</div>
</td>
<td align="left" valign="top">
Boolean Index
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
Objects indexed: <dtml-var numObjects>
<br>
Distinct values: <dtml-var indexSize>
</p>
<dtml-var manage_page_footer>
##############################################################################
#
# 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.
#
##############################################################################
import unittest
from BTrees.IIBTree import IISet
class Dummy(object):
def __init__(self, docid, truth):
self.id = docid
self.truth = truth
class TestBooleanIndex(unittest.TestCase):
def _getTargetClass(self):
from Products.PluginIndexes.BooleanIndex import BooleanIndex
return BooleanIndex.BooleanIndex
def _makeOne(self, attr='truth'):
return self._getTargetClass()(attr)
def test_index_true(self):
index = self._makeOne()
obj = Dummy(1, True)
index._index_object(obj.id, obj, attr='truth')
self.failUnless(1 in index._unindex)
self.failUnless(1 in index._index)
def test_index_false(self):
index = self._makeOne()
obj = Dummy(1, False)
index._index_object(obj.id, obj, attr='truth')
self.failUnless(1 in index._unindex)
self.failIf(1 in index._index)
def test_search_true(self):
index = self._makeOne()
obj = Dummy(1, True)
index._index_object(obj.id, obj, attr='truth')
obj = Dummy(2, False)
index._index_object(obj.id, obj, attr='truth')
res, idx = index._apply_index({'truth': True})
self.failUnlessEqual(idx, ('truth', ))
self.failUnlessEqual(list(res), [1])
def test_search_false(self):
index = self._makeOne()
obj = Dummy(1, True)
index._index_object(obj.id, obj, attr='truth')
obj = Dummy(2, False)
index._index_object(obj.id, obj, attr='truth')
res, idx = index._apply_index({'truth': False})
self.failUnlessEqual(idx, ('truth', ))
self.failUnlessEqual(list(res), [2])
def test_search_inputresult(self):
index = self._makeOne()
obj = Dummy(1, True)
index._index_object(obj.id, obj, attr='truth')
obj = Dummy(2, False)
index._index_object(obj.id, obj, attr='truth')
res, idx = index._apply_index({'truth': True}, resultset=IISet([]))
self.failUnlessEqual(idx, ('truth', ))
self.failUnlessEqual(list(res), [])
res, idx = index._apply_index({'truth': True}, resultset=IISet([2]))
self.failUnlessEqual(idx, ('truth', ))
self.failUnlessEqual(list(res), [])
res, idx = index._apply_index({'truth': True}, resultset=IISet([1]))
self.failUnlessEqual(idx, ('truth', ))
self.failUnlessEqual(list(res), [1])
res, idx = index._apply_index({'truth': True}, resultset=IISet([1, 2]))
self.failUnlessEqual(idx, ('truth', ))
self.failUnlessEqual(list(res), [1])
res, idx = index._apply_index({'truth': False},
resultset=IISet([1, 2]))
self.failUnlessEqual(idx, ('truth', ))
self.failUnlessEqual(list(res), [2])
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(TestBooleanIndex))
return suite
......@@ -91,3 +91,17 @@ def initialize(context):
icon='www/index.gif',
visibility=None,
)
from Products.PluginIndexes.BooleanIndex.BooleanIndex import BooleanIndex
from Products.PluginIndexes.BooleanIndex.BooleanIndex import \
manage_addBooleanIndex
from Products.PluginIndexes.BooleanIndex.BooleanIndex import \
manage_addBooleanIndexForm
context.registerClass(BooleanIndex,
permission='Add Pluggable Index',
constructors=(manage_addBooleanIndexForm,
manage_addBooleanIndex),
icon='www/index.gif',
visibility=None,
)
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