265 lines
6.6 KiB
Python
265 lines
6.6 KiB
Python
#
|
|
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
|
|
"""
|
|
Catalog query terms and their logical combinations.
|
|
|
|
This is mainly a simplified version of Martijn Faassen's hurry.query
|
|
(http://cheeseshop.python.org/pypi/hurry.query).
|
|
|
|
$Id$
|
|
"""
|
|
|
|
from BTrees.IFBTree import weightedIntersection, weightedUnion
|
|
from BTrees.IFBTree import difference, IFBTree, IFBucket, IFSet
|
|
from BTrees.IIBTree import IISet, union
|
|
from zope.app.catalog.catalog import ResultSet
|
|
from zope.app.catalog.field import IFieldIndex
|
|
from zope.app.catalog.text import ITextIndex
|
|
from zope.app.catalog.interfaces import ICatalog
|
|
from zope import component
|
|
from zope.intid.interfaces import IIntIds
|
|
|
|
from cybertools.catalog.keyword import IKeywordIndex
|
|
|
|
|
|
class Term(object):
|
|
|
|
def __and__(self, other):
|
|
return And(self, other)
|
|
|
|
def __rand__(self, other):
|
|
return And(other, self)
|
|
|
|
def __or__(self, other):
|
|
return Or(self, other)
|
|
|
|
def __ror__(self, other):
|
|
return Or(other, self)
|
|
|
|
def __invert__(self):
|
|
return Not(self)
|
|
|
|
|
|
class And(Term):
|
|
|
|
def __init__(self, *terms):
|
|
self.terms = terms
|
|
|
|
def apply(self):
|
|
results = []
|
|
for term in self.terms:
|
|
r = term.apply()
|
|
if not r:
|
|
# empty results
|
|
return r
|
|
results.append((len(r), r))
|
|
if not results:
|
|
# no applicable terms at all
|
|
return IFBucket()
|
|
results.sort()
|
|
_, result = results.pop(0)
|
|
for _, r in results:
|
|
w, result = weightedIntersection(result, r)
|
|
return result
|
|
|
|
|
|
class Or(Term):
|
|
|
|
def __init__(self, *terms):
|
|
self.terms = terms
|
|
|
|
def apply(self):
|
|
results = []
|
|
for term in self.terms:
|
|
r = term.apply()
|
|
# empty results
|
|
if not r:
|
|
continue
|
|
results.append(r)
|
|
if not results:
|
|
# no applicable terms at all
|
|
return IFBucket()
|
|
result = results.pop(0)
|
|
for r in results:
|
|
w, result = weightedUnion(result, r)
|
|
return result
|
|
|
|
|
|
class Not(Term):
|
|
|
|
def __init__(self, term):
|
|
self.term = term
|
|
|
|
def apply(self):
|
|
return difference(self._all(), self.term.apply())
|
|
|
|
def _all(self):
|
|
# XXX may not work well/be efficient with extentcatalog
|
|
# XXX not very efficient in general, better to use internal
|
|
# IntIds datastructure but that would break abstraction..
|
|
intids = component.getUtility(IIntIds)
|
|
result = IFBucket()
|
|
for uid in intids:
|
|
result[uid] = 0
|
|
return result
|
|
|
|
|
|
class IndexTerm(Term):
|
|
|
|
def __init__(self, (catalog_name, index_name)):
|
|
self.catalog_name = catalog_name
|
|
self.index_name = index_name
|
|
|
|
def getIndex(self):
|
|
catalog = component.getUtility(ICatalog, self.catalog_name)
|
|
index = catalog[self.index_name]
|
|
return index
|
|
|
|
|
|
# field index
|
|
|
|
class FieldTerm(IndexTerm):
|
|
|
|
def getIndex(self):
|
|
index = super(FieldTerm, self).getIndex()
|
|
assert IFieldIndex.providedBy(index)
|
|
return index
|
|
|
|
|
|
class Eq(FieldTerm):
|
|
|
|
def __init__(self, index_id, value):
|
|
assert value is not None
|
|
super(Eq, self).__init__(index_id)
|
|
self.value = value
|
|
|
|
def apply(self):
|
|
return self.getIndex().apply((self.value, self.value))
|
|
|
|
|
|
class NotEq(FieldTerm):
|
|
|
|
def __init__(self, index_id, not_value):
|
|
super(NotEq, self).__init__(index_id)
|
|
self.not_value = not_value
|
|
|
|
def apply(self):
|
|
index = self.getIndex()
|
|
all = index.apply((None, None))
|
|
r = index.apply((self.not_value, self.not_value))
|
|
return difference(all, r)
|
|
|
|
|
|
class Between(FieldTerm):
|
|
|
|
def __init__(self, index_id, min_value, max_value):
|
|
super(Between, self).__init__(index_id)
|
|
self.min_value = min_value
|
|
self.max_value = max_value
|
|
|
|
def apply(self):
|
|
return self.getIndex().apply((self.min_value, self.max_value))
|
|
|
|
|
|
class Ge(Between):
|
|
|
|
def __init__(self, index_id, min_value):
|
|
super(Ge, self).__init__(index_id, min_value, None)
|
|
|
|
|
|
class Le(Between):
|
|
|
|
def __init__(self, index_id, max_value):
|
|
super(Le, self).__init__(index_id, None, max_value)
|
|
|
|
|
|
class In(FieldTerm):
|
|
|
|
def __init__(self, index_id, values):
|
|
assert None not in values
|
|
super(In, self).__init__(index_id)
|
|
self.values = values
|
|
|
|
def apply(self):
|
|
results = []
|
|
index = self.getIndex()
|
|
for value in self.values:
|
|
r = index.apply((value, value))
|
|
# empty results
|
|
if not r:
|
|
continue
|
|
results.append(r)
|
|
if not results:
|
|
# no applicable terms at all
|
|
return IFBucket()
|
|
result = results.pop(0)
|
|
for r in results:
|
|
w, result = weightedUnion(result, r)
|
|
return result
|
|
|
|
|
|
# text index
|
|
|
|
class Text(IndexTerm):
|
|
|
|
def __init__(self, index_id, text):
|
|
super(Text, self).__init__(index_id)
|
|
self.text = text
|
|
|
|
def getIndex(self):
|
|
index = super(Text, self).getIndex()
|
|
assert ITextIndex.providedBy(index)
|
|
return index
|
|
|
|
def apply(self):
|
|
index = self.getIndex()
|
|
return index.apply(self.text)
|
|
|
|
|
|
# keyword index
|
|
|
|
class KeywordTerm(IndexTerm):
|
|
|
|
def __init__(self, index_id, values):
|
|
super(KeywordTerm, self).__init__(index_id)
|
|
self.values = values
|
|
|
|
def getIndex(self):
|
|
index = super(KeywordTerm, self).getIndex()
|
|
#assert IKeywordIndex.providedBy(index)
|
|
return index
|
|
|
|
|
|
class AnyOf(KeywordTerm):
|
|
|
|
def apply(self):
|
|
result = self.getIndex().search(self.values, 'or')
|
|
if isinstance(result, IFSet):
|
|
return result
|
|
return IFSet(result)
|
|
|
|
|
|
class AllOf(KeywordTerm):
|
|
|
|
def apply(self):
|
|
result = self.getIndex().search(self.values, 'and')
|
|
if isinstance(result, IFSet):
|
|
return result
|
|
return IFSet(result)
|
|
|