Index.py 9.1 KB
Newer Older
's avatar
committed
1
##############################################################################
Jim Fulton's avatar
Jim Fulton committed
2 3
# 
# Zope Public License (ZPL) Version 0.9.7
's avatar
committed
4 5 6 7
# ---------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
Jim Fulton's avatar
Jim Fulton committed
8 9 10 11 12
# This license has been certified as Open Source(tm).
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
's avatar
committed
13
# 
Jim Fulton's avatar
Jim Fulton committed
14 15
# 1. Redistributions in source code must retain the above copyright
#    notice, this list of conditions, and the following disclaimer.
's avatar
committed
16
# 
Jim Fulton's avatar
Jim Fulton committed
17 18 19 20
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
's avatar
committed
21
# 
Jim Fulton's avatar
Jim Fulton committed
22 23 24 25 26 27 28
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
's avatar
committed
29
# 
Jim Fulton's avatar
Jim Fulton committed
30 31 32
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
's avatar
committed
33
# 
Jim Fulton's avatar
Jim Fulton committed
34 35
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
's avatar
committed
36 37
#      (http://www.zope.org/)."
# 
Jim Fulton's avatar
Jim Fulton committed
38 39 40
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
's avatar
committed
41
# 
Jim Fulton's avatar
Jim Fulton committed
42 43 44
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
's avatar
committed
45
# 
Jim Fulton's avatar
Jim Fulton committed
46 47 48 49 50
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
's avatar
committed
51 52
#      (http://www.zope.org/)."
# 
Jim Fulton's avatar
Jim Fulton committed
53 54 55 56 57 58 59 60 61 62
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
's avatar
committed
63 64 65
# 
# Disclaimer
# 
Jim Fulton's avatar
Jim Fulton committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
's avatar
committed
83 84 85 86
# 
##############################################################################

"""Simple column indices"""
87
__version__='$Revision: 1.19 $'[11:-2]
Jim Fulton's avatar
Jim Fulton committed
88

89
from Globals import Persistent
Jim Fulton's avatar
Jim Fulton committed
90
from BTree import BTree
Jim Fulton's avatar
Jim Fulton committed
91 92
from intSet import intSet
import operator
93
from Missing import MV
Jeffrey Shell's avatar
Jeffrey Shell committed
94
import string
Jim Fulton's avatar
Jim Fulton committed
95 96

ListType=type([])
97 98 99 100 101
StringType=type('s')

def nonEmpty(s):
    "returns true if a non-empty string or any other (nonstring) type"
    if type(s) is StringType:
's avatar
committed
102 103
        if s: return 1
        else: return 0
104
    else:
's avatar
committed
105
        return 1
Jim Fulton's avatar
Jim Fulton committed
106

107
class Index(Persistent):
Jim Fulton's avatar
Jim Fulton committed
108 109
    """Index object interface"""

110
    def __init__(self,data,schema,id):
's avatar
committed
111
        """Create an index
Jim Fulton's avatar
Jim Fulton committed
112

's avatar
committed
113
        The arguments are:
Jim Fulton's avatar
Jim Fulton committed
114

's avatar
committed
115
          'data' -- a mapping from integer object ids to objects or records,
Jim Fulton's avatar
Jim Fulton committed
116

's avatar
committed
117
          'schema' -- a mapping from item name to index into data records.
Jim Fulton's avatar
Jim Fulton committed
118 119
              If 'data' is a mapping to objects, then schema should ne 'None'.

's avatar
committed
120 121 122 123 124 125 126 127 128
          'id' -- the name of the item attribute to index.  This is either
              an attribute name or a record key.
        """
        self._data=data
        self._schema=schema
        self.id=id
        self._index=BTree()
        
        self._reindex()
Jim Fulton's avatar
Jim Fulton committed
129

130 131 132
    # for b/w compatability
    _init = __init__

133
    def dpHasUniqueValuesFor(self, name):
's avatar
committed
134 135 136 137 138
        ' has unique values for column NAME '
        if name == self.id:
            return 1
        else:
            return 0
139 140

    def dpUniqueValues(self, name=None, withLengths=0):
's avatar
committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
        """\
        returns the unique values for name

        if withLengths is true, returns a sequence of
        tuples of (value, length)
        """
        if name is None:
            name = self.id
        elif name != self.id:
            return []
        if not withLengths: return tuple(
            filter(nonEmpty,self._index.keys())
            )
        else: 
            rl=[]
            for i in self._index.keys():
                if not nonEmpty(i): continue
                else: rl.append((i, len(self._index[i])))
            return tuple(rl)
