source: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/catalog.txt @ 11962

Last change on this file since 11962 was 11949, checked in by Henrik Bettermann, 10 years ago

Change of name.

File size: 7.1 KB
Line 
1:mod:`waeup.ikoba.catalog` -- Cataloging support Ikoba
2****************************************************
3
4.. module:: waeup.ikoba.catalog
5
6Components that support cataloging and searching objects inside a
7Ikoba site.
8
9.. :doctest:
10.. :layer: waeup.ikoba.testing.IkobaUnitTestLayer
11
12.. contents::
13
14Classes
15=======
16
17:class:`IkobaQuery`
18------------------
19
20.. class:: IkobaQuery()
21
22   .. attribute:: grok.implements(hurry.query.interfaces.IQuery)
23
24   A `hurry.query.query.Query` compatible query that also supports
25   retrival of plain ``Bree`` result sets as used inside a catalog.
26
27   Like `hurry.query.query.Query` objects, `IkobaQuery` is some kind
28   of a meta query or 'compound query' that can give the cataloged
29   objects (or their int ids) matching one or more 'subqueries'.
30
31   This way you can search for objects (or their int ids) that match
32   several criteria at the same time. See ``examples`` section below.
33
34   A singleton instance of this class is also available as global
35   utility.
36
37   .. method:: searchResults(query)
38
39     Get the cataloged objects determined by ``query``.
40
41   .. method:: apply(query)
42
43     Get the list of int ids (a `BTree` result set) for objects
44     determined by ``query``.
45
46     The list of int ids is less expensive to compute than the
47     complete search results and sufficient, for instance, when you
48     only need the number of objects that match a query and not the
49     objects themselves.
50
51Examples
52========
53
54Getting a general query object
55------------------------------
56
57We can get a IkobaQuery object by asking for an unnamed global utility
58implementing `hurry.query.interfaces.IQuery`:
59
60    >>> from hurry.query.interfaces import IQuery
61    >>> from zope.component import getUtility
62    >>> q = getUtility(IQuery)
63    >>> q
64    <waeup.ikoba.catalog.IkobaQuery object at 0x...>
65
66This query can get 'subqueries' and delivers the objects found or
67their ids. To show this we have to setup a catalog with some entries.
68
69
70Setting up a catalog and feeding it
71-----------------------------------
72
73    >>> from zope.catalog.interfaces import ICatalog
74    >>> from zope.catalog.catalog import Catalog
75    >>> mycat = Catalog()
76
77We register this catalog with the component architechture as a utility
78named 'mycatalog':
79
80    >>> from zope.component import provideUtility
81    >>> provideUtility(mycat, ICatalog, 'mycatalog')
82
83We setup a special content type whose instances we will catalog later:
84
85    >>> from zope.interface import Interface, Attribute, implements
86    >>> from zope.container.contained import Contained
87    >>> class IMammoth(Interface):
88    ...   name = Attribute('name')
89    ...   age = Attribute('age')
90
91    >>> class Mammoth(Contained):
92    ...   implements(IMammoth)
93    ...   def __init__(self, name, age):
94    ...     self.name = name
95    ...     self.age = age
96    ...   def __cmp__(self, other):
97    ...     return cmp(self.name, other.name)
98
99By including the __cmp__ method we make sure search results can be
100stably sorted.
101
102We also setup a `zope.intid.interfaces.IIntIds` utility. This is not
103necessary for plain catalogs, but when we want to use IkobaQuery (or
104`hurry.query.query.Query` objects), as to get a unique mapping from
105objects (stored in ZODB) to integer numbers (stored in catalogs),
106these query objects lookup a global IIntIds utiliy:
107
108    >>> from zope import interface
109    >>> import zope.intid.interfaces
110    >>> class DummyIntId(object):
111    ...     interface.implements(zope.intid.interfaces.IIntIds)
112    ...     MARKER = '__dummy_int_id__'
113    ...     def __init__(self):
114    ...         self.counter = 0
115    ...         self.data = {}
116    ...     def register(self, obj):
117    ...         intid = getattr(obj, self.MARKER, None)
118    ...         if intid is None:
119    ...             setattr(obj, self.MARKER, self.counter)
120    ...             self.data[self.counter] = obj
121    ...             intid = self.counter
122    ...             self.counter += 1
123    ...         return intid
124    ...     def getObject(self, intid):
125    ...         return self.data[intid]
126    ...     def __iter__(self):
127    ...         return iter(self.data)
128    >>> intid = DummyIntId()
129    >>> from zope.component import provideUtility
130    >>> provideUtility(intid, zope.intid.interfaces.IIntIds)
131
132Now we can catalog some mammoths. Here we create a herd and catalog
133each item of it:
134
135    >>> from zope.catalog.field import FieldIndex
136    >>> mycat['mammoth_name'] = FieldIndex('name', IMammoth)
137    >>> mycat['mammoth_age'] = FieldIndex('age', IMammoth)
138
139    >>> herd = [
140    ...   Mammoth(name='Fred', age=33),
141    ...   Mammoth(name='Hank', age=30),
142    ...   Mammoth(name='Wilma', age=28),
143    ... ]
144
145    >>> for mammoth in herd:
146    ...   mycat.index_doc(intid.register(mammoth), mammoth)
147
148
149Searching for result sets
150-------------------------
151
152Finally we can perform queries:
153
154    >>> from hurry.query import Eq
155    >>> from zope.component import getUtility
156    >>> subquery1 = Eq(('mycatalog', 'mammoth_name'), 'Fred')
157
158The latter means: search for objects whose name is ``'Fred'`` in the
159``mammoth_name`` index of a catalog registered as a utility named
160``mycatalog``.
161
162    >>> from hurry.query import Between
163    >>> subquery2 = Between(('mycatalog', 'mammoth_age'), 30, 33)
164
165This means: ask for objects cataloged in an index named 'mammoth_age',
166whose cataloged value is between 30 and 33 (including this values).
167
168    >>> r1 = q.apply(subquery2)
169    >>> r1
170    IFSet([0, 1])
171
172Using ``apply()`` above, we get a set of values stored in an
173``IFBTree``:
174
175    >>> type(r1)
176    <type 'BTrees.IFBTree.IFSet'>
177
178``IFBTree`` objects implement a rather efficient integer to float
179mapping where also integers are allowed as values. For each object
180found (i.e. mammoths whose age is between 30 and 33), we get the
181number of its entry.
182
183To get the real object, we can use intids here, because we setup an
184appropriate IIntIds utility before:
185
186    >>> [intid.getObject(x).name for x in r1]
187    ['Fred', 'Hank']
188
189We can (and should) also use the `searchResults()` method explained
190below to do that.
191
192Retrieving BTree sets can, however, make sense, if you want to know
193only the number of results for a particular query or whether there are
194results at all in a more efficient way:
195
196    >>> len(r1)
197    2
198
199Searching for objects
200---------------------
201
202Very often we don't want to know the catalog-internal 'ids' of
203searched objects but the objects themselves.
204
205This can be done by using the ``searchResults`` method of
206``IkobaQuery``:
207
208    >>> r2 = q.searchResults(subquery1)
209    >>> r2
210    <zope.catalog.catalog.ResultSet instance at 0x...>
211
212    >>> list(r2)
213    [<Mammoth object at 0x...>]
214
215We got one result item, we can immediately ask for further infos. To
216access a result item by its index number, we have to turn the
217ResultSet into an ordinary list before:
218
219    >>> entry = list(r2)[0]
220    >>> entry.name, entry.age
221    ('Fred', 33)
222
223We can also use ``subquery2`` as above:
224
225    >>> r3 = q.searchResults(subquery2)
226    >>> [(x.name, x.age) for x in r3]
227    [('Fred', 33), ('Hank', 30)]
228
229or use both queries at once:
230
231    >>> r4 = q.searchResults(subquery1 & subquery2)
232    >>> [(x.name, x.age) for x in r4]
233    [('Fred', 33)]
234
235which will give us, of course, the same result set as with subquery1.
Note: See TracBrowser for help on using the repository browser.