[Zope-Checkins] CVS: Zope/lib/python/Products/PluginIndexes/DateIndexes - DateIndex.py:1.1.2.1 DateRangeIndex.py:1.1.2.1 __init__.py:1.1.2.1
Martijn Pieters
mj@zope.com
Wed, 8 May 2002 16:41:42 -0400
Update of /cvs-repository/Zope/lib/python/Products/PluginIndexes/DateIndexes
In directory cvs.zope.org:/tmp/cvs-serv7740/PluginIndexes/DateIndexes
Added Files:
Tag: mj-dateindexes_integration-branch
DateIndex.py DateRangeIndex.py __init__.py
Log Message:
Whoops, wrong place. Code needs lots of work to make it all work.
=== Added File Zope/lib/python/Products/PluginIndexes/DateIndexes/DateIndex.py ===
from DateTime.DateTime import DateTime
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common.UnIndex import UnIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from types import StringType, FloatType, IntType
from Globals import DTMLFile
from OFS.SimpleItem import SimpleItem
from BTrees.IOBTree import IOBTree
from BTrees.OIBTree import OIBTree
from BTrees.IIBTree import IISet, union
_marker = []
class DateIndex( UnIndex
, PluggableIndex.PluggableIndex
, SimpleItem
):
""" Index for Dates """
__implements__ = (PluggableIndex.PluggableIndexInterface,)
meta_type = 'DateIndex'
query_options = ['query', 'range']
manage = manage_main = DTMLFile( 'dtml/manageDateIndex', globals() )
manage_main._setName( 'manage_main' )
manage_options = ( { 'label' : 'Settings'
, 'action' : 'manage_main'
},
)
def clear( self ):
""" Complete reset """
self._index = IOBTree()
self._unindex = OIBTree()
def index_object( self, documentId, obj, threshold=None ):
"""index an object"""
returnStatus = 0
try:
date_attr = getattr( obj, self.id )
if callable( date_attr ):
date_attr = date_attr()
ConvertedDate = self._convert( value=date_attr, default=_marker )
except AttributeError:
ConvertedDate = _marker
oldConvertedDate = self._unindex.get( documentId, _marker )
if ConvertedDate != oldConvertedDate:
if oldConvertedDate is not _marker:
self.removeForwardIndexEntry( oldConvertedDate, documentId )
if ConvertedDate is not _marker:
self.insertForwardIndexEntry( ConvertedDate, documentId )
self._unindex[documentId] = ConvertedDate
returnStatus = 1
return returnStatus
def _apply_index( self, request, cid='', type=type, None=None ):
"""Apply the index to query parameters given in the argument"""
record = parseIndexRequest( request, self.id, self.query_options )
if record.keys == None: return None
keys = map( self._convert, record.keys )
index = self._index
r = None
opr = None
#experimental code for specifing the operator
operator = record.get( 'operator', self.useOperator )
if not operator in self.operators :
raise exepctions.RuntimeError,"operator not valid: %s" % operator
# depending on the operator we use intersection or union
if operator=="or": set_func = union
else: set_func = intersection
# range parameter
if record.get('range',None):
opr = "range"
opr_args = []
if range.find("min")>-1: opr_args.append("min")
if range.find("max")>-1: opr_args.append("max")
if record.get('usage',None):
# see if any usage params are sent to field
opr = record.usage.lower().split(':')
opr, opr_args=opr[0], opr[1:]
if opr=="range": # range search
if 'min' in opr_args: lo = min(keys)
else: lo = None
if 'max' in opr_args: hi = max(keys)
else: hi = None
if hi:
setlist = index.items(lo,hi)
else:
setlist = index.items(lo)
for k, set in setlist:
if type(set) is IntType:
set = IISet((set,))
r = set_func(r, set)
else: # not a range search
for key in keys:
set=index.get(key, None)
if set is not None:
if type(set) is IntType:
set = IISet((set,))
r = set_func(r, set)
if type(r) is IntType: r=IISet((r,))
if r is None:
return IISet(), (self.id,)
else:
return r, (self.id,)
def numObjects( self ):
""" How many objects are in the index? """
return len( self._unindex )
def _convert( self, value, default=None ):
"""Convert Date/Time value to our internal representation"""
if isinstance( value, DateTime ):
t_tup = value.parts()
elif type( value ) is FloatType:
t_tup = time.gmtime( value )
elif type( value ) is StringType:
t_obj = DateTime( value )
t_tup = t_obj.parts()
else:
return default
yr = t_tup[0]
mo = t_tup[1]
dy = t_tup[2]
hr = t_tup[3]
mn = t_tup[4]
t_val = ( ( ( ( yr * 12 + mo ) * 31 + dy ) * 24 + hr ) * 60 + mn )
return t_val
manage_addDateIndexForm = DTMLFile( 'dtml/addDateIndex', globals() )
def manage_addDateIndex( self, id, REQUEST=None, RESPONSE=None, URL3=None):
"""Add a Date index"""
return self.manage_addIndex(id, 'DateIndex', extra=None, \
REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3)
=== Added File Zope/lib/python/Products/PluginIndexes/DateIndexes/DateRangeIndex.py ===
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from OFS.SimpleItem import SimpleItem
from BTrees.IOBTree import IOBTree
from BTrees.IIBTree import IISet, union, intersection
from Globals import package_home, DTMLFile, InitializeClass
from AccessControl import ClassSecurityInfo
from DateTime.DateTime import DateTime
import os
_dtmldir = os.path.join( package_home( globals() ), 'dtml' )
VIEW_PERMISSION = 'View'
INDEX_MGMT_PERMISSION = 'Manage ZCatalogIndex Entries'
class DateRangeIndex( PluggableIndex.PluggableIndex, SimpleItem ):
"""
Index a date range, such as the canonical "effective-expiration"
range in the CMF. Any object may return None for either the
start or the end date: for the start date, this should be
the logical equivalent of "since the beginning of time"; for the
end date, "until the end of time".
Therefore, divide the space of indexed objects into four containers:
- Objects which always match ( i.e., they returned None for both );
- Objects which match after a given time ( i.e., they returned None
for the end date );
- Objects which match until a given time ( i.e., they returned None
for the start date );
- Objects which match only during a specific interval.
"""
__implements__ = ( PluggableIndex.PluggableIndexInterface, )
security = ClassSecurityInfo()
meta_type = "DateRangeIndex"
manage_options= ( { 'label' : 'Properties'
, 'action' : 'manage_indexProperties'
}
,
)
since_field = until_field = None
def __init__(self, id, since_field=None, until_field=None,
caller=None, extra=None):
if extra:
since_field = extra.since_field
until_field = extra.until_field
self._setId(id)
self._edit(since_field, until_field)
self.clear()
security.declareProtected( VIEW_PERMISSION
, 'getSinceField'
)
def getSinceField( self ):
"""
"""
return self._since_field
security.declareProtected( VIEW_PERMISSION
, 'getUntilField'
)
def getUntilField( self ):
"""
"""
return self._until_field
manage_indexProperties = DTMLFile( 'dri_properties', _dtmldir )
security.declareProtected( INDEX_MGMT_PERMISSION
, 'manage_edit'
)
def manage_edit( self, since_field, until_field, REQUEST ):
"""
"""
self._edit( since_field, until_field )
REQUEST[ 'RESPONSE' ].redirect( '%s/manage_main'
'?manage_tabs_message=Updated'
% REQUEST.get('URL2')
)
security.declarePrivate( '_edit' )
def _edit( self, since_field, until_field ):
"""
Update the fields used to compute the range.
"""
self._since_field = since_field
self._until_field = until_field
security.declareProtected( INDEX_MGMT_PERMISSION
, 'clear'
)
def clear( self ):
"""
Start over fresh.
"""
self._always = IISet() # XXX is this the right one?
self._since_only = IOBTree()
self._until_only = IOBTree()
self._since = IOBTree()
self._until = IOBTree()
self._unindex = IOBTree() # 'datum' will be a tuple of date ints
#
# PluggableIndexInterface implementation (XXX inherit assertions?)
#
def getEntryForObject( self, documentId, default=None ):
"""
Get all information contained for the specific object
identified by 'documentId'. Return 'default' if not found.
"""
return self._unindex.get( documentId, default )
def index_object( self, documentId, obj, threshold=None ):
"""
Index an object:
- 'documentId' is the integer ID of the document
- 'obj' is the object to be indexed
- ignore threshold
"""
if self._since_field is None:
return 0
since = getattr( obj, self._since_field, None )
if callable( since ):
since = since()
since = self._convertDateTime( since )
until = getattr( obj, self._until_field, None )
if callable( until ):
until = until()
until = self._convertDateTime( until )
datum = ( since, until )
old_datum = self._unindex.get( documentId, None )
if datum == old_datum: # No change? bail out!
return 0
if old_datum is not None:
old_since, old_until = old_datum
self._removeForwardIndexEntry( old_since, old_until, documentId )
self._insertForwardIndexEntry( since, until, documentId )
self._unindex[ documentId ] = datum
return 1
def unindex_object( self, documentId ):
"""
Remove the object corresponding to 'documentId' from the index.
"""
datum = self._unindex.get( documentId, None )
if datum is None:
return
since, until = datum
self._removeForwardIndexEntry( since, until, documentId )
del self._unindex[ documentId ]
def uniqueValues( self, name=None, withLengths=0 ):
"""
Return a list of unique values for 'name'.
If 'withLengths' is true, return a sequence of tuples, in
the form '( value, length )'.
"""
if not name in ( self._since_field, self._until_field ):
return []
if name == self._since_field:
t1 = self._since
t2 = self._since_only
else:
t1 = self._until
t2 = self._until_only
result = []
IntType = type( 0 )
if not withValues:
result.extend( t1.keys() )
result.extend( t2.keys() )
else:
for key in t1.keys():
set = t1[ key ]
if type( set ) is IntType:
length = 1
else:
length = len( set )
result.append( ( key, length) )
for key in t2.keys():
set = t2[ key ]
if type( set ) is IntType:
length = 1
else:
length = len( set )
result.append( ( key, length) )
return tuple( result )
def _apply_index( self, request, cid='' ):
"""
Apply the index to query parameters given in 'request', which
should be a mapping object.
If the request does not contain the needed parametrs, then
return None.
If the request contains a parameter with the name of the
column + "_usage", snif for information on how to handle
applying the index.
Otherwise return two objects. The first object is a ResultSet
containing the record numbers of the matching records. The
second object is a tuple containing the names of all data fields
used.
"""
record = parseIndexRequest( request, self.getId() )
if record.keys is None:
return None
term = self._convertDateTime( record.keys[0] )
#
# Aggregate sets for each bucket separately, to avoid
# large-small union penalties.
#
until_only = IISet()
# XXX use multi-union
map( until_only.update, self._until_only.values( term ) )
since_only = IISet()
# XXX use multi-union
map( since_only.update, self._since_only.values( None, term ) )
until = IISet()
# XXX use multi-union
map( until.update, self._until.values( term ) )
since = IISet()
# XXX use multi-union
map( since.update, self._since.values( None, term ) )
bounded = intersection( until, since )
result = union( self._always, until_only )
result = union( result, since_only )
result = union( result, bounded )
return result, ( self._since_field, self._until_field )
#
# ZCatalog needs this, although it isn't (yet) part of the interface.
#
security.declareProtected( VIEW_PERMISSION , 'numObjects' )
def numObjects( self ):
"""
"""
return len( self._unindex )
#
# Helper functions.
#
def _insertForwardIndexEntry( self, since, until, documentId ):
"""
Insert 'documentId' into the appropriate set based on
'datum'.
"""
if since is None and until is None:
self._always.insert( documentId )
elif since is None:
set = self._until_only.get( until, None )
if set is None:
set = self._until_only[ until ] = IISet() # XXX: Store an int?
set.insert( documentId )
elif until is None:
set = self._since_only.get( since, None )
if set is None:
set = self._since_only[ since ] = IISet() # XXX: Store an int?
set.insert( documentId )
else:
set = self._since.get( since, None )
if set is None:
set = self._since[ since ] = IISet() # XXX: Store an int?
set.insert( documentId )
set = self._until.get( until, None )
if set is None:
set = self._until[ until ] = IISet() # XXX: Store an int?
set.insert( documentId )
def _removeForwardIndexEntry( self, since, until, documentId ):
"""
Remove 'documentId' from the appropriate set based on
'datum'.
"""
if since is None and until is None:
self._always.remove( documentId )
elif since is None:
set = self._until_only.get( until, None )
if set is not None:
set.remove( documentId )
if not set:
del self._until_only[ until ]
elif until is None:
set = self._since_only.get( since, None )
if set is not None:
set.remove( documentId )
if not set:
del self._since_only[ since ]
else:
set = self._since.get( since, None )
if set is not None:
set.remove( documentId )
if not set:
del self._since[ since ]
set = self._until.get( until, None )
if set is not None:
set.remove( documentId )
if not set:
del self._until[ until ]
def _convertDateTime( self, value ):
if value is None:
return value
if type( value ) == type( '' ):
dt_obj = DateTime( value )
value = dt_obj.millis() / 1000 / 60 # flatten to minutes
if isinstance( value, DateTime ):
value = value.millis() / 1000 / 60 # flatten to minutes
return int( value )
InitializeClass( DateRangeIndex )
addDateRangeIndexForm = DTMLFile( 'dri_add', _dtmldir )
def addDateRangeIndex(self, id, extra=None,
REQUEST=None, RESPONSE=None, URL3=None):
"""
Add a date range index to the catalog, using the incredibly icky
double-indirection-which-hides-NOTHING.
"""
return self.manage_addIndex(id, 'DateRangeIndex', extra,
REQUEST, RESPONSE, URL3)
=== Added File Zope/lib/python/Products/PluginIndexes/DateIndexes/__init__.py ===
"""
Implement specialized index for date-range queries.
"""
import DateRangeIndex
import DateIndex
from DateTime.DateTime import DateTime
DRI_CTORS = ( ( 'manage_addDateRangeIndexForm'
, DateRangeIndex.addDateRangeIndexForm
)
, DateRangeIndex.addDateRangeIndex
)
def initialize( context ):
context.registerClass( DateRangeIndex.DateRangeIndex
, constructors=DRI_CTORS
, permission='Add Pluggable Index'
, icon='www/index.gif'
, visibility=None
)
context.registerClass( DateIndex.DateIndex
, permission='Add Pluggable Index'
, constructors=( DateIndex.manage_addDateIndexForm
, DateIndex.manage_addDateIndex
)
, icon='www/index.gif'
, visibility=None
)