160

Jim Fulton's avatar
Jim Fulton committed
161
    def clear(self):
's avatar
committed
162
        self._index=BTree()
Jim Fulton's avatar
Jim Fulton committed
163 164

    def _reindex(self,start=0):
's avatar
committed
165
        """Recompute index data for data with ids >= start."""
Jim Fulton's avatar
Jim Fulton committed
166

's avatar
committed
167 168 169 170
        index=self._index
        get=index.get
        
        if not start: index.clear()
Jim Fulton's avatar
Jim Fulton committed
171

's avatar
committed
172 173 174 175 176 177
        id=self.id
        if self._schema is None:
            f=getattr
        else:
            f=operator.__getitem__
            id=self._schema[id]
Jim Fulton's avatar
Jim Fulton committed
178

's avatar
committed
179 180
        for i,row in self._data.items(start):
            k=f(row,id)
181

's avatar
committed
182
            if k is None or k == MV: continue
Jim Fulton's avatar
Jim Fulton committed
183

's avatar
committed
184 185 186
            set=get(k)
            if set is None: index[k]=set=intSet()
            set.insert(i)
Jim Fulton's avatar
Jim Fulton committed
187 188

    def index_item(self,i):
's avatar
committed
189
        """Recompute index data for data with ids >= start."""
Jim Fulton's avatar
Jim Fulton committed
190

's avatar
committed
191
        index=self._index
Jim Fulton's avatar
Jim Fulton committed
192

's avatar
committed
193 194 195 196 197 198
        id=self.id
        if self._schema is None:
            f=getattr
        else:
            f=operator.__getitem__
            id=self._schema[id]
Jim Fulton's avatar
Jim Fulton committed
199

's avatar
committed
200 201
        row=self._data[i]
        k=f(row,id)
202

's avatar
committed
203
        if k is None or k == MV: return
204

's avatar
committed
205 206 207
        set=index.get(k)
        if set is None: index[k]=set=intSet()
        set.insert(i)
Jim Fulton's avatar
Jim Fulton committed
208 209

    def unindex_item(self,i):
's avatar
committed
210
        """Recompute index data for data with ids >= start."""
Jim Fulton's avatar
Jim Fulton committed
211

's avatar
committed
212
        index=self._index
Jim Fulton's avatar
Jim Fulton committed
213

's avatar
committed
214 215 216 217 218 219
        id=self.id
        if self._schema is None:
            f=getattr
        else:
            f=operator.__getitem__
            id=self._schema[id]
Jim Fulton's avatar
Jim Fulton committed
220

's avatar
committed
221 222 223 224 225
        row=self._data[i]
        k=f(row,id)
        
        set=index.get(k)
        if set is not None: set.remove(i)
Jim Fulton's avatar
Jim Fulton committed
226

227
    def _apply_index(self, request, cid=''):
's avatar
committed
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
        """Apply the index to query parameters given in the argument, request

        The argument should be a mapping object.

        If the request does not contain the needed parameters, then None is
        returned.

        If the request contains a parameter with the name of the column
        + '_usage', it is sniffed for information on how to handle applying
        the index.

        Otherwise two objects are returned.  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.

        """
        id=self.id              #name of the column

        cidid="%s/%s" % (cid,id)
        has_key=request.has_key
        if has_key(cidid): keys=request[cidid]
        elif has_key(id): keys=request[id]
        else: return None

        if type(keys) is not ListType: keys=[keys]
        index=self._index
        r=None
        anyTrue=0
        opr=None

        if request.has_key(id+'_usage'):
            # see if any usage params are sent to field
            opr=string.split(string.lower(request[id+"_usage"]),':')
            opr, opr_args=opr[0], opr[1:]

        if opr=="range":
            if 'min' in opr_args: lo=min(keys)
            else: lo=None
            if 'max' in opr_args: hi=max(keys)
            else: hi=None

            anyTrue=1
            try:
                if hi: setlist=index.items(lo,hi)
                else:  setlist=index.items(lo)
                for k,set in setlist:
                    if r is None: r=set
                    else: r=r.union(set)
            except KeyError: pass
        else:           #not a range
            get=index.get
            for key in keys:
                if key: anyTrue=1
                set=get(key)
                if set is not None:
                    if r is None: r=set
                    else: r = r.union(set)

        if r is None:
            if anyTrue: r=intSet()
            else: return None

        return r, (id,